/* * 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 core import ( "bytes" "fmt" "github.com/unidoc/unidoc/common" ) // PDF Primitives implement the PdfObject interface. type PdfObject interface { String() string // Output a string representation of the primitive (for debugging). DefaultWriteString() string // Output the PDF primitive as expected by the standard. } 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)) } func (this *PdfObjectName) DefaultWriteString() string { var output bytes.Buffer if len(*this) > 127 { common.Log.Debug("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 }