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