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"
|
|
|
|
"crypto/md5"
|
|
|
|
"crypto/rand"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
2016-07-17 19:59:17 +00:00
|
|
|
"github.com/unidoc/unidoc/common"
|
2016-07-09 14:09:27 +00:00
|
|
|
"github.com/unidoc/unidoc/license"
|
2016-09-08 17:53:45 +00:00
|
|
|
. "github.com/unidoc/unidoc/pdf/core"
|
2016-07-09 14:09:27 +00:00
|
|
|
)
|
|
|
|
|
2016-08-02 16:31:37 +00:00
|
|
|
var pdfProducer = ""
|
|
|
|
var pdfCreator = ""
|
|
|
|
|
|
|
|
func getPdfProducer() string {
|
|
|
|
if len(pdfProducer) > 0 {
|
|
|
|
return pdfProducer
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return default.
|
|
|
|
licenseKey := license.GetLicenseKey()
|
|
|
|
return fmt.Sprintf("UniDoc Library version %s (%s) - http://unidoc.io", getUniDocVersion(), licenseKey.TypeToString())
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetPdfProducer(producer string) {
|
|
|
|
licenseKey := license.GetLicenseKey()
|
|
|
|
commercial := licenseKey.Type == license.LicenseTypeCommercial
|
|
|
|
if !commercial {
|
|
|
|
// Only commercial users can modify the producer.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
pdfProducer = producer
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPdfCreator() string {
|
|
|
|
if len(pdfCreator) > 0 {
|
|
|
|
return pdfCreator
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return default.
|
|
|
|
return "UniDoc - http://unidoc.io"
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetPdfCreator(creator string) {
|
|
|
|
pdfCreator = creator
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
outlines []*PdfIndirectObject
|
|
|
|
outlineTree *PdfOutlineTreeNode
|
|
|
|
catalog *PdfObjectDictionary
|
|
|
|
fields []PdfObject
|
|
|
|
infoObj *PdfIndirectObject
|
2016-07-09 14:09:27 +00:00
|
|
|
// Encryption
|
|
|
|
crypter *PdfCrypt
|
|
|
|
encryptDict *PdfObjectDictionary
|
|
|
|
encryptObj *PdfIndirectObject
|
|
|
|
ids *PdfObjectArray
|
2016-09-07 17:56:45 +00:00
|
|
|
// Forms.
|
|
|
|
acroForm *PdfAcroForm
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewPdfWriter() PdfWriter {
|
|
|
|
w := PdfWriter{}
|
|
|
|
|
|
|
|
w.objectsMap = map[PdfObject]bool{}
|
|
|
|
w.objects = []PdfObject{}
|
|
|
|
|
|
|
|
// Creation info.
|
|
|
|
infoDict := PdfObjectDictionary{}
|
2016-08-02 16:31:37 +00:00
|
|
|
infoDict[PdfObjectName("Producer")] = MakeString(getPdfProducer())
|
|
|
|
infoDict[PdfObjectName("Creator")] = MakeString(getPdfCreator())
|
2016-07-09 14:09:27 +00:00
|
|
|
infoObj := PdfIndirectObject{}
|
|
|
|
infoObj.PdfObject = &infoDict
|
|
|
|
w.infoObj = &infoObj
|
|
|
|
w.addObject(&infoObj)
|
|
|
|
|
|
|
|
// Root catalog.
|
|
|
|
catalog := PdfIndirectObject{}
|
|
|
|
catalogDict := PdfObjectDictionary{}
|
2016-07-25 14:06:37 +00:00
|
|
|
catalogDict[PdfObjectName("Type")] = MakeName("Catalog")
|
|
|
|
catalogDict[PdfObjectName("Version")] = MakeName("1.3")
|
2016-07-09 14:09:27 +00:00
|
|
|
catalog.PdfObject = &catalogDict
|
|
|
|
|
|
|
|
w.root = &catalog
|
|
|
|
w.addObject(&catalog)
|
|
|
|
|
|
|
|
// Pages.
|
|
|
|
pages := PdfIndirectObject{}
|
|
|
|
pagedict := PdfObjectDictionary{}
|
2016-07-25 14:06:37 +00:00
|
|
|
pagedict[PdfObjectName("Type")] = MakeName("Pages")
|
2016-07-09 14:09:27 +00:00
|
|
|
kids := PdfObjectArray{}
|
|
|
|
pagedict[PdfObjectName("Kids")] = &kids
|
2016-07-25 14:06:37 +00:00
|
|
|
pagedict[PdfObjectName("Count")] = MakeInteger(0)
|
2016-07-09 14:09:27 +00:00
|
|
|
pages.PdfObject = &pagedict
|
|
|
|
|
|
|
|
w.pages = &pages
|
|
|
|
w.addObject(&pages)
|
|
|
|
|
|
|
|
catalogDict[PdfObjectName("Pages")] = &pages
|
|
|
|
w.catalog = &catalogDict
|
|
|
|
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Info("Catalog %s", catalog)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Adding objects!")
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
if io, isIndirectObj := obj.(*PdfIndirectObject); isIndirectObj {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Indirect")
|
2016-12-05 00:46:27 +00:00
|
|
|
common.Log.Debug("- %s (%p)", obj, io)
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("- %s", io.PdfObject)
|
2016-07-09 14:09:27 +00:00
|
|
|
if this.addObject(io) {
|
|
|
|
err := this.addObjects(io.PdfObject)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if so, isStreamObj := obj.(*PdfObjectStream); isStreamObj {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Stream")
|
|
|
|
common.Log.Debug("- %s", obj)
|
2016-07-09 14:09:27 +00:00
|
|
|
if this.addObject(so) {
|
|
|
|
err := this.addObjects(so.PdfObjectDictionary)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if dict, isDict := obj.(*PdfObjectDictionary); isDict {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Dict")
|
|
|
|
common.Log.Debug("- %s", obj)
|
2016-07-09 14:09:27 +00:00
|
|
|
for k, v := range *dict {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Key %s", k)
|
2016-07-09 14:09:27 +00:00
|
|
|
if k != "Parent" {
|
|
|
|
err := this.addObjects(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-08-16 16:22:01 +00:00
|
|
|
} else {
|
2016-09-08 17:53:45 +00:00
|
|
|
if hasObj := this.hasObject(v); !hasObj {
|
|
|
|
fmt.Printf("Parent obj is missing!! %T %p %v\n", v, v, v)
|
|
|
|
}
|
2016-08-16 16:22:01 +00:00
|
|
|
// How to handle the parent? Make sure it is present?
|
|
|
|
if parentObj, parentIsRef := (*dict)["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)")
|
2016-08-16 16:22:01 +00:00
|
|
|
return fmt.Errorf("Parent is a reference object - Cannot be in writer (needs to be resolved) - %s", parentObj)
|
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if arr, isArray := obj.(*PdfObjectArray); isArray {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Array")
|
|
|
|
common.Log.Debug("- %s", obj)
|
2016-07-09 14:09:27 +00:00
|
|
|
for _, v := range *arr {
|
|
|
|
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!")
|
2016-07-09 14:09:27 +00:00
|
|
|
return errors.New("Reference not allowed")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a page to the PDF file. The new page should be an indirect
|
|
|
|
// object.
|
|
|
|
func (this *PdfWriter) AddPage(pageObj PdfObject) error {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("==========")
|
2016-09-12 17:59:45 +00:00
|
|
|
common.Log.Debug("Appending to page list %T", pageObj)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
page, ok := pageObj.(*PdfIndirectObject)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Page should be an indirect object")
|
|
|
|
}
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("%s", page)
|
|
|
|
common.Log.Debug("%s", page.PdfObject)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2016-07-17 21:42:09 +00:00
|
|
|
pDict, ok := page.PdfObject.(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Page object should be a dictionary")
|
|
|
|
}
|
|
|
|
|
|
|
|
otype, ok := (*pDict)["Type"].(*PdfObjectName)
|
|
|
|
if !ok {
|
2016-09-12 17:59:45 +00:00
|
|
|
return fmt.Errorf("Page should have a Type key with a value of type name (%T)", (*pDict)["Type"])
|
|
|
|
|
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"}
|
|
|
|
parent, hasParent := (*pDict)["Parent"].(*PdfIndirectObject)
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Page Parent: %T (%v)", (*pDict)["Parent"], hasParent)
|
2016-07-09 14:09:27 +00:00
|
|
|
for hasParent {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Page Parent: %T", parent)
|
2016-07-09 14:09:27 +00:00
|
|
|
parentDict, ok := parent.PdfObject.(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Invalid Parent object")
|
|
|
|
}
|
|
|
|
for _, field := range inheritedFields {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Field %s", field)
|
2016-07-09 14:09:27 +00:00
|
|
|
if _, hasAlready := (*pDict)[field]; hasAlready {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("- page has already")
|
2016-07-09 14:09:27 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj, hasField := (*parentDict)[field]; hasField {
|
|
|
|
// Parent has the field. Inherit, pass to the new page.
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Inheriting field %s", field)
|
2016-07-09 14:09:27 +00:00
|
|
|
(*pDict)[field] = obj
|
|
|
|
}
|
|
|
|
}
|
|
|
|
parent, hasParent = (*parentDict)["Parent"].(*PdfIndirectObject)
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Next parent: %T", (*parentDict)["Parent"])
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Traversal done")
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Update the dictionary.
|
|
|
|
// Reuses the input object, updating the fields.
|
|
|
|
(*pDict)["Parent"] = this.pages
|
|
|
|
page.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)["Kids"].(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Invalid Pages Kids obj (not an array)")
|
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
*kids = append(*kids, page)
|
2016-07-17 21:42:09 +00:00
|
|
|
pageCount, ok := (*pagesDict)["Count"].(*PdfObjectInteger)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Invalid Pages Count object (not an integer)")
|
|
|
|
}
|
|
|
|
// Update the count.
|
2016-07-09 14:09:27 +00:00
|
|
|
*pageCount = *pageCount + 1
|
|
|
|
|
|
|
|
this.addObject(page)
|
|
|
|
|
|
|
|
// Traverse the page and record all object references.
|
|
|
|
err := this.addObjects(pDict)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-18 09:43:44 +00:00
|
|
|
// Add outlines to a PDF file.
|
|
|
|
func (this *PdfWriter) AddOutlineTree(outlineTree *PdfOutlineTreeNode) {
|
|
|
|
this.outlineTree = outlineTree
|
|
|
|
}
|
|
|
|
|
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?
|
|
|
|
func (this *PdfWriter) seekByName(obj PdfObject, followKeys []string, key string) ([]PdfObject, error) {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Seek by name.. %T", obj)
|
2016-07-09 14:09:27 +00:00
|
|
|
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 {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Dict")
|
2016-07-09 14:09:27 +00:00
|
|
|
for k, v := range *dict {
|
|
|
|
if string(k) == key {
|
|
|
|
list = append(list, v)
|
|
|
|
}
|
|
|
|
for _, followKey := range followKeys {
|
|
|
|
if string(k) == followKey {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Follow key %s", followKey)
|
2016-07-09 14:09:27 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add Acroforms to a PDF file.
|
|
|
|
func (this *PdfWriter) AddForms(forms *PdfObjectDictionary) error {
|
|
|
|
// Traverse the forms object...
|
|
|
|
// Keep a list of stuff?
|
|
|
|
|
|
|
|
// Forms dictionary should have:
|
|
|
|
// Fields array.
|
|
|
|
if forms == nil {
|
|
|
|
return errors.New("forms == nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
// For now, support only regular forms with fields
|
|
|
|
var fieldsArray *PdfObjectArray
|
|
|
|
if fields, hasFields := (*forms)["Fields"]; hasFields {
|
|
|
|
if arr, isArray := fields.(*PdfObjectArray); isArray {
|
|
|
|
fieldsArray = arr
|
|
|
|
} else if ind, isInd := fields.(*PdfIndirectObject); isInd {
|
|
|
|
if arr, isArray := ind.PdfObject.(*PdfObjectArray); isArray {
|
|
|
|
fieldsArray = arr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if fieldsArray == nil {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Writer - no fields to be added to forms")
|
2016-07-09 14:09:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the fields.
|
|
|
|
for _, field := range *fieldsArray {
|
|
|
|
fieldObj, ok := field.(*PdfIndirectObject)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Field not pointing indirect object")
|
|
|
|
}
|
|
|
|
|
|
|
|
followKeys := []string{"Fields", "Kids"}
|
|
|
|
list, err := this.seekByName(fieldObj, followKeys, "P")
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Done seeking!")
|
2016-07-09 14:09:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("List of P objects %d", len(list))
|
2016-07-09 14:09:27 +00:00
|
|
|
if len(list) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
includeField := false
|
|
|
|
for _, p := range list {
|
|
|
|
if po, ok := p.(*PdfIndirectObject); ok {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("P entry is an indirect object (page)")
|
2016-07-09 14:09:27 +00:00
|
|
|
if this.hasObject(po) {
|
|
|
|
includeField = true
|
|
|
|
} else {
|
|
|
|
return errors.New("P pointing outside of write pages")
|
|
|
|
}
|
|
|
|
} else {
|
2016-10-31 21:48:25 +00:00
|
|
|
common.Log.Debug("ERROR: P entry not an indirect object (%T)", p)
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This won't work. There can be many sub objects.
|
|
|
|
// Need to specifically go and check the page object!
|
|
|
|
// P or the appearance dictionary.
|
|
|
|
if includeField {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Add the field! (%T)", field)
|
2016-07-09 14:09:27 +00:00
|
|
|
// Add if nothing referenced outside of the writer.
|
|
|
|
// Probably need to add some objects first...
|
|
|
|
this.addObject(field)
|
|
|
|
this.fields = append(this.fields, field)
|
|
|
|
} else {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Field not relevant!")
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-07 17:56:45 +00:00
|
|
|
// Add Acroforms to a PDF file.
|
|
|
|
func (this *PdfWriter) AddForms2(form *PdfAcroForm) error {
|
|
|
|
//form.ToPdfObject(true)
|
|
|
|
this.acroForm = form
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-07-09 14:09:27 +00:00
|
|
|
// Write out an indirect / stream object.
|
|
|
|
func (this *PdfWriter) writeObject(num int, obj PdfObject) {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Write obj #%d\n", num)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
if pobj, isIndirect := obj.(*PdfIndirectObject); isIndirect {
|
|
|
|
outStr := fmt.Sprintf("%d 0 obj\n", num)
|
|
|
|
outStr += pobj.PdfObject.DefaultWriteString()
|
|
|
|
outStr += "\nendobj\n"
|
|
|
|
this.writer.WriteString(outStr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if pobj, isStream := obj.(*PdfObjectStream); isStream {
|
|
|
|
outStr := fmt.Sprintf("%d 0 obj\n", num)
|
|
|
|
outStr += pobj.PdfObjectDictionary.DefaultWriteString()
|
|
|
|
outStr += "\nstream\n"
|
|
|
|
this.writer.WriteString(outStr)
|
|
|
|
this.writer.Write(pobj.Stream)
|
|
|
|
this.writer.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 {
|
|
|
|
if io, isIndirect := obj.(*PdfIndirectObject); isIndirect {
|
|
|
|
io.ObjectNumber = int64(idx + 1)
|
|
|
|
io.GenerationNumber = 0
|
|
|
|
}
|
|
|
|
if so, isStream := obj.(*PdfObjectStream); isStream {
|
|
|
|
so.ObjectNumber = int64(idx + 1)
|
|
|
|
so.GenerationNumber = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-09 16:40:39 +00:00
|
|
|
type EncryptOptions struct {
|
|
|
|
Permissions AccessPermissions
|
|
|
|
}
|
|
|
|
|
2016-07-09 14:09:27 +00:00
|
|
|
// Encrypt 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 {
|
2016-07-09 14:09:27 +00:00
|
|
|
crypter := PdfCrypt{}
|
|
|
|
this.crypter = &crypter
|
|
|
|
|
2016-09-09 15:02:52 +00:00
|
|
|
crypter.EncryptedObjects = map[PdfObject]bool{}
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2016-09-09 15:02:52 +00:00
|
|
|
crypter.CryptFilters = CryptFilters{}
|
|
|
|
crypter.CryptFilters["Default"] = CryptFilter{Cfm: "V2", Length: 128}
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Set
|
|
|
|
crypter.P = -1
|
|
|
|
crypter.V = 2
|
|
|
|
crypter.R = 3
|
2016-09-09 15:02:52 +00:00
|
|
|
crypter.Length = 128
|
|
|
|
crypter.EncryptMetadata = true
|
2016-07-09 16:40:39 +00:00
|
|
|
if options != nil {
|
|
|
|
crypter.P = int(options.Permissions.GetP())
|
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Prepare the ID object for the trailer.
|
|
|
|
hashcode := md5.Sum([]byte(time.Now().Format(time.RFC850)))
|
|
|
|
id0 := PdfObjectString(hashcode[:])
|
|
|
|
b := make([]byte, 100)
|
|
|
|
rand.Read(b)
|
|
|
|
hashcode = md5.Sum(b)
|
|
|
|
id1 := PdfObjectString(hashcode[:])
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Random b: % x", b)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
this.ids = &PdfObjectArray{&id0, &id1}
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Gen Id 0: % x", id0)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
2016-09-09 15:02:52 +00:00
|
|
|
crypter.Id0 = string(id0)
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Make the O and U objects.
|
2016-09-09 15:02:52 +00:00
|
|
|
O, err := crypter.Alg3(userPass, ownerPass)
|
2016-07-09 14:09:27 +00:00
|
|
|
if err != nil {
|
2016-10-31 21:48:25 +00:00
|
|
|
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
|
2016-07-09 14:09:27 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
crypter.O = []byte(O)
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("gen O: % x", O)
|
2016-09-09 15:02:52 +00:00
|
|
|
U, key, err := crypter.Alg5(userPass)
|
2016-07-09 14:09:27 +00:00
|
|
|
if err != nil {
|
2016-10-31 21:48:25 +00:00
|
|
|
common.Log.Debug("ERROR: Error generating O for encryption (%s)", err)
|
2016-07-09 14:09:27 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("gen U: % x", U)
|
2016-07-09 14:09:27 +00:00
|
|
|
crypter.U = []byte(U)
|
2016-09-09 15:02:52 +00:00
|
|
|
crypter.EncryptionKey = key
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
// Generate the encryption dictionary.
|
|
|
|
encDict := &PdfObjectDictionary{}
|
2016-07-25 14:06:37 +00:00
|
|
|
(*encDict)[PdfObjectName("Filter")] = MakeName("Standard")
|
|
|
|
(*encDict)[PdfObjectName("P")] = MakeInteger(int64(crypter.P))
|
|
|
|
(*encDict)[PdfObjectName("V")] = MakeInteger(int64(crypter.V))
|
|
|
|
(*encDict)[PdfObjectName("R")] = MakeInteger(int64(crypter.R))
|
2016-09-09 15:02:52 +00:00
|
|
|
(*encDict)[PdfObjectName("Length")] = MakeInteger(int64(crypter.Length))
|
2016-07-09 14:09:27 +00:00
|
|
|
(*encDict)[PdfObjectName("O")] = &O
|
|
|
|
(*encDict)[PdfObjectName("U")] = &U
|
|
|
|
this.encryptDict = encDict
|
|
|
|
|
|
|
|
// Make an object to contain it.
|
|
|
|
io := &PdfIndirectObject{}
|
|
|
|
io.PdfObject = encDict
|
|
|
|
this.encryptObj = io
|
|
|
|
this.addObject(io)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the pdf out.
|
|
|
|
func (this *PdfWriter) Write(ws io.WriteSeeker) error {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Write()")
|
2016-08-18 09:43:44 +00:00
|
|
|
// Outlines.
|
|
|
|
if this.outlineTree != nil {
|
2016-12-05 00:46:27 +00:00
|
|
|
common.Log.Debug("OutlineTree: %+v", this.outlineTree)
|
2016-09-11 23:17:38 +00:00
|
|
|
outlines := this.outlineTree.ToPdfObject()
|
2016-12-05 00:46:27 +00:00
|
|
|
common.Log.Debug("Outlines: %+v (%T, p:%p)", outlines, outlines, outlines)
|
2016-08-18 09:43:44 +00:00
|
|
|
(*this.catalog)["Outlines"] = outlines
|
|
|
|
err := this.addObjects(outlines)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-08-18 15:55:20 +00:00
|
|
|
// Form fields.
|
2016-09-08 17:53:45 +00:00
|
|
|
/*
|
|
|
|
if len(this.fields) > 0 {
|
|
|
|
forms := PdfIndirectObject{}
|
|
|
|
formsDict := PdfObjectDictionary{}
|
|
|
|
forms.PdfObject = &formsDict
|
|
|
|
fieldsArray := PdfObjectArray{}
|
|
|
|
for _, field := range this.fields {
|
|
|
|
fieldsArray = append(fieldsArray, field)
|
|
|
|
}
|
|
|
|
formsDict[PdfObjectName("Fields")] = &fieldsArray
|
|
|
|
(*this.catalog)[PdfObjectName("AcroForm")] = &forms
|
|
|
|
err := this.addObjects(&forms)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}*/
|
2016-09-07 17:56:45 +00:00
|
|
|
// Acroform.
|
|
|
|
if this.acroForm != nil {
|
2016-09-11 23:17:38 +00:00
|
|
|
indObj := this.acroForm.ToPdfObject()
|
2016-09-08 17:53:45 +00:00
|
|
|
(*this.catalog)[PdfObjectName("AcroForm")] = indObj
|
2016-09-07 17:56:45 +00:00
|
|
|
err := this.addObjects(indObj)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-07-09 14:09:27 +00:00
|
|
|
|
|
|
|
w := bufio.NewWriter(ws)
|
|
|
|
this.writer = w
|
|
|
|
|
|
|
|
w.WriteString("%PDF-1.3\n")
|
|
|
|
w.WriteString("%âãÏÓ\n")
|
|
|
|
w.Flush()
|
|
|
|
|
|
|
|
this.updateObjectNumbers()
|
|
|
|
|
|
|
|
offsets := []int64{}
|
|
|
|
|
|
|
|
// Write objects
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Writing %d obj", len(this.objects))
|
2016-07-09 14:09:27 +00:00
|
|
|
for idx, obj := range this.objects {
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Writing %d", idx)
|
2016-07-09 14:09:27 +00:00
|
|
|
this.writer.Flush()
|
|
|
|
offset, _ := ws.Seek(0, os.SEEK_CUR)
|
|
|
|
offsets = append(offsets, offset)
|
|
|
|
|
|
|
|
// 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)
|
2016-07-09 14:09:27 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
this.writeObject(idx+1, obj)
|
|
|
|
}
|
|
|
|
w.Flush()
|
|
|
|
|
|
|
|
xrefOffset, _ := ws.Seek(0, os.SEEK_CUR)
|
|
|
|
// Write xref table.
|
|
|
|
this.writer.WriteString("xref\r\n")
|
|
|
|
outStr := fmt.Sprintf("%d %d\r\n", 0, len(this.objects)+1)
|
|
|
|
this.writer.WriteString(outStr)
|
|
|
|
outStr = fmt.Sprintf("%.10d %.5d f\r\n", 0, 65535)
|
|
|
|
this.writer.WriteString(outStr)
|
|
|
|
for _, offset := range offsets {
|
|
|
|
outStr = fmt.Sprintf("%.10d %.5d n\r\n", offset, 0)
|
|
|
|
this.writer.WriteString(outStr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate & write trailer
|
|
|
|
trailer := PdfObjectDictionary{}
|
|
|
|
trailer["Info"] = this.infoObj
|
|
|
|
trailer["Root"] = this.root
|
2016-07-25 14:06:37 +00:00
|
|
|
trailer["Size"] = MakeInteger(int64(len(this.objects) + 1))
|
2016-07-09 14:09:27 +00:00
|
|
|
// If encrypted!
|
|
|
|
if this.crypter != nil {
|
|
|
|
trailer["Encrypt"] = this.encryptObj
|
|
|
|
trailer[PdfObjectName("ID")] = this.ids
|
2016-07-17 19:59:17 +00:00
|
|
|
common.Log.Debug("Ids: %s", this.ids)
|
2016-07-09 14:09:27 +00:00
|
|
|
}
|
|
|
|
this.writer.WriteString("trailer\n")
|
|
|
|
this.writer.WriteString(trailer.DefaultWriteString())
|
|
|
|
this.writer.WriteString("\n")
|
|
|
|
|
|
|
|
// Make offset reference.
|
|
|
|
outStr = fmt.Sprintf("startxref\n%d\n", xrefOffset)
|
|
|
|
this.writer.WriteString(outStr)
|
|
|
|
this.writer.WriteString("%%EOF\n")
|
|
|
|
w.Flush()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|