CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pikepdf

Read and write PDFs with Python, powered by qpdf

Pending
Overview
Eval results
Files

forms.mddocs/

Forms and Annotations

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.

Capabilities

AcroForm Class

The AcroForm class provides comprehensive PDF form management including field creation, modification, and appearance generation.

class AcroForm:
    """
    PDF form (AcroForm) manager for interactive form handling.
    
    Provides access to form fields, appearance generation, and
    form-level operations for PDF documents.
    """
    
    @property
    def exists(self) -> bool:
        """
        Whether the PDF contains an interactive form.
        
        Returns:
        bool: True if the PDF has an AcroForm dictionary
        """
    
    @property
    def fields(self) -> list[AcroFormField]:
        """
        List of all form fields in the PDF.
        
        Returns:
        list[AcroFormField]: All form fields including nested fields
        """
    
    @property
    def needs_appearances(self) -> bool:
        """
        Whether form field appearances need to be generated.
        
        When True, PDF viewers should generate field appearances.
        When False, appearances are already present in the PDF.
        
        Returns:
        bool: Current NeedAppearances flag state
        """
    
    @needs_appearances.setter
    def needs_appearances(self, value: bool) -> None:
        """
        Set whether form field appearances need to be generated.
        
        Parameters:
        - value (bool): New NeedAppearances flag value
        """
    
    def add_field(self, field: AcroFormField) -> None:
        """
        Add a form field to the PDF.
        
        Parameters:
        - field (AcroFormField): Field to add to the form
        
        Raises:
        ValueError: If field is invalid or conflicts with existing fields
        """
    
    def remove_fields(self, names: list[str]) -> None:
        """
        Remove form fields by their fully qualified names.
        
        Parameters:
        - names (list[str]): List of field names to remove
        
        Raises:
        KeyError: If any specified field name is not found
        """
    
    def generate_appearances_if_needed(self) -> None:
        """
        Generate appearances for fields that need them.
        
        This method should be called before saving if any field
        values have been modified programmatically.
        """

AcroFormField Class

Individual form field objects with type-specific operations and value management.

class AcroFormField:
    """
    Individual PDF form field with type-specific operations.
    
    Represents a single form field such as text field, checkbox,
    radio button, choice field, or signature field.
    """
    
    @property
    def field_type(self) -> str:
        """
        The form field type.
        
        Returns:
        str: Field type ('Tx' for text, 'Btn' for button, 'Ch' for choice, 'Sig' for signature)
        """
    
    @property
    def fully_qualified_name(self) -> str:
        """
        The field's fully qualified name including parent hierarchy.
        
        Returns:
        str: Complete field name path (e.g., 'form.section.fieldname')
        """
    
    @property
    def partial_name(self) -> str:
        """
        The field's partial name (not including parent hierarchy).
        
        Returns:
        str: Field's own name without parent path
        """
    
    @property
    def value(self) -> Object:
        """
        The field's current value.
        
        Returns:
        Object: Field value (type depends on field type)
        """
    
    @value.setter
    def value(self, new_value: Object) -> None:
        """
        Set the field's value.
        
        Parameters:
        - new_value (Object): New value for the field
        """
    
    @property
    def default_value(self) -> Object:
        """
        The field's default value.
        
        Returns:
        Object: Default value for reset operations
        """
    
    @property
    def is_text(self) -> bool:
        """
        Whether this is a text field.
        
        Returns:
        bool: True if field type is 'Tx' (text field)
        """
    
    @property
    def is_checkbox(self) -> bool:
        """
        Whether this is a checkbox field.
        
        Returns:
        bool: True if this is a checkbox button field
        """
    
    @property
    def is_radiobutton(self) -> bool:
        """
        Whether this is a radio button field.
        
        Returns:
        bool: True if this is a radio button field
        """
    
    @property
    def is_choice(self) -> bool:
        """
        Whether this is a choice field (list box or combo box).
        
        Returns:
        bool: True if field type is 'Ch' (choice field)
        """
    
    @property
    def is_signature(self) -> bool:
        """
        Whether this is a signature field.
        
        Returns:
        bool: True if field type is 'Sig' (signature field)
        """
    
    @property
    def flags(self) -> int:
        """
        Form field flags as a bitmask.
        
        Returns:
        int: Field flags (readonly, required, etc.)
        """
    
    @property
    def rect(self) -> Rectangle:
        """
        Field's bounding rectangle on the page.
        
        Returns:
        Rectangle: Field's position and size
        """
    
    def set_value(self, value) -> None:
        """
        Set the field's value with automatic type conversion.
        
        Parameters:
        - value: New value (automatically converted to appropriate PDF object type)
        """
    
    def generate_appearance(self) -> None:
        """
        Generate the field's appearance stream.
        
        Creates the visual representation of the field based on
        its current value and formatting properties.
        """

