Skip to main content

pylopdf

PyPI CI Python License: MIT

Japanese README / Documentation: https://yhay81.github.io/pylopdf/ (with a pymupdf migration guide and API stability policy)

PDF editing, rendering, extraction, and generation for Python, powered by Rust — lopdf for editing, hayro (the pure-Rust PDF renderer adopted by Typst) for rendering and extraction, and krilla with HarfRust for generated text and form appearances.

MIT licensed, no mandatory Python dependencies, lightweight wheels. Covers the common pymupdf use cases without the AGPL.

Why pylopdf?

pylopdf pymupdf pypdf pypdfium2 pdf_oxide pikepdf
License MIT AGPL / commercial BSD Apache/BSD MIT/Apache-2.0 MPL-2.0
Wheel size (MiB) ~5.0–5.8 ~17.5–24.7 small (pure Python) ~2.7–5.0 ~9.7–10.9 ~1.9–4.6
Editing (merge / split / rotate / outlines) limited ✅ (structure-focused)
Rendering (PNG / SVG) ✅ (PNG) ❌ (docs point to other tools)
Text extraction ✅ (positioned text, tables, Markdown) ✅ (advanced) ✅ (advanced, table detection / Markdown) ❌ (docs point to other tools)
Encryption (AES-256) ✅ read & write read only undocumented ✅ (via qpdf)
Japanese font fallback / generation ✅ ([cjk] extra) manual
Implementation pure Rust C/C++ Python C++ (PDFium) Rust C++ (qpdf)

Wheel sizes are the ranges of published files for pylopdf 0.10.0, pymupdf 1.28.0, pypdfium2 5.12.1, pdf-oxide 0.3.75, and pikepdf 10.10.0 on 2026-07-25; the exact artifact depends on platform and Python ABI.

  • Fits size-constrained environments such as AWS Lambda
  • Safe for commercial projects that need to avoid the AGPL
  • abi3: one wheel covers Python 3.10–3.14
  • v0.10 includes native cp314t wheels for free-threaded Python 3.14
  • API modeled after pymupdf

Limitations: multicolumn text follows deterministic whitespace gutters, and find_tables() reconstructs bordered grids from strokes or thin filled rules, including rectangular merged cells. It conservatively separates repeated text records when a generator omits internal rules inside an otherwise connected grid. The opt-in find_tables(strategy="text") handles high-confidence borderless layouts, but can interpret aligned multicolumn prose as a table. Document.to_markdown() inserts complete bordered tables by default; pass table_strategy="text" to opt into borderless candidates or None to preserve plain layout text. Vertical CJK columns are reconstructed conservatively and ordered right-to-left; ruby, warichu, and mixed-orientation Japanese typography are not interpreted semantically. There is no general-purpose regeneration of arbitrary existing annotation appearances; the rendering snapshot does conservatively supply missing appearances for bounded RGB Highlight, Underline, StrikeOut, and Squiggly annotations with valid QuadPoints. AcroForm filling generates appearances for text, choice, checkbox, radio, and comb text fields, but rich text, pushbuttons, and signature fields remain out of scope. Typesetting, PDF/A output, and digital signatures are covered by the ecosystem recipes below. Native OCR returns axis-aligned word boxes; automatic page orientation, arbitrary deskew, ruby, warichu, and mixed-orientation typography remain explicit limits.

Install

pip install pylopdf

To render Japanese PDFs without embedded fonts, or auto-subset a JP font for Japanese/Han insert_text and insert_textbox, install the optional font package (Noto Sans/Serif JP):

pip install pylopdf[cjk]

For local PP-OCRv6 recognition without system executables, shared libraries, network requests, or an ONNX parser at runtime, install the optional model wheel:

pip install "pylopdf[ocr]"

See the offline OCR guide for memory controls, searchable-layer behavior, and current layout boundaries.

WebAssembly and Cloudflare Workers

pylopdf 0.11 adds a static PyEmscripten wheel for the pinned Python 3.13 / Pyodide 0.28.3 ABI. Cloudflare Python Workers are the supported public installation path: every release resolves the wheel from PyPI, bundles the repository's bounded PDF extraction Worker, starts local workerd, and verifies that a module-scope import pylopdf can serve /health.

Pyodide 0.28.3 itself is runtime-tested, but its older micropip cannot install PyPI's PEP 783 wheel tag directly. Native OCR inference is intentionally absent from Wasm; external OCR results can still be inserted with Page.insert_ocr_text_layer(). See the WebAssembly guide for the exact version matrix, local Pyodide workflow, resource policy, and release gates.

