2017-07-08 21:04:13 +00:00
|
|
|
/*
|
|
|
|
* This file is subject to the terms and conditions defined in
|
|
|
|
* file 'LICENSE.md', which is part of this source code package.
|
|
|
|
*/
|
|
|
|
|
2017-02-22 21:10:57 +00:00
|
|
|
package ps
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
2019-12-18 12:30:54 -08:00
|
|
|
"fmt"
|
2017-02-22 21:10:57 +00:00
|
|
|
"io"
|
|
|
|
|
2019-05-16 23:08:40 +03:00
|
|
|
"github.com/unidoc/unipdf/v3/common"
|
2019-05-16 23:44:51 +03:00
|
|
|
pdfcore "github.com/unidoc/unipdf/v3/core"
|
2019-12-18 12:30:54 -08:00
|
|
|
"github.com/unidoc/unipdf/v3/internal/parseutils"
|
2017-02-22 21:10:57 +00:00
|
|
|
)
|
|
|
|
|
2018-08-01 20:27:34 +00:00
|
|
|
// PSParser is a basic Postscript parser.
|
2017-02-22 21:10:57 +00:00
|
|
|
type PSParser struct {
|
|
|
|
reader *bufio.Reader
|
|
|
|
}
|
|
|
|
|
2018-08-01 20:27:34 +00:00
|
|
|
// NewPSParser returns a new instance of the PDF Postscript parser from input data.
|
2017-02-22 21:10:57 +00:00
|
|
|
func NewPSParser(content []byte) *PSParser {
|
|
|
|
parser := PSParser{}
|
|
|
|
|
|
|
|
buffer := bytes.NewBuffer(content)
|
|
|
|
parser.reader = bufio.NewReader(buffer)
|
|
|
|
|
|
|
|
return &parser
|
|
|
|
}
|
|
|
|
|
2018-08-01 20:27:34 +00:00
|
|
|
// Parse parses the postscript and store as a program that can be executed.
|
|
|
|
func (p *PSParser) Parse() (*PSProgram, error) {
|
|
|
|
p.skipSpaces()
|
|
|
|
bb, err := p.reader.Peek(2)
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if bb[0] != '{' {
|
2018-08-01 20:27:34 +00:00
|
|
|
return nil, errors.New("invalid PS Program not starting with {")
|
2017-02-22 21:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-08-01 20:27:34 +00:00
|
|
|
program, err := p.parseFunction()
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return program, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Detect the signature at the current parse position and parse
|
|
|
|
// the corresponding object.
|
2018-08-01 20:27:34 +00:00
|
|
|
func (p *PSParser) parseFunction() (*PSProgram, error) {
|
|
|
|
c, _ := p.reader.ReadByte()
|
2017-02-22 21:10:57 +00:00
|
|
|
if c != '{' {
|
2018-08-01 20:27:34 +00:00
|
|
|
return nil, errors.New("invalid function")
|
2017-02-22 21:10:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function := NewPSProgram()
|
|
|
|
|
|
|
|
for {
|
2018-08-01 20:27:34 +00:00
|
|
|
p.skipSpaces()
|
|
|
|
bb, err := p.reader.Peek(2)
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-03-02 18:06:32 +00:00
|
|
|
common.Log.Trace("Peek string: %s", string(bb))
|
2017-02-22 21:10:57 +00:00
|
|
|
// Determine type.
|
|
|
|
if bb[0] == '}' {
|
2017-03-02 18:06:32 +00:00
|
|
|
common.Log.Trace("EOF function")
|
2018-08-01 20:27:34 +00:00
|
|
|
p.reader.ReadByte()
|
2017-02-22 21:10:57 +00:00
|
|
|
break
|
|
|
|
} else if bb[0] == '{' {
|
2017-03-02 18:06:32 +00:00
|
|
|
common.Log.Trace("Function!")
|
2018-08-01 20:27:34 +00:00
|
|
|
inlineF, err := p.parseFunction()
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
function.Append(inlineF)
|
|
|
|
} else if pdfcore.IsDecimalDigit(bb[0]) || (bb[0] == '-' && pdfcore.IsDecimalDigit(bb[1])) {
|
2017-03-02 18:06:32 +00:00
|
|
|
common.Log.Trace("->Number!")
|
2018-08-01 20:27:34 +00:00
|
|
|
number, err := p.parseNumber()
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
function.Append(number)
|
|
|
|
} else {
|
2017-03-02 18:06:32 +00:00
|
|
|
common.Log.Trace("->Operand or bool?")
|
2017-02-22 21:10:57 +00:00
|
|
|
// Let's peek farther to find out.
|
2018-08-01 20:27:34 +00:00
|
|
|
bb, _ = p.reader.Peek(5)
|
2017-02-22 21:10:57 +00:00
|
|
|
peekStr := string(bb)
|
2017-03-02 18:06:32 +00:00
|
|
|
common.Log.Trace("Peek str: %s", peekStr)
|
2017-02-22 21:10:57 +00:00
|
|
|
|
|
|
|
if (len(peekStr) > 4) && (peekStr[:5] == "false") {
|
2018-08-01 20:27:34 +00:00
|
|
|
b, err := p.parseBool()
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
function.Append(b)
|
|
|
|
} else if (len(peekStr) > 3) && (peekStr[:4] == "true") {
|
2018-08-01 20:27:34 +00:00
|
|
|
b, err := p.parseBool()
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
function.Append(b)
|
|
|
|
} else {
|
2018-08-01 20:27:34 +00:00
|
|
|
operand, err := p.parseOperand()
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
function.Append(operand)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return function, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip over any spaces. Returns the number of spaces skipped and
|
|
|
|
// an error if any.
|
2018-08-01 20:27:34 +00:00
|
|
|
func (p *PSParser) skipSpaces() (int, error) {
|
2017-02-22 21:10:57 +00:00
|
|
|
cnt := 0
|
|
|
|
for {
|
2018-08-01 20:27:34 +00:00
|
|
|
bb, err := p.reader.Peek(1)
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if pdfcore.IsWhiteSpace(bb[0]) {
|
2018-08-01 20:27:34 +00:00
|
|
|
p.reader.ReadByte()
|
2017-02-22 21:10:57 +00:00
|
|
|
cnt++
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cnt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Numeric objects.
|
|
|
|
// Integer or Real numbers.
|
2018-08-01 20:27:34 +00:00
|
|
|
func (p *PSParser) parseNumber() (PSObject, error) {
|
2019-12-18 12:30:54 -08:00
|
|
|
num, err := parseutils.ParseNumber(p.reader)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-02-22 21:10:57 +00:00
|
|
|
}
|
2019-12-18 12:30:54 -08:00
|
|
|
switch num := num.(type) {
|
|
|
|
case float64:
|
|
|
|
return MakeReal(num), nil
|
|
|
|
case int64:
|
|
|
|
return MakeInteger(int(num)), nil
|
2017-02-22 21:10:57 +00:00
|
|
|
}
|
2019-12-18 12:30:54 -08:00
|
|
|
return nil, fmt.Errorf("unhandled number type %T", num)
|
2017-02-22 21:10:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse bool object.
|
2018-08-01 20:27:34 +00:00
|
|
|
func (p *PSParser) parseBool() (*PSBoolean, error) {
|
|
|
|
bb, err := p.reader.Peek(4)
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return MakeBool(false), err
|
|
|
|
}
|
|
|
|
if (len(bb) >= 4) && (string(bb[:4]) == "true") {
|
2018-08-01 20:27:34 +00:00
|
|
|
p.reader.Discard(4)
|
2017-02-22 21:10:57 +00:00
|
|
|
return MakeBool(true), nil
|
|
|
|
}
|
|
|
|
|
2018-08-01 20:27:34 +00:00
|
|
|
bb, err = p.reader.Peek(5)
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return MakeBool(false), err
|
|
|
|
}
|
|
|
|
if (len(bb) >= 5) && (string(bb[:5]) == "false") {
|
2018-08-01 20:27:34 +00:00
|
|
|
p.reader.Discard(5)
|
2017-02-22 21:10:57 +00:00
|
|
|
return MakeBool(false), nil
|
|
|
|
}
|
|
|
|
|
2018-08-01 20:27:34 +00:00
|
|
|
return MakeBool(false), errors.New("unexpected boolean string")
|
2017-02-22 21:10:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// An operand is a text command represented by a word.
|
2018-08-01 20:27:34 +00:00
|
|
|
func (p *PSParser) parseOperand() (*PSOperand, error) {
|
2018-12-09 19:28:50 +02:00
|
|
|
var bytes []byte
|
2017-02-22 21:10:57 +00:00
|
|
|
for {
|
2018-08-01 20:27:34 +00:00
|
|
|
bb, err := p.reader.Peek(1)
|
2017-02-22 21:10:57 +00:00
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if pdfcore.IsDelimiter(bb[0]) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if pdfcore.IsWhiteSpace(bb[0]) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2018-08-01 20:27:34 +00:00
|
|
|
b, _ := p.reader.ReadByte()
|
2017-02-22 21:10:57 +00:00
|
|
|
bytes = append(bytes, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(bytes) == 0 {
|
2018-08-01 20:27:34 +00:00
|
|
|
return nil, errors.New("invalid operand (empty)")
|
2017-02-22 21:10:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return MakeOperand(string(bytes)), nil
|
|
|
|
}
|