Document
class paperjam.Document
A PDF document with lazy page loading. All manipulation methods return a new Document — the original is never modified.
Use as a context manager for automatic resource cleanup, or let the garbage collector free resources:
# Context manager (recommended)
with paperjam.open("file.pdf") as doc:
print(doc.page_count)
# Without context manager
doc = paperjam.open("file.pdf")
print(doc.page_count)
doc.close()
Properties
page_count
doc.page_count: int
Total number of pages in the document.
pages
doc.pages # _PageAccessor
Access pages by 0-based index (like a list) or iterate. Supports slices.
first = doc.pages[0]
last = doc.pages[-1]
subset = doc.pages[1:5]
for page in doc.pages:
...
metadata
doc.metadata: Metadata
Document metadata as a frozen Metadata dataclass. See types reference.
bookmarks
doc.bookmarks: list[Bookmark]
Nested bookmark tree. Top-level entries are returned; each Bookmark has a children tuple.
has_form
doc.has_form: bool
True if the document contains an AcroForm dictionary.
form_fields
doc.form_fields: list[FormField]
All form fields extracted from the AcroForm.
signatures
doc.signatures: list[SignatureInfo]
All digital signatures found in the document. Requires the signatures feature.
Saving
save
doc.save(path: str | os.PathLike) -> None
Save the document to a file.
save_bytes
doc.save_bytes() -> bytes
Serialize the document to bytes in memory.
close
doc.close() -> None
Explicitly release resources. Called automatically when used as a context manager.
Text and content extraction
extract_structure
doc.extract_structure(
*,
heading_size_ratio: float = 1.2,
detect_lists: bool = True,
include_tables: bool = True,
layout_aware: bool = False,
) -> list[ContentBlock]
Extract structured content (headings, paragraphs, list items, tables) from all pages.
extract_tables
doc.extract_tables(
*,
strategy: TableStrategy | str = TableStrategy.AUTO,
min_rows: int = 2,
min_cols: int = 2,
snap_tolerance: float = 3.0,
row_tolerance: float = 0.5,
min_col_gap: float = 10.0,
) -> list[Table]
Extract tables from all pages.
extract_links
doc.extract_links() -> list[Link]
Extract all hyperlinks from all pages.
search
doc.search(
query: str,
*,
case_sensitive: bool = True,
max_results: int = 0,
use_regex: bool = False,
) -> list[SearchResult]
Search for text across all pages. max_results=0 means unlimited.
to_markdown
doc.to_markdown(
*,
heading_offset: int = 0,
include_page_numbers: bool = False,
html_tables: bool = False,
layout_aware: bool = False,
# ... additional formatting options
) -> str
Convert the entire document to Markdown.
Metadata and bookmarks
set_metadata
doc.set_metadata(
*,
title: str | None = ...,
author: str | None = ...,
subject: str | None = ...,
keywords: str | None = ...,
creator: str | None = ...,
producer: str | None = ...,
) -> Document
Update document metadata. Pass a string to set a field, None to remove it, or omit it to leave it unchanged. Returns a new Document.
set_bookmarks
doc.set_bookmarks(bookmarks: list[Bookmark]) -> Document
Replace the entire bookmark tree. Pass an empty list to remove all bookmarks. Returns a new Document.
generate_toc
doc.generate_toc(
*,
max_depth: int = 3,
heading_size_ratio: float = 1.2,
layout_aware: bool = False,
replace_existing: bool = True,
) -> tuple[Document, list[Bookmark]]
Auto-generate a table of contents from the document's heading structure. Returns (new_document, bookmarks).
Page manipulation
split
doc.split(ranges: list[tuple[int, int]]) -> list[Document]
Split into multiple documents by page ranges. Ranges are 1-indexed and inclusive on both ends.
split_pages
doc.split_pages() -> list[Document]
Split into individual single-page documents.
reorder
doc.reorder(page_order: list[int]) -> Document
Reorder pages. page_order is a list of 1-indexed page numbers in the desired output order. You may repeat page numbers (to duplicate) or omit them (to delete). Returns a new Document.
rotate
doc.rotate(page_rotations: list[tuple[int, Rotation | int]]) -> Document
Rotate pages. Each tuple is (page_number, angle) where page numbers are 1-indexed and angle is degrees or a Rotation enum value. Returns a new Document.
delete_pages
doc.delete_pages(page_numbers: list[int]) -> Document
Delete pages by 1-indexed page number. At least one page must remain. Returns a new Document.
insert_blank_pages
doc.insert_blank_pages(
positions: list[tuple[int, float, float]],
) -> Document
Insert blank pages. Each tuple is (after_page, width_pt, height_pt). after_page=0 inserts before page 1. Returns a new Document.
stamp
doc.stamp(
stamp_doc: Document,
*,
source_page: int = 1,
target_pages: list[int] | None = None,
x: float = 0.0,
y: float = 0.0,
scale: float = 1.0,
opacity: float = 1.0,
layer: str = "over",
) -> Document
Overlay a page from another PDF onto pages of this document. layer is "over" or "under". Returns a new Document.
Annotations
add_annotation
doc.add_annotation(
page: int,
annotation_type: AnnotationType | str,
rect: tuple[float, float, float, float],
*,
contents: str | None = None,
author: str | None = None,
color: tuple[float, float, float] | None = None,
opacity: float | None = None,
url: str | None = None,
) -> Document
Add an annotation to a page. Returns a new Document.
add_watermark
doc.add_watermark(
text: str,
*,
font_size: float = 60.0,
rotation: float = 45.0,
opacity: float = 0.3,
color: tuple[float, float, float] = (0.5, 0.5, 0.5),
position: WatermarkPosition | str = WatermarkPosition.CENTER,
layer: WatermarkLayer | str = WatermarkLayer.OVER,
pages: list[int] | None = None,
x: float | None = None,
y: float | None = None,
) -> Document
Add a text watermark to pages. Returns a new Document.
remove_annotations
doc.remove_annotations(
page: int,
*,
annotation_types: list[AnnotationType | str] | None = None,
indices: list[int] | None = None,
) -> tuple[Document, int]
Remove annotations from a page. Returns (new_document, count_removed).
Security
sanitize
doc.sanitize(
*,
remove_javascript: bool = True,
remove_embedded_files: bool = True,
remove_actions: bool = True,
remove_links: bool = True,
) -> tuple[Document, SanitizeResult]
Remove potentially dangerous content. Returns (sanitized_document, result_stats).
redact
doc.redact(
regions: list[RedactRegion],
*,
fill_color: tuple[float, float, float] | None = None,
) -> tuple[Document, RedactResult]
Redact specific rectangular regions from the content stream. Returns (redacted_document, result_stats).
redact_text
doc.redact_text(
query: str,
*,
case_sensitive: bool = True,
use_regex: bool = False,
fill_color: tuple[float, float, float] | None = None,
) -> tuple[Document, RedactResult]
Find and redact all occurrences of a text query. Returns (redacted_document, result_stats).
encrypt
doc.encrypt(
*,
user_password: str,
owner_password: str | None = None,
permissions: Permissions | None = None,
algorithm: str = "aes128",
) -> tuple[bytes, EncryptResult]
Encrypt the document. Returns (encrypted_bytes, result) — note the first element is bytes, not a Document. algorithm can be "aes128" (default), "aes256", or "rc4".
validate_pdf_a
doc.validate_pdf_a(level: str = "1b") -> ValidationReport
Validate PDF/A compliance. level is "1b", "1a", or "2b".
convert_to_pdf_a
doc.convert_to_pdf_a(
*,
level: str = "1b",
force: bool = False,
) -> tuple[Document, ConversionResult]
Convert the document to PDF/A conformance. Returns (converted_document, result). Set force=True to proceed even when some issues (like unembedded fonts) cannot be fixed automatically.
validate_pdf_ua
doc.validate_pdf_ua(level: str = "1") -> PdfUaReport
Validate PDF/UA accessibility compliance. Checks structure tree, alt text, language, tagged content, and more.
Forms
fill_form
doc.fill_form(
values: dict[str, str],
*,
generate_appearances: bool = False,
) -> tuple[Document, FillFormResult]
Fill form fields by name. Returns (new_document, result).
add_form_field
doc.add_form_field(
name: str,
field_type: str,
*,
page: int = 1,
rect: tuple[float, float, float, float],
# ... many optional kwargs
) -> tuple[Document, CreateFieldResult]
Create a new form field. Returns (new_document, result).
modify_form_field
doc.modify_form_field(
field_name: str,
*,
value: str | None = None,
read_only: bool | None = None,
required: bool | None = None,
max_length: int | None = None,
options: list[ChoiceOption] | None = None,
) -> tuple[Document, ModifyFieldResult]
Modify an existing form field. Returns (new_document, result).
Rendering
render_page
doc.render_page(
page_number: int,
*,
dpi: float = 150,
format: str = "png",
quality: int = 85,
background_color: tuple[int, int, int] | None = None,
scale_to_width: int | None = None,
scale_to_height: int | None = None,
) -> RenderedImage
Render a single page to an image. Requires the render feature.
render_pages
doc.render_pages(
*,
pages: list[int] | None = None,
dpi: float = 150,
format: str = "png",
quality: int = 85,
background_color: tuple[int, int, int] | None = None,
) -> list[RenderedImage]
Render multiple (or all) pages. pages=None renders all pages.
Comparison
diff
doc.diff(other: Document) -> DiffResult
Compare this document to another at the text level.
visual_diff
doc.visual_diff(
other: Document,
*,
dpi: float = 150,
highlight_color: tuple[int, int, int, int] | None = None,
mode: str = "both",
threshold: int = 10,
) -> VisualDiffResult
Compare this document to another visually (pixel-level). Requires the render feature.
Signatures
verify_signatures
doc.verify_signatures() -> list[SignatureValidity]
Verify all digital signatures. Returns a list of SignatureValidity results.
sign
doc.sign(
*,
private_key: bytes,
certificates: list[bytes],
reason: str | None = None,
location: str | None = None,
contact_info: str | None = None,
field_name: str = "Signature1",
tsa_url: str | None = None,
timestamp_token: bytes | None = None,
ocsp_responses: list[bytes] | None = None,
crls: list[bytes] | None = None,
) -> bytes
Sign the document. Returns the signed PDF as bytes. Pass tsa_url to add an RFC 3161 timestamp for long-term validation (LTV).
Async methods
See the Async guide for usage. Async versions use the a prefix: asave, asave_bytes, arender_page, arender_pages, aextract_tables, ato_markdown, asearch, adiff, aredact_text.