Every release installs each of the five abi3-py310 and five CPython 3.14t wheels on an architecture-matched Linux, macOS, or Windows runner and exercises PDF creation, saving, reopening, extraction, rendering, and immutable Pixmap storage. The sdist and PyEmscripten wheel have equivalent environment-specific gates, and every artifact carries build provenance.

Building from source (requires a Rust toolchain):

uv sync

Concurrency and free-threaded Python

Starting with v0.10, pylopdf supports concurrent work on distinct Document objects. Heavy native operations release the GIL, and the cp314-cp314t wheel keeps the GIL disabled on free-threaded Python 3.14. Calls or edits on the same Document must be serialized; use Document.render_pages(workers=...) for supported parallel rendering within one document.

Pixmap is immutable. The cp314t wheel supports read-only, zero-copy memoryview(pixmap); the Python 3.10-compatible abi3 wheel uses the one-copy pixmap.samples fallback. See the full concurrency contract.

Usage

import pylopdf

# Open from a path or bytes
doc = pylopdf.open("input.pdf")
doc = pylopdf.open(stream=pdf_bytes)

# Page count
print(doc.page_count)  # same as len(doc)

# Metadata
print(doc.metadata["title"])
doc.set_metadata({"title": "Monthly Report", "author": "Alice"})

# Text extraction (0-based page numbers)
text = doc.get_page_text(0)

# Positioned text and search (pymupdf-style, top-left origin)
words = doc[0].get_text("words")     # (x0, y0, x1, y1, word, block, line, word_no)
layout = doc[0].get_text("dict")     # blocks -> lines -> spans with bboxes
rects = doc[0].search_for("tax")     # case-insensitive, list[Rect]
tables = doc[0].find_tables(clip=(30, 30, 500, 700))  # complete bordered grids in a region
text_tables = doc[0].find_tables(strategy="text")  # opt-in borderless tables
confidence = text_tables[0].confidence if text_tables else None  # ranking heuristic, not probability
images = doc[0].get_images()         # [{"width", "height", "bbox", "ext", "image"}]
pix = doc[0].get_pixmap(dpi=144, clip=(0, 0, 300, 200))  # cropped RGBA8 pixels for NumPy / PIL

# Rendering
png: bytes = doc.render_page(0)             # 72 dpi; 64 MiB encoded-output cap
png2x: bytes = doc.render_page(0, scale=2)  # 144 dpi
png300 = doc.render_page(0, dpi=300)        # by resolution
png_bg = doc.render_page(0, background=(255, 255, 255))  # white background (default: transparent)
batch = doc.render_pages([0, 1, 2], scale=2, workers=4)  # ordered parallel PNGs
svg: str = doc.render_page_svg(0)

# Delete pages (split)
doc.delete_page(0)
doc.delete_pages([1, 2])

# Keep/reorder pages (repeating a page duplicates it)
doc.select([2, 0])

# Page objects (0-based; negative counts from the end)
page = doc[0]
for page in doc:
    print(page.number, page.rect)
page.set_rotation(90)                # display rotation (multiples of 90)
page.set_mediabox((0, 0, 300, 400))  # page boxes

# Insert / copy pages
doc.new_page()          # blank A4 appended
doc.copy_page(0, to=1)  # duplicate page 0 in front of page 1

# Drawing (coordinates are the same top-left display space as search_for / get_text)
page.insert_image((72, 72, 200, 200), filename="logo.png")   # JPEG passthrough, PNG with alpha
page.insert_image(page.search_for("Approved")[0], stream=stamp_png)  # stamp at a search hit
page.insert_image((300, 72, 500, 200), pixmap=thumbnail, rotate=90)  # direct RGBA, clockwise rotation
page.show_pdf_page(page.rect, letterhead)  # vector overlay; same-document sources also work
page.replace_text("DRAFT", "FINAL")        # bounded, atomic simple-font replacement

# Headers / footers / page numbers (standard-14 fonts, WinAnsi range)
for i, p in enumerate(doc):
    p.insert_text((p.rect.width - 90, p.rect.height - 30), f"Page {i + 1}", fontsize=9)

# Japanese/Han text auto-subsets the JP font with pip install "pylopdf[cjk]"
page.insert_text((40, 80), "社外秘", fontsize=20, color=(0.8, 0, 0))

# Wrap a paragraph into a rectangle; negative means nothing was drawn
spare = page.insert_textbox(
    (40, 100, 300, 220),
    "日本語も空白なしで自然に折り返します。",
    fontsize=12,
    align=pylopdf.TEXT_ALIGN_JUSTIFY,
)

# Annotations: search & highlight / link
page.add_highlight_annot(page.search_for("important"))  # appearance stream included (visible everywhere)
page.add_link_annot(page.search_for("Example")[0], "https://example.com/")
print(page.annots())  # [{"type", "rect", "contents", "uri"}]

# Native offline OCR: model inputs share a 64 MiB cap; add a searchable layer
engine = pylopdf.OcrEngine(threads=4, max_concurrent=1)  # pip install "pylopdf[ocr]"
words = page.get_text_ocr(engine=engine)
page.apply_ocr(engine=engine)  # skips existing searchable text by default
# Correct a sideways scan clockwise for OCR without changing page rotation
page.apply_ocr(engine=engine, rotation=270)

# Or write external OCR results as an invisible text layer
page.insert_ocr_text_layer(ocr_words)  # (x0, y0, x1, y1, text, ...); max 4,096 words / 1 MiB UTF-8 text

# Markdown conversion (RAG / LLM preprocessing; bordered tables are automatic)
md = doc.to_markdown()
md_with_borderless_tables = doc.to_markdown(table_strategy="text")
md_p1 = doc[0].to_markdown()

# Read the PDF/A self-declaration (1 MiB XMP cap; validation belongs to veraPDF)
print(doc.get_pdfa_claim())  # e.g. (2, "B") for PDF/A-2b; None if absent

# Forms (AcroForm): read and fill
print(doc.get_form_fields())        # [{"name", "type", "value"}]
doc.set_form_field("customer", "Taro Yamada")
doc.set_form_field("customer_ja", "山田 太郎")  # auto-subset with pylopdf[cjk]
doc.set_form_field("agree", True)   # checkboxes take bool or a state name

# Page labels (display numbers: roman front matter + decimal body, etc.)
doc.set_page_labels([{"startpage": 0, "style": "r"}, {"startpage": 3, "style": "D"}])
print(doc[4].get_label())  # "2"

# File attachments (e.g. attach the XML data to an invoice PDF)
doc.embfile_add("invoice.xml", xml_bytes, filename="invoice-data.xml")  # 64 MiB input cap
print(doc.embfile_names())  # ["invoice.xml"]
xml = doc.embfile_get("invoice.xml")  # decoded output is capped at 64 MiB by default
# known_large = doc.embfile_get("archive.bin", max_size=256 * 1024 * 1024)

# Table of contents (page numbers are 1-based here, pymupdf-compatible)
doc.set_toc([[1, "Chapter 1", 1], [2, "Section 1.1", 2]])
print(doc.get_toc())

# Merge (with ranges, reversed order, and an insertion position)
merged = pylopdf.Document()
merged.insert_pdf(pylopdf.open("a.pdf"))
merged.insert_pdf(pylopdf.open("b.pdf"), from_page=0, to_page=2, start_at=0)

# Save
merged.save("merged.pdf")
data: bytes = merged.tobytes()  # 512 MiB output cap; max_size=None opts out

# Optimized save (prune unreferenced objects + compress + object streams)
merged.save("small.pdf", garbage=True, deflate=True, object_streams=True)

# Encrypted save (AES-256; owner_pw alone = open freely, restricted permissions)
merged.save("locked.pdf", user_pw="secret", permissions=pylopdf.Permissions.PRINT)

# Fast metadata probe without parsing the whole document
info = pylopdf.peek_metadata("input.pdf", max_file_size=32 * 1024 * 1024)
print(info["title"], info["page_count"], info["encrypted"], info["repaired"])

# Context manager
with pylopdf.open("input.pdf") as doc:
    print(doc.metadata)

# Encrypted PDFs (RC4-40/128, AES-128, AES-256; empty user passwords open transparently)
doc = pylopdf.open("locked.pdf", password="secret")
doc = pylopdf.open("locked.pdf")
if doc.needs_pass:
    doc.authenticate("secret")  # 0=failed, 2=user, 4=owner, 6=both

# A bounded repair of an incorrect final classic startxref is always visible.
if doc.is_repaired:
    doc.save("normalized.pdf")

# CJK fallback font for PDFs without embedded fonts
# (automatic with pylopdf[cjk]; or bring your own font)
doc.set_fallback_font("NotoSansJP-Regular.otf")
doc.set_fallback_font(font_bytes, kind="serif")

