зеркало из https://github.com/golang/image.git
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:
Родитель
2e35bd52b4
Коммит
c0851fbc5b
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче