font/sfnt: implement Font.Kern.

Change-Id: I9ffb93dd3ee08b8871dadf5bc36578710b800199
Reviewed-on: https://go-review.googlesource.com/37410
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
index 25574ba..b534a5b 100644
--- a/font/sfnt/proprietary_test.go
+++ b/font/sfnt/proprietary_test.go
@@ -31,11 +31,13 @@
 // TODO: enable Apple/Microsoft tests by default on Darwin/Windows?
 
 import (
+	"errors"
 	"flag"
 	"io/ioutil"
 	"path/filepath"
 	"testing"
 
+	"golang.org/x/image/font"
 	"golang.org/x/image/math/fixed"
 )
 
@@ -134,7 +136,7 @@
 	if err != nil {
 		t.Fatalf("Parse: %v", err)
 	}
-	ppem := fixed.Int26_6(f.UnitsPerEm()) << 6
+	ppem := fixed.Int26_6(f.UnitsPerEm())
 	qualifiedFilename := proprietor + "/" + filename
 	var buf Buffer
 
@@ -183,6 +185,33 @@
 			continue
 		}
 	}
+
+kernLoop:
+	for _, tc := range proprietaryKernTestCases[qualifiedFilename] {
+		var indexes [2]GlyphIndex
+		for i := range indexes {
+			x, err := f.GlyphIndex(&buf, tc.runes[i])
+			if x == 0 && err == nil {
+				err = errors.New("no glyph index found")
+			}
+			if err != nil {
+				t.Errorf("GlyphIndex(%q): %v", tc.runes[0], err)
+				continue kernLoop
+			}
+			indexes[i] = x
+		}
+		kern, err := f.Kern(&buf, indexes[0], indexes[1], tc.ppem, tc.hinting)
+		if err != nil {
+			t.Errorf("Kern(%q, %q, ppem=%d, hinting=%v): %v",
+				tc.runes[0], tc.runes[1], tc.ppem, tc.hinting, err)
+			continue
+		}
+		if got := Units(kern); got != tc.want {
+			t.Errorf("Kern(%q, %q, ppem=%d, hinting=%v): got %d, want %d",
+				tc.runes[0], tc.runes[1], tc.ppem, tc.hinting, got, tc.want)
+			continue
+		}
+	}
 }
 
 // proprietaryVersions holds the expected version string of each proprietary
@@ -282,3 +311,45 @@
 		'\uf042': 37, // PRIVATE USE AREA
 	},
 }
+
+type kernTestCase struct {
+	ppem    fixed.Int26_6
+	hinting font.Hinting
+	runes   [2]rune
+	want    Units
+}
+
+// proprietaryKernTestCases hold a sample of each font's kerning pairs. The
+// numerical values can be verified by running the ttx tool.
+var proprietaryKernTestCases = map[string][]kernTestCase{
+	"microsoft/Arial.ttf": {
+		{2048, font.HintingNone, [2]rune{'A', 'V'}, -152},
+		// U+03B8 GREEK SMALL LETTER THETA
+		// U+03BB GREEK SMALL LETTER LAMDA
+		{2048, font.HintingNone, [2]rune{'\u03b8', '\u03bb'}, -39},
+		{2048, font.HintingNone, [2]rune{'\u03bb', '\u03b8'}, -0},
+	},
+	"microsoft/Comic_Sans_MS.ttf": {
+		{2048, font.HintingNone, [2]rune{'A', 'V'}, 0},
+	},
+	"microsoft/Times_New_Roman.ttf": {
+		{768, font.HintingNone, [2]rune{'A', 'V'}, -99},
+		{768, font.HintingFull, [2]rune{'A', 'V'}, -128},
+		{2048, font.HintingNone, [2]rune{'A', 'A'}, 0},
+		{2048, font.HintingNone, [2]rune{'A', 'T'}, -227},
+		{2048, font.HintingNone, [2]rune{'A', 'V'}, -264},
+		{2048, font.HintingNone, [2]rune{'T', 'A'}, -164},
+		{2048, font.HintingNone, [2]rune{'T', 'T'}, 0},
+		{2048, font.HintingNone, [2]rune{'T', 'V'}, 0},
+		{2048, font.HintingNone, [2]rune{'V', 'A'}, -264},
+		{2048, font.HintingNone, [2]rune{'V', 'T'}, 0},
+		{2048, font.HintingNone, [2]rune{'V', 'V'}, 0},
+		// U+0390 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+		// U+0393 GREEK CAPITAL LETTER GAMMA
+		{2048, font.HintingNone, [2]rune{'\u0390', '\u0393'}, 0},
+		{2048, font.HintingNone, [2]rune{'\u0393', '\u0390'}, 76},
+	},
+	"microsoft/Webdings.ttf": {
+		{2048, font.HintingNone, [2]rune{'\uf041', '\uf042'}, 0},
+	},
+}
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index b181688..79a9d14 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -23,6 +23,7 @@
 	"errors"
 	"io"
 
