2016-11-27 10:19:50 +01:00
|
|
|
// Copyright 2016 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package sfnt
|
|
|
|
|
|
|
|
// Compact Font Format (CFF) fonts are written in PostScript, a stack-based
|
|
|
|
// programming language.
|
|
|
|
//
|
|
|
|
// A fundamental concept is a DICT, or a key-value map, expressed in reverse
|
|
|
|
// Polish notation. For example, this sequence of operations:
|
|
|
|
// - push the number 379
|
|
|
|
// - version operator
|
|
|
|
// - push the number 392
|
|
|
|
// - Notice operator
|
|
|
|
// - etc
|
|
|
|
// - push the number 100
|
|
|
|
// - push the number 0
|
|
|
|
// - push the number 500
|
|
|
|
// - push the number 800
|
|
|
|
// - FontBBox operator
|
|
|
|
// - etc
|
2016-12-02 07:58:28 +01:00
|
|
|
// defines a DICT that maps "version" to the String ID (SID) 379, "Notice" to
|
|
|
|
// the SID 392, "FontBBox" to the four numbers [100, 0, 500, 800], etc.
|
2016-11-27 10:19:50 +01:00
|
|
|
//
|
|
|
|
// The first 391 String IDs (starting at 0) are predefined as per the CFF spec
|
|
|
|
// Appendix A, in 5176.CFF.pdf referenced below. For example, 379 means
|
|
|
|
// "001.000". String ID 392 is not predefined, and is mapped by a separate
|
|
|
|
// structure, the "String INDEX", inside the CFF data. (String ID 391 is also
|
|
|
|
// not predefined. Specifically for ../testdata/CFFTest.otf, 391 means
|
|
|
|
// "uni4E2D", as this font contains a glyph for U+4E2D).
|
|
|
|
//
|
|
|
|
// The actual glyph vectors are similarly encoded (in PostScript), in a format
|
|
|
|
// called Type 2 Charstrings. The wire encoding is similar to but not exactly
|
|
|
|
// the same as CFF's. For example, the byte 0x05 means FontBBox for CFF DICTs,
|
|
|
|
// but means rlineto (relative line-to) for Type 2 Charstrings. See
|
|
|
|
// 5176.CFF.pdf Appendix H and 5177.Type2.pdf Appendix A in the PDF files
|
|
|
|
// referenced below.
|
|
|
|
//
|
|
|
|
// CFF is a stand-alone format, but CFF as used in SFNT fonts have further
|
|
|
|
// restrictions. For example, a stand-alone CFF can contain multiple fonts, but
|
|
|
|
// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The Name
|
|
|
|
// INDEX in the CFF must contain only one entry; that is, there must be only
|
|
|
|
// one font in the CFF FontSet".
|
|
|
|
//
|
|
|
|
// The relevant specifications are:
|
|
|
|
// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf
|
|
|
|
// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5177.Type2.pdf
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2016-12-02 07:58:28 +01:00
|
|
|
"math"
|
|
|
|
"strconv"
|
2016-12-05 13:52:00 +01:00
|
|
|
|
|
|
|
"golang.org/x/image/math/fixed"
|
2016-11-27 10:19:50 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// psStackSize is the stack size for a PostScript interpreter. 5176.CFF.pdf
|
|
|
|
// section 4 "DICT Data" says that "An operator may be preceded by up to a
|
|
|
|
// maximum of 48 operands". Similarly, 5177.Type2.pdf Appendix B "Type 2
|
|
|
|
// Charstring Implementation Limits" says that "Argument stack 48".
|
|
|
|
psStackSize = 48
|
|
|
|
)
|
|
|
|
|
|
|
|
func bigEndian(b []byte) uint32 {
|
|
|
|
switch len(b) {
|
|
|
|
case 1:
|
|
|
|
return uint32(b[0])
|
|
|
|
case 2:
|
|
|
|
return uint32(b[0])<<8 | uint32(b[1])
|
|
|
|
case 3:
|
|
|
|
return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])
|
|
|
|
case 4:
|
|
|
|
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
|
|
|
|
}
|
|
|
|
panic("unreachable")
|
|
|
|
}
|
|
|
|
|
|
|
|
// cffParser parses the CFF table from an SFNT font.
|
|
|
|
type cffParser struct {
|
|
|
|
src *source
|
|
|
|
base int
|
|
|
|
offset int
|
|
|
|
end int
|
|
|
|
err error
|
2016-12-02 07:58:28 +01:00
|
|
|
|
|
|
|
buf []byte
|
2016-11-27 10:19:50 +01:00
|
|
|
locBuf [2]uint32
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
psi psInterpreter
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *cffParser) parse() (locations []uint32, err error) {
|
|
|
|
// Parse header.
|
|
|
|
{
|
|
|
|
if !p.read(4) {
|
|
|
|
return nil, p.err
|
|
|
|
}
|
|
|
|
if p.buf[0] != 1 || p.buf[1] != 0 || p.buf[2] != 4 {
|
|
|
|
return nil, errUnsupportedCFFVersion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse Name INDEX.
|
|
|
|
{
|
|
|
|
count, offSize, ok := p.parseIndexHeader()
|
|
|
|
if !ok {
|
|
|
|
return 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
|
|
|
|
}
|
|
|
|
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
|
|
|
|
return nil, p.err
|
|
|
|
}
|
|
|
|
p.offset = int(p.locBuf[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse Top DICT INDEX.
|
|
|
|
{
|
|
|
|
count, offSize, ok := p.parseIndexHeader()
|
|
|
|
if !ok {
|
|
|
|
return 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
|
|
|
|
}
|
|
|
|
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
|
|
|
|
return nil, p.err
|
|
|
|
}
|
|
|
|
if !p.read(int(p.locBuf[1] - p.locBuf[0])) {
|
|
|
|
return nil, p.err
|
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
p.psi.topDict.initialize()
|
|
|
|
if p.err = p.psi.run(psContextTopDict, p.buf); p.err != nil {
|
|
|
|
return nil, p.err
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the CharStrings INDEX, whose location was found in the Top DICT.
|
2016-12-05 13:52:00 +01:00
|
|
|
if p.psi.topDict.charStrings <= 0 || int32(p.end-p.base) < p.psi.topDict.charStrings {
|
2016-11-27 10:19:50 +01:00
|
|
|
return nil, errInvalidCFFTable
|
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
p.offset = p.base + int(p.psi.topDict.charStrings)
|
2016-11-27 10:19:50 +01:00
|
|
|
count, offSize, ok := p.parseIndexHeader()
|
|
|
|
if !ok {
|
|
|
|
return nil, p.err
|
|
|
|
}
|
|
|
|
if count == 0 {
|
|
|
|
return nil, errInvalidCFFTable
|
|
|
|
}
|
|
|
|
locations = make([]uint32, count+1)
|
|
|
|
if !p.parseIndexLocations(locations, count, offSize) {
|
|
|
|
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
|
|
|
|
// advances p.offset by n.
|
|
|
|
//
|
|
|
|
// As per the source.view method, the caller should not modify the contents of
|
|
|
|
// p.buf after read returns, other than by calling read again.
|
|
|
|
//
|
|
|
|
// The caller should also avoid modifying the pointer / length / capacity of
|
|
|
|
// the p.buf slice, not just avoid modifying the slice's contents, in order to
|
|
|
|
// maximize the opportunity to re-use p.buf's allocated memory when viewing the
|
|
|
|
// underlying source data for subsequent read calls.
|
|
|
|
func (p *cffParser) read(n int) (ok bool) {
|
|
|
|
if p.end-p.offset < n {
|
|
|
|
p.err = errInvalidCFFTable
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
p.buf, p.err = p.src.view(p.buf, p.offset, n)
|
|
|
|
p.offset += n
|
|
|
|
return p.err == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *cffParser) parseIndexHeader() (count, offSize int32, ok bool) {
|
|
|
|
if !p.read(2) {
|
|
|
|
return 0, 0, false
|
|
|
|
}
|
|
|
|
count = int32(u16(p.buf[:2]))
|
|
|
|
// 5176.CFF.pdf section 5 "INDEX Data" says that "An empty INDEX is
|
|
|
|
// represented by a count field with a 0 value and no additional fields.
|
|
|
|
// Thus, the total size of an empty INDEX is 2 bytes".
|
|
|
|
if count == 0 {
|
|
|
|
return count, 0, true
|
|
|
|
}
|
|
|
|
if !p.read(1) {
|
|
|
|
return 0, 0, false
|
|
|
|
}
|
|
|
|
offSize = int32(p.buf[0])
|
|
|
|
if offSize < 1 || 4 < offSize {
|
|
|
|
p.err = errInvalidCFFTable
|
|
|
|
return 0, 0, false
|
|
|
|
}
|
|
|
|
return count, offSize, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *cffParser) parseIndexLocations(dst []uint32, count, offSize int32) (ok bool) {
|
|
|
|
if count == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if len(dst) != int(count+1) {
|
|
|
|
panic("unreachable")
|
|
|
|
}
|
|
|
|
if !p.read(len(dst) * int(offSize)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
buf, prev := p.buf, uint32(0)
|
|
|
|
for i := range dst {
|
|
|
|
loc := bigEndian(buf[:offSize])
|
|
|
|
buf = buf[offSize:]
|
|
|
|
|
|
|
|
// Locations are off by 1 byte. 5176.CFF.pdf section 5 "INDEX Data"
|
|
|
|
// says that "Offsets in the offset array are relative to the byte that
|
|
|
|
// precedes the object data... This ensures that every object has a
|
|
|
|
// corresponding offset which is always nonzero".
|
|
|
|
if loc == 0 {
|
|
|
|
p.err = errInvalidCFFTable
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
loc--
|
|
|
|
|
|
|
|
// In the same paragraph, "Therefore the first element of the offset
|
|
|
|
// array is always 1" before correcting for the off-by-1.
|
|
|
|
if i == 0 {
|
|
|
|
if loc != 0 {
|
|
|
|
p.err = errInvalidCFFTable
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else if loc <= prev { // Check that locations are increasing.
|
|
|
|
p.err = errInvalidCFFTable
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that locations are in bounds.
|
|
|
|
if uint32(p.end-p.offset) < loc {
|
|
|
|
p.err = errInvalidCFFTable
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
dst[i] = uint32(p.offset) + loc
|
|
|
|
prev = loc
|
|
|
|
}
|
|
|
|
return p.err == nil
|
|
|
|
}
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
type psContext uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
psContextTopDict psContext = iota
|
|
|
|
psContextType2Charstring
|
|
|
|
)
|
|
|
|
|
|
|
|
// psTopDictData contains fields specific to the Top DICT context.
|
|
|
|
type psTopDictData struct {
|
|
|
|
charStrings int32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *psTopDictData) initialize() {
|
|
|
|
*d = psTopDictData{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// psType2CharstringsData contains fields specific to the Type 2 Charstrings
|
|
|
|
// context.
|
|
|
|
type psType2CharstringsData struct {
|
|
|
|
segments []Segment
|
|
|
|
x, y int32
|
|
|
|
hintBits int32
|
|
|
|
seenWidth bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *psType2CharstringsData) initialize(segments []Segment) {
|
|
|
|
*d = psType2CharstringsData{
|
|
|
|
segments: segments,
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
}
|
2016-11-27 10:19:50 +01:00
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
// psInterpreter is a PostScript interpreter.
|
|
|
|
type psInterpreter struct {
|
|
|
|
ctx psContext
|
|
|
|
instructions []byte
|
|
|
|
stack struct {
|
|
|
|
a [psStackSize]int32
|
|
|
|
top int32
|
|
|
|
}
|
|
|
|
parseNumberBuf [maxRealNumberStrLen]byte
|
|
|
|
topDict psTopDictData
|
|
|
|
type2Charstrings psType2CharstringsData
|
|
|
|
}
|
2016-12-02 07:58:28 +01:00
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
func (p *psInterpreter) run(ctx psContext, instructions []byte) error {
|
|
|
|
p.ctx = ctx
|
|
|
|
p.instructions = instructions
|
|
|
|
p.stack.top = 0
|
|
|
|
|
|
|
|
loop:
|
|
|
|
for len(p.instructions) > 0 {
|
|
|
|
// Push a numeric operand on the stack, if applicable.
|
|
|
|
if hasResult, err := p.parseNumber(); hasResult {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
2016-12-02 07:58:28 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
// Otherwise, execute an operator.
|
|
|
|
b := p.instructions[0]
|
|
|
|
p.instructions = p.instructions[1:]
|
|
|
|
|
|
|
|
for escaped, ops := false, psOperators[ctx][0]; ; {
|
|
|
|
if b == escapeByte && !escaped {
|
|
|
|
if len(p.instructions) <= 0 {
|
|
|
|
return errInvalidCFFTable
|
2016-12-02 07:58:28 +01:00
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
b = p.instructions[0]
|
|
|
|
p.instructions = p.instructions[1:]
|
|
|
|
escaped = true
|
|
|
|
ops = psOperators[ctx][1]
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if int(b) < len(ops) {
|
|
|
|
if op := ops[b]; op.name != "" {
|
|
|
|
if p.stack.top < op.numPop {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
if op.run != nil {
|
|
|
|
if err := op.run(p); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if op.numPop < 0 {
|
|
|
|
p.stack.top = 0
|
|
|
|
} else {
|
|
|
|
p.stack.top -= op.numPop
|
|
|
|
}
|
|
|
|
continue loop
|
2016-12-02 07:58:28 +01:00
|
|
|
}
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
if escaped {
|
|
|
|
return fmt.Errorf("sfnt: unrecognized CFF 2-byte operator (12 %d)", b)
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("sfnt: unrecognized CFF 1-byte operator (%d)", b)
|
|
|
|
}
|
2016-12-02 07:58:28 +01:00
|
|
|
}
|
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
return nil
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// See 5176.CFF.pdf section 4 "DICT Data".
|
2016-12-05 13:52:00 +01:00
|
|
|
func (p *psInterpreter) parseNumber() (hasResult bool, err error) {
|
|
|
|
number := int32(0)
|
|
|
|
switch b := p.instructions[0]; {
|
|
|
|
case b == 28:
|
2016-11-27 10:19:50 +01:00
|
|
|
if len(p.instructions) < 3 {
|
2016-12-05 13:52:00 +01:00
|
|
|
return true, errInvalidCFFTable
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
number, hasResult = int32(int16(u16(p.instructions[1:]))), true
|
2016-11-27 10:19:50 +01:00
|
|
|
p.instructions = p.instructions[3:]
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
case b == 29 && p.ctx == psContextTopDict:
|
2016-11-27 10:19:50 +01:00
|
|
|
if len(p.instructions) < 5 {
|
2016-12-05 13:52:00 +01:00
|
|
|
return true, errInvalidCFFTable
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
number, hasResult = int32(u32(p.instructions[1:])), true
|
2016-11-27 10:19:50 +01:00
|
|
|
p.instructions = p.instructions[5:]
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
case b == 30 && p.ctx == psContextTopDict:
|
2016-12-02 07:58:28 +01:00
|
|
|
// 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
|
|
|
|
// to integer operands. This operand begins with a byte value of 30
|
|
|
|
// followed by a variable-length sequence of bytes."
|
|
|
|
|
|
|
|
s := p.parseNumberBuf[:0]
|
|
|
|
p.instructions = p.instructions[1:]
|
2016-12-05 13:52:00 +01:00
|
|
|
loop:
|
2016-12-02 07:58:28 +01:00
|
|
|
for {
|
|
|
|
if len(p.instructions) == 0 {
|
2016-12-05 13:52:00 +01:00
|
|
|
return true, errInvalidCFFTable
|
2016-12-02 07:58:28 +01:00
|
|
|
}
|
|
|
|
b := p.instructions[0]
|
|
|
|
p.instructions = p.instructions[1:]
|
|
|
|
// Process b's two nibbles, high then low.
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
nib := b >> 4
|
|
|
|
b = b << 4
|
|
|
|
if nib == 0x0f {
|
|
|
|
f, err := strconv.ParseFloat(string(s), 32)
|
|
|
|
if err != nil {
|
2016-12-05 13:52:00 +01:00
|
|
|
return true, errInvalidCFFTable
|
2016-12-02 07:58:28 +01:00
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
number, hasResult = int32(math.Float32bits(float32(f))), true
|
|
|
|
break loop
|
2016-12-02 07:58:28 +01:00
|
|
|
}
|
|
|
|
if nib == 0x0d {
|
2016-12-05 13:52:00 +01:00
|
|
|
return true, errInvalidCFFTable
|
2016-12-02 07:58:28 +01:00
|
|
|
}
|
|
|
|
if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) {
|
2016-12-05 13:52:00 +01:00
|
|
|
return true, errUnsupportedRealNumberEncoding
|
2016-12-02 07:58:28 +01:00
|
|
|
}
|
|
|
|
s = append(s, nibbleDefs[nib]...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
case b < 32:
|
2016-11-27 10:19:50 +01:00
|
|
|
// No-op.
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
case b < 247:
|
2016-11-27 10:19:50 +01:00
|
|
|
p.instructions = p.instructions[1:]
|
2016-12-05 13:52:00 +01:00
|
|
|
number, hasResult = int32(b)-139, true
|
2016-11-27 10:19:50 +01:00
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
case b < 251:
|
2016-11-27 10:19:50 +01:00
|
|
|
if len(p.instructions) < 2 {
|
2016-12-05 13:52:00 +01:00
|
|
|
return true, errInvalidCFFTable
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
b1 := p.instructions[1]
|
|
|
|
p.instructions = p.instructions[2:]
|
2016-12-05 13:52:00 +01:00
|
|
|
number, hasResult = +int32(b-247)*256+int32(b1)+108, true
|
2016-11-27 10:19:50 +01:00
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
case b < 255:
|
2016-11-27 10:19:50 +01:00
|
|
|
if len(p.instructions) < 2 {
|
2016-12-05 13:52:00 +01:00
|
|
|
return true, errInvalidCFFTable
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
b1 := p.instructions[1]
|
|
|
|
p.instructions = p.instructions[2:]
|
2016-12-05 13:52:00 +01:00
|
|
|
number, hasResult = -int32(b-251)*256-int32(b1)-108, true
|
|
|
|
|
|
|
|
case b == 255 && p.ctx == psContextType2Charstring:
|
|
|
|
if len(p.instructions) < 5 {
|
|
|
|
return true, errInvalidCFFTable
|
|
|
|
}
|
|
|
|
number, hasResult = int32(u32(p.instructions[1:])), true
|
|
|
|
p.instructions = p.instructions[5:]
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
if hasResult {
|
|
|
|
if p.stack.top == psStackSize {
|
|
|
|
return true, errInvalidCFFTable
|
|
|
|
}
|
|
|
|
p.stack.a[p.stack.top] = number
|
|
|
|
p.stack.top++
|
|
|
|
}
|
|
|
|
return hasResult, nil
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
|
2016-12-02 07:58:28 +01:00
|
|
|
const maxNibbleDefsLength = len("E-")
|
|
|
|
|
|
|
|
// nibbleDefs encodes 5176.CFF.pdf Table 5 "Nibble Definitions".
|
|
|
|
var nibbleDefs = [16]string{
|
|
|
|
0x00: "0",
|
|
|
|
0x01: "1",
|
|
|
|
0x02: "2",
|
|
|
|
0x03: "3",
|
|
|
|
0x04: "4",
|
|
|
|
0x05: "5",
|
|
|
|
0x06: "6",
|
|
|
|
0x07: "7",
|
|
|
|
0x08: "8",
|
|
|
|
0x09: "9",
|
|
|
|
0x0a: ".",
|
|
|
|
0x0b: "E",
|
|
|
|
0x0c: "E-",
|
|
|
|
0x0d: "",
|
|
|
|
0x0e: "-",
|
|
|
|
0x0f: "",
|
|
|
|
}
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
type psOperator struct {
|
2016-11-27 10:19:50 +01:00
|
|
|
// numPop is the number of stack values to pop. -1 means "array" and -2
|
|
|
|
// means "delta" as per 5176.CFF.pdf Table 6 "Operand Types".
|
|
|
|
numPop int32
|
|
|
|
// name is the operator name. An empty name (i.e. the zero value for the
|
|
|
|
// struct overall) means an unrecognized 1-byte operator.
|
|
|
|
name string
|
|
|
|
// run is the function that implements the operator. Nil means that we
|
|
|
|
// ignore the operator, other than popping its arguments off the stack.
|
2016-12-05 13:52:00 +01:00
|
|
|
run func(*psInterpreter) error
|
2016-11-27 10:19:50 +01:00
|
|
|
}
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
// psOperators holds the 1-byte and 2-byte operators for PostScript interpreter
|
|
|
|
// contexts.
|
|
|
|
var psOperators = [...][2][]psOperator{
|
|
|
|
// The Top DICT operators are defined by 5176.CFF.pdf Table 9 "Top DICT
|
|
|
|
// Operator Entries" and Table 10 "CIDFont Operator Extensions".
|
|
|
|
psContextTopDict: {{
|
|
|
|
// 1-byte operators.
|
|
|
|
0: {+1, "version", nil},
|
|
|
|
1: {+1, "Notice", nil},
|
|
|
|
2: {+1, "FullName", nil},
|
|
|
|
3: {+1, "FamilyName", nil},
|
|
|
|
4: {+1, "Weight", nil},
|
|
|
|
5: {-1, "FontBBox", nil},
|
|
|
|
13: {+1, "UniqueID", nil},
|
|
|
|
14: {-1, "XUID", nil},
|
|
|
|
15: {+1, "charset", nil},
|
|
|
|
16: {+1, "Encoding", nil},
|
|
|
|
17: {+1, "CharStrings", func(p *psInterpreter) error {
|
|
|
|
p.topDict.charStrings = p.stack.a[p.stack.top-1]
|
|
|
|
return nil
|
|
|
|
}},
|
|
|
|
18: {+2, "Private", nil},
|
|
|
|
}, {
|
|
|
|
// 2-byte operators. The first byte is the escape byte.
|
|
|
|
0: {+1, "Copyright", nil},
|
|
|
|
1: {+1, "isFixedPitch", nil},
|
|
|
|
2: {+1, "ItalicAngle", nil},
|
|
|
|
3: {+1, "UnderlinePosition", nil},
|
|
|
|
4: {+1, "UnderlineThickness", nil},
|
|
|
|
5: {+1, "PaintType", nil},
|
|
|
|
6: {+1, "CharstringType", nil},
|
|
|
|
7: {-1, "FontMatrix", nil},
|
|
|
|
8: {+1, "StrokeWidth", nil},
|
|
|
|
20: {+1, "SyntheticBase", nil},
|
|
|
|
21: {+1, "PostScript", nil},
|
|
|
|
22: {+1, "BaseFontName", nil},
|
|
|
|
23: {-2, "BaseFontBlend", nil},
|
|
|
|
30: {+3, "ROS", nil},
|
|
|
|
31: {+1, "CIDFontVersion", nil},
|
|
|
|
32: {+1, "CIDFontRevision", nil},
|
|
|
|
33: {+1, "CIDFontType", nil},
|
|
|
|
34: {+1, "CIDCount", nil},
|
|
|
|
35: {+1, "UIDBase", nil},
|
|
|
|
36: {+1, "FDArray", nil},
|
|
|
|
37: {+1, "FDSelect", nil},
|
|
|
|
38: {+1, "FontName", nil},
|
|
|
|
}},
|
|
|
|
|
|
|
|
// The Type 2 Charstring operators are defined by 5177.Type2.pdf Appendix A
|
|
|
|
// "Type 2 Charstring Command Codes".
|
|
|
|
psContextType2Charstring: {{
|
|
|
|
// 1-byte operators.
|
|
|
|
0: {}, // Reserved.
|
|
|
|
1: {-1, "hstem", t2CStem},
|
|
|
|
2: {}, // Reserved.
|
|
|
|
3: {-1, "vstem", t2CStem},
|
|
|
|
4: {-1, "vmoveto", t2CVmoveto},
|
|
|
|
5: {-1, "rlineto", t2CRlineto},
|
|
|
|
6: {-1, "hlineto", t2CHlineto},
|
|
|
|
7: {-1, "vlineto", t2CVlineto},
|
2016-12-09 12:46:30 +01:00
|
|
|
8: {-1, "rrcurveto", t2CRrcurveto},
|
2016-12-05 13:52:00 +01:00
|
|
|
9: {}, // Reserved.
|
|
|
|
10: {}, // callsubr.
|
|
|
|
11: {}, // return.
|
|
|
|
12: {}, // escape.
|
|
|
|
13: {}, // Reserved.
|
|
|
|
14: {-1, "endchar", t2CEndchar},
|
|
|
|
15: {}, // Reserved.
|
|
|
|
16: {}, // Reserved.
|
|
|
|
17: {}, // Reserved.
|
|
|
|
18: {-1, "hstemhm", t2CStem},
|
|
|
|
19: {-1, "hintmask", t2CMask},
|
|
|
|
20: {-1, "cntrmask", t2CMask},
|
|
|
|
21: {-1, "rmoveto", t2CRmoveto},
|
|
|
|
22: {-1, "hmoveto", t2CHmoveto},
|
|
|
|
23: {-1, "vstemhm", t2CStem},
|
2017-03-12 02:07:09 +01:00
|
|
|
24: {-1, "rcurveline", t2CRcurveline},
|
|
|
|
25: {-1, "rlinecurve", t2CRlinecurve},
|
2017-03-10 08:20:44 +01:00
|
|
|
26: {-1, "vvcurveto", t2CVvcurveto},
|
|
|
|
27: {-1, "hhcurveto", t2CHhcurveto},
|
2016-12-05 13:52:00 +01:00
|
|
|
28: {}, // shortint.
|
|
|
|
29: {}, // callgsubr.
|
|
|
|
30: {-1, "vhcurveto", t2CVhcurveto},
|
|
|
|
31: {-1, "hvcurveto", t2CHvcurveto},
|
|
|
|
}, {
|
|
|
|
// 2-byte operators. The first byte is the escape byte.
|
|
|
|
0: {}, // Reserved.
|
|
|
|
// TODO: more operators.
|
2016-11-27 10:19:50 +01:00
|
|
|
}},
|
2016-12-05 13:52:00 +01:00
|
|
|
}
|
2016-12-02 07:58:28 +01:00
|
|
|
|
|
|
|
// 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an
|
|
|
|
// initial escape byte of 12".
|
|
|
|
const escapeByte = 12
|
2016-12-05 13:52:00 +01:00
|
|
|
|
|
|
|
// t2CReadWidth reads the optional width adjustment. If present, it is on the
|
|
|
|
// bottom of the stack.
|
|
|
|
//
|
|
|
|
// 5177.Type2.pdf page 16 Note 4 says: "The first stack-clearing operator,
|
|
|
|
// which must be one of hstem, hstemhm, vstem, vstemhm, cntrmask, hintmask,
|
|
|
|
// hmoveto, vmoveto, rmoveto, or endchar, takes an additional argument — the
|
|
|
|
// width... which may be expressed as zero or one numeric argument."
|
|
|
|
func t2CReadWidth(p *psInterpreter, nArgs int32) {
|
|
|
|
if p.type2Charstrings.seenWidth {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.type2Charstrings.seenWidth = true
|
|
|
|
switch nArgs {
|
|
|
|
case 0:
|
|
|
|
if p.stack.top != 1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case 1:
|
|
|
|
if p.stack.top <= 1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if p.stack.top%nArgs != 1 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// When parsing a standalone CFF, we'd save the value of p.stack.a[0] here
|
|
|
|
// as it defines the glyph's width (horizontal advance). Specifically, if
|
|
|
|
// present, it is a delta to the font-global nominalWidthX value found in
|
|
|
|
// the Private DICT. If absent, the glyph's width is the defaultWidthX
|
|
|
|
// value in that dict. See 5176.CFF.pdf section 15 "Private DICT Data".
|
|
|
|
//
|
|
|
|
// For a CFF embedded in an SFNT font (i.e. an OpenType font), glyph widths
|
|
|
|
// are already stored in the hmtx table, separate to the CFF table, and it
|
|
|
|
// is simpler to parse that table for all OpenType fonts (PostScript and
|
|
|
|
// TrueType). We therefore ignore the width value here, and just remove it
|
|
|
|
// from the bottom of the stack.
|
|
|
|
copy(p.stack.a[:p.stack.top-1], p.stack.a[1:p.stack.top])
|
|
|
|
p.stack.top--
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CStem(p *psInterpreter) error {
|
|
|
|
t2CReadWidth(p, 2)
|
|
|
|
if p.stack.top%2 != 0 {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
// We update the number of hintBits need to parse hintmask and cntrmask
|
|
|
|
// instructions, but this Type 2 Charstring implementation otherwise
|
|
|
|
// ignores the stem hints.
|
|
|
|
p.type2Charstrings.hintBits += p.stack.top / 2
|
|
|
|
if p.type2Charstrings.hintBits > maxHintBits {
|
|
|
|
return errUnsupportedNumberOfHints
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CMask(p *psInterpreter) error {
|
|
|
|
hintBytes := (p.type2Charstrings.hintBits + 7) / 8
|
|
|
|
t2CReadWidth(p, hintBytes)
|
|
|
|
if len(p.instructions) < int(hintBytes) {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
p.instructions = p.instructions[hintBytes:]
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CAppendMoveto(p *psInterpreter) {
|
|
|
|
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
|
|
|
Op: SegmentOpMoveTo,
|
|
|
|
Args: [6]fixed.Int26_6{
|
2017-02-18 03:13:36 +01:00
|
|
|
0: fixed.Int26_6(p.type2Charstrings.x),
|
|
|
|
1: fixed.Int26_6(p.type2Charstrings.y),
|
2016-12-05 13:52:00 +01:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CAppendLineto(p *psInterpreter) {
|
|
|
|
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
|
|
|
Op: SegmentOpLineTo,
|
|
|
|
Args: [6]fixed.Int26_6{
|
2017-02-18 03:13:36 +01:00
|
|
|
0: fixed.Int26_6(p.type2Charstrings.x),
|
|
|
|
1: fixed.Int26_6(p.type2Charstrings.y),
|
2016-12-05 13:52:00 +01:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CAppendCubeto(p *psInterpreter, dxa, dya, dxb, dyb, dxc, dyc int32) {
|
|
|
|
p.type2Charstrings.x += dxa
|
|
|
|
p.type2Charstrings.y += dya
|
|
|
|
xa := p.type2Charstrings.x
|
|
|
|
ya := p.type2Charstrings.y
|
|
|
|
p.type2Charstrings.x += dxb
|
|
|
|
p.type2Charstrings.y += dyb
|
|
|
|
xb := p.type2Charstrings.x
|
|
|
|
yb := p.type2Charstrings.y
|
|
|
|
p.type2Charstrings.x += dxc
|
|
|
|
p.type2Charstrings.y += dyc
|
|
|
|
xc := p.type2Charstrings.x
|
|
|
|
yc := p.type2Charstrings.y
|
|
|
|
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
|
|
|
Op: SegmentOpCubeTo,
|
|
|
|
Args: [6]fixed.Int26_6{
|
2017-02-18 03:13:36 +01:00
|
|
|
0: fixed.Int26_6(xa),
|
|
|
|
1: fixed.Int26_6(ya),
|
|
|
|
2: fixed.Int26_6(xb),
|
|
|
|
3: fixed.Int26_6(yb),
|
|
|
|
4: fixed.Int26_6(xc),
|
|
|
|
5: fixed.Int26_6(yc),
|
2016-12-05 13:52:00 +01:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CHmoveto(p *psInterpreter) error {
|
|
|
|
t2CReadWidth(p, 1)
|
|
|
|
if p.stack.top < 1 {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
for i := int32(0); i < p.stack.top; i++ {
|
|
|
|
p.type2Charstrings.x += p.stack.a[i]
|
|
|
|
}
|
|
|
|
t2CAppendMoveto(p)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CVmoveto(p *psInterpreter) error {
|
|
|
|
t2CReadWidth(p, 1)
|
|
|
|
if p.stack.top < 1 {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
for i := int32(0); i < p.stack.top; i++ {
|
|
|
|
p.type2Charstrings.y += p.stack.a[i]
|
|
|
|
}
|
|
|
|
t2CAppendMoveto(p)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CRmoveto(p *psInterpreter) error {
|
|
|
|
t2CReadWidth(p, 2)
|
|
|
|
if p.stack.top < 2 || p.stack.top%2 != 0 {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
for i := int32(0); i < p.stack.top; i += 2 {
|
|
|
|
p.type2Charstrings.x += p.stack.a[i+0]
|
|
|
|
p.type2Charstrings.y += p.stack.a[i+1]
|
|
|
|
}
|
|
|
|
t2CAppendMoveto(p)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CHlineto(p *psInterpreter) error { return t2CLineto(p, false) }
|
|
|
|
func t2CVlineto(p *psInterpreter) error { return t2CLineto(p, true) }
|
|
|
|
|
|
|
|
func t2CLineto(p *psInterpreter, vertical bool) error {
|
2017-03-12 02:07:09 +01:00
|
|
|
if !p.type2Charstrings.seenWidth || p.stack.top < 1 {
|
2016-12-05 13:52:00 +01:00
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
for i := int32(0); i < p.stack.top; i, vertical = i+1, !vertical {
|
|
|
|
if vertical {
|
|
|
|
p.type2Charstrings.y += p.stack.a[i]
|
|
|
|
} else {
|
|
|
|
p.type2Charstrings.x += p.stack.a[i]
|
|
|
|
}
|
|
|
|
t2CAppendLineto(p)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CRlineto(p *psInterpreter) error {
|
2017-03-12 02:07:09 +01:00
|
|
|
if !p.type2Charstrings.seenWidth || p.stack.top < 2 || p.stack.top%2 != 0 {
|
2016-12-05 13:52:00 +01:00
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
2017-03-12 02:07:09 +01:00
|
|
|
for i := int32(0); i < p.stack.top; i += 2 {
|
|
|
|
p.type2Charstrings.x += p.stack.a[i+0]
|
|
|
|
p.type2Charstrings.y += p.stack.a[i+1]
|
|
|
|
t2CAppendLineto(p)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// As per 5177.Type2.pdf section 4.1 "Path Construction Operators",
|
|
|
|
//
|
|
|
|
// rcurveline is:
|
|
|
|
// - {dxa dya dxb dyb dxc dyc}+ dxd dyd
|
|
|
|
//
|
|
|
|
// rlinecurve is:
|
|
|
|
// - {dxa dya}+ dxb dyb dxc dyc dxd dyd
|
|
|
|
|
|
|
|
func t2CRcurveline(p *psInterpreter) error {
|
|
|
|
if !p.type2Charstrings.seenWidth || p.stack.top < 8 || p.stack.top%6 != 2 {
|
2016-12-05 13:52:00 +01:00
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
2017-03-12 02:07:09 +01:00
|
|
|
i := int32(0)
|
|
|
|
for iMax := p.stack.top - 2; i < iMax; i += 6 {
|
|
|
|
t2CAppendCubeto(p,
|
|
|
|
p.stack.a[i+0],
|
|
|
|
p.stack.a[i+1],
|
|
|
|
p.stack.a[i+2],
|
|
|
|
p.stack.a[i+3],
|
|
|
|
p.stack.a[i+4],
|
|
|
|
p.stack.a[i+5],
|
|
|
|
)
|
|
|
|
}
|
|
|
|
p.type2Charstrings.x += p.stack.a[i+0]
|
|
|
|
p.type2Charstrings.y += p.stack.a[i+1]
|
|
|
|
t2CAppendLineto(p)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func t2CRlinecurve(p *psInterpreter) error {
|
|
|
|
if !p.type2Charstrings.seenWidth || p.stack.top < 8 || p.stack.top%2 != 0 {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
i := int32(0)
|
|
|
|
for iMax := p.stack.top - 6; i < iMax; i += 2 {
|
2016-12-05 13:52:00 +01:00
|
|
|
p.type2Charstrings.x += p.stack.a[i+0]
|
|
|
|
p.type2Charstrings.y += p.stack.a[i+1]
|
|
|
|
t2CAppendLineto(p)
|
|
|
|
}
|
2017-03-12 02:07:09 +01:00
|
|
|
t2CAppendCubeto(p,
|
|
|
|
p.stack.a[i+0],
|
|
|
|
p.stack.a[i+1],
|
|
|
|
p.stack.a[i+2],
|
|
|
|
p.stack.a[i+3],
|
|
|
|
p.stack.a[i+4],
|
|
|
|
p.stack.a[i+5],
|
|
|
|
)
|
2016-12-05 13:52:00 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// As per 5177.Type2.pdf section 4.1 "Path Construction Operators",
|
|
|
|
//
|
2017-03-10 08:20:44 +01:00
|
|
|
// hhcurveto is:
|
|
|
|
// - dy1 {dxa dxb dyb dxc}+
|
|
|
|
//
|
|
|
|
// vvcurveto is:
|
|
|
|
// - dx1 {dya dxb dyb dyc}+
|
|
|
|
//
|
2016-12-05 13:52:00 +01:00
|
|
|
// hvcurveto is one of:
|
|
|
|
// - dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
|
|
|
|
// - {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
|
|
|
|
//
|
|
|
|
// vhcurveto is one of:
|
|
|
|
// - dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf?
|
|
|
|
// - {dya dxb dyb dxc dxd dxe dye dyf}+ dxf?
|
|
|
|
|
2017-03-10 08:20:44 +01:00
|
|
|
func t2CHhcurveto(p *psInterpreter) error { return t2CCurveto(p, false, false) }
|
|
|
|
func t2CVvcurveto(p *psInterpreter) error { return t2CCurveto(p, false, true) }
|
|
|
|
func t2CHvcurveto(p *psInterpreter) error { return t2CCurveto(p, true, false) }
|
|
|
|
func t2CVhcurveto(p *psInterpreter) error { return t2CCurveto(p, true, true) }
|
2016-12-05 13:52:00 +01:00
|
|
|
|
2017-03-10 08:20:44 +01:00
|
|
|
// t2CCurveto implements the hh / vv / hv / vh xxcurveto operators. N relative
|
|
|
|
// cubic curve requires 6*N control points, but only 4*N+0 or 4*N+1 are used
|
|
|
|
// here: all (or all but one) of the piecewise cubic curve's tangents are
|
|
|
|
// implicitly horizontal or vertical.
|
|
|
|
//
|
|
|
|
// swap is whether that implicit horizontal / vertical constraint swaps as you
|
|
|
|
// move along the piecewise cubic curve. If swap is false, the constraints are
|
|
|
|
// either all horizontal or all vertical. If swap is true, it alternates.
|
|
|
|
//
|
|
|
|
// vertical is whether the first implicit constraint is vertical.
|
|
|
|
func t2CCurveto(p *psInterpreter, swap, vertical bool) error {
|
2016-12-05 13:52:00 +01:00
|
|
|
if !p.type2Charstrings.seenWidth || p.stack.top < 4 {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
2017-03-10 08:20:44 +01:00
|
|
|
|
|
|
|
i := int32(0)
|
|
|
|
switch p.stack.top & 3 {
|
|
|
|
case 0:
|
|
|
|
// No-op.
|
|
|
|
case 1:
|
|
|
|
if swap {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
i = 1
|
2016-12-05 13:52:00 +01:00
|
|
|
if vertical {
|
2017-03-10 08:20:44 +01:00
|
|
|
p.type2Charstrings.x += p.stack.a[0]
|
2016-12-05 13:52:00 +01:00
|
|
|
} else {
|
2017-03-10 08:20:44 +01:00
|
|
|
p.type2Charstrings.y += p.stack.a[0]
|
2016-12-05 13:52:00 +01:00
|
|
|
}
|
2017-03-10 08:20:44 +01:00
|
|
|
default:
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
|
|
|
|
for i != p.stack.top {
|
|
|
|
i = t2CCurveto4(p, swap, vertical, i)
|
2016-12-05 13:52:00 +01:00
|
|
|
if i < 0 {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
2017-03-10 08:20:44 +01:00
|
|
|
if swap {
|
|
|
|
vertical = !vertical
|
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-03-10 08:20:44 +01:00
|
|
|
func t2CCurveto4(p *psInterpreter, swap bool, vertical bool, i int32) (j int32) {
|
2016-12-05 13:52:00 +01:00
|
|
|
if i+4 > p.stack.top {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
dxa := p.stack.a[i+0]
|
2017-03-10 08:20:44 +01:00
|
|
|
dya := int32(0)
|
2016-12-05 13:52:00 +01:00
|
|
|
dxb := p.stack.a[i+1]
|
|
|
|
dyb := p.stack.a[i+2]
|
2017-03-10 08:20:44 +01:00
|
|
|
dxc := p.stack.a[i+3]
|
|
|
|
dyc := int32(0)
|
2016-12-05 13:52:00 +01:00
|
|
|
i += 4
|
2017-03-10 08:20:44 +01:00
|
|
|
|
|
|
|
if vertical {
|
|
|
|
dxa, dya = dya, dxa
|
2016-12-05 13:52:00 +01:00
|
|
|
}
|
|
|
|
|
2017-03-10 08:20:44 +01:00
|
|
|
if swap {
|
|
|
|
if i+1 == p.stack.top {
|
|
|
|
dyc = p.stack.a[i]
|
|
|
|
i++
|
|
|
|
}
|
2016-12-05 13:52:00 +01:00
|
|
|
}
|
2017-03-10 08:20:44 +01:00
|
|
|
|
|
|
|
if swap != vertical {
|
|
|
|
dxc, dyc = dyc, dxc
|
2016-12-05 13:52:00 +01:00
|
|
|
}
|
2017-03-10 08:20:44 +01:00
|
|
|
|
|
|
|
t2CAppendCubeto(p, dxa, dya, dxb, dyb, dxc, dyc)
|
2016-12-05 13:52:00 +01:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2016-12-09 12:46:30 +01:00
|
|
|
func t2CRrcurveto(p *psInterpreter) error {
|
|
|
|
if !p.type2Charstrings.seenWidth || p.stack.top < 6 || p.stack.top%6 != 0 {
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
for i := int32(0); i != p.stack.top; i += 6 {
|
|
|
|
t2CAppendCubeto(p,
|
|
|
|
p.stack.a[i+0],
|
|
|
|
p.stack.a[i+1],
|
|
|
|
p.stack.a[i+2],
|
|
|
|
p.stack.a[i+3],
|
|
|
|
p.stack.a[i+4],
|
|
|
|
p.stack.a[i+5],
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-12-05 13:52:00 +01:00
|
|
|
func t2CEndchar(p *psInterpreter) error {
|
|
|
|
t2CReadWidth(p, 0)
|
|
|
|
if p.stack.top != 0 || len(p.instructions) != 0 {
|
|
|
|
if p.stack.top == 4 {
|
|
|
|
// TODO: process the implicit "seac" command as per 5177.Type2.pdf
|
|
|
|
// Appendix C "Compatibility and Deprecated Operators".
|
|
|
|
return errUnsupportedType2Charstring
|
|
|
|
}
|
|
|
|
return errInvalidCFFTable
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|