postscript

package module
v0.4.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 6, 2024 License: GPL-3.0 Imports: 10 Imported by: 1

Documentation

Overview

Package postscript implements a rudimentary PostScript interpreter.

The package does not implement any drawing operations. It does implement enough of the PostScript language to be able to read most Type 1 fonts.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrExecutionLimitExceeded = &postScriptError{eInterrupt, "execution limit exceeded"}
	ErrNoPostScript           = errors.New("not a PostScript file")
)
View Source
var CIDInit = Dict{
	"begincmap": builtin(func(intp *Interpreter) error {
		intp.cmap = &CMapInfo{}
		return nil
	}),
	"endcmap": builtin(func(intp *Interpreter) error {
		if len(intp.DictStack) < 1 || intp.cmap == nil {
			return intp.e(eStackunderflow, "endcmap: cmap dictionary not found")
		}
		sort.Slice(intp.cmap.CodeSpaceRanges, func(i, j int) bool {
			if len(intp.cmap.CodeSpaceRanges[i].Low) != len(intp.cmap.CodeSpaceRanges[j].Low) {
				return len(intp.cmap.CodeSpaceRanges[i].Low) < len(intp.cmap.CodeSpaceRanges[j].Low)
			}
			return bytes.Compare(intp.cmap.CodeSpaceRanges[i].Low, intp.cmap.CodeSpaceRanges[j].Low) < 0
		})
		sort.Slice(intp.cmap.Chars, func(i, j int) bool {
			return bytes.Compare(intp.cmap.Chars[i].Src, intp.cmap.Chars[j].Src) < 0
		})
		sort.Slice(intp.cmap.Ranges, func(i, j int) bool {
			return bytes.Compare(intp.cmap.Ranges[i].Low, intp.cmap.Ranges[j].Low) < 0
		})
		dict := intp.DictStack[len(intp.DictStack)-1]
		dict["CodeMap"] = intp.cmap
		intp.cmap = nil
		return nil
	}),
	"usecmap": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "usecmap: not in cmap block")
		}
		if len(intp.Stack) < 1 {
			return intp.e(eStackunderflow, "usecmap: not enough arguments")
		}
		name, ok := intp.Stack[len(intp.Stack)-1].(Name)
		if !ok {
			return intp.e(eTypecheck, "usecmap: expected name, got %T", intp.Stack[len(intp.Stack)-1])
		}
		intp.Stack = intp.Stack[:len(intp.Stack)-1]
		intp.cmap.UseCMap = string(name)
		return nil
	}),
	"begincodespacerange": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "begincodespacerange: not in cmap block")
		}
		if len(intp.Stack) < 1 {
			return intp.e(eStackunderflow, "begincodespacerange: not enough arguments")
		}
		n, ok := intp.Stack[len(intp.Stack)-1].(Integer)
		if !ok {
			return intp.e(eTypecheck, "begincodespacerange: expected integer, got %T", intp.Stack[len(intp.Stack)-1])
		} else if n < 0 || n > 100 {
			return intp.e(eRangecheck, "begincodespacerange: invalid length %d", n)
		}
		intp.Stack = intp.Stack[:len(intp.Stack)-1]
		intp.cmap.tmpCodeSpaceRanges = make([]CodeSpaceRange, n)
		return nil
	}),
	"endcodespacerange": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "endcodespacerange: not in cmap block")
		}
		base := len(intp.Stack) - 2*len(intp.cmap.tmpCodeSpaceRanges)
		if base < 0 {
			return intp.e(eStackunderflow, "endcodespacerange: not enough arguments")
		}
		for i := range intp.cmap.tmpCodeSpaceRanges {
			lo, ok := intp.Stack[base+2*i].(String)
			if !ok {
				return intp.e(eTypecheck, "endcodespacerange: expected string, got %T", intp.Stack[base+2*i])
			}
			hi, ok := intp.Stack[base+2*i+1].(String)
			if !ok {
				return intp.e(eTypecheck, "endcodespacerange: expected string, got %T", intp.Stack[base+2*i+1])
			}
			if len(lo) != len(hi) {
				return intp.e(eRangecheck, "endcodespacerange: expected strings of equal length, got %d and %d", len(lo), len(hi))
			}
			intp.cmap.tmpCodeSpaceRanges[i] = CodeSpaceRange{lo, hi}
		}
		intp.Stack = intp.Stack[:base]
		intp.cmap.CodeSpaceRanges = append(intp.cmap.CodeSpaceRanges, intp.cmap.tmpCodeSpaceRanges...)
		intp.cmap.tmpCodeSpaceRanges = nil
		return nil
	}),
	"begincidchar": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "begincidchar: not in cmap block")
		}
		if len(intp.Stack) < 1 {
			return intp.e(eStackunderflow, "begincidchar: not enough arguments")
		}
		n, ok := intp.Stack[len(intp.Stack)-1].(Integer)
		if !ok {
			return intp.e(eTypecheck, "begincidchar: expected integer, got %T", intp.Stack[len(intp.Stack)-1])
		} else if n < 0 || n > 100 {
			return intp.e(eRangecheck, "begincidchar: invalid length %d", n)
		}
		intp.Stack = intp.Stack[:len(intp.Stack)-1]
		intp.cmap.tmpChars = make([]CharMap, n)
		return nil
	}),
	"endcidchar": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "endcidchar: not in cmap block")
		}
		base := len(intp.Stack) - 2*len(intp.cmap.tmpChars)
		if base < 0 {
			return intp.e(eStackunderflow, "endcidchar: not enough arguments")
		}
		for i := range intp.cmap.tmpChars {
			code, ok := intp.Stack[base+2*i].(String)
			if !ok {
				return intp.e(eTypecheck, "endcidchar: expected string, got %T", intp.Stack[base+2*i])
			}
			val := intp.Stack[base+2*i+1]
			if _, ok := val.(Integer); !ok {
				return intp.e(eTypecheck, "endcidchar: expected integer, got %T", val)
			}
			intp.cmap.tmpChars[i] = CharMap{code, val}
		}
		intp.Stack = intp.Stack[:base]
		intp.cmap.Chars = append(intp.cmap.Chars, intp.cmap.tmpChars...)
		intp.cmap.tmpChars = nil
		return nil
	}),
	"begincidrange": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "begincidrange: not in cmap block")
		}
		if len(intp.Stack) < 1 {
			return intp.e(eStackunderflow, "begincidrange: not enough arguments")
		}
		n, ok := intp.Stack[len(intp.Stack)-1].(Integer)
		if !ok {
			return intp.e(eTypecheck, "begincidrange: expected integer, got %T", intp.Stack[len(intp.Stack)-1])
		} else if n < 0 || n > 100 {
			return intp.e(eRangecheck, "begincidrange: invalid length %d", n)
		}
		intp.Stack = intp.Stack[:len(intp.Stack)-1]
		intp.cmap.tmpRanges = make([]RangeMap, n)
		return nil
	}),
	"endcidrange": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "endcidrange: not in cmap block")
		}
		base := len(intp.Stack) - 3*len(intp.cmap.tmpRanges)
		if base < 0 {
			return intp.e(eStackunderflow, "endcidrange: not enough arguments")
		}
		for i := range intp.cmap.tmpRanges {
			lo, ok := intp.Stack[base+3*i].(String)
			if !ok {
				return intp.e(eTypecheck, "endcidrange: expected string, got %T", intp.Stack[base+3*i])
			}
			hi, ok := intp.Stack[base+3*i+1].(String)
			if !ok {
				return intp.e(eTypecheck, "endcidrange: expected string, got %T", intp.Stack[base+3*i+1])
			}
			if len(lo) != len(hi) || bytes.Compare(lo, hi) > 0 {
				return intp.e(eRangecheck, "endcidrange: invalid range <%x> <%x>", lo, hi)
			}
			val := intp.Stack[base+3*i+2]
			if _, ok := val.(Integer); !ok {
				return intp.e(eTypecheck, "endcidrange: expected integer, got %T", val)
			}
			intp.cmap.tmpRanges[i].Low = lo
			intp.cmap.tmpRanges[i].High = hi
			intp.cmap.tmpRanges[i].Dst = val
		}
		intp.Stack = intp.Stack[:base]
		intp.cmap.Ranges = append(intp.cmap.Ranges, intp.cmap.tmpRanges...)
		intp.cmap.tmpRanges = nil
		return nil
	}),
	"beginbfchar": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "beginbfchar: not in cmap block")
		}
		if len(intp.Stack) < 1 {
			return intp.e(eStackunderflow, "beginbfchar: not enough arguments")
		}
		n, ok := intp.Stack[len(intp.Stack)-1].(Integer)
		if !ok {
			return intp.e(eTypecheck, "beginbfchar: expected integer, got %T", intp.Stack[len(intp.Stack)-1])
		} else if n < 0 || n > 100 {
			return intp.e(eRangecheck, "beginbfchar: invalid length %d", n)
		}
		intp.Stack = intp.Stack[:len(intp.Stack)-1]
		intp.cmap.tmpChars = make([]CharMap, n)
		return nil
	}),
	"endbfchar": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "endbfchar: not in cmap block")
		}
		base := len(intp.Stack) - 2*len(intp.cmap.tmpChars)
		if base < 0 {
			return intp.e(eStackunderflow, "endbfchar: not enough arguments")
		}
		for i := range intp.cmap.tmpChars {
			code, ok := intp.Stack[base+2*i].(String)
			if !ok {
				return intp.e(eTypecheck, "endbfchar: expected string, got %T", intp.Stack[base+2*i])
			}
			val := intp.Stack[base+2*i+1]
			if !isStringOrName(val) {
				return intp.e(eTypecheck, "endbfchar: expected string or name, got %T", val)
			}
			intp.cmap.tmpChars[i] = CharMap{code, val}
		}
		intp.Stack = intp.Stack[:base]
		intp.cmap.Chars = append(intp.cmap.Chars, intp.cmap.tmpChars...)
		intp.cmap.tmpChars = nil
		return nil
	}),
	"beginbfrange": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "beginbfrange: not in cmap block")
		}
		if len(intp.Stack) < 1 {
			return intp.e(eStackunderflow, "beginbfrange: not enough arguments")
		}
		n, ok := intp.Stack[len(intp.Stack)-1].(Integer)
		if !ok {
			return intp.e(eTypecheck, "beginbfrange: expected integer, got %T", intp.Stack[len(intp.Stack)-1])
		} else if n < 0 || n > 100 {
			return intp.e(eRangecheck, "beginbfrange: invalid length %d", n)
		}
		intp.Stack = intp.Stack[:len(intp.Stack)-1]
		intp.cmap.tmpRanges = make([]RangeMap, n)
		return nil
	}),
	"endbfrange": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "endbfrange: not in cmap block")
		}
		base := len(intp.Stack) - 3*len(intp.cmap.tmpRanges)
		if base < 0 {
			return intp.e(eStackunderflow, "endbfrange: not enough arguments")
		}
		for i := range intp.cmap.tmpRanges {
			lo, ok := intp.Stack[base+3*i].(String)
			if !ok {
				return intp.e(eTypecheck, "endbfrange: expected string, got %T", intp.Stack[base+3*i])
			}
			hi, ok := intp.Stack[base+3*i+1].(String)
			if !ok {
				return intp.e(eTypecheck, "endbfrange: expected string, got %T", intp.Stack[base+3*i+1])
			}
			if len(lo) != len(hi) || bytes.Compare(lo, hi) > 0 {
				return intp.e(eRangecheck, "endbfrange: invalid range <%x> <%x>", lo, hi)
			}
			val := intp.Stack[base+3*i+2]
			if !isStringOrArray(val) {
				return intp.e(eTypecheck, "endbfrange: expected string or array of names, got %T", val)
			}
			intp.cmap.tmpRanges[i].Low = lo
			intp.cmap.tmpRanges[i].High = hi
			intp.cmap.tmpRanges[i].Dst = val
		}
		intp.Stack = intp.Stack[:base]
		intp.cmap.Ranges = append(intp.cmap.Ranges, intp.cmap.tmpRanges...)
		intp.cmap.tmpRanges = nil
		return nil
	}),
	"beginnotdefchar": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "beginnotdefchar: not in cmap block")
		}
		if len(intp.Stack) < 1 {
			return intp.e(eStackunderflow, "beginnotdefchar: not enough arguments")
		}
		n, ok := intp.Stack[len(intp.Stack)-1].(Integer)
		if !ok {
			return intp.e(eTypecheck, "beginnotdefchar: expected integer, got %T", intp.Stack[len(intp.Stack)-1])
		} else if n < 0 || n > 100 {
			return intp.e(eRangecheck, "beginnotdefchar: invalid length %d", n)
		}
		intp.Stack = intp.Stack[:len(intp.Stack)-1]
		intp.cmap.tmpChars = make([]CharMap, n)
		return nil
	}),
	"endnotdefchar": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "endnotdefchar: not in cmap block")
		}
		base := len(intp.Stack) - 2*len(intp.cmap.tmpChars)
		if base < 0 {
			return intp.e(eStackunderflow, "endnotdefchar: not enough arguments")
		}
		for i := range intp.cmap.tmpChars {
			code, ok := intp.Stack[base+2*i].(String)
			if !ok {
				return intp.e(eTypecheck, "endnotdefchar: expected string, got %T", intp.Stack[base+2*i])
			}
			val := intp.Stack[base+2*i+1]
			if _, ok := val.(Integer); !ok {
				return intp.e(eTypecheck, "endnotdefchar: expected integer, got %T", val)
			}
			intp.cmap.tmpChars[i] = CharMap{code, val}
		}
		intp.Stack = intp.Stack[:base]

		intp.cmap.tmpChars = nil
		return nil
	}),
	"beginnotdefrange": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "beginnotdefrange: not in cmap block")
		}
		if len(intp.Stack) < 1 {
			return intp.e(eStackunderflow, "beginnotdefrange: not enough arguments")
		}
		n, ok := intp.Stack[len(intp.Stack)-1].(Integer)
		if !ok {
			return intp.e(eTypecheck, "beginnotdefrange: expected integer, got %T", intp.Stack[len(intp.Stack)-1])
		} else if n < 0 || n > 100 {
			return intp.e(eRangecheck, "beginnotdefrange: invalid length %d", n)
		}
		intp.Stack = intp.Stack[:len(intp.Stack)-1]
		intp.cmap.tmpRanges = make([]RangeMap, n)
		return nil
	}),
	"endnotdefrange": builtin(func(intp *Interpreter) error {
		if intp.cmap == nil {
			return intp.e(eUndefined, "endnotdefrange: not in cmap block")
		}
		base := len(intp.Stack) - 3*len(intp.cmap.tmpRanges)
		if base < 0 {
			return intp.e(eStackunderflow, "endnotdefrange: not enough arguments")
		}
		for i := range intp.cmap.tmpRanges {
			lo, ok := intp.Stack[base+3*i].(String)
			if !ok {
				return intp.e(eTypecheck, "endnotdefrange: expected string, got %T", intp.Stack[base+3*i])
			}
			hi, ok := intp.Stack[base+3*i+1].(String)
			if !ok {
				return intp.e(eTypecheck, "endnotdefrange: expected string, got %T", intp.Stack[base+3*i+1])
			}
			if len(lo) != len(hi) || bytes.Compare(lo, hi) > 0 {
				return intp.e(eRangecheck, "endnotdefrange: invalid range <%x> <%x>", lo, hi)
			}
			val := intp.Stack[base+3*i+2]
			if _, ok := val.(Integer); !ok {
				return intp.e(eTypecheck, "endnotdefrange: expected integer, got %T", val)
			}
			intp.cmap.tmpRanges[i].Low = lo
			intp.cmap.tmpRanges[i].High = hi
			intp.cmap.tmpRanges[i].Dst = val
		}
		intp.Stack = intp.Stack[:base]

		intp.cmap.tmpRanges = nil
		return nil
	}),
}

