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 }