font/sfnt: implement Font Dict Select.

Change-Id: I8c463b41421a35455701847520367add4727cbe3
Reviewed-on: https://go-review.googlesource.com/38871
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go
index 5506819..183f0cb 100644
--- a/font/sfnt/postscript.go
+++ b/font/sfnt/postscript.go
@@ -80,6 +80,44 @@
 	panic("unreachable")
 }
 
+// fdSelect holds a CFF font's Font Dict Select data.
+type fdSelect struct {
+	format    uint8
+	numRanges uint16
+	offset    int32
+}
+
+func (t *fdSelect) lookup(f *Font, b *Buffer, x GlyphIndex) (int, error) {
+	switch t.format {
+	case 0:
+		buf, err := b.view(&f.src, int(t.offset)+int(x), 1)
+		if err != nil {
+			return 0, err
+		}
+		return int(buf[0]), nil
+	case 3:
+		lo, hi := 0, int(t.numRanges)
+		for lo < hi {
+			i := (lo + hi) / 2
+			buf, err := b.view(&f.src, int(t.offset)+3*i, 3+2)
+			if err != nil {
+				return 0, err
+			}
+			// buf holds the range [xlo, xhi).
+			if xlo := GlyphIndex(u16(buf[0:])); x < xlo {
+				hi = i
+				continue
+			}
+			if xhi := GlyphIndex(u16(buf[3:])); xhi <= x {
+				lo = i + 1
+				continue
+			}
+			return int(buf[2]), nil
+		}
+	}
+	return 0, ErrNotFound
+}
+
 // cffParser parses the CFF table from an SFNT font.
 type cffParser struct {
 	src    *source
@@ -94,14 +132,14 @@
 	psi psInterpreter
 }
 
-func (p *cffParser) parse() (locations, gsubrs, subrs []uint32, err error) {
+func (p *cffParser) parse(numGlyphs int) (ret glyphData, err error) {
 	// Parse the header.
 	{
 		if !p.read(4) {
-			return nil, nil, nil, p.err
+			return glyphData{}, p.err
 		}
 		if p.buf[0] != 1 || p.buf[1] != 0 || p.buf[2] != 4 {
-			return nil, nil, nil, errUnsupportedCFFVersion
+			return glyphData{}, errUnsupportedCFFVersion
 		}
 	}
 
@@ -109,15 +147,15 @@
 	{
 		count, offSize, ok := p.parseIndexHeader()
 		if !ok {
-			return nil, nil, nil, p.err
+			return glyphData{}, 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, nil, nil, errInvalidCFFTable
+			return glyphData{}, errInvalidCFFTable
 		}
 		if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
-			return nil, nil, nil, p.err
+			return glyphData{}, p.err
 		}
 		p.offset = int(p.locBuf[1])
 	}
@@ -127,21 +165,21 @@
 	{
 		count, offSize, ok := p.parseIndexHeader()
 		if !ok {
-			return nil, nil, nil, p.err
+			return glyphData{}, 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, nil, nil, errInvalidCFFTable
+			return glyphData{}, errInvalidCFFTable
 		}
 		if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
-			return nil, nil, nil, p.err
+			return glyphData{}, p.err
 		}
 		if !p.read(int(p.locBuf[1] - p.locBuf[0])) {
-			return nil, nil, nil, p.err
+			return glyphData{}, p.err
 		}
 		if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil {
-			return nil, nil, nil, p.err
+			return glyphData{}, p.err
 		}
 	}
 
@@ -149,25 +187,25 @@
 	{
 		count, offSize, ok := p.parseIndexHeader()
 		if !ok {
-			return nil, nil, nil, p.err
+			return glyphData{}, 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
+				return glyphData{}, p.err
 			}
 			if !p.read(int(offSize)) {
-				return nil, nil, nil, p.err
+				return glyphData{}, 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
+				return glyphData{}, errInvalidCFFTable
 			}
 			// Skip the index data.
 			if !p.skip(int(loc)) {
-				return nil, nil, nil, p.err
+				return glyphData{}, p.err
 			}
 		}
 	}
