// SPDX-License-Identifier: Unlicense OR BSD-3-Clause

// Package psinterpreter implement a Postscript interpreter // required to parse .CFF files, and Type1 and Type2 Charstrings. // This package provides the low-level mechanisms needed to // read such formats; the data is consumed in higher level packages, // which implement `PsOperatorHandler`. // It also provides helpers to interpret glyph outline descriptions, // shared between Type1 and CFF font formats. // // See https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf
package psinterpreter import ( ) var ( // ErrInterrupt signals the interpreter to stop early, without erroring. ErrInterrupt = errors.New("interruption") errInvalidCFFTable = errors.New("invalid ps instructions") errUnsupportedRealNumberEncoding = errors.New("unsupported real number encoding") be = binary.BigEndian ) const ( // psArgStackSize is the argument stack size for a PostScript interpreter, // set to 513 in CFF2 // See https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#appendixD psArgStackSize = 513 // Similarly, Appendix B says "Subr nesting, stack limit 10". psCallStackSize = 10 maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation. ) // Context is the flavour of the Postcript language. type Context uint32 const ( TopDict Context = iota // Top dict in CFF files PrivateDict // Private dict in CFF files Type2Charstring // Charstring in CFF files Type1Charstring // Charstring in Type1 font files ) type ArgStack struct { Vals [psArgStackSize]float64 // we have to use float64 to properly store floats and int32 values // Effecive size currently in use. The first value to // pop is at index Top-1 Top int32 } // Uint16 returns the top level value as uint16, // without popping the stack. func ( *ArgStack) () uint16 { return uint16(.Vals[.Top-1]) } // Pop returns the top level value and decrease `Top` // It will panic if the stack is empty. func ( *ArgStack) () float64 { .Top-- return .Vals[.Top] } // Clear clears the stack func ( *ArgStack) () { .Top = 0 } // PopN check and remove the n top levels entries. // Passing a negative `numPop` clears all the stack. func ( *ArgStack) ( int32) error { if .Top < { return fmt.Errorf("invalid number of operands in PS stack: %d", ) } if < 0 { // pop all .Top = 0 } else { .Top -= } return nil } // Machine is a PostScript interpreter. // A same interpreter may be re-used using muliples `Run` calls. type Machine struct { localSubrs [][]byte globalSubrs [][]byte instructions []byte callStack struct { vals [psCallStackSize][]byte // parent instructions top int32 // effecive size currently in use } ArgStack ArgStack parseNumberBuf [maxRealNumberStrLen]byte ctx Context } // SkipBytes skips the next `count` bytes from the instructions, and clears the argument stack. // It does nothing if `count` exceed the length of the instructions. func ( *Machine) ( int32) { if int() >= len(.instructions) { return } .instructions = .instructions[:] .ArgStack.Clear() } // 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an // initial escape byte of 12". const escapeByte = 12 // Run runs the instructions in the PostScript context asked by `handler`. // `localSubrs` and `globalSubrs` contains the subroutines that may be called in the instructions. func ( *Machine) ( []byte, , [][]byte, OperatorHandler) error { .ctx = .Context() .instructions = .localSubrs = .globalSubrs = .ArgStack.Top = 0 .callStack.top = 0 for len(.instructions) > 0 { // Push a numeric operand on the stack, if applicable. if , := .parseNumber(); { if != nil { return } continue } // Otherwise, execute an operator. := .instructions[0] .instructions = .instructions[1:] // check for the escape byte := == escapeByte if { if len(.instructions) <= 0 { return errInvalidCFFTable } = .instructions[0] .instructions = .instructions[1:] } := .Apply(, Operator{Operator: , IsEscaped: }) if == ErrInterrupt { // stop cleanly return nil } if != nil { return } } return nil } // See 5176.CFF.pdf section 4 "DICT Data". func ( *Machine) () ( bool, error) { := 0. switch := .instructions[0]; { case == 28: if len(.instructions) < 3 { return true, errInvalidCFFTable } , = float64(int16(be.Uint16(.instructions[1:]))), true .instructions = .instructions[3:] case == 29 && .ctx != Type2Charstring: if len(.instructions) < 5 { return true, errInvalidCFFTable } , = float64(int32(be.Uint32(.instructions[1:]))), true .instructions = .instructions[5:] case == 30 && .ctx != Type2Charstring && .ctx != Type1Charstring: // 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." := .parseNumberBuf[:0] .instructions = .instructions[1:] : for { if len(.instructions) == 0 { return true, errInvalidCFFTable } := .instructions[0] .instructions = .instructions[1:] // Process by's two nibbles, high then low. for := 0; < 2; ++ { := >> 4 = << 4 if == 0x0f { , := strconv.ParseFloat(string(), 32) if != nil { return true, errInvalidCFFTable } , = float64(), true break } if == 0x0d { return true, errInvalidCFFTable } if len()+maxNibbleDefsLength > len(.parseNumberBuf) { return true, errUnsupportedRealNumberEncoding } = append(, nibbleDefs[]...) } } case < 32: // not a number: no-op. case < 247: .instructions = .instructions[1:] , = float64()-139, true case < 251: if len(.instructions) < 2 { return true, errInvalidCFFTable } := .instructions[1] .instructions = .instructions[2:] , = float64(+int32(-247)*256+int32()+108), true case < 255: if len(.instructions) < 2 { return true, errInvalidCFFTable } := .instructions[1] .instructions = .instructions[2:] , = float64(-int32(-251)*256-int32()-108), true case == 255 && (.ctx == Type2Charstring || .ctx == Type1Charstring): if len(.instructions) < 5 { return true, errInvalidCFFTable } := int32(be.Uint32(.instructions[1:])) if .ctx == Type2Charstring { // 5177.Type2.pdf section 3.2 "Charstring Number Encoding" says "If the // charstring byte contains the value 255... [this] number is // interpreted as a Fixed; that is, a signed number with 16 bits of // fraction". // // we just round the 16.16 fixed point number to the closest integer value = float64() / (1 << 16) = true } else { , = float64(), true } .instructions = .instructions[5:] } if { if .ArgStack.Top == psArgStackSize { return true, errInvalidCFFTable } .ArgStack.Vals[.ArgStack.Top] = .ArgStack.Top++ } return , 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: "", } // subrBias returns the subroutine index bias as per 5177.Type2.pdf section 4.7 // "Subroutine Operators". func subrBias( int) int32 { if < 1240 { return 107 } if < 33900 { return 1131 } return 32768 } // CallSubroutine calls the subroutine, identified by its index, as found // in the instructions (that is, before applying the subroutine biased). // `isLocal` controls whether the local or global subroutines are used. // No argument stack modification is performed. func ( *Machine) ( int32, bool) error { := .globalSubrs if { = .localSubrs } // no bias in type1 fonts if .ctx == Type2Charstring { += subrBias(len()) } if < 0 || int() >= len() { return fmt.Errorf("invalid subroutine index %d (for length %d)", , len()) } if .callStack.top == psCallStackSize { return errors.New("maximum call stack size reached") } // save the current instructions .callStack.vals[.callStack.top] = .instructions .callStack.top++ // activate the subroutine .instructions = [] return nil } // Return returns from a subroutine call. func ( *Machine) () error { if .callStack.top <= 0 { return errors.New("no subroutine has been called") } .callStack.top-- // restore the previous instructions .instructions = .callStack.vals[.callStack.top] return nil } // Operator is a postcript command, which may be escaped. type Operator struct { Operator byte IsEscaped bool } func ( Operator) () string { if .IsEscaped { return fmt.Sprintf("2-byte operator (12 %d)", .Operator) } return fmt.Sprintf("1-byte operator (%d)", .Operator) } // OperatorHandler defines the behaviour of an operator. type OperatorHandler interface { // Context defines the precise behaviour of the interpreter, // which has small nuances depending on the context. Context() Context // Apply implements the operator defined by `operator` (which is the second byte if `escaped` is true). // // Returning `ErrInterrupt` stop the parsing of the instructions, without reporting an error. // It can be used as an optimization. Apply(state *Machine, operator Operator) error }