+	"golang.org/x/image/font"
 	"golang.org/x/image/math/fixed"
 	"golang.org/x/text/encoding/charmap"
 )
@@ -63,6 +64,7 @@
 	errInvalidCmapTable     = errors.New("sfnt: invalid cmap table")
 	errInvalidGlyphData     = errors.New("sfnt: invalid glyph data")
 	errInvalidHeadTable     = errors.New("sfnt: invalid head table")
+	errInvalidKernTable     = errors.New("sfnt: invalid kern table")
 	errInvalidLocaTable     = errors.New("sfnt: invalid loca table")
 	errInvalidLocationData  = errors.New("sfnt: invalid location data")
 	errInvalidMaxpTable     = errors.New("sfnt: invalid maxp table")
@@ -78,6 +80,7 @@
 	errUnsupportedCmapEncodings        = errors.New("sfnt: unsupported cmap encodings")
 	errUnsupportedCompoundGlyph        = errors.New("sfnt: unsupported compound glyph")
 	errUnsupportedGlyphDataLength      = errors.New("sfnt: unsupported glyph data length")
+	errUnsupportedKernTable            = errors.New("sfnt: unsupported kern table")
 	errUnsupportedRealNumberEncoding   = errors.New("sfnt: unsupported real number encoding")
 	errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
 	errUnsupportedNumberOfHints        = errors.New("sfnt: unsupported number of hints")
@@ -286,11 +289,17 @@
 // The Font methods that don't take a *Buffer argument are always safe to call
 // concurrently.
 //
-// Some methods provide lengths or co-ordinates, e.g. bounds, font metrics and
+// Some methods provide lengths or coordinates, e.g. bounds, font metrics and
 // control points. All of these methods take a ppem parameter, which is the
 // number of pixels in 1 em, expressed as a 26.6 fixed point value. For
 // example, if 1 em is 10 pixels then ppem is fixed.I(10), which equals
 // fixed.Int26_6(10 << 6).
+//
+// To get those lengths or coordinates in terms of font units instead of
+// pixels, use ppem = fixed.Int26_6(f.UnitsPerEm()) and if those methods take a
+// font.Hinting parameter, use font.HintingNone. The return values will have
+// type fixed.Int26_6, but those numbers can be converted back to Units with no
+// further scaling necessary.
 type Font struct {
 	src source
 
@@ -327,12 +336,15 @@
 	// https://www.microsoft.com/typography/otspec/otff.htm#otttables
 	// "Other OpenType Tables".
 	//
-	// TODO: hdmx, kern, vmtx? Others?
+	// TODO: hdmx, vmtx? Others?
+	kern table
 
 	cached struct {
 		glyphIndex       func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
 		indexToLocFormat bool // false means short, true means long.
 		isPostScript     bool
+		kernNumPairs     int32
+		kernOffset       int32
 		postTableVersion uint32
 		unitsPerEm       Units
 
@@ -375,6 +387,10 @@
 	if err != nil {
 		return err
 	}
+	buf, err = f.parseKern(buf)
+	if err != nil {
+		return err
+	}
 	buf, err = f.parsePost(buf)
 	if err != nil {
 		return err
@@ -444,6 +460,8 @@
 			f.hhea = table{o, n}
 		case 0x686d7478:
 			f.hmtx = table{o, n}
+		case 0x6b65726e:
+			f.kern = table{o, n}
 		case 0x6c6f6361:
 			f.loca = table{o, n}
 		case 0x6d617870:
@@ -542,6 +560,90 @@
 	return buf, nil
 }
 
+func (f *Font) parseKern(buf []byte) ([]byte, error) {
+	// https://www.microsoft.com/typography/otspec/kern.htm
+
+	if f.kern.length == 0 {
+		return buf, nil
+	}
+	const headerSize = 4
+	if f.kern.length < headerSize {
+		return nil, errInvalidKernTable
+	}
+	buf, err := f.src.view(buf, int(f.kern.offset), headerSize)
+	if err != nil {
+		return nil, err
+	}
+	offset := int(f.kern.offset) + headerSize
+	length := int(f.kern.length) - headerSize
+
+	switch version := u16(buf); version {
+	case 0:
+		// TODO: support numTables != 1. Testing that requires finding such a font.
+		if numTables := int(u16(buf[2:])); numTables != 1 {
+			return nil, errUnsupportedKernTable
+		}
+		return f.parseKernVersion0(buf, offset, length)
+	case 1:
+		// TODO: find such a (proprietary?) font, and support it. Both of
+		// https://www.microsoft.com/typography/otspec/kern.htm
+		// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
+		// say that such fonts work on Mac OS but not on Windows.
+	}
+	return nil, errUnsupportedKernTable
+}
+
+func (f *Font) parseKernVersion0(buf []byte, offset, length int) ([]byte, error) {
+	const headerSize = 6
+	if length < headerSize {
+		return nil, errInvalidKernTable
+	}
+	buf, err := f.src.view(buf, offset, headerSize)
+	if err != nil {
+		return nil, err
+	}
+	if version := u16(buf); version != 0 {
+		return nil, errUnsupportedKernTable
+	}
+	subtableLength := int(u16(buf[2:]))
+	if subtableLength < headerSize || length < subtableLength {
+		return nil, errInvalidKernTable
+	}
+	if coverageBits := buf[5]; coverageBits != 0x01 {
+		// We only support horizontal kerning.
+		return nil, errUnsupportedKernTable
+	}
+	offset += headerSize
+	length -= headerSize
+	subtableLength -= headerSize
+
+	switch format := buf[4]; format {
+	case 0:
+		return f.parseKernFormat0(buf, offset, subtableLength)
+	case 2:
+		// TODO: find such a (proprietary?) font, and support it.
+	}
+	return nil, errUnsupportedKernTable
+}
+
+func (f *Font) parseKernFormat0(buf []byte, offset, length int) ([]byte, error) {
+	const headerSize, entrySize = 8, 6
+	if length < headerSize {
+		return nil, errInvalidKernTable
+	}
+	buf, err := f.src.view(buf, offset, headerSize)
+	if err != nil {
+		return nil, err
+	}
+	numPairs := u16(buf)
+	if length != headerSize+entrySize*int(numPairs) {
+		return nil, errInvalidKernTable
+	}
+	f.cached.kernNumPairs = int32(numPairs)
+	f.cached.kernOffset = int32(offset) + headerSize
+	return buf, nil
+}
+
 func (f *Font) parseMaxp(buf []byte) ([]byte, error) {
 	// https://www.microsoft.com/typography/otspec/maxp.htm
 
@@ -763,6 +865,64 @@
 	}
 }
 
+// Kern returns the horizontal adjustment for the kerning pair (x0, x1). A
+// positive kern means to move the glyphs further apart. ppem is the number of
+// pixels in 1 em.
+//
+// It returns ErrNotFound if either glyph index is out of range.
+func (f *Font) Kern(b *Buffer, x0, x1 GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
+	// TODO: how should this work with the GPOS table and CFF fonts?
+	// https://www.microsoft.com/typography/otspec/kern.htm says that
+	// "OpenType™ fonts containing CFF outlines are not supported by the 'kern'
+	// table and must use the 'GPOS' OpenType Layout table."
+
+	if n := f.NumGlyphs(); int(x0) >= n || int(x1) >= n {
+		return 0, ErrNotFound
+	}
+	// Not every font has a kern table. If it doesn't, there's no need to
+	// allocate a Buffer.
+	if f.kern.length == 0 {
+		return 0, nil
+	}
+	if b == nil {
+		b = &Buffer{}
+	}
+
+	key := uint32(x0)<<16 | uint32(x1)
+	lo, hi := int32(0), f.cached.kernNumPairs
+	for lo < hi {
+		i := (lo + hi) / 2
+
+		// TODO: this view call inside the inner loop can lead to many small
+		// reads instead of fewer larger reads, which can be expensive. We
+		// should be able to do better, although we don't want to make (one)
+		// arbitrarily large read. Perhaps we should round up reads to 4K or 8K
+		// chunks. For reference, Arial.ttf's kern table is 5472 bytes.
+		// Times_New_Roman.ttf's kern table is 5220 bytes.
+		const entrySize = 6
+		buf, err := b.view(&f.src, int(f.cached.kernOffset+i*entrySize), entrySize)
+		if err != nil {
+			return 0, err
+		}
+
+		k := u32(buf)
+		if k < key {
+			lo = i + 1
+		} else if k > key {
+			hi = i
+		} else {
+			kern := fixed.Int26_6(int16(u16(buf[4:])))
+			kern = scale(kern*ppem, f.cached.unitsPerEm)
+			if h == font.HintingFull {
+				// Quantize the fixed.Int26_6 value to the nearest pixel.
+				kern = (kern + 32) &^ 63
+			}
+			return kern, nil
+		}
+	}
+	return 0, nil
+}
+
 // Name returns the name value keyed by the given NameID.
 //
 // It returns ErrNotFound if there is no value for that key.
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index 6f6fff9..85d96a9 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -15,69 +15,31 @@
 	"golang.org/x/image/math/fixed"
 )
 
