Skip to content

Commit

Permalink
adding documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
pubpub-zz committed Feb 28, 2024
1 parent f23f01e commit 9060ec5
Showing 1 changed file with 30 additions and 4 deletions.
34 changes: 30 additions & 4 deletions docs/user/forms.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,28 @@ Viewer to recompute the field's rendering, and may trigger a "save changes"
dialog for users who open the generated PDF.

## A note about form fields and annotations

The PDF form stores form fields as annotations with the subtype "\Widget". This means that the following two blocks of code will give fairly similar results:

PDF forms have a dual-nature approach about the fields:
* within the root object, an `/AcroForm` structure exists.
Inside it you could find (optional):
- some global elements (Fonts, Ressources,...)
- some global flags (like `/NeedAppearances` (set/cleared with `auto_regenerate` parameter in `update_form_field_values()`) that indicates if the reading program should re-render the visual fields upon document launch)
- `/XFA` that houses a form in XDP format (very specific XML that describes the form rendered by some viewers) ; the `/XFA` form overrides the page content.
- `/Fields` that houses array of indirect references that references TopMost(roots) _Field_ Objects
* within the page `/Annots`, you will spot `/Widget` annotations that defines the visual renderings.

To flesh out this overview:
* the core specific properties of a fields are :
- `/FT` : Field Type (Button, Text, Choice, Signatures)
- `/T` : Partial Field Name (see PDF Reference for more details)
- `/V` : Field Value
- `/DV` : Default Field Value (used when reseting a form for exemple)
* in order to streamline readability, _Field_ Objects and _Widget_ Objects can be fused housing all properties.
* Field can be organised hierarchically, meaning one field can be placed under another. in such instances, the `/Parent` will stock an IndirectObject providing Bottom-Up links and `/Childs` is an array carrying IndirectObjects for Top-Down navigation ; _Widget_ Objects are still required for visual rendering ; to call upon them, use *full qualified field name* (with all the individual names of the parent objects are seperated by `.`)
For instance 2 (visual) fields both called _city_ but attached below _sender_ and _receiver_ ; the data full names will be _sender.city_ and _receiver.city_
* When a field is repeated on multiple pages, the Field Object will have many _Widget_ Objects in `/Childs`. These objects are pure _widgets_, containing no _field_ specific data
* if Fields stores only hidden values, No _Widget_ are required.

In _pypdf_ fields are extracted from the `/Fields` array
```python
from pypdf import PdfReader

Expand All @@ -59,7 +78,7 @@ from pypdf.constants import AnnotationDictionaryAttributes

reader = PdfReader("form.pdf")
fields = []
for page in reader.pages:
for page in reader.pagesP:
for annot in page.annotations:
annot = annot.get_object()
if annot[AnnotationDictionaryAttributes.Subtype] == "/Widget":
Expand All @@ -69,3 +88,10 @@ for page in reader.pages:
However, while similar, there are some very important differences between the two above blocks of code. Most importantly, the first block will return a list of Field objects, where as the second will return more generic dictionary-like objects. The objects lists will *mostly* reference the same object in the underlying PDF, meaning you'll find that `obj_taken_fom_first_list.indirect_reference == obj_taken_from _second_list.indirect_reference`. Field objects are generally more ergonomic, as the exposed data can be access via clearly named properties. However, the more generic dictionary-like objects will contain data that the Field object does not expose, such as the Rect (the widget's position on the page). So, which to use will depend on your use case.

However, it's also important to note that the two lists do not *always* refer to the same underlying PDF objects. For example, if the form contains radio buttons, you will find that `reader.get_fields()` will get the parent object (the group of radio buttons) whereas `page.annotations` will return all the child objects (the individual radio buttons).


__Caution:
Remember that fields are not stored in pages: If you use `add_page()` the field structure is not copied.
It is recommended to use `.append() with the proper parameters`__

In case of missing _field_ objects in `/Fields`, `writer.reattach_fields()` will parse page(s) annotations and will reattach them. This fix can not guess intermediate fields and will not report fields using the same _name_

0 comments on commit 9060ec5

Please sign in to comment.