Annotation Class

PDF annotation objects for interactive elements and markup.

class Annotation(Object):
    """
    PDF annotation object for interactive elements and markup.
    
    Annotations include form fields, comments, highlights, links,
    and other interactive or markup elements.
    """
    
    @property
    def subtype(self) -> Name:
        """
        The annotation's subtype.
        
        Returns:
        Name: Annotation subtype (e.g., Name.Widget, Name.Link, Name.Text)
        """
    
    @property
    def rect(self) -> Rectangle:
        """
        The annotation's bounding rectangle.
        
        Returns:
        Rectangle: Position and size of the annotation
        """
    
    @property
    def flags(self) -> int:
        """
        Annotation flags as a bitmask.
        
        Returns:
        int: Flags controlling annotation behavior and appearance
        """
    
    @property
    def appearance_dict(self) -> Dictionary:
        """
        The annotation's appearance dictionary.
        
        Contains appearance streams for different annotation states.
        
        Returns:
        Dictionary: Appearance dictionary with normal, rollover, and down states
        """
    
    @property
    def contents(self) -> String:
        """
        The annotation's text content or description.
        
        Returns:
        String: Textual content associated with the annotation
        """
    
    @property
    def page(self) -> Page:
        """
        The page containing this annotation.
        
        Returns:
        Page: Page object where this annotation is placed
        """
    
    def get_appearance_stream(self, which: str = 'N') -> Stream:
        """
        Get an appearance stream for the annotation.
        
        Parameters:
        - which (str): Appearance state ('N' for normal, 'R' for rollover, 'D' for down)
        
        Returns:
        Stream: Appearance stream for the specified state
        
        Raises:
        KeyError: If the requested appearance state doesn't exist
        """
    
    def get_page_content_for_appearance(self) -> bytes:
        """
        Get page content suitable for use in appearance generation.
        
        Returns:
        bytes: Content stream data for appearance generation
        """

Form Field Flags

Enumeration of form field flags for controlling field behavior.

from enum import IntFlag

class FormFieldFlag(IntFlag):
    """Form field flags controlling field behavior and appearance."""
    
    readonly = ...  # Field is read-only
    required = ...  # Field is required
    noexport = ...  # Field value is not exported
    multiline = ...  # Text field allows multiple lines
    password = ...  # Text field is a password field
    notoggletooff = ...  # Radio button cannot be turned off
    radio = ...  # Button field is a radio button
    pushbutton = ...  # Button field is a push button
    combo = ...  # Choice field is a combo box
    edit = ...  # Choice field allows text editing
    sort = ...  # Choice field options should be sorted
    fileselect = ...  # Text field is for file selection
    multiselect = ...  # Choice field allows multiple selections
    donotspellcheck = ...  # Field content should not be spell-checked
    donotscroll = ...  # Text field should not scroll
    comb = ...  # Text field is a comb field
    richtext = ...  # Text field supports rich text formatting
    radios_in_unison = ...  # Radio buttons act in unison
    commit_on_sel_change = ...  # Commit value on selection change

