font/sfnt: support PostScript compound glyphs (subroutines).
Change-Id: I8aa10150aa004b1bc1128bf0b3d5c14b74ee089c
Reviewed-on: https://go-review.googlesource.com/38280
Reviewed-by: David Crawshaw <crawshaw@golang.org>
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 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 @@
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 @@
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 @@
}
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 @@
)
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 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 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
}