font/sfnt: implement Font.Bounds.

Change-Id: I24ab4cfa74a791ebb8223b38e5d6624c74caa9f8
Reviewed-on: https://go-review.googlesource.com/39670
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index f177e82..3d33e3b 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -445,6 +445,7 @@
 	cached struct {
 		glyphData        glyphData
 		glyphIndex       glyphIndexFunc
+		bounds           [4]int16
 		indexToLocFormat bool // false means short, true means long.
 		isPostScript     bool
 		kernNumPairs     int32
@@ -478,7 +479,7 @@
 	// When implementing new parseXxx methods, take care not to call methods
 	// such as Font.NumGlyphs that implicitly depend on f.cached fields.
 
-	buf, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
+	buf, bounds, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
 	if err != nil {
 		return err
 	}
@@ -513,6 +514,7 @@
 
 	f.cached.glyphData = glyphData
 	f.cached.glyphIndex = glyphIndex
+	f.cached.bounds = bounds
 	f.cached.indexToLocFormat = indexToLocFormat
 	f.cached.isPostScript = isPostScript
 	f.cached.kernNumPairs = kernNumPairs
@@ -668,26 +670,36 @@
 	return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
 }
 
-func (f *Font) parseHead(buf []byte) (buf1 []byte, indexToLocFormat bool, unitsPerEm Units, err error) {
+func (f *Font) parseHead(buf []byte) (buf1 []byte, bounds [4]int16, indexToLocFormat bool, unitsPerEm Units, err error) {
 	// https://www.microsoft.com/typography/otspec/head.htm
 
 	if f.head.length != 54 {
-		return nil, false, 0, errInvalidHeadTable
+		return nil, [4]int16{}, false, 0, errInvalidHeadTable
 	}
+
 	u, err := f.src.u16(buf, f.head, 18)
 	if err != nil {
-		return nil, false, 0, err
+		return nil, [4]int16{}, false, 0, err
 	}
 	if u == 0 {
-		return nil, false, 0, errInvalidHeadTable
+		return nil, [4]int16{}, false, 0, errInvalidHeadTable
 	}
 	unitsPerEm = Units(u)
+
+	for i := range bounds {
+		u, err := f.src.u16(buf, f.head, 36+2*i)
+		if err != nil {
+			return nil, [4]int16{}, false, 0, err
+		}
+		bounds[i] = int16(u)
+	}
+
 	u, err = f.src.u16(buf, f.head, 50)
 	if err != nil {
-		return nil, false, 0, err
+		return nil, [4]int16{}, false, 0, err
 	}
 	indexToLocFormat = u != 0
-	return buf, indexToLocFormat, unitsPerEm, nil
+	return buf, bounds, indexToLocFormat, unitsPerEm, nil
 }
 
 func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, numHMetrics int32, err error) {
@@ -895,6 +907,33 @@
 	return buf, u, nil
 }
 
+// Bounds returns the union of a Font's glyphs' bounds.
+//
+// In the returned Rectangle26_6's (x, y) coordinates, the Y axis increases
+// down.
+func (f *Font) Bounds(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (fixed.Rectangle26_6, error) {
+	// The 0, 3, 2, 1 indices are to flip the Y coordinates. OpenType's Y axis
+	// increases up. Go's standard graphics libraries' Y axis increases down.
+	r := fixed.Rectangle26_6{
+		Min: fixed.Point26_6{
+			X: +scale(fixed.Int26_6(f.cached.bounds[0])*ppem, f.cached.unitsPerEm),
+			Y: -scale(fixed.Int26_6(f.cached.bounds[3])*ppem, f.cached.unitsPerEm),
+		},
+		Max: fixed.Point26_6{
+			X: +scale(fixed.Int26_6(f.cached.bounds[2])*ppem, f.cached.unitsPerEm),
+			Y: -scale(fixed.Int26_6(f.cached.bounds[1])*ppem, f.cached.unitsPerEm),
+		},
+	}
+	if h == font.HintingFull {
+		// Quantize the Min down and Max up to a whole pixel.
+		r.Min.X = (r.Min.X + 0) &^ 63
+		r.Min.Y = (r.Min.Y + 0) &^ 63
+		r.Max.X = (r.Max.X + 63) &^ 63
+		r.Max.Y = (r.Max.Y + 63) &^ 63
+	}
+	return r, nil
+}
+
 // TODO: API for looking up glyph variants?? For example, some fonts may
 // provide both slashed and dotted zero glyphs ('0'), or regular and 'old
 // style' numerals, and users can direct software to choose a variant.
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index 3f4fcd5..e27fa2b 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -112,6 +112,73 @@
 	}
 }
 
+func fontData(name string) []byte {
+	switch name {
+	case "gobold":
+		return gobold.TTF
+	case "gomono":
+		return gomono.TTF
+	case "goregular":
+		return goregular.TTF
+	}
+	panic("unreachable")
+}
+
+func TestBounds(t *testing.T) {
+	testCases := map[string]fixed.Rectangle26_6{
+		"gobold": {
+			Min: fixed.Point26_6{
+				X: -452,
+				Y: -2193,
+			},
+			Max: fixed.Point26_6{
+				X: 2190,
+				Y: 432,
+			},
+		},
+		"gomono": {
+			Min: fixed.Point26_6{
+				X: 0,
+				Y: -2227,
+			},
+			Max: fixed.Point26_6{
+				X: 1229,
+				Y: 432,
+			},
+		},
+		"goregular": {
+			Min: fixed.Point26_6{
+				X: -440,
+				Y: -2118,
+			},
+			Max: fixed.Point26_6{
+				X: 2160,
+				Y: 543,
+			},
+		},
+	}
+
+	var b Buffer
+	for name, want := range testCases {
+		f, err := Parse(fontData(name))
+		if err != nil {
+			t.Errorf("Parse(%q): %v", name, err)
+			continue
+		}
+		ppem := fixed.Int26_6(f.UnitsPerEm())
+
+		got, err := f.Bounds(&b, ppem, font.HintingNone)
+		if err != nil {
+			t.Errorf("name=%q: Bounds: %v", name, err)
+			continue
+		}
+		if got != want {
+			t.Errorf("name=%q: Bounds: got %v, want %v", name, got, want)
+			continue
+		}
+	}
+}
+
 func TestGlyphAdvance(t *testing.T) {
 	testCases := map[string][]struct {
 		r    rune
@@ -145,16 +212,7 @@
 
 	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)
+		f, err := Parse(fontData(name))
 		if err != nil {
 			t.Errorf("Parse(%q): %v", name, err)
 			continue