2016-08-24 09:13:08 +00:00
|
|
|
/*
|
|
|
|
* This file is subject to the terms and conditions defined in
|
|
|
|
* file 'LICENSE.md', which is part of this source code package.
|
|
|
|
*/
|
|
|
|
|
|
|
|
//
|
|
|
|
// Higher level manipulation of forms (AcroForm).
|
|
|
|
//
|
|
|
|
|
|
|
|
package pdf
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2016-09-05 09:57:16 +00:00
|
|
|
// Higher level object convertible to a PDF primitive.
|
2016-09-06 09:33:58 +00:00
|
|
|
type PdfObjectConvertible interface {
|
2016-09-07 17:56:45 +00:00
|
|
|
ToPdfObject(bool) PdfObject
|
2016-09-06 09:33:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Avoid global. Should go into the reader or builder.
|
|
|
|
var PdfObjectConvertibleCache map[PdfObjectConvertible]PdfObject = map[PdfObjectConvertible]PdfObject{}
|
2016-09-05 09:57:16 +00:00
|
|
|
|
2016-08-24 09:13:08 +00:00
|
|
|
type PdfAcroForm struct {
|
2016-08-25 08:01:15 +00:00
|
|
|
Fields *[]*PdfField
|
2016-08-24 09:13:08 +00:00
|
|
|
NeedAppearances PdfObject
|
|
|
|
SigFlags PdfObject
|
|
|
|
CO PdfObject
|
|
|
|
DR PdfObject
|
|
|
|
DA PdfObject
|
|
|
|
Q PdfObject
|
|
|
|
XFA PdfObject
|
|
|
|
}
|
|
|
|
|
2016-08-31 11:18:00 +00:00
|
|
|
// Used when loading forms from PDF files.
|
2016-08-25 08:01:15 +00:00
|
|
|
func (r *PdfReader) newPdfAcroFormFromDict(d PdfObjectDictionary) (*PdfAcroForm, error) {
|
|
|
|
acroForm := PdfAcroForm{}
|
|
|
|
|
|
|
|
if obj, has := d["Fields"]; has {
|
|
|
|
obj, err := r.traceToObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fieldArray, ok := TraceToDirectObject(obj).(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Fields not an array (%T)", obj)
|
|
|
|
}
|
|
|
|
|
|
|
|
fields := []*PdfField{}
|
2016-09-05 09:57:16 +00:00
|
|
|
for _, obj := range *fieldArray {
|
2016-08-25 08:01:15 +00:00
|
|
|
obj, err := r.traceToObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fDict, ok := TraceToDirectObject(obj).(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Invalid Fields entry: %T", obj)
|
|
|
|
}
|
2016-09-05 09:57:16 +00:00
|
|
|
field, err := r.newPdfFieldFromDict(*fDict, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-08-25 08:01:15 +00:00
|
|
|
fields = append(fields, field)
|
|
|
|
}
|
|
|
|
acroForm.Fields = &fields
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj, has := d["NeedAppearances"]; has {
|
|
|
|
acroForm.NeedAppearances = obj
|
|
|
|
}
|
|
|
|
if obj, has := d["SigFlags"]; has {
|
|
|
|
acroForm.SigFlags = obj
|
|
|
|
}
|
|
|
|
if obj, has := d["CO"]; has {
|
|
|
|
acroForm.CO = obj
|
|
|
|
}
|
|
|
|
if obj, has := d["DR"]; has {
|
|
|
|
acroForm.DR = obj
|
|
|
|
}
|
|
|
|
if obj, has := d["DA"]; has {
|
|
|
|
acroForm.DA = obj
|
|
|
|
}
|
|
|
|
if obj, has := d["Q"]; has {
|
|
|
|
acroForm.Q = obj
|
|
|
|
}
|
|
|
|
if obj, has := d["XFA"]; has {
|
|
|
|
acroForm.XFA = obj
|
|
|
|
}
|
|
|
|
|
|
|
|
return &acroForm, nil
|
|
|
|
}
|
|
|
|
|
2016-09-05 09:57:16 +00:00
|
|
|
func (this *PdfAcroForm) ToPdfObject(updateIfExists bool) PdfObject {
|
|
|
|
var container PdfIndirectObject
|
|
|
|
|
2016-09-06 09:33:58 +00:00
|
|
|
if cachedObj, isCached := PdfObjectConvertibleCache[this]; isCached {
|
2016-09-05 09:57:16 +00:00
|
|
|
if !updateIfExists {
|
|
|
|
return cachedObj
|
|
|
|
}
|
|
|
|
obj := cachedObj.(*PdfIndirectObject)
|
|
|
|
container = *obj
|
|
|
|
}
|
|
|
|
|
|
|
|
container = PdfIndirectObject{}
|
2016-08-31 11:18:00 +00:00
|
|
|
dict := PdfObjectDictionary{}
|
2016-09-05 09:57:16 +00:00
|
|
|
container.PdfObject = &dict
|
2016-08-25 08:01:15 +00:00
|
|
|
|
2016-08-31 11:18:00 +00:00
|
|
|
if this.Fields != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
arr := PdfObjectArray{}
|
|
|
|
for _, field := range *this.Fields {
|
|
|
|
arr = append(arr, field.ToPdfObject(false))
|
|
|
|
}
|
|
|
|
dict["Fields"] = &arr
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if this.NeedAppearances != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["NeedAppearances"] = this.NeedAppearances
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
if this.SigFlags != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["SigFlags"] = this.SigFlags
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
if this.CO != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["CO"] = this.CO
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
if this.DR != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["DR"] = this.DR
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
if this.DA != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["DA"] = this.DA
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
if this.Q != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["Q"] = this.Q
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
if this.XFA != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["XFA"] = this.XFA
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
|
2016-09-06 09:33:58 +00:00
|
|
|
PdfObjectConvertibleCache[this] = &container
|
2016-09-05 09:57:16 +00:00
|
|
|
return &container
|
|
|
|
}
|
|
|
|
|
|
|
|
type PdfField struct {
|
|
|
|
FT *PdfObjectName // field type
|
|
|
|
Parent *PdfField
|
|
|
|
// In a non-terminal field, the Kids array shall refer to field dictionaries that are immediate descendants of this field.
|
|
|
|
// In a terminal field, the Kids array ordinarily shall refer to one or more separate widget annotations that are associated
|
|
|
|
// with this field. However, if there is only one associated widget annotation, and its contents have been merged into the field
|
|
|
|
// dictionary, Kids shall be omitted.
|
2016-09-07 17:56:45 +00:00
|
|
|
KidsF []PdfObjectConvertible // Kids can be array of other fields or widgets (PdfObjectConvertible)
|
|
|
|
KidsA []PdfAnnotation
|
|
|
|
T PdfObject
|
|
|
|
TU PdfObject
|
|
|
|
TM PdfObject
|
|
|
|
Ff PdfObject // field flag
|
|
|
|
V PdfObject //value
|
|
|
|
DV PdfObject
|
|
|
|
AA PdfObject
|
2016-09-05 09:57:16 +00:00
|
|
|
// Widget annotation can be merged in.
|
|
|
|
// PdfAnnotationWidget
|
2016-08-25 08:01:15 +00:00
|
|
|
}
|
|
|
|
|
2016-08-31 11:18:00 +00:00
|
|
|
// Used when loading fields from PDF files.
|
2016-09-05 09:57:16 +00:00
|
|
|
func (r *PdfReader) newPdfFieldFromDict(d PdfObjectDictionary, parent *PdfField) (*PdfField, error) {
|
2016-08-25 08:01:15 +00:00
|
|
|
field := PdfField{}
|
|
|
|
|
|
|
|
// Field type (required in terminal fields).
|
|
|
|
// Can be /Btn /Tx /Ch /Sig
|
|
|
|
// Required for a terminal field (inheritable).
|
2016-09-05 09:57:16 +00:00
|
|
|
var err error
|
2016-08-25 08:01:15 +00:00
|
|
|
if obj, has := d["FT"]; has {
|
|
|
|
obj, err = r.traceToObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
name, ok := obj.(*PdfObjectName)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Invalid type of FT field (%T)", obj)
|
|
|
|
}
|
|
|
|
|
2016-09-05 09:57:16 +00:00
|
|
|
field.FT = name
|
2016-08-25 08:01:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Partial field name (Optional)
|
|
|
|
if obj, has := d["T"]; has {
|
|
|
|
field.T = obj
|
|
|
|
}
|
|
|
|
// Alternate description (Optional)
|
|
|
|
if obj, has := d["TU"]; has {
|
|
|
|
field.TU = obj
|
|
|
|
}
|
|
|
|
// Mapping name (Optional)
|
|
|
|
if obj, has := d["TM"]; has {
|
|
|
|
field.TM = obj
|
|
|
|
}
|
|
|
|
// Field flag. (Optional; inheritable)
|
|
|
|
if obj, has := d["Ff"]; has {
|
|
|
|
field.Ff = obj
|
|
|
|
}
|
|
|
|
// Value (Optional; inheritable) - Various types depending on the field type.
|
|
|
|
if obj, has := d["V"]; has {
|
|
|
|
field.V = obj
|
|
|
|
}
|
|
|
|
// Default value for reset (Optional; inheritable)
|
|
|
|
if obj, has := d["DV"]; has {
|
|
|
|
field.DV = obj
|
|
|
|
}
|
|
|
|
// Additional actions dictionary (Optional)
|
|
|
|
if obj, has := d["AA"]; has {
|
|
|
|
field.AA = obj
|
|
|
|
}
|
|
|
|
|
2016-08-24 09:13:08 +00:00
|
|
|
// In a non-terminal field, the Kids array shall refer to field dictionaries that are immediate descendants of this field.
|
|
|
|
// In a terminal field, the Kids array ordinarily shall refer to one or more separate widget annotations that are associated
|
|
|
|
// with this field. However, if there is only one associated widget annotation, and its contents have been merged into the field
|
|
|
|
// dictionary, Kids shall be omitted.
|
2016-09-05 09:57:16 +00:00
|
|
|
|
|
|
|
// Set ourself?
|
|
|
|
if parent != nil {
|
|
|
|
field.Parent = parent
|
|
|
|
}
|
|
|
|
|
|
|
|
// Has a merged-in widget annotation?
|
|
|
|
if obj, has := d["Subtype"]; has {
|
|
|
|
obj, err = r.traceToObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-09-07 17:56:45 +00:00
|
|
|
fmt.Printf("Merged in annotation (%T)\n", obj)
|
2016-09-05 09:57:16 +00:00
|
|
|
name, ok := obj.(*PdfObjectName)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Invalid type of Subtype (%T)", obj)
|
|
|
|
}
|
|
|
|
if *name == "Widget" {
|
|
|
|
// Is a merged field / widget dict.
|
2016-09-07 17:56:45 +00:00
|
|
|
widget, err := newPdfAnnotationWidgetFromDict(&d)
|
2016-09-06 09:33:58 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-09-07 17:56:45 +00:00
|
|
|
fmt.Printf("Widget: %+v\n", *widget)
|
2016-09-06 09:33:58 +00:00
|
|
|
|
|
|
|
widget.Parent = field.ToPdfObject(false)
|
2016-09-07 17:56:45 +00:00
|
|
|
field.KidsA = append(field.KidsA, widget.PdfAnnotation)
|
|
|
|
fmt.Printf("Field: %+v\n", field)
|
2016-09-05 09:57:16 +00:00
|
|
|
return &field, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj, has := d["Kids"]; has {
|
|
|
|
obj, err := r.traceToObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fieldArray, ok := TraceToDirectObject(obj).(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Fields not an array (%T)", obj)
|
|
|
|
}
|
|
|
|
|
2016-09-07 17:56:45 +00:00
|
|
|
field.KidsF = []PdfObjectConvertible{}
|
2016-09-05 09:57:16 +00:00
|
|
|
for _, obj := range *fieldArray {
|
|
|
|
obj, err := r.traceToObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fDict, ok := TraceToDirectObject(obj).(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Invalid Fields entry: %T", obj)
|
|
|
|
}
|
|
|
|
|
|
|
|
childField, err := r.newPdfFieldFromDict(*fDict, &field)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-09-07 17:56:45 +00:00
|
|
|
field.KidsF = append(field.KidsF, childField)
|
2016-09-05 09:57:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &field, nil
|
2016-08-25 08:01:15 +00:00
|
|
|
}
|
2016-08-24 09:13:08 +00:00
|
|
|
|
2016-09-05 09:57:16 +00:00
|
|
|
// If Kids refer only to a single pdf widget annotation widget, then can merge it in.
|
|
|
|
// Currently not merging it in.
|
|
|
|
func (this *PdfField) ToPdfObject(updateIfExists bool) PdfObject {
|
|
|
|
var container PdfIndirectObject
|
|
|
|
|
2016-09-06 09:33:58 +00:00
|
|
|
if cachedObj, isCached := PdfObjectConvertibleCache[this]; isCached {
|
2016-09-05 09:57:16 +00:00
|
|
|
if !updateIfExists {
|
|
|
|
return cachedObj
|
|
|
|
}
|
|
|
|
obj := cachedObj.(*PdfIndirectObject)
|
|
|
|
container = *obj
|
|
|
|
}
|
|
|
|
|
|
|
|
container = PdfIndirectObject{}
|
2016-08-31 11:18:00 +00:00
|
|
|
dict := PdfObjectDictionary{}
|
2016-09-05 09:57:16 +00:00
|
|
|
container.PdfObject = &dict
|
2016-08-25 08:01:15 +00:00
|
|
|
|
2016-08-31 11:18:00 +00:00
|
|
|
if this.Parent != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["Parent"] = this.Parent.ToPdfObject(false)
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
2016-09-05 09:57:16 +00:00
|
|
|
|
2016-09-07 17:56:45 +00:00
|
|
|
if this.KidsF != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
// Create an array of the kids (fields or widgets).
|
2016-09-07 17:56:45 +00:00
|
|
|
fmt.Printf("KidsF: %+v\n", this.KidsF)
|
2016-09-05 09:57:16 +00:00
|
|
|
arr := PdfObjectArray{}
|
2016-09-07 17:56:45 +00:00
|
|
|
for _, child := range this.KidsF {
|
2016-09-05 09:57:16 +00:00
|
|
|
arr = append(arr, child.ToPdfObject(false))
|
|
|
|
}
|
|
|
|
dict["Kids"] = &arr
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
2016-09-07 17:56:45 +00:00
|
|
|
if this.KidsA != nil {
|
|
|
|
fmt.Printf("KidsA: %+v\n", this.KidsA)
|
|
|
|
_, hasKids := dict["Kids"].(*PdfObjectArray)
|
|
|
|
if !hasKids {
|
|
|
|
dict["Kids"] = &PdfObjectArray{}
|
|
|
|
}
|
|
|
|
arr := dict["Kids"].(*PdfObjectArray)
|
|
|
|
for _, child := range this.KidsA {
|
|
|
|
*arr = append(*arr, child.ToPdfObject())
|
|
|
|
}
|
|
|
|
}
|
2016-08-25 08:01:15 +00:00
|
|
|
|
2016-08-31 11:18:00 +00:00
|
|
|
if this.T != nil {
|
|
|
|
dict["T"] = this.T
|
|
|
|
}
|
|
|
|
if this.TU != nil {
|
2016-09-05 09:57:16 +00:00
|
|
|
dict["TU"] = this.TU
|
2016-08-31 11:18:00 +00:00
|
|
|
}
|
|
|
|
if this.TM != nil {
|
|
|
|
dict["TM"] = this.TM
|
|
|
|
}
|
|
|
|
if this.Ff != nil {
|
|
|
|
dict["Ff"] = this.Ff
|
|
|
|
}
|
|
|
|
if this.V != nil {
|
|
|
|
dict["V"] = this.V
|
|
|
|
}
|
|
|
|
if this.DV != nil {
|
|
|
|
dict["DV"] = this.DV
|
|
|
|
}
|
|
|
|
if this.AA != nil {
|
|
|
|
dict["AA"] = this.AA
|
|
|
|
}
|
2016-09-05 09:57:16 +00:00
|
|
|
|
2016-09-06 09:33:58 +00:00
|
|
|
PdfObjectConvertibleCache[this] = &container
|
2016-09-05 09:57:16 +00:00
|
|
|
return &container
|
2016-08-24 09:13:08 +00:00
|
|
|
}
|