or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations-forms.mddocument-creation-modification.mddocument-operations.mddocument-rendering.mdgeometry-transformations.mdindex.mdpage-content-extraction.mdtable-extraction.md

annotations-forms.mddocs/

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

```