unipdf/pdf/model/writer.go

930 lines
26 KiB
Go
Raw Normal View History

/*
* This file is subject to the terms and conditions defined in
2016-07-29 17:23:39 +00:00
* file 'LICENSE.md', which is part of this source code package.
*/
// Default writing implementation. Basic output with version 1.3
// for compatibility.
package model
import (
"bufio"
2018-09-29 17:22:53 +03:00
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
2016-07-17 19:59:17 +00:00
"github.com/unidoc/unidoc/common"
2017-07-11 13:08:48 +00:00
"github.com/unidoc/unidoc/common/license"
. "github.com/unidoc/unidoc/pdf/core"
"github.com/unidoc/unidoc/pdf/core/security"
"github.com/unidoc/unidoc/pdf/core/security/crypt"
2018-02-23 14:07:26 +00:00
"github.com/unidoc/unidoc/pdf/model/fonts"
)
2018-09-29 17:22:53 +03:00
type crossReference struct {
Type int
// Type 1
Offset int64
Generation int64 // and Type 0
// Type 2
ObjectNumber int // and Type 0
Index int
}
2016-08-02 16:31:37 +00:00
var pdfCreator = ""
func getPdfProducer() string {
2017-07-08 22:00:11 +00:00
licenseKey := license.GetLicenseKey()
return fmt.Sprintf("UniDoc v%s (%s) - http://unidoc.io", getUniDocVersion(), licenseKey.TypeToString())
2016-08-02 16:31:37 +00:00
}
func getPdfCreator() string {
if len(pdfCreator) > 0 {
return pdfCreator
}
// Return default.
return "UniDoc - http://unidoc.io"
}
// SetPdfCreator sets the Creator attribute of the output PDF.
2016-08-02 16:31:37 +00:00
func SetPdfCreator(creator string) {
pdfCreator = creator
}
// PdfWriter handles outputing PDF content.
type PdfWriter struct {
root *PdfIndirectObject
pages *PdfIndirectObject
objects []PdfObject
objectsMap map[PdfObject]bool // Quick lookup table.
writer *bufio.Writer
writePos int64 // Represents the current position within output file.
outlines []*PdfIndirectObject
outlineTree *PdfOutlineTreeNode
catalog *PdfObjectDictionary
fields []PdfObject
infoObj *PdfIndirectObject
// Encryption
crypter *PdfCrypt
encryptDict *PdfObjectDictionary
encryptObj *PdfIndirectObject
ids *PdfObjectArray
// PDF version
majorVersion int
minorVersion int
// Objects to be followed up on prior to writing.
// These are objects that are added and reference objects that are not included
// for writing.
// The map stores the object and the dictionary it is contained in.
// Only way so we can access the dictionary entry later.
pendingObjects map[PdfObject]*PdfObjectDictionary
2016-09-07 17:56:45 +00:00
// Forms.
acroForm *PdfAcroForm
2018-09-29 17:22:53 +03:00
optimizer Optimizer
crossReferenceMap map[int]crossReference
}
// NewPdfWriter initializes a new PdfWriter.
func NewPdfWriter() PdfWriter {
w := PdfWriter{}
w.objectsMap = map[PdfObject]bool{}
w.objects = []PdfObject{}
w.pendingObjects = map[PdfObject]*PdfObjectDictionary{}
// PDF Version. Can be changed if using more advanced features in PDF.
// By default it is set to 1.3.
w.majorVersion = 1
w.minorVersion = 3
// Creation info.
infoDict := MakeDict()
infoDict.Set("Producer", MakeString(getPdfProducer()))
infoDict.Set("Creator", MakeString(getPdfCreator()))
infoObj := PdfIndirectObject{}
infoObj.PdfObject = infoDict
w.infoObj = &infoObj
w.addObject(&infoObj)
// Root catalog.
catalog := PdfIndirectObject{}
catalogDict := MakeDict()
catalogDict.Set("Type", MakeName("Catalog"))
catalog.PdfObject = catalogDict
w.root = &catalog
w.addObject(&catalog)
// Pages.
pages := PdfIndirectObject{}
pagedict := MakeDict()
pagedict.Set("Type", MakeName("Pages"))
kids := PdfObjectArray{}
pagedict.Set("Kids", &kids)
pagedict.Set("Count", MakeInteger(0))
pages.PdfObject = pagedict
w.pages = &pages
w.addObject(&pages)
catalogDict.Set("Pages", &pages)
w.catalog = catalogDict
common.Log.Trace("Catalog %s", catalog)
return w
}
2018-09-29 17:22:53 +03:00
// copyObject creates deep copy of the Pdf object and
// fills objectToObjectCopyMap to replace the old object to the copy of object if needed.
// Parameter objectToObjectCopyMap is needed to replace object references to its copies.
// Because many objects can contain references to another objects like pages to images.
func copyObject(obj PdfObject, objectToObjectCopyMap map[PdfObject]PdfObject) PdfObject {
if newObj, ok := objectToObjectCopyMap[obj]; ok {
return newObj
}
switch t := obj.(type) {
case *PdfObjectArray:
newObj := &PdfObjectArray{}
objectToObjectCopyMap[obj] = newObj
for _, val := range t.Elements() {
newObj.Append(copyObject(val, objectToObjectCopyMap))
}
return newObj
case *PdfObjectStreams:
newObj := &PdfObjectStreams{PdfObjectReference: t.PdfObjectReference}
objectToObjectCopyMap[obj] = newObj
for _, val := range t.Elements() {
newObj.Append(copyObject(val, objectToObjectCopyMap))
}
return newObj
case *PdfObjectStream:
newObj := &PdfObjectStream{
Stream: t.Stream,
PdfObjectReference: t.PdfObjectReference,
}
objectToObjectCopyMap[obj] = newObj
newObj.PdfObjectDictionary = copyObject(t.PdfObjectDictionary, objectToObjectCopyMap).(*PdfObjectDictionary)
return newObj
case *PdfObjectDictionary:
newObj := MakeDict()
objectToObjectCopyMap[obj] = newObj
for _, key := range t.Keys() {
val := t.Get(key)
newObj.Set(key, copyObject(val, objectToObjectCopyMap))
}
return newObj
case *PdfIndirectObject:
newObj := &PdfIndirectObject{
PdfObjectReference: t.PdfObjectReference,
}
objectToObjectCopyMap[obj] = newObj
newObj.PdfObject = copyObject(t.PdfObject, objectToObjectCopyMap)
return newObj
case *PdfObjectString:
newObj := &PdfObjectString{}
*newObj = *t
objectToObjectCopyMap[obj] = newObj
return newObj
case *PdfObjectName:
newObj := PdfObjectName(*t)
objectToObjectCopyMap[obj] = &newObj
return &newObj
case *PdfObjectNull:
newObj := PdfObjectNull{}
objectToObjectCopyMap[obj] = &newObj
return &newObj
case *PdfObjectInteger:
newObj := PdfObjectInteger(*t)
objectToObjectCopyMap[obj] = &newObj
return &newObj
case *PdfObjectReference:
newObj := PdfObjectReference(*t)
objectToObjectCopyMap[obj] = &newObj
return &newObj
case *PdfObjectFloat:
newObj := PdfObjectFloat(*t)
objectToObjectCopyMap[obj] = &newObj
return &newObj
case *PdfObjectBool:
newObj := PdfObjectBool(*t)
objectToObjectCopyMap[obj] = &newObj
return &newObj
default:
common.Log.Info("TODO(a5i): implement copyObject for %+v", obj)
}
// return other objects as is
return obj
}
// copyObjects makes objects copy and set as working.
func (this *PdfWriter) copyObjects() {
objectToObjectCopyMap := make(map[PdfObject]PdfObject)
objects := make([]PdfObject, len(this.objects))
objectsMap := make(map[PdfObject]bool)
for i, obj := range this.objects {
newObject := copyObject(obj, objectToObjectCopyMap)
objects[i] = newObject
if this.objectsMap[obj] {
objectsMap[newObject] = true
}
}
this.objects = objects
this.objectsMap = objectsMap
this.infoObj = copyObject(this.infoObj, objectToObjectCopyMap).(*PdfIndirectObject)
this.root = copyObject(this.root, objectToObjectCopyMap).(*PdfIndirectObject)
2018-09-30 18:45:35 +00:00
if this.encryptObj != nil {
this.encryptObj = copyObject(this.encryptObj, objectToObjectCopyMap).(*PdfIndirectObject)
}
2018-09-29 17:22:53 +03:00
}
// SetVersion sets the PDF version of the output file.
func (this *PdfWriter) SetVersion(majorVersion, minorVersion int) {
this.majorVersion = majorVersion
this.minorVersion = minorVersion
}
// SetOCProperties sets the optional content properties.
func (this *PdfWriter) SetOCProperties(ocProperties PdfObject) error {
dict := this.catalog
if ocProperties != nil {
common.Log.Trace("Setting OC Properties...")
dict.Set("OCProperties", ocProperties)
// Any risk of infinite loops?
this.addObjects(ocProperties)
}
return nil
}
2018-09-29 17:22:53 +03:00
// SetOptimizer sets the optimizer to optimize PDF before writing.
func (this *PdfWriter) SetOptimizer(optimizer Optimizer) {
this.optimizer = optimizer
}
// GetOptimizer returns current PDF optimizer.
func (this *PdfWriter) GetOptimizer() Optimizer {
return this.optimizer
}
func (this *PdfWriter) hasObject(obj PdfObject) bool {
// Check if already added.
for _, o := range this.objects {
// GH: May perform better to use a hash map to check if added?
if o == obj {
return true
}
}
return false
}
// Adds the object to list of objects and returns true if the obj was
// not already added.
// Returns false if the object was previously added.
func (this *PdfWriter) addObject(obj PdfObject) bool {
hasObj := this.hasObject(obj)
if !hasObj {
this.objects = append(this.objects, obj)
return true
}
return false
}
func (this *PdfWriter) addObjects(obj PdfObject) error {
common.Log.Trace("Adding objects!")
if io, isIndirectObj := obj.(*PdfIndirectObject); isIndirectObj {
common.Log.Trace("Indirect")
common.Log.Trace("- %s (%p)", obj, io)
common.Log.Trace("- %s", io.PdfObject)
if this.addObject(io) {
err := this.addObjects(io.PdfObject)
if err != nil {
return err
}
}
return nil
}
if so, isStreamObj := obj.(*PdfObjectStream); isStreamObj {
common.Log.Trace("Stream")
common.Log.Trace("- %s %p", obj, obj)
if this.addObject(so) {
err := this.addObjects(so.PdfObjectDictionary)
if err != nil {
return err
}
}
return nil
}
if dict, isDict := obj.(*PdfObjectDictionary); isDict {
common.Log.Trace("Dict")
common.Log.Trace("- %s", obj)
for _, k := range dict.Keys() {
v := dict.Get(k)
common.Log.Trace("Key %s", k)
if k != "Parent" {
err := this.addObjects(v)
if err != nil {
return err
}
} else {
if _, parentIsNull := dict.Get("Parent").(*PdfObjectNull); parentIsNull {
// Parent is null. We can ignore it.
continue
}
if hasObj := this.hasObject(v); !hasObj {
common.Log.Debug("Parent obj is missing!! %T %p %v", v, v, v)
this.pendingObjects[v] = dict
// Although it is missing at this point, it could be added later...
}
// How to handle the parent? Make sure it is present?
if parentObj, parentIsRef := dict.Get("Parent").(*PdfObjectReference); parentIsRef {
// Parent is a reference. Means we can drop it?
// Could refer to somewhere outside of the scope of the output doc.
// Should be done by the reader already.
// -> ERROR.
2016-10-31 21:48:25 +00:00
common.Log.Debug("ERROR: Parent is a reference object - Cannot be in writer (needs to be resolved)")
return fmt.Errorf("Parent is a reference object - Cannot be in writer (needs to be resolved) - %s", parentObj)
}
}
}
return nil
}
if arr, isArray := obj.(*PdfObjectArray); isArray {
common.Log.Trace("Array")
common.Log.Trace("- %s", obj)
if arr == nil {
return errors.New("Array is nil")
}
for _, v := range arr.Elements() {
err := this.addObjects(v)
if err != nil {
return err
}
}
return nil
}
if _, isReference := obj.(*PdfObjectReference); isReference {
// Should never be a reference, should already be resolved.
2016-10-31 21:48:25 +00:00
common.Log.Debug("ERROR: Cannot be a reference!")
return errors.New("Reference not allowed")
}
return nil
}
// AddPage adds a page to the PDF file. The new page should be an indirect object.
func (this *PdfWriter) AddPage(page *PdfPage) error {
obj := page.ToPdfObject()
common.Log.Trace("==========")
common.Log.Trace("Appending to page list %T", obj)
2018-02-23 14:07:26 +00:00
procPage(page)
pageObj, ok := obj.(*PdfIndirectObject)
if !ok {
return errors.New("Page should be an indirect object")
}
common.Log.Trace("%s", pageObj)
common.Log.Trace("%s", pageObj.PdfObject)
pDict, ok := pageObj.PdfObject.(*PdfObjectDictionary)
2016-07-17 21:42:09 +00:00
if !ok {
return errors.New("Page object should be a dictionary")
}
otype, ok := pDict.Get("Type").(*PdfObjectName)
2016-07-17 21:42:09 +00:00
if !ok {
return fmt.Errorf("Page should have a Type key with a value of type name (%T)", pDict.Get("Type"))
2016-07-17 21:42:09 +00:00
}
if *otype != "Page" {
return errors.New("Type != Page (Required).")
}
// Copy inherited fields if missing.
inheritedFields := []PdfObjectName{"Resources", "MediaBox", "CropBox", "Rotate"}
parent, hasParent := pDict.Get("Parent").(*PdfIndirectObject)
common.Log.Trace("Page Parent: %T (%v)", pDict.Get("Parent"), hasParent)
for hasParent {
common.Log.Trace("Page Parent: %T", parent)
parentDict, ok := parent.PdfObject.(*PdfObjectDictionary)
if !ok {
return errors.New("Invalid Parent object")
}
for _, field := range inheritedFields {
common.Log.Trace("Field %s", field)
if pDict.Get(field) != nil {
common.Log.Trace("- page has already")
continue
}
if obj := parentDict.Get(field); obj != nil {
// Parent has the field. Inherit, pass to the new page.
common.Log.Trace("Inheriting field %s", field)
pDict.Set(field, obj)
}
}
parent, hasParent = parentDict.Get("Parent").(*PdfIndirectObject)
common.Log.Trace("Next parent: %T", parentDict.Get("Parent"))
}
common.Log.Trace("Traversal done")
// Update the dictionary.
// Reuses the input object, updating the fields.
pDict.Set("Parent", this.pages)
pageObj.PdfObject = pDict
// Add to Pages.
2016-07-17 21:42:09 +00:00
pagesDict, ok := this.pages.PdfObject.(*PdfObjectDictionary)
if !ok {
return errors.New("Invalid Pages obj (not a dict)")
}
kids, ok := pagesDict.Get("Kids").(*PdfObjectArray)
2016-07-17 21:42:09 +00:00
if !ok {
return errors.New("Invalid Pages Kids obj (not an array)")
}
kids.Append(pageObj)
pageCount, ok := pagesDict.Get("Count").(*PdfObjectInteger)
2016-07-17 21:42:09 +00:00
if !ok {
return errors.New("Invalid Pages Count object (not an integer)")
}
// Update the count.
*pageCount = *pageCount + 1
this.addObject(pageObj)
// Traverse the page and record all object references.
err := this.addObjects(pDict)
if err != nil {
return err
}
return nil
}
2018-02-23 14:07:26 +00:00
func procPage(p *PdfPage) {
lk := license.GetLicenseKey()
if lk != nil && lk.IsLicensed() {
return
}
// Add font as needed.
f := fonts.NewFontHelvetica()
p.Resources.SetFontByName("UF1", f.ToPdfObject())
var ops []string
2018-02-23 14:07:26 +00:00
ops = append(ops, "q")
ops = append(ops, "BT")
ops = append(ops, "/UF1 14 Tf")
ops = append(ops, "1 0 0 rg")
ops = append(ops, "10 10 Td")
s := "Unlicensed UniDoc - Get a license on https://unidoc.io"
ops = append(ops, fmt.Sprintf("(%s) Tj", s))
ops = append(ops, "ET")
ops = append(ops, "Q")
contentstr := strings.Join(ops, "\n")
p.AddContentStreamByString(contentstr)
// Update page object.
p.ToPdfObject()
}
// AddOutlineTree adds outlines to a PDF file.
func (this *PdfWriter) AddOutlineTree(outlineTree *PdfOutlineTreeNode) {
this.outlineTree = outlineTree
}
// Look for a specific key. Returns a list of entries.
// What if something appears on many pages?
func (this *PdfWriter) seekByName(obj PdfObject, followKeys []string, key string) ([]PdfObject, error) {
common.Log.Trace("Seek by name.. %T", obj)
var list []PdfObject
if io, isIndirectObj := obj.(*PdfIndirectObject); isIndirectObj {
return this.seekByName(io.PdfObject, followKeys, key)
}
if so, isStreamObj := obj.(*PdfObjectStream); isStreamObj {
return this.seekByName(so.PdfObjectDictionary, followKeys, key)
}
if dict, isDict := obj.(*PdfObjectDictionary); isDict {
common.Log.Trace("Dict")
for _, k := range dict.Keys() {
v := dict.Get(k)
if string(k) == key {
list = append(list, v)
}
for _, followKey := range followKeys {
if string(k) == followKey {
common.Log.Trace("Follow key %s", followKey)
items, err := this.seekByName(v, followKeys, key)
if err != nil {
return list, err
}
for _, item := range items {
list = append(list, item)
}
break
}
}
}
return list, nil
}
return list, nil
}
// SetForms sets the Acroform for a PDF file.
func (this *PdfWriter) SetForms(form *PdfAcroForm) error {
2016-09-07 17:56:45 +00:00
this.acroForm = form
return nil
}
// writeObject writes out an indirect / stream object.
func (this *PdfWriter) writeObject(num int, obj PdfObject) {
common.Log.Trace("Write obj #%d\n", num)
if pobj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
2018-09-29 17:22:53 +03:00
this.crossReferenceMap[num] = crossReference{Type: 1, Offset: this.writePos, Generation: pobj.GenerationNumber}
outStr := fmt.Sprintf("%d 0 obj\n", num)
outStr += pobj.PdfObject.DefaultWriteString()
outStr += "\nendobj\n"
this.writeString(outStr)
return
}
// XXX/TODO: Add a default encoder if Filter not specified?
// Still need to make sure is encrypted.
if pobj, isStream := obj.(*PdfObjectStream); isStream {
2018-09-29 17:22:53 +03:00
this.crossReferenceMap[num] = crossReference{Type: 1, Offset: this.writePos, Generation: pobj.GenerationNumber}
outStr := fmt.Sprintf("%d 0 obj\n", num)
outStr += pobj.PdfObjectDictionary.DefaultWriteString()
outStr += "\nstream\n"
this.writeString(outStr)
this.writeBytes(pobj.Stream)
this.writeString("\nendstream\nendobj\n")
return
}
2018-09-29 17:22:53 +03:00
if ostreams, isObjStreams := obj.(*PdfObjectStreams); isObjStreams {
this.crossReferenceMap[num] = crossReference{Type: 1, Offset: this.writePos, Generation: ostreams.GenerationNumber}
outStr := fmt.Sprintf("%d 0 obj\n", num)
var offsets []string
var objData string
var offset int64
for index, obj := range ostreams.Elements() {
io, isIndirect := obj.(*PdfIndirectObject)
if !isIndirect {
common.Log.Error("Object streams N %d contains non indirect pdf object %v", num, obj)
}
data := io.PdfObject.DefaultWriteString() + " "
objData = objData + data
offsets = append(offsets, fmt.Sprintf("%d %d", io.ObjectNumber, offset))
this.crossReferenceMap[int(io.ObjectNumber)] = crossReference{Type: 2, ObjectNumber: num, Index: index}
offset = offset + int64(len([]byte(data)))
}
offsetsStr := strings.Join(offsets, " ") + " "
encoder := NewFlateEncoder()
//encoder := NewRawEncoder()
dict := encoder.MakeStreamDict()
dict.Set(PdfObjectName("Type"), MakeName("ObjStm"))
n := int64(ostreams.Len())
dict.Set(PdfObjectName("N"), MakeInteger(n))
first := int64(len(offsetsStr))
dict.Set(PdfObjectName("First"), MakeInteger(first))
data, _ := encoder.EncodeBytes([]byte(offsetsStr + objData))
length := int64(len(data))
dict.Set(PdfObjectName("Length"), MakeInteger(length))
outStr += dict.DefaultWriteString()
outStr += "\nstream\n"
this.writeString(outStr)
this.writeBytes(data)
this.writeString("\nendstream\nendobj\n")
return
}
this.writer.WriteString(obj.DefaultWriteString())
}
// Update all the object numbers prior to writing.
func (this *PdfWriter) updateObjectNumbers() {
// Update numbers
for idx, obj := range this.objects {
2018-10-03 01:27:38 +03:00
switch o := obj.(type) {
case *PdfIndirectObject:
o.ObjectNumber = int64(idx + 1)
o.GenerationNumber = 0
case *PdfObjectStream:
o.ObjectNumber = int64(idx + 1)
o.GenerationNumber = 0
case *PdfObjectStreams:
o.ObjectNumber = int64(idx + 1)
o.GenerationNumber = 0
2018-09-29 17:22:53 +03:00
}
}
}
// EncryptOptions represents encryption options for an output PDF.
2016-07-09 16:40:39 +00:00
type EncryptOptions struct {
Permissions security.Permissions
Algorithm EncryptionAlgorithm
2016-07-09 16:40:39 +00:00
}
// EncryptionAlgorithm is used in EncryptOptions to change the default algorithm used to encrypt the document.
type EncryptionAlgorithm int
const (
// RC4_128bit uses RC4 encryption (128 bit)
RC4_128bit = EncryptionAlgorithm(iota)
// AES_128bit uses AES encryption (128 bit, PDF 1.6)
AES_128bit
// AES_256bit uses AES encryption (256 bit, PDF 2.0)
AES_256bit
)
// Encrypt encrypts the output file with a specified user/owner password.
2016-07-09 16:40:39 +00:00
func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptions) error {
algo := RC4_128bit
if options != nil {
algo = options.Algorithm
}
perm := security.PermOwner
if options != nil {
perm = options.Permissions
}
var cf crypt.Filter
switch algo {
case RC4_128bit:
cf = crypt.NewFilterV2(16)
case AES_128bit:
cf = crypt.NewFilterAESV2()
case AES_256bit:
cf = crypt.NewFilterAESV3()
default:
return fmt.Errorf("unsupported algorithm: %v", options.Algorithm)
}
crypter, info, err := PdfCryptNewEncrypt(cf, userPass, ownerPass, perm)
if err != nil {
return err
}
this.crypter = crypter
if info.Major != 0 {
this.SetVersion(info.Major, info.Minor)
}
this.encryptDict = info.Encrypt
this.ids = MakeArray(MakeHexString(info.ID0), MakeHexString(info.ID1))
// Make an object to contain the encryption dictionary.
io := MakeIndirectObject(info.Encrypt)
this.encryptObj = io
this.addObject(io)
return nil
}
// Wrapper function to handle writing out string.
func (this *PdfWriter) writeString(s string) error {
n, err := this.writer.WriteString(s)
if err != nil {
return err
}
this.writePos += int64(n)
return nil
}
// Wrapper function to handle writing out bytes.
func (this *PdfWriter) writeBytes(bb []byte) error {
n, err := this.writer.Write(bb)
if err != nil {
return err
}
this.writePos += int64(n)
return nil
}
// Write writes out the PDF.
func (this *PdfWriter) Write(writer io.Writer) error {
common.Log.Trace("Write()")
2018-02-23 14:07:26 +00:00
lk := license.GetLicenseKey()
if lk == nil || !lk.IsLicensed() {
fmt.Printf("Unlicensed copy of unidoc\n")
fmt.Printf("To get rid of the watermark - Please get a license on https://unidoc.io\n")
}
// Outlines.
if this.outlineTree != nil {
common.Log.Trace("OutlineTree: %+v", this.outlineTree)
outlines := this.outlineTree.ToPdfObject()
common.Log.Trace("Outlines: %+v (%T, p:%p)", outlines, outlines, outlines)
this.catalog.Set("Outlines", outlines)
err := this.addObjects(outlines)
if err != nil {
return err
}
}
2016-08-18 15:55:20 +00:00
// Form fields.
2016-09-07 17:56:45 +00:00
if this.acroForm != nil {
common.Log.Trace("Writing acro forms")
indObj := this.acroForm.ToPdfObject()
common.Log.Trace("AcroForm: %+v", indObj)
this.catalog.Set("AcroForm", indObj)
2016-09-07 17:56:45 +00:00
err := this.addObjects(indObj)
if err != nil {
return err
}
}
// Check pending objects prior to write.
for pendingObj, pendingObjDict := range this.pendingObjects {
if !this.hasObject(pendingObj) {
common.Log.Debug("ERROR Pending object %+v %T (%p) never added for writing", pendingObj, pendingObj, pendingObj)
for _, key := range pendingObjDict.Keys() {
val := pendingObjDict.Get(key)
if val == pendingObj {
common.Log.Debug("Pending object found! and replaced with null")
pendingObjDict.Set(key, MakeNull())
break
}
}
}
}
// Set version in the catalog.
this.catalog.Set("Version", MakeName(fmt.Sprintf("%d.%d", this.majorVersion, this.minorVersion)))
// Make a copy of objects prior to optimizing as this can alter the objects.
2018-09-29 17:22:53 +03:00
this.copyObjects()
if this.optimizer != nil {
var err error
this.objects, err = this.optimizer.Optimize(this.objects)
if err != nil {
return err
}
}
w := bufio.NewWriter(writer)
this.writer = w
this.writePos = 0
2018-09-29 17:22:53 +03:00
useCrossReferenceStream := this.majorVersion > 1 || (this.majorVersion == 1 && this.minorVersion > 4)
objectsInObjectStreams := make(map[PdfObject]bool)
if !useCrossReferenceStream {
for _, obj := range this.objects {
if objStm, isObjectStreams := obj.(*PdfObjectStreams); isObjectStreams {
useCrossReferenceStream = true
for _, obj := range objStm.Elements() {
objectsInObjectStreams[obj] = true
if io, isIndirectObj := obj.(*PdfIndirectObject); isIndirectObj {
objectsInObjectStreams[io.PdfObject] = true
}
}
}
}
}
if useCrossReferenceStream && this.majorVersion == 1 && this.minorVersion < 5 {
this.minorVersion = 5
}
this.writeString(fmt.Sprintf("%%PDF-%d.%d\n", this.majorVersion, this.minorVersion))
this.writeString("%âãÏÓ\n")
this.updateObjectNumbers()
// Write objects
common.Log.Trace("Writing %d obj", len(this.objects))
2018-09-29 17:22:53 +03:00
this.crossReferenceMap = make(map[int]crossReference)
this.crossReferenceMap[0] = crossReference{Type: 0, ObjectNumber: 0, Generation: 0xFFFF}
for idx, obj := range this.objects {
2018-09-29 17:22:53 +03:00
if skip := objectsInObjectStreams[obj]; skip {
continue
}
common.Log.Trace("Writing %d", idx)
// Encrypt prior to writing.
// Encrypt dictionary should not be encrypted.
if this.crypter != nil && obj != this.encryptObj {
err := this.crypter.Encrypt(obj, int64(idx+1), 0)
if err != nil {
2016-10-31 21:48:25 +00:00
common.Log.Debug("ERROR: Failed encrypting (%s)", err)
return err
}
}
this.writeObject(idx+1, obj)
}
xrefOffset := this.writePos
2018-09-29 17:22:53 +03:00
if useCrossReferenceStream {
crossObjNumber := len(this.crossReferenceMap)
this.crossReferenceMap[crossObjNumber] = crossReference{Type: 1, ObjectNumber: crossObjNumber, Offset: xrefOffset}
crossReferenceData := bytes.NewBuffer(nil)
for idx := 0; idx < len(this.crossReferenceMap); idx++ {
ref := this.crossReferenceMap[idx]
switch ref.Type {
case 0:
binary.Write(crossReferenceData, binary.BigEndian, byte(0))
binary.Write(crossReferenceData, binary.BigEndian, uint32(0))
binary.Write(crossReferenceData, binary.BigEndian, uint16(0xFFFF))
case 1:
binary.Write(crossReferenceData, binary.BigEndian, byte(1))
binary.Write(crossReferenceData, binary.BigEndian, uint32(ref.Offset))
binary.Write(crossReferenceData, binary.BigEndian, uint16(ref.Generation))
case 2:
binary.Write(crossReferenceData, binary.BigEndian, byte(2))
binary.Write(crossReferenceData, binary.BigEndian, uint32(ref.ObjectNumber))
binary.Write(crossReferenceData, binary.BigEndian, uint16(ref.Index))
}
}
crossReferenceStream, err := MakeStream(crossReferenceData.Bytes(), NewFlateEncoder())
if err != nil {
return err
}
crossReferenceStream.ObjectNumber = int64(crossObjNumber)
crossReferenceStream.PdfObjectDictionary.Set("Type", MakeName("XRef"))
crossReferenceStream.PdfObjectDictionary.Set("W", MakeArray(MakeInteger(1), MakeInteger(4), MakeInteger(2)))
crossReferenceStream.PdfObjectDictionary.Set("Index", MakeArray(MakeInteger(0), MakeInteger(crossReferenceStream.ObjectNumber+1)))
crossReferenceStream.PdfObjectDictionary.Set("Size", MakeInteger(crossReferenceStream.ObjectNumber+1))
crossReferenceStream.PdfObjectDictionary.Set("Info", this.infoObj)
crossReferenceStream.PdfObjectDictionary.Set("Root", this.root)
// If encrypted!
if this.crypter != nil {
crossReferenceStream.Set("Encrypt", this.encryptObj)
crossReferenceStream.Set("ID", this.ids)
common.Log.Trace("Ids: %s", this.ids)
}
this.writeObject(int(crossReferenceStream.ObjectNumber), crossReferenceStream)
} else {
this.writeString("xref\r\n")
outStr := fmt.Sprintf("%d %d\r\n", 0, len(this.crossReferenceMap))
this.writeString(outStr)
2018-09-29 17:22:53 +03:00
for idx := 0; idx < len(this.crossReferenceMap); idx++ {
ref := this.crossReferenceMap[idx]
switch ref.Type {
case 0:
outStr = fmt.Sprintf("%.10d %.5d f\r\n", 0, 65535)
this.writeString(outStr)
case 1:
outStr = fmt.Sprintf("%.10d %.5d n\r\n", ref.Offset, 0)
this.writeString(outStr)
}
}
// Generate & write trailer
trailer := MakeDict()
trailer.Set("Info", this.infoObj)
trailer.Set("Root", this.root)
trailer.Set("Size", MakeInteger(int64(len(this.objects)+1)))
// If encrypted!
if this.crypter != nil {
trailer.Set("Encrypt", this.encryptObj)
trailer.Set("ID", this.ids)
common.Log.Trace("Ids: %s", this.ids)
}
this.writeString("trailer\n")
this.writeString(trailer.DefaultWriteString())
this.writeString("\n")
}
// Make offset reference.
2018-09-29 17:22:53 +03:00
outStr := fmt.Sprintf("startxref\n%d\n", xrefOffset)
this.writeString(outStr)
this.writeString("%%EOF\n")
this.writer.Flush()
return nil
}