Functions

This section is empty.

Types

type Array

type Array []Object

type Boolean

type Boolean bool

type CMapInfo added in v0.3.5

type CMapInfo struct {
	CodeSpaceRanges []CodeSpaceRange

	Chars []CharMap

	Ranges []RangeMap

	UseCMap string
	// contains filtered or unexported fields
}

type CharMap added in v0.3.5

type CharMap struct {
	Src []byte
	Dst Object
}

type CodeSpaceRange added in v0.3.5

type CodeSpaceRange struct {
	Low, High []byte
}

type Comment

type Comment struct {
	Key   string
	Value string
}

type Dict

type Dict map[Name]Object

func (Dict) String

func (d Dict) String() string

type Integer

type Integer int

type Interpreter

type Interpreter struct {
	// CheckStart can be set to true to make the interpreter check that the
	// first two input bytes are "%!"; otherwise, ErrNoPostScript is returned.
	// This flag must be set before the first call to Execute.
	CheckStart bool

	// MaxOps can be set to a positive value to limit the number of executed
	// operations.  If this limit is exceeded, ErrExecutionLimitExceeded is
	// returned.
	MaxOps int

	// Stack is the PostScript operand stack.
	Stack []Object

	// DictStack is the PostScript dictionary stack.
	DictStack []Dict

	// NumOps is the number of executed operations so far.
	NumOps int

	// SystemDict is the PostScript system dictionary.
	SystemDict Dict

	// InternalDict is the PostScript internal dictionary.
	InternalDict Dict

	// UserDict is the PostScript user dictionary.
	UserDict Dict

	// ErrorDict is the PostScript error dictionary.
	ErrorDict Dict

	// Resources is a Dict of Dicts, which contains the resources
	// for each category.
	Resources Dict

	// FontDirectory is the PostScript font directory.
	// The `definefont` PostScript operator adds fonts to this dictionary.
	FontDirectory Dict

	CMapDirectory Dict

	// DSC contains all DSC comments found in the input so far.
	// These are comments of the form "%%key: value" or "%%key".
	DSC []Comment
	// contains filtered or unexported fields
}

Interpreter represents one instance of the PostScript interpreter.

func NewInterpreter

func NewInterpreter() *Interpreter

NewInterpreter creates a new instance of the PostScript interpreter.

func (*Interpreter) Execute

func (intp *Interpreter) Execute(r io.Reader) error

Execute executes the PostScript code in the given reader.

func (*Interpreter) ExecuteString

func (intp *Interpreter) ExecuteString(code string) error

ExecuteString executes the PostScript code in the given string.

type Name

type Name string

func (Name) PS

func (n Name) PS() string

func (Name) String

func (n Name) String() string

type Object

type Object interface{}

type Operator

type Operator string

type Procedure

type Procedure []Object

func (Procedure) String

func (p Procedure) String() string

type RangeMap added in v0.3.5

type RangeMap struct {
	Low, High []byte
	Dst       Object
}

type Real

type Real float64

type String

type String []byte

func (String) PS

func (s String) PS() string

PS returns the string as it would be written in a PostScript file.

func (String) String

func (s String) String() string

Directories

Path Synopsis
Package afm provides a parser for Adobe Font Metrics (AFM) files.
Package afm provides a parser for Adobe Font Metrics (AFM) files.
examples
Package funit contains types for representing font design units.
Package funit contains types for representing font design units.
Package pfb implements the PFB (Printer Font Binary) format.
Package pfb implements the PFB (Printer Font Binary) format.
Package type1 implements reading and writing of PostScript Type 1 fonts.
Package type1 implements reading and writing of PostScript Type 1 fonts.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL