font/sfnt: implement Font.GlyphAdvance.

Change-Id: I3e15c6e634d858a87e73221bd9d5a9e3979d674a
Reviewed-on: https://go-review.googlesource.com/39250
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go
index 2f746c4..f166b6c 100644
--- a/font/sfnt/postscript.go
+++ b/font/sfnt/postscript.go
@@ -132,7 +132,7 @@
 	psi psInterpreter
 }
 
-func (p *cffParser) parse(numGlyphs int) (ret glyphData, err error) {
+func (p *cffParser) parse(numGlyphs int32) (ret glyphData, err error) {
 	// Parse the header.
 	{
 		if !p.read(4) {
@@ -236,7 +236,7 @@
 		if !ok {
 			return glyphData{}, p.err
 		}
-		if count == 0 || int(count) != numGlyphs {
+		if count == 0 || int32(count) != numGlyphs {
 			return glyphData{}, errInvalidCFFTable
 		}
 		ret.locations = make([]uint32, count+1)
@@ -312,7 +312,7 @@
 
 // parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section
 // 19 "FDSelect".
-func (p *cffParser) parseFDSelect(offset int32, numGlyphs int) (ret fdSelect, err error) {
+func (p *cffParser) parseFDSelect(offset int32, numGlyphs int32) (ret fdSelect, err error) {
 	if !p.seekFromBase(p.psi.topDict.fdSelect) {
 		return fdSelect{}, errInvalidCFFTable
 	}
@@ -322,7 +322,7 @@
 	ret.format = p.buf[0]
 	switch ret.format {
 	case 0:
-		if p.end-p.offset < numGlyphs {
+		if p.end-p.offset < int(numGlyphs) {
 			return fdSelect{}, errInvalidCFFTable
 		}
 		ret.offset = int32(p.offset)
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 6668606..f177e82 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -75,6 +75,8 @@
 	errInvalidGlyphData       = errors.New("sfnt: invalid glyph data")
 	errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length")
 	errInvalidHeadTable       = errors.New("sfnt: invalid head table")
+	errInvalidHheaTable       = errors.New("sfnt: invalid hhea table")
+	errInvalidHmtxTable       = errors.New("sfnt: invalid hmtx table")
 	errInvalidKernTable       = errors.New("sfnt: invalid kern table")
 	errInvalidLocaTable       = errors.New("sfnt: invalid loca table")
 	errInvalidLocationData    = errors.New("sfnt: invalid location data")
@@ -447,6 +449,7 @@
 		isPostScript     bool
 		kernNumPairs     int32
 		kernOffset       int32
+		numHMetrics      int32
 		postTableVersion uint32
 		unitsPerEm       Units
 	}
@@ -495,6 +498,14 @@
 	if err != nil {
 		return err
 	}
+	buf, numHMetrics, err := f.parseHhea(buf, numGlyphs)
+	if err != nil {
+		return err
+	}
+	buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics)
+	if err != nil {
+		return err
+	}
 	buf, postTableVersion, err := f.parsePost(buf, numGlyphs)
 	if err != nil {
 		return err
@@ -506,6 +517,7 @@
 	f.cached.isPostScript = isPostScript
 	f.cached.kernNumPairs = kernNumPairs
 	f.cached.kernOffset = kernOffset
+	f.cached.numHMetrics = numHMetrics
 	f.cached.postTableVersion = postTableVersion
 	f.cached.unitsPerEm = unitsPerEm
 
@@ -678,6 +690,31 @@
 	return buf, indexToLocFormat, unitsPerEm, nil
 }
 
+func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, numHMetrics int32, err error) {
+	// https://www.microsoft.com/typography/OTSPEC/hhea.htm
+
+	if f.hhea.length != 36 {
+		return nil, 0, errInvalidHheaTable
+	}
+	u, err := f.src.u16(buf, f.hhea, 34)
+	if err != nil {
+		return nil, 0, err
+	}
+	if int32(u) > numGlyphs || u == 0 {
+		return nil, 0, errInvalidHheaTable
+	}
+	return buf, int32(u), nil
+}
+
+func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) {
+	// https://www.microsoft.com/typography/OTSPEC/hmtx.htm
+
+	if f.hmtx.length != uint32(2*numGlyphs+2*numHMetrics) {
+		return nil, errInvalidHmtxTable
+	}
+	return buf, nil
+}
+
 func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
 	// https://www.microsoft.com/typography/otspec/kern.htm
 
@@ -772,7 +809,7 @@
 	return buf, kernNumPairs, int32(offset) + headerSize, nil
 }
 
-func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int, err error) {
+func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int32, err error) {
 	// https://www.microsoft.com/typography/otspec/maxp.htm
 
 	if isPostScript {
@@ -788,7 +825,7 @@
 	if err != nil {
 		return nil, 0, err
 	}
-	return buf, int(u), nil
+	return buf, int32(u), nil
 }
 
 type glyphData struct {
@@ -809,7 +846,7 @@
 	fdSelect fdSelect
 }
 
-func (f *Font) parseGlyphData(buf []byte, numGlyphs int, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, err error) {
+func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, err error) {
 	if isPostScript {
 		p := cffParser{
 			src:    &f.src,
@@ -827,14 +864,14 @@
 			return nil, glyphData{}, err
 		}
 	}
-	if len(ret.locations) != numGlyphs+1 {
+	if len(ret.locations) != int(numGlyphs+1) {
 		return nil, glyphData{}, errInvalidLocationData
 	}
 
 	return buf, ret, nil
 }
 
-func (f *Font) parsePost(buf []byte, numGlyphs int) (buf1 []byte, postTableVersion uint32, err error) {
+func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, postTableVersion uint32, err error) {
 	// https://www.microsoft.com/typography/otspec/post.htm
 
 	const headerSize = 32
@@ -1022,6 +1059,39 @@
 	}
 }
 
+// GlyphAdvance returns the advance width for the x'th glyph. ppem is the
+// number of pixels in 1 em.
+//
+// It returns ErrNotFound if the glyph index is out of range.
+func (f *Font) GlyphAdvance(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
+	if int(x) >= f.NumGlyphs() {
+		return 0, ErrNotFound
+	}
+	if b == nil {
+		b = &Buffer{}
+	}
+
+	// https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an
+	// optimization, the number of records can be less than the number of
+	// glyphs, in which case the advance width value of the last record applies
+	// to all remaining glyph IDs."
+	if n := GlyphIndex(f.cached.numHMetrics - 1); x > n {
+		x = n
+	}
+
+	buf, err := b.view(&f.src, int(f.hmtx.offset)+int(4*x), 2)
+	if err != nil {
+		return 0, err
+	}
+	adv := fixed.Int26_6(u16(buf))
+	adv = scale(adv*ppem, f.cached.unitsPerEm)
+	if h == font.HintingFull {
+		// Quantize the fixed.Int26_6 value to the nearest pixel.
+		adv = (adv + 32) &^ 63
+	}
+	return adv, nil
+}
+
 // 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.
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index 2bb508b..3f4fcd5 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -11,6 +11,9 @@
 	"path/filepath"
 	"testing"
 
+	"golang.org/x/image/font"
+	"golang.org/x/image/font/gofont/gobold"
+	"golang.org/x/image/font/gofont/gomono"
 	"golang.org/x/image/font/gofont/goregular"
 	"golang.org/x/image/math/fixed"
 )
@@ -109,6 +112,74 @@
 	}
 }
 
+func TestGlyphAdvance(t *testing.T) {
+	testCases := map[string][]struct {
+		r    rune
+		want fixed.Int26_6
+	}{
+		"gobold": {
+			{' ', 569},
+			{'A', 1479},
+			{'Á', 1479},
+			{'Æ', 2048},
+			{'i', 592},
+			{'x', 1139},
+		},
+		"gomono": {
+			{' ', 1229},
+			{'A', 1229},
+			{'Á', 1229},
+			{'Æ', 1229},
+			{'i', 1229},
+			{'x', 1229},
+		},
+		"goregular": {
+			{' ', 569},
+			{'A', 1366},
+			{'Á', 1366},
+			{'Æ', 2048},
+			{'i', 505},
+			{'x', 1024},
+		},
+	}
+
+	var b Buffer
+	for name, testCases1 := range testCases {
+		data := []byte(nil)
+		switch name {
+		case "gobold":
+			data = gobold.TTF
+		case "gomono":
+			data = gomono.TTF
+		case "goregular":
+			data = goregular.TTF
+		}
+		f, err := Parse(data)
+		if err != nil {
+			t.Errorf("Parse(%q): %v", name, err)
+			continue
+		}
+		ppem := fixed.Int26_6(f.UnitsPerEm())
+
+		for _, tc := range testCases1 {
+			x, err := f.GlyphIndex(&b, tc.r)
+			if err != nil {
+				t.Errorf("name=%q, r=%q: GlyphIndex: %v", name, tc.r, err)
+				continue
+			}
+			got, err := f.GlyphAdvance(&b, x, ppem, font.HintingNone)
+			if err != nil {
+				t.Errorf("name=%q, r=%q: GlyphAdvance: %v", name, tc.r, err)
+				continue
+			}
+			if got != tc.want {
+				t.Errorf("name=%q, r=%q: GlyphAdvance: got %d, want %d", name, tc.r, got, tc.want)
+				continue
+			}
+		}
+	}
+}
+
 func TestGoRegularGlyphIndex(t *testing.T) {
 	f, err := Parse(goregular.TTF)
 	if err != nil {
diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go
index 25455e5..ab27f5b 100644
--- a/font/sfnt/truetype.go
+++ b/font/sfnt/truetype.go
@@ -51,7 +51,7 @@
 	}
 }
 
-func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int) (locations []uint32, err error) {
+func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int32) (locations []uint32, err error) {
 	if indexToLocFormat {
 		if loca.length != 4*uint32(numGlyphs+1) {
 			return nil, errInvalidLocaTable