Embedded-font insert_text shapes each line with HarfRust and asks krilla to subset and embed the resulting glyphs. With pylopdf[cjk], Japanese and Han text automatically selects its JP-subset sans font; a Times fontname selects serif. This is one whole-run font selection, not per-glyph fallback. Pass fontfile= / fontbuffer= for Hangul, locale-specific Chinese glyph forms, other scripts, or another typeface. RTL glyph shaping works, but extraction currently follows visual rather than logical order. Use typst below when full typesetting is required.

Ecosystem recipes (typesetting, PDF/A, signatures)

pylopdf stays a lightweight core for editing, extraction, rendering, and bounded text/form generation; adjacent concerns are solved by pairing it with established libraries. The recipes below are covered by integration tests (tests/test_interop.py).

Typesetting / creating new documents = typst (via typst-py). Typeset reports with typst and feed the bytes straight into pylopdf:

import typst
import pylopdf

pdf_bytes = typst.compile("report.typ")   # typesetting: typst
doc = pylopdf.open(stream=pdf_bytes)      # editing / extraction / merging: pylopdf

PDF/A for new documents is also typst's job (validated export via krilla; PDF/A-1b through 4 and PDF/UA-1):

pdf_a: bytes = typst.compile("report.typ", pdf_standards="a-2b")

Richly typeset CJK watermarks / headers / footers can combine typst with pylopdf. For simple text, use insert_text(fontfile=...) directly; for a full-page composition, typeset one stamp page with typst (fonts get subset-embedded), then burn it onto every page as vectors with show_pdf_page:

from pylopdf_fonts_cjk import sans_path  # pip install pylopdf[cjk] (reuses the Noto fonts)

stamp_typ = """
#set page(width: 595pt, height: 842pt, fill: none)
#set text(font: "Noto Sans JP", size: 48pt, fill: rgb(255, 0, 0, 40%))
#align(center + horizon)[社外秘]
"""
stamp = pylopdf.open(stream=typst.compile(stamp_typ.encode(), font_paths=[str(sans_path().parent)]))
for page in doc:
    page.show_pdf_page((0, 0, page.rect.width, page.rect.height), stamp)

Converting or validating existing PDFs against PDF/A is a different problem; veraPDF (Java) is the de-facto validator.

Digital signatures (PAdES) = pyHanko (MIT). pyHanko signs with an incremental update, so the bytes produced by pylopdf remain untouched as a prefix of the signed file:

import io
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
from pyhanko.sign import signers

signer = signers.SimpleSigner.load("key.pem", "cert.pem")
out = signers.sign_pdf(
    IncrementalPdfFileWriter(io.BytesIO(doc.tobytes())),
    signers.PdfSignatureMetadata(field_name="Signature1"),
    signer=signer,
)
signed_pdf: bytes = out.getvalue()

API

pylopdf.Document (pylopdf.open() is an alias constructor):

Method / property Description
Document(filename=None, stream=None, password=None, max_decompressed_size=None, *, limits=None) Open from a path or bytes; empty document if both are None. Passwords stop at 127 UTF-8 bytes. Use limits=DocumentLimits.web() for a complete untrusted-upload policy; max_decompressed_size remains the compatible per-stream shorthand
doc[i] / load_page(pno) / for page in doc Get a Page view (negative indices count from the end; re-fetch after structural changes)
needs_pass / is_encrypted Encryption status (pymupdf-compatible semantics)
is_repaired Whether opening repaired an incorrect final classic startxref; a PylopdfWarning is also emitted and saving normalizes the xref data
authenticate(password) Decrypt with a password of at most 127 UTF-8 bytes (returns 0/1/2/4/6, pymupdf-compatible)
page_count / len(doc) Number of pages
limits / complexity Immutable open-time resource policy, including optional rendering/extraction snapshot, positioned-text glyph, and plain-text assembly caps / cheap page, object, stream, encoded-byte, and direct-depth facts without decoding
metadata Bounded standard metadata dict (title, author, subject, keywords, creator, producer, creationDate, modDate, format); 1 MiB aggregate Info text
set_metadata(dict) Atomically set standard metadata under a 1 MiB pre-PyO3 UTF-8/encoded boundary (empty string deletes the entry)
get_page_text(pno, option="text") Extract text (or positioned layout: "words" / "blocks" / "dict")
render_page(pno, scale=1.0, dpi=None, background=None, max_size=64 MiB) Render bounded PNG bytes; dpi replaces scale, background is an RGB(A) fill (max 65,535 px per side / 64 MP total); None opts out
render_pages(pages=None, scale=1.0, workers=None, max_size=512 MiB, ...) Render up to 4,096 ordered PNGs from one immutable snapshot; up to 4 workers by default, ~512 MB estimated live-work concurrency, and a cumulative encoded-output cap (None opts out)
render_page_svg(pno, max_size=64 MiB) Render bounded UTF-8 SVG; over-limit output is rejected before Python string conversion, None opts out
compress_images(dpi=150, quality=75) Lossily downsample and JPEG-recompress safe unmasked DeviceGray/DeviceRGB DCT or Flate XObjects; preserves the largest reuse, skips non-smaller output, and returns typed byte/count statistics
set_fallback_font(font, kind="sans", index=0, max_font_size=64 MiB) Set a bounded fallback font (path/bytes) for non-embedded CJK fonts; font=None disables auto-detection and max_font_size=None opts trusted font input out
select(page_numbers) Keep up to 4,096 page entries in the given order (repeats duplicate the page)
delete_page(pno) / delete_pages(iterable) Delete up to 4,096 page entries per call; an empty iterable is a true no-op
insert_pdf(other, from_page=0, to_page=-1, start_at=-1) Merge up to 4,096 pages per call (negative / reversed ranges; start_at sets the insertion position)
new_page(pno=-1, width=595, height=842) / copy_page(pno, to=-1) Insert a blank page / duplicate a page
get_toc() / set_toc(toc) Read/write cycle-aware bounded outlines as [[level, title, page], ...] (page numbers are 1-based here; caps: 4,096 entries/nodes, 8,192 edges, 64 levels, 1 MiB text preflighted before PyO3 on writes)
to_markdown(pages=None, table_strategy="lines", max_size=64 MiB) Page-at-a-time two-pass Markdown conversion with a bounded linear entry builder, capped at 4,096 pages and cumulative UTF-8 output (None opts out); headings, emphasis, CJK joining, lists, columns, vertical order, and bordered/opt-in borderless tables
get_form_fields() / set_form_field(name, value, fontfile=, fontbuffer=, fontindex=, max_font_size=64 MiB) List and fill AcroForm fields with native text/choice/button appearances; bounded field-tree, 1 MiB caller name/value, button-state, and font interpretation; checkboxes take bool
get_pdfa_claim(max_size=1 MiB) Bounded-decode the XMP PDF/A declaration (part, conformance) (a self-claim read, not validation); max_size=None explicitly opts out
embfile_add(name, data, filename=, desc=, max_size=64 MiB) / embfile_names() / embfile_get(name, max_size=64 MiB) / embfile_del(name) Add / list / retrieve / delete attachments under symmetric data/decode defaults; max_size=None explicitly opts out, caller text stops at 1 MiB, name trees are capped at 4,096 entries/nodes, and inline FileSpec clone shapes are bounded
get_page_labels() / set_page_labels(labels) Read/write page label ranges ({"startpage", "style", "prefix", "firstpagenum"}); fixed caps: 4,096 entries/nodes, 32 levels, 1 MiB label text preflighted before PyO3 on writes
save(filename, garbage=, deflate=, object_streams=, user_pw=, owner_pw=, permissions=) / tobytes(same, max_size=512 MiB) Atomically replace a file after a complete same-directory streamed write, or return bounded PDF bytes; prune / compress / object streams, or AES-256 encryption via 127-byte-bounded user_pw / owner_pw; max_size=None opts out of the byte-return limit
close() Close (supports with)

pylopdf.Page (obtained via doc[i]):

