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