Annotation Flags

Enumeration of annotation flags for controlling annotation behavior.

class AnnotationFlag(IntFlag):
    """Annotation flags controlling annotation behavior and appearance."""
    
    invisible = ...  # Annotation is invisible
    hidden = ...  # Annotation is hidden
    print_ = ...  # Annotation should be printed
    nozoom = ...  # Annotation should not scale with zoom
    norotate = ...  # Annotation should not rotate with page
    noview = ...  # Annotation should not be displayed
    readonly = ...  # Annotation is read-only
    locked = ...  # Annotation is locked
    togglenoview = ...  # Toggle view state on mouse click
    lockedcontents = ...  # Annotation contents are locked

Usage Examples

Working with Form Fields

import pikepdf

# Open a PDF with form fields
pdf = pikepdf.open('form_document.pdf')

# Access the form
form = pdf.acroform

if form.exists:
    print(f"Form has {len(form.fields)} fields")
    
    # Iterate through all fields
    for field in form.fields:
        name = field.fully_qualified_name
        field_type = field.field_type
        value = field.value
        
        print(f"Field '{name}' (type: {field_type}): {value}")
        
        # Check field properties
        if field.is_text:
            print(f"  Text field with value: {value}")
        elif field.is_checkbox:
            print(f"  Checkbox is {'checked' if value else 'unchecked'}")
        elif field.is_choice:
            print(f"  Choice field with selection: {value}")

pdf.close()

Modifying Form Field Values

import pikepdf

pdf = pikepdf.open('form_document.pdf')
form = pdf.acroform

if form.exists:
    for field in form.fields:
        name = field.fully_qualified_name
        
        # Set text field values
        if field.is_text and 'name' in name.lower():
            field.set_value("John Doe")
        elif field.is_text and 'email' in name.lower():
            field.set_value("john.doe@example.com")
        
        # Check/uncheck checkboxes
        elif field.is_checkbox and 'agree' in name.lower():
            field.set_value(True)  # Check the box
        
        # Set choice field selections
        elif field.is_choice and 'country' in name.lower():
            field.set_value("United States")
    
    # Generate field appearances after modification
    form.generate_appearances_if_needed()
    
    # Flatten form (make fields non-editable)
    # form.remove_fields([field.fully_qualified_name for field in form.fields])

pdf.save('filled_form.pdf')
pdf.close()

Creating New Form Fields

import pikepdf

pdf = pikepdf.open('document.pdf')
page = pdf.pages[0]

# Ensure the PDF has a form
if not pdf.acroform.exists:
    # Create form structure
    pdf.Root['/AcroForm'] = pikepdf.Dictionary({
        '/Fields': pikepdf.Array(),
        '/NeedAppearances': True
    })

# Create a text field
text_field = pikepdf.Dictionary({
    '/Type': pikepdf.Name.Annot,
    '/Subtype': pikepdf.Name.Widget,
    '/FT': pikepdf.Name.Tx,  # Text field
    '/T': pikepdf.String('username'),  # Field name
    '/V': pikepdf.String(''),  # Default value
    '/Rect': pikepdf.Array([100, 700, 300, 720]),  # Position and size
    '/P': page  # Parent page
})

# Create a checkbox field
checkbox_field = pikepdf.Dictionary({
    '/Type': pikepdf.Name.Annot,
    '/Subtype': pikepdf.Name.Widget,
    '/FT': pikepdf.Name.Btn,  # Button field
    '/Ff': 0,  # Not a radio button or push button (checkbox)
    '/T': pikepdf.String('subscribe'),
    '/V': pikepdf.Name.Off,  # Unchecked
    '/Rect': pikepdf.Array([100, 650, 120, 670]),
    '/P': page
})

# Add fields to form and page
pdf.Root['/AcroForm']['/Fields'].extend([text_field, checkbox_field])