Method / property Description
number / parent 0-based page number and owning Document
get_label() Display label of the page ("iv", "A-2", …; empty string if undefined)
get_text(option="text") Text extraction; "words" / "blocks" / "dict" return positioned layout
get_text_ocr(dpi=300, engine=None, tile_size=1408, overlap=192, min_confidence=0.5, rotation=0, clip=None) Recognize positioned words locally through pylopdf[ocr] without modifying the page; rotation corrects rendered input clockwise and clip uses display coordinates
apply_ocr(..., rotation=0, clip=None, skip_existing=True) Recognize and insert an orientation-aware invisible searchable layer; existing searchable text in the selected region is skipped by default
to_markdown(table_strategy="lines", max_size=64 MiB) Single-page Markdown with the same table and UTF-8 output controls as the document method
search_for(needle, max_hits=4096) Case-insensitive bounded search returning list[Rect]; terms stop at 4,096 UTF-8 bytes and max_hits=None opts trusted result sets out
find_tables(strategy="lines", clip=None) Detect complete or conservatively refined bordered grids and rectangular merged cells; use strategy="text" for opt-in borderless detection; clip filters in display coordinates and results expose confidence diagnostics
get_images() Extract page images (original JPEG bytes passed through; others as PNG); rejects partial output above 4,096 placements, 64,000,000 cumulative pixels, or 64 MiB of payloads per page
get_drawings() Extract interpreted vector fill/stroke paths as typed pymupdf-style dictionaries with display-space line/cubic geometry, RGB/opacity, fill rule, width, cap, join, and dashes
get_pixmap(scale, dpi=, background=, clip=None) Render to an immutable Pixmap; clip is a display-coordinate rectangle (straight RGBA8: samples / width / height / stride / tobytes(max_size=64 MiB) / streaming, failure-atomic PNG-only save(path); cp314t also supports read-only zero-copy memoryview())
insert_image(rect, filename=/stream=/pixmap=, rotate=0, keep_proportion=True, overlay=True, max_size=64 MiB, max_pixels=64,000,000) Draw JPEG without recompression, bounded PNG with alpha, or a rendered RGBA Pixmap without a PNG round trip; None opts trusted encoded input or PNG pixels out of its boundary; optional clockwise right-angle rotation and rect use display coordinates
show_pdf_page(rect, src, pno=0, keep_proportion=True, overlay=True) Overlay a page as vectors from another or the same document; same-document placement uses a stable pre-edit snapshot
insert_text(point, text, fontsize=11, fontname="helv", fontfile=, fontbuffer=, fontindex=, color=, overlay=True, max_font_size=64 MiB, max_text_size=1 MiB) Print bounded multiline text with a standard-14 or shaped subset font; generated input stops at 4,096 lines; pylopdf[cjk] auto-selects its JP font for Japanese/Han; None opts the corresponding trusted font or text input out; upright on rotated pages
insert_textbox(rect, text, fontsize=11, fontname="helv", fontfile=, fontbuffer=, fontindex=, color=, align=0, lineheight=None, expandtabs=8, overlay=True, max_font_size=64 MiB, max_text_size=1 MiB) Wrap bounded UTF-8 text with UAX #14 and Core 14, explicit OpenType, or auto-selected JP font metrics; physical and wrapped layout stop at 4,096 lines, tab expansion is preflighted, and overflow draws nothing
insert_ocr_text_layer(words, rotation=0) Write up to 4,096 words / 1 MiB incrementally counted UTF-8 text as an orientation-aware invisible OCR layer (searchable PDFs; no font embedding)
annots() / get_links() Bounded annotation/link reads, including one cycle-aware named-destination index per call (4,096 annotations and 1 MiB aggregate metadata text; display coordinates)
add_highlight_annot(rects, color=(1,1,0), opacity=0.4, content=None) Highlight annotation; bounded iteration of up to 4,096 search_for results; appearance stream included; 1 MiB pre-copy subtype/content budget
add_link_annot(rect, uri) URI link annotation (no border; 1 MiB pre-copy subtype/URI budget)
replace_text(search, replacement, default_char=None, max_size=64 MiB) Atomic copy-on-write text replacement (simple-encoded fonts only; 4,096-byte caller text is counted without a complete encoded copy; bounded output; returns the count; no CJK)
render(scale, dpi=, background=) / render_svg(max_size=64 MiB) PNG / bounded UTF-8 SVG rendering
rotation / set_rotation(deg) Display rotation (multiples of 90, inheritance-resolved)
mediabox / cropbox / rect Page boxes (Rect); rect is the rotation-aware visible rectangle
set_mediabox(rect) / set_cropbox(rect) Set page boxes

Drawing insertions preflight page /Contents before decoding inputs or creating dependent objects. The resulting array is capped at 4,096 stream references; the one-time q/Q isolation pair is included in that total, and failures do not mutate the document.

Module level:

Name Description
peek_metadata(filename/stream, password=None, *, max_file_size=None) Fast metadata / page-count / encryption probe; optional input-size rejection and 127-byte password boundary; repaired reports bounded classic-startxref recovery
Permissions Encryption permission flags (IntFlag)
Rect Rectangle NamedTuple with width / height
ImageCompressionResult Typed counts and rewritten source/result byte totals from compress_images()
DrawingInfo / DrawingItem Typed vector-path dictionary and its line/cubic command union
TEXT_ALIGN_LEFT / CENTER / RIGHT / JUSTIFY insert_textbox alignment constants (0–3, pymupdf-compatible)
OcrEngine / OcrWord / OcrRotation Reusable pure-Rust PP-OCR engine, its typed positioned-word result, and the 0 / 90 / 180 / 270 clockwise-correction contract
TableFinder / Table / TableDiagnostics Owned table geometry, cell text, strategy, and confidence evidence; Table.to_markdown(max_size=64 MiB) preflights escaped UTF-8 output
PylopdfWarning Recoverable interpretation warning, including bounded xref repair, font resolution, and image decoding
Exceptions PdfError (ValueError-compatible base), PasswordError, OcrError, DocumentClosedError, EncryptedDocumentError, StalePageError

For low-level access, use pylopdf.pylopdf_core._Document (a thin lopdf wrapper) directly.

Architecture

Follows the division of labor in the 2026 Rust PDF ecosystem:

pylopdf.Document (Python, pymupdf-style API)
   └─ _Document (PyO3)
        ├─ lopdf 0.44   … editing: open → modify → save
        ├─ hayro 0.7    … rendering and positioned extraction
        └─ krilla 0.8 + HarfRust 0.12
                         … shaped, subset-embedded text and form appearances
rust/          # PyO3 bindings
src/pylopdf/   # Python high-level API
tests/         # pytest (Rust behavior is verified through Python tests)
uv sync                    # build + install dependencies
uv run pytest              # tests
uv run ruff check .        # lint
uv run mypy src tests      # type check
uv build --wheel           # build a wheel

uv sync detects Rust source changes and rebuilds automatically (via tool.uv.cache-keys).

Contributing

Bug reports and focused contributions are welcome. See CONTRIBUTING.md for development commands, test expectations, and the rules for sharing PDF regression files. Report security vulnerabilities privately through GitHub Security Advisories.

Benchmarks

A reproducible benchmark ships with the repo (same corpus, same tasks, medians — wins and losses are published as-is). See bench/results/latest.md for the latest numbers with environment details. Free-threaded extraction is generated independently at bench/results/free-threaded-latest.md so a normal benchmark run cannot overwrite its CPython 3.14t evidence:

uv sync --all-extras --group bench && uv run python bench/run.py
# Windows:
py -3.14t bench/free_threaded.py
# POSIX:
python3.14t bench/free_threaded.py

The separate native OCR report publishes strict and NFKC-normalized CER plus elapsed time on two licensed Japanese fixtures, including an image-only archival scan. It also records a bounded shared-engine concurrency check:

uv sync --all-extras && uv run python bench/ocr.py

License