-func moveTo(xa, ya int) Segment {
+func moveTo(xa, ya fixed.Int26_6) Segment {
 	return Segment{
-		Op: SegmentOpMoveTo,
-		Args: [6]fixed.Int26_6{
-			0: fixed.I(xa),
-			1: fixed.I(ya),
-		},
+		Op:   SegmentOpMoveTo,
+		Args: [6]fixed.Int26_6{xa, ya},
 	}
 }
 
-func moveTo26_6(xa, ya fixed.Int26_6) Segment {
+func lineTo(xa, ya fixed.Int26_6) Segment {
 	return Segment{
-		Op: SegmentOpMoveTo,
-		Args: [6]fixed.Int26_6{
-			0: xa,
-			1: ya,
-		},
+		Op:   SegmentOpLineTo,
+		Args: [6]fixed.Int26_6{xa, ya},
 	}
 }
 
-func lineTo(xa, ya int) Segment {
+func quadTo(xa, ya, xb, yb fixed.Int26_6) Segment {
 	return Segment{
-		Op: SegmentOpLineTo,
-		Args: [6]fixed.Int26_6{
-			0: fixed.I(xa),
-			1: fixed.I(ya),
-		},
+		Op:   SegmentOpQuadTo,
+		Args: [6]fixed.Int26_6{xa, ya, xb, yb},
 	}
 }
 
-func lineTo26_6(xa, ya fixed.Int26_6) Segment {
+func cubeTo(xa, ya, xb, yb, xc, yc fixed.Int26_6) Segment {
 	return Segment{
-		Op: SegmentOpLineTo,
-		Args: [6]fixed.Int26_6{
-			0: xa,
-			1: ya,
-		},
-	}
-}
-
-func quadTo(xa, ya, xb, yb int) Segment {
-	return Segment{
-		Op: SegmentOpQuadTo,
-		Args: [6]fixed.Int26_6{
-			0: fixed.I(xa),
-			1: fixed.I(ya),
-			2: fixed.I(xb),
-			3: fixed.I(yb),
-		},
-	}
-}
-
-func cubeTo(xa, ya, xb, yb, xc, yc int) Segment {
-	return Segment{
-		Op: SegmentOpCubeTo,
-		Args: [6]fixed.Int26_6{
-			0: fixed.I(xa),
-			1: fixed.I(ya),
-			2: fixed.I(xb),
-			3: fixed.I(yb),
-			4: fixed.I(xc),
-			5: fixed.I(yc),
-		},
+		Op:   SegmentOpCubeTo,
+		Args: [6]fixed.Int26_6{xa, ya, xb, yb, xc, yc},
 	}
 }
 
@@ -430,7 +392,7 @@
 	if err != nil {
 		t.Fatalf("Parse: %v", err)
 	}
-	ppem := fixed.Int26_6(f.UnitsPerEm()) << 6
+	ppem := fixed.Int26_6(f.UnitsPerEm())
 
 	if ng := f.NumGlyphs(); ng != len(wants) {
 		t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
@@ -481,16 +443,16 @@
 		ppem fixed.Int26_6
 		want []Segment
 	}{{
-		ppem: fixed.I(12),
+		ppem: fixed.Int26_6(12 << 6),
 		want: []Segment{
-			moveTo26_6(77, 0),
-			lineTo26_6(77, 614),
-			lineTo26_6(230, 614),
-			lineTo26_6(230, 0),
-			lineTo26_6(77, 0),
+			moveTo(77, 0),
+			lineTo(77, 614),
+			lineTo(230, 614),
+			lineTo(230, 0),
+			lineTo(77, 0),
 		},
 	}, {
-		ppem: fixed.I(2048),
+		ppem: fixed.Int26_6(2048),
 		want: []Segment{
 			moveTo(205, 0),
 			lineTo(205, 1638),
diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go
index 4111feb..c0eefba 100644
--- a/font/sfnt/truetype.go
+++ b/font/sfnt/truetype.go
@@ -131,12 +131,12 @@
 
 	// For simple (non-compound) glyphs, the remainder of the glyf data
 	// consists of (flags, x, y) points: the Bézier curve segments. These are
-	// stored in columns (all the flags first, then all the x co-ordinates,
-	// then all the y co-ordinates), not rows, as it compresses better.
+	// stored in columns (all the flags first, then all the x coordinates, then
+	// all the y coordinates), not rows, as it compresses better.
 	//
 	// Decoding those points in row order involves two passes. The first pass
 	// determines the indexes (relative to the data slice) of where the flags,
-	// the x co-ordinates and the y co-ordinates each start.
+	// the x coordinates and the y coordinates each start.
 	flagIndex := int32(index)
 	xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
 	if !ok {