Skip to main content

PDF editing and rendering for Python, powered by Rust (lopdf + hayro). MIT, zero dependencies, lightweight wheels.

Project description

pylopdf

PyPI CI Python License: MIT

日本語版 README はこちら

PDF editing and rendering for Python, powered by Rust — lopdf for editing and hayro (the pure-Rust PDF renderer adopted by typst) for rendering.

MIT licensed, zero runtime 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 ~3.5 MB ~40 MB+ small (pure Python) ~8 MB ~10–11 MB ~2–5 MB
Editing (merge / split / rotate / outlines) limited ✅ (structure-focused)
Rendering (PNG / SVG) ✅ (PNG) ❌ (docs point to other tools)
Text extraction ✅ (basic) ✅ (advanced) ✅ (advanced, table detection / Markdown) ❌ (docs point to other tools)
Encryption (AES-256) ✅ read & write undocumented ✅ (via qpdf)
CJK font fallback ✅ ([cjk] extra) manual
Implementation pure Rust C Python C++ (PDFium) Rust C++ (qpdf)
  • 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
  • API modeled after pymupdf

Limitations: no precise layout analysis, no annotation/form editing. Use pymupdf if you need those.

Install

pip install pylopdf

To render Japanese PDFs without embedded fonts, install the optional CJK fonts (Noto Sans/Serif JP, auto-detected at render time):

pip install pylopdf[cjk]

Building from source (requires a Rust toolchain):

uv sync

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]
images = doc[0].get_images()         # [{"width", "height", "bbox", "ext", "image"}]
pix = doc[0].get_pixmap(dpi=144)     # RGBA8 pixels for NumPy / PIL (pix.samples)

# Rendering
png: bytes = doc.render_page(0)             # 72 dpi
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)
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

# 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()

# 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"])

# 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

# 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")

API

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

Method / property Description
Document(filename=None, stream=None, password=None, max_decompressed_size=None) Open from a path or bytes; empty document if both are None. max_decompressed_size guards against decompression bombs
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)
authenticate(password) Decrypt with a password (returns 0/1/2/4/6, pymupdf-compatible)
page_count / len(doc) Number of pages
metadata Metadata dict (title, author, subject, keywords, creator, producer, creationDate, modDate, format)
set_metadata(dict) Set metadata (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) Render a page to PNG bytes; dpi replaces scale, background is an RGB(A) fill (max 65,535 px per side / 64 MP total)
render_page_svg(pno) Render a page to an SVG string
set_fallback_font(font, kind="sans", index=0) Set a fallback font (path/bytes) for non-embedded CJK fonts; None disables auto-detection
select(page_numbers) Keep only the given pages, in the given order (repeats duplicate the page)
delete_page(pno) / delete_pages(iterable) Delete pages
insert_pdf(other, from_page=0, to_page=-1, start_at=-1) Merge a page range (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 outlines as [[level, title, page], ...] (page numbers are 1-based here)
save(filename, garbage=, deflate=, object_streams=, user_pw=, owner_pw=, permissions=) / tobytes(same) Save; prune / compress / object streams, or AES-256 encryption via user_pw / owner_pw (the in-memory document stays plain)
close() Close (supports with)

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

Method / property Description
number / parent 0-based page number and owning Document
get_text(option="text") Text extraction; "words" / "blocks" / "dict" return positioned layout
search_for(needle) Case-insensitive text search returning list[Rect]
get_images() Extract page images (original JPEG bytes passed through; others as PNG)
get_pixmap(scale, dpi=, background=) Render to a Pixmap (straight RGBA8: samples / width / height / stride / tobytes())
render(scale, dpi=, background=) / render_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

Module level:

Name Description
peek_metadata(filename/stream, password=None) Fast metadata / page-count / encryption probe without parsing the whole file
Permissions Encryption permission flags (IntFlag)
Rect Rectangle NamedTuple with width / height
Exceptions PdfError (ValueError-compatible base), PasswordError, 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: PNG / SVG (standard fonts embedded)
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).

License

MIT (lopdf is MIT; hayro is MIT/Apache-2.0)

Project details


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.7.0.tar.gz (53.4 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.7.0-cp310-abi3-win_amd64.whl (3.7 MB view details)

Uploaded CPython 3.10+Windows x86-64

pylopdf-0.7.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.2 MB view details)

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

pylopdf-0.7.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (4.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

pylopdf-0.7.0-cp310-abi3-macosx_11_0_arm64.whl (3.5 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

pylopdf-0.7.0-cp310-abi3-macosx_10_12_x86_64.whl (3.9 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

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

File metadata

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

File hashes

Hashes for pylopdf-0.7.0.tar.gz
Algorithm Hash digest
SHA256 958048b94d7ff4a2c67c891714b18705f7f009d0b2f92e773e5025643ac5a8c1
MD5 4fc06dabbada523d9eef13c83e96ff2a
BLAKE2b-256 994e1b606f999ea9343fda78040053c206e6257a73e0bf8b5d179ae60ae32fb0

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: pylopdf-0.7.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 3.7 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.7.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 144119cb9ec503bac3fd7d7006c2069fe9c828871d4ba2e338a0c7fd3e92d10e
MD5 b7c257a1a9ad1504e5dcb18c56cf169a
BLAKE2b-256 76c8304ec10ed67d81153112a6f77fa07912406baabab01753f274f92fc53d5e

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for pylopdf-0.7.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 59da3b19d6b77d763c3ded3a47772eb3e5b168db7dcc3a3c6b733bad1613f454
MD5 3ff10778548fa7d225318a926b81ea71
BLAKE2b-256 1e4275df13db4ee523ef4dd5bffd7416f1814c066dff7ab63758051ae8352be5

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for pylopdf-0.7.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 8af2dfc945a0f49e0b1036cc4e4d523f612908e01db124b1d4361e95b121fa22
MD5 a60fef2aec6842dfd05c1cded913066d
BLAKE2b-256 60a70d6a92782d9c68f75ef5ee52e67bf6ff88886be5ec0adee994be099a5e91

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for pylopdf-0.7.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6aee3ea1eac846c41e6f7202f0f77bd9b39f17ecb5b214a8f2475c6788dceda2
MD5 d93e86758d314da059c6db71c196c323
BLAKE2b-256 189902623cc96413ecd83666ad1fa8fbbd169dba9de3a16e01c5583399bab3e9

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for pylopdf-0.7.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 998c5d9b78cb2109bc21024d8d8b0af42acbd561fae6afbf4a82f59731631d2c
MD5 e4135863e8c5a085bbb4c6750a082a1c
BLAKE2b-256 7f4dc63788ac44def8e9119cffcdd40a497a895ed586315615a0373b427526cb

See more details on using hashes here.

Provenance

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page