or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced.mdattachments.mdcontent-streams.mdcore-operations.mdencryption.mdforms.mdimages.mdindex.mdmetadata.mdobjects.mdoutlines.mdpages.md

forms.mddocs/

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

```