golang-image/font/sfnt/postscript.go

1415 lines
37 KiB
Go
Raw Permalink Normal View History

// 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
// 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.
//
// 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"
"math"
"strconv"
"git.fireandbrimst.one/aw/golang-image/math/fixed"
)
const (
// psArgStackSize is the argument 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". 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 {
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")
}
// fdSelect holds a CFF font's Font Dict Select data.
type fdSelect struct {
format uint8
numRanges uint16
offset int32
}
func (t *fdSelect) lookup(f *Font, b *Buffer, x GlyphIndex) (int, error) {
switch t.format {
case 0:
buf, err := b.view(&f.src, int(t.offset)+int(x), 1)
if err != nil {
return 0, err
}
return int(buf[0]), nil
case 3:
lo, hi := 0, int(t.numRanges)
for lo < hi {
i := (lo + hi) / 2
buf, err := b.view(&f.src, int(t.offset)+3*i, 3+2)
if err != nil {
return 0, err
}
// buf holds the range [xlo, xhi).
if xlo := GlyphIndex(u16(buf[0:])); x < xlo {
hi = i
continue
}
if xhi := GlyphIndex(u16(buf[3:])); xhi <= x {
lo = i + 1
continue
}
return int(buf[2]), nil
}
}
return 0, ErrNotFound
}
// cffParser parses the CFF table from an SFNT font.
type cffParser struct {
src *source
base int
offset int
end int
err error
buf []byte
locBuf [2]uint32
psi psInterpreter
}
func (p *cffParser) parse(numGlyphs int32) (ret glyphData, err error) {
// Parse the header.
{
if !p.read(4) {
return glyphData{}, p.err
}
if p.buf[0] != 1 || p.buf[1] != 0 || p.buf[2] != 4 {
return glyphData{}, errUnsupportedCFFVersion
}
}
// Parse the Name INDEX.
{
count, offSize, ok := p.parseIndexHeader()
if !ok {
return glyphData{}, 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 glyphData{}, errInvalidCFFTable
}
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
return glyphData{}, p.err
}
p.offset = int(p.locBuf[1])
}
// Parse the Top DICT INDEX.
p.psi.topDict.initialize()
{
count, offSize, ok := p.parseIndexHeader()
if !ok {
return glyphData{}, 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 glyphData{}, errInvalidCFFTable
}
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
return glyphData{}, p.err
}
if !p.read(int(p.locBuf[1] - p.locBuf[0])) {
return glyphData{}, p.err
}
if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil {
return glyphData{}, p.err
}
}
// Skip the String INDEX.
{
count, offSize, ok := p.parseIndexHeader()
if !ok {
return glyphData{}, 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 glyphData{}, p.err
}
if !p.read(int(offSize)) {
return glyphData{}, p.err
}
loc := bigEndian(p.buf) - 1
// Check that locations are in bounds.
if uint32(p.end-p.offset) < loc {
return glyphData{}, errInvalidCFFTable
}
// Skip the index data.
if !p.skip(int(loc)) {
return glyphData{}, p.err
}
}
}
// Parse the Global Subrs [Subroutines] INDEX.
{
count, offSize, ok := p.parseIndexHeader()
if !ok {
return glyphData{}, p.err
}
if count != 0 {
if count > maxNumSubroutines {
return glyphData{}, errUnsupportedNumberOfSubroutines
}
ret.gsubrs = make([]uint32, count+1)
if !p.parseIndexLocations(ret.gsubrs, count, offSize) {
return glyphData{}, p.err
}
}
}
// Parse the CharStrings INDEX, whose location was found in the Top DICT.
{
if !p.seekFromBase(p.psi.topDict.charStringsOffset) {
return glyphData{}, errInvalidCFFTable
}
count, offSize, ok := p.parseIndexHeader()
if !ok {
return glyphData{}, p.err
}
if count == 0 || int32(count) != numGlyphs {
return glyphData{}, errInvalidCFFTable
}
ret.locations = make([]uint32, count+1)
if !p.parseIndexLocations(ret.locations, count, offSize) {
return glyphData{}, p.err
}
}
if !p.psi.topDict.isCIDFont {
// Parse the Private DICT, whose location was found in the Top DICT.
ret.singleSubrs, err = p.parsePrivateDICT(
p.psi.topDict.privateDictOffset,
p.psi.topDict.privateDictLength,
)
if err != nil {
return glyphData{}, err
}
} else {
// Parse the Font Dict Select data, whose location was found in the Top
// DICT.
ret.fdSelect, err = p.parseFDSelect(p.psi.topDict.fdSelect, numGlyphs)
if err != nil {
return glyphData{}, err
}
// Parse the Font Dicts. Each one contains its own Private DICT.
if !p.seekFromBase(p.psi.topDict.fdArray) {
return glyphData{}, errInvalidCFFTable
}
count, offSize, ok := p.parseIndexHeader()
if !ok {
return glyphData{}, p.err
}
if count > maxNumFontDicts {
return glyphData{}, errUnsupportedNumberOfFontDicts
}
fdLocations := make([]uint32, count+1)
if !p.parseIndexLocations(fdLocations, count, offSize) {
return glyphData{}, p.err
}
privateDicts := make([]struct {
offset, length int32
}, count)
for i := range privateDicts {
length := fdLocations[i+1] - fdLocations[i]
if !p.read(int(length)) {
return glyphData{}, errInvalidCFFTable
}
p.psi.topDict.initialize()
if p.err = p.psi.run(psContextTopDict, p.buf, 0, 0); p.err != nil {
return glyphData{}, p.err
}
privateDicts[i].offset = p.psi.topDict.privateDictOffset
privateDicts[i].length = p.psi.topDict.privateDictLength
}
ret.multiSubrs = make([][]uint32, count)
for i, pd := range privateDicts {
ret.multiSubrs[i], err = p.parsePrivateDICT(pd.offset, pd.length)
if err != nil {
return glyphData{}, err
}
}
}
return ret, err
}
// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section
// 19 "FDSelect".
func (p *cffParser) parseFDSelect(offset int32, numGlyphs int32) (ret fdSelect, err error) {
if !p.seekFromBase(p.psi.topDict.fdSelect) {
return fdSelect{}, errInvalidCFFTable
}
if !p.read(1) {
return fdSelect{}, p.err
}
ret.format = p.buf[0]
switch ret.format {
case 0:
if p.end-p.offset < int(numGlyphs) {
return fdSelect{}, errInvalidCFFTable
}
ret.offset = int32(p.offset)
return ret, nil
case 3:
if !p.read(2) {
return fdSelect{}, p.err
}
ret.numRanges = u16(p.buf)
if p.end-p.offset < 3*int(ret.numRanges)+2 {
return fdSelect{}, errInvalidCFFTable
}
ret.offset = int32(p.offset)
return ret, nil
}
return fdSelect{}, errUnsupportedCFFFDSelectTable
}
func (p *cffParser) parsePrivateDICT(offset, length int32) (subrs []uint32, err error) {
p.psi.privateDict.initialize()
if length != 0 {
fullLength := int32(p.end - p.base)
if offset <= 0 || fullLength < offset || fullLength-offset < length || length < 0 {
return nil, errInvalidCFFTable
}
p.offset = p.base + int(offset)
if !p.read(int(length)) {
return nil, p.err
}
if p.err = p.psi.run(psContextPrivateDict, p.buf, 0, 0); p.err != nil {
return nil, p.err
}
}
// Parse the Local Subrs [Subroutines] INDEX, whose location was found in
// the Private DICT.
if p.psi.privateDict.subrsOffset != 0 {
if !p.seekFromBase(offset + p.psi.privateDict.subrsOffset) {
return nil, errInvalidCFFTable
}
count, offSize, ok := p.parseIndexHeader()
if !ok {
return nil, p.err
}
if count != 0 {
if count > maxNumSubroutines {
return nil, errUnsupportedNumberOfSubroutines
}
subrs = make([]uint32, count+1)
if !p.parseIndexLocations(subrs, count, offSize) {
return nil, p.err
}
}
}
return subrs, err
}
// 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 n < 0 || p.end-p.offset < n {
p.err = errInvalidCFFTable
return false
}
p.buf, p.err = p.src.view(p.buf, p.offset, n)
// TODO: if p.err == io.EOF, change that to a different error??
p.offset += n
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) seekFromBase(offset int32) (ok bool) {
if offset < 0 || int32(p.end-p.base) < offset {
return false
}
p.offset = p.base + int(offset)
return true
}
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
}
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 {
charStringsOffset int32
fdArray int32
fdSelect int32
isCIDFont bool
privateDictOffset int32
privateDictLength int32
}
func (d *psTopDictData) initialize() {
*d = psTopDictData{}
}
// psPrivateDictData contains fields specific to the Private DICT context.
type psPrivateDictData struct {
subrsOffset int32
}
func (d *psPrivateDictData) initialize() {
*d = psPrivateDictData{}
}
// psType2CharstringsData contains fields specific to the Type 2 Charstrings
// context.
type psType2CharstringsData struct {
f *Font
b *Buffer
x int32
y int32
firstX int32
firstY int32
hintBits int32
seenWidth bool
ended bool
glyphIndex GlyphIndex
// fdSelectIndexPlusOne is the result of the Font Dict Select lookup, plus
// one. That plus one lets us use the zero value to denote either unused
// (for CFF fonts with a single Font Dict) or lazily evaluated.
fdSelectIndexPlusOne int32
}
func (d *psType2CharstringsData) initialize(f *Font, b *Buffer, glyphIndex GlyphIndex) {
*d = psType2CharstringsData{
f: f,
b: b,
glyphIndex: glyphIndex,
}
}
func (d *psType2CharstringsData) closePath() {
if d.x != d.firstX || d.y != d.firstY {
d.b.segments = append(d.b.segments, Segment{
Op: SegmentOpLineTo,
Args: [3]fixed.Point26_6{{
X: fixed.Int26_6(d.firstX),
Y: fixed.Int26_6(d.firstY),
}},
})
}
}
func (d *psType2CharstringsData) moveTo(dx, dy int32) {
d.closePath()
d.x += dx
d.y += dy
d.b.segments = append(d.b.segments, Segment{
Op: SegmentOpMoveTo,
Args: [3]fixed.Point26_6{{
X: fixed.Int26_6(d.x),
Y: fixed.Int26_6(d.y),
}},
})
d.firstX = d.x
d.firstY = d.y
}
func (d *psType2CharstringsData) lineTo(dx, dy int32) {
d.x += dx
d.y += dy
d.b.segments = append(d.b.segments, Segment{
Op: SegmentOpLineTo,
Args: [3]fixed.Point26_6{{
X: fixed.Int26_6(d.x),
Y: fixed.Int26_6(d.y),
}},
})
}
func (d *psType2CharstringsData) cubeTo(dxa, dya, dxb, dyb, dxc, dyc int32) {
d.x += dxa
d.y += dya
xa := fixed.Int26_6(d.x)
ya := fixed.Int26_6(d.y)
d.x += dxb
d.y += dyb
xb := fixed.Int26_6(d.x)
yb := fixed.Int26_6(d.y)
d.x += dxc
d.y += dyc
xc := fixed.Int26_6(d.x)
yc := fixed.Int26_6(d.y)
d.b.segments = append(d.b.segments, Segment{
Op: SegmentOpCubeTo,
Args: [3]fixed.Point26_6{{X: xa, Y: ya}, {X: xb, Y: yb}, {X: xc, Y: yc}},
})
}
// psInterpreter is a PostScript interpreter.
type psInterpreter struct {
ctx psContext
instructions []byte
instrOffset uint32
instrLength uint32
argStack struct {
a [psArgStackSize]int32
top int32
}
callStack struct {
a [psCallStackSize]psCallStackEntry
top int32
}
parseNumberBuf [maxRealNumberStrLen]byte
topDict psTopDictData
privateDict psPrivateDictData
type2Charstrings psType2CharstringsData
}
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 {
// Push a numeric operand on the stack, if applicable.
if hasResult, err := p.parseNumber(); hasResult {
if err != nil {
return err
}
continue
}
// 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
}
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.argStack.top < op.numPop {
return errInvalidCFFTable
}
if op.run != nil {
if err := op.run(p); err != nil {
return err
}
}
if op.numPop < 0 {
p.argStack.top = 0
} else {
p.argStack.top -= op.numPop
}
continue loop
}
}
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)
}
}
}
return nil
}
// See 5176.CFF.pdf section 4 "DICT Data".
func (p *psInterpreter) parseNumber() (hasResult bool, err error) {
number := int32(0)
switch b := p.instructions[0]; {
case b == 28:
if len(p.instructions) < 3 {
return true, errInvalidCFFTable
}
number, hasResult = int32(int16(u16(p.instructions[1:]))), true
p.instructions = p.instructions[3:]
case b == 29 && p.ctx != psContextType2Charstring:
if len(p.instructions) < 5 {
return true, errInvalidCFFTable
}
number, hasResult = int32(u32(p.instructions[1:])), true
p.instructions = p.instructions[5:]
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
// 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:]
loop:
for {
if len(p.instructions) == 0 {
return true, errInvalidCFFTable
}
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 {
return true, errInvalidCFFTable
}
number, hasResult = int32(math.Float32bits(float32(f))), true
break loop
}
if nib == 0x0d {
return true, errInvalidCFFTable
}
if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) {
return true, errUnsupportedRealNumberEncoding
}
s = append(s, nibbleDefs[nib]...)
}
}
case b < 32:
// No-op.
case b < 247:
p.instructions = p.instructions[1:]
number, hasResult = int32(b)-139, true
case b < 251:
if len(p.instructions) < 2 {
return true, errInvalidCFFTable
}
b1 := p.instructions[1]
p.instructions = p.instructions[2:]
number, hasResult = +int32(b-247)*256+int32(b1)+108, true
case b < 255:
if len(p.instructions) < 2 {
return true, errInvalidCFFTable
}
b1 := p.instructions[1]
p.instructions = p.instructions[2:]
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:]
}
if hasResult {
if p.argStack.top == psArgStackSize {
return true, errInvalidCFFTable
}
p.argStack.a[p.argStack.top] = number
p.argStack.top++
}
return hasResult, nil
}
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: "",
}
type psOperator struct {
// 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.
run func(*psInterpreter) error
}
// 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.charStringsOffset = p.argStack.a[p.argStack.top-1]
return 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},
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", func(p *psInterpreter) error {
p.topDict.isCIDFont = true
return nil
}},
31: {+1, "CIDFontVersion", nil},
32: {+1, "CIDFontRevision", nil},
33: {+1, "CIDFontType", nil},
34: {+1, "CIDCount", nil},
35: {+1, "UIDBase", nil},
36: {+1, "FDArray", func(p *psInterpreter) error {
p.topDict.fdArray = p.argStack.a[p.argStack.top-1]
return nil
}},
37: {+1, "FDSelect", func(p *psInterpreter) error {
p.topDict.fdSelect = p.argStack.a[p.argStack.top-1]
return 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.subrsOffset = 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: {{
// 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},
8: {-1, "rrcurveto", t2CRrcurveto},
9: {}, // Reserved.
10: {+1, "callsubr", t2CCallsubr},
11: {+0, "return", t2CReturn},
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},
24: {-1, "rcurveline", t2CRcurveline},
25: {-1, "rlinecurve", t2CRlinecurve},
26: {-1, "vvcurveto", t2CVvcurveto},
27: {-1, "hhcurveto", t2CHhcurveto},
28: {}, // shortint.
29: {+1, "callgsubr", t2CCallgsubr},
30: {-1, "vhcurveto", t2CVhcurveto},
31: {-1, "hvcurveto", t2CHvcurveto},
}, {
// 2-byte operators. The first byte is the escape byte.
34: {+7, "hflex", t2CHflex},
36: {+9, "hflex1", t2CHflex1},
// TODO: more operators.
}},
}
// 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an
// initial escape byte of 12".
const escapeByte = 12
// t2CReadWidth reads the optional width adjustment. If present, it is on the
// bottom of the arg stack. nArgs is the expected number of arguments on the
// stack. A negative nArgs means a multiple of 2.
//
// 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
if nArgs >= 0 {
if p.argStack.top != nArgs+1 {
return
}
} else if p.argStack.top&1 == 0 {
return
}
// When parsing a standalone CFF, we'd save the value of p.argStack.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 argStack.
copy(p.argStack.a[:p.argStack.top-1], p.argStack.a[1:p.argStack.top])
p.argStack.top--
}
func t2CStem(p *psInterpreter) error {
t2CReadWidth(p, -1)
if p.argStack.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.argStack.top / 2
if p.type2Charstrings.hintBits > maxHintBits {
return errUnsupportedNumberOfHints
}
return nil
}
func t2CMask(p *psInterpreter) error {
// 5176.CFF.pdf section 4.3 "Hint Operators" says that "If hstem and vstem
// hints are both declared at the beginning of a charstring, and this
// sequence is followed directly by the hintmask or cntrmask operators, the
// vstem hint operator need not be included."
//
// What we implement here is more permissive (but the same as what the
// FreeType implementation does, and simpler than tracking the previous
// operator and other hinting state): if a hintmask is given any arguments
// (i.e. the argStack is non-empty), we run an implicit vstem operator.
//
// Note that the vstem operator consumes from p.argStack, but the hintmask
// or cntrmask operators consume from p.instructions.
if p.argStack.top != 0 {
if err := t2CStem(p); err != nil {
return err
}
} else if !p.type2Charstrings.seenWidth {
p.type2Charstrings.seenWidth = true
}
hintBytes := (p.type2Charstrings.hintBits + 7) / 8
if len(p.instructions) < int(hintBytes) {
return errInvalidCFFTable
}
p.instructions = p.instructions[hintBytes:]
return nil
}
func t2CHmoveto(p *psInterpreter) error {
t2CReadWidth(p, 1)
if p.argStack.top != 1 {
return errInvalidCFFTable
}
p.type2Charstrings.moveTo(p.argStack.a[0], 0)
return nil
}
func t2CVmoveto(p *psInterpreter) error {
t2CReadWidth(p, 1)
if p.argStack.top != 1 {
return errInvalidCFFTable
}
p.type2Charstrings.moveTo(0, p.argStack.a[0])
return nil
}
func t2CRmoveto(p *psInterpreter) error {
t2CReadWidth(p, 2)
if p.argStack.top != 2 {
return errInvalidCFFTable
}
p.type2Charstrings.moveTo(p.argStack.a[0], p.argStack.a[1])
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 {
if !p.type2Charstrings.seenWidth || p.argStack.top < 1 {
return errInvalidCFFTable
}
for i := int32(0); i < p.argStack.top; i, vertical = i+1, !vertical {
dx, dy := p.argStack.a[i], int32(0)
if vertical {
dx, dy = dy, dx
}
p.type2Charstrings.lineTo(dx, dy)
}
return nil
}
func t2CRlineto(p *psInterpreter) error {
if !p.type2Charstrings.seenWidth || p.argStack.top < 2 || p.argStack.top%2 != 0 {
return errInvalidCFFTable
}
for i := int32(0); i < p.argStack.top; i += 2 {
p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
}
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.argStack.top < 8 || p.argStack.top%6 != 2 {
return errInvalidCFFTable
}
i := int32(0)
for iMax := p.argStack.top - 2; i < iMax; i += 6 {
p.type2Charstrings.cubeTo(
p.argStack.a[i+0],
p.argStack.a[i+1],
p.argStack.a[i+2],
p.argStack.a[i+3],
p.argStack.a[i+4],
p.argStack.a[i+5],
)
}
p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
return nil
}
func t2CRlinecurve(p *psInterpreter) error {
if !p.type2Charstrings.seenWidth || p.argStack.top < 8 || p.argStack.top%2 != 0 {
return errInvalidCFFTable
}
i := int32(0)
for iMax := p.argStack.top - 6; i < iMax; i += 2 {
p.type2Charstrings.lineTo(p.argStack.a[i], p.argStack.a[i+1])
}
p.type2Charstrings.cubeTo(
p.argStack.a[i+0],
p.argStack.a[i+1],
p.argStack.a[i+2],
p.argStack.a[i+3],
p.argStack.a[i+4],
p.argStack.a[i+5],
)
return nil
}
// As per 5177.Type2.pdf section 4.1 "Path Construction Operators",
//
// hhcurveto is:
// - dy1 {dxa dxb dyb dxc}+
//
// vvcurveto is:
// - dx1 {dya dxb dyb dyc}+
//
// 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?
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) }
// 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 {
if !p.type2Charstrings.seenWidth || p.argStack.top < 4 {
return errInvalidCFFTable
}
i := int32(0)
switch p.argStack.top & 3 {
case 0:
// No-op.
case 1:
if swap {
break
}
i = 1
if vertical {
p.type2Charstrings.x += p.argStack.a[0]
} else {
p.type2Charstrings.y += p.argStack.a[0]
}
default:
return errInvalidCFFTable
}
for i != p.argStack.top {
i = t2CCurveto4(p, swap, vertical, i)
if i < 0 {
return errInvalidCFFTable
}
if swap {
vertical = !vertical
}
}
return nil
}
func t2CCurveto4(p *psInterpreter, swap bool, vertical bool, i int32) (j int32) {
if i+4 > p.argStack.top {
return -1
}
dxa := p.argStack.a[i+0]
dya := int32(0)
dxb := p.argStack.a[i+1]
dyb := p.argStack.a[i+2]
dxc := p.argStack.a[i+3]
dyc := int32(0)
i += 4
if vertical {
dxa, dya = dya, dxa
}
if swap {
if i+1 == p.argStack.top {
dyc = p.argStack.a[i]
i++
}
}
if swap != vertical {
dxc, dyc = dyc, dxc
}
p.type2Charstrings.cubeTo(dxa, dya, dxb, dyb, dxc, dyc)
return i
}
func t2CRrcurveto(p *psInterpreter) error {
if !p.type2Charstrings.seenWidth || p.argStack.top < 6 || p.argStack.top%6 != 0 {
return errInvalidCFFTable
}
for i := int32(0); i != p.argStack.top; i += 6 {
p.type2Charstrings.cubeTo(
p.argStack.a[i+0],
p.argStack.a[i+1],
p.argStack.a[i+2],
p.argStack.a[i+3],
p.argStack.a[i+4],
p.argStack.a[i+5],
)
}
return nil
}
// For the flex operators, we ignore the flex depth and always produce cubic
// segments, not linear segments. It's not obvious why the Type 2 Charstring
// format cares about switching behavior based on a metric in pixels, not in
// ideal font units. The Go vector rasterizer has no problems with almost
// linear cubic segments.
func t2CHflex(p *psInterpreter) error {
p.type2Charstrings.cubeTo(
p.argStack.a[0], 0,
p.argStack.a[1], +p.argStack.a[2],
p.argStack.a[3], 0,
)
p.type2Charstrings.cubeTo(
p.argStack.a[4], 0,
p.argStack.a[5], -p.argStack.a[2],
p.argStack.a[6], 0,
)
return nil
}
func t2CHflex1(p *psInterpreter) error {
dy1 := p.argStack.a[1]
dy2 := p.argStack.a[3]
dy5 := p.argStack.a[7]
dy6 := -dy1 - dy2 - dy5
p.type2Charstrings.cubeTo(
p.argStack.a[0], dy1,
p.argStack.a[2], dy2,
p.argStack.a[4], 0,
)
p.type2Charstrings.cubeTo(
p.argStack.a[5], 0,
p.argStack.a[6], dy5,
p.argStack.a[8], dy6,
)
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.glyphData.gsubrs)
}
func t2CCallsubr(p *psInterpreter) error {
t := &p.type2Charstrings
d := &t.f.cached.glyphData
subrs := d.singleSubrs
if d.multiSubrs != nil {
if t.fdSelectIndexPlusOne == 0 {
index, err := d.fdSelect.lookup(t.f, t.b, t.glyphIndex)
if err != nil {
return err
}
if index < 0 || len(d.multiSubrs) <= index {
return errInvalidCFFTable
}
t.fdSelectIndexPlusOne = int32(index + 1)
}
subrs = d.multiSubrs[t.fdSelectIndexPlusOne-1]
}
return t2CCall(p, 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 || p.hasMoreInstructions() {
if p.argStack.top == 4 {
// TODO: process the implicit "seac" command as per 5177.Type2.pdf
// Appendix C "Compatibility and Deprecated Operators".
return errUnsupportedType2Charstring
}
return errInvalidCFFTable
}
p.type2Charstrings.closePath()
p.type2Charstrings.ended = true
return nil
}