# Add annotations to page
if '/Annots' not in page:
    page['/Annots'] = pikepdf.Array()
page['/Annots'].extend([text_field, checkbox_field])

pdf.save('document_with_form.pdf')
pdf.close()

Working with Annotations

import pikepdf

pdf = pikepdf.open('document.pdf')
page = pdf.pages[0]

# Check if page has annotations
if '/Annots' in page:
    annotations = page['/Annots']
    
    for annot_ref in annotations:
        annot = annot_ref  # Resolve if indirect
        
        # Check annotation type
        subtype = annot.get('/Subtype')
        
        if subtype == pikepdf.Name.Link:
            # Handle link annotation
            rect = annot['/Rect']
            action = annot.get('/A')
            print(f"Link at {rect}: {action}")
            
        elif subtype == pikepdf.Name.Text:
            # Handle text annotation (note/comment)
            contents = annot.get('/Contents', '')
            print(f"Note: {contents}")
            
        elif subtype == pikepdf.Name.Widget:
            # Handle form field widget
            field_name = annot.get('/T', 'unnamed')
            field_type = annot.get('/FT')
            print(f"Form field '{field_name}' of type {field_type}")

pdf.close()

Form Field Validation and Processing

import pikepdf
import re

pdf = pikepdf.open('form_document.pdf')
form = pdf.acroform

def validate_email(email):
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(pattern, email) is not None

if form.exists:
    errors = []
    
    for field in form.fields:
        name = field.fully_qualified_name
        value = str(field.value) if field.value else ""
        
        # Check required fields
        if field.flags & pikepdf.FormFieldFlag.required:
            if not value.strip():
                errors.append(f"Required field '{name}' is empty")
        
        # Validate email fields
        if 'email' in name.lower() and value:
            if not validate_email(value):
                errors.append(f"Invalid email in field '{name}': {value}")
        
        # Check text field length limits
        if field.is_text and hasattr(field, 'max_length'):
            if len(value) > field.max_length:
                errors.append(f"Field '{name}' exceeds maximum length")
    
    if errors:
        print("Form validation errors:")
        for error in errors:
            print(f"  - {error}")
    else:
        print("Form validation passed")

pdf.close()

Advanced Form Operations

import pikepdf

pdf = pikepdf.open('complex_form.pdf')
form = pdf.acroform

# Group fields by type
field_groups = {
    'text': [],
    'checkbox': [],
    'radio': [],
    'choice': [],
    'signature': []
}

if form.exists:
    for field in form.fields:
        if field.is_text:
            field_groups['text'].append(field)
        elif field.is_checkbox:
            field_groups['checkbox'].append(field)
        elif field.is_radiobutton:
            field_groups['radio'].append(field)
        elif field.is_choice:
            field_groups['choice'].append(field)
        elif field.is_signature:
            field_groups['signature'].append(field)
    
    # Report field statistics
    for field_type, fields in field_groups.items():
        print(f"{field_type.capitalize()} fields: {len(fields)}")
        for field in fields:
            print(f"  - {field.fully_qualified_name}")
    
    # Batch operations
    # Make all text fields read-only
    for field in field_groups['text']:
        field.flags |= pikepdf.FormFieldFlag.readonly
    
    # Clear all checkbox values
    for field in field_groups['checkbox']:
        field.set_value(False)
    
    # Set default selections for choice fields
    for field in field_groups['choice']:
        if hasattr(field, 'options') and field.options:
            field.set_value(field.options[0])  # Select first option
    
    # Generate appearances after modifications
    form.generate_appearances_if_needed()

pdf.save('processed_form.pdf')
pdf.close()

Install with Tessl CLI

npx tessl i tessl/pypi-pikepdf

docs

advanced.md

attachments.md

content-streams.md

core-operations.md

encryption.md

forms.md

images.md

index.md

metadata.md

objects.md

outlines.md

pages.md

tile.json