@@ -176,80 +214,171 @@
 	{
 		count, offSize, ok := p.parseIndexHeader()
 		if !ok {
-			return nil, nil, nil, p.err
+			return glyphData{}, p.err
 		}
 		if count != 0 {
 			if count > maxNumSubroutines {
-				return nil, nil, nil, errUnsupportedNumberOfSubroutines
+				return glyphData{}, errUnsupportedNumberOfSubroutines
 			}
-			gsubrs = make([]uint32, count+1)
-			if !p.parseIndexLocations(gsubrs, count, offSize) {
-				return nil, nil, nil, p.err
+			ret.gsubrs = make([]uint32, count+1)
+			if !p.parseIndexLocations(ret.gsubrs, count, offSize) {
+				return glyphData{}, 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, nil, nil, errInvalidCFFTable
+		if !p.seekFromBase(p.psi.topDict.charStringsOffset) {
+			return glyphData{}, errInvalidCFFTable
 		}
-		p.offset = p.base + int(p.psi.topDict.charStrings)
 		count, offSize, ok := p.parseIndexHeader()
 		if !ok {
-			return nil, nil, nil, p.err
+			return glyphData{}, p.err
 		}
-		if count == 0 {
-			return nil, nil, nil, errInvalidCFFTable
+		if count == 0 || int(count) != numGlyphs {
+			return glyphData{}, errInvalidCFFTable
 		}
-		locations = make([]uint32, count+1)
-		if !p.parseIndexLocations(locations, count, offSize) {
-			return nil, nil, nil, p.err
+		ret.locations = make([]uint32, count+1)
+		if !p.parseIndexLocations(ret.locations, count, offSize) {
+			return glyphData{}, p.err
 		}
 	}
 
-	// Parse the Private DICT, whose location was found in the Top DICT.
+	if !p.psi.topDict.isCIDFont {
+		// Parse the Private DICT, whose location was found in the Top DICT.
+		ret.singleSubrs, err = p.parsePrivateDICT(
+			p.psi.topDict.privateDictOffset,
+			p.psi.topDict.privateDictLength,
+		)
+		if err != nil {
+			return glyphData{}, err
+		}
+
+	} else {
+		// Parse the Font Dict Select data, whose location was found in the Top
+		// DICT.
+		ret.fdSelect, err = p.parseFDSelect(p.psi.topDict.fdSelect, numGlyphs)
+		if err != nil {
+			return glyphData{}, err
+		}
+
+		// Parse the Font Dicts. Each one contains its own Private DICT.
+		if !p.seekFromBase(p.psi.topDict.fdArray) {
+			return glyphData{}, errInvalidCFFTable
+		}
+
+		count, offSize, ok := p.parseIndexHeader()
+		if !ok {
+			return glyphData{}, p.err
+		}
+		if count > maxNumFontDicts {
+			return glyphData{}, errUnsupportedNumberOfFontDicts
+		}
+
+		fdLocations := make([]uint32, count+1)
+		if !p.parseIndexLocations(fdLocations, count, offSize) {
+			return glyphData{}, p.err
+		}
+
+		privateDicts := make([]struct {
+			offset, length int32
+		}, count)
+
+		for i := range privateDicts {
+			length := fdLocations[i+1] - fdLocations[i]
+			if !p.read(int(length)) {
+				return glyphData{}, errInvalidCFFTable
+			}
+			p.psi.topDict.initialize()
+			if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil {
+				return glyphData{}, p.err
+			}
+			privateDicts[i].offset = p.psi.topDict.privateDictOffset
+			privateDicts[i].length = p.psi.topDict.privateDictLength
+		}
+
+		ret.multiSubrs = make([][]uint32, count)
+		for i, pd := range privateDicts {
+			ret.multiSubrs[i], err = p.parsePrivateDICT(pd.offset, pd.length)
+			if err != nil {
+				return glyphData{}, err
+			}
+		}
+	}
+
+	return ret, err
+}
+
+// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section
+// 19 "FDSelect".
+func (p *cffParser) parseFDSelect(offset int32, numGlyphs int) (ret fdSelect, err error) {
+	if !p.seekFromBase(p.psi.topDict.fdSelect) {
+		return fdSelect{}, errInvalidCFFTable
+	}
+	if !p.read(1) {
+		return fdSelect{}, p.err
+	}
+	ret.format = p.buf[0]
+	switch ret.format {
+	case 0:
+		if p.end-p.offset < numGlyphs {
+			return fdSelect{}, errInvalidCFFTable
+		}
+		ret.offset = int32(p.offset)
+		return ret, nil
+	case 3:
+		if !p.read(2) {
+			return fdSelect{}, p.err
+		}
+		ret.numRanges = u16(p.buf)
+		if p.end-p.offset < 3*int(ret.numRanges)+2 {
+			return fdSelect{}, errInvalidCFFTable
+		}
+		ret.offset = int32(p.offset)
+		return ret, nil
+	}
+	return fdSelect{}, errUnsupportedCFFFDSelectTable
+}
+
+func (p *cffParser) parsePrivateDICT(offset, length int32) (subrs []uint32, err error) {
 	p.psi.privateDict.initialize()
-	if p.psi.topDict.privateDictLength != 0 {
-		offset := p.psi.topDict.privateDictOffset
-		length := p.psi.topDict.privateDictLength
+	if length != 0 {
 		fullLength := int32(p.end - p.base)
 		if offset <= 0 || fullLength < offset || fullLength-offset < length || length < 0 {
-			return nil, nil, nil, errInvalidCFFTable
+			return nil, errInvalidCFFTable
 		}
 		p.offset = p.base + int(offset)
 		if !p.read(int(length)) {
-			return nil, nil, nil, p.err
+			return nil, p.err
 		}
 		if p.err = p.psi.run(psContextPrivateDict, p.buf, 0, 0); p.err != nil {
-			return nil, nil, nil, p.err
+			return nil, p.err
 		}
 	}
 
 	// 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
+	if p.psi.privateDict.subrsOffset != 0 {
+		if !p.seekFromBase(offset + p.psi.privateDict.subrsOffset) {
+			return nil, errInvalidCFFTable
 		}
-		p.offset = p.base + int(offset)
 		count, offSize, ok := p.parseIndexHeader()
 		if !ok {
-			return nil, nil, nil, p.err
+			return nil, p.err
 		}
 		if count != 0 {
 			if count > maxNumSubroutines {
-				return nil, nil, nil, errUnsupportedNumberOfSubroutines
+				return nil, errUnsupportedNumberOfSubroutines
 			}
 			subrs = make([]uint32, count+1)
 			if !p.parseIndexLocations(subrs, count, offSize) {
-				return nil, nil, nil, p.err
+				return nil, p.err
 			}
 		}
 	}
 
-	return locations, gsubrs, subrs, nil
+	return subrs, err
 }
 
 // read sets p.buf to view the n bytes from p.offset to p.offset+n. It also
@@ -263,11 +392,12 @@
 // maximize the opportunity to re-use p.buf's allocated memory when viewing the
 // underlying source data for subsequent read calls.
 func (p *cffParser) read(n int) (ok bool) {
-	if p.end-p.offset < n {
+	if n < 0 || p.end-p.offset < n {
 		p.err = errInvalidCFFTable
 		return false
 	}
 	p.buf, p.err = p.src.view(p.buf, p.offset, n)
+	// TODO: if p.err == io.EOF, change that to a different error??
 	p.offset += n
 	return p.err == nil
 }
@@ -281,6 +411,14 @@
 	return true
 }
 
+func (p *cffParser) seekFromBase(offset int32) (ok bool) {
+	if offset < 0 || int32(p.end-p.base) < offset {
+		return false
+	}
+	p.offset = p.base + int(offset)
+	return true
+}
+
 func (p *cffParser) parseIndexHeader() (count, offSize int32, ok bool) {
 	if !p.read(2) {
 		return 0, 0, false
@@ -367,7 +505,10 @@
 
 // psTopDictData contains fields specific to the Top DICT context.
 type psTopDictData struct {
-	charStrings       int32
+	charStringsOffset int32
+	fdArray           int32
+	fdSelect          int32
+	isCIDFont         bool
 	privateDictOffset int32
 	privateDictLength int32
 }
@@ -378,7 +519,7 @@
 
 // psPrivateDictData contains fields specific to the Private DICT context.
 type psPrivateDictData struct {
-	subrs int32
+	subrsOffset int32
 }
 
 func (d *psPrivateDictData) initialize() {
@@ -388,18 +529,24 @@
 // psType2CharstringsData contains fields specific to the Type 2 Charstrings
 // context.
 type psType2CharstringsData struct {
-	f         *Font
-	b         *Buffer
-	x, y      int32
-	hintBits  int32
-	seenWidth bool
-	ended     bool
+	f          *Font
+	b          *Buffer
+	x, y       int32
+	hintBits   int32
+	seenWidth  bool
+	ended      bool
+	glyphIndex GlyphIndex
+	// fdSelectIndexPlusOne is the result of the Font Dict Select lookup, plus
+	// one. That plus one lets us use the zero value to denote either unused
+	// (for CFF fonts with a single Font Dict) or lazily evaluated.
+	fdSelectIndexPlusOne int32
 }
 
-func (d *psType2CharstringsData) initialize(f *Font, b *Buffer) {
+func (d *psType2CharstringsData) initialize(f *Font, b *Buffer, glyphIndex GlyphIndex) {
 	*d = psType2CharstringsData{
-		f: f,
-		b: b,
+		f:          f,
+		b:          b,
+		glyphIndex: glyphIndex,
 	}
 }
 
@@ -513,7 +660,7 @@
 		number, hasResult = int32(int16(u16(p.instructions[1:]))), true
 		p.instructions = p.instructions[3:]
 
-	case b == 29 && p.ctx == psContextTopDict:
+	case b == 29 && p.ctx != psContextType2Charstring:
 		if len(p.instructions) < 5 {
 			return true, errInvalidCFFTable
 		}
@@ -651,7 +798,7 @@
 		15: {+1, "charset", nil},
 		16: {+1, "Encoding", nil},
 		17: {+1, "CharStrings", func(p *psInterpreter) error {
-			p.topDict.charStrings = p.argStack.a[p.argStack.top-1]
+			p.topDict.charStringsOffset = p.argStack.a[p.argStack.top-1]
 			return nil
 		}},
 		18: {+2, "Private", func(p *psInterpreter) error {
@@ -674,14 +821,23 @@
 		21: {+1, "PostScript", nil},
 		22: {+1, "BaseFontName", nil},
 		23: {-2, "BaseFontBlend", nil},
-		30: {+3, "ROS", nil},
+		30: {+3, "ROS", func(p *psInterpreter) error {
+			p.topDict.isCIDFont = true
+			return 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},
+		36: {+1, "FDArray", func(p *psInterpreter) error {
+			p.topDict.fdArray = p.argStack.a[p.argStack.top-1]
+			return nil
+		}},
+		37: {+1, "FDSelect", func(p *psInterpreter) error {
+			p.topDict.fdSelect = p.argStack.a[p.argStack.top-1]
+			return nil
+		}},
 		38: {+1, "FontName", nil},
 	}},
 
@@ -696,7 +852,7 @@
 		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]
+			p.privateDict.subrsOffset = p.argStack.a[p.argStack.top-1]
 			return nil
 		}},
 		20: {+1, "defaultWidthX", nil},
@@ -1123,8 +1279,29 @@
 	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 t2CCallgsubr(p *psInterpreter) error {
+	return t2CCall(p, p.type2Charstrings.f.cached.glyphData.gsubrs)
+}
+
+func t2CCallsubr(p *psInterpreter) error {
+	t := &p.type2Charstrings
+	d := &t.f.cached.glyphData
+	subrs := d.singleSubrs
+	if d.multiSubrs != nil {
+		if t.fdSelectIndexPlusOne == 0 {
+			index, err := d.fdSelect.lookup(t.f, t.b, t.glyphIndex)
+			if err != nil {
+				return err
+			}
+			if index < 0 || len(d.multiSubrs) <= index {
+				return errInvalidCFFTable
+			}
+			t.fdSelectIndexPlusOne = int32(index + 1)
+		}
+		subrs = d.multiSubrs[t.fdSelectIndexPlusOne-1]
+	}
+	return t2CCall(p, subrs)
+}
 
 func t2CCall(p *psInterpreter, subrs []uint32) error {
 	if p.callStack.top == psCallStackSize || len(subrs) == 0 {
diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go
index bf26719..3b57ecd 100644
--- a/font/sfnt/proprietary_test.go
+++ b/font/sfnt/proprietary_test.go
@@ -88,7 +88,7 @@
 }
 
 func TestProprietaryAdobeSourceHanSansSC(t *testing.T) {
-	testProprietary(t, "adobe", "SourceHanSansSC-Regular.otf", 65535, 2)
+	testProprietary(t, "adobe", "SourceHanSansSC-Regular.otf", 65535, -1)
 }
 
 func TestProprietaryAdobeSourceSansProOTF(t *testing.T) {
@@ -302,6 +302,18 @@
 			continue
 		}
 	}
+
+	for x, want := range proprietaryFDSelectTestCases[qualifiedFilename] {
+		got, err := f.cached.glyphData.fdSelect.lookup(f, &buf, x)
+		if err != nil {
+			t.Errorf("fdSelect.lookup(%d): %v", x, err)
+			continue
+		}
+		if got != want {
+			t.Errorf("fdSelect.lookup(%d): got %d, want %d", x, got, want)
+			continue
+		}
+	}
 }
 
 // proprietaryNumFonts holds the expected number of fonts in each collection,
@@ -446,6 +458,77 @@
 //	- for TrueType glyphs, ttx coordinates are absolute, and consecutive
 //	  off-curve points implies an on-curve point at the midpoint.
 var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
+	"adobe/SourceHanSansSC-Regular.otf": {
+		'!': {
+			// -312 123 callsubr # 123 + bias = 230
+			// :	# Arg stack is [-312].
+			// :	-13 140 -119 -21 return
+			// :	# Arg stack is [-312 -13 140 -119 -21].
+			// 120 callsubr # 120 + bias = 227
+			// :	# Arg stack is [-312 -13 140 -119 -21].
+			// :	hstemhm
+			// :	95 132 -103 75 return
+			// :	# Arg stack is [95 132 -103 75].
+			// hintmask 01010000
+			// 8 callsubr # 8 + bias = 115
+			// :	# Arg stack is [].
+			// :	130 221 rmoveto
+			moveTo(130, 221),
+			// :	63 hlineto
+			lineTo(193, 221),
+			// :	12 424 3 -735 callgsubr # -735 + bias = 396
+			// :	:	# Arg stack is [12 424 3].
+			// :	:	104 rlineto
+			lineTo(205, 645),
+			lineTo(208, 749),
+			// :	:	-93 hlineto
+			lineTo(115, 749),
+			// :	:	3 -104 rlineto
+			lineTo(118, 645),
+			// :	:	return
+			// :	:	# Arg stack is [].
+			// :	return
+			// :	# Arg stack is [].
+			// hintmask 01100000
+			// 106 callsubr # 106 + bias = 213
+			// :	# Arg stack is [].
+			// :	43 -658 rmoveto
+			moveTo(161, -13),
+			// :	37 29 28 41 return
+			// :	# Arg stack is [37 29 28 41].
+			// hvcurveto
+			cubeTo(198, -13, 227, 15, 227, 56),
+			// hintmask 10100000
+			// 41 -29 30 -37 -36 -30 -30 -41 vhcurveto
+			cubeTo(227, 97, 198, 127, 161, 127),
+			cubeTo(125, 127, 95, 97, 95, 56),
+			// hintmask 01100000
+			// 111 callsubr # 111 + bias = 218
+			// :	# Arg stack is [].
+			// :	-41 30 -28 36 vhcurveto
+			cubeTo(95, 15, 125, -13, 161, -13),
+			// :	endchar
+		},
+
+		'二': { // U+4E8C <CJK Ideograph> "two; twice"
+			// 23 81 510 79 hstem
+			// 60 881 cntrmask 11000000
+			// 144 693 rmoveto
+			moveTo(144, 693),
+			// -79 713 79 vlineto
+			lineTo(144, 614),
+			lineTo(857, 614),
+			lineTo(857, 693),
+			// -797 -589 rmoveto
+			moveTo(60, 104),
+			// -81 881 81 vlineto
+			lineTo(60, 23),
+			lineTo(941, 23),
+			lineTo(941, 104),
+			// endchar
+		},
+	},
+
 	"adobe/SourceSansPro-Regular.otf": {
 		',': {
 			// -309 -1 115 hstem
@@ -966,3 +1049,81 @@
 		{2048, font.HintingNone, [2]rune{'\uf041', '\uf042'}, 0},
 	},
 }
+
+// proprietaryFDSelectTestCases hold a sample of each font's Font Dict Select
+// (FDSelect) map. The numerical values can be verified by grepping the output
+// of the ttx tool:
+//
+//	grep CharString.*fdSelectIndex SourceHanSansSC-Regular.ttx
+//
+// will print lines like this:
+//
+//	<CharString name="cid00100" fdSelectIndex="15">
+//	<CharString name="cid00101" fdSelectIndex="15">
+//	<CharString name="cid00102" fdSelectIndex="3">
+//	<CharString name="cid00103" fdSelectIndex="15">
+//
+// As for what the values like 3 or 15 actually mean, grepping that ttx file
+// for "FontName" gives this list:
+//
+//	0:	<FontName value="SourceHanSansSC-Regular-Alphabetic"/>
+//	1:	<FontName value="SourceHanSansSC-Regular-AlphabeticDigits"/>
+//	2:	<FontName value="SourceHanSansSC-Regular-Bopomofo"/>
+//	3:	<FontName value="SourceHanSansSC-Regular-Dingbats"/>
+//	4:	<FontName value="SourceHanSansSC-Regular-DingbatsDigits"/>
+//	5:	<FontName value="SourceHanSansSC-Regular-Generic"/>
+//	6:	<FontName value="SourceHanSansSC-Regular-HDingbats"/>
+//	7:	<FontName value="SourceHanSansSC-Regular-HHangul"/>
+//	8:	<FontName value="SourceHanSansSC-Regular-HKana"/>
+//	9:	<FontName value="SourceHanSansSC-Regular-HWidth"/>
+//	10:	<FontName value="SourceHanSansSC-Regular-HWidthCJK"/>
+//	11:	<FontName value="SourceHanSansSC-Regular-HWidthDigits"/>
+//	12:	<FontName value="SourceHanSansSC-Regular-Hangul"/>
+//	13:	<FontName value="SourceHanSansSC-Regular-Ideographs"/>
+//	14:	<FontName value="SourceHanSansSC-Regular-Kana"/>
+//	15:	<FontName value="SourceHanSansSC-Regular-Proportional"/>
+//	16:	<FontName value="SourceHanSansSC-Regular-ProportionalCJK"/>
+//	17:	<FontName value="SourceHanSansSC-Regular-ProportionalDigits"/>
+//	18:	<FontName value="SourceHanSansSC-Regular-VKana"/>
+//
+// As a sanity check, the cmap table maps U+3127 BOPOMOFO LETTER I to the glyph
+// named "cid65353", proprietaryFDSelectTestCases here maps 65353 to Font Dict
+// 2, and the list immediately above maps 2 to "Bopomofo".
+var proprietaryFDSelectTestCases = map[string]map[GlyphIndex]int{
+	"adobe/SourceHanSansSC-Regular.otf": {
+		0:     5,
+		1:     15,
+		2:     15,
+		16:    15,
+		17:    17,
+		26:    17,
+		27:    15,
+		100:   15,
+		101:   15,
+		102:   3,
+		103:   15,
+		777:   4,
+		1000:  3,
+		2000:  3,
+		3000:  13,
+		4000:  13,
+		20000: 13,
+		48000: 12,
+		59007: 1,
+		59024: 0,
+		59087: 8,
+		59200: 7,
+		59211: 6,
+		60000: 13,
+		63000: 16,
+		63039: 9,
+		63060: 11,
+		63137: 10,
+		65353: 2,
+		65486: 14,
+		65505: 18,
+		65506: 5,
+		65533: 5,
+		65534: 5,
+	},
+}
diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go
index 356841d..73ec6c9 100644
--- a/font/sfnt/sfnt.go
+++ b/font/sfnt/sfnt.go
@@ -53,6 +53,7 @@
 	maxCompoundStackSize      = 64
 	maxGlyphDataLength        = 64 * 1024
 	maxHintBits               = 256
+	maxNumFontDicts           = 256
 	maxNumFonts               = 256
 	maxNumTables              = 256
 	maxRealNumberStrLen       = 64 // Maximum length in bytes of the "-123.456E-7" representation.
@@ -86,6 +87,7 @@
 	errInvalidTableTagOrder   = errors.New("sfnt: invalid table tag order")
 	errInvalidUCS2String      = errors.New("sfnt: invalid UCS-2 string")
 
+	errUnsupportedCFFFDSelectTable     = errors.New("sfnt: unsupported CFF FDSelect table")
 	errUnsupportedCFFVersion           = errors.New("sfnt: unsupported CFF version")
 	errUnsupportedCmapEncodings        = errors.New("sfnt: unsupported cmap encodings")
 	errUnsupportedCompoundGlyph        = errors.New("sfnt: unsupported compound glyph")
@@ -93,6 +95,7 @@
 	errUnsupportedKernTable            = errors.New("sfnt: unsupported kern table")
 	errUnsupportedRealNumberEncoding   = errors.New("sfnt: unsupported real number encoding")
 	errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
+	errUnsupportedNumberOfFontDicts    = errors.New("sfnt: unsupported number of font dicts")
 	errUnsupportedNumberOfFonts        = errors.New("sfnt: unsupported number of fonts")
 	errUnsupportedNumberOfHints        = errors.New("sfnt: unsupported number of hints")
 	errUnsupportedNumberOfSubroutines  = errors.New("sfnt: unsupported number of subroutines")
@@ -438,6 +441,7 @@
 	kern table
 
 	cached struct {
+		glyphData        glyphData
 		glyphIndex       glyphIndexFunc
 		indexToLocFormat bool // false means short, true means long.
 		isPostScript     bool
@@ -445,23 +449,11 @@
 		kernOffset       int32
 		postTableVersion uint32
 		unitsPerEm       Units
-
-		// 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
 	}
 }
 
 // NumGlyphs returns the number of glyphs in f.
-func (f *Font) NumGlyphs() int { return len(f.cached.locations) - 1 }
+func (f *Font) NumGlyphs() int { return len(f.cached.glyphData.locations) - 1 }
 
 // UnitsPerEm returns the number of units per em for f.
 func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm }
@@ -487,7 +479,11 @@
 	if err != nil {
 		return err
 	}
-	buf, numGlyphs, locations, gsubrs, subrs, err := f.parseMaxp(buf, indexToLocFormat, isPostScript)
+	buf, numGlyphs, err := f.parseMaxp(buf, isPostScript)
+	if err != nil {
+		return err
+	}
+	buf, glyphData, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
 	if err != nil {
 		return err
 	}
@@ -504,6 +500,7 @@
 		return err
 	}
 
+	f.cached.glyphData = glyphData
 	f.cached.glyphIndex = glyphIndex
 	f.cached.indexToLocFormat = indexToLocFormat
 	f.cached.isPostScript = isPostScript
@@ -511,9 +508,6 @@
 	f.cached.kernOffset = kernOffset
 	f.cached.postTableVersion = postTableVersion
 	f.cached.unitsPerEm = unitsPerEm
-	f.cached.locations = locations
-	f.cached.gsubrs = gsubrs
-	f.cached.subrs = subrs
 
 	return nil
 }
@@ -778,24 +772,44 @@
 	return buf, kernNumPairs, int32(offset) + headerSize, nil
 }
 
-func (f *Font) parseMaxp(buf []byte, indexToLocFormat, isPostScript bool) (buf1 []byte, numGlyphs int, locations, gsubrs, subrs []uint32, err error) {
+func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int, err error) {
 	// https://www.microsoft.com/typography/otspec/maxp.htm
 
 	if isPostScript {
 		if f.maxp.length != 6 {
-			return nil, 0, nil, nil, nil, errInvalidMaxpTable
+			return nil, 0, errInvalidMaxpTable
 		}
 	} else {
 		if f.maxp.length != 32 {
-			return nil, 0, nil, nil, nil, errInvalidMaxpTable
+			return nil, 0, errInvalidMaxpTable
 		}
 	}
 	u, err := f.src.u16(buf, f.maxp, 4)
 	if err != nil {
-		return nil, 0, nil, nil, nil, err
+		return nil, 0, err
 	}
-	numGlyphs = int(u)
+	return buf, int(u), nil
+}
 
+type glyphData struct {
+	// 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 []uint32 slice length equals 1 plus the number of subroutines
+	gsubrs      []uint32
+	singleSubrs []uint32
+	multiSubrs  [][]uint32
+
+	fdSelect fdSelect
+}
+
+func (f *Font) parseGlyphData(buf []byte, numGlyphs int, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, err error) {
 	if isPostScript {
 		p := cffParser{
 			src:    &f.src,
@@ -803,21 +817,21 @@
 			offset: int(f.cff.offset),
 			end:    int(f.cff.offset + f.cff.length),
 		}
-		locations, gsubrs, subrs, err = p.parse()
+		ret, err = p.parse(numGlyphs)
 		if err != nil {
-			return nil, 0, nil, nil, nil, err
+			return nil, glyphData{}, err
 		}
 	} else {
-		locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
+		ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
 		if err != nil {
-			return nil, 0, nil, nil, nil, err
+			return nil, glyphData{}, err
 		}
 	}
-	if len(locations) != numGlyphs+1 {
-		return nil, 0, nil, nil, nil, errInvalidLocationData
+	if len(ret.locations) != numGlyphs+1 {
+		return nil, glyphData{}, errInvalidLocationData
 	}
 
-	return buf, numGlyphs, locations, gsubrs, subrs, nil
+	return buf, ret, nil
 }
 
 func (f *Font) parsePost(buf []byte, numGlyphs int) (buf1 []byte, postTableVersion uint32, err error) {
@@ -866,8 +880,8 @@
 	if f.NumGlyphs() <= xx {
 		return nil, 0, 0, ErrNotFound
 	}
-	i := f.cached.locations[xx+0]
-	j := f.cached.locations[xx+1]
+	i := f.cached.glyphData.locations[xx+0]
+	j := f.cached.glyphData.locations[xx+1]
 	if j < i {
 		return nil, 0, 0, errInvalidGlyphDataLength
 	}
@@ -900,7 +914,7 @@
 		if err != nil {
 			return nil, err
 		}
-		b.psi.type2Charstrings.initialize(f, b)
+		b.psi.type2Charstrings.initialize(f, b, x)
 		if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil {
 			return nil, err
 		}