0
# Annotations and Forms
1
2
Comprehensive annotation handling and interactive forms support for PDF documents. PyMuPDF provides complete control over PDF annotations including creation, modification, deletion, and rendering of various annotation types.
3
4
## Capabilities
5
6
### Annotation Management
7
8
Core annotation operations for working with PDF annotations.
9
10
```python { .api }
11
class Page:
12
def first_annot(self) -> Annot:
13
"""
14
Get first annotation on page.
15
16
Returns:
17
First Annot object or None if no annotations
18
"""
19
20
def load_annot(self, ident: typing.Union[str, int]) -> Annot:
21
"""
22
Load specific annotation by identifier.
23
24
Parameters:
25
- ident: annotation identifier (xref number or unique name)
26
27
Returns:
28
Annot object
29
"""
30
31
def annots(self, types: list = None) -> list:
32
"""
33
Get list of annotations on page.
34
35
Parameters:
36
- types: filter by annotation types (list of integers)
37
38
Returns:
39
List of Annot objects
40
"""
41
42
def annot_names(self) -> list:
43
"""
44
Get list of annotation names on page.
45
46
Returns:
47
List of annotation unique names
48
"""
49
50
def add_text_annot(self, point: Point, text: str, icon: str = "Note") -> Annot:
51
"""
52
Add text annotation.
53
54
Parameters:
55
- point: annotation position
56
- text: annotation content
57
- icon: icon name ("Note", "Comment", "Key", "Help", etc.)
58
59
Returns:
60
New Annot object
61
"""
62
63
def add_highlight_annot(self, quads: typing.Union[Quad, list]) -> Annot:
64
"""
65
Add highlight annotation.
66
67
Parameters:
68
- quads: Quad object or list of Quad objects to highlight
69
70
Returns:
71
New Annot object
72
"""
73
74
def add_underline_annot(self, quads: typing.Union[Quad, list]) -> Annot:
75
"""
76
Add underline annotation.
77
78
Parameters:
79
- quads: Quad object or list of Quad objects to underline
80
81
Returns:
82
New Annot object
83
"""
84
85
def add_strikeout_annot(self, quads: typing.Union[Quad, list]) -> Annot:
86
"""
87
Add strikeout annotation.
88
89
Parameters:
90
- quads: Quad object or list of Quad objects to strike out
91
92
Returns:
93
New Annot object
94
"""
95
96
def add_squiggly_annot(self, quads: typing.Union[Quad, list]) -> Annot:
97
"""
98
Add squiggly underline annotation.
99
100
Parameters:
101
- quads: Quad object or list of Quad objects for squiggly underline
102
103
Returns:
104
New Annot object
105
"""
106
107
def add_rect_annot(self, rect: Rect) -> Annot:
108
"""
109
Add rectangle annotation.
110
111
Parameters:
112
- rect: rectangle coordinates
113
114
Returns:
115
New Annot object
116
"""
117
118
def add_circle_annot(self, rect: Rect) -> Annot:
119
"""
120
Add circle annotation.
121
122
Parameters:
123
- rect: bounding rectangle for circle
124
125
Returns:
126
New Annot object
127
"""
128
129
def add_line_annot(self, p1: Point, p2: Point) -> Annot:
130
"""
131
Add line annotation.
132
133
Parameters:
134
- p1: start point
135
- p2: end point
136
137
Returns:
138
New Annot object
139
"""
140
141
def add_polyline_annot(self, points: list) -> Annot:
142
"""
143
Add polyline annotation.
144
145
Parameters:
146
- points: list of Point objects
147
148
Returns:
149
New Annot object
150
"""
151
152
def add_polygon_annot(self, points: list) -> Annot:
153
"""
154
Add polygon annotation.
155
156
Parameters:
157
- points: list of Point objects
158
159
Returns:
160
New Annot object
161
"""
162
163
def add_freetext_annot(self, rect: Rect, text: str, **kwargs) -> Annot:
164
"""
165
Add free text annotation.
166
167
Parameters:
168
- rect: annotation rectangle
169
- text: text content
170
- fontsize: font size
171
- fontname: font name
172
- text_color: text color
173
- fill_color: background color
174
- align: text alignment (0=left, 1=center, 2=right)
175
176
Returns:
177
New Annot object
178
"""
179
180
def add_ink_annot(self, handwriting: list) -> Annot:
181
"""
182
Add ink annotation (freehand drawing).
183
184
Parameters:
185
- handwriting: list of lists of Point objects (strokes)
186
187
Returns:
188
New Annot object
189
"""
190
191
def add_stamp_annot(self, rect: Rect, stamp: int = 0) -> Annot:
192
"""
193
Add stamp annotation.
194
195
Parameters:
196
- rect: stamp rectangle
197
- stamp: stamp type (0-13 for predefined stamps)
198
199
Returns:
200
New Annot object
201
"""
202
```
203
204
### Annotation Class
205
206
Individual annotation object with comprehensive manipulation capabilities.
207
208
```python { .api }
209
class Annot:
210
def set_info(self, content: str = None, title: str = None,
211
creationDate: str = None, modDate: str = None,
212
subject: str = None) -> None:
213
"""
214
Set annotation information.
215
216
Parameters:
217
- content: annotation content/text
218
- title: annotation title/author
219
- creationDate: creation date string
220
- modDate: modification date string
221
- subject: annotation subject
222
"""
223
224
def get_info(self) -> dict:
225
"""
226
Get annotation information.
227
228
Returns:
229
Dictionary with content, title, creationDate, modDate, subject
230
"""
231
232
def set_rect(self, rect: Rect) -> None:
233
"""
234
Set annotation rectangle.
235
236
Parameters:
237
- rect: new annotation rectangle
238
"""
239
240
def set_colors(self, colors: dict = None) -> None:
241
"""
242
Set annotation colors.
243
244
Parameters:
245
- colors: dictionary with 'stroke' and/or 'fill' color lists
246
"""
247
248
def set_border(self, border: dict = None) -> None:
249
"""
250
Set annotation border properties.
251
252
Parameters:
253
- border: dictionary with 'width', 'style', 'dashes' keys
254
"""
255
256
def set_flags(self, flags: int) -> None:
257
"""
258
Set annotation flags.
259
260
Parameters:
261
- flags: annotation flags (bitwise combination)
262
"""
263
264
def set_oc(self, xref: int) -> None:
265
"""
266
Set optional content (layer) reference.
267
268
Parameters:
269
- xref: optional content group xref
270
"""
271
272
def update(self, opacity: float = -1, blend_mode: str = None,
273
fontsize: float = 0, text_color: list = None,
274
border_color: list = None, fill_color: list = None) -> None:
275
"""
276
Update annotation appearance.
277
278
Parameters:
279
- opacity: annotation opacity (0-1)
280
- blend_mode: PDF blend mode
281
- fontsize: font size for text annotations
282
- text_color: text color as RGB list
283
- border_color: border color as RGB list
284
- fill_color: fill color as RGB list
285
"""
286
287
def delete(self) -> None:
288
"""Delete annotation from page."""
289
290
def get_pixmap(self, matrix: Matrix = None, colorspace: Colorspace = None,
291
alpha: bool = False) -> Pixmap:
292
"""
293
Render annotation to pixmap.
294
295
Parameters:
296
- matrix: transformation matrix
297
- colorspace: target color space
298
- alpha: include alpha channel
299
300
Returns:
301
Pixmap with annotation rendering
302
"""
303
304
def get_sound(self) -> dict:
305
"""
306
Get sound annotation data.
307
308
Returns:
309
Dictionary with sound properties
310
"""
311
312
def get_file(self) -> bytes:
313
"""
314
Get file attachment annotation data.
315
316
Returns:
317
File data as bytes
318
"""
319
320
def set_name(self, name: str) -> None:
321
"""
322
Set annotation unique name.
323
324
Parameters:
325
- name: unique annotation name
326
"""
327
328
@property
329
def type(self) -> list:
330
"""Annotation type as [type_number, type_string]."""
331
332
@property
333
def rect(self) -> Rect:
334
"""Annotation rectangle."""
335
336
@property
337
def next(self) -> Annot:
338
"""Next annotation on page."""
339
340
@property
341
def xref(self) -> int:
342
"""Annotation xref number."""
343
344
@property
345
def parent(self) -> Page:
346
"""Parent page object."""
347
348
@property
349
def flags(self) -> int:
350
"""Annotation flags."""
351
352
@property
353
def line_ends(self) -> list:
354
"""Line ending styles for line annotations."""
355
356
@property
357
def vertices(self) -> list:
358
"""Vertices for polygon/polyline annotations."""
359
360
@property
361
def colors(self) -> dict:
362
"""Annotation colors dictionary."""
363
364
@property
365
def border(self) -> dict:
366
"""Annotation border properties."""
367
```
368
369
### Form Field Operations
370
371
Handle interactive PDF forms and form fields.
372
373
```python { .api }
374
class Page:
375
def first_widget(self) -> Widget:
376
"""
377
Get first form widget on page.
378
379
Returns:
380
First Widget object or None
381
"""
382
383
def load_widget(self, xref: int) -> Widget:
384
"""
385
Load widget by xref number.
386
387
Parameters:
388
- xref: widget xref number
389
390
Returns:
391
Widget object
392
"""
393
```
394
395
### Widget Class
396
397
Interactive form field representation.
398
399
```python { .api }
400
class Widget:
401
def field_name(self) -> str:
402
"""
403
Get field name.
404
405
Returns:
406
Form field name
407
"""
408
409
def field_value(self) -> typing.Any:
410
"""
411
Get field value.
412
413
Returns:
414
Current field value
415
"""
416
417
def field_type(self) -> int:
418
"""
419
Get field type.
420
421
Returns:
422
Field type number
423
"""
424
425
def field_type_string(self) -> str:
426
"""
427
Get field type as string.
428
429
Returns:
430
Field type string ("Text", "Button", "Choice", etc.)
431
"""
432
433
def field_flags(self) -> int:
434
"""
435
Get field flags.
436
437
Returns:
438
Field flags bitfield
439
"""
440
441
def field_display(self) -> int:
442
"""
443
Get field display mode.
444
445
Returns:
446
Display mode (0=visible, 1=hidden, 2=no print, 3=no view)
447
"""
448
449
def set_field_value(self, value: typing.Any, ignore_limits: bool = False) -> bool:
450
"""
451
Set field value.
452
453
Parameters:
454
- value: new field value
455
- ignore_limits: ignore field validation limits
456
457
Returns:
458
True if value was set successfully
459
"""
460
461
def reset_field(self) -> None:
462
"""Reset field to default value."""
463
464
def update(self) -> None:
465
"""Update widget appearance."""
466
467
@property
468
def rect(self) -> Rect:
469
"""Widget rectangle."""
470
471
@property
472
def xref(self) -> int:
473
"""Widget xref number."""
474
475
@property
476
def parent(self) -> Page:
477
"""Parent page object."""
478
479
@property
480
def next(self) -> Widget:
481
"""Next widget on page."""
482
```
483
484
### Redaction Operations
485
486
Handle content redaction (permanent removal).
487
488
```python { .api }
489
class Page:
490
def add_redact_annot(self, rect: Rect, text: str = "",
491
fill: list = None, text_color: list = None,
492
cross_out: bool = True, **kwargs) -> Annot:
493
"""
494
Add redaction annotation.
495
496
Parameters:
497
- rect: area to redact
498
- text: replacement text (optional)
499
- fill: fill color for redacted area
500
- text_color: replacement text color
501
- cross_out: draw diagonal lines over area
502
- fontname: font for replacement text
503
- fontsize: font size for replacement text
504
- align: text alignment (0=left, 1=center, 2=right)
505
506
Returns:
507
New redaction Annot object
508
"""
509
510
def apply_redactions(self, images: int = 2, graphics: int = 2,
511
text: int = 2) -> bool:
512
"""
513
Apply all redaction annotations on page.
514
515
Parameters:
516
- images: how to handle images (0=ignore, 1=remove if overlapping, 2=remove if any overlap)
517
- graphics: how to handle graphics (0=ignore, 1=remove if overlapping, 2=remove if any overlap)
518
- text: how to handle text (0=ignore, 1=remove if overlapping, 2=remove if any overlap)
519
520
Returns:
521
True if redactions were applied
522
"""
523
524
def get_redactions(self) -> list:
525
"""
526
Get list of redaction annotations.
527
528
Returns:
529
List of redaction Annot objects
530
"""
531
```
532
533
## Usage Examples
534
535
### Basic Annotation Operations
536
537
```python
538
import pymupdf
539
540
doc = pymupdf.open("document.pdf")
541
page = doc.load_page(0)
542
543
# Add text annotation
544
point = pymupdf.Point(100, 100)
545
annot = page.add_text_annot(point, "This is a note", icon="Comment")
546
annot.set_info(title="Author Name", subject="Review Comment")
547
annot.update()
548
549
# Add highlight annotation
550
rect = pymupdf.Rect(100, 200, 300, 220)
551
quad = rect.quad
552
highlight = page.add_highlight_annot(quad)
553
highlight.set_colors({"stroke": [1, 1, 0]}) # Yellow highlight
554
highlight.update()
555
556
# Save document with annotations
557
doc.save("annotated_document.pdf")
558
doc.close()
559
```
560
561
### Working with Existing Annotations
562
563
```python
564
import pymupdf
565
566
doc = pymupdf.open("annotated_document.pdf")
567
page = doc.load_page(0)
568
569
# Iterate through all annotations
570
for annot in page.annots():
571
info = annot.get_info()
572
print(f"Type: {annot.type[1]}")
573
print(f"Content: {info['content']}")
574
print(f"Author: {info['title']}")
575
print(f"Rectangle: {annot.rect}")
576
577
# Modify annotation
578
if annot.type[1] == "Text":
579
annot.set_info(content="Updated content")
580
annot.update()
581
582
# Remove all highlight annotations
583
for annot in page.annots():
584
if annot.type[1] == "Highlight":
585
annot.delete()
586
587
doc.save("modified_annotations.pdf")
588
doc.close()
589
```
590
591
### Advanced Annotation Creation
592
593
```python
594
import pymupdf
595
596
doc = pymupdf.open("document.pdf")
597
page = doc.load_page(0)
598
599
# Add free text annotation with formatting
600
rect = pymupdf.Rect(100, 100, 400, 150)
601
freetext = page.add_freetext_annot(
602
rect,
603
"This is formatted text",
604
fontsize=12,
605
fontname="Arial",
606
text_color=[0, 0, 1], # Blue text
607
fill_color=[1, 1, 0.8], # Light yellow background
608
align=1 # Center aligned
609
)
610
freetext.update()
611
612
# Add ink annotation (freehand drawing)
613
strokes = [
614
[pymupdf.Point(200, 200), pymupdf.Point(250, 180), pymupdf.Point(300, 200)],
615
[pymupdf.Point(200, 220), pymupdf.Point(250, 240), pymupdf.Point(300, 220)]
616
]
617
ink = page.add_ink_annot(strokes)
618
ink.set_colors({"stroke": [1, 0, 0]}) # Red ink
619
ink.set_border({"width": 2})
620
ink.update()
621
622
# Add stamp annotation
623
stamp_rect = pymupdf.Rect(400, 400, 500, 450)
624
stamp = page.add_stamp_annot(stamp_rect, stamp=5) # "APPROVED" stamp
625
stamp.update()
626
627
doc.save("advanced_annotations.pdf")
628
doc.close()
629
```
630
631
### Form Field Manipulation
632
633
```python
634
import pymupdf
635
636
doc = pymupdf.open("form_document.pdf")
637
638
# Iterate through all form fields
639
for page_num in range(doc.page_count):
640
page = doc.load_page(page_num)
641
642
widget = page.first_widget()
643
while widget:
644
field_name = widget.field_name()
645
field_type = widget.field_type_string()
646
current_value = widget.field_value()
647
648
print(f"Field: {field_name}, Type: {field_type}, Value: {current_value}")
649
650
# Set field values based on name
651
if field_name == "Name":
652
widget.set_field_value("John Doe")
653
elif field_name == "Email":
654
widget.set_field_value("john.doe@example.com")
655
elif field_name == "Subscribe" and field_type == "CheckBox":
656
widget.set_field_value(True)
657
658
widget.update()
659
widget = widget.next
660
661
# Save filled form
662
doc.save("filled_form.pdf")
663
doc.close()
664
```
665
666
### Content Redaction
667
668
```python
669
import pymupdf
670
671
doc = pymupdf.open("sensitive_document.pdf")
672
page = doc.load_page(0)
673
674
# Search for sensitive information
675
sensitive_terms = ["SSN", "Social Security", "confidential"]
676
677
for term in sensitive_terms:
678
text_instances = page.search_for(term)
679
for inst in text_instances:
680
# Add redaction annotation
681
redact = page.add_redact_annot(
682
inst,
683
text="[REDACTED]",
684
fill=[0, 0, 0], # Black fill
685
text_color=[1, 1, 1], # White text
686
cross_out=True
687
)
688
689
# Apply all redaction annotations
690
page.apply_redactions()
691
692
# Save redacted document
693
doc.save("redacted_document.pdf")
694
doc.close()
695
```
696
697
### Annotation Export and Import
698
699
```python
700
import pymupdf
701
import json
702
703
def export_annotations(doc_path: str) -> dict:
704
"""Export all annotations to a dictionary."""
705
doc = pymupdf.open(doc_path)
706
annotations = {}
707
708
for page_num in range(doc.page_count):
709
page = doc.load_page(page_num)
710
page_annots = []
711
712
for annot in page.annots():
713
annot_data = {
714
"type": annot.type,
715
"rect": list(annot.rect),
716
"info": annot.get_info(),
717
"colors": annot.colors,
718
"border": annot.border
719
}
720
page_annots.append(annot_data)
721
722
if page_annots:
723
annotations[page_num] = page_annots
724
725
doc.close()
726
return annotations
727
728
def import_annotations(doc_path: str, annotations: dict, output_path: str):
729
"""Import annotations from dictionary to document."""
730
doc = pymupdf.open(doc_path)
731
732
for page_num, page_annots in annotations.items():
733
page = doc.load_page(int(page_num))
734
735
for annot_data in page_annots:
736
rect = pymupdf.Rect(annot_data["rect"])
737
738
# Create annotation based on type
739
if annot_data["type"][1] == "Text":
740
annot = page.add_text_annot(rect.tl, annot_data["info"]["content"])
741
elif annot_data["type"][1] == "Highlight":
742
annot = page.add_highlight_annot(rect.quad)
743
# ... handle other types
744
745
# Apply properties
746
annot.set_info(**annot_data["info"])
747
if annot_data["colors"]:
748
annot.set_colors(annot_data["colors"])
749
if annot_data["border"]:
750
annot.set_border(annot_data["border"])
751
annot.update()
752
753
doc.save(output_path)
754
doc.close()
755
756
# Usage
757
annotations = export_annotations("source.pdf")
758
with open("annotations.json", "w") as f:
759
json.dump(annotations, f, indent=2)
760
761
# Later, import to another document
762
with open("annotations.json", "r") as f:
763
annotations = json.load(f)
764
import_annotations("target.pdf", annotations, "target_with_annotations.pdf")
765
```