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. 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.

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")
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 file
info = pylopdf.peek_metadata("input.pdf")
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. 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 (returns 0/1/2/4/6, pymupdf-compatible)
page_count / len(doc) Number of pages
limits / complexity Immutable open-time resource policy / 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 the 1 MiB input/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)
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/name/value/button-state/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=) / embfile_names() / embfile_get(name, max_size=64 MiB) / embfile_del(name) Add / list / bounded-decode / delete attachments; max_size=None explicitly opts out, name trees are capped at 4,096 entries/nodes, and add metadata plus 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
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 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) Case-insensitive text search returning list[Rect]
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) Print multiline text with a standard-14 or bounded shaped subset font; pylopdf[cjk] auto-selects its JP font for Japanese/Han; None opts trusted font 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) Wrap with UAX #14 and Core 14, bounded explicit OpenType, or auto-selected JP font metrics; returns spare height and draws nothing on overflow
insert_ocr_text_layer(words, rotation=0) Write up to 4,096 words / 1 MiB 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; feed up to 4,096 search_for results directly; appearance stream included; 1 MiB subtype/content budget
add_link_annot(rect, uri) URI link annotation (no border; 1 MiB subtype/URI budget)
replace_text(search, replacement, default_char=None, max_size=64 MiB) Atomic copy-on-write text replacement (simple-encoded fonts only; bounded input/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) Fast metadata / page-count / encryption probe; 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:

uv sync --all-extras --group bench && uv run python bench/run.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.11.1.tar.gz (218.1 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.11.1-cp314-cp314t-win_amd64.whl (7.3 MB view details)

Uploaded CPython 3.14tWindows x86-64

pylopdf-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (8.1 MB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.17+ x86-64

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

Uploaded CPython 3.14tmanylinux: glibc 2.17+ ARM64

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

Uploaded CPython 3.14tmacOS 11.0+ ARM64

pylopdf-0.11.1-cp314-cp314t-macosx_10_12_x86_64.whl (7.6 MB view details)

Uploaded CPython 3.14tmacOS 10.12+ x86-64

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

Uploaded CPython 3.10+Windows x86-64

pylopdf-0.11.1-cp310-abi3-pyemscripten_2025_0_wasm32.whl (4.1 MB view details)

Uploaded CPython 3.10+PyEmscripten 2025.0 wasm32

pylopdf-0.11.1-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.11.1-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.11.1-cp310-abi3-macosx_11_0_arm64.whl (7.0 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

pylopdf-0.11.1-cp310-abi3-macosx_10_12_x86_64.whl (7.6 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

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

File metadata

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

File hashes

Hashes for pylopdf-0.11.1.tar.gz
Algorithm Hash digest
SHA256 f66a52df438aa3a054df9b845c0f664012a8f40146d76f51e274fd12a2779d13
MD5 e6230bc90d79e67de3077dd44a4aed05
BLAKE2b-256 af38c0131b0e3d1bd38c1fbd5e20333d02fd51786eb3a9792a821658dd798dfa

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1.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.11.1-cp314-cp314t-win_amd64.whl.

File metadata

  • Download URL: pylopdf-0.11.1-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.11.1-cp314-cp314t-win_amd64.whl
Algorithm Hash digest
SHA256 049e002edc52dfb14323bdbad166625ba77e5985da47cbcf25610fc17a4bc1c5
MD5 c66975554cad24a3d9d8772ee1dfc9eb
BLAKE2b-256 a1556a19aa20c77233ee08ef03100d9696d06305e7b7f56280803a3186c87715

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 3aee08ebbb89b387f808d2a3c7728eff997e87742730203f51f479a8cfe1293e
MD5 daedb382472805628d1a6e7aea1fe2ff
BLAKE2b-256 07b7cf1cfc68a04fa5f7630cfb514bf3e90d5ebf1ba9465d1c2fdda91a25b008

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f02a39d2b90624db034183e6971d5170277c5ddfa3298fc1b4cf0e656b87e37c
MD5 03ab48fa6a9f6b77b6ed23b0bc8415c8
BLAKE2b-256 e1032253d285178afbfe080457f0bad276d9b03f62a87ddcd64bb89aa7bc9593

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp314-cp314t-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b8712fa01e34e319788c43ba14b659965bc28a1620ac85b76d53b6141ff80133
MD5 182d7bb1bf05abaf8b87938e84fac4ba
BLAKE2b-256 3b9f67e6c462e57483610c8a959e623811008f9c9622ae3a2e691c05a885661a

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp314-cp314t-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp314-cp314t-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 6f897bca907b5630615253cb19753e20fa5206e2b710081d7aa60e28cdb70df2
MD5 8b5164c3ef74e99dea61b7cdf552237c
BLAKE2b-256 119a04c6c48a35239bb320b765c527d4c0fa921d5811df83b4929afd97da93fd

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: pylopdf-0.11.1-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.11.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a05db005f387c11bf82218153a7553860fa6d9c534e0daa2ca72108388e18d8d
MD5 94de9221a319870ffed0c992fd502821
BLAKE2b-256 576bcfa3e60f9894aaec2a0cd5deb1ea67cee91726a2f7a7f06b2664756ddab4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp310-abi3-pyemscripten_2025_0_wasm32.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp310-abi3-pyemscripten_2025_0_wasm32.whl
Algorithm Hash digest
SHA256 288d9feaea45fc8cecc5012eee8e574cf772ca0d27d19c7109c81617f9eba15d
MD5 82e835eae8b9067c00dd3b719e633578
BLAKE2b-256 500ce3c34b3fde233a9a6a6ca0b8345153db63c4c1816d51f8cfdc2ec293860d

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 61c2d020e25fdc5c5a156dc9bf87e803bccf045779835ba574445e68908b8d68
MD5 fb16d11e922796b31a6e7867a5eebabb
BLAKE2b-256 8410bdd027018086061a5c2a075fde0d992f42613260f1272867efa8f506b027

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 f642bd288b529ade373a0ce172ff5d99905850de5deacf2559ce5cb8e79b5728
MD5 771315245d0908f7c77a8484fc272a8e
BLAKE2b-256 320a8e5a72157e245c57b7ad1973eb31e3abd82fa4c23ebdeb0edcb05c07eb4c

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4c673fd9fdc55251b71f641fb715c64f69ef21ac1bac5d1de9e2f49d1445d9b8
MD5 1684eeed0362724cc207dca6de62acfc
BLAKE2b-256 902549c1ef8820ccc698aadff6cb768b316e45f0c723392d44f8009cf6f13d05

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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.11.1-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pylopdf-0.11.1-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 8b90c42c6e9d13bbeb0e8fe272528eb709146e82fb089e24aa1e079204def9be
MD5 349b829fc207f8f673e947c6d07ac013
BLAKE2b-256 3a80dc27270e5d4c778e3b9e55e0ef3e9a0d3f73ee86ecf3c44c45bdf3f0fc63

See more details on using hashes here.

Provenance

The following attestation bundles were made for pylopdf-0.11.1-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

0.12.0

12 files

This release

0.11.1 This release

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