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>
This commit is contained in:
Nigel Tao 2017-03-18 17:56:20 +11:00
parent 2e35bd52b4
commit c0851fbc5b
4 changed files with 450 additions and 88 deletions

View File

@ -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
}

View File

@ -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
},
},

View File

@ -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

View File

@ -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
}