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 }