0
# Forms and Annotations
1
2
Interactive PDF elements including form fields, annotations, and user input handling with comprehensive field type support. These capabilities enable creation and manipulation of interactive PDF documents.
3
4
## Capabilities
5
6
### AcroForm Class
7
8
The AcroForm class provides comprehensive PDF form management including field creation, modification, and appearance generation.
9
10
```python { .api }
11
class AcroForm:
12
"""
13
PDF form (AcroForm) manager for interactive form handling.
14
15
Provides access to form fields, appearance generation, and
16
form-level operations for PDF documents.
17
"""
18
19
@property
20
def exists(self) -> bool:
21
"""
22
Whether the PDF contains an interactive form.
23
24
Returns:
25
bool: True if the PDF has an AcroForm dictionary
26
"""
27
28
@property
29
def fields(self) -> list[AcroFormField]:
30
"""
31
List of all form fields in the PDF.
32
33
Returns:
34
list[AcroFormField]: All form fields including nested fields
35
"""
36
37
@property
38
def needs_appearances(self) -> bool:
39
"""
40
Whether form field appearances need to be generated.
41
42
When True, PDF viewers should generate field appearances.
43
When False, appearances are already present in the PDF.
44
45
Returns:
46
bool: Current NeedAppearances flag state
47
"""
48
49
@needs_appearances.setter
50
def needs_appearances(self, value: bool) -> None:
51
"""
52
Set whether form field appearances need to be generated.
53
54
Parameters:
55
- value (bool): New NeedAppearances flag value
56
"""
57
58
def add_field(self, field: AcroFormField) -> None:
59
"""
60
Add a form field to the PDF.
61
62
Parameters:
63
- field (AcroFormField): Field to add to the form
64
65
Raises:
66
ValueError: If field is invalid or conflicts with existing fields
67
"""
68
69
def remove_fields(self, names: list[str]) -> None:
70
"""
71
Remove form fields by their fully qualified names.
72
73
Parameters:
74
- names (list[str]): List of field names to remove
75
76
Raises:
77
KeyError: If any specified field name is not found
78
"""
79
80
def generate_appearances_if_needed(self) -> None:
81
"""
82
Generate appearances for fields that need them.
83
84
This method should be called before saving if any field
85
values have been modified programmatically.
86
"""
87
```
88
89
### AcroFormField Class
90
91
Individual form field objects with type-specific operations and value management.
92
93
```python { .api }
94
class AcroFormField:
95
"""
96
Individual PDF form field with type-specific operations.
97
98
Represents a single form field such as text field, checkbox,
99
radio button, choice field, or signature field.
100
"""
101
102
@property
103
def field_type(self) -> str:
104
"""
105
The form field type.
106
107
Returns:
108
str: Field type ('Tx' for text, 'Btn' for button, 'Ch' for choice, 'Sig' for signature)
109
"""
110
111
@property
112
def fully_qualified_name(self) -> str:
113
"""
114
The field's fully qualified name including parent hierarchy.
115
116
Returns:
117
str: Complete field name path (e.g., 'form.section.fieldname')
118
"""
119
120
@property
121
def partial_name(self) -> str:
122
"""
123
The field's partial name (not including parent hierarchy).
124
125
Returns:
126
str: Field's own name without parent path
127
"""
128
129
@property
130
def value(self) -> Object:
131
"""
132
The field's current value.
133
134
Returns:
135
Object: Field value (type depends on field type)
136
"""
137
138
@value.setter
139
def value(self, new_value: Object) -> None:
140
"""
141
Set the field's value.
142
143
Parameters:
144
- new_value (Object): New value for the field
145
"""
146
147
@property
148
def default_value(self) -> Object:
149
"""
150
The field's default value.
151
152
Returns:
153
Object: Default value for reset operations
154
"""
155
156
@property
157
def is_text(self) -> bool:
158
"""
159
Whether this is a text field.
160
161
Returns:
162
bool: True if field type is 'Tx' (text field)
163
"""
164
165
@property
166
def is_checkbox(self) -> bool:
167
"""
168
Whether this is a checkbox field.
169
170
Returns:
171
bool: True if this is a checkbox button field
172
"""
173
174
@property
175
def is_radiobutton(self) -> bool:
176
"""
177
Whether this is a radio button field.
178
179
Returns:
180
bool: True if this is a radio button field
181
"""
182
183
@property
184
def is_choice(self) -> bool:
185
"""
186
Whether this is a choice field (list box or combo box).
187
188
Returns:
189
bool: True if field type is 'Ch' (choice field)
190
"""
191
192
@property
193
def is_signature(self) -> bool:
194
"""
195
Whether this is a signature field.
196
197
Returns:
198
bool: True if field type is 'Sig' (signature field)
199
"""
200
201
@property
202
def flags(self) -> int:
203
"""
204
Form field flags as a bitmask.
205
206
Returns:
207
int: Field flags (readonly, required, etc.)
208
"""
209
210
@property
211
def rect(self) -> Rectangle:
212
"""
213
Field's bounding rectangle on the page.
214
215
Returns:
216
Rectangle: Field's position and size
217
"""
218
219
def set_value(self, value) -> None:
220
"""
221
Set the field's value with automatic type conversion.
222
223
Parameters:
224
- value: New value (automatically converted to appropriate PDF object type)
225
"""
226
227
def generate_appearance(self) -> None:
228
"""
229
Generate the field's appearance stream.
230
231
Creates the visual representation of the field based on
232
its current value and formatting properties.
233
"""
234
```
235
236
### Annotation Class
237
238
PDF annotation objects for interactive elements and markup.
239
240
```python { .api }
241
class Annotation(Object):
242
"""
243
PDF annotation object for interactive elements and markup.
244
245
Annotations include form fields, comments, highlights, links,
246
and other interactive or markup elements.
247
"""
248
249
@property
250
def subtype(self) -> Name:
251
"""
252
The annotation's subtype.
253
254
Returns:
255
Name: Annotation subtype (e.g., Name.Widget, Name.Link, Name.Text)
256
"""
257
258
@property
259
def rect(self) -> Rectangle:
260
"""
261
The annotation's bounding rectangle.
262
263
Returns:
264
Rectangle: Position and size of the annotation
265
"""
266
267
@property
268
def flags(self) -> int:
269
"""
270
Annotation flags as a bitmask.
271
272
Returns:
273
int: Flags controlling annotation behavior and appearance
274
"""
275
276
@property
277
def appearance_dict(self) -> Dictionary:
278
"""
279
The annotation's appearance dictionary.
280
281
Contains appearance streams for different annotation states.
282
283
Returns:
284
Dictionary: Appearance dictionary with normal, rollover, and down states
285
"""
286
287
@property
288
def contents(self) -> String:
289
"""
290
The annotation's text content or description.
291
292
Returns:
293
String: Textual content associated with the annotation
294
"""
295
296
@property
297
def page(self) -> Page:
298
"""
299
The page containing this annotation.
300
301
Returns:
302
Page: Page object where this annotation is placed
303
"""
304
305
def get_appearance_stream(self, which: str = 'N') -> Stream:
306
"""
307
Get an appearance stream for the annotation.
308
309
Parameters:
310
- which (str): Appearance state ('N' for normal, 'R' for rollover, 'D' for down)
311
312
Returns:
313
Stream: Appearance stream for the specified state
314
315
Raises:
316
KeyError: If the requested appearance state doesn't exist
317
"""
318
319
def get_page_content_for_appearance(self) -> bytes:
320
"""
321
Get page content suitable for use in appearance generation.
322
323
Returns:
324
bytes: Content stream data for appearance generation
325
"""
326
```
327
328
### Form Field Flags
329
330
Enumeration of form field flags for controlling field behavior.
331
332
```python { .api }
333
from enum import IntFlag
334
335
class FormFieldFlag(IntFlag):
336
"""Form field flags controlling field behavior and appearance."""
337
338
readonly = ... # Field is read-only
339
required = ... # Field is required
340
noexport = ... # Field value is not exported
341
multiline = ... # Text field allows multiple lines
342
password = ... # Text field is a password field
343
notoggletooff = ... # Radio button cannot be turned off
344
radio = ... # Button field is a radio button
345
pushbutton = ... # Button field is a push button
346
combo = ... # Choice field is a combo box
347
edit = ... # Choice field allows text editing
348
sort = ... # Choice field options should be sorted
349
fileselect = ... # Text field is for file selection
350
multiselect = ... # Choice field allows multiple selections
351
donotspellcheck = ... # Field content should not be spell-checked
352
donotscroll = ... # Text field should not scroll
353
comb = ... # Text field is a comb field
354
richtext = ... # Text field supports rich text formatting
355
radios_in_unison = ... # Radio buttons act in unison
356
commit_on_sel_change = ... # Commit value on selection change
357
```
358
359
### Annotation Flags
360
361
Enumeration of annotation flags for controlling annotation behavior.
362
363
```python { .api }
364
class AnnotationFlag(IntFlag):
365
"""Annotation flags controlling annotation behavior and appearance."""
366
367
invisible = ... # Annotation is invisible
368
hidden = ... # Annotation is hidden
369
print_ = ... # Annotation should be printed
370
nozoom = ... # Annotation should not scale with zoom
371
norotate = ... # Annotation should not rotate with page
372
noview = ... # Annotation should not be displayed
373
readonly = ... # Annotation is read-only
374
locked = ... # Annotation is locked
375
togglenoview = ... # Toggle view state on mouse click
376
lockedcontents = ... # Annotation contents are locked
377
```
378
379
## Usage Examples
380
381
### Working with Form Fields
382
383
```python
384
import pikepdf
385
386
# Open a PDF with form fields
387
pdf = pikepdf.open('form_document.pdf')
388
389
# Access the form
390
form = pdf.acroform
391
392
if form.exists:
393
print(f"Form has {len(form.fields)} fields")
394
395
# Iterate through all fields
396
for field in form.fields:
397
name = field.fully_qualified_name
398
field_type = field.field_type
399
value = field.value
400
401
print(f"Field '{name}' (type: {field_type}): {value}")
402
403
# Check field properties
404
if field.is_text:
405
print(f" Text field with value: {value}")
406
elif field.is_checkbox:
407
print(f" Checkbox is {'checked' if value else 'unchecked'}")
408
elif field.is_choice:
409
print(f" Choice field with selection: {value}")
410
411
pdf.close()
412
```
413
414
### Modifying Form Field Values
415
416
```python
417
import pikepdf
418
419
pdf = pikepdf.open('form_document.pdf')
420
form = pdf.acroform
421
422
if form.exists:
423
for field in form.fields:
424
name = field.fully_qualified_name
425
426
# Set text field values
427
if field.is_text and 'name' in name.lower():
428
field.set_value("John Doe")
429
elif field.is_text and 'email' in name.lower():
430
field.set_value("john.doe@example.com")
431
432
# Check/uncheck checkboxes
433
elif field.is_checkbox and 'agree' in name.lower():
434
field.set_value(True) # Check the box
435
436
# Set choice field selections
437
elif field.is_choice and 'country' in name.lower():
438
field.set_value("United States")
439
440
# Generate field appearances after modification
441
form.generate_appearances_if_needed()
442
443
# Flatten form (make fields non-editable)
444
# form.remove_fields([field.fully_qualified_name for field in form.fields])
445
446
pdf.save('filled_form.pdf')
447
pdf.close()
448
```
449
450
### Creating New Form Fields
451
452
```python
453
import pikepdf
454
455
pdf = pikepdf.open('document.pdf')
456
page = pdf.pages[0]
457
458
# Ensure the PDF has a form
459
if not pdf.acroform.exists:
460
# Create form structure
461
pdf.Root['/AcroForm'] = pikepdf.Dictionary({
462
'/Fields': pikepdf.Array(),
463
'/NeedAppearances': True
464
})
465
466
# Create a text field
467
text_field = pikepdf.Dictionary({
468
'/Type': pikepdf.Name.Annot,
469
'/Subtype': pikepdf.Name.Widget,
470
'/FT': pikepdf.Name.Tx, # Text field
471
'/T': pikepdf.String('username'), # Field name
472
'/V': pikepdf.String(''), # Default value
473
'/Rect': pikepdf.Array([100, 700, 300, 720]), # Position and size
474
'/P': page # Parent page
475
})
476
477
# Create a checkbox field
478
checkbox_field = pikepdf.Dictionary({
479
'/Type': pikepdf.Name.Annot,
480
'/Subtype': pikepdf.Name.Widget,
481
'/FT': pikepdf.Name.Btn, # Button field
482
'/Ff': 0, # Not a radio button or push button (checkbox)
483
'/T': pikepdf.String('subscribe'),
484
'/V': pikepdf.Name.Off, # Unchecked
485
'/Rect': pikepdf.Array([100, 650, 120, 670]),
486
'/P': page
487
})
488
489
# Add fields to form and page
490
pdf.Root['/AcroForm']['/Fields'].extend([text_field, checkbox_field])
491
492
# Add annotations to page
493
if '/Annots' not in page:
494
page['/Annots'] = pikepdf.Array()
495
page['/Annots'].extend([text_field, checkbox_field])
496
497
pdf.save('document_with_form.pdf')
498
pdf.close()
499
```
500
501
### Working with Annotations
502
503
```python
504
import pikepdf
505
506
pdf = pikepdf.open('document.pdf')
507
page = pdf.pages[0]
508
509
# Check if page has annotations
510
if '/Annots' in page:
511
annotations = page['/Annots']
512
513
for annot_ref in annotations:
514
annot = annot_ref # Resolve if indirect
515
516
# Check annotation type
517
subtype = annot.get('/Subtype')
518
519
if subtype == pikepdf.Name.Link:
520
# Handle link annotation
521
rect = annot['/Rect']
522
action = annot.get('/A')
523
print(f"Link at {rect}: {action}")
524
525
elif subtype == pikepdf.Name.Text:
526
# Handle text annotation (note/comment)
527
contents = annot.get('/Contents', '')
528
print(f"Note: {contents}")
529
530
elif subtype == pikepdf.Name.Widget:
531
# Handle form field widget
532
field_name = annot.get('/T', 'unnamed')
533
field_type = annot.get('/FT')
534
print(f"Form field '{field_name}' of type {field_type}")
535
536
pdf.close()
537
```
538
539
### Form Field Validation and Processing
540
541
```python
542
import pikepdf
543
import re
544
545
pdf = pikepdf.open('form_document.pdf')
546
form = pdf.acroform
547
548
def validate_email(email):
549
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
550
return re.match(pattern, email) is not None
551
552
if form.exists:
553
errors = []
554
555
for field in form.fields:
556
name = field.fully_qualified_name
557
value = str(field.value) if field.value else ""
558
559
# Check required fields
560
if field.flags & pikepdf.FormFieldFlag.required:
561
if not value.strip():
562
errors.append(f"Required field '{name}' is empty")
563
564
# Validate email fields
565
if 'email' in name.lower() and value:
566
if not validate_email(value):
567
errors.append(f"Invalid email in field '{name}': {value}")
568
569
# Check text field length limits
570
if field.is_text and hasattr(field, 'max_length'):
571
if len(value) > field.max_length:
572
errors.append(f"Field '{name}' exceeds maximum length")
573
574
if errors:
575
print("Form validation errors:")
576
for error in errors:
577
print(f" - {error}")
578
else:
579
print("Form validation passed")
580
581
pdf.close()
582
```
583
584
### Advanced Form Operations
585
586
```python
587
import pikepdf
588
589
pdf = pikepdf.open('complex_form.pdf')
590
form = pdf.acroform
591
592
# Group fields by type
593
field_groups = {
594
'text': [],
595
'checkbox': [],
596
'radio': [],
597
'choice': [],
598
'signature': []
599
}
600
601
if form.exists:
602
for field in form.fields:
603
if field.is_text:
604
field_groups['text'].append(field)
605
elif field.is_checkbox:
606
field_groups['checkbox'].append(field)
607
elif field.is_radiobutton:
608
field_groups['radio'].append(field)
609
elif field.is_choice:
610
field_groups['choice'].append(field)
611
elif field.is_signature:
612
field_groups['signature'].append(field)
613
614
# Report field statistics
615
for field_type, fields in field_groups.items():
616
print(f"{field_type.capitalize()} fields: {len(fields)}")
617
for field in fields:
618
print(f" - {field.fully_qualified_name}")
619
620
# Batch operations
621
# Make all text fields read-only
622
for field in field_groups['text']:
623
field.flags |= pikepdf.FormFieldFlag.readonly
624
625
# Clear all checkbox values
626
for field in field_groups['checkbox']:
627
field.set_value(False)
628
629
# Set default selections for choice fields
630
for field in field_groups['choice']:
631
if hasattr(field, 'options') and field.options:
632
field.set_value(field.options[0]) # Select first option
633
634
# Generate appearances after modifications
635
form.generate_appearances_if_needed()
636
637
pdf.save('processed_form.pdf')
638
pdf.close()
639
```