0
# Annotations
1
2
Complete annotation system supporting markup annotations (highlights, text annotations, shapes) and interactive elements (links, popups) with full customization capabilities. pypdf provides comprehensive annotation support for creating interactive PDFs.
3
4
## Capabilities
5
6
### Base Annotation Classes
7
8
Foundation classes for all annotation types with common properties and methods.
9
10
```python { .api }
11
class AnnotationDictionary:
12
"""Base class for all PDF annotations."""
13
14
def __init__(self, **kwargs):
15
"""
16
Initialize annotation with properties.
17
18
Args:
19
**kwargs: Annotation properties
20
"""
21
22
class NO_FLAGS:
23
"""Constant for annotations with no flags."""
24
```
25
26
### Markup Annotations
27
28
Annotations that mark up document content with visual highlighting, text, and shapes.
29
30
```python { .api }
31
class MarkupAnnotation(AnnotationDictionary):
32
"""Base class for markup annotations."""
33
34
class Highlight(MarkupAnnotation):
35
"""Highlight annotation for marking important text."""
36
37
def __init__(
38
self,
39
rect,
40
quad_points,
41
highlight_color: str = "ffff00",
42
**kwargs
43
):
44
"""
45
Create a highlight annotation.
46
47
Args:
48
rect: Rectangle defining annotation bounds
49
quad_points: Points defining highlighted area
50
highlight_color: Highlight color in hex format
51
**kwargs: Additional annotation properties
52
"""
53
54
class Text(MarkupAnnotation):
55
"""Text annotation (sticky note)."""
56
57
def __init__(
58
self,
59
rect,
60
text: str,
61
icon: str = "Note",
62
**kwargs
63
):
64
"""
65
Create a text annotation.
66
67
Args:
68
rect: Rectangle defining annotation position
69
text: Annotation text content
70
icon: Icon type ("Note", "Comment", "Key", etc.)
71
**kwargs: Additional annotation properties
72
"""
73
74
class FreeText(MarkupAnnotation):
75
"""Free text annotation for adding text directly to the page."""
76
77
def __init__(
78
self,
79
rect,
80
text: str,
81
font: str = "Helvetica",
82
font_size: float = 12,
83
**kwargs
84
):
85
"""
86
Create a free text annotation.
87
88
Args:
89
rect: Rectangle defining text area
90
text: Text content
91
font: Font name
92
font_size: Font size in points
93
**kwargs: Additional annotation properties
94
"""
95
96
class Line(MarkupAnnotation):
97
"""Line annotation for drawing lines."""
98
99
def __init__(
100
self,
101
p1: tuple,
102
p2: tuple,
103
line_color: str = "000000",
104
line_width: float = 1,
105
**kwargs
106
):
107
"""
108
Create a line annotation.
109
110
Args:
111
p1: Start point (x, y)
112
p2: End point (x, y)
113
line_color: Line color in hex format
114
line_width: Line width in points
115
**kwargs: Additional annotation properties
116
"""
117
118
class Rectangle(MarkupAnnotation):
119
"""Rectangle annotation for drawing rectangles."""
120
121
def __init__(
122
self,
123
rect,
124
stroke_color: str = "000000",
125
fill_color: str | None = None,
126
line_width: float = 1,
127
**kwargs
128
):
129
"""
130
Create a rectangle annotation.
131
132
Args:
133
rect: Rectangle coordinates
134
stroke_color: Border color in hex format
135
fill_color: Fill color in hex format (None for no fill)
136
line_width: Border width in points
137
**kwargs: Additional annotation properties
138
"""
139
140
class Ellipse(MarkupAnnotation):
141
"""Ellipse annotation for drawing ellipses and circles."""
142
143
def __init__(
144
self,
145
rect,
146
stroke_color: str = "000000",
147
fill_color: str | None = None,
148
line_width: float = 1,
149
**kwargs
150
):
151
"""
152
Create an ellipse annotation.
153
154
Args:
155
rect: Bounding rectangle for ellipse
156
stroke_color: Border color in hex format
157
fill_color: Fill color in hex format (None for no fill)
158
line_width: Border width in points
159
**kwargs: Additional annotation properties
160
"""
161
162
class Polygon(MarkupAnnotation):
163
"""Polygon annotation for drawing multi-sided shapes."""
164
165
def __init__(
166
self,
167
vertices: list,
168
stroke_color: str = "000000",
169
fill_color: str | None = None,
170
line_width: float = 1,
171
**kwargs
172
):
173
"""
174
Create a polygon annotation.
175
176
Args:
177
vertices: List of (x, y) coordinates defining polygon vertices
178
stroke_color: Border color in hex format
179
fill_color: Fill color in hex format (None for no fill)
180
line_width: Border width in points
181
**kwargs: Additional annotation properties
182
"""
183
184
class PolyLine(MarkupAnnotation):
185
"""Polyline annotation for drawing connected line segments."""
186
187
def __init__(
188
self,
189
vertices: list,
190
line_color: str = "000000",
191
line_width: float = 1,
192
**kwargs
193
):
194
"""
195
Create a polyline annotation.
196
197
Args:
198
vertices: List of (x, y) coordinates defining line points
199
line_color: Line color in hex format
200
line_width: Line width in points
201
**kwargs: Additional annotation properties
202
"""
203
```
204
205
### Interactive Annotations
206
207
Non-markup annotations that provide interactive functionality.
208
209
```python { .api }
210
class Link:
211
"""Link annotation for creating clickable links."""
212
213
def __init__(
214
self,
215
rect,
216
target,
217
**kwargs
218
):
219
"""
220
Create a link annotation.
221
222
Args:
223
rect: Rectangle defining clickable area
224
target: Target URL or internal destination
225
**kwargs: Additional annotation properties
226
"""
227
228
class Popup:
229
"""Popup annotation associated with other annotations."""
230
231
def __init__(
232
self,
233
rect,
234
parent,
235
**kwargs
236
):
237
"""
238
Create a popup annotation.
239
240
Args:
241
rect: Rectangle defining popup area
242
parent: Parent annotation this popup belongs to
243
**kwargs: Additional annotation properties
244
"""
245
```
246
247
## Usage Examples
248
249
### Adding Text Annotations
250
251
```python
252
from pypdf import PdfReader, PdfWriter
253
from pypdf.annotations import Text
254
255
reader = PdfReader("document.pdf")
256
writer = PdfWriter()
257
258
# Add text annotation to first page
259
page = reader.pages[0]
260
261
text_annotation = Text(
262
rect=(100, 100, 200, 150), # x1, y1, x2, y2
263
text="This is a note about this section",
264
icon="Comment"
265
)
266
267
page.annotations.append(text_annotation)
268
writer.add_page(page)
269
270
# Copy remaining pages
271
for page in reader.pages[1:]:
272
writer.add_page(page)
273
274
with open("annotated.pdf", "wb") as output:
275
writer.write(output)
276
```
277
278
### Adding Highlight Annotations
279
280
```python
281
from pypdf import PdfReader, PdfWriter
282
from pypdf.annotations import Highlight
283
284
reader = PdfReader("document.pdf")
285
writer = PdfWriter()
286
287
page = reader.pages[0]
288
289
# Highlight annotation requires quad points defining the highlighted area
290
# For simplicity, using rectangle coordinates
291
highlight = Highlight(
292
rect=(100, 200, 300, 220),
293
quad_points=[(100, 200), (300, 200), (100, 220), (300, 220)],
294
highlight_color="ffff00" # Yellow highlight
295
)
296
297
page.annotations.append(highlight)
298
writer.add_page(page)
299
300
# Copy remaining pages
301
for page in reader.pages[1:]:
302
writer.add_page(page)
303
304
with open("highlighted.pdf", "wb") as output:
305
writer.write(output)
306
```
307
308
### Adding Shape Annotations
309
310
```python
311
from pypdf import PdfReader, PdfWriter
312
from pypdf.annotations import Rectangle, Ellipse, Line
313
314
reader = PdfReader("document.pdf")
315
writer = PdfWriter()
316
317
page = reader.pages[0]
318
319
# Add rectangle
320
rectangle = Rectangle(
321
rect=(50, 50, 150, 100),
322
stroke_color="ff0000", # Red border
323
fill_color="ffcccc", # Light red fill
324
line_width=2
325
)
326
327
# Add ellipse
328
ellipse = Ellipse(
329
rect=(200, 200, 300, 250),
330
stroke_color="0000ff", # Blue border
331
fill_color="ccccff", # Light blue fill
332
line_width=1.5
333
)
334
335
# Add line
336
line = Line(
337
p1=(100, 300),
338
p2=(400, 350),
339
line_color="00ff00", # Green line
340
line_width=3
341
)
342
343
# Add all annotations to the page
344
page.annotations.extend([rectangle, ellipse, line])
345
writer.add_page(page)
346
347
# Copy remaining pages
348
for page in reader.pages[1:]:
349
writer.add_page(page)
350
351
with open("shapes.pdf", "wb") as output:
352
writer.write(output)
353
```
354
355
### Adding Free Text Annotations
356
357
```python
358
from pypdf import PdfReader, PdfWriter
359
from pypdf.annotations import FreeText
360
361
reader = PdfReader("document.pdf")
362
writer = PdfWriter()
363
364
page = reader.pages[0]
365
366
# Add free text annotation
367
free_text = FreeText(
368
rect=(100, 400, 300, 450),
369
text="This text appears directly on the page",
370
font="Arial",
371
font_size=14
372
)
373
374
page.annotations.append(free_text)
375
writer.add_page(page)
376
377
# Copy remaining pages
378
for page in reader.pages[1:]:
379
writer.add_page(page)
380
381
with open("free_text.pdf", "wb") as output:
382
writer.write(output)
383
```
384
385
### Creating Link Annotations
386
387
```python
388
from pypdf import PdfReader, PdfWriter
389
from pypdf.annotations import Link
390
391
reader = PdfReader("document.pdf")
392
writer = PdfWriter()
393
394
page = reader.pages[0]
395
396
# External URL link
397
url_link = Link(
398
rect=(100, 100, 200, 120),
399
target="https://example.com"
400
)
401
402
# Internal page link (go to page 2)
403
page_link = Link(
404
rect=(100, 150, 200, 170),
405
target={"type": "goto", "page": 1} # 0-indexed page number
406
)
407
408
page.annotations.extend([url_link, page_link])
409
writer.add_page(page)
410
411
# Copy remaining pages
412
for page in reader.pages[1:]:
413
writer.add_page(page)
414
415
with open("links.pdf", "wb") as output:
416
writer.write(output)
417
```
418
419
### Complex Polygon Annotation
420
421
```python
422
from pypdf import PdfReader, PdfWriter
423
from pypdf.annotations import Polygon
424
425
reader = PdfReader("document.pdf")
426
writer = PdfWriter()
427
428
page = reader.pages[0]
429
430
# Create a pentagon
431
pentagon_vertices = [
432
(200, 300), # Top point
433
(250, 250), # Top right
434
(225, 200), # Bottom right
435
(175, 200), # Bottom left
436
(150, 250) # Top left
437
]
438
439
pentagon = Polygon(
440
vertices=pentagon_vertices,
441
stroke_color="800080", # Purple border
442
fill_color="dda0dd", # Plum fill
443
line_width=2
444
)
445
446
page.annotations.append(pentagon)
447
writer.add_page(page)
448
449
# Copy remaining pages
450
for page in reader.pages[1:]:
451
writer.add_page(page)
452
453
with open("polygon.pdf", "wb") as output:
454
writer.write(output)
455
```
456
457
### Reading Existing Annotations
458
459
```python
460
from pypdf import PdfReader
461
462
reader = PdfReader("annotated_document.pdf")
463
464
for page_num, page in enumerate(reader.pages):
465
print(f"Page {page_num + 1}:")
466
467
if page.annotations:
468
for i, annotation in enumerate(page.annotations):
469
print(f" Annotation {i + 1}:")
470
print(f" Type: {annotation.get('/Subtype', 'Unknown')}")
471
print(f" Rectangle: {annotation.get('/Rect', 'Not specified')}")
472
473
# Check for text content
474
if '/Contents' in annotation:
475
print(f" Text: {annotation['/Contents']}")
476
477
# Check for appearance
478
if '/AP' in annotation:
479
print(f" Has appearance stream")
480
481
print()
482
else:
483
print(" No annotations found")
484
print()
485
```
486
487
### Annotation Management
488
489
```python
490
from pypdf import PdfReader, PdfWriter
491
492
def remove_annotations(input_pdf: str, output_pdf: str):
493
"""Remove all annotations from a PDF."""
494
reader = PdfReader(input_pdf)
495
writer = PdfWriter()
496
497
for page in reader.pages:
498
# Clear annotations
499
if page.annotations:
500
page.annotations.clear()
501
writer.add_page(page)
502
503
with open(output_pdf, "wb") as output:
504
writer.write(output)
505
506
def filter_annotations_by_type(input_pdf: str, output_pdf: str, keep_types: list):
507
"""Keep only specific annotation types."""
508
reader = PdfReader(input_pdf)
509
writer = PdfWriter()
510
511
for page in reader.pages:
512
if page.annotations:
513
# Filter annotations
514
filtered_annotations = []
515
for annotation in page.annotations:
516
annotation_type = annotation.get('/Subtype')
517
if annotation_type in keep_types:
518
filtered_annotations.append(annotation)
519
520
# Replace annotations with filtered list
521
page.annotations.clear()
522
page.annotations.extend(filtered_annotations)
523
524
writer.add_page(page)
525
526
with open(output_pdf, "wb") as output:
527
writer.write(output)
528
529
# Remove all annotations
530
remove_annotations("annotated.pdf", "clean.pdf")
531
532
# Keep only text and highlight annotations
533
filter_annotations_by_type(
534
"annotated.pdf",
535
"filtered.pdf",
536
['/Text', '/Highlight']
537
)
538
```
539
540
### Batch Annotation Processing
541
542
```python
543
from pypdf import PdfReader, PdfWriter
544
from pypdf.annotations import Text
545
from pathlib import Path
546
547
def add_review_annotations(pdf_directory: str, reviewer_name: str):
548
"""Add review annotations to all PDFs in a directory."""
549
550
for pdf_path in Path(pdf_directory).glob("*.pdf"):
551
try:
552
reader = PdfReader(str(pdf_path))
553
writer = PdfWriter()
554
555
# Add review annotation to first page
556
if reader.pages:
557
first_page = reader.pages[0]
558
559
review_note = Text(
560
rect=(50, 750, 150, 800), # Top-left corner
561
text=f"Reviewed by: {reviewer_name}",
562
icon="Key"
563
)
564
565
first_page.annotations.append(review_note)
566
writer.add_page(first_page)
567
568
# Copy remaining pages
569
for page in reader.pages[1:]:
570
writer.add_page(page)
571
572
# Save with "_reviewed" suffix
573
output_path = pdf_path.with_stem(f"{pdf_path.stem}_reviewed")
574
with open(output_path, "wb") as output:
575
writer.write(output)
576
577
print(f"Added review annotation to {pdf_path.name}")
578
579
except Exception as e:
580
print(f"Error processing {pdf_path.name}: {e}")
581
582
# Add review annotations to all PDFs
583
add_review_annotations("documents/", "John Reviewer")
584
```