MIT (lopdf and HarfRust are MIT; hayro and krilla are MIT/Apache-2.0)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pylopdf-0.12.0.tar.gz (232.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pylopdf-0.12.0-cp314-cp314t-win_amd64.whl (7.3 MB view details)

Uploaded CPython 3.14tWindows x86-64

pylopdf-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (8.2 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.17+ x86-64

pylopdf-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (8.0 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.17+ ARM64

pylopdf-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl (7.0 MB view details)

Uploaded CPython 3.14tmacOS 11.0+ ARM64

pylopdf-0.12.0-cp314-cp314t-macosx_10_12_x86_64.whl (7.7 MB view details)

Uploaded CPython 3.14tmacOS 10.12+ x86-64

pylopdf-0.12.0-cp310-abi3-win_amd64.whl (7.3 MB view details)

Uploaded CPython 3.10+Windows x86-64

pylopdf-0.12.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl (4.0 MB view details)

Uploaded CPython 3.10+PyEmscripten 2025.0 wasm32

pylopdf-0.12.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (8.2 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

pylopdf-0.12.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (8.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

pylopdf-0.12.0-cp310-abi3-macosx_11_0_arm64.whl (7.1 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

pylopdf-0.12.0-cp310-abi3-macosx_10_12_x86_64.whl (7.7 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file pylopdf-0.12.0.tar.gz.

File metadata

  • Download URL: pylopdf-0.12.0.tar.gz
  • Upload date:
  • Size: 232.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pylopdf-0.12.0.tar.gz
Algorithm Hash digest
SHA256 877ff3d5fce45f049693b85273e7f1c6a022bfc40573cee1e4d3735166135718
MD5 63a150069cfb68f9488968642a7cb204
BLAKE2b-256 3d73aa5fbcd17d2c8c851fcf82c5ee487c988bf6c115c2749ac8c6f5a0737aa7

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0.tar.gz:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp314-cp314t-win_amd64.whl.

File metadata

  • Download URL: pylopdf-0.12.0-cp314-cp314t-win_amd64.whl
  • Upload date:
  • Size: 7.3 MB
  • Tags: CPython 3.14t, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pylopdf-0.12.0-cp314-cp314t-win_amd64.whl
Algorithm Hash digest
SHA256 9982063bc70ffdd70e1f0edf86a447451dfb9a5b336cd64205da0b706c3c1d7c
MD5 21adb55621658999517c4767b22dcc0d
BLAKE2b-256 dcc46bfbc16f5a5485f677b326446758c77aef8222e8b07f82a83fb635803c50

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp314-cp314t-win_amd64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 51519bce13377f69def1f2af99dd34e47a8776829466cff98f8b002803e79640
MD5 8c9f00f047db351c35edce71a4df5832
BLAKE2b-256 83515f230ef6f6e27fcbc352643c780fb1637c354d9997658937230383dc088d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3e9eac276760125234f33bbb17feee5da720a2b13c0a9865e5a8d20b8671471b
MD5 8a180cd819d7462d3bf7097e87104b45
BLAKE2b-256 786f59344de735c087336c03ea4e91bc9c900f84f306a61359c69625dd025b7a

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1e99995038a794d15fa9663ead411760703290d6d1adfbf299d3b047e7c96cee
MD5 c0e26d1c4a30eab17f4a2467f5da9bc5
BLAKE2b-256 113a4381a8c8abe8d3e56eb66d20c6743f9c0262f9b7bf527b6e9fdd09b572a8

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp314-cp314t-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp314-cp314t-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 6aafcc69cc548e04353dc3626a1fe614a693df4b5f4ea9f9a68298ae62cf0ffa
MD5 c61cdcf379a78f579c8e2f3fcc0770c9
BLAKE2b-256 0caf7f0fb35c2bd48351dd14e7fd3f70656c07a3725b01e8dc242cfd7ea3c290

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp314-cp314t-macosx_10_12_x86_64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: pylopdf-0.12.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 7.3 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pylopdf-0.12.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a90444d618f2208d953fd9c0b7232332b3c8f328323dfc71316a9eaae5328cb6
MD5 609434be1d1a75648a9191b21f1992cf
BLAKE2b-256 af995a9f3dfe0171aea2418f410b9bd9a7a4c1d233695d7fb62e05a5dcadcbf4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp310-abi3-win_amd64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl
Algorithm Hash digest
SHA256 a084e032ecf7507eaeb6580dc39757ef0d1bc93093d1ab004ad0adebb57415e9
MD5 4838d165980f3db73b759a06c465a88c
BLAKE2b-256 50f942504ea025fc56489777de967e4caacc988c053c12ddad1edb93e20339c4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 4a385802cb6d65b1f78c23af15a7337ecfd76dd80163205d4634ca1aa60845e6
MD5 dcfea12462719cae6b953af7450c9c9a
BLAKE2b-256 e321dac9ef014761caa362cba5bee58446856bbc7ec206a8d59c25edfc546901

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 74debdaf4a9df95c18fd874161307851855b59a65ea97a78e1c2cffd2e915996
MD5 af9e30af46470f1214897d8926af8122
BLAKE2b-256 8111a36ffb7c1f41db6391028aea539c1220fc9d489b27780fa556cb62661d88

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 adba8cf2136986fc00ee6b57fe3dbfa26bca3f36571286680642250de09f666c
MD5 06cc2193729e67e2484ad3d06b928107
BLAKE2b-256 a26ded2c6213b2a88d8f23108b380dc7986832261b4d62f12c5829de508ac09d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pylopdf-0.12.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pylopdf-0.12.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 798c757f8276aa6efa41fe1cab92cebe7b6ead928f4239769745793dde4175e0
MD5 396ca6e428d07f9d52ce599c24968b4b
BLAKE2b-256 f82dfc362854eebeed06e1997fdff515b2330830330bd5b9d38c1eb64741455d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.12.0-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on yhay81/pylopdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.13.0

12 files

This release

0.12.0 This release

12 files

0.11.1

12 files

0.11.0

12 files

0.10.0

11 files

0.9.0

6 files

0.8.0

6 files

0.7.0

6 files

0.6.0

6 files

0.5.0

6 files

0.4.1

6 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page