github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/model/forms.go (about)

     1  /*
     2   * This file is subject to the terms and conditions defined in
     3   * file 'LICENSE.md', which is part of this source code package.
     4   */
     5  
     6  package model
     7  
     8  import (
     9  	"fmt"
    10  
    11  	"github.com/unidoc/unidoc/common"
    12  	. "github.com/unidoc/unidoc/pdf/core"
    13  )
    14  
    15  //
    16  // High level manipulation of forms (AcroForm).
    17  //
    18  type PdfAcroForm struct {
    19  	Fields          *[]*PdfField
    20  	NeedAppearances *PdfObjectBool
    21  	SigFlags        *PdfObjectInteger
    22  	CO              *PdfObjectArray
    23  	DR              *PdfPageResources
    24  	DA              *PdfObjectString
    25  	Q               *PdfObjectInteger
    26  	XFA             PdfObject
    27  
    28  	primitive *PdfIndirectObject
    29  }
    30  
    31  func NewPdfAcroForm() *PdfAcroForm {
    32  	acroForm := &PdfAcroForm{}
    33  
    34  	container := &PdfIndirectObject{}
    35  	container.PdfObject = MakeDict()
    36  
    37  	acroForm.primitive = container
    38  	return acroForm
    39  }
    40  
    41  // Used when loading forms from PDF files.
    42  func (r *PdfReader) newPdfAcroFormFromDict(d *PdfObjectDictionary) (*PdfAcroForm, error) {
    43  	acroForm := NewPdfAcroForm()
    44  
    45  	if obj := d.Get("Fields"); obj != nil {
    46  		obj, err := r.traceToObject(obj)
    47  		if err != nil {
    48  			return nil, err
    49  		}
    50  		fieldArray, ok := TraceToDirectObject(obj).(*PdfObjectArray)
    51  		if !ok {
    52  			return nil, fmt.Errorf("Fields not an array (%T)", obj)
    53  		}
    54  
    55  		fields := []*PdfField{}
    56  		for _, obj := range *fieldArray {
    57  			obj, err := r.traceToObject(obj)
    58  			if err != nil {
    59  				return nil, err
    60  			}
    61  			container, isIndirect := obj.(*PdfIndirectObject)
    62  			if !isIndirect {
    63  				if _, isNull := obj.(*PdfObjectNull); isNull {
    64  					common.Log.Trace("Skipping over null field")
    65  					continue
    66  				}
    67  				common.Log.Debug("Field not contained in indirect object %T", obj)
    68  				return nil, fmt.Errorf("Field not in an indirect object")
    69  			}
    70  			field, err := r.newPdfFieldFromIndirectObject(container, nil)
    71  			if err != nil {
    72  				return nil, err
    73  			}
    74  			common.Log.Trace("AcroForm Field: %+v", *field)
    75  			fields = append(fields, field)
    76  		}
    77  		acroForm.Fields = &fields
    78  	}
    79  
    80  	if obj := d.Get("NeedAppearances"); obj != nil {
    81  		val, ok := obj.(*PdfObjectBool)
    82  		if ok {
    83  			acroForm.NeedAppearances = val
    84  		} else {
    85  			common.Log.Debug("ERROR: NeedAppearances invalid (got %T)", obj)
    86  		}
    87  	}
    88  
    89  	if obj := d.Get("SigFlags"); obj != nil {
    90  		val, ok := obj.(*PdfObjectInteger)
    91  		if ok {
    92  			acroForm.SigFlags = val
    93  		} else {
    94  			common.Log.Debug("ERROR: SigFlags invalid (got %T)", obj)
    95  		}
    96  	}
    97  
    98  	if obj := d.Get("CO"); obj != nil {
    99  		obj = TraceToDirectObject(obj)
   100  		arr, ok := obj.(*PdfObjectArray)
   101  		if ok {
   102  			acroForm.CO = arr
   103  		} else {
   104  			common.Log.Debug("ERROR: CO invalid (got %T)", obj)
   105  		}
   106  	}
   107  
   108  	if obj := d.Get("DR"); obj != nil {
   109  		obj = TraceToDirectObject(obj)
   110  		if d, ok := obj.(*PdfObjectDictionary); ok {
   111  			resources, err := NewPdfPageResourcesFromDict(d)
   112  			if err != nil {
   113  				common.Log.Error("Invalid DR: %v", err)
   114  				return nil, err
   115  			}
   116  
   117  			acroForm.DR = resources
   118  		} else {
   119  			common.Log.Debug("ERROR: DR invalid (got %T)", obj)
   120  		}
   121  	}
   122  
   123  	if obj := d.Get("DA"); obj != nil {
   124  		str, ok := obj.(*PdfObjectString)
   125  		if ok {
   126  			acroForm.DA = str
   127  		} else {
   128  			common.Log.Debug("ERROR: DA invalid (got %T)", obj)
   129  		}
   130  	}
   131  
   132  	if obj := d.Get("Q"); obj != nil {
   133  		val, ok := obj.(*PdfObjectInteger)
   134  		if ok {
   135  			acroForm.Q = val
   136  		} else {
   137  			common.Log.Debug("ERROR: Q invalid (got %T)", obj)
   138  		}
   139  	}
   140  
   141  	if obj := d.Get("XFA"); obj != nil {
   142  		acroForm.XFA = obj
   143  	}
   144  
   145  	return acroForm, nil
   146  }
   147  
   148  func (this *PdfAcroForm) GetContainingPdfObject() PdfObject {
   149  	return this.primitive
   150  }
   151  
   152  func (this *PdfAcroForm) ToPdfObject() PdfObject {
   153  	container := this.primitive
   154  	dict := container.PdfObject.(*PdfObjectDictionary)
   155  
   156  	if this.Fields != nil {
   157  		arr := PdfObjectArray{}
   158  		for _, field := range *this.Fields {
   159  			arr = append(arr, field.ToPdfObject())
   160  		}
   161  		dict.Set("Fields", &arr)
   162  	}
   163  
   164  	if this.NeedAppearances != nil {
   165  		dict.Set("NeedAppearances", this.NeedAppearances)
   166  	}
   167  	if this.SigFlags != nil {
   168  		dict.Set("SigFlags", this.SigFlags)
   169  
   170  	}
   171  	if this.CO != nil {
   172  		dict.Set("CO", this.CO)
   173  	}
   174  	if this.DR != nil {
   175  		dict.Set("DR", this.DR.ToPdfObject())
   176  	}
   177  	if this.DA != nil {
   178  		dict.Set("DA", this.DA)
   179  	}
   180  	if this.Q != nil {
   181  		dict.Set("Q", this.Q)
   182  	}
   183  	if this.XFA != nil {
   184  		dict.Set("XFA", this.XFA)
   185  	}
   186  
   187  	return container
   188  }
   189  
   190  // PdfField represents a field of an interactive form.
   191  // Implements PdfModel interface.
   192  type PdfField struct {
   193  	FT     *PdfObjectName // field type
   194  	Parent *PdfField
   195  	// In a non-terminal field, the Kids array shall refer to field dictionaries that are immediate descendants of this field.
   196  	// In a terminal field, the Kids array ordinarily shall refer to one or more separate widget annotations that are associated
   197  	// with this field. However, if there is only one associated widget annotation, and its contents have been merged into the field
   198  	// dictionary, Kids shall be omitted.
   199  	KidsF []PdfModel // Kids can be array of other fields or widgets (PdfModel).
   200  	KidsA []*PdfAnnotation
   201  	T     PdfObject
   202  	TU    PdfObject
   203  	TM    PdfObject
   204  	Ff    PdfObject // field flag
   205  	V     PdfObject //value
   206  	DV    PdfObject
   207  	AA    PdfObject
   208  
   209  	// Variable Text:
   210  	DA PdfObject
   211  	Q  PdfObject
   212  	DS PdfObject
   213  	RV PdfObject
   214  
   215  	primitive *PdfIndirectObject
   216  }
   217  
   218  func NewPdfField() *PdfField {
   219  	field := &PdfField{}
   220  
   221  	container := &PdfIndirectObject{}
   222  	container.PdfObject = MakeDict()
   223  
   224  	field.primitive = container
   225  	return field
   226  }
   227  
   228  // Used when loading fields from PDF files.
   229  func (r *PdfReader) newPdfFieldFromIndirectObject(container *PdfIndirectObject, parent *PdfField) (*PdfField, error) {
   230  	d, isDict := container.PdfObject.(*PdfObjectDictionary)
   231  	if !isDict {
   232  		return nil, fmt.Errorf("Pdf Field indirect object not containing a dictionary")
   233  	}
   234  
   235  	field := NewPdfField()
   236  
   237  	// Field type (required in terminal fields).
   238  	// Can be /Btn /Tx /Ch /Sig
   239  	// Required for a terminal field (inheritable).
   240  	var err error
   241  	if obj := d.Get("FT"); obj != nil {
   242  		obj, err = r.traceToObject(obj)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  		name, ok := obj.(*PdfObjectName)
   247  		if !ok {
   248  			return nil, fmt.Errorf("Invalid type of FT field (%T)", obj)
   249  		}
   250  
   251  		field.FT = name
   252  	}
   253  
   254  	// Partial field name (Optional)
   255  	field.T = d.Get("T")
   256  	// Alternate description (Optional)
   257  	field.TU = d.Get("TU")
   258  	// Mapping name (Optional)
   259  	field.TM = d.Get("TM")
   260  	// Field flag. (Optional; inheritable)
   261  	field.Ff = d.Get("Ff")
   262  	// Value (Optional; inheritable) - Various types depending on the field type.
   263  	field.V = d.Get("V")
   264  	// Default value for reset (Optional; inheritable)
   265  	field.DV = d.Get("DV")
   266  	// Additional actions dictionary (Optional)
   267  	field.AA = d.Get("AA")
   268  
   269  	// Variable text:
   270  	field.DA = d.Get("DA")
   271  	field.Q = d.Get("Q")
   272  	field.DS = d.Get("DS")
   273  	field.RV = d.Get("RV")
   274  
   275  	// In a non-terminal field, the Kids array shall refer to field dictionaries that are immediate descendants of this field.
   276  	// In a terminal field, the Kids array ordinarily shall refer to one or more separate widget annotations that are associated
   277  	// with this field. However, if there is only one associated widget annotation, and its contents have been merged into the field
   278  	// dictionary, Kids shall be omitted.
   279  
   280  	// Set ourself?
   281  	if parent != nil {
   282  		field.Parent = parent
   283  	}
   284  
   285  	// Has a merged-in widget annotation?
   286  	if obj := d.Get("Subtype"); obj != nil {
   287  		obj, err = r.traceToObject(obj)
   288  		if err != nil {
   289  			return nil, err
   290  		}
   291  		common.Log.Trace("Merged in annotation (%T)", obj)
   292  		name, ok := obj.(*PdfObjectName)
   293  		if !ok {
   294  			return nil, fmt.Errorf("Invalid type of Subtype (%T)", obj)
   295  		}
   296  		if *name == "Widget" {
   297  			// Is a merged field / widget dict.
   298  
   299  			// Check if the annotation has already been loaded?
   300  			// Most likely referenced to by a page...  Could be in either direction.
   301  			// r.newPdfAnnotationFromIndirectObject acts as a caching mechanism.
   302  			annot, err := r.newPdfAnnotationFromIndirectObject(container)
   303  			if err != nil {
   304  				return nil, err
   305  			}
   306  			widget, ok := annot.GetContext().(*PdfAnnotationWidget)
   307  			if !ok {
   308  				return nil, fmt.Errorf("Invalid widget")
   309  			}
   310  
   311  			widget.Parent = field.GetContainingPdfObject()
   312  			field.KidsA = append(field.KidsA, annot)
   313  			return field, nil
   314  		}
   315  	}
   316  
   317  	if obj := d.Get("Kids"); obj != nil {
   318  		obj, err := r.traceToObject(obj)
   319  		if err != nil {
   320  			return nil, err
   321  		}
   322  		fieldArray, ok := TraceToDirectObject(obj).(*PdfObjectArray)
   323  		if !ok {
   324  			return nil, fmt.Errorf("Kids not an array (%T)", obj)
   325  		}
   326  
   327  		field.KidsF = []PdfModel{}
   328  		for _, obj := range *fieldArray {
   329  			obj, err := r.traceToObject(obj)
   330  			if err != nil {
   331  				return nil, err
   332  			}
   333  
   334  			container, isIndirect := obj.(*PdfIndirectObject)
   335  			if !isIndirect {
   336  				return nil, fmt.Errorf("Not an indirect object (form field)")
   337  			}
   338  
   339  			childField, err := r.newPdfFieldFromIndirectObject(container, field)
   340  			if err != nil {
   341  				return nil, err
   342  			}
   343  
   344  			field.KidsF = append(field.KidsF, childField)
   345  		}
   346  	}
   347  
   348  	return field, nil
   349  }
   350  
   351  func (this *PdfField) GetContainingPdfObject() PdfObject {
   352  	return this.primitive
   353  }
   354  
   355  // If Kids refer only to a single pdf widget annotation widget, then can merge it in.
   356  // Currently not merging it in.
   357  func (this *PdfField) ToPdfObject() PdfObject {
   358  	container := this.primitive
   359  	dict := container.PdfObject.(*PdfObjectDictionary)
   360  
   361  	if this.Parent != nil {
   362  		dict.Set("Parent", this.Parent.GetContainingPdfObject())
   363  	}
   364  
   365  	if this.KidsF != nil {
   366  		// Create an array of the kids (fields or widgets).
   367  		common.Log.Trace("KidsF: %+v", this.KidsF)
   368  		arr := PdfObjectArray{}
   369  		for _, child := range this.KidsF {
   370  			arr = append(arr, child.ToPdfObject())
   371  		}
   372  		dict.Set("Kids", &arr)
   373  	}
   374  	if this.KidsA != nil {
   375  		common.Log.Trace("KidsA: %+v", this.KidsA)
   376  		_, hasKids := dict.Get("Kids").(*PdfObjectArray)
   377  		if !hasKids {
   378  			dict.Set("Kids", &PdfObjectArray{})
   379  		}
   380  		arr := dict.Get("Kids").(*PdfObjectArray)
   381  		for _, child := range this.KidsA {
   382  			*arr = append(*arr, child.GetContext().ToPdfObject())
   383  		}
   384  	}
   385  
   386  	if this.FT != nil {
   387  		dict.Set("FT", this.FT)
   388  	}
   389  
   390  	if this.T != nil {
   391  		dict.Set("T", this.T)
   392  	}
   393  	if this.TU != nil {
   394  		dict.Set("TU", this.TU)
   395  	}
   396  	if this.TM != nil {
   397  		dict.Set("TM", this.TM)
   398  	}
   399  	if this.Ff != nil {
   400  		dict.Set("Ff", this.Ff)
   401  	}
   402  	if this.V != nil {
   403  		dict.Set("V", this.V)
   404  	}
   405  	if this.DV != nil {
   406  		dict.Set("DV", this.DV)
   407  	}
   408  	if this.AA != nil {
   409  		dict.Set("AA", this.AA)
   410  	}
   411  
   412  	// Variable text:
   413  	dict.SetIfNotNil("DA", this.DA)
   414  	dict.SetIfNotNil("Q", this.Q)
   415  	dict.SetIfNotNil("DS", this.DS)
   416  	dict.SetIfNotNil("RV", this.RV)
   417  
   418  	return container
   419  }