fix wrong git ignore
This commit is contained in:
412
internal/pdf/model/forms.go
Normal file
412
internal/pdf/model/forms.go
Normal file
@@ -0,0 +1,412 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.tecamino.com/paadi/pdfmerge/internal/pdf/common"
|
||||
"gitea.tecamino.com/paadi/pdfmerge/internal/pdf/core"
|
||||
)
|
||||
|
||||
// High level manipulation of forms (AcroForm).
|
||||
type PdfAcroForm struct {
|
||||
Fields *[]*PdfField
|
||||
NeedAppearances *core.PdfObjectBool
|
||||
SigFlags *core.PdfObjectInteger
|
||||
CO *core.PdfObjectArray
|
||||
DR *PdfPageResources
|
||||
DA *core.PdfObjectString
|
||||
Q *core.PdfObjectInteger
|
||||
XFA core.PdfObject
|
||||
|
||||
primitive *core.PdfIndirectObject
|
||||
}
|
||||
|
||||
func NewPdfAcroForm() *PdfAcroForm {
|
||||
acroForm := &PdfAcroForm{}
|
||||
|
||||
container := &core.PdfIndirectObject{}
|
||||
container.PdfObject = core.MakeDict()
|
||||
|
||||
acroForm.primitive = container
|
||||
return acroForm
|
||||
}
|
||||
|
||||
// Used when loading forms from PDF files.
|
||||
func (r *PdfReader) newPdfAcroFormFromDict(d *core.PdfObjectDictionary) (*PdfAcroForm, error) {
|
||||
acroForm := NewPdfAcroForm()
|
||||
|
||||
if obj := d.Get("Fields"); obj != nil {
|
||||
obj, err := r.traceToObject(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fieldArray, ok := core.TraceToDirectObject(obj).(*core.PdfObjectArray)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("fields not an array (%T)", obj)
|
||||
}
|
||||
|
||||
fields := []*PdfField{}
|
||||
for _, obj := range *fieldArray {
|
||||
obj, err := r.traceToObject(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container, isIndirect := obj.(*core.PdfIndirectObject)
|
||||
if !isIndirect {
|
||||
if _, isNull := obj.(*core.PdfObjectNull); isNull {
|
||||
common.Log.Trace("Skipping over null field")
|
||||
continue
|
||||
}
|
||||
common.Log.Debug("Field not contained in indirect object %T", obj)
|
||||
return nil, fmt.Errorf("field not in an indirect object")
|
||||
}
|
||||
field, err := r.newPdfFieldFromIndirectObject(container, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.Log.Trace("AcroForm Field: %+v", *field)
|
||||
fields = append(fields, field)
|
||||
}
|
||||
acroForm.Fields = &fields
|
||||
}
|
||||
|
||||
if obj := d.Get("NeedAppearances"); obj != nil {
|
||||
val, ok := obj.(*core.PdfObjectBool)
|
||||
if ok {
|
||||
acroForm.NeedAppearances = val
|
||||
} else {
|
||||
common.Log.Debug("error: NeedAppearances invalid (got %T)", obj)
|
||||
}
|
||||
}
|
||||
|
||||
if obj := d.Get("SigFlags"); obj != nil {
|
||||
val, ok := obj.(*core.PdfObjectInteger)
|
||||
if ok {
|
||||
acroForm.SigFlags = val
|
||||
} else {
|
||||
common.Log.Debug("error: SigFlags invalid (got %T)", obj)
|
||||
}
|
||||
}
|
||||
|
||||
if obj := d.Get("CO"); obj != nil {
|
||||
obj = core.TraceToDirectObject(obj)
|
||||
arr, ok := obj.(*core.PdfObjectArray)
|
||||
if ok {
|
||||
acroForm.CO = arr
|
||||
} else {
|
||||
common.Log.Debug("error: CO invalid (got %T)", obj)
|
||||
}
|
||||
}
|
||||
|
||||
if obj := d.Get("DR"); obj != nil {
|
||||
obj = core.TraceToDirectObject(obj)
|
||||
if d, ok := obj.(*core.PdfObjectDictionary); ok {
|
||||
resources, err := NewPdfPageResourcesFromDict(d)
|
||||
if err != nil {
|
||||
common.Log.Error("invalid DR: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acroForm.DR = resources
|
||||
} else {
|
||||
common.Log.Debug("error: DR invalid (got %T)", obj)
|
||||
}
|
||||
}
|
||||
|
||||
if obj := d.Get("DA"); obj != nil {
|
||||
str, ok := obj.(*core.PdfObjectString)
|
||||
if ok {
|
||||
acroForm.DA = str
|
||||
} else {
|
||||
common.Log.Debug("error: DA invalid (got %T)", obj)
|
||||
}
|
||||
}
|
||||
|
||||
if obj := d.Get("Q"); obj != nil {
|
||||
val, ok := obj.(*core.PdfObjectInteger)
|
||||
if ok {
|
||||
acroForm.Q = val
|
||||
} else {
|
||||
common.Log.Debug("error: Q invalid (got %T)", obj)
|
||||
}
|
||||
}
|
||||
|
||||
if obj := d.Get("XFA"); obj != nil {
|
||||
acroForm.XFA = obj
|
||||
}
|
||||
|
||||
return acroForm, nil
|
||||
}
|
||||
|
||||
func (af *PdfAcroForm) GetContainingPdfObject() core.PdfObject {
|
||||
return af.primitive
|
||||
}
|
||||
|
||||
func (af *PdfAcroForm) ToPdfObject() core.PdfObject {
|
||||
container := af.primitive
|
||||
dict := container.PdfObject.(*core.PdfObjectDictionary)
|
||||
|
||||
if af.Fields != nil {
|
||||
arr := core.PdfObjectArray{}
|
||||
for _, field := range *af.Fields {
|
||||
arr = append(arr, field.ToPdfObject())
|
||||
}
|
||||
dict.Set("Fields", &arr)
|
||||
}
|
||||
|
||||
if af.NeedAppearances != nil {
|
||||
dict.Set("NeedAppearances", af.NeedAppearances)
|
||||
}
|
||||
if af.SigFlags != nil {
|
||||
dict.Set("SigFlags", af.SigFlags)
|
||||
|
||||
}
|
||||
if af.CO != nil {
|
||||
dict.Set("CO", af.CO)
|
||||
}
|
||||
if af.DR != nil {
|
||||
dict.Set("DR", af.DR.ToPdfObject())
|
||||
}
|
||||
if af.DA != nil {
|
||||
dict.Set("DA", af.DA)
|
||||
}
|
||||
if af.Q != nil {
|
||||
dict.Set("Q", af.Q)
|
||||
}
|
||||
if af.XFA != nil {
|
||||
dict.Set("XFA", af.XFA)
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
// PdfField represents a field of an interactive form.
|
||||
// Implements PdfModel interface.
|
||||
type PdfField struct {
|
||||
FT *core.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.
|
||||
KidsF []PdfModel // Kids can be array of other fields or widgets (PdfModel).
|
||||
KidsA []*PdfAnnotation
|
||||
T core.PdfObject
|
||||
TU core.PdfObject
|
||||
TM core.PdfObject
|
||||
Ff core.PdfObject // field flag
|
||||
V core.PdfObject //value
|
||||
DV core.PdfObject
|
||||
AA core.PdfObject
|
||||
|
||||
// Variable Text:
|
||||
DA core.PdfObject
|
||||
Q core.PdfObject
|
||||
DS core.PdfObject
|
||||
RV core.PdfObject
|
||||
|
||||
primitive *core.PdfIndirectObject
|
||||
}
|
||||
|
||||
func NewPdfField() *PdfField {
|
||||
field := &PdfField{}
|
||||
|
||||
container := &core.PdfIndirectObject{}
|
||||
container.PdfObject = core.MakeDict()
|
||||
|
||||
field.primitive = container
|
||||
return field
|
||||
}
|
||||
|
||||
// Used when loading fields from PDF files.
|
||||
func (r *PdfReader) newPdfFieldFromIndirectObject(container *core.PdfIndirectObject, parent *PdfField) (*PdfField, error) {
|
||||
d, isDict := container.PdfObject.(*core.PdfObjectDictionary)
|
||||
if !isDict {
|
||||
return nil, fmt.Errorf("pdf Field indirect object not containing a dictionary")
|
||||
}
|
||||
|
||||
field := NewPdfField()
|
||||
|
||||
// Field type (required in terminal fields).
|
||||
// Can be /Btn /Tx /Ch /Sig
|
||||
// Required for a terminal field (inheritable).
|
||||
var err error
|
||||
if obj := d.Get("FT"); obj != nil {
|
||||
obj, err = r.traceToObject(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name, ok := obj.(*core.PdfObjectName)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid type of FT field (%T)", obj)
|
||||
}
|
||||
|
||||
field.FT = name
|
||||
}
|
||||
|
||||
// Partial field name (Optional)
|
||||
field.T = d.Get("T")
|
||||
// Alternate description (Optional)
|
||||
field.TU = d.Get("TU")
|
||||
// Mapping name (Optional)
|
||||
field.TM = d.Get("TM")
|
||||
// Field flag. (Optional; inheritable)
|
||||
field.Ff = d.Get("Ff")
|
||||
// Value (Optional; inheritable) - Various types depending on the field type.
|
||||
field.V = d.Get("V")
|
||||
// Default value for reset (Optional; inheritable)
|
||||
field.DV = d.Get("DV")
|
||||
// Additional actions dictionary (Optional)
|
||||
field.AA = d.Get("AA")
|
||||
|
||||
// Variable text:
|
||||
field.DA = d.Get("DA")
|
||||
field.Q = d.Get("Q")
|
||||
field.DS = d.Get("DS")
|
||||
field.RV = d.Get("RV")
|
||||
|
||||
// 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.
|
||||
|
||||
// Set ourself?
|
||||
if parent != nil {
|
||||
field.Parent = parent
|
||||
}
|
||||
|
||||
// Has a merged-in widget annotation?
|
||||
if obj := d.Get("Subtype"); obj != nil {
|
||||
obj, err = r.traceToObject(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common.Log.Trace("Merged in annotation (%T)", obj)
|
||||
name, ok := obj.(*core.PdfObjectName)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid type of Subtype (%T)", obj)
|
||||
}
|
||||
if *name == "Widget" {
|
||||
// Is a merged field / widget dict.
|
||||
|
||||
// Check if the annotation has already been loaded?
|
||||
// Most likely referenced to by a page... Could be in either direction.
|
||||
// r.newPdfAnnotationFromIndirectObject acts as a caching mechanism.
|
||||
annot, err := r.newPdfAnnotationFromIndirectObject(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
widget, ok := annot.GetContext().(*PdfAnnotationWidget)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid widget")
|
||||
}
|
||||
|
||||
widget.Parent = field.GetContainingPdfObject()
|
||||
field.KidsA = append(field.KidsA, annot)
|
||||
return field, nil
|
||||
}
|
||||
}
|
||||
|
||||
if obj := d.Get("Kids"); obj != nil {
|
||||
obj, err := r.traceToObject(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fieldArray, ok := core.TraceToDirectObject(obj).(*core.PdfObjectArray)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("kids not an array (%T)", obj)
|
||||
}
|
||||
|
||||
field.KidsF = []PdfModel{}
|
||||
for _, obj := range *fieldArray {
|
||||
obj, err := r.traceToObject(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container, isIndirect := obj.(*core.PdfIndirectObject)
|
||||
if !isIndirect {
|
||||
return nil, fmt.Errorf("not an indirect object (form field)")
|
||||
}
|
||||
|
||||
childField, err := r.newPdfFieldFromIndirectObject(container, field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
field.KidsF = append(field.KidsF, childField)
|
||||
}
|
||||
}
|
||||
|
||||
return field, nil
|
||||
}
|
||||
|
||||
func (pf *PdfField) GetContainingPdfObject() core.PdfObject {
|
||||
return pf.primitive
|
||||
}
|
||||
|
||||
// If Kids refer only to a single pdf widget annotation widget, then can merge it in.
|
||||
// Currently not merging it in.
|
||||
func (pf *PdfField) ToPdfObject() core.PdfObject {
|
||||
container := pf.primitive
|
||||
dict := container.PdfObject.(*core.PdfObjectDictionary)
|
||||
|
||||
if pf.Parent != nil {
|
||||
dict.Set("Parent", pf.Parent.GetContainingPdfObject())
|
||||
}
|
||||
|
||||
if pf.KidsF != nil {
|
||||
// Create an array of the kids (fields or widgets).
|
||||
common.Log.Trace("KidsF: %+v", pf.KidsF)
|
||||
arr := core.PdfObjectArray{}
|
||||
for _, child := range pf.KidsF {
|
||||
arr = append(arr, child.ToPdfObject())
|
||||
}
|
||||
dict.Set("Kids", &arr)
|
||||
}
|
||||
if pf.KidsA != nil {
|
||||
common.Log.Trace("KidsA: %+v", pf.KidsA)
|
||||
_, hasKids := dict.Get("Kids").(*core.PdfObjectArray)
|
||||
if !hasKids {
|
||||
dict.Set("Kids", &core.PdfObjectArray{})
|
||||
}
|
||||
arr := dict.Get("Kids").(*core.PdfObjectArray)
|
||||
for _, child := range pf.KidsA {
|
||||
*arr = append(*arr, child.GetContext().ToPdfObject())
|
||||
}
|
||||
}
|
||||
|
||||
if pf.FT != nil {
|
||||
dict.Set("FT", pf.FT)
|
||||
}
|
||||
|
||||
if pf.T != nil {
|
||||
dict.Set("T", pf.T)
|
||||
}
|
||||
if pf.TU != nil {
|
||||
dict.Set("TU", pf.TU)
|
||||
}
|
||||
if pf.TM != nil {
|
||||
dict.Set("TM", pf.TM)
|
||||
}
|
||||
if pf.Ff != nil {
|
||||
dict.Set("Ff", pf.Ff)
|
||||
}
|
||||
if pf.V != nil {
|
||||
dict.Set("V", pf.V)
|
||||
}
|
||||
if pf.DV != nil {
|
||||
dict.Set("DV", pf.DV)
|
||||
}
|
||||
if pf.AA != nil {
|
||||
dict.Set("AA", pf.AA)
|
||||
}
|
||||
|
||||
// Variable text:
|
||||
dict.SetIfNotNil("DA", pf.DA)
|
||||
dict.SetIfNotNil("Q", pf.Q)
|
||||
dict.SetIfNotNil("DS", pf.DS)
|
||||
dict.SetIfNotNil("RV", pf.RV)
|
||||
|
||||
return container
|
||||
}
|
||||
Reference in New Issue
Block a user