font/sfnt: add Metrics to Font

Change-Id: I4bfcf264e5ee7e4f3ddf89e289d730f230095401
Reviewed-on: https://go-review.googlesource.com/67330
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 53ac94b..cc4ceac 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -564,14 +564,17 @@
 	kern table
 
 	cached struct {
+		ascent           int32
 		glyphData        glyphData
 		glyphIndex       glyphIndexFunc
 		bounds           [4]int16
+		descent          int32
 		indexToLocFormat bool // false means short, true means long.
 		isColorBitmap    bool
 		isPostScript     bool
 		kernNumPairs     int32
 		kernOffset       int32
+		lineGap          int32
 		numHMetrics      int32
 		postTableVersion uint32
 		unitsPerEm       Units
@@ -621,7 +624,7 @@
 	if err != nil {
 		return err
 	}
-	buf, numHMetrics, err := f.parseHhea(buf, numGlyphs)
+	buf, ascent, descent, lineGap, numHMetrics, err := f.parseHhea(buf, numGlyphs)
 	if err != nil {
 		return err
 	}
@@ -634,14 +637,17 @@
 		return err
 	}
 
+	f.cached.ascent = ascent
 	f.cached.glyphData = glyphData
 	f.cached.glyphIndex = glyphIndex
 	f.cached.bounds = bounds
+	f.cached.descent = descent
 	f.cached.indexToLocFormat = indexToLocFormat
 	f.cached.isColorBitmap = isColorBitmap
 	f.cached.isPostScript = isPostScript
 	f.cached.kernNumPairs = kernNumPairs
 	f.cached.kernOffset = kernOffset
+	f.cached.lineGap = lineGap
 	f.cached.numHMetrics = numHMetrics
 	f.cached.postTableVersion = postTableVersion
 	f.cached.unitsPerEm = unitsPerEm
@@ -838,20 +844,32 @@
 	return buf, bounds, indexToLocFormat, unitsPerEm, nil
 }
 
-func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, numHMetrics int32, err error) {
+func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, ascent, descent, lineGap, numHMetrics int32, err error) {
 	// https://www.microsoft.com/typography/OTSPEC/hhea.htm
 
 	if f.hhea.length != 36 {
-		return nil, 0, errInvalidHheaTable
+		return nil, 0, 0, 0, 0, errInvalidHheaTable
 	}
 	u, err := f.src.u16(buf, f.hhea, 34)
 	if err != nil {
-		return nil, 0, err
+		return nil, 0, 0, 0, 0, err
 	}
 	if int32(u) > numGlyphs || u == 0 {
-		return nil, 0, errInvalidHheaTable
+		return nil, 0, 0, 0, 0, errInvalidHheaTable
 	}
-	return buf, int32(u), nil
+	a, err := f.src.u16(buf, f.hhea, 4)
+	if err != nil {
+		return nil, 0, 0, 0, 0, err
+	}
+	d, err := f.src.u16(buf, f.hhea, 6)
+	if err != nil {
+		return nil, 0, 0, 0, 0, err
+	}
+	l, err := f.src.u16(buf, f.hhea, 8)
+	if err != nil {
+		return nil, 0, 0, 0, 0, err
+	}
+	return buf, int32(int16(a)), int32(int16(d)), int32(int16(l)), int32(u), nil
 }
 
 func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) {
@@ -1336,6 +1354,23 @@
 	return 0, nil
 }
 
+// Metrics returns the metrics of this font.
+func (f *Font) Metrics(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (font.Metrics, error) {
+	m := font.Metrics{
+		// TODO: is adding lineGap correct?
+		Height:  ppem + scale(fixed.Int26_6(f.cached.lineGap)*ppem, f.cached.unitsPerEm),
+		Ascent:  +scale(fixed.Int26_6(f.cached.ascent)*ppem, f.cached.unitsPerEm),
+		Descent: -scale(fixed.Int26_6(f.cached.descent)*ppem, f.cached.unitsPerEm),
+	}
+	if h == font.HintingFull {
+		// Quantize up to a whole pixel.
+		m.Height = (m.Height + 63) &^ 63
+		m.Ascent = (m.Ascent + 63) &^ 63
+		m.Descent = (m.Descent + 63) &^ 63
+	}
+	return m, 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 74de278..b9b66a7 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -214,6 +214,40 @@
 	}
 }
 
+func TestMetrics(t *testing.T) {
+	cmapFont, err := ioutil.ReadFile(filepath.FromSlash("../testdata/cmapTest.ttf"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	testCases := map[string]struct {
+		font []byte
+		want font.Metrics
+	}{
+		"goregular": {goregular.TTF, font.Metrics{Height: 2048, Ascent: 1935, Descent: 432}},
+		// cmapTest.ttf has a non-zero lineGap.
+		"cmapTest": {cmapFont, font.Metrics{Height: 2232, Ascent: 1365, Descent: 0}},
+	}
+	var b Buffer
+	for name, tc := range testCases {
+		f, err := Parse(tc.font)
+		if err != nil {
+			t.Errorf("name=%q: Parse: %v", name, err)
+			continue
+		}
+		ppem := fixed.Int26_6(f.UnitsPerEm())
+
+		got, err := f.Metrics(&b, ppem, font.HintingNone)
+		if err != nil {
+			t.Errorf("name=%q: Metrics: %v", name, err)
+			continue
+		}
+		if got != tc.want {
+			t.Errorf("name=%q: Metrics: got %v, want %v", name, got, tc.want)
+			continue
+		}
+	}
+}
+
 func TestGlyphAdvance(t *testing.T) {
 	testCases := map[string][]struct {
 		r    rune