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 @@ func bigEndian(b []byte) uint32 { 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 @@ type cffParser struct { 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 @@ func (p *cffParser) parse() (locations, gsubrs, subrs []uint32, err error) { { 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 @@ func (p *cffParser) parse() (locations, gsubrs, subrs []uint32, err error) { { 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 @@ func (p *cffParser) parse() (locations, gsubrs, subrs []uint32, err error) { { 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 @@ func (p *cffParser) parse() (locations, gsubrs, subrs []uint32, err error) { { 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 @@ func (p *cffParser) parse() (locations, gsubrs, subrs []uint32, err error) { // 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 @@ func (p *cffParser) skip(n int) (ok bool) { 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 @@ const ( // 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 @@ func (d *psTopDictData) initialize() { // psPrivateDictData contains fields specific to the Private DICT context. type psPrivateDictData struct { - subrs int32 + subrsOffset int32 } func (d *psPrivateDictData) initialize() { @@ -388,18 +529,24 @@ func (d *psPrivateDictData) initialize() { // 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 @@ func (p *psInterpreter) parseNumber() (hasResult bool, err error) { 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 @@ var psOperators = [...][2][]psOperator{ 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 @@ var psOperators = [...][2][]psOperator{ 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 @@ var psOperators = [...][2][]psOperator{ 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 @@ func subrBias(numSubroutines int) int32 { 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 TestProprietaryAdobeSourceCodeProTTF(t *testing.T) { } 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 @@ kernLoop: 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 @@ var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{ // - 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 "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 @@ var proprietaryKernTestCases = map[string][]kernTestCase{ {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: +// +// +// +// +// +// +// As for what the values like 3 or 15 actually mean, grepping that ttx file +// for "FontName" gives this list: +// +// 0: +// 1: +// 2: +// 3: +// 4: +// 5: +// 6: +// 7: +// 8: +// 9: +// 10: +// 11: +// 12: +// 13: +// 14: +// 15: +// 16: +// 17: +// 18: +// +// 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 @@ const ( 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 @@ var ( 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 @@ var ( 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 @@ type Font struct { kern table cached struct { + glyphData glyphData glyphIndex glyphIndexFunc indexToLocFormat bool // false means short, true means long. isPostScript bool @@ -445,23 +449,11 @@ type Font struct { 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 @@ func (f *Font) initialize(offset int) error { 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 @@ func (f *Font) initialize(offset int) error { return err } + f.cached.glyphData = glyphData f.cached.glyphIndex = glyphIndex f.cached.indexToLocFormat = indexToLocFormat f.cached.isPostScript = isPostScript @@ -511,9 +508,6 @@ func (f *Font) initialize(offset int) error { 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 @@ func (f *Font) parseKernFormat0(buf []byte, offset, length int) (buf1 []byte, ke 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 @@ func (f *Font) parseMaxp(buf []byte, indexToLocFormat, isPostScript bool) (buf1 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 @@ func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, lengt 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 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *Load 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 }