font/sfnt: parse Type 2 Charstrings.

Change-Id: I61beec4611612800a519045e2552c513eb83c3f8
Reviewed-on: https://go-review.googlesource.com/33932
Reviewed-by: Dave Day <djd@golang.org>
diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go
index cc277cb..93f5490 100644
--- a/font/sfnt/postscript.go
+++ b/font/sfnt/postscript.go
@@ -51,6 +51,8 @@
 	"fmt"
 	"math"
 	"strconv"
+
+	"golang.org/x/image/math/fixed"
 )
 
 const (
@@ -86,18 +88,7 @@
 	buf    []byte
 	locBuf [2]uint32
 
-	parseNumberBuf [maxRealNumberStrLen]byte
-
-	instructions []byte
-
-	stack struct {
-		a   [psStackSize]int32
-		top int32
-	}
-
-	saved struct {
-		charStrings int32
-	}
+	psi psInterpreter
 }
 
 func (p *cffParser) parse() (locations []uint32, err error) {
@@ -145,20 +136,17 @@
 		if !p.read(int(p.locBuf[1] - p.locBuf[0])) {
 			return nil, p.err
 		}
-
-		for p.instructions = p.buf; len(p.instructions) > 0; {
-			p.step()
-			if p.err != nil {
-				return nil, p.err
-			}
+		p.psi.topDict.initialize()
+		if p.err = p.psi.run(psContextTopDict, p.buf); p.err != nil {
+			return nil, p.err
 		}
 	}
 
 	// Parse the CharStrings INDEX, whose location was found in the Top DICT.
-	if p.saved.charStrings <= 0 || int32(p.end-p.base) < p.saved.charStrings {
+	if p.psi.topDict.charStrings <= 0 || int32(p.end-p.base) < p.psi.topDict.charStrings {
 		return nil, errInvalidCFFTable
 	}
-	p.offset = p.base + int(p.saved.charStrings)
+	p.offset = p.base + int(p.psi.topDict.charStrings)
 	count, offSize, ok := p.parseIndexHeader()
 	if !ok {
 		return nil, p.err
@@ -265,99 +253,129 @@
 	return p.err == nil
 }
 
-// step executes a single operation, whether pushing a numeric operand onto the
-// stack or executing an operator.
-func (p *cffParser) step() {
-	if number, res := p.parseNumber(); res != prNone {
-		if res < 0 || p.stack.top == psStackSize {
-			if res == prUnsupportedRNE {
-				p.err = errUnsupportedRealNumberEncoding
-			} else {
-				p.err = errInvalidCFFTable
-			}
-			return
-		}
-		p.stack.a[p.stack.top] = number
-		p.stack.top++
-		return
-	}
+type psContext uint32
 
-	b0 := p.instructions[0]
-	p.instructions = p.instructions[1:]
+const (
+	psContextTopDict psContext = iota
+	psContextType2Charstring
+)
 
-	for b, escaped, operators := b0, false, topDictOperators[0]; ; {
-		if b == escapeByte && !escaped {
-			if len(p.instructions) <= 0 {
-				p.err = errInvalidCFFTable
-				return
-			}
-			b = p.instructions[0]
-			p.instructions = p.instructions[1:]
-			escaped = true
-			operators = topDictOperators[1]
-			continue
-		}
+// psTopDictData contains fields specific to the Top DICT context.
+type psTopDictData struct {
+	charStrings int32
+}
 
-		if int(b) < len(operators) {
-			if op := operators[b]; op.name != "" {
-				if p.stack.top < op.numPop {
-					p.err = errInvalidCFFTable
-					return
-				}
-				if op.run != nil {
-					op.run(p)
-				}
-				if op.numPop < 0 {
-					p.stack.top = 0
-				} else {
-					p.stack.top -= op.numPop
-				}
-				return
-			}
-		}
+func (d *psTopDictData) initialize() {
+	*d = psTopDictData{}
+}
 
-		if escaped {
-			p.err = fmt.Errorf("sfnt: unrecognized CFF 2-byte operator (12 %d)", b)
-		} else {
-			p.err = fmt.Errorf("sfnt: unrecognized CFF 1-byte operator (%d)", b)
-		}
-		return
+// psType2CharstringsData contains fields specific to the Type 2 Charstrings
+// context.
+type psType2CharstringsData struct {
+	segments  []Segment
+	x, y      int32
+	hintBits  int32
+	seenWidth bool
+}
+
+func (d *psType2CharstringsData) initialize(segments []Segment) {
+	*d = psType2CharstringsData{
+		segments: segments,
 	}
 }
 
-type parseResult int32
+// psInterpreter is a PostScript interpreter.
+type psInterpreter struct {
+	ctx          psContext
+	instructions []byte
+	stack        struct {
+		a   [psStackSize]int32
+		top int32
+	}
+	parseNumberBuf   [maxRealNumberStrLen]byte
+	topDict          psTopDictData
+	type2Charstrings psType2CharstringsData
+}
 
-const (
-	prUnsupportedRNE parseResult = -2
-	prInvalid        parseResult = -1
-	prNone           parseResult = +0
-	prGood           parseResult = +1
-)
+func (p *psInterpreter) run(ctx psContext, instructions []byte) error {
+	p.ctx = ctx
+	p.instructions = instructions
+	p.stack.top = 0
+
+loop:
+	for len(p.instructions) > 0 {
+		// Push a numeric operand on the stack, if applicable.
+		if hasResult, err := p.parseNumber(); hasResult {
+			if err != nil {
+				return err
+			}
+			continue
+		}
+
+		// Otherwise, execute an operator.
+		b := p.instructions[0]
+		p.instructions = p.instructions[1:]
+
+		for escaped, ops := false, psOperators[ctx][0]; ; {
+			if b == escapeByte && !escaped {
+				if len(p.instructions) <= 0 {
+					return errInvalidCFFTable
+				}
+				b = p.instructions[0]
+				p.instructions = p.instructions[1:]
+				escaped = true
+				ops = psOperators[ctx][1]
+				continue
+			}
+
+			if int(b) < len(ops) {
+				if op := ops[b]; op.name != "" {
+					if p.stack.top < op.numPop {
+						return errInvalidCFFTable
+					}
+					if op.run != nil {
+						if err := op.run(p); err != nil {
+							return err
+						}
+					}
+					if op.numPop < 0 {
+						p.stack.top = 0
+					} else {
+						p.stack.top -= op.numPop
+					}
+					continue loop
+				}
+			}
+
+			if escaped {
+				return fmt.Errorf("sfnt: unrecognized CFF 2-byte operator (12 %d)", b)
+			} else {
+				return fmt.Errorf("sfnt: unrecognized CFF 1-byte operator (%d)", b)
+			}
+		}
+	}
+	return nil
+}
 
 // See 5176.CFF.pdf section 4 "DICT Data".
-func (p *cffParser) parseNumber() (number int32, res parseResult) {
-	if len(p.instructions) == 0 {
-		return 0, prNone
-	}
-
-	switch b0 := p.instructions[0]; {
-	case b0 == 28:
+func (p *psInterpreter) parseNumber() (hasResult bool, err error) {
+	number := int32(0)
+	switch b := p.instructions[0]; {
+	case b == 28:
 		if len(p.instructions) < 3 {
-			return 0, prInvalid
+			return true, errInvalidCFFTable
 		}
-		number = int32(int16(u16(p.instructions[1:])))
+		number, hasResult = int32(int16(u16(p.instructions[1:]))), true
 		p.instructions = p.instructions[3:]
-		return number, prGood
 
-	case b0 == 29:
+	case b == 29 && p.ctx == psContextTopDict:
 		if len(p.instructions) < 5 {
-			return 0, prInvalid
+			return true, errInvalidCFFTable
 		}
-		number = int32(u32(p.instructions[1:]))
+		number, hasResult = int32(u32(p.instructions[1:])), true
 		p.instructions = p.instructions[5:]
-		return number, prGood
 
-	case b0 == 30:
+	case b == 30 && p.ctx == psContextTopDict:
 		// 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
@@ -366,9 +384,10 @@
 
 		s := p.parseNumberBuf[:0]
 		p.instructions = p.instructions[1:]
+	loop:
 		for {
 			if len(p.instructions) == 0 {
-				return 0, prInvalid
+				return true, errInvalidCFFTable
 			}
 			b := p.instructions[0]
 			p.instructions = p.instructions[1:]
@@ -379,45 +398,60 @@
 				if nib == 0x0f {
 					f, err := strconv.ParseFloat(string(s), 32)
 					if err != nil {
-						return 0, prInvalid
+						return true, errInvalidCFFTable
 					}
-					return int32(math.Float32bits(float32(f))), prGood
+					number, hasResult = int32(math.Float32bits(float32(f))), true
+					break loop
 				}
 				if nib == 0x0d {
-					return 0, prInvalid
+					return true, errInvalidCFFTable
 				}
 				if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) {
-					return 0, prUnsupportedRNE
+					return true, errUnsupportedRealNumberEncoding
 				}
 				s = append(s, nibbleDefs[nib]...)
 			}
 		}
 
-	case b0 < 32:
+	case b < 32:
 		// No-op.
 
-	case b0 < 247:
+	case b < 247:
 		p.instructions = p.instructions[1:]
-		return int32(b0) - 139, prGood
+		number, hasResult = int32(b)-139, true
 
-	case b0 < 251:
+	case b < 251:
 		if len(p.instructions) < 2 {
-			return 0, prInvalid
+			return true, errInvalidCFFTable
 		}
 		b1 := p.instructions[1]
 		p.instructions = p.instructions[2:]
-		return +int32(b0-247)*256 + int32(b1) + 108, prGood
+		number, hasResult = +int32(b-247)*256+int32(b1)+108, true
 
-	case b0 < 255:
+	case b < 255:
 		if len(p.instructions) < 2 {
-			return 0, prInvalid
+			return true, errInvalidCFFTable
 		}
 		b1 := p.instructions[1]
 		p.instructions = p.instructions[2:]
-		return -int32(b0-251)*256 - int32(b1) - 108, prGood
+		number, hasResult = -int32(b-251)*256-int32(b1)-108, true
+
+	case b == 255 && p.ctx == psContextType2Charstring:
+		if len(p.instructions) < 5 {
+			return true, errInvalidCFFTable
+		}
+		number, hasResult = int32(u32(p.instructions[1:])), true
+		p.instructions = p.instructions[5:]
 	}
 
-	return 0, prNone
+	if hasResult {
+		if p.stack.top == psStackSize {
+			return true, errInvalidCFFTable
+		}
+		p.stack.a[p.stack.top] = number
+		p.stack.top++
+	}
+	return hasResult, nil
 }
 
 const maxNibbleDefsLength = len("E-")
@@ -442,7 +476,7 @@
 	0x0f: "",
 }
 
-type cffOperator struct {
+type psOperator struct {
 	// numPop is the number of stack values to pop. -1 means "array" and -2
 	// means "delta" as per 5176.CFF.pdf Table 6 "Operand Types".
 	numPop int32
@@ -451,54 +485,364 @@
 	name string
 	// run is the function that implements the operator. Nil means that we
 	// ignore the operator, other than popping its arguments off the stack.
-	run func(*cffParser)
+	run func(*psInterpreter) error
 }
 
-// topDictOperators encodes the subset of 5176.CFF.pdf Table 9 "Top DICT
-// Operator Entries" and Table 10 "CIDFont Operator Extensions" used by this
-// implementation.
-var topDictOperators = [2][]cffOperator{{
-	// 1-byte operators.
-	0:  {+1, "version", nil},
-	1:  {+1, "Notice", nil},
-	2:  {+1, "FullName", nil},
-	3:  {+1, "FamilyName", nil},
-	4:  {+1, "Weight", nil},
-	5:  {-1, "FontBBox", nil},
-	13: {+1, "UniqueID", nil},
-	14: {-1, "XUID", nil},
-	15: {+1, "charset", nil},
-	16: {+1, "Encoding", nil},
-	17: {+1, "CharStrings", func(p *cffParser) {
-		p.saved.charStrings = p.stack.a[p.stack.top-1]
+// psOperators holds the 1-byte and 2-byte operators for PostScript interpreter
+// contexts.
+var psOperators = [...][2][]psOperator{
+	// The Top DICT operators are defined by 5176.CFF.pdf Table 9 "Top DICT
+	// Operator Entries" and Table 10 "CIDFont Operator Extensions".
+	psContextTopDict: {{
+		// 1-byte operators.
+		0:  {+1, "version", nil},
+		1:  {+1, "Notice", nil},
+		2:  {+1, "FullName", nil},
+		3:  {+1, "FamilyName", nil},
+		4:  {+1, "Weight", nil},
+		5:  {-1, "FontBBox", nil},
+		13: {+1, "UniqueID", nil},
+		14: {-1, "XUID", nil},
+		15: {+1, "charset", nil},
+		16: {+1, "Encoding", nil},
+		17: {+1, "CharStrings", func(p *psInterpreter) error {
+			p.topDict.charStrings = p.stack.a[p.stack.top-1]
+			return nil
+		}},
+		18: {+2, "Private", nil},
+	}, {
+		// 2-byte operators. The first byte is the escape byte.
+		0:  {+1, "Copyright", nil},
+		1:  {+1, "isFixedPitch", nil},
+		2:  {+1, "ItalicAngle", nil},
+		3:  {+1, "UnderlinePosition", nil},
+		4:  {+1, "UnderlineThickness", nil},
+		5:  {+1, "PaintType", nil},
+		6:  {+1, "CharstringType", nil},
+		7:  {-1, "FontMatrix", nil},
+		8:  {+1, "StrokeWidth", nil},
+		20: {+1, "SyntheticBase", nil},
+		21: {+1, "PostScript", nil},
+		22: {+1, "BaseFontName", nil},
+		23: {-2, "BaseFontBlend", nil},
+		30: {+3, "ROS", nil},
+		31: {+1, "CIDFontVersion", nil},
+		32: {+1, "CIDFontRevision", nil},
+		33: {+1, "CIDFontType", nil},
+		34: {+1, "CIDCount", nil},
+		35: {+1, "UIDBase", nil},
+		36: {+1, "FDArray", nil},
+		37: {+1, "FDSelect", nil},
+		38: {+1, "FontName", nil},
 	}},
-	18: {+2, "Private", nil},
-}, {
-	// 2-byte operators. The first byte is the escape byte.
-	0:  {+1, "Copyright", nil},
-	1:  {+1, "isFixedPitch", nil},
-	2:  {+1, "ItalicAngle", nil},
-	3:  {+1, "UnderlinePosition", nil},
-	4:  {+1, "UnderlineThickness", nil},
-	5:  {+1, "PaintType", nil},
-	6:  {+1, "CharstringType", nil},
-	7:  {-1, "FontMatrix", nil},
-	8:  {+1, "StrokeWidth", nil},
-	20: {+1, "SyntheticBase", nil},
-	21: {+1, "PostScript", nil},
-	22: {+1, "BaseFontName", nil},
-	23: {-2, "BaseFontBlend", nil},
-	30: {+3, "ROS", nil},
-	31: {+1, "CIDFontVersion", nil},
-	32: {+1, "CIDFontRevision", nil},
-	33: {+1, "CIDFontType", nil},
-	34: {+1, "CIDCount", nil},
-	35: {+1, "UIDBase", nil},
-	36: {+1, "FDArray", nil},
-	37: {+1, "FDSelect", nil},
-	38: {+1, "FontName", nil},
-}}
+
+	// The Type 2 Charstring operators are defined by 5177.Type2.pdf Appendix A
+	// "Type 2 Charstring Command Codes".
+	psContextType2Charstring: {{
+		// 1-byte operators.
+		0:  {}, // Reserved.
+		1:  {-1, "hstem", t2CStem},
+		2:  {}, // Reserved.
+		3:  {-1, "vstem", t2CStem},
+		4:  {-1, "vmoveto", t2CVmoveto},
+		5:  {-1, "rlineto", t2CRlineto},
+		6:  {-1, "hlineto", t2CHlineto},
+		7:  {-1, "vlineto", t2CVlineto},
+		8:  {}, // rrcurveto.
+		9:  {}, // Reserved.
+		10: {}, // callsubr.
+		11: {}, // return.
+		12: {}, // escape.
+		13: {}, // Reserved.
+		14: {-1, "endchar", t2CEndchar},
+		15: {}, // Reserved.
+		16: {}, // Reserved.
+		17: {}, // Reserved.
+		18: {-1, "hstemhm", t2CStem},
+		19: {-1, "hintmask", t2CMask},
+		20: {-1, "cntrmask", t2CMask},
+		21: {-1, "rmoveto", t2CRmoveto},
+		22: {-1, "hmoveto", t2CHmoveto},
+		23: {-1, "vstemhm", t2CStem},
+		24: {}, // rcurveline.
+		25: {}, // rlinecurve.
+		26: {}, // vvcurveto.
+		27: {}, // hhcurveto.
+		28: {}, // shortint.
+		29: {}, // callgsubr.
+		30: {-1, "vhcurveto", t2CVhcurveto},
+		31: {-1, "hvcurveto", t2CHvcurveto},
+	}, {
+		// 2-byte operators. The first byte is the escape byte.
+		0: {}, // Reserved.
+		// TODO: more operators.
+	}},
+}
 
 // 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an
 // initial escape byte of 12".
 const escapeByte = 12
+
+// t2CReadWidth reads the optional width adjustment. If present, it is on the
+// bottom of the stack.
+//
+// 5177.Type2.pdf page 16 Note 4 says: "The first stack-clearing operator,
+// which must be one of hstem, hstemhm, vstem, vstemhm, cntrmask, hintmask,
+// hmoveto, vmoveto, rmoveto, or endchar, takes an additional argument — the
+// width... which may be expressed as zero or one numeric argument."
+func t2CReadWidth(p *psInterpreter, nArgs int32) {
+	if p.type2Charstrings.seenWidth {
+		return
+	}
+	p.type2Charstrings.seenWidth = true
+	switch nArgs {
+	case 0:
+		if p.stack.top != 1 {
+			return
+		}
+	case 1:
+		if p.stack.top <= 1 {
+			return
+		}
+	default:
+		if p.stack.top%nArgs != 1 {
+			return
+		}
+	}
+	// When parsing a standalone CFF, we'd save the value of p.stack.a[0] here
+	// as it defines the glyph's width (horizontal advance). Specifically, if
+	// present, it is a delta to the font-global nominalWidthX value found in
+	// the Private DICT. If absent, the glyph's width is the defaultWidthX
+	// value in that dict. See 5176.CFF.pdf section 15 "Private DICT Data".
+	//
+	// For a CFF embedded in an SFNT font (i.e. an OpenType font), glyph widths
+	// are already stored in the hmtx table, separate to the CFF table, and it
+	// is simpler to parse that table for all OpenType fonts (PostScript and
+	// TrueType). We therefore ignore the width value here, and just remove it
+	// from the bottom of the stack.
+	copy(p.stack.a[:p.stack.top-1], p.stack.a[1:p.stack.top])
+	p.stack.top--
+}
+
+func t2CStem(p *psInterpreter) error {
+	t2CReadWidth(p, 2)
+	if p.stack.top%2 != 0 {
+		return errInvalidCFFTable
+	}
+	// We update the number of hintBits need to parse hintmask and cntrmask
+	// instructions, but this Type 2 Charstring implementation otherwise
+	// ignores the stem hints.
+	p.type2Charstrings.hintBits += p.stack.top / 2
+	if p.type2Charstrings.hintBits > maxHintBits {
+		return errUnsupportedNumberOfHints
+	}
+	return nil
+}
+
+func t2CMask(p *psInterpreter) error {
+	hintBytes := (p.type2Charstrings.hintBits + 7) / 8
+	t2CReadWidth(p, hintBytes)
+	if len(p.instructions) < int(hintBytes) {
+		return errInvalidCFFTable
+	}
+	p.instructions = p.instructions[hintBytes:]
+	return nil
+}
+
+func t2CAppendMoveto(p *psInterpreter) {
+	p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
+		Op: SegmentOpMoveTo,
+		Args: [6]fixed.Int26_6{
+			0: fixed.Int26_6(p.type2Charstrings.x) << 6,
+			1: fixed.Int26_6(p.type2Charstrings.y) << 6,
+		},
+	})
+}
+
+func t2CAppendLineto(p *psInterpreter) {
+	p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
+		Op: SegmentOpLineTo,
+		Args: [6]fixed.Int26_6{
+			0: fixed.Int26_6(p.type2Charstrings.x) << 6,
+			1: fixed.Int26_6(p.type2Charstrings.y) << 6,
+		},
+	})
+}
+
+func t2CAppendCubeto(p *psInterpreter, dxa, dya, dxb, dyb, dxc, dyc int32) {
+	p.type2Charstrings.x += dxa
+	p.type2Charstrings.y += dya
+	xa := p.type2Charstrings.x
+	ya := p.type2Charstrings.y
+	p.type2Charstrings.x += dxb
+	p.type2Charstrings.y += dyb
+	xb := p.type2Charstrings.x
+	yb := p.type2Charstrings.y
+	p.type2Charstrings.x += dxc
+	p.type2Charstrings.y += dyc
+	xc := p.type2Charstrings.x
+	yc := p.type2Charstrings.y
+	p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
+		Op: SegmentOpCubeTo,
+		Args: [6]fixed.Int26_6{
+			0: fixed.Int26_6(xa) << 6,
+			1: fixed.Int26_6(ya) << 6,
+			2: fixed.Int26_6(xb) << 6,
+			3: fixed.Int26_6(yb) << 6,
+			4: fixed.Int26_6(xc) << 6,
+			5: fixed.Int26_6(yc) << 6,
+		},
+	})
+}
+
+func t2CHmoveto(p *psInterpreter) error {
+	t2CReadWidth(p, 1)
+	if p.stack.top < 1 {
+		return errInvalidCFFTable
+	}
+	for i := int32(0); i < p.stack.top; i++ {
+		p.type2Charstrings.x += p.stack.a[i]
+	}
+	t2CAppendMoveto(p)
+	return nil
+}
+
+func t2CVmoveto(p *psInterpreter) error {
+	t2CReadWidth(p, 1)
+	if p.stack.top < 1 {
+		return errInvalidCFFTable
+	}
+	for i := int32(0); i < p.stack.top; i++ {
+		p.type2Charstrings.y += p.stack.a[i]
+	}
+	t2CAppendMoveto(p)
+	return nil
+}
+
+func t2CRmoveto(p *psInterpreter) error {
+	t2CReadWidth(p, 2)
+	if p.stack.top < 2 || p.stack.top%2 != 0 {
+		return errInvalidCFFTable
+	}
+	for i := int32(0); i < p.stack.top; i += 2 {
+		p.type2Charstrings.x += p.stack.a[i+0]
+		p.type2Charstrings.y += p.stack.a[i+1]
+	}
+	t2CAppendMoveto(p)
+	return nil
+}
+
+func t2CHlineto(p *psInterpreter) error { return t2CLineto(p, false) }
+func t2CVlineto(p *psInterpreter) error { return t2CLineto(p, true) }
+
+func t2CLineto(p *psInterpreter, vertical bool) error {
+	if !p.type2Charstrings.seenWidth {
+		return errInvalidCFFTable
+	}
+	if p.stack.top < 1 {
+		return errInvalidCFFTable
+	}
+	for i := int32(0); i < p.stack.top; i, vertical = i+1, !vertical {
+		if vertical {
+			p.type2Charstrings.y += p.stack.a[i]
+		} else {
+			p.type2Charstrings.x += p.stack.a[i]
+		}
+		t2CAppendLineto(p)
+	}
+	return nil
+}
+
+func t2CRlineto(p *psInterpreter) error {
+	if !p.type2Charstrings.seenWidth {
+		return errInvalidCFFTable
+	}
+	if p.stack.top < 2 || p.stack.top%2 != 0 {
+		return errInvalidCFFTable
+	}
+	for i := int32(0); i < p.stack.top; i += 2 {
+		p.type2Charstrings.x += p.stack.a[i+0]
+		p.type2Charstrings.y += p.stack.a[i+1]
+		t2CAppendLineto(p)
+	}
+	return nil
+}
+
+// As per 5177.Type2.pdf section 4.1 "Path Construction Operators",
+//
+// hvcurveto is one of:
+//	- dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
+//	- {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
+//
+// vhcurveto is one of:
+//	- dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf?
+//	- {dya dxb dyb dxc dxd dxe dye dyf}+ dxf?
+
+func t2CHvcurveto(p *psInterpreter) error { return t2CCurveto(p, false) }
+func t2CVhcurveto(p *psInterpreter) error { return t2CCurveto(p, true) }
+
+func t2CCurveto(p *psInterpreter, vertical bool) error {
+	if !p.type2Charstrings.seenWidth || p.stack.top < 4 {
+		return errInvalidCFFTable
+	}
+	for i := int32(0); i != p.stack.top; vertical = !vertical {
+		if vertical {
+			i = t2CVcurveto(p, i)
+		} else {
+			i = t2CHcurveto(p, i)
+		}
+		if i < 0 {
+			return errInvalidCFFTable
+		}
+	}
+	return nil
+}
+
+func t2CHcurveto(p *psInterpreter, i int32) (j int32) {
+	if i+4 > p.stack.top {
+		return -1
+	}
+	dxa := p.stack.a[i+0]
+	dxb := p.stack.a[i+1]
+	dyb := p.stack.a[i+2]
+	dyc := p.stack.a[i+3]
+	dxc := int32(0)
+	i += 4
+	if i+1 == p.stack.top {
+		dxc = p.stack.a[i]
+		i++
+	}
+	t2CAppendCubeto(p, dxa, 0, dxb, dyb, dxc, dyc)
+	return i
+}
+
+func t2CVcurveto(p *psInterpreter, i int32) (j int32) {
+	if i+4 > p.stack.top {
+		return -1
+	}
+	dya := p.stack.a[i+0]
+	dxb := p.stack.a[i+1]
+	dyb := p.stack.a[i+2]
+	dxc := p.stack.a[i+3]
+	dyc := int32(0)
+	i += 4
+	if i+1 == p.stack.top {
+		dyc = p.stack.a[i]
+		i++
+	}
+	t2CAppendCubeto(p, 0, dya, dxb, dyb, dxc, dyc)
+	return i
+}
+
+func t2CEndchar(p *psInterpreter) error {
+	t2CReadWidth(p, 0)
+	if p.stack.top != 0 || len(p.instructions) != 0 {
+		if p.stack.top == 4 {
+			// TODO: process the implicit "seac" command as per 5177.Type2.pdf
+			// Appendix C "Compatibility and Deprecated Operators".
+			return errUnsupportedType2Charstring
+		}
+		return errInvalidCFFTable
+	}
+	return nil
+}
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 801cb0b..4ebf952 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -17,11 +17,14 @@
 import (
 	"errors"
 	"io"
+
+	"golang.org/x/image/math/fixed"
 )
 
 // These constants are not part of the specifications, but are limitations used
 // by this implementation.
 const (
+	maxHintBits         = 256
 	maxNumTables        = 256
 	maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
 
@@ -45,10 +48,15 @@
 
 	errUnsupportedCFFVersion         = errors.New("sfnt: unsupported CFF version")
 	errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
+	errUnsupportedNumberOfHints      = errors.New("sfnt: unsupported number of hints")
 	errUnsupportedNumberOfTables     = errors.New("sfnt: unsupported number of tables")
 	errUnsupportedTableOffsetLength  = errors.New("sfnt: unsupported table offset or length")
+	errUnsupportedType2Charstring    = errors.New("sfnt: unsupported Type 2 Charstring")
 )
 
+// GlyphIndex is a glyph index in a Font.
+type GlyphIndex uint16
+
 // Units are an integral number of abstract, scalable "font units". The em
 // square is typically 1000 or 2048 "font units". This would map to a certain
 // number (e.g. 30 pixels) of physical pixels, depending on things like the
@@ -85,6 +93,16 @@
 	return (s.b == nil) != (s.r == nil)
 }
 
+// viewBufferWritable returns whether the []byte returned by source.view can be
+// written to by the caller, including by passing it to the same method
+// (source.view) on other receivers (i.e. different sources).
+//
+// In other words, it returns whether the source's underlying data is an
+// io.ReaderAt, not a []byte.
+func (s *source) viewBufferWritable() bool {
+	return s.b == nil
+}
+
 // view returns the length bytes at the given offset. buf is an optional
 // scratch buffer to reduce allocations when calling view multiple times. A nil
 // buf is valid. The []byte returned may be a sub-slice of buf[:cap(buf)], or
@@ -159,6 +177,11 @@
 }
 
 // Font is an SFNT font.
+//
+// All of its methods are safe to call concurrently, although the method
+// arguments may have further restrictions. For example, it is valid to have
+// multiple concurrent Font.LoadGlyph calls to the same *Font receiver, as long
+// as each call has a different Buffer.
 type Font struct {
 	src source
 
@@ -343,11 +366,78 @@
 	return nil
 }
 
-func (f *Font) viewGlyphData(buf []byte, glyphIndex int) ([]byte, error) {
-	if glyphIndex < 0 || f.NumGlyphs() <= glyphIndex {
+// TODO: func (f *Font) GlyphIndex(r rune) (x GlyphIndex, ok bool)
+// This will require parsing the cmap table.
+
+func (f *Font) viewGlyphData(buf []byte, x GlyphIndex) ([]byte, error) {
+	xx := int(x)
+	if f.NumGlyphs() <= xx {
 		return nil, errGlyphIndexOutOfRange
 	}
-	i := f.cached.locations[glyphIndex+0]
-	j := f.cached.locations[glyphIndex+1]
+	i := f.cached.locations[xx+0]
+	j := f.cached.locations[xx+1]
 	return f.src.view(buf, int(i), int(j-i))
 }
+
+// LoadGlyphOptions are the options to the Font.LoadGlyph method.
+type LoadGlyphOptions struct {
+	// TODO: scale / transform / hinting.
+}
+
+// LoadGlyph loads the glyphs vectors for the x'th glyph into b.Segments.
+func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) error {
+	buf, err := f.viewGlyphData(b.buf, x)
+	if err != nil {
+		return err
+	}
+	// Only update b.buf if it is safe to re-use buf.
+	if f.src.viewBufferWritable() {
+		b.buf = buf
+	}
+
+	b.Segments = b.Segments[:0]
+	if f.cached.isPostScript {
+		b.psi.type2Charstrings.initialize(b.Segments)
+		if err := b.psi.run(psContextType2Charstring, buf); err != nil {
+			return err
+		}
+		b.Segments = b.psi.type2Charstrings.segments
+	} else {
+		return errors.New("sfnt: TODO: load glyf data")
+	}
+
+	// TODO: look at opts to scale / transform / hint the Buffer.Segments.
+
+	return nil
+}
+
+// Buffer holds the result of the Font.LoadGlyph method. It is valid to re-use
+// a Buffer with multiple Font.LoadGlyph calls, even with different *Font
+// receivers, as long as they are not concurrent calls.
+//
+// It is also valid to have multiple concurrent Font.LoadGlyph calls to the
+// same *Font receiver, as long as each call has a different Buffer.
+type Buffer struct {
+	Segments []Segment
+	// buf is a byte buffer for when a Font's source is an io.ReaderAt.
+	buf []byte
+	// psi is a PostScript interpreter for when the Font is an OpenType/CFF
+	// font.
+	psi psInterpreter
+}
+
+// Segment is a segment of a vector path.
+type Segment struct {
+	Op   SegmentOp
+	Args [6]fixed.Int26_6
+}
+
+// SegmentOp is a vector path segment's operator.
+type SegmentOp uint32
+
+const (
+	SegmentOpMoveTo SegmentOp = iota
+	SegmentOpLineTo
+	SegmentOpQuadTo
+	SegmentOpCubeTo
+)
diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go
index 8c9df63..68ef19c 100644
--- a/font/sfnt/sfnt_test.go
+++ b/font/sfnt/sfnt_test.go
@@ -11,8 +11,43 @@
 	"testing"
 
 	"golang.org/x/image/font/gofont/goregular"
+	"golang.org/x/image/math/fixed"
 )
 
+func moveTo(xa, ya int) Segment {
+	return Segment{
+		Op: SegmentOpMoveTo,
+		Args: [6]fixed.Int26_6{
+			0: fixed.I(xa),
+			1: fixed.I(ya),
+		},
+	}
+}
+
+func lineTo(xa, ya int) Segment {
+	return Segment{
+		Op: SegmentOpLineTo,
+		Args: [6]fixed.Int26_6{
+			0: fixed.I(xa),
+			1: fixed.I(ya),
+		},
+	}
+}
+
+func cubeTo(xa, ya, xb, yb, xc, yc int) Segment {
+	return Segment{
+		Op: SegmentOpCubeTo,
+		Args: [6]fixed.Int26_6{
+			0: fixed.I(xa),
+			1: fixed.I(ya),
+			2: fixed.I(xb),
+			3: fixed.I(yb),
+			4: fixed.I(xc),
+			5: fixed.I(yc),
+		},
+	}
+}
+
 func TestTrueTypeParse(t *testing.T) {
 	f, err := Parse(goregular.TTF)
 	if err != nil {
@@ -51,28 +86,83 @@
 		t.Fatal(err)
 	}
 
-	// TODO: replace this by a higher level test, once we parse Type 2
-	// Charstrings.
+	// wants' vectors correspond 1-to-1 to what's in the CFFTest.sfd file,
+	// although for some unknown reason, FontForge reverses the order somewhere
+	// along the way when converting from SFD to OpenType/CFF.
 	//
-	// As a sanity check for now, note that each string ends in '\x0e', which
-	// 5177.Type2.pdf Appendix A defines as "endchar".
-	wants := [...]string{
-		"\xf7\x63\x8b\xbd\xf8\x45\xbd\x01\xbd\xbd\xf7\xc0\xbd\x03\xbd\x16\xf8\x24\xf8\xa9\xfc\x24\x06\xbd\xfc\x77\x15\xf8\x45\xf7\xc0\xfc\x45\x07\x0e",
-		"\x8b\xef\xf8\xec\xef\x01\xef\xdb\xf7\x84\xdb\x03\xf7\xc0\xf9\x50\x15\xdb\xb3\xfb\x0c\x3b\xfb\x2a\x6d\xfb\x8e\x31\x3b\x63\xf7\x0c\xdb\xf7\x2a\xa9\xf7\x8e\xe5\x1f\xef\x04\x27\x27\xfb\x70\xfb\x48\xfb\x48\xef\xfb\x70\xef\xef\xef\xf7\x70\xf7\x48\xf7\x48\x27\xf7\x70\x27\x1f\x0e",
-		"\xf6\xa0\x76\x01\xef\xf7\x5c\x03\xef\x16\xf7\x5c\xf9\xb4\xfb\x5c\x06\x0e",
-		"\xf7\x89\xe1\x03\xf7\x21\xf8\x9c\x15\x87\xfb\x38\xf7\x00\xb7\xe1\xfc\x0a\xa3\xf8\x18\xf7\x00\x9f\x81\xf7\x4e\xfb\x04\x6f\x81\xf7\x3a\x33\x85\x83\xfb\x52\x05\x0e",
-	}
+	// The .notdef glyph isn't explicitly in the SFD file, but for some unknown
+	// reason, FontForge generates a .notdef glyph in the OpenType/CFF file.
+	wants := [...][]Segment{{
+		// .notdef
+		// - contour #0
+		moveTo(50, 0),
+		lineTo(450, 0),
+		lineTo(450, 533),
+		lineTo(50, 533),
+		// - contour #1
+		moveTo(100, 50),
+		lineTo(100, 483),
+		lineTo(400, 483),
+		lineTo(400, 50),
+	}, {
+		// zero
+		// - contour #0
+		moveTo(300, 700),
+		cubeTo(380, 700, 420, 580, 420, 500),
+		cubeTo(420, 350, 390, 100, 300, 100),
+		cubeTo(220, 100, 180, 220, 180, 300),
+		cubeTo(180, 450, 210, 700, 300, 700),
+		// - contour #1
+		moveTo(300, 800),
+		cubeTo(200, 800, 100, 580, 100, 400),
+		cubeTo(100, 220, 200, 0, 300, 0),
+		cubeTo(400, 0, 500, 220, 500, 400),
+		cubeTo(500, 580, 400, 800, 300, 800),
+	}, {
+		// one
+		// - contour #0
+		moveTo(100, 0),
+		lineTo(300, 0),
+		lineTo(300, 800),
+		lineTo(100, 800),
+	}, {
+		// uni4E2D
+		// - contour #0
+		moveTo(141, 520),
+		lineTo(137, 356),
+		lineTo(245, 400),
+		lineTo(331, 26),
+		lineTo(355, 414),
+		lineTo(463, 434),
+		lineTo(453, 620),
+		lineTo(341, 592),
+		lineTo(331, 758),
+		lineTo(243, 752),
+		lineTo(235, 562),
+	}}
+
 	if ng := f.NumGlyphs(); ng != len(wants) {
 		t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
 	}
+	var b Buffer
+loop:
 	for i, want := range wants {
-		gd, err := f.viewGlyphData(nil, i)
-		if err != nil {
-			t.Errorf("i=%d: %v", i, err)
+		if err := f.LoadGlyph(&b, GlyphIndex(i), nil); err != nil {
+			t.Errorf("i=%d: LoadGlyph: %v", i, err)
 			continue
 		}
-		if got := string(gd); got != want {
-			t.Errorf("i=%d:\ngot  % x\nwant % x", i, got, want)
+		got := b.Segments
+		if len(got) != len(want) {
+			t.Errorf("i=%d: got %d elements, want %d\noverall:\ngot  %v\nwant %v",
+				i, len(got), len(want), got, want)
+			continue
+		}
+		for j, g := range got {
+			if w := want[j]; g != w {
+				t.Errorf("i=%d: element %d:\ngot  %v\nwant %v\noverall:\ngot  %v\nwant %v",
+					i, j, g, w, got, want)
+				continue loop
+			}
 		}
 	}
 }