2016-07-09 14:09:27 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
2016-07-09 14:09:27 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
// Default writing implementation. Basic output with version 1.3
|
|
|
|
// for compatibility.
|
|
|
|
|
2016-09-08 17:53:45 +00:00
|
|
|
package model
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2018-09-29 17:22:53 +03:00
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
2016-07-09 14:09:27 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2018-07-14 02:25:29 +00:00
|
|
|
"strings"
|
2016-07-09 14:09:27 +00:00
|
|
|
|
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"
|
2016-09-08 17:53:45 +00:00
|
|
|
. "github.com/unidoc/unidoc/pdf/core"
|
2018-10-04 04:33:32 +03:00
|
|
|
"github.com/unidoc/unidoc/pdf/core/security"
|
2018-10-08 01:04:56 +03:00
|
|
|
"github.com/unidoc/unidoc/pdf/core/security/crypt"
|
2018-02-23 14:07:26 +00:00
|
|
|
"github.com/unidoc/unidoc/pdf/model/fonts"
|
2016-07-09 14:09:27 +00:00
|
|
|
)
|
|
|
|
|
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"
|
|
|
|
}
|
|
|
|
|
2018-07-15 17:52:53 +00:00
|
|
|
// SetPdfCreator sets the Creator attribute of the output PDF.
|
2016-08-02 16:31:37 +00:00
|
|
|
func SetPdfCreator(creator string) {
|
|
|
|
pdfCreator = creator
|
|
|
|
}
|
|
|
|
|
2018-07-15 17:52:53 +00:00
|
|
|
// PdfWriter handles outputing PDF content.
|
2016-07-09 14:09:27 +00:00
|
|
|
type PdfWriter struct {
|
2016-08-18 09:43:44 +00:00
|
|
|
root *PdfIndirectObject
|
|
|
|
pages *PdfIndirectObject
|
|
|
|
objects []PdfObject
|
|
|
|
objectsMap map[PdfObject]bool // Quick lookup table.
|
|
|
|
writer *bufio.Writer
|
2018-07-17 23:56:59 +00:00
|
|
|
writePos int64 // Represents the current position within output file.
|
2016-08-18 09:43:44 +00:00
|
|
|
outlines []*PdfIndirectObject
|
|
|
|
outlineTree *PdfOutlineTreeNode
|
|
|
|
catalog *PdfObjectDictionary
|
|
|
|
fields []PdfObject
|
|
|
|
infoObj *PdfIndirectObject
|
2016-12-05 16:21:23 +00:00
|
|
|
|
2016-07-09 14:09:27 +00:00
|
|
|
// Encryption
|
|
|
|
crypter *PdfCrypt
|
|
|
|
encryptDict *PdfObjectDictionary
|
|
|
|
encryptObj *PdfIndirectObject
|
|
|
|
ids *PdfObjectArray
|
2016-12-05 16:21:23 +00:00
|
|
|
|
2017-02-28 08:58:50 +00:00
|
|
|
// PDF version
|
|
|
|
majorVersion int
|
|
|
|
minorVersion int
|
|
|
|
|
2016-12-05 16:21:23 +00:00
|
|
|
// Objects to be followed up on prior to writing.
|
|
|
|
// These are objects that are added and reference objects that are not included
|
|
|
|
// for writing.
|
2016-12-06 01:18:44 +00:00
|
|
|
// 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-12-05 16:21:23 +00:00
|
|
|
|
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
|
2018-12-11 16:06:34 +03:00
|
|
|
writeOffset int64 // used by PdfAppender
|
|
|
|
ObjNumOffset int
|
|
|
|
appendMode bool
|
|
|
|
appendToXrefs XrefTable
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// NewPdfWriter initializes a new PdfWriter.
|
2016-07-09 14:09:27 +00:00
|
|
|
func NewPdfWriter() PdfWriter {
|
|
|
|
w := PdfWriter{}
|
|
|
|
|
|
|
|
w.objectsMap = map[PdfObject]bool{}
|
|
|
|
w.objects = []PdfObject{}
|
2016-12-06 01:18:44 +00:00
|
|
|
w.pendingObjects = map[PdfObject]*PdfObjectDictionary{}
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2017-02-28 08:58:50 +00:00
|
|
|
// 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
|
|
|
|
|
2016-07-09 14:09:27 +00:00
|
|
|
// Creation info.
|
2017-07-08 21:04:13 +00:00
|
|
|
infoDict := MakeDict()
|
|
|
|
infoDict.Set("Producer", MakeString(getPdfProducer()))
|
|
|
|
infoDict.Set("Creator", MakeString(getPdfCreator()))
|
2016-07-09 14:09:27 +00:00
|
|
|
infoObj := PdfIndirectObject{}
|
2017-07-08 21:04:13 +00:00
|
|
|
infoObj.PdfObject = infoDict
|
2016-07-09 14:09:27 +00:00
|
|
|
w.infoObj = &infoObj
|
|
|
|
w.addObject(&infoObj)
|
|
|
|
|
|
|
|
// Root catalog.
|
|
|
|
catalog := PdfIndirectObject{}
|
2017-07-08 21:04:13 +00:00
|
|
|
catalogDict := MakeDict()
|
|
|
|
catalogDict.Set("Type", MakeName("Catalog"))
|
|
|
|
catalog.PdfObject = catalogDict
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
w.root = &catalog
|
2018-12-11 16:06:34 +03:00
|
|
|
w.addObject(w.root)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Pages.
|
|
|
|
pages := PdfIndirectObject{}
|
2017-07-08 21:04:13 +00:00
|
|
|
pagedict := MakeDict()
|
|
|
|
pagedict.Set("Type", MakeName("Pages"))
|
2016-07-09 14:09:27 +00:00
|
|
|
kids := PdfObjectArray{}
|
2017-07-08 21:04:13 +00:00
|
|
|
pagedict.Set("Kids", &kids)
|
|
|
|
pagedict.Set("Count", MakeInteger(0))
|
|
|
|
pages.PdfObject = pagedict
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
w.pages = &pages
|
2018-12-11 16:06:34 +03:00
|
|
|
w.addObject(w.pages)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
catalogDict.Set("Pages", &pages)
|
|
|
|
w.catalog = catalogDict
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Catalog %s", catalog)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
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.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) copyObjects() {
|
2018-09-29 17:22:53 +03:00
|
|
|
objectToObjectCopyMap := make(map[PdfObject]PdfObject)
|
2018-12-09 21:25:30 +02:00
|
|
|
objects := make([]PdfObject, len(w.objects))
|
2018-09-29 17:22:53 +03:00
|
|
|
objectsMap := make(map[PdfObject]bool)
|
2018-12-09 21:25:30 +02:00
|
|
|
for i, obj := range w.objects {
|
2018-09-29 17:22:53 +03:00
|
|
|
newObject := copyObject(obj, objectToObjectCopyMap)
|
|
|
|
objects[i] = newObject
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.objectsMap[obj] {
|
2018-09-29 17:22:53 +03:00
|
|
|
objectsMap[newObject] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
w.objects = objects
|
|
|
|
w.objectsMap = objectsMap
|
|
|
|
w.infoObj = copyObject(w.infoObj, objectToObjectCopyMap).(*PdfIndirectObject)
|
|
|
|
w.root = copyObject(w.root, objectToObjectCopyMap).(*PdfIndirectObject)
|
|
|
|
if w.encryptObj != nil {
|
|
|
|
w.encryptObj = copyObject(w.encryptObj, objectToObjectCopyMap).(*PdfIndirectObject)
|
2018-09-30 18:45:35 +00:00
|
|
|
}
|
2018-09-29 17:22:53 +03:00
|
|
|
}
|
|
|
|
|
2018-12-09 20:22:33 +02:00
|
|
|
// SetVersion sets the PDF version of the output file.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) SetVersion(majorVersion, minorVersion int) {
|
|
|
|
w.majorVersion = majorVersion
|
|
|
|
w.minorVersion = minorVersion
|
2017-02-28 08:58:50 +00:00
|
|
|
}
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// SetOCProperties sets the optional content properties.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) SetOCProperties(ocProperties PdfObject) error {
|
|
|
|
dict := w.catalog
|
2017-02-15 14:21:20 +00:00
|
|
|
|
|
|
|
if ocProperties != nil {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Setting OC Properties...")
|
2017-07-08 21:04:13 +00:00
|
|
|
dict.Set("OCProperties", ocProperties)
|
2017-02-15 14:21:20 +00:00
|
|
|
// Any risk of infinite loops?
|
2018-12-09 21:25:30 +02:00
|
|
|
w.addObjects(ocProperties)
|
2017-02-15 14:21:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-29 17:22:53 +03:00
|
|
|
// SetOptimizer sets the optimizer to optimize PDF before writing.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) SetOptimizer(optimizer Optimizer) {
|
|
|
|
w.optimizer = optimizer
|
2018-09-29 17:22:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetOptimizer returns current PDF optimizer.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) GetOptimizer() Optimizer {
|
|
|
|
return w.optimizer
|
2018-09-29 17:22:53 +03:00
|
|
|
}
|
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) hasObject(obj PdfObject) bool {
|
2016-07-09 14:09:27 +00:00
|
|
|
// Check if already added.
|
2018-12-09 21:25:30 +02:00
|
|
|
for _, o := range w.objects {
|
2016-07-09 14:09:27 +00:00
|
|
|
// 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.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) addObject(obj PdfObject) bool {
|
|
|
|
hasObj := w.hasObject(obj)
|
2016-07-09 14:09:27 +00:00
|
|
|
if !hasObj {
|
2018-12-09 21:25:30 +02:00
|
|
|
w.objects = append(w.objects, obj)
|
2016-07-09 14:09:27 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) addObjects(obj PdfObject) error {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Adding objects!")
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
if io, isIndirectObj := obj.(*PdfIndirectObject); isIndirectObj {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Indirect")
|
|
|
|
common.Log.Trace("- %s (%p)", obj, io)
|
|
|
|
common.Log.Trace("- %s", io.PdfObject)
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.addObject(io) {
|
|
|
|
err := w.addObjects(io.PdfObject)
|
2016-07-09 14:09:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if so, isStreamObj := obj.(*PdfObjectStream); isStreamObj {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Stream")
|
|
|
|
common.Log.Trace("- %s %p", obj, obj)
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.addObject(so) {
|
|
|
|
err := w.addObjects(so.PdfObjectDictionary)
|
2016-07-09 14:09:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if dict, isDict := obj.(*PdfObjectDictionary); isDict {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Dict")
|
|
|
|
common.Log.Trace("- %s", obj)
|
2017-07-08 21:04:13 +00:00
|
|
|
for _, k := range dict.Keys() {
|
|
|
|
v := dict.Get(k)
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Key %s", k)
|
2016-07-09 14:09:27 +00:00
|
|
|
if k != "Parent" {
|
2018-12-09 21:25:30 +02:00
|
|
|
err := w.addObjects(v)
|
2016-07-09 14:09:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-08-16 16:22:01 +00:00
|
|
|
} else {
|
2017-07-08 21:04:13 +00:00
|
|
|
if _, parentIsNull := dict.Get("Parent").(*PdfObjectNull); parentIsNull {
|
2016-12-05 16:21:23 +00:00
|
|
|
// Parent is null. We can ignore it.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
if hasObj := w.hasObject(v); !hasObj {
|
2017-04-19 12:05:20 +00:00
|
|
|
common.Log.Debug("Parent obj is missing!! %T %p %v", v, v, v)
|
2018-12-09 21:25:30 +02:00
|
|
|
w.pendingObjects[v] = dict
|
2016-12-05 16:21:23 +00:00
|
|
|
// Although it is missing at this point, it could be added later...
|
2016-09-08 17:53:45 +00:00
|
|
|
}
|
2016-08-16 16:22:01 +00:00
|
|
|
// How to handle the parent? Make sure it is present?
|
2017-07-08 21:04:13 +00:00
|
|
|
if parentObj, parentIsRef := dict.Get("Parent").(*PdfObjectReference); parentIsRef {
|
2016-08-16 16:22:01 +00:00
|
|
|
// 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)")
|
2018-12-08 19:16:52 +02:00
|
|
|
return fmt.Errorf("parent is a reference object - Cannot be in writer (needs to be resolved) - %s", parentObj)
|
2016-08-16 16:22:01 +00:00
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if arr, isArray := obj.(*PdfObjectArray); isArray {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Array")
|
|
|
|
common.Log.Trace("- %s", obj)
|
2017-04-04 05:51:58 +00:00
|
|
|
if arr == nil {
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("array is nil")
|
2017-04-04 05:51:58 +00:00
|
|
|
}
|
2018-07-15 17:52:53 +00:00
|
|
|
for _, v := range arr.Elements() {
|
2018-12-09 21:25:30 +02:00
|
|
|
err := w.addObjects(v)
|
2016-07-09 14:09:27 +00:00
|
|
|
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!")
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("reference not allowed")
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// AddPage adds a page to the PDF file. The new page should be an indirect object.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) AddPage(page *PdfPage) error {
|
2016-12-13 19:40:33 +00:00
|
|
|
obj := page.ToPdfObject()
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("==========")
|
|
|
|
common.Log.Trace("Appending to page list %T", obj)
|
2018-02-23 14:07:26 +00:00
|
|
|
procPage(page)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2016-12-13 19:40:33 +00:00
|
|
|
pageObj, ok := obj.(*PdfIndirectObject)
|
2016-07-09 14:09:27 +00:00
|
|
|
if !ok {
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("page should be an indirect object")
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("%s", pageObj)
|
|
|
|
common.Log.Trace("%s", pageObj.PdfObject)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2016-12-13 19:40:33 +00:00
|
|
|
pDict, ok := pageObj.PdfObject.(*PdfObjectDictionary)
|
2016-07-17 21:42:09 +00:00
|
|
|
if !ok {
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("page object should be a dictionary")
|
2016-07-17 21:42:09 +00:00
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
otype, ok := pDict.Get("Type").(*PdfObjectName)
|
2016-07-17 21:42:09 +00:00
|
|
|
if !ok {
|
2018-12-08 19:16:52 +02:00
|
|
|
return fmt.Errorf("page should have a Type key with a value of type name (%T)", pDict.Get("Type"))
|
2016-09-12 17:59:45 +00:00
|
|
|
|
2016-07-17 21:42:09 +00:00
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
if *otype != "Page" {
|
|
|
|
return errors.New("Type != Page (Required).")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy inherited fields if missing.
|
|
|
|
inheritedFields := []PdfObjectName{"Resources", "MediaBox", "CropBox", "Rotate"}
|
2017-07-08 21:04:13 +00:00
|
|
|
parent, hasParent := pDict.Get("Parent").(*PdfIndirectObject)
|
|
|
|
common.Log.Trace("Page Parent: %T (%v)", pDict.Get("Parent"), hasParent)
|
2016-07-09 14:09:27 +00:00
|
|
|
for hasParent {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Page Parent: %T", parent)
|
2016-07-09 14:09:27 +00:00
|
|
|
parentDict, ok := parent.PdfObject.(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("invalid Parent object")
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
for _, field := range inheritedFields {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Field %s", field)
|
2017-07-08 21:04:13 +00:00
|
|
|
if pDict.Get(field) != nil {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("- page has already")
|
2016-07-09 14:09:27 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := parentDict.Get(field); obj != nil {
|
2016-07-09 14:09:27 +00:00
|
|
|
// Parent has the field. Inherit, pass to the new page.
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Inheriting field %s", field)
|
2017-07-08 21:04:13 +00:00
|
|
|
pDict.Set(field, obj)
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
}
|
2017-07-08 21:04:13 +00:00
|
|
|
parent, hasParent = parentDict.Get("Parent").(*PdfIndirectObject)
|
|
|
|
common.Log.Trace("Next parent: %T", parentDict.Get("Parent"))
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Traversal done")
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Update the dictionary.
|
|
|
|
// Reuses the input object, updating the fields.
|
2018-12-09 21:25:30 +02:00
|
|
|
pDict.Set("Parent", w.pages)
|
2016-12-13 19:40:33 +00:00
|
|
|
pageObj.PdfObject = pDict
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Add to Pages.
|
2018-12-09 21:25:30 +02:00
|
|
|
pagesDict, ok := w.pages.PdfObject.(*PdfObjectDictionary)
|
2016-07-17 21:42:09 +00:00
|
|
|
if !ok {
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("invalid Pages obj (not a dict)")
|
2016-07-17 21:42:09 +00:00
|
|
|
}
|
2017-07-08 21:04:13 +00:00
|
|
|
kids, ok := pagesDict.Get("Kids").(*PdfObjectArray)
|
2016-07-17 21:42:09 +00:00
|
|
|
if !ok {
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("invalid Pages Kids obj (not an array)")
|
2016-07-17 21:42:09 +00:00
|
|
|
}
|
2018-07-15 17:52:53 +00:00
|
|
|
kids.Append(pageObj)
|
2017-07-08 21:04:13 +00:00
|
|
|
pageCount, ok := pagesDict.Get("Count").(*PdfObjectInteger)
|
2016-07-17 21:42:09 +00:00
|
|
|
if !ok {
|
2018-12-08 19:16:52 +02:00
|
|
|
return errors.New("invalid Pages Count object (not an integer)")
|
2016-07-17 21:42:09 +00:00
|
|
|
}
|
|
|
|
// Update the count.
|
2016-07-09 14:09:27 +00:00
|
|
|
*pageCount = *pageCount + 1
|
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
w.addObject(pageObj)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Traverse the page and record all object references.
|
2018-12-09 21:25:30 +02:00
|
|
|
err := w.addObjects(pDict)
|
2016-07-09 14:09:27 +00:00
|
|
|
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())
|
|
|
|
|
2018-12-09 19:28:50 +02:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// AddOutlineTree adds outlines to a PDF file.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) AddOutlineTree(outlineTree *PdfOutlineTreeNode) {
|
|
|
|
w.outlineTree = outlineTree
|
2016-08-18 09:43:44 +00:00
|
|
|
}
|
|
|
|
|
2016-07-09 14:09:27 +00:00
|
|
|
// Look for a specific key. Returns a list of entries.
|
|
|
|
// What if something appears on many pages?
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) seekByName(obj PdfObject, followKeys []string, key string) ([]PdfObject, error) {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Seek by name.. %T", obj)
|
2018-12-09 19:28:50 +02:00
|
|
|
var list []PdfObject
|
2016-07-09 14:09:27 +00:00
|
|
|
if io, isIndirectObj := obj.(*PdfIndirectObject); isIndirectObj {
|
2018-12-09 21:25:30 +02:00
|
|
|
return w.seekByName(io.PdfObject, followKeys, key)
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if so, isStreamObj := obj.(*PdfObjectStream); isStreamObj {
|
2018-12-09 21:25:30 +02:00
|
|
|
return w.seekByName(so.PdfObjectDictionary, followKeys, key)
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if dict, isDict := obj.(*PdfObjectDictionary); isDict {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Dict")
|
2017-07-08 21:04:13 +00:00
|
|
|
for _, k := range dict.Keys() {
|
|
|
|
v := dict.Get(k)
|
2016-07-09 14:09:27 +00:00
|
|
|
if string(k) == key {
|
|
|
|
list = append(list, v)
|
|
|
|
}
|
|
|
|
for _, followKey := range followKeys {
|
|
|
|
if string(k) == followKey {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Follow key %s", followKey)
|
2018-12-09 21:25:30 +02:00
|
|
|
items, err := w.seekByName(v, followKeys, key)
|
2016-07-09 14:09:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return list, err
|
|
|
|
}
|
|
|
|
for _, item := range items {
|
|
|
|
list = append(list, item)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return list, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return list, nil
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// SetForms sets the Acroform for a PDF file.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) SetForms(form *PdfAcroForm) error {
|
|
|
|
w.acroForm = form
|
2016-09-07 17:56:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// writeObject writes out an indirect / stream object.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) writeObject(num int, obj PdfObject) {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Write obj #%d\n", num)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
if pobj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
|
2018-12-09 21:25:30 +02:00
|
|
|
w.crossReferenceMap[num] = crossReference{Type: 1, Offset: w.writePos, Generation: pobj.GenerationNumber}
|
2016-07-09 14:09:27 +00:00
|
|
|
outStr := fmt.Sprintf("%d 0 obj\n", num)
|
2018-12-11 04:37:00 +02:00
|
|
|
outStr += pobj.PdfObject.WriteString()
|
2016-07-09 14:09:27 +00:00
|
|
|
outStr += "\nendobj\n"
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString(outStr)
|
2016-07-09 14:09:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-12-09 21:37:27 +02:00
|
|
|
// TODO: Add a default encoder if Filter not specified?
|
2017-02-22 21:10:57 +00:00
|
|
|
// Still need to make sure is encrypted.
|
2016-07-09 14:09:27 +00:00
|
|
|
if pobj, isStream := obj.(*PdfObjectStream); isStream {
|
2018-12-09 21:25:30 +02:00
|
|
|
w.crossReferenceMap[num] = crossReference{Type: 1, Offset: w.writePos, Generation: pobj.GenerationNumber}
|
2016-07-09 14:09:27 +00:00
|
|
|
outStr := fmt.Sprintf("%d 0 obj\n", num)
|
2018-12-11 04:37:00 +02:00
|
|
|
outStr += pobj.PdfObjectDictionary.WriteString()
|
2016-07-09 14:09:27 +00:00
|
|
|
outStr += "\nstream\n"
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString(outStr)
|
|
|
|
w.writeBytes(pobj.Stream)
|
|
|
|
w.writeString("\nendstream\nendobj\n")
|
2016-07-09 14:09:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-29 17:22:53 +03:00
|
|
|
if ostreams, isObjStreams := obj.(*PdfObjectStreams); isObjStreams {
|
2018-12-09 21:25:30 +02:00
|
|
|
w.crossReferenceMap[num] = crossReference{Type: 1, Offset: w.writePos, Generation: ostreams.GenerationNumber}
|
2018-09-29 17:22:53 +03:00
|
|
|
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)
|
|
|
|
}
|
2018-12-11 04:37:00 +02:00
|
|
|
data := io.PdfObject.WriteString() + " "
|
2018-09-29 17:22:53 +03:00
|
|
|
objData = objData + data
|
|
|
|
offsets = append(offsets, fmt.Sprintf("%d %d", io.ObjectNumber, offset))
|
2018-12-09 21:25:30 +02:00
|
|
|
w.crossReferenceMap[int(io.ObjectNumber)] = crossReference{Type: 2, ObjectNumber: num, Index: index}
|
2018-09-29 17:22:53 +03:00
|
|
|
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))
|
2018-12-11 04:37:00 +02:00
|
|
|
outStr += dict.WriteString()
|
2018-09-29 17:22:53 +03:00
|
|
|
outStr += "\nstream\n"
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString(outStr)
|
|
|
|
w.writeBytes(data)
|
|
|
|
w.writeString("\nendstream\nendobj\n")
|
2018-09-29 17:22:53 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-12-11 04:37:00 +02:00
|
|
|
w.writer.WriteString(obj.WriteString())
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update all the object numbers prior to writing.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) updateObjectNumbers() {
|
2018-12-12 09:47:28 +00:00
|
|
|
offset := w.ObjNumOffset
|
2016-07-09 14:09:27 +00:00
|
|
|
// Update numbers
|
2018-12-09 21:25:30 +02:00
|
|
|
for idx, obj := range w.objects {
|
2018-10-03 01:27:38 +03:00
|
|
|
switch o := obj.(type) {
|
|
|
|
case *PdfIndirectObject:
|
2018-12-11 16:06:34 +03:00
|
|
|
o.ObjectNumber = int64(idx + 1 + offset)
|
2018-10-03 01:27:38 +03:00
|
|
|
o.GenerationNumber = 0
|
|
|
|
case *PdfObjectStream:
|
2018-12-11 16:06:34 +03:00
|
|
|
o.ObjectNumber = int64(idx + 1 + offset)
|
2018-10-03 01:27:38 +03:00
|
|
|
o.GenerationNumber = 0
|
|
|
|
case *PdfObjectStreams:
|
2018-12-11 16:06:34 +03:00
|
|
|
o.ObjectNumber = int64(idx + 1 + offset)
|
2018-10-03 01:27:38 +03:00
|
|
|
o.GenerationNumber = 0
|
2018-09-29 17:22:53 +03:00
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// EncryptOptions represents encryption options for an output PDF.
|
2016-07-09 16:40:39 +00:00
|
|
|
type EncryptOptions struct {
|
2018-10-04 04:33:32 +03:00
|
|
|
Permissions security.Permissions
|
2018-09-19 06:02:15 +03:00
|
|
|
Algorithm EncryptionAlgorithm
|
2016-07-09 16:40:39 +00:00
|
|
|
}
|
|
|
|
|
2018-09-19 06:02:15 +03: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
|
|
|
|
)
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// Encrypt encrypts the output file with a specified user/owner password.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptions) error {
|
2018-09-19 06:02:15 +03:00
|
|
|
algo := RC4_128bit
|
|
|
|
if options != nil {
|
|
|
|
algo = options.Algorithm
|
|
|
|
}
|
2018-10-04 04:33:32 +03:00
|
|
|
perm := security.PermOwner
|
2018-09-29 04:39:14 +03:00
|
|
|
if options != nil {
|
|
|
|
perm = options.Permissions
|
|
|
|
}
|
2018-09-19 06:02:15 +03:00
|
|
|
|
2018-10-04 05:35:00 +03:00
|
|
|
var cf crypt.Filter
|
2018-09-19 06:02:15 +03:00
|
|
|
switch algo {
|
|
|
|
case RC4_128bit:
|
2018-10-04 05:35:00 +03:00
|
|
|
cf = crypt.NewFilterV2(16)
|
2018-09-19 06:02:15 +03:00
|
|
|
case AES_128bit:
|
2018-10-04 05:35:00 +03:00
|
|
|
cf = crypt.NewFilterAESV2()
|
2018-09-19 06:02:15 +03:00
|
|
|
case AES_256bit:
|
2018-10-04 05:35:00 +03:00
|
|
|
cf = crypt.NewFilterAESV3()
|
2018-09-19 06:02:15 +03:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("unsupported algorithm: %v", options.Algorithm)
|
|
|
|
}
|
2018-09-29 04:39:14 +03:00
|
|
|
crypter, info, err := PdfCryptNewEncrypt(cf, userPass, ownerPass, perm)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
2018-12-09 21:25:30 +02:00
|
|
|
w.crypter = crypter
|
2018-09-29 04:39:14 +03:00
|
|
|
if info.Major != 0 {
|
2018-12-09 21:25:30 +02:00
|
|
|
w.SetVersion(info.Major, info.Minor)
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
2018-12-09 21:25:30 +02:00
|
|
|
w.encryptDict = info.Encrypt
|
2018-09-29 04:39:14 +03:00
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
w.ids = MakeArray(MakeHexString(info.ID0), MakeHexString(info.ID1))
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2018-09-19 06:02:15 +03:00
|
|
|
// Make an object to contain the encryption dictionary.
|
2018-09-29 04:39:14 +03:00
|
|
|
io := MakeIndirectObject(info.Encrypt)
|
2018-12-09 21:25:30 +02:00
|
|
|
w.encryptObj = io
|
|
|
|
w.addObject(io)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:56:59 +00:00
|
|
|
// Wrapper function to handle writing out string.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) writeString(s string) error {
|
|
|
|
n, err := w.writer.WriteString(s)
|
2018-07-17 23:56:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writePos += int64(n)
|
2018-07-17 23:56:59 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrapper function to handle writing out bytes.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) writeBytes(bb []byte) error {
|
|
|
|
n, err := w.writer.Write(bb)
|
2018-07-17 23:56:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writePos += int64(n)
|
2018-07-17 23:56:59 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write writes out the PDF.
|
2018-12-09 21:25:30 +02:00
|
|
|
func (w *PdfWriter) Write(writer io.Writer) error {
|
2017-02-28 12:16:46 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2016-08-18 09:43:44 +00:00
|
|
|
// Outlines.
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.outlineTree != nil {
|
|
|
|
common.Log.Trace("OutlineTree: %+v", w.outlineTree)
|
|
|
|
outlines := w.outlineTree.ToPdfObject()
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Outlines: %+v (%T, p:%p)", outlines, outlines, outlines)
|
2018-12-09 21:25:30 +02:00
|
|
|
w.catalog.Set("Outlines", outlines)
|
|
|
|
err := w.addObjects(outlines)
|
2016-08-18 09:43:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-04-19 12:05:20 +00:00
|
|
|
|
2016-08-18 15:55:20 +00:00
|
|
|
// Form fields.
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.acroForm != nil {
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Writing acro forms")
|
2018-12-09 21:25:30 +02:00
|
|
|
indObj := w.acroForm.ToPdfObject()
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("AcroForm: %+v", indObj)
|
2018-12-09 21:25:30 +02:00
|
|
|
w.catalog.Set("AcroForm", indObj)
|
|
|
|
err := w.addObjects(indObj)
|
2016-09-07 17:56:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2016-12-05 16:21:23 +00:00
|
|
|
// Check pending objects prior to write.
|
2018-12-09 21:25:30 +02:00
|
|
|
for pendingObj, pendingObjDict := range w.pendingObjects {
|
|
|
|
if !w.hasObject(pendingObj) {
|
2016-12-05 16:21:23 +00:00
|
|
|
common.Log.Debug("ERROR Pending object %+v %T (%p) never added for writing", pendingObj, pendingObj, pendingObj)
|
2017-07-08 21:04:13 +00:00
|
|
|
for _, key := range pendingObjDict.Keys() {
|
|
|
|
val := pendingObjDict.Get(key)
|
2016-12-06 01:18:44 +00:00
|
|
|
if val == pendingObj {
|
|
|
|
common.Log.Debug("Pending object found! and replaced with null")
|
2017-07-08 21:04:13 +00:00
|
|
|
pendingObjDict.Set(key, MakeNull())
|
2016-12-06 01:18:44 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2016-12-05 16:21:23 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-28 08:58:50 +00:00
|
|
|
// Set version in the catalog.
|
2018-12-09 21:25:30 +02:00
|
|
|
w.catalog.Set("Version", MakeName(fmt.Sprintf("%d.%d", w.majorVersion, w.minorVersion)))
|
2018-09-30 18:38:50 +00:00
|
|
|
|
|
|
|
// Make a copy of objects prior to optimizing as this can alter the objects.
|
2018-12-09 21:25:30 +02:00
|
|
|
w.copyObjects()
|
2018-09-29 17:22:53 +03:00
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.optimizer != nil {
|
2018-09-29 17:22:53 +03:00
|
|
|
var err error
|
2018-12-09 21:25:30 +02:00
|
|
|
w.objects, err = w.optimizer.Optimize(w.objects)
|
2018-09-29 17:22:53 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-12-05 16:21:23 +00:00
|
|
|
|
2018-12-12 09:47:28 +00:00
|
|
|
w.writePos = w.writeOffset
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writer = bufio.NewWriter(writer)
|
|
|
|
useCrossReferenceStream := w.majorVersion > 1 || (w.majorVersion == 1 && w.minorVersion > 4)
|
2018-12-11 16:06:34 +03:00
|
|
|
|
2018-09-29 17:22:53 +03:00
|
|
|
objectsInObjectStreams := make(map[PdfObject]bool)
|
|
|
|
if !useCrossReferenceStream {
|
2018-12-09 21:25:30 +02:00
|
|
|
for _, obj := range w.objects {
|
2018-09-29 17:22:53 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
if useCrossReferenceStream && w.majorVersion == 1 && w.minorVersion < 5 {
|
|
|
|
w.minorVersion = 5
|
2018-09-29 17:22:53 +03:00
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2018-12-12 09:47:28 +00:00
|
|
|
if w.appendMode {
|
|
|
|
w.writeString("\n")
|
2018-12-11 16:06:34 +03:00
|
|
|
} else {
|
2018-12-12 09:47:28 +00:00
|
|
|
w.writeString(fmt.Sprintf("%%PDF-%d.%d\n", w.majorVersion, w.minorVersion))
|
|
|
|
w.writeString("%âãÏÓ\n")
|
2018-12-11 16:06:34 +03:00
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
w.updateObjectNumbers()
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Write objects
|
2018-12-09 21:25:30 +02:00
|
|
|
common.Log.Trace("Writing %d obj", len(w.objects))
|
|
|
|
w.crossReferenceMap = make(map[int]crossReference)
|
|
|
|
w.crossReferenceMap[0] = crossReference{Type: 0, ObjectNumber: 0, Generation: 0xFFFF}
|
2018-12-12 09:47:28 +00:00
|
|
|
if w.appendToXrefs != nil {
|
|
|
|
for idx, xref := range w.appendToXrefs {
|
2018-12-11 16:06:34 +03:00
|
|
|
if idx == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if xref.XType == XrefTypeObjectStream {
|
|
|
|
cr := crossReference{Type: 2, ObjectNumber: xref.OsObjNumber, Index: xref.OsObjIndex}
|
2018-12-12 09:47:28 +00:00
|
|
|
w.crossReferenceMap[idx] = cr
|
2018-12-11 16:06:34 +03:00
|
|
|
}
|
|
|
|
if xref.XType == XrefTypeTableEntry {
|
|
|
|
cr := crossReference{Type: 1, ObjectNumber: xref.ObjectNumber, Offset: xref.Offset}
|
2018-12-12 09:47:28 +00:00
|
|
|
w.crossReferenceMap[idx] = cr
|
2018-12-11 16:06:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-12 09:47:28 +00:00
|
|
|
offset := w.ObjNumOffset
|
2018-12-09 21:25:30 +02:00
|
|
|
for idx, obj := range w.objects {
|
2018-09-29 17:22:53 +03:00
|
|
|
if skip := objectsInObjectStreams[obj]; skip {
|
|
|
|
continue
|
|
|
|
}
|
2017-02-28 12:16:46 +00:00
|
|
|
common.Log.Trace("Writing %d", idx)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2018-12-11 16:06:34 +03:00
|
|
|
objectNumber := int64(idx + 1 + offset)
|
2016-07-09 14:09:27 +00:00
|
|
|
// Encrypt prior to writing.
|
|
|
|
// Encrypt dictionary should not be encrypted.
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.crypter != nil && obj != w.encryptObj {
|
2018-12-12 09:47:28 +00:00
|
|
|
err := w.crypter.Encrypt(obj, int64(objectNumber), 0)
|
2016-07-09 14:09:27 +00:00
|
|
|
if err != nil {
|
2016-10-31 21:48:25 +00:00
|
|
|
common.Log.Debug("ERROR: Failed encrypting (%s)", err)
|
2016-07-09 14:09:27 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-12-12 09:47:28 +00:00
|
|
|
w.writeObject(int(objectNumber), obj)
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
xrefOffset := w.writePos
|
2018-12-11 16:06:34 +03:00
|
|
|
var maxIndex int
|
2018-12-12 09:47:28 +00:00
|
|
|
for idx := range w.crossReferenceMap {
|
2018-12-11 16:06:34 +03:00
|
|
|
if idx > maxIndex {
|
|
|
|
maxIndex = idx
|
|
|
|
}
|
|
|
|
}
|
2018-09-29 17:22:53 +03:00
|
|
|
if useCrossReferenceStream {
|
|
|
|
|
2018-12-11 16:06:34 +03:00
|
|
|
crossObjNumber := maxIndex + 1
|
2018-12-09 21:25:30 +02:00
|
|
|
w.crossReferenceMap[crossObjNumber] = crossReference{Type: 1, ObjectNumber: crossObjNumber, Offset: xrefOffset}
|
2018-09-29 17:22:53 +03:00
|
|
|
crossReferenceData := bytes.NewBuffer(nil)
|
|
|
|
|
2018-12-11 16:06:34 +03:00
|
|
|
for idx := 0; idx <= maxIndex+1; idx++ {
|
2018-12-09 21:25:30 +02:00
|
|
|
ref := w.crossReferenceMap[idx]
|
2018-09-29 17:22:53 +03:00
|
|
|
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))
|
2018-12-09 21:25:30 +02:00
|
|
|
crossReferenceStream.PdfObjectDictionary.Set("Info", w.infoObj)
|
|
|
|
crossReferenceStream.PdfObjectDictionary.Set("Root", w.root)
|
2018-09-29 17:22:53 +03:00
|
|
|
// If encrypted!
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.crypter != nil {
|
|
|
|
crossReferenceStream.Set("Encrypt", w.encryptObj)
|
|
|
|
crossReferenceStream.Set("ID", w.ids)
|
|
|
|
common.Log.Trace("Ids: %s", w.ids)
|
2018-09-29 17:22:53 +03:00
|
|
|
}
|
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeObject(int(crossReferenceStream.ObjectNumber), crossReferenceStream)
|
2018-09-29 17:22:53 +03:00
|
|
|
|
|
|
|
} else {
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString("xref\r\n")
|
|
|
|
outStr := fmt.Sprintf("%d %d\r\n", 0, len(w.crossReferenceMap))
|
|
|
|
w.writeString(outStr)
|
2018-12-11 16:06:34 +03:00
|
|
|
for idx := 0; idx <= maxIndex; idx++ {
|
2018-12-09 21:25:30 +02:00
|
|
|
ref := w.crossReferenceMap[idx]
|
2018-09-29 17:22:53 +03:00
|
|
|
switch ref.Type {
|
|
|
|
case 0:
|
|
|
|
outStr = fmt.Sprintf("%.10d %.5d f\r\n", 0, 65535)
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString(outStr)
|
2018-09-29 17:22:53 +03:00
|
|
|
case 1:
|
|
|
|
outStr = fmt.Sprintf("%.10d %.5d n\r\n", ref.Offset, 0)
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString(outStr)
|
2018-09-29 17:22:53 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate & write trailer
|
|
|
|
trailer := MakeDict()
|
2018-12-09 21:25:30 +02:00
|
|
|
trailer.Set("Info", w.infoObj)
|
|
|
|
trailer.Set("Root", w.root)
|
2018-12-12 09:47:28 +00:00
|
|
|
trailer.Set("Size", MakeInteger(int64(len(w.crossReferenceMap))))
|
2018-09-29 17:22:53 +03:00
|
|
|
// If encrypted!
|
2018-12-09 21:25:30 +02:00
|
|
|
if w.crypter != nil {
|
|
|
|
trailer.Set("Encrypt", w.encryptObj)
|
|
|
|
trailer.Set("ID", w.ids)
|
|
|
|
common.Log.Trace("Ids: %s", w.ids)
|
2018-09-29 17:22:53 +03:00
|
|
|
}
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString("trailer\n")
|
2018-12-11 04:37:00 +02:00
|
|
|
w.writeString(trailer.WriteString())
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString("\n")
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make offset reference.
|
2018-09-29 17:22:53 +03:00
|
|
|
outStr := fmt.Sprintf("startxref\n%d\n", xrefOffset)
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writeString(outStr)
|
|
|
|
w.writeString("%%EOF\n")
|
2018-07-17 23:56:59 +00:00
|
|
|
|
2018-12-09 21:25:30 +02:00
|
|
|
w.writer.Flush()
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|