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
}