package psinterpreter
import (
"encoding/binary"
"errors"
"fmt"
"strconv"
)
var (
ErrInterrupt = errors .New ("interruption" )
errInvalidCFFTable = errors .New ("invalid ps instructions" )
errUnsupportedRealNumberEncoding = errors .New ("unsupported real number encoding" )
be = binary .BigEndian
)
const (
psArgStackSize = 513
psCallStackSize = 10
maxRealNumberStrLen = 64
)
type Context uint32
const (
TopDict Context = iota
PrivateDict
Type2Charstring
Type1Charstring
)
type ArgStack struct {
Vals [psArgStackSize ]float64
Top int32
}
func (a *ArgStack ) Uint16 () uint16 { return uint16 (a .Vals [a .Top -1 ]) }
func (a *ArgStack ) Pop () float64 {
a .Top --
return a .Vals [a .Top ]
}
func (a *ArgStack ) Clear () { a .Top = 0 }
func (a *ArgStack ) PopN (numPop int32 ) error {
if a .Top < numPop {
return fmt .Errorf ("invalid number of operands in PS stack: %d" , numPop )
}
if numPop < 0 {
a .Top = 0
} else {
a .Top -= numPop
}
return nil
}
type Machine struct {
localSubrs [][]byte
globalSubrs [][]byte
instructions []byte
callStack struct {
vals [psCallStackSize ][]byte
top int32
}
ArgStack ArgStack
parseNumberBuf [maxRealNumberStrLen ]byte
ctx Context
}
func (p *Machine ) SkipBytes (count int32 ) {
if int (count ) >= len (p .instructions ) {
return
}
p .instructions = p .instructions [count :]
p .ArgStack .Clear ()
}
const escapeByte = 12
func (p *Machine ) Run (instructions []byte , localSubrs , globalSubrs [][]byte , handler OperatorHandler ) error {
p .ctx = handler .Context ()
p .instructions = instructions
p .localSubrs = localSubrs
p .globalSubrs = globalSubrs
p .ArgStack .Top = 0
p .callStack .top = 0
for len (p .instructions ) > 0 {
if hasResult , err := p .parseNumber (); hasResult {
if err != nil {
return err
}
continue
}
b := p .instructions [0 ]
p .instructions = p .instructions [1 :]
escaped := b == escapeByte
if escaped {
if len (p .instructions ) <= 0 {
return errInvalidCFFTable
}
b = p .instructions [0 ]
p .instructions = p .instructions [1 :]
}
err := handler .Apply (p , Operator {Operator : b , IsEscaped : escaped })
if err == ErrInterrupt {
return nil
}
if err != nil {
return err
}
}
return nil
}
func (p *Machine ) parseNumber () (hasResult bool , err error ) {
number := 0.
switch b := p .instructions [0 ]; {
case b == 28 :
if len (p .instructions ) < 3 {
return true , errInvalidCFFTable
}
number , hasResult = float64 (int16 (be .Uint16 (p .instructions [1 :]))), true
p .instructions = p .instructions [3 :]
case b == 29 && p .ctx != Type2Charstring :
if len (p .instructions ) < 5 {
return true , errInvalidCFFTable
}
number , hasResult = float64 (int32 (be .Uint32 (p .instructions [1 :]))), true
p .instructions = p .instructions [5 :]
case b == 30 && p .ctx != Type2Charstring && p .ctx != Type1Charstring :
s := p .parseNumberBuf [:0 ]
p .instructions = p .instructions [1 :]
loop :
for {
if len (p .instructions ) == 0 {
return true , errInvalidCFFTable
}
by := p .instructions [0 ]
p .instructions = p .instructions [1 :]
for i := 0 ; i < 2 ; i ++ {
nib := by >> 4
by = by << 4
if nib == 0x0f {
f , err := strconv .ParseFloat (string (s ), 32 )
if err != nil {
return true , errInvalidCFFTable
}
number , hasResult = float64 (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 :
case b < 247 :
p .instructions = p .instructions [1 :]
number , hasResult = float64 (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 = float64 (+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 = float64 (-int32 (b -251 )*256 -int32 (b1 )-108 ), true
case b == 255 && (p .ctx == Type2Charstring || p .ctx == Type1Charstring ):
if len (p .instructions ) < 5 {
return true , errInvalidCFFTable
}
intValue := int32 (be .Uint32 (p .instructions [1 :]))
if p .ctx == Type2Charstring {
number = float64 (intValue ) / (1 << 16 )
hasResult = true
} else {
number , hasResult = float64 (intValue ), true
}
p .instructions = p .instructions [5 :]
}
if hasResult {
if p .ArgStack .Top == psArgStackSize {
return true , errInvalidCFFTable
}
p .ArgStack .Vals [p .ArgStack .Top ] = number
p .ArgStack .Top ++
}
return hasResult , nil
}
const maxNibbleDefsLength = len ("E-" )
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 : "" ,
}
func subrBias(numSubroutines int ) int32 {
if numSubroutines < 1240 {
return 107
}
if numSubroutines < 33900 {
return 1131
}
return 32768
}
func (p *Machine ) CallSubroutine (index int32 , isLocal bool ) error {
subrs := p .globalSubrs
if isLocal {
subrs = p .localSubrs
}
if p .ctx == Type2Charstring {
index += subrBias (len (subrs ))
}
if index < 0 || int (index ) >= len (subrs ) {
return fmt .Errorf ("invalid subroutine index %d (for length %d)" , index , len (subrs ))
}
if p .callStack .top == psCallStackSize {
return errors .New ("maximum call stack size reached" )
}
p .callStack .vals [p .callStack .top ] = p .instructions
p .callStack .top ++
p .instructions = subrs [index ]
return nil
}
func (p *Machine ) Return () error {
if p .callStack .top <= 0 {
return errors .New ("no subroutine has been called" )
}
p .callStack .top --
p .instructions = p .callStack .vals [p .callStack .top ]
return nil
}
type Operator struct {
Operator byte
IsEscaped bool
}
func (p Operator ) String () string {
if p .IsEscaped {
return fmt .Sprintf ("2-byte operator (12 %d)" , p .Operator )
}
return fmt .Sprintf ("1-byte operator (%d)" , p .Operator )
}
type OperatorHandler interface {
Context () Context
Apply (state *Machine , operator Operator ) error
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .