Python API reference
The pdfboss package re-exports the compiled extension module pdfboss._pdfboss. Its public API is the document, page, layout and iteration classes, thirteen classes for the interactive form and the catalog's document-level structures, the md and write submodules and the __version__ string; the typed stubs in _pdfboss.pyi are the authoritative reference for every signature and docstring. This chapter is the inventory; worked examples live in the guide chapters.
The document classes
| Name | What it is |
|---|---|
Document | A loaded PDF, from a path or bytes; pages by index, the metadata property, extract_text, extract_markdown, layout, render_pages, elements, spans, plus the form, catalog and document structure readers below |
Page | One page: geometry (width/height/rotation and the five boxes), extract_text, extract_markdown, layout, spans, images, render, render_reporting, extract_images, plus piece_info, thumbnail, thumbnail_image, beads, presentation, viewports, separation_info |
ReadingOrder | CONTENT, STRUCTURE_TREE, GEOMETRIC: the reading_order keyword every extraction method takes, as the enum or its string value |
AsyncDocument | The async twin of Document, opened from a path, bytes, or an HTTP URL via range requests; data-fetching methods are coroutines |
AsyncPage | The async twin of Page; attributes are synchronous, extraction and rendering are coroutines |
Element | One physical or logical element of a PDF (kind, span, ref, page, lazy value()), yielded by elements |
ElementIter | Lazy sync iterator over elements; each step releases the GIL |
AsyncElementIter | Async iterator over elements; each step is a coroutine |
Span | One styled text span: text, position, bbox with ascent/descent, font identity, bold/italic/monospace/serif, font family/stretch/weight, underline/strikethrough/highlight with highlight color, rise, vertical, invisible, color |
SpanIter | Lazy sync iterator over a document's spans, buffering one page at a time |
AsyncSpanIter | Async iterator over a document's spans |
PageImage | One embedded image extracted from a page: native width/height and PNG-encoded data |
PlacedImage | One image a page draws, where it is drawn: page, device-space bbox, native width/height, stencil, inline; yielded by Page.images without decoding pixels |
LayoutBlock | One block of a page's layout from layout: kind (heading, paragraph, list, table), heading level, paragraph role, bbox in PDF user space, plain text, list items, table rows |
LayoutListItem | One list item of a LayoutBlock: marker, text, bbox |
LayoutCell | One table cell of a LayoutBlock: text, colspan, rowspan |
PdfError | The exception type for any PDF processing error |
Document.metadata is a property returning the document information
dictionary as a dict[str, str], only keys present in the file included;
AsyncDocument.metadata() is a coroutine yielding the same mapping.
AsyncDocument also has page(index) (synchronous, 0-based, no negative
indexes; subscription doc[i] accepts them) and get_object(num, gen=0), a
coroutine fetching one indirect object and returning it through the same
plain-Python conversion as Element.value().
The form and catalog classes
Ten Document methods read the interactive form and the catalog's document-level structures, each with an AsyncDocument coroutine of the same name and result: interactive_form(), form_fields(), outline(), named_destinations(), page_labels(), page_label(index), embedded_files(), embedded_file_data(file), viewer_preferences() and extensions(). They return frozen instances of the classes below; every enumeration is a kebab-case string ("check-box", "roman-upper", "fit-r"), every object reference a (num, gen) tuple like Element.ref, and a field's value plain Python data through the Element.value() conversion. form_fields, outline, named_destinations and embedded_file_data release the GIL while they read.
| Name | What it is |
|---|---|
InteractiveForm | The form dictionary: root fields, need_appearances, signatures_exist, append_only, calculation_order, default_resources, default_appearance, quadding, xfa |
FormField | One field with inherited entries filled in: ref, parent, kids, widgets, field_type, name (fully qualified), partial_name, alternate_name, mapping_name, flags, value, default_value, default_appearance, quadding, default_style, rich_text, max_len, options, top_index, selected_indices, additional_actions, lock, seed_value; the typed readers text, button_kind, state, checked, on_widgets, signature, selected |
FieldFlags | The flag word as bits, the set flags' names, and one boolean per flag (read_only, required, multiline, combo, radio, pushbutton, …) |
Widget | One widget annotation of a field: ref, appearance_state, on_state, characteristics |
AppearanceCharacteristics | A button widget's captions and icons: caption, rollover_caption, alternate_caption, icon, rollover_icon, alternate_icon, caption_position |
ChoiceOption | One choice option or button export value: export_value, name |
Signature | A signature dictionary read as data, nothing verified: filter, sub_filter, byte_range, contents, name, signing_time, location, reason, contact_info |
DefaultAppearance | A parsed default appearance string, from DefaultAppearance.parse(da): font, font_size, fill_color |
OutlineItem | One bookmark: title, destination, page, open, color, italic, bold, structure_element, nested children |
Destination | A page and how it is shown: page (0-based, resolved through the page tree), page_ref, fit, left, top, right, bottom, zoom |
PageLabel | One page-numbering range: first_page, style, prefix, start_at, label(index); write.PageLabel takes the same fields |
EmbeddedFile | One attachment: name, file_name, description, file_system, volatile, ref, mime, size, created, modified (ISO 8601 strings), checksum |
ViewerPreferences | The catalog's viewer preferences with the standard's defaults filled in: the five hide/fit/center flags, display_doc_title, non_full_screen_page_mode, direction, the four view and print boxes, print_scaling, duplex, pick_tray_by_pdf_size, print_page_range, num_copies |
DeveloperExtension | One catalog extension: prefix, base_version, extension_level |
import pdfboss
doc = pdfboss.Document("form.pdf")
for field in doc.form_fields():
if field.field_type == "text":
print(field.name, field.text)
elif field.button_kind == "check-box":
print(field.name, field.checked)
elif field.field_type == "choice":
print(field.name, field.selected)
for item in doc.outline():
print(item.title, item.page)
for attachment in doc.embedded_files():
open(attachment.name, "wb").write(doc.embedded_file_data(attachment))
Guide chapters with runnable examples: Extracting text, Markdown output, Styled spans, Rendering pages, Extracting images, Reading forms, bookmarks and attachments, Creating PDFs, Markdown to PDF, Async and remote documents, Encrypted documents, Editing PDFs, Assembling documents.
The document and page structure classes
Nine more Document methods read structures beyond the catalog readers above: linearization(), is_linearized(), output_intents(), optional_content_groups(event), piece_info(), articles(), permission_handlers(), requirements() and legal_attestation(). AsyncDocument has each under the same name; the first two are plain calls there too, since the file head is read at open, the rest are coroutines. Ten Page methods read a page's own structures, each with an AsyncPage coroutine twin: piece_info(), thumbnail(), thumbnail_image(compression), beads(), presentation(), viewports(), separation_info(), annotations(), additional_actions() and content_items(). Document.additional_actions() reads the catalog's trigger events and Document.file_spec_data(spec) decodes the file a FileSpec embeds, both with AsyncDocument twins. The conventions above hold: enumerations are kebab-case strings, object references (num, gen) tuples, dates ISO 8601 strings, and every reader that resolves objects releases the GIL.
| Name | What it is |
|---|---|
Linearization | The linearization parameter dictionary as written: version, file_length, hint_streams, first_page_object, first_page_end, page_count, main_xref_offset, first_page; Document.is_linearized() applies the length rule |
OutputIntent | One output intent: subtype, output_condition, output_condition_identifier, registry_name, info, destination_profile (a reference) |
OptionalContentGroup | One optional content group (a PDF layer): ref, name, intent, usage, visible under the event Document.optional_content_groups(event) was asked for: "view" (the default, also what extraction and rendering use), "print", "export", or None for the default configuration alone |
OptionalContentUsage | A group's usage dictionary: view, print, print_subtype, export, zoom_min, zoom_max, language, language_preferred, page_element, creator, creator_subtype, user_type, user_names |
PagePiece | One product's entry in a page-piece dictionary: product, last_modified (ISO 8601), private (plain Python data) |
Thumbnail | A page's thumbnail as written: width, height, bits_per_component, color_space, decode; Page.thumbnail_image() decodes it to a PageImage |
ArticleThread | One article thread: ref, info (the keys of Document.metadata), beads in reading order |
Bead | One bead: ref, page (0-based, resolved through the page tree), page_ref, rect |
Presentation | A page's display in a presentation: duration, transition |
Transition | A page transition with the standard's defaults filled in: style, duration, dimension, motion, direction (degrees or None), scale, opaque |
PermissionHandlers | The catalog's certifying and usage rights signatures, read as data: doc_mdp, usage_rights (each a Signature or None) |
Requirement | One entry of the catalog's requirements array, a feature a reader needs to show the document as intended: kind ("EnableJavaScripts" or the name as written), handlers |
RequirementHandler | One handler for a reader that lacks the feature, reported and never run: kind ("JS", "NoOp" or the name as written), script |
LegalAttestation | The catalog's legal attestation: one integer attribute per count in Table 259 (java_script_actions, uri_actions, non_embedded_fonts, ...), attestation, and counts() for the whole table as a dict |
Viewport | One measurement viewport of a page: bbox, name, measure |
Measure | How a viewport's coordinates convert to real-world units, with the standard's defaults filled in: subtype, scale_ratio, the number-format chains x, y, distance, area, angle, slope, plus origin and y_to_x |
NumberFormat | One unit of a chain: unit, conversion, fraction ("decimal", "fraction", "round", "truncate"), precision, fixed_denominator, thousands, radix, prefix_spacing, suffix_spacing, label ("suffix" or "prefix") |
SeparationInfo | A pre-separated page's separation dictionary: pages (0-based, None where the reference names no page), page_refs, device_colorant, color_space (plain Python data) |
Annotation | One annotation of a page, Table 164 read as data: ref, subtype, rect, contents, name, modified and modified_date, flags, has_appearance, appearance_state, border, color, struct_parent, optional_content; then markup, state and state_model, open, icon, parent, file, destination (a link's /Dest), action and additional_actions |
AnnotationFlags | The /F word with one boolean per flag: invisible, hidden, print, no_zoom, no_rotate, no_view, read_only, locked, toggle_no_view, locked_contents, plus value |
Border | The /Border array: horizontal_radius, vertical_radius, width, dash |
Markup | The Table 170 entries of a markup annotation: title, popup, opacity, rich_contents, created, in_reply_to, subject, reply_type ("reply" or "group"), intent |
FileSpec | A file specification: name, file, unicode_file, description, file_system, url (for /FS /URL), volatile, ref (the embedded stream, decoded by Document.file_spec_data) |
Action | One action dictionary, never executed: kind (the /S name as written), destination and named_destination for the go-to family, file and new_window, target for an embedded go-to, windows for a launch, uri and is_map, name for a named action, script for JavaScript, entries (the whole dictionary) for any other kind, and next (the /Next chain) |
Target | One step of an embedded go-to action's path: relationship, name, page, annotation, next |
WindowsLaunch | A launch action's Windows parameters: file, directory, operation, parameters |
TriggeredAction | One entry of an additional-actions dictionary: trigger (kebab-case, "cursor-enter", "page-open", "will-close" and the rest of Tables 194 to 197) and action |
ContentItem | One content item of a page in structure-tree order (14.7.4), returned by Page.content_items(): kind ("sequence" or "object"), mcid and struct_parents for a marked-content sequence, ref for an annotation the tree holds through an object reference (14.7.4.3), rank, the holding element's structure_type, mapped_type, standard_type and path, plus alt, lang, expansion |
import pdfboss
doc = pdfboss.Document("slides.pdf")
print(doc.is_linearized(), [intent.subtype for intent in doc.output_intents()])
for thread in doc.articles():
print(thread.info.get("title"), [(bead.page, bead.rect) for bead in thread.beads])
page = doc[0]
shown = page.presentation()
if shown and shown.transition:
print(shown.duration, shown.transition.style)
thumb = page.thumbnail_image()
if thumb:
open("thumb.png", "wb").write(thumb.data)
for viewport in page.viewports():
scale = viewport.measure
if scale:
print(viewport.name, scale.scale_ratio, [f.unit for f in scale.distance])
separation = page.separation_info()
if separation:
print(separation.device_colorant, separation.pages)
for requirement in doc.requirements():
print(requirement.kind, [handler.kind for handler in requirement.handlers])
legal = doc.legal_attestation()
if legal:
print(legal.attestation, legal.counts())
for annotation in page.annotations():
action = annotation.action
if annotation.subtype == "Link" and action and action.kind == "URI":
print(annotation.rect, action.uri)
elif annotation.destination:
print(annotation.rect, "-> page", annotation.destination.page)
if annotation.markup:
print(annotation.markup.title, annotation.contents, annotation.state)
if annotation.file and annotation.file.ref:
open(annotation.file.name, "wb").write(doc.file_spec_data(annotation.file))
for triggered in page.additional_actions() + doc.additional_actions():
print(triggered.trigger, triggered.action.kind)
for item in page.content_items():
print(item.rank, item.standard_type, item.ref if item.kind == "object" else item.mcid)
The md submodule
pdfboss.md.to_pdf(markdown, theme=None, size="a4", landscape=False, base_dir=None) composes CommonMark+GFM source into a themed PDF and returns the file bytes. theme is CSS source text, not a path; size is a page size name (a0 to a10, b0 to b10, letter, legal, tabloid) or <width>x<height> in pt, mm, cm or in, and an unknown one raises PdfError; replaced characters and skipped raw HTML surface as one UserWarning. Details and examples in Markdown to PDF.
Canvas-level and element-level creation from Python is the write submodule below.
The write submodule
pdfboss.write composes new PDFs from frozen values joined with |; the same vocabulary is the Rust crate pdfboss-write and the pdfboss create CLI, and worked examples live in Creating PDFs. Fourteen classes:
| Name | What it is |
|---|---|
Pdf | A document under construction; composes pages, attachments and page labels (appended) and the singleton Metadata, Outline and Viewer slots; save(path) and to_bytes() serialize |
Page | One page (size names a size case-insensitively, default "a4"; landscape swaps width and height); composes Text, Image, Link, Paragraph or any draw object |
Text | One line of text: value, at=(x, y) baseline origin, font (default Standard14.HELVETICA), size (default 12.0), optional (r, g, b) color |
Paragraph | Wrapped, aligned text: text, rect, font, size (default 11.0), leading (default derived from size), align of left, center, right or justify |
Image | A placed raster: data as a path string or bytes, at, optional width/height; either source is read and decoded at save/to_bytes time |
Link | A clickable rectangle: rect plus exactly one of url or page (0-based) |
Bookmark | One outline entry: title, page, keyword-only children; nests by construction |
Outline | The bookmark panel, Outline(*bookmarks); a singleton Pdf slot |
Attachment | An embedded file: name, data, optional mime and description; carries no dates |
PageLabel | One page-numbering range: first_page (0-based), optional style (decimal, roman-upper, roman-lower, letters-upper, letters-lower), prefix, start_at (default 1) |
Viewer | Opening preferences: layout, mode, open_to; a singleton Pdf slot |
Metadata | /Info text fields: title, author, subject, keywords, creator, producer; a singleton Pdf slot |
Standard14 | The fourteen standard faces as SCREAMING_SNAKE members: HELVETICA, TIMES_BOLD_ITALIC, ZAPF_DINGBATS, … |
Canvas | The painting surface handed to a draw object's draw method; it has no public constructor |
Every | returns a new value and leaves the receiver unchanged; copies are cheap handle clones, and nothing is built until save or to_bytes, which lower the composition once under the GIL and release it to serialize. to_bytes may be called repeatedly, since the composed value is never consumed.
The draw protocol is structural: any object with a callable draw attribute composes onto a Page, and its draw(canvas) receives a Canvas with twelve methods (text, line, rect, move_to, line_to, curve_to, close, stroke, fill, set_fill, set_stroke, set_line_width) that paints in content order. The stub declares a Draw protocol type for checkers only; there is no runtime Draw class to import or inherit. The canvas is only usable inside the call, and every method raises PdfError once draw has returned.
Two more names work on existing files, neither in the table above since neither composes with |: watermark(data, overlay, *, rewrite=False, under=False) takes two PDFs as bytes and returns data with the first page of overlay drawn over every page, as an incremental update appended to data's bytes, or with rewrite=True as a fresh, compressed file; under=True draws the overlay beneath each page's own content instead of over it (see Watermarking an existing file); Update(doc) stages a metadata edit over an existing Document, with set_metadata(title=None, author=None, subject=None, keywords=None, creator=None, producer=None) merging the given fields and save(path) or to_bytes() writing the result as an incremental update (see Editing PDFs). Both release the GIL while they run. Update always raises PdfError for an encrypted base, whether or not it opens. watermark's default (rewrite=False, appending) does the same for an encrypted data or overlay; with rewrite=True, only a data or overlay that cannot be opened at all raises PdfError, and one that opens under its password, including the empty user password, copies across as plain, unencrypted bytes instead.
Six more functions assemble documents from existing file bytes, also outside the table since none composes with | (see Assembling documents): merge(inputs) gathers each item, raw bytes for every page or a (bytes, list[int]) tuple for specific 0-based pages, into one fresh document in argument order; split(data, every) cuts data into consecutive parts of every pages, the last part carrying whatever remains; split_parts(data, every, *, password="") yields the same parts one at a time as a SplitIterator iterator usable with both for and async for; split_stream(stream, every, *, password="") does the same for a PDF arriving as an async iterable of bytes chunks, collecting the chunks before the first part is built (see One part at a time); rotate(data, by, pages=None, rewrite=False) turns pages (0-based, every page when omitted) by by degrees clockwise, restricted to 90, 180 or 270 (else ValueError), appending an incremental update by default or writing a fresh file with rewrite=True; rewrite(data) writes data fresh, recompressed with unreachable objects and earlier update sections left behind. every below 1 raises ValueError from all three split functions at the call. merge, the split functions and rewrite always produce fresh files; only rotate can append instead, the same choice Update/meta offers. An input that cannot be opened at all (encrypted with no working password) raises PdfError from merge, split, rotate and rewrite immediately, before any assembly work begins; split_parts and split_stream parse lazily, so they raise it from the first part instead, split_stream after the whole stream has been read. One that opens under its password, including the empty user password, passes through merge, split and rewrite as plain, unencrypted bytes, through split_parts and split_stream the same way when opened with their password, and through rotate the same way when rewrite=True; rotate's default appending mode still raises PdfError for any encrypted input, opened or not. An owner-password-only file therefore loses its /P restrictions the moment its holder runs it through rewrite (or merge, a split function, or rotate(rewrite=True)): the freshly written output carries no encryption at all, restrictions included. rotate also refuses, in either mode, a page inlined directly into /Kids with no object of its own, since pdfboss does not yet restructure such a page to rotate it.
Two more functions protect or remove protection from a document's bytes, also outside the table above (see Encrypted PDFs): encrypt(data, *, user_password="", owner_password="", allow=None) AES-256, revision-6 protects data and returns the fresh file, restricted by allow, a list drawn from print, modify, copy, annotate, fill-forms, accessibility, assemble and print-hires (every permission when omitted); an unknown allow value raises ValueError naming it, and so does leaving both user_password and owner_password empty, while an empty owner_password alone falls back to user_password. decrypt(data, *, password="") opens data under its user or owner password and returns a fresh, unencrypted file; a wrong or missing password raises PdfError. Both release the GIL around the actual work.
Error handling
Everything raises PdfError: bad or truncated data, unsupported encryption, stream decode failures and I/O errors, with the underlying detail in the message. Messages from the element and async APIs are prefixed by the layer they came from: "parse: …", "io: …" or "http: …".
import pdfboss
try:
doc = pdfboss.Document("report.pdf")
except pdfboss.PdfError as e:
print(f"could not open: {e}")
Two conventional exceptions apply where Python conventions demand them: constructing a Document with neither or both of path and data raises ValueError (as does a non-positive scale, an unknown fonts=, compression= or format= string, a quality= outside 1 to 100, or an unusable fonts="full" setup in render), and an out-of-range page index raises IndexError. Element iterators have salvage semantics: a per-item failure raises PdfError for that item, and iteration may be continued. Span iterators raise PdfError when a page cannot be materialized.
The write submodule splits its failures by phase. TypeError is raised at construction for a Link with neither or both of url and page, an unknown align, page-label style, viewer layout or mode, or Image data that is neither str nor bytes; and at composition for an unsupported | operand or a second Metadata, Outline or Viewer. Everything that fails while lowering (an unreadable or undecodable image file, a paragraph overflowing its rect, an unencodable character, a target page out of range) raises PdfError from save/to_bytes. A draw object's Canvas stops working the moment its draw call returns: any later method call on it raises PdfError at that call site. An exception raised inside draw() itself propagates from save/to_bytes exactly as the Python code raised it.
Threading
A Document (and any Page it hands out) may be used from any thread. Access to the underlying parsed document is serialized internally, and extract_text/render release the GIL while they run, so other Python threads keep making progress during long extractions or renders. Element and span iteration release the GIL per step the same way. Document.render_pages fans page rendering out across the machine's cores.
The async API needs no thread juggling: AsyncDocument's coroutines are driven by one global multi-thread tokio runtime, and AsyncDocument.render_pages fans out as tokio tasks so the asyncio loop stays free.