font/sfnt: support TrueType compound glyphs.

Change-Id: I129e3f7894ad0edccc9e8ca4a21fc9e60e23105b
Reviewed-on: https://go-review.googlesource.com/38111
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
index 2e127b6..4c6a633 100644
--- a/font/sfnt/proprietary_test.go
+++ b/font/sfnt/proprietary_test.go
@@ -88,15 +88,15 @@
 }
 
 func TestProprietaryMicrosoftArial(t *testing.T) {
-	testProprietary(t, "microsoft", "Arial.ttf", 1200, 98)
+	testProprietary(t, "microsoft", "Arial.ttf", 1200, 599)
 }
 
 func TestProprietaryMicrosoftComicSansMS(t *testing.T) {
-	testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, 98)
+	testProprietary(t, "microsoft", "Comic_Sans_MS.ttf", 550, -1)
 }
 
 func TestProprietaryMicrosoftTimesNewRoman(t *testing.T) {
-	testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, 98)
+	testProprietary(t, "microsoft", "Times_New_Roman.ttf", 1200, 423)
 }
 
 func TestProprietaryMicrosoftWebdings(t *testing.T) {
@@ -351,6 +351,7 @@
 			// 1 -53 -34 -44 -57 -25 rrcurveto
 			cubeTo(138, -53, 104, -97, 47, -122),
 		},
+
 		'Q': {
 			// - contour #0
 			// 332 57 rmoveto
@@ -380,6 +381,7 @@
 			// -90 38 83 -66 121 hhcurveto
 			cubeTo(329, -99, 412, -165, 533, -165),
 		},
+
 		'Λ': { // U+039B GREEK CAPITAL LETTER LAMDA
 			// 0 vmoveto
 			moveTo(0, 0),
@@ -416,6 +418,7 @@
 			quadTo(281, -91, 284, 0),
 			lineTo(182, 0),
 		},
+
 		'i': {
 			// - contour #0
 			moveTo(136, 1259),
@@ -430,6 +433,7 @@
 			lineTo(316, 0),
 			lineTo(136, 0),
 		},
+
 		'o': {
 			// - contour #0
 			moveTo(68, 531),
@@ -453,6 +457,83 @@
 			quadTo(431, 937, 342, 836),
 			quadTo(253, 735, 253, 531),
 		},
+
+		'í': { // U+00ED LATIN SMALL LETTER I WITH ACUTE
+			// - contour #0
+			translate(0, 0, moveTo(198, 0)),
+			translate(0, 0, lineTo(198, 1062)),
+			translate(0, 0, lineTo(378, 1062)),
+			translate(0, 0, lineTo(378, 0)),
+			translate(0, 0, lineTo(198, 0)),
+			// - contour #1
+			translate(-33, 0, moveTo(222, 1194)),
+			translate(-33, 0, lineTo(355, 1474)),
+			translate(-33, 0, lineTo(591, 1474)),
+			translate(-33, 0, lineTo(371, 1194)),
+			translate(-33, 0, lineTo(222, 1194)),
+		},
+
+		'Ī': { // U+012A LATIN CAPITAL LETTER I WITH MACRON
+			// - contour #0
+			translate(0, 0, moveTo(191, 0)),
+			translate(0, 0, lineTo(191, 1466)),
+			translate(0, 0, lineTo(385, 1466)),
+			translate(0, 0, lineTo(385, 0)),
+			translate(0, 0, lineTo(191, 0)),
+			// - contour #1
+			translate(-57, 336, moveTo(29, 1227)),
+			translate(-57, 336, lineTo(29, 1375)),
+			translate(-57, 336, lineTo(653, 1375)),
+			translate(-57, 336, lineTo(653, 1227)),
+			translate(-57, 336, lineTo(29, 1227)),
+		},
+
+		// Ǻ is a compound glyph whose elements are also compound glyphs.
+		'Ǻ': { // U+01FA LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
+			// - contour #0
+			translate(0, 0, moveTo(-3, 0)),
+			translate(0, 0, lineTo(560, 1466)),
+			translate(0, 0, lineTo(769, 1466)),
+			translate(0, 0, lineTo(1369, 0)),
+			translate(0, 0, lineTo(1148, 0)),
+			translate(0, 0, lineTo(977, 444)),
+			translate(0, 0, lineTo(364, 444)),
+			translate(0, 0, lineTo(203, 0)),
+			translate(0, 0, lineTo(-3, 0)),
+			// - contour #1
+			translate(0, 0, moveTo(420, 602)),
+			translate(0, 0, lineTo(917, 602)),
+			translate(0, 0, lineTo(764, 1008)),
+			translate(0, 0, quadTo(694, 1193, 660, 1312)),
+			translate(0, 0, quadTo(632, 1171, 581, 1032)),
+			translate(0, 0, lineTo(420, 602)),
+			// - contour #2
+			translate(319, 263, moveTo(162, 1338)),
+			translate(319, 263, quadTo(162, 1411, 215, 1464)),
+			translate(319, 263, quadTo(269, 1517, 342, 1517)),
+			translate(319, 263, quadTo(416, 1517, 469, 1463)),
+			translate(319, 263, quadTo(522, 1410, 522, 1334)),
+			translate(319, 263, quadTo(522, 1257, 469, 1204)),
+			translate(319, 263, quadTo(416, 1151, 343, 1151)),
+			translate(319, 263, quadTo(268, 1151, 215, 1204)),
+			translate(319, 263, quadTo(162, 1258, 162, 1338)),
+			// - contour #3
+			translate(319, 263, moveTo(238, 1337)),
+			translate(319, 263, quadTo(238, 1290, 269, 1258)),
+			translate(319, 263, quadTo(301, 1226, 344, 1226)),
+			translate(319, 263, quadTo(387, 1226, 418, 1258)),
+			translate(319, 263, quadTo(450, 1290, 450, 1335)),
+			translate(319, 263, quadTo(450, 1380, 419, 1412)),
+			translate(319, 263, quadTo(388, 1444, 344, 1444)),
+			translate(319, 263, quadTo(301, 1444, 269, 1412)),
+			translate(319, 263, quadTo(238, 1381, 238, 1337)),
+			// - contour #4
+			translate(339, 650, moveTo(222, 1194)),
+			translate(339, 650, lineTo(355, 1474)),
+			translate(339, 650, lineTo(591, 1474)),
+			translate(339, 650, lineTo(371, 1194)),
+			translate(339, 650, lineTo(222, 1194)),
+		},
 	},
 }
 
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 02efd5f..71aae52 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -45,10 +45,12 @@
 	// safe to call concurrently, as long as each call has a different *Buffer.
 	maxCmapSegments = 20000
 
-	maxGlyphDataLength  = 64 * 1024
-	maxHintBits         = 256
-	maxNumTables        = 256
-	maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
+	maxCompoundRecursionDepth = 8
+	maxCompoundStackSize      = 64
+	maxGlyphDataLength        = 64 * 1024
+	maxHintBits               = 256
+	maxNumTables              = 256
+	maxRealNumberStrLen       = 64 // Maximum length in bytes of the "-123.456E-7" representation.
 
 	// (maxTableOffset + maxTableLength) will not overflow an int32.
 	maxTableLength = 1 << 29
@@ -766,24 +768,21 @@
 		b = &Buffer{}
 	}
 
-	buf, err := f.viewGlyphData(b, x)
-	if err != nil {
-		return nil, err
-	}
-
 	b.segments = b.segments[:0]
 	if f.cached.isPostScript {
+		buf, err := f.viewGlyphData(b, x)
+		if err != nil {
+			return nil, err
+		}
 		b.psi.type2Charstrings.initialize(b.segments)
 		if err := b.psi.run(psContextType2Charstring, buf); err != nil {
 			return nil, err
 		}
 		b.segments = b.psi.type2Charstrings.segments
 	} else {
-		segments, err := appendGlyfSegments(b.segments, buf)
-		if err != nil {
+		if err := loadGlyf(f, b, x, 0, 0); err != nil {
 			return nil, err
 		}
-		b.segments = segments
 	}
 
 	// Scale the segments. If we want to support hinting, we'll have to push
@@ -1024,6 +1023,11 @@
 	buf []byte
 	// segments holds glyph vector path segments.
 	segments []Segment
+	// compoundStack holds the components of a TrueType compound glyph.
+	compoundStack [maxCompoundStackSize]struct {
+		glyphIndex GlyphIndex
+		dx, dy     int16
+	}
 	// psi is a PostScript interpreter for when the Font is an OpenType/CFF
 	// font.
 	psi psInterpreter
@@ -1056,3 +1060,13 @@
 	SegmentOpQuadTo
 	SegmentOpCubeTo
 )
+
+func translate(dx, dy fixed.Int26_6, s Segment) Segment {
+	s.Args[0] += dx
+	s.Args[1] += dy
+	s.Args[2] += dx
+	s.Args[3] += dy
+	s.Args[4] += dx
+	s.Args[5] += dy
+	return s
+}
diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go
index c0eefba..3c0f880 100644
--- a/font/sfnt/truetype.go
+++ b/font/sfnt/truetype.go
@@ -84,13 +84,16 @@
 // glyph begins with the following [10 byte] header".
 const glyfHeaderLen = 10
 
-// appendGlyfSegments appends to dst the segments encoded in the glyf data.
-func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
+func loadGlyf(f *Font, b *Buffer, x GlyphIndex, stackBottom, recursionDepth uint32) error {
+	data, err := f.viewGlyphData(b, x)
+	if err != nil {
+		return err
+	}
 	if len(data) == 0 {
-		return dst, nil
+		return nil
 	}
 	if len(data) < glyfHeaderLen {
-		return nil, errInvalidGlyphData
+		return errInvalidGlyphData
 	}
 	index := glyfHeaderLen
 
@@ -99,34 +102,33 @@
 	case numContours == -1:
 		// We have a compound glyph. No-op.
 	case numContours == 0:
-		return dst, nil
+		return nil
 	case numContours > 0:
 		// We have a simple (non-compound) glyph.
 		index += 2 * int(numContours)
 		if index > len(data) {
-			return nil, errInvalidGlyphData
+			return errInvalidGlyphData
 		}
 		// The +1 for numPoints is because the value in the file format is
 		// inclusive, but Go's slice[:index] semantics are exclusive.
 		numPoints = 1 + int(u16(data[index-2:]))
 	default:
-		return nil, errInvalidGlyphData
+		return errInvalidGlyphData
 	}
 
-	// TODO: support compound glyphs.
 	if numContours < 0 {
-		return nil, errUnsupportedCompoundGlyph
+		return loadCompoundGlyf(f, b, data[glyfHeaderLen:], stackBottom, recursionDepth)
 	}
 
 	// Skip the hinting instructions.
 	index += 2
 	if index > len(data) {
-		return nil, errInvalidGlyphData
+		return errInvalidGlyphData
 	}
 	hintsLength := int(u16(data[index-2:]))
 	index += hintsLength
 	if index > len(data) {
-		return nil, errInvalidGlyphData
+		return errInvalidGlyphData
 	}
 
 	// For simple (non-compound) glyphs, the remainder of the glyf data
@@ -140,7 +142,7 @@
 	flagIndex := int32(index)
 	xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
 	if !ok {
-		return nil, errInvalidGlyphData
+		return errInvalidGlyphData
 	}
 
 	// The second pass decodes each (flags, x, y) tuple in row order.
@@ -157,13 +159,10 @@
 	}
 	for g.nextContour() {
 		for g.nextSegment() {
-			dst = append(dst, g.seg)
+			b.segments = append(b.segments, g.seg)
 		}
 	}
-	if g.err != nil {
-		return nil, g.err
-	}
-	return dst, nil
+	return g.err
 }
 
 func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) {
@@ -215,6 +214,76 @@
 	return int32(index), int32(index + xDataLen), true
 }
 
+func loadCompoundGlyf(f *Font, b *Buffer, data []byte, stackBottom, recursionDepth uint32) error {
+	if recursionDepth++; recursionDepth == maxCompoundRecursionDepth {
+		return errUnsupportedCompoundGlyph
+	}
+
+	// Read and process the compound glyph's components. They are two separate
+	// for loops, since reading parses the elements of the data slice, and
+	// processing can overwrite the backing array.
+
+	stackTop := stackBottom
+	for {
+		if stackTop >= maxCompoundStackSize {
+			return errUnsupportedCompoundGlyph
+		}
+		elem := &b.compoundStack[stackTop]
+		stackTop++
+
+		if len(data) < 4 {
+			return errInvalidGlyphData
+		}
+		flags := u16(data)
+		elem.glyphIndex = GlyphIndex(u16(data[2:]))
+		if flags&flagArg1And2AreWords == 0 {
+			if len(data) < 6 {
+				return errInvalidGlyphData
+			}
+			elem.dx = int16(int8(data[4]))
+			elem.dy = int16(int8(data[5]))
+			data = data[6:]
+		} else {
+			if len(data) < 8 {
+				return errInvalidGlyphData
+			}
+			elem.dx = int16(u16(data[4:]))
+			elem.dy = int16(u16(data[6:]))
+			data = data[8:]
+		}
+
+		if flags&flagArgsAreXYValues == 0 {
+			return errUnsupportedCompoundGlyph
+		}
+		// TODO: read the other elem.transform elements.
+		if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 {
+			return errUnsupportedCompoundGlyph
+		}
+		if flags&flagMoreComponents == 0 {
+			break
+		}
+	}
+
+	// To support hinting, we'd have to save the remaining bytes in data here
+	// and interpret them after the for loop below, since that for loop's
+	// loadGlyf calls can overwrite the backing array.
+
+	for i := stackBottom; i < stackTop; i++ {
+		elem := &b.compoundStack[i]
+		base := len(b.segments)
+		if err := loadGlyf(f, b, elem.glyphIndex, stackTop, recursionDepth); err != nil {
+			return err
+		}
+		dx, dy := fixed.Int26_6(elem.dx), fixed.Int26_6(elem.dy)
+		segs := b.segments[base:]
+		for j := range segs {
+			segs[j] = translate(dx, dy, segs[j])
+		}
+	}
+
+	return nil
+}
+
 type glyfIter struct {
 	data []byte
 	err  error