unipdf/pdf/pdfobject.go
2016-08-20 11:32:22 +00:00

302 lines
6.3 KiB
Go

/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
// Defines PDF primitive objects as per the standard. Also defines a PdfObject
// interface allowing to universally work with these objects. It allows
// recursive writing of the objects to file as well and stringifying for
// debug purposes.
package pdf
import (
"bytes"
"fmt"
"github.com/unidoc/unidoc/common"
)
type PdfObject interface {
String() string
DefaultWriteString() string
// Make a recursive traverse function too with a handler function?
}
type PdfObjectBool bool
type PdfObjectInteger int64
type PdfObjectFloat float64
type PdfObjectString string
type PdfObjectName string
type PdfObjectArray []PdfObject
type PdfObjectDictionary map[PdfObjectName]PdfObject
type PdfObjectNull struct{}
type PdfObjectReference struct {
ObjectNumber int64
GenerationNumber int64
}
type PdfIndirectObject struct {
PdfObjectReference
PdfObject
}
type PdfObjectStream struct {
PdfObjectReference
*PdfObjectDictionary
Stream []byte
}
// Quick functions to make pdf objects form primitive objects.
func MakeName(s string) *PdfObjectName {
name := PdfObjectName(s)
return &name
}
func MakeInteger(val int64) *PdfObjectInteger {
num := PdfObjectInteger(val)
return &num
}
func MakeFloat(val float64) *PdfObjectFloat {
num := PdfObjectFloat(val)
return &num
}
func MakeString(s string) *PdfObjectString {
str := PdfObjectString(s)
return &str
}
func MakeNull() *PdfObjectNull {
null := PdfObjectNull{}
return &null
}
func (this *PdfObjectBool) String() string {
if *this {
return "true"
} else {
return "false"
}
}
func (this *PdfObjectBool) DefaultWriteString() string {
if *this {
return "true"
} else {
return "false"
}
}
func (this *PdfObjectInteger) String() string {
return fmt.Sprintf("%d", *this)
}
func (this *PdfObjectInteger) DefaultWriteString() string {
return fmt.Sprintf("%d", *this)
}
func (this *PdfObjectFloat) String() string {
return fmt.Sprintf("%f", *this)
}
func (this *PdfObjectFloat) DefaultWriteString() string {
return fmt.Sprintf("%f", *this)
}
func (this *PdfObjectString) String() string {
return fmt.Sprintf("%s", string(*this))
}
func (this *PdfObjectString) DefaultWriteString() string {
var output bytes.Buffer
escapeSequences := map[byte]string{
'\n': "\\n",
'\r': "\\r",
'\t': "\\t",
'\b': "\\b",
'\f': "\\f",
'(': "\\(",
')': "\\)",
'\\': "\\\\",
}
output.WriteString("(")
for i := 0; i < len(*this); i++ {
char := (*this)[i]
if escStr, useEsc := escapeSequences[char]; useEsc {
output.WriteString(escStr)
} else {
output.WriteByte(char)
}
}
output.WriteString(")")
return output.String()
}
func (this *PdfObjectName) String() string {
return fmt.Sprintf("%s", string(*this))
}
// Regular characters that are outside the range EXCLAMATION MARK(21h)
// (!) to TILDE (7Eh) (~) should be written using the hexadecimal notation.
func isPrintable(char byte) bool {
if char < 0x21 || char > 0x7E {
return false
}
return true
}
func isDelimiter(char byte) bool {
if char == '(' || char == ')' {
return true
}
if char == '<' || char == '>' {
return true
}
if char == '[' || char == ']' {
return true
}
if char == '{' || char == '}' {
return true
}
if char == '/' {
return true
}
if char == '%' {
return true
}
return false
}
func (this *PdfObjectName) DefaultWriteString() string {
var output bytes.Buffer
if len(*this) > 127 {
common.Log.Error("Name too long (%s)", *this)
}
output.WriteString("/")
for i := 0; i < len(*this); i++ {
char := (*this)[i]
if !isPrintable(char) || char == '#' || isDelimiter(char) {
output.WriteString(fmt.Sprintf("#%.2x", char))
} else {
output.WriteByte(char)
}
}
return output.String()
}
func (this *PdfObjectArray) String() string {
outStr := "["
for ind, o := range *this {
outStr += o.String()
if ind < (len(*this) - 1) {
outStr += ", "
}
}
outStr += "]"
return outStr
}
func (this *PdfObjectArray) DefaultWriteString() string {
outStr := "["
for ind, o := range *this {
outStr += o.DefaultWriteString()
if ind < (len(*this) - 1) {
outStr += " "
}
}
outStr += "]"
return outStr
}
func (this *PdfObjectDictionary) String() string {
outStr := "Dict("
for k, v := range *this {
outStr += fmt.Sprintf("\"%s\": %s, ", k, v.String())
}
outStr += ")"
return outStr
}
func (this *PdfObjectDictionary) DefaultWriteString() string {
outStr := "<<"
for k, v := range *this {
common.Log.Debug("Writing k: %s %T", k, v)
outStr += k.DefaultWriteString()
outStr += " "
outStr += v.DefaultWriteString()
}
outStr += ">>"
return outStr
}
func (d *PdfObjectDictionary) Set(key PdfObjectName, val PdfObject) {
(*d)[key] = val
}
func (d *PdfObjectDictionary) SetIfNotNil(key PdfObjectName, val PdfObject) {
if val != nil {
(*d)[key] = val
}
}
func (this *PdfObjectReference) String() string {
return fmt.Sprintf("Ref(%d %d)", this.ObjectNumber, this.GenerationNumber)
}
func (this *PdfObjectReference) DefaultWriteString() string {
return fmt.Sprintf("%d %d R", this.ObjectNumber, this.GenerationNumber)
}
func (this *PdfIndirectObject) String() string {
// Avoid printing out the object, can cause problems with circular
// references.
return fmt.Sprintf("IObject:%d", (*this).ObjectNumber)
}
func (this *PdfIndirectObject) DefaultWriteString() string {
outStr := fmt.Sprintf("%d 0 R", (*this).ObjectNumber)
return outStr
}
func (this *PdfObjectStream) String() string {
return fmt.Sprintf("Object stream %d: %s", this.ObjectNumber, this.PdfObjectDictionary)
}
func (this *PdfObjectStream) DefaultWriteString() string {
outStr := fmt.Sprintf("%d 0 R", (*this).ObjectNumber)
return outStr
}
func (this *PdfObjectNull) String() string {
return "null"
}
func (this *PdfObjectNull) DefaultWriteString() string {
return "null"
}
// Handy functions to work with primitive objects.
// Traces a pdf object to a direct object. For example contained
// in indirect objects (can be double referenced even).
//
// Note: This function does not trace/resolve references.
// That needs to be done beforehand.
func TraceToDirectObject(obj PdfObject) PdfObject {
iobj, isIndirectObj := obj.(*PdfIndirectObject)
for isIndirectObj == true {
obj = iobj.PdfObject
iobj, isIndirectObj = obj.(*PdfIndirectObject)
}
return obj
}