font/sfnt: support PostScript compound glyphs (subroutines).

Change-Id: I8aa10150aa004b1bc1128bf0b3d5c14b74ee089c
Reviewed-on: https://go-review.googlesource.com/38280
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go
index 19186be..1a00b82 100644
--- a/font/sfnt/postscript.go
+++ b/font/sfnt/postscript.go
@@ -61,6 +61,9 @@
 	// preceded by up to a maximum of 48 operands". 5177.Type2.pdf Appendix B
 	// "Type 2 Charstring Implementation Limits" says that "Argument stack 48".
 	psArgStackSize = 48
+
+	// Similarly, Appendix B says "Subr nesting, stack limit 10".
+	psCallStackSize = 10
 )
 
 func bigEndian(b []byte) uint32 {
@@ -91,74 +94,162 @@
 	psi psInterpreter
 }
 
-func (p *cffParser) parse() (locations []uint32, err error) {
-	// Parse header.
+func (p *cffParser) parse() (locations, gsubrs, subrs []uint32, err error) {
+	// Parse the header.
 	{
 		if !p.read(4) {
-			return nil, p.err
+			return nil, nil, nil, p.err
 		}
 		if p.buf[0] != 1 || p.buf[1] != 0 || p.buf[2] != 4 {
-			return nil, errUnsupportedCFFVersion
+			return nil, nil, nil, errUnsupportedCFFVersion
 		}
 	}
 
-	// Parse Name INDEX.
+	// Parse the Name INDEX.
 	{
 		count, offSize, ok := p.parseIndexHeader()
 		if !ok {
-			return nil, p.err
+			return nil, nil, nil, p.err
 		}
 		// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The
 		// Name INDEX in the CFF must contain only one entry".
 		if count != 1 {
-			return nil, errInvalidCFFTable
+			return nil, nil, nil, errInvalidCFFTable
 		}
 		if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
-			return nil, p.err
+			return nil, nil, nil, p.err
 		}
 		p.offset = int(p.locBuf[1])
 	}
 
-	// Parse Top DICT INDEX.
+	// Parse the Top DICT INDEX.
+	p.psi.topDict.initialize()
 	{
 		count, offSize, ok := p.parseIndexHeader()
 		if !ok {
-			return nil, p.err
+			return nil, nil, nil, p.err
 		}
 		// 5176.CFF.pdf section 8 "Top DICT INDEX" says that the count here
 		// should match the count of the Name INDEX, which is 1.
 		if count != 1 {
-			return nil, errInvalidCFFTable
+			return nil, nil, nil, errInvalidCFFTable
 		}
 		if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
-			return nil, p.err
+			return nil, nil, nil, p.err
 		}
 		if !p.read(int(p.locBuf[1] - p.locBuf[0])) {
-			return nil, p.err
+			return nil, nil, nil, p.err
 		}
-		p.psi.topDict.initialize()
-		if p.err = p.psi.run(psContextTopDict, p.buf); p.err != nil {
-			return nil, p.err
+		if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil {
+			return nil, nil, nil, p.err
+		}
+	}
+
+	// Skip the String INDEX.
+	{
+		count, offSize, ok := p.parseIndexHeader()
+		if !ok {
+			return nil, nil, nil, p.err
+		}
+		if count != 0 {
+			// Read the last location. Locations are off by 1 byte. See the
+			// comment in parseIndexLocations.
+			if !p.skip(int(count * offSize)) {
+				return nil, nil, nil, p.err
+			}
+			if !p.read(int(offSize)) {
+				return nil, nil, nil, p.err
+			}
+			loc := bigEndian(p.buf) - 1
+			// Check that locations are in bounds.
+			if uint32(p.end-p.offset) < loc {
+				return nil, nil, nil, errInvalidCFFTable
+			}
+			// Skip the index data.
+			if !p.skip(int(loc)) {
+				return nil, nil, nil, p.err
+			}
+		}
+	}
+
+	// Parse the Global Subrs [Subroutines] INDEX.
+	{
+		count, offSize, ok := p.parseIndexHeader()
+		if !ok {
+			return nil, nil, nil, p.err
+		}
+		if count != 0 {
+			if count > maxNumSubroutines {
+				return nil, nil, nil, errUnsupportedNumberOfSubroutines
+			}
+			gsubrs = make([]uint32, count+1)
+			if !p.parseIndexLocations(gsubrs, count, offSize) {
+				return nil, nil, nil, p.err
+			}
 		}
 	}
 
 	// Parse the CharStrings INDEX, whose location was found in the Top DICT.
-	if p.psi.topDict.charStrings <= 0 || int32(p.end-p.base) < p.psi.topDict.charStrings {
-		return nil, errInvalidCFFTable
+	{
+		if p.psi.topDict.charStrings <= 0 || int32(p.end-p.base) < p.psi.topDict.charStrings {
+			return nil, nil, nil, errInvalidCFFTable
+		}
+		p.offset = p.base + int(p.psi.topDict.charStrings)
+		count, offSize, ok := p.parseIndexHeader()
+		if !ok {
+			return nil, nil, nil, p.err
+		}
+		if count == 0 {
+			return nil, nil, nil, errInvalidCFFTable
+		}
+		locations = make([]uint32, count+1)
+		if !p.parseIndexLocations(locations, count, offSize) {
+			return nil, nil, nil, p.err
+		}
 	}
-	p.offset = p.base + int(p.psi.topDict.charStrings)
-	count, offSize, ok := p.parseIndexHeader()
-	if !ok {
-		return nil, p.err
+
+	// Parse the Private DICT, whose location was found in the Top DICT.
+	p.psi.privateDict.initialize()
+	if p.psi.topDict.privateDictLength != 0 {
+		offset := p.psi.topDict.privateDictOffset
+		length := p.psi.topDict.privateDictLength
+		fullLength := int32(p.end - p.base)
+		if offset <= 0 || fullLength < offset || fullLength-offset < length || length < 0 {
+			return nil, nil, nil, errInvalidCFFTable
+		}
+		p.offset = p.base + int(offset)
+		if !p.read(int(length)) {
+			return nil, nil, nil, p.err
+		}
+		if p.err = p.psi.run(psContextPrivateDict, p.buf, 0, 0); p.err != nil {
+			return nil, nil, nil, p.err
+		}
 	}
-	if count == 0 {
-		return nil, errInvalidCFFTable
+
+	// Parse the Local Subrs [Subroutines] INDEX, whose location was found in
+	// the Private DICT.
+	if p.psi.privateDict.subrs != 0 {
+		offset := p.psi.topDict.privateDictOffset + p.psi.privateDict.subrs
+		if offset <= 0 || int32(p.end-p.base) < offset {
+			return nil, nil, nil, errInvalidCFFTable
+		}
+		p.offset = p.base + int(offset)
+		count, offSize, ok := p.parseIndexHeader()
+		if !ok {
+			return nil, nil, nil, p.err
+		}
+		if count != 0 {
+			if count > maxNumSubroutines {
+				return nil, nil, nil, errUnsupportedNumberOfSubroutines
+			}
+			subrs = make([]uint32, count+1)
+			if !p.parseIndexLocations(subrs, count, offSize) {
+				return nil, nil, nil, p.err
+			}
+		}
 	}
-	locations = make([]uint32, count+1)
-	if !p.parseIndexLocations(locations, count, offSize) {
-		return nil, p.err
-	}
-	return locations, nil
+
+	return locations, gsubrs, subrs, nil
 }
 
 // read sets p.buf to view the n bytes from p.offset to p.offset+n. It also
@@ -181,6 +272,15 @@
 	return p.err == nil
 }
 
+func (p *cffParser) skip(n int) (ok bool) {
+	if p.end-p.offset < n {
+		p.err = errInvalidCFFTable
+		return false
+	}
+	p.offset += n
+	return true
+}
+
 func (p *cffParser) parseIndexHeader() (count, offSize int32, ok bool) {
 	if !p.read(2) {
 		return 0, 0, false
@@ -253,34 +353,53 @@
 	return p.err == nil
 }
 
+type psCallStackEntry struct {
+	offset, length uint32
+}
+
 type psContext uint32
 
 const (
 	psContextTopDict psContext = iota
+	psContextPrivateDict
 	psContextType2Charstring
 )
 
 // psTopDictData contains fields specific to the Top DICT context.
 type psTopDictData struct {
-	charStrings int32
+	charStrings       int32
+	privateDictOffset int32
+	privateDictLength int32
 }
 
 func (d *psTopDictData) initialize() {
 	*d = psTopDictData{}
 }
 
+// psPrivateDictData contains fields specific to the Private DICT context.
+type psPrivateDictData struct {
+	subrs int32
+}
+
+func (d *psPrivateDictData) initialize() {
+	*d = psPrivateDictData{}
+}
+
 // psType2CharstringsData contains fields specific to the Type 2 Charstrings
 // context.
 type psType2CharstringsData struct {
-	segments  []Segment
+	f         *Font
+	b         *Buffer
 	x, y      int32
 	hintBits  int32
 	seenWidth bool
+	ended     bool
 }
 
-func (d *psType2CharstringsData) initialize(segments []Segment) {
+func (d *psType2CharstringsData) initialize(f *Font, b *Buffer) {
 	*d = psType2CharstringsData{
-		segments: segments,
+		f: f,
+		b: b,
 	}
 }
 
@@ -288,19 +407,45 @@
 type psInterpreter struct {
 	ctx          psContext
 	instructions []byte
+	instrOffset  uint32
+	instrLength  uint32
 	argStack     struct {
 		a   [psArgStackSize]int32
 		top int32
 	}
-	parseNumberBuf   [maxRealNumberStrLen]byte
+	callStack struct {
+		a   [psCallStackSize]psCallStackEntry
+		top int32
+	}
+	parseNumberBuf [maxRealNumberStrLen]byte
+
 	topDict          psTopDictData
+	privateDict      psPrivateDictData
 	type2Charstrings psType2CharstringsData
 }
 
-func (p *psInterpreter) run(ctx psContext, instructions []byte) error {
+func (p *psInterpreter) hasMoreInstructions() bool {
+	if len(p.instructions) != 0 {
+		return true
+	}
+	for i := int32(0); i < p.callStack.top; i++ {
+		if p.callStack.a[i].length != 0 {
+			return true
+		}
+	}
+	return false
+}
+
+// run runs the instructions in the given PostScript context. For the
+// psContextType2Charstring context, offset and length give the location of the
+// instructions in p.type2Charstrings.f.src.
+func (p *psInterpreter) run(ctx psContext, instructions []byte, offset, length uint32) error {
 	p.ctx = ctx
 	p.instructions = instructions
+	p.instrOffset = offset
+	p.instrLength = length
 	p.argStack.top = 0
+	p.callStack.top = 0
 
 loop:
 	for len(p.instructions) > 0 {
@@ -375,7 +520,7 @@
 		number, hasResult = int32(u32(p.instructions[1:])), true
 		p.instructions = p.instructions[5:]
 
-	case b == 30 && p.ctx == psContextTopDict:
+	case b == 30 && p.ctx != psContextType2Charstring:
 		// Parse a real number. This isn't listed in 5176.CFF.pdf Table 3
 		// "Operand Encoding" but that table lists integer encodings. Further
 		// down the page it says "A real number operand is provided in addition
@@ -509,7 +654,11 @@
 			p.topDict.charStrings = p.argStack.a[p.argStack.top-1]
 			return nil
 		}},
-		18: {+2, "Private", nil},
+		18: {+2, "Private", func(p *psInterpreter) error {
+			p.topDict.privateDictLength = p.argStack.a[p.argStack.top-2]
+			p.topDict.privateDictOffset = p.argStack.a[p.argStack.top-1]
+			return nil
+		}},
 	}, {
 		// 2-byte operators. The first byte is the escape byte.
 		0:  {+1, "Copyright", nil},
@@ -536,6 +685,35 @@
 		38: {+1, "FontName", nil},
 	}},
 
+	// The Private DICT operators are defined by 5176.CFF.pdf Table 23 "Private
+	// DICT Operators".
+	psContextPrivateDict: {{
+		// 1-byte operators.
+		6:  {-2, "BlueValues", nil},
+		7:  {-2, "OtherBlues", nil},
+		8:  {-2, "FamilyBlues", nil},
+		9:  {-2, "FamilyOtherBlues", nil},
+		10: {+1, "StdHW", nil},
+		11: {+1, "StdVW", nil},
+		19: {+1, "Subrs", func(p *psInterpreter) error {
+			p.privateDict.subrs = p.argStack.a[p.argStack.top-1]
+			return nil
+		}},
+		20: {+1, "defaultWidthX", nil},
+		21: {+1, "nominalWidthX", nil},
+	}, {
+		// 2-byte operators. The first byte is the escape byte.
+		9:  {+1, "BlueScale", nil},
+		10: {+1, "BlueShift", nil},
+		11: {+1, "BlueFuzz", nil},
+		12: {-2, "StemSnapH", nil},
+		13: {-2, "StemSnapV", nil},
+		14: {+1, "ForceBold", nil},
+		17: {+1, "LanguageGroup", nil},
+		18: {+1, "ExpansionFactor", nil},
+		19: {+1, "initialRandomSeed", nil},
+	}},
+
 	// The Type 2 Charstring operators are defined by 5177.Type2.pdf Appendix A
 	// "Type 2 Charstring Command Codes".
 	psContextType2Charstring: {{
@@ -550,8 +728,8 @@
 		7:  {-1, "vlineto", t2CVlineto},
 		8:  {-1, "rrcurveto", t2CRrcurveto},
 		9:  {}, // Reserved.
-		10: {}, // callsubr.
-		11: {}, // return.
+		10: {+1, "callsubr", t2CCallsubr},
+		11: {+0, "return", t2CReturn},
 		12: {}, // escape.
 		13: {}, // Reserved.
 		14: {-1, "endchar", t2CEndchar},
@@ -569,7 +747,7 @@
 		26: {-1, "vvcurveto", t2CVvcurveto},
 		27: {-1, "hhcurveto", t2CHhcurveto},
 		28: {}, // shortint.
-		29: {}, // callgsubr.
+		29: {+1, "callgsubr", t2CCallgsubr},
 		30: {-1, "vhcurveto", t2CVhcurveto},
 		31: {-1, "hvcurveto", t2CHvcurveto},
 	}, {
@@ -650,7 +828,7 @@
 }
 
 func t2CAppendMoveto(p *psInterpreter) {
-	p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
+	p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
 		Op: SegmentOpMoveTo,
 		Args: [6]fixed.Int26_6{
 			0: fixed.Int26_6(p.type2Charstrings.x),
@@ -660,7 +838,7 @@
 }
 
 func t2CAppendLineto(p *psInterpreter) {
-	p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
+	p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
 		Op: SegmentOpLineTo,
 		Args: [6]fixed.Int26_6{
 			0: fixed.Int26_6(p.type2Charstrings.x),
@@ -682,7 +860,7 @@
 	p.type2Charstrings.y += dyc
 	xc := p.type2Charstrings.x
 	yc := p.type2Charstrings.y
-	p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
+	p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
 		Op: SegmentOpCubeTo,
 		Args: [6]fixed.Int26_6{
 			0: fixed.Int26_6(xa),
@@ -926,9 +1104,76 @@
 	return nil
 }
 
+// subrBias returns the subroutine index bias as per 5177.Type2.pdf section 4.7
+// "Subroutine Operators".
+func subrBias(numSubroutines int) int32 {
+	if numSubroutines < 1240 {
+		return 107
+	}
+	if numSubroutines < 33900 {
+		return 1131
+	}
+	return 32768
+}
+
+func t2CCallgsubr(p *psInterpreter) error { return t2CCall(p, p.type2Charstrings.f.cached.gsubrs) }
+func t2CCallsubr(p *psInterpreter) error  { return t2CCall(p, p.type2Charstrings.f.cached.subrs) }
+
+func t2CCall(p *psInterpreter, subrs []uint32) error {
+	if p.callStack.top == psCallStackSize || len(subrs) == 0 {
+		return errInvalidCFFTable
+	}
+	length := uint32(len(p.instructions))
+	p.callStack.a[p.callStack.top] = psCallStackEntry{
+		offset: p.instrOffset + p.instrLength - length,
+		length: length,
+	}
+	p.callStack.top++
+
+	subrIndex := p.argStack.a[p.argStack.top-1] + subrBias(len(subrs)-1)
+	if subrIndex < 0 || int32(len(subrs)-1) <= subrIndex {
+		return errInvalidCFFTable
+	}
+	i := subrs[subrIndex+0]
+	j := subrs[subrIndex+1]
+	if j < i {
+		return errInvalidCFFTable
+	}
+	if j-i > maxGlyphDataLength {
+		return errUnsupportedGlyphDataLength
+	}
+	buf, err := p.type2Charstrings.b.view(&p.type2Charstrings.f.src, int(i), int(j-i))
+	if err != nil {
+		return err
+	}
+
+	p.instructions = buf
+	p.instrOffset = i
+	p.instrLength = j - i
+	return nil
+}
+
+func t2CReturn(p *psInterpreter) error {
+	if p.callStack.top <= 0 {
+		return errInvalidCFFTable
+	}
+	p.callStack.top--
+	o := p.callStack.a[p.callStack.top].offset
+	n := p.callStack.a[p.callStack.top].length
+	buf, err := p.type2Charstrings.b.view(&p.type2Charstrings.f.src, int(o), int(n))
+	if err != nil {
+		return err
+	}
+
+	p.instructions = buf
+	p.instrOffset = o
+	p.instrLength = n
+	return nil
+}
+
 func t2CEndchar(p *psInterpreter) error {
 	t2CReadWidth(p, 0)
-	if p.argStack.top != 0 || len(p.instructions) != 0 {
+	if p.argStack.top != 0 || p.hasMoreInstructions() {
 		if p.argStack.top == 4 {
 			// TODO: process the implicit "seac" command as per 5177.Type2.pdf
 			// Appendix C "Compatibility and Deprecated Operators".
@@ -936,5 +1181,6 @@
 		}
 		return errInvalidCFFTable
 	}
+	p.type2Charstrings.ended = true
 	return nil
 }
diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
index f133103..ccf013d 100644
--- a/font/sfnt/proprietary_test.go
+++ b/font/sfnt/proprietary_test.go
@@ -80,7 +80,7 @@
 )
 
 func TestProprietaryAdobeSourceCodeProOTF(t *testing.T) {
-	testProprietary(t, "adobe", "SourceCodePro-Regular.otf", 1500, 2)
+	testProprietary(t, "adobe", "SourceCodePro-Regular.otf", 1500, 34)
 }
 
 func TestProprietaryAdobeSourceCodeProTTF(t *testing.T) {
@@ -92,7 +92,7 @@
 }
 
 func TestProprietaryAdobeSourceSansProOTF(t *testing.T) {
-	testProprietary(t, "adobe", "SourceSansPro-Regular.otf", 1800, 2)
+	testProprietary(t, "adobe", "SourceSansPro-Regular.otf", 1800, 34)
 }
 
 func TestProprietaryAdobeSourceSansProTTF(t *testing.T) {
@@ -449,7 +449,6 @@
 var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
 	"adobe/SourceSansPro-Regular.otf": {
 		',': {
-			// - contour #0
 			// 67 -170 rmoveto
 			moveTo(67, -170),
 			// 81 34 50 67 86 vvcurveto
@@ -461,10 +460,10 @@
 			cubeTo(130, -1, 134, -1, 137, 0),
 			// 1 -53 -34 -44 -57 -25 rrcurveto
 			cubeTo(138, -53, 104, -97, 47, -122),
+			// endchar
 		},
 
 		'Q': {
-			// - contour #0
 			// 332 57 rmoveto
 			moveTo(332, 57),
 			// -117 -77 106 168 163 77 101 117 117 77 -101 -163 -168 -77 -106 -117 hvcurveto
@@ -472,7 +471,6 @@
 			cubeTo(138, 494, 215, 595, 332, 595),
 			cubeTo(449, 595, 526, 494, 526, 331),
 			cubeTo(526, 163, 449, 57, 332, 57),
-			// - contour #1
 			// 201 -222 rmoveto
 			moveTo(533, -165),
 			// 39 35 7 8 20 hvcurveto
@@ -491,6 +489,101 @@
 			cubeTo(52, 138, 148, 11, 291, -9),
 			// -90 38 83 -66 121 hhcurveto
 			cubeTo(329, -99, 412, -165, 533, -165),
+			// endchar
+		},
+
+		'ī': { // U+012B LATIN SMALL LETTER I WITH MACRON
+			// 92 callgsubr # 92 + bias = 199.
+			// :	# Arg stack is [].
+			// :	-312 21 85 callgsubr # 85 + bias = 192.
+			// :	:	# Arg stack is [-312 21].
+			// :	:	-21 486 -20 return
+			// :	:	# Arg stack is [-312 21 -21 486 -20].
+			// :	return
+			// :	# Arg stack is [-312 21 -21 486 -20].
+			// 135 57 112 callgsubr # 112 + bias = 219
+			// :	# Arg stack is [-312 21 -21 486 -20 135 57].
+			// :	hstem
+			// :	82 82 vstem
+			// :	134 callsubr # 134 + bias = 241
+			// :	:	# Arg stack is [].
+			// :	:	82 hmoveto
+			moveTo(82, 0),
+			// :	:	82 127 callsubr # 127 + bias = 234
+			// :	:	:	# Arg stack is [82].
+			// :	:	:	486 -82 hlineto
+			lineTo(164, 0),
+			lineTo(164, 486),
+			lineTo(82, 486),
+			// :	:	:	return
+			// :	:	:	# Arg stack is [].
+			// :	:	return
+			// :	:	# Arg stack is [].
+			// :	return
+			// :	# Arg stack is [].
+			// -92 115 -60 callgsubr # -60 + bias = 47
+			// :	# Arg stack is [-92 115].
+			// :	rmoveto
+			moveTo(-10, 601),
+			// :	266 57 -266 hlineto
+			lineTo(256, 601),
+			lineTo(256, 658),
+			lineTo(-10, 658),
+			// :	endchar
+		},
+
+		'ĭ': { // U+012D LATIN SMALL LETTER I WITH BREVE
+			// 92 callgsubr # 92 + bias = 199.
+			// :	# Arg stack is [].
+			// :	-312 21 85 callgsubr # 85 + bias = 192.
+			// :	:	# Arg stack is [-312 21].
+			// :	:	-21 486 -20 return
+			// :	:	# Arg stack is [-312 21 -21 486 -20].
+			// :	return
+			// :	# Arg stack is [-312 21 -21 486 -20].
+			// 105 55 96 -20 hstem
+			// -32 51 63 82 65 51 vstem
+			// 134 callsubr # 134 + bias = 241
+			// :	# Arg stack is [].
+			// :	82 hmoveto
+			moveTo(82, 0),
+			// :	82 127 callsubr # 127 + bias = 234
+			// :	:	# Arg stack is [82].
+			// :	:	486 -82 hlineto
+			lineTo(164, 0),
+			lineTo(164, 486),
+			lineTo(82, 486),
+			// :	:	return
+			// :	:	# Arg stack is [].
+			// :	return
+			// :	# Arg stack is [].
+			// 42 85 143 callsubr # 143 + bias = 250
+			// :	# Arg stack is [42 85].
+			// :	rmoveto
+			moveTo(124, 571),
+			// :	-84 callsubr # -84 + bias = 23
+			// :	:	# Arg stack is [].
+			// :	:	107 44 77 74 5 hvcurveto
+			cubeTo(231, 571, 275, 648, 280, 722),
+			// :	:	-51 8 rlineto
+			lineTo(229, 730),
+			// :	:	-51 -8 -32 -53 -65 hhcurveto
+			cubeTo(221, 679, 189, 626, 124, 626),
+			// :	:	-65 -32 53 51 -8 hvcurveto
+			cubeTo(59, 626, 27, 679, 19, 730),
+			// :	:	-51 -22 callsubr # -22 + bias = 85
+			// :	:	:	# Arg stack is [-51].
+			// :	:	:	-8 rlineto
+			lineTo(-32, 722),
+			// :	:	:	-74 5 44 -77 107 hhcurveto
+			cubeTo(-27, 648, 17, 571, 124, 571),
+			// :	:	:	return
+			// :	:	:	# Arg stack is [].
+			// :	:	return
+			// :	:	# Arg stack is [].
+			// :	return
+			// :	# Arg stack is [].
+			// endchar
 		},
 
 		'Λ': { // U+039B GREEK CAPITAL LETTER LAMDA
@@ -512,6 +605,7 @@
 			lineTo(305, 656),
 			// -96 hlineto
 			lineTo(209, 656),
+			// endchar
 		},
 	},
 
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index a497d0e..356841d 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -45,6 +45,10 @@
 	// safe to call concurrently, as long as each call has a different *Buffer.
 	maxCmapSegments = 20000
 
+	// TODO: similarly, load subroutine locations lazily. Adobe's
+	// SourceHanSansSC-Regular.otf has up to 30000 subroutines.
+	maxNumSubroutines = 40000
+
 	maxCompoundRecursionDepth = 8
 	maxCompoundStackSize      = 64
 	maxGlyphDataLength        = 64 * 1024
@@ -62,24 +66,25 @@
 	// ErrNotFound indicates that the requested value was not found.
 	ErrNotFound = errors.New("sfnt: not found")
 
-	errInvalidBounds         = errors.New("sfnt: invalid bounds")
-	errInvalidCFFTable       = errors.New("sfnt: invalid CFF table")
-	errInvalidCmapTable      = errors.New("sfnt: invalid cmap table")
-	errInvalidFont           = errors.New("sfnt: invalid font")
-	errInvalidFontCollection = errors.New("sfnt: invalid font collection")
-	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")
-	errInvalidNameTable      = errors.New("sfnt: invalid name table")
-	errInvalidPostTable      = errors.New("sfnt: invalid post table")
-	errInvalidSingleFont     = errors.New("sfnt: invalid single font (data is a font collection)")
-	errInvalidSourceData     = errors.New("sfnt: invalid source data")
-	errInvalidTableOffset    = errors.New("sfnt: invalid table offset")
-	errInvalidTableTagOrder  = errors.New("sfnt: invalid table tag order")
-	errInvalidUCS2String     = errors.New("sfnt: invalid UCS-2 string")
+	errInvalidBounds          = errors.New("sfnt: invalid bounds")
+	errInvalidCFFTable        = errors.New("sfnt: invalid CFF table")
+	errInvalidCmapTable       = errors.New("sfnt: invalid cmap table")
+	errInvalidFont            = errors.New("sfnt: invalid font")
+	errInvalidFontCollection  = errors.New("sfnt: invalid font collection")
+	errInvalidGlyphData       = errors.New("sfnt: invalid glyph data")
+	errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length")
+	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")
+	errInvalidNameTable       = errors.New("sfnt: invalid name table")
+	errInvalidPostTable       = errors.New("sfnt: invalid post table")
+	errInvalidSingleFont      = errors.New("sfnt: invalid single font (data is a font collection)")
+	errInvalidSourceData      = errors.New("sfnt: invalid source data")
+	errInvalidTableOffset     = errors.New("sfnt: invalid table offset")
+	errInvalidTableTagOrder   = errors.New("sfnt: invalid table tag order")
+	errInvalidUCS2String      = errors.New("sfnt: invalid UCS-2 string")
 
 	errUnsupportedCFFVersion           = errors.New("sfnt: unsupported CFF version")
 	errUnsupportedCmapEncodings        = errors.New("sfnt: unsupported cmap encodings")
@@ -90,6 +95,7 @@
 	errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
 	errUnsupportedNumberOfFonts        = errors.New("sfnt: unsupported number of fonts")
 	errUnsupportedNumberOfHints        = errors.New("sfnt: unsupported number of hints")
+	errUnsupportedNumberOfSubroutines  = errors.New("sfnt: unsupported number of subroutines")
 	errUnsupportedNumberOfTables       = errors.New("sfnt: unsupported number of tables")
 	errUnsupportedPlatformEncoding     = errors.New("sfnt: unsupported platform encoding")
 	errUnsupportedPostTable            = errors.New("sfnt: unsupported post table")
@@ -440,9 +446,17 @@
 		postTableVersion uint32
 		unitsPerEm       Units
 
-		// The glyph data for the glyph index i is in
+		// The glyph data for the i'th glyph index is in
 		// src[locations[i+0]:locations[i+1]].
+		//
+		// The slice length equals 1 plus the number of glyphs.
 		locations []uint32
+
+		// For PostScript fonts, the bytecode for the i'th global or local
+		// subroutine is in src[x[i+0]:x[i+1]].
+		//
+		// The slice length equals 1 plus the number of subroutines
+		gsubrs, subrs []uint32
 	}
 }
 
@@ -473,7 +487,7 @@
 	if err != nil {
 		return err
 	}
-	buf, numGlyphs, locations, err := f.parseMaxp(buf, indexToLocFormat, isPostScript)
+	buf, numGlyphs, locations, gsubrs, subrs, err := f.parseMaxp(buf, indexToLocFormat, isPostScript)
 	if err != nil {
 		return err
 	}
@@ -498,6 +512,8 @@
 	f.cached.postTableVersion = postTableVersion
 	f.cached.unitsPerEm = unitsPerEm
 	f.cached.locations = locations
+	f.cached.gsubrs = gsubrs
+	f.cached.subrs = subrs
 
 	return nil
 }
@@ -762,21 +778,21 @@
 	return buf, kernNumPairs, int32(offset) + headerSize, nil
 }
 
-func (f *Font) parseMaxp(buf []byte, indexToLocFormat, isPostScript bool) (buf1 []byte, numGlyphs int, locations []uint32, err error) {
+func (f *Font) parseMaxp(buf []byte, indexToLocFormat, isPostScript bool) (buf1 []byte, numGlyphs int, locations, gsubrs, subrs []uint32, err error) {
 	// https://www.microsoft.com/typography/otspec/maxp.htm
 
 	if isPostScript {
 		if f.maxp.length != 6 {
-			return nil, 0, nil, errInvalidMaxpTable
+			return nil, 0, nil, nil, nil, errInvalidMaxpTable
 		}
 	} else {
 		if f.maxp.length != 32 {
-			return nil, 0, nil, errInvalidMaxpTable
+			return nil, 0, nil, nil, nil, errInvalidMaxpTable
 		}
 	}
 	u, err := f.src.u16(buf, f.maxp, 4)
 	if err != nil {
-		return nil, 0, nil, err
+		return nil, 0, nil, nil, nil, err
 	}
 	numGlyphs = int(u)
 
@@ -787,21 +803,21 @@
 			offset: int(f.cff.offset),
 			end:    int(f.cff.offset + f.cff.length),
 		}
-		locations, err = p.parse()
+		locations, gsubrs, subrs, err = p.parse()
 		if err != nil {
-			return nil, 0, nil, err
+			return nil, 0, nil, nil, nil, err
 		}
 	} else {
 		locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
 		if err != nil {
-			return nil, 0, nil, err
+			return nil, 0, nil, nil, nil, err
 		}
 	}
 	if len(locations) != numGlyphs+1 {
-		return nil, 0, nil, errInvalidLocationData
+		return nil, 0, nil, nil, nil, errInvalidLocationData
 	}
 
-	return buf, numGlyphs, locations, nil
+	return buf, numGlyphs, locations, gsubrs, subrs, nil
 }
 
 func (f *Font) parsePost(buf []byte, numGlyphs int) (buf1 []byte, postTableVersion uint32, err error) {
@@ -845,17 +861,21 @@
 	return f.cached.glyphIndex(f, b, r)
 }
 
-func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) ([]byte, error) {
+func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) {
 	xx := int(x)
 	if f.NumGlyphs() <= xx {
-		return nil, ErrNotFound
+		return nil, 0, 0, ErrNotFound
 	}
 	i := f.cached.locations[xx+0]
 	j := f.cached.locations[xx+1]
-	if j-i > maxGlyphDataLength {
-		return nil, errUnsupportedGlyphDataLength
+	if j < i {
+		return nil, 0, 0, errInvalidGlyphDataLength
 	}
-	return b.view(&f.src, int(i), int(j-i))
+	if j-i > maxGlyphDataLength {
+		return nil, 0, 0, errUnsupportedGlyphDataLength
+	}
+	buf, err = b.view(&f.src, int(i), int(j-i))
+	return buf, i, j - i, err
 }
 
 // LoadGlyphOptions are the options to the Font.LoadGlyph method.
@@ -876,15 +896,17 @@
 
 	b.segments = b.segments[:0]
 	if f.cached.isPostScript {
-		buf, err := f.viewGlyphData(b, x)
+		buf, offset, length, 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 {
+		b.psi.type2Charstrings.initialize(f, b)
+		if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil {
 			return nil, err
 		}
-		b.segments = b.psi.type2Charstrings.segments
+		if !b.psi.type2Charstrings.ended {
+			return nil, errInvalidCFFTable
+		}
 	} else {
 		if err := loadGlyf(f, b, x, 0, 0); err != nil {
 			return nil, err
diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go
index cce0cc7..4181961 100644
--- a/font/sfnt/truetype.go
+++ b/font/sfnt/truetype.go
@@ -85,7 +85,7 @@
 const glyfHeaderLen = 10
 
 func loadGlyf(f *Font, b *Buffer, x GlyphIndex, stackBottom, recursionDepth uint32) error {
-	data, err := f.viewGlyphData(b, x)
+	data, _, _, err := f.viewGlyphData(b, x)
 	if err != nil {
 		return err
 	}