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:
parent
2e35bd52b4
commit
c0851fbc5b
|
@ -61,6 +61,9 @@ const (
|
||||||
// preceded by up to a maximum of 48 operands". 5177.Type2.pdf Appendix B
|
// preceded by up to a maximum of 48 operands". 5177.Type2.pdf Appendix B
|
||||||
// "Type 2 Charstring Implementation Limits" says that "Argument stack 48".
|
// "Type 2 Charstring Implementation Limits" says that "Argument stack 48".
|
||||||
psArgStackSize = 48
|
psArgStackSize = 48
|
||||||
|
|
||||||
|
// Similarly, Appendix B says "Subr nesting, stack limit 10".
|
||||||
|
psCallStackSize = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
func bigEndian(b []byte) uint32 {
|
func bigEndian(b []byte) uint32 {
|
||||||
|
@ -91,74 +94,162 @@ type cffParser struct {
|
||||||
psi psInterpreter
|
psi psInterpreter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *cffParser) parse() (locations []uint32, err error) {
|
func (p *cffParser) parse() (locations, gsubrs, subrs []uint32, err error) {
|
||||||
// Parse header.
|
// Parse the header.
|
||||||
{
|
{
|
||||||
if !p.read(4) {
|
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 {
|
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()
|
count, offSize, ok := p.parseIndexHeader()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, p.err
|
return nil, nil, nil, p.err
|
||||||
}
|
}
|
||||||
// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The
|
// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The
|
||||||
// Name INDEX in the CFF must contain only one entry".
|
// Name INDEX in the CFF must contain only one entry".
|
||||||
if count != 1 {
|
if count != 1 {
|
||||||
return nil, errInvalidCFFTable
|
return nil, nil, nil, errInvalidCFFTable
|
||||||
}
|
}
|
||||||
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
|
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
|
||||||
return nil, p.err
|
return nil, nil, nil, p.err
|
||||||
}
|
}
|
||||||
p.offset = int(p.locBuf[1])
|
p.offset = int(p.locBuf[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse Top DICT INDEX.
|
// Parse the Top DICT INDEX.
|
||||||
|
p.psi.topDict.initialize()
|
||||||
{
|
{
|
||||||
count, offSize, ok := p.parseIndexHeader()
|
count, offSize, ok := p.parseIndexHeader()
|
||||||
if !ok {
|
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
|
// 5176.CFF.pdf section 8 "Top DICT INDEX" says that the count here
|
||||||
// should match the count of the Name INDEX, which is 1.
|
// should match the count of the Name INDEX, which is 1.
|
||||||
if count != 1 {
|
if count != 1 {
|
||||||
return nil, errInvalidCFFTable
|
return nil, nil, nil, errInvalidCFFTable
|
||||||
}
|
}
|
||||||
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
|
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])) {
|
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, 0, 0); p.err != nil {
|
||||||
if p.err = p.psi.run(psContextTopDict, p.buf); p.err != nil {
|
return nil, nil, nil, p.err
|
||||||
return 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.
|
// 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()
|
// Parse the Private DICT, whose location was found in the Top DICT.
|
||||||
if !ok {
|
p.psi.privateDict.initialize()
|
||||||
return nil, p.err
|
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 locations, gsubrs, subrs, nil
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
return locations, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read sets p.buf to view the n bytes from p.offset to p.offset+n. It also
|
// 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
|
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) {
|
func (p *cffParser) parseIndexHeader() (count, offSize int32, ok bool) {
|
||||||
if !p.read(2) {
|
if !p.read(2) {
|
||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
|
@ -253,34 +353,53 @@ func (p *cffParser) parseIndexLocations(dst []uint32, count, offSize int32) (ok
|
||||||
return p.err == nil
|
return p.err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type psCallStackEntry struct {
|
||||||
|
offset, length uint32
|
||||||
|
}
|
||||||
|
|
||||||
type psContext uint32
|
type psContext uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
psContextTopDict psContext = iota
|
psContextTopDict psContext = iota
|
||||||
|
psContextPrivateDict
|
||||||
psContextType2Charstring
|
psContextType2Charstring
|
||||||
)
|
)
|
||||||
|
|
||||||
// psTopDictData contains fields specific to the Top DICT context.
|
// psTopDictData contains fields specific to the Top DICT context.
|
||||||
type psTopDictData struct {
|
type psTopDictData struct {
|
||||||
charStrings int32
|
charStrings int32
|
||||||
|
privateDictOffset int32
|
||||||
|
privateDictLength int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *psTopDictData) initialize() {
|
func (d *psTopDictData) initialize() {
|
||||||
*d = psTopDictData{}
|
*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
|
// psType2CharstringsData contains fields specific to the Type 2 Charstrings
|
||||||
// context.
|
// context.
|
||||||
type psType2CharstringsData struct {
|
type psType2CharstringsData struct {
|
||||||
segments []Segment
|
f *Font
|
||||||
|
b *Buffer
|
||||||
x, y int32
|
x, y int32
|
||||||
hintBits int32
|
hintBits int32
|
||||||
seenWidth bool
|
seenWidth bool
|
||||||
|
ended bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *psType2CharstringsData) initialize(segments []Segment) {
|
func (d *psType2CharstringsData) initialize(f *Font, b *Buffer) {
|
||||||
*d = psType2CharstringsData{
|
*d = psType2CharstringsData{
|
||||||
segments: segments,
|
f: f,
|
||||||
|
b: b,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,19 +407,45 @@ func (d *psType2CharstringsData) initialize(segments []Segment) {
|
||||||
type psInterpreter struct {
|
type psInterpreter struct {
|
||||||
ctx psContext
|
ctx psContext
|
||||||
instructions []byte
|
instructions []byte
|
||||||
|
instrOffset uint32
|
||||||
|
instrLength uint32
|
||||||
argStack struct {
|
argStack struct {
|
||||||
a [psArgStackSize]int32
|
a [psArgStackSize]int32
|
||||||
top int32
|
top int32
|
||||||
}
|
}
|
||||||
parseNumberBuf [maxRealNumberStrLen]byte
|
callStack struct {
|
||||||
|
a [psCallStackSize]psCallStackEntry
|
||||||
|
top int32
|
||||||
|
}
|
||||||
|
parseNumberBuf [maxRealNumberStrLen]byte
|
||||||
|
|
||||||
topDict psTopDictData
|
topDict psTopDictData
|
||||||
|
privateDict psPrivateDictData
|
||||||
type2Charstrings psType2CharstringsData
|
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.ctx = ctx
|
||||||
p.instructions = instructions
|
p.instructions = instructions
|
||||||
|
p.instrOffset = offset
|
||||||
|
p.instrLength = length
|
||||||
p.argStack.top = 0
|
p.argStack.top = 0
|
||||||
|
p.callStack.top = 0
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
for len(p.instructions) > 0 {
|
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
|
number, hasResult = int32(u32(p.instructions[1:])), true
|
||||||
p.instructions = p.instructions[5:]
|
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
|
// Parse a real number. This isn't listed in 5176.CFF.pdf Table 3
|
||||||
// "Operand Encoding" but that table lists integer encodings. Further
|
// "Operand Encoding" but that table lists integer encodings. Further
|
||||||
// down the page it says "A real number operand is provided in addition
|
// 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]
|
p.topDict.charStrings = p.argStack.a[p.argStack.top-1]
|
||||||
return nil
|
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.
|
// 2-byte operators. The first byte is the escape byte.
|
||||||
0: {+1, "Copyright", nil},
|
0: {+1, "Copyright", nil},
|
||||||
|
@ -536,6 +685,35 @@ var psOperators = [...][2][]psOperator{
|
||||||
38: {+1, "FontName", nil},
|
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
|
// The Type 2 Charstring operators are defined by 5177.Type2.pdf Appendix A
|
||||||
// "Type 2 Charstring Command Codes".
|
// "Type 2 Charstring Command Codes".
|
||||||
psContextType2Charstring: {{
|
psContextType2Charstring: {{
|
||||||
|
@ -550,8 +728,8 @@ var psOperators = [...][2][]psOperator{
|
||||||
7: {-1, "vlineto", t2CVlineto},
|
7: {-1, "vlineto", t2CVlineto},
|
||||||
8: {-1, "rrcurveto", t2CRrcurveto},
|
8: {-1, "rrcurveto", t2CRrcurveto},
|
||||||
9: {}, // Reserved.
|
9: {}, // Reserved.
|
||||||
10: {}, // callsubr.
|
10: {+1, "callsubr", t2CCallsubr},
|
||||||
11: {}, // return.
|
11: {+0, "return", t2CReturn},
|
||||||
12: {}, // escape.
|
12: {}, // escape.
|
||||||
13: {}, // Reserved.
|
13: {}, // Reserved.
|
||||||
14: {-1, "endchar", t2CEndchar},
|
14: {-1, "endchar", t2CEndchar},
|
||||||
|
@ -569,7 +747,7 @@ var psOperators = [...][2][]psOperator{
|
||||||
26: {-1, "vvcurveto", t2CVvcurveto},
|
26: {-1, "vvcurveto", t2CVvcurveto},
|
||||||
27: {-1, "hhcurveto", t2CHhcurveto},
|
27: {-1, "hhcurveto", t2CHhcurveto},
|
||||||
28: {}, // shortint.
|
28: {}, // shortint.
|
||||||
29: {}, // callgsubr.
|
29: {+1, "callgsubr", t2CCallgsubr},
|
||||||
30: {-1, "vhcurveto", t2CVhcurveto},
|
30: {-1, "vhcurveto", t2CVhcurveto},
|
||||||
31: {-1, "hvcurveto", t2CHvcurveto},
|
31: {-1, "hvcurveto", t2CHvcurveto},
|
||||||
}, {
|
}, {
|
||||||
|
@ -650,7 +828,7 @@ func t2CMask(p *psInterpreter) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func t2CAppendMoveto(p *psInterpreter) {
|
func t2CAppendMoveto(p *psInterpreter) {
|
||||||
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
|
||||||
Op: SegmentOpMoveTo,
|
Op: SegmentOpMoveTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{
|
||||||
0: fixed.Int26_6(p.type2Charstrings.x),
|
0: fixed.Int26_6(p.type2Charstrings.x),
|
||||||
|
@ -660,7 +838,7 @@ func t2CAppendMoveto(p *psInterpreter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func t2CAppendLineto(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,
|
Op: SegmentOpLineTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{
|
||||||
0: fixed.Int26_6(p.type2Charstrings.x),
|
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
|
p.type2Charstrings.y += dyc
|
||||||
xc := p.type2Charstrings.x
|
xc := p.type2Charstrings.x
|
||||||
yc := p.type2Charstrings.y
|
yc := p.type2Charstrings.y
|
||||||
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
p.type2Charstrings.b.segments = append(p.type2Charstrings.b.segments, Segment{
|
||||||
Op: SegmentOpCubeTo,
|
Op: SegmentOpCubeTo,
|
||||||
Args: [6]fixed.Int26_6{
|
Args: [6]fixed.Int26_6{
|
||||||
0: fixed.Int26_6(xa),
|
0: fixed.Int26_6(xa),
|
||||||
|
@ -926,9 +1104,76 @@ func t2CRrcurveto(p *psInterpreter) error {
|
||||||
return nil
|
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 {
|
func t2CEndchar(p *psInterpreter) error {
|
||||||
t2CReadWidth(p, 0)
|
t2CReadWidth(p, 0)
|
||||||
if p.argStack.top != 0 || len(p.instructions) != 0 {
|
if p.argStack.top != 0 || p.hasMoreInstructions() {
|
||||||
if p.argStack.top == 4 {
|
if p.argStack.top == 4 {
|
||||||
// TODO: process the implicit "seac" command as per 5177.Type2.pdf
|
// TODO: process the implicit "seac" command as per 5177.Type2.pdf
|
||||||
// Appendix C "Compatibility and Deprecated Operators".
|
// Appendix C "Compatibility and Deprecated Operators".
|
||||||
|
@ -936,5 +1181,6 @@ func t2CEndchar(p *psInterpreter) error {
|
||||||
}
|
}
|
||||||
return errInvalidCFFTable
|
return errInvalidCFFTable
|
||||||
}
|
}
|
||||||
|
p.type2Charstrings.ended = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProprietaryAdobeSourceCodeProOTF(t *testing.T) {
|
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) {
|
func TestProprietaryAdobeSourceCodeProTTF(t *testing.T) {
|
||||||
|
@ -92,7 +92,7 @@ func TestProprietaryAdobeSourceHanSansSC(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProprietaryAdobeSourceSansProOTF(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) {
|
func TestProprietaryAdobeSourceSansProTTF(t *testing.T) {
|
||||||
|
@ -449,7 +449,6 @@ var proprietaryGlyphIndexTestCases = map[string]map[rune]GlyphIndex{
|
||||||
var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
||||||
"adobe/SourceSansPro-Regular.otf": {
|
"adobe/SourceSansPro-Regular.otf": {
|
||||||
',': {
|
',': {
|
||||||
// - contour #0
|
|
||||||
// 67 -170 rmoveto
|
// 67 -170 rmoveto
|
||||||
moveTo(67, -170),
|
moveTo(67, -170),
|
||||||
// 81 34 50 67 86 vvcurveto
|
// 81 34 50 67 86 vvcurveto
|
||||||
|
@ -461,10 +460,10 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
||||||
cubeTo(130, -1, 134, -1, 137, 0),
|
cubeTo(130, -1, 134, -1, 137, 0),
|
||||||
// 1 -53 -34 -44 -57 -25 rrcurveto
|
// 1 -53 -34 -44 -57 -25 rrcurveto
|
||||||
cubeTo(138, -53, 104, -97, 47, -122),
|
cubeTo(138, -53, 104, -97, 47, -122),
|
||||||
|
// endchar
|
||||||
},
|
},
|
||||||
|
|
||||||
'Q': {
|
'Q': {
|
||||||
// - contour #0
|
|
||||||
// 332 57 rmoveto
|
// 332 57 rmoveto
|
||||||
moveTo(332, 57),
|
moveTo(332, 57),
|
||||||
// -117 -77 106 168 163 77 101 117 117 77 -101 -163 -168 -77 -106 -117 hvcurveto
|
// -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(138, 494, 215, 595, 332, 595),
|
||||||
cubeTo(449, 595, 526, 494, 526, 331),
|
cubeTo(449, 595, 526, 494, 526, 331),
|
||||||
cubeTo(526, 163, 449, 57, 332, 57),
|
cubeTo(526, 163, 449, 57, 332, 57),
|
||||||
// - contour #1
|
|
||||||
// 201 -222 rmoveto
|
// 201 -222 rmoveto
|
||||||
moveTo(533, -165),
|
moveTo(533, -165),
|
||||||
// 39 35 7 8 20 hvcurveto
|
// 39 35 7 8 20 hvcurveto
|
||||||
|
@ -491,6 +489,101 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
||||||
cubeTo(52, 138, 148, 11, 291, -9),
|
cubeTo(52, 138, 148, 11, 291, -9),
|
||||||
// -90 38 83 -66 121 hhcurveto
|
// -90 38 83 -66 121 hhcurveto
|
||||||
cubeTo(329, -99, 412, -165, 533, -165),
|
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
|
'Λ': { // U+039B GREEK CAPITAL LETTER LAMDA
|
||||||
|
@ -512,6 +605,7 @@ var proprietaryGlyphTestCases = map[string]map[rune][]Segment{
|
||||||
lineTo(305, 656),
|
lineTo(305, 656),
|
||||||
// -96 hlineto
|
// -96 hlineto
|
||||||
lineTo(209, 656),
|
lineTo(209, 656),
|
||||||
|
// endchar
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,10 @@ const (
|
||||||
// safe to call concurrently, as long as each call has a different *Buffer.
|
// safe to call concurrently, as long as each call has a different *Buffer.
|
||||||
maxCmapSegments = 20000
|
maxCmapSegments = 20000
|
||||||
|
|
||||||
|
// TODO: similarly, load subroutine locations lazily. Adobe's
|
||||||
|
// SourceHanSansSC-Regular.otf has up to 30000 subroutines.
|
||||||
|
maxNumSubroutines = 40000
|
||||||
|
|
||||||
maxCompoundRecursionDepth = 8
|
maxCompoundRecursionDepth = 8
|
||||||
maxCompoundStackSize = 64
|
maxCompoundStackSize = 64
|
||||||
maxGlyphDataLength = 64 * 1024
|
maxGlyphDataLength = 64 * 1024
|
||||||
|
@ -62,24 +66,25 @@ var (
|
||||||
// ErrNotFound indicates that the requested value was not found.
|
// ErrNotFound indicates that the requested value was not found.
|
||||||
ErrNotFound = errors.New("sfnt: not found")
|
ErrNotFound = errors.New("sfnt: not found")
|
||||||
|
|
||||||
errInvalidBounds = errors.New("sfnt: invalid bounds")
|
errInvalidBounds = errors.New("sfnt: invalid bounds")
|
||||||
errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
|
errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
|
||||||
errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
|
errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
|
||||||
errInvalidFont = errors.New("sfnt: invalid font")
|
errInvalidFont = errors.New("sfnt: invalid font")
|
||||||
errInvalidFontCollection = errors.New("sfnt: invalid font collection")
|
errInvalidFontCollection = errors.New("sfnt: invalid font collection")
|
||||||
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
||||||
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length")
|
||||||
errInvalidKernTable = errors.New("sfnt: invalid kern table")
|
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
||||||
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
|
errInvalidKernTable = errors.New("sfnt: invalid kern table")
|
||||||
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
|
||||||
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
||||||
errInvalidNameTable = errors.New("sfnt: invalid name table")
|
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
||||||
errInvalidPostTable = errors.New("sfnt: invalid post table")
|
errInvalidNameTable = errors.New("sfnt: invalid name table")
|
||||||
errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)")
|
errInvalidPostTable = errors.New("sfnt: invalid post table")
|
||||||
errInvalidSourceData = errors.New("sfnt: invalid source data")
|
errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)")
|
||||||
errInvalidTableOffset = errors.New("sfnt: invalid table offset")
|
errInvalidSourceData = errors.New("sfnt: invalid source data")
|
||||||
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
errInvalidTableOffset = errors.New("sfnt: invalid table offset")
|
||||||
errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
|
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
||||||
|
errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
|
||||||
|
|
||||||
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
||||||
errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
|
errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
|
||||||
|
@ -90,6 +95,7 @@ var (
|
||||||
errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
|
errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
|
||||||
errUnsupportedNumberOfFonts = errors.New("sfnt: unsupported number of fonts")
|
errUnsupportedNumberOfFonts = errors.New("sfnt: unsupported number of fonts")
|
||||||
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
||||||
|
errUnsupportedNumberOfSubroutines = errors.New("sfnt: unsupported number of subroutines")
|
||||||
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
||||||
errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
|
errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
|
||||||
errUnsupportedPostTable = errors.New("sfnt: unsupported post table")
|
errUnsupportedPostTable = errors.New("sfnt: unsupported post table")
|
||||||
|
@ -440,9 +446,17 @@ type Font struct {
|
||||||
postTableVersion uint32
|
postTableVersion uint32
|
||||||
unitsPerEm Units
|
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]].
|
// src[locations[i+0]:locations[i+1]].
|
||||||
|
//
|
||||||
|
// The slice length equals 1 plus the number of glyphs.
|
||||||
locations []uint32
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -498,6 +512,8 @@ func (f *Font) initialize(offset int) error {
|
||||||
f.cached.postTableVersion = postTableVersion
|
f.cached.postTableVersion = postTableVersion
|
||||||
f.cached.unitsPerEm = unitsPerEm
|
f.cached.unitsPerEm = unitsPerEm
|
||||||
f.cached.locations = locations
|
f.cached.locations = locations
|
||||||
|
f.cached.gsubrs = gsubrs
|
||||||
|
f.cached.subrs = subrs
|
||||||
|
|
||||||
return nil
|
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
|
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
|
// https://www.microsoft.com/typography/otspec/maxp.htm
|
||||||
|
|
||||||
if isPostScript {
|
if isPostScript {
|
||||||
if f.maxp.length != 6 {
|
if f.maxp.length != 6 {
|
||||||
return nil, 0, nil, errInvalidMaxpTable
|
return nil, 0, nil, nil, nil, errInvalidMaxpTable
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if f.maxp.length != 32 {
|
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)
|
u, err := f.src.u16(buf, f.maxp, 4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
numGlyphs = int(u)
|
numGlyphs = int(u)
|
||||||
|
|
||||||
|
@ -787,21 +803,21 @@ func (f *Font) parseMaxp(buf []byte, indexToLocFormat, isPostScript bool) (buf1
|
||||||
offset: int(f.cff.offset),
|
offset: int(f.cff.offset),
|
||||||
end: int(f.cff.offset + f.cff.length),
|
end: int(f.cff.offset + f.cff.length),
|
||||||
}
|
}
|
||||||
locations, err = p.parse()
|
locations, gsubrs, subrs, err = p.parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
|
locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(locations) != numGlyphs+1 {
|
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) {
|
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)
|
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)
|
xx := int(x)
|
||||||
if f.NumGlyphs() <= xx {
|
if f.NumGlyphs() <= xx {
|
||||||
return nil, ErrNotFound
|
return nil, 0, 0, ErrNotFound
|
||||||
}
|
}
|
||||||
i := f.cached.locations[xx+0]
|
i := f.cached.locations[xx+0]
|
||||||
j := f.cached.locations[xx+1]
|
j := f.cached.locations[xx+1]
|
||||||
if j-i > maxGlyphDataLength {
|
if j < i {
|
||||||
return nil, errUnsupportedGlyphDataLength
|
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.
|
// 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]
|
b.segments = b.segments[:0]
|
||||||
if f.cached.isPostScript {
|
if f.cached.isPostScript {
|
||||||
buf, err := f.viewGlyphData(b, x)
|
buf, offset, length, err := f.viewGlyphData(b, x)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.psi.type2Charstrings.initialize(b.segments)
|
b.psi.type2Charstrings.initialize(f, b)
|
||||||
if err := b.psi.run(psContextType2Charstring, buf); err != nil {
|
if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.segments = b.psi.type2Charstrings.segments
|
if !b.psi.type2Charstrings.ended {
|
||||||
|
return nil, errInvalidCFFTable
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := loadGlyf(f, b, x, 0, 0); err != nil {
|
if err := loadGlyf(f, b, x, 0, 0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -85,7 +85,7 @@ func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool
|
||||||
const glyfHeaderLen = 10
|
const glyfHeaderLen = 10
|
||||||
|
|
||||||
func loadGlyf(f *Font, b *Buffer, x GlyphIndex, stackBottom, recursionDepth uint32) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user