Skip to main content

pdfspine

PyPI

An Apache-2.0-licensed, pure-Rust reimplementation of PyMuPDF (fitz), with PyO3 Python bindings.

🦴 Part of the spine family — framework-free backend engines, each the spine of a domain: zero framework lock-in, Protocol-ized seams, offline-capable. pdfspine is the PDF spine (this repo); ragspine is the RAG spine (deterministic dual-channel retrieval + agent orchestration).

🤖 For AI agents / LLMs: before using this library, read llms.txt (concise index) and python/pdfspine/_llms/docs/ (full API / recipes / gotchas); after pip install they ship at site-packages/pdfspine/_llms/.

Status: alpha / pre-1.0, but the core is feature-complete. pdfspine can already parse/repair/decrypt PDFs, extract text & tables, search, edit / merge / split / save (incl. byte-exact incremental), encrypt, annotate, fill & flatten forms, redact (destructively), open image files as documents, render pages to images, and OCR (pure-Rust PaddleOCR PP-OCRv5 ONNX by default, stronger on CJK; Tesseract as an optional fallback). 91.4% (703 / 769) of the PyMuPDF 1.24 public API is implemented and tested in the current local source. Published v0.11.2 covers 91.4% (703 / 769). The named 2026-09-11 local paragraph-dash full gate passed 2,034 Rust + 1,535 Python tests (68 existing Python skips). See validation baselines for exact source and binary identities. In the dated 58-document benchmark, its aggregate mean text scores trail fitz by 0.2–1.4 percentage points (and it beats fitz on Arabic / RTL); rendering is at/near parity (SSIM 0.984 mean, 2026-06-21) though about 1.3× slower than fitz (11.40 vs 8.93 ms, conformance/BENCH.md, 2026-09-05), and the pure-Rust PaddleOCR engine beats fitz on CJK scans (see Accuracy). Now on PyPI: pip install pdfspine (see Install); or build from source.


Spine 家族 / Spine family

本仓库是 Spine 家族的成员之一(角色:L1 引擎(含 pdf-typeset / pdf-fonts 被 git dep))。家族全部成员、分层、依赖方向、依赖形式与当前差距见 docs/spine-family.md;该文件在每个家族仓库中的副本内容相同,真源在家族根目录 ~/startup/spine/docs/spine-family.md,用根目录 make family-doc-sync 同步。

Why pdfspine?

PyMuPDF is excellent, but it is AGPL-3.0 (or a commercial license from Artifex) — a non-starter for many closed-source products, SaaS backends, and permissively-licensed open-source projects.

pdfspine is a drop-in-shaped, permissively-licensed (Apache-2.0) alternative:

  • Apache-2.0 throughout — permissive, with an explicit patent grant. The dependency graph is gated by cargo-deny to exclude GPL / AGPL / LGPL / MPL / SSPL from the shipped wheel. License cleanliness is CI-enforced, not a promise.
  • Pure Rust, no C blob. Self-contained wheels, no system zlib/C linkage, no bundled prebuilt engine (the differentiator vs pdfium-based wrappers).
  • import fitz compatible (opt-in). A compatibility shim lets much existing PyMuPDF code run unmodified — available as import pdfspine.fitz as fitz, or registered under the global fitz / pymupdf names with one call to pdfspine.install_fitz_shim(). A default install is collision-safe: it does not claim those global names, so it coexists with a real PyMuPDF in the same environment. A machine-readable COMPAT.toml documents every symbol's status.
  • Memory-safe by construction. #![forbid(unsafe_code)] in every first-party crate except the single audited PyO3 FFI chokepoint.
  • Clean-room. No code, tests, or fixtures derived from MuPDF / PyMuPDF / any AGPL source.

What works today

Area Capabilities
Read open (file/bytes), malformed-PDF repair, encrypted PDFs (RC4 / AES-128 / AES-256, R2–R6)
Text get_text (text/words/blocks/dict/rawdict/json/rawjson/html/xhtml/xml), per-glyph rendering geometry, search_for, TextPage, fonts/images inventory
Tables find_tables with merged-cell detection → extract() / to_markdown() / to_html(); typed slots / origin_cells distinguish blank, unavailable and merged slots; optional Microsoft TATR vision backend for borderless tables
Edit & save full + byte-exact incremental save, garbage collection, page insert/delete/copy/move/select, insert_pdf merge, metadata/XMP, TOC, links, encryption write
Annotate all common annotation types with /AP appearance streams; AcroForm read / fill / flatten + Widget; destructive redaction (verified content removal)
Render get_pixmap (vector + text + image + shadings via a tiny-skia rasterizer), Pixmap (buffer-protocol/numpy), DisplayList, get_svg_image
Images open PNG/JPEG/TIFF/GIF/BMP/WEBP as documents, convert_to_pdf, image-XObject decode (DCT/CCITT/JBIG2/JPX), extract_image
Markdown markdown_to_pdf() — a pdfspine original extension (not a PyMuPDF API): CommonMark + GFM tables / strikethrough / task lists → PDF via a deterministic pure-Rust layout engine; local & data:-URI images (never the network); optional user TTF via font= / cjk_font= (CJK)
Markdown / layout export Page.to_markdown() / Document.to_markdown() / save_markdown() and get_text("layout") — a pdfspine original extension (not a PyMuPDF API): PDF → Markdown for RAG / LLM pipelines (the reverse of markdown_to_pdf) plus layout-preserving plain text
Layers Optional Content Groups read/write (get_ocgs / add_ocg / set_layer)
OCR pure-Rust PaddleOCR by default (PP-OCRv5, weights from the shared ocrspine-models package, stronger on CJK), with an explicit Tesseract compatibility adapter → searchable-sandwich PDF
CLI pdfspine info / text / render / merge / split / pages / images / toc

Planned next: reading-order residuals, Type0/Type3 glyph-rendering edges, broader CJK coverage. See PRD.md / docs/ROADMAP.md. Out of scope: digital-signature creation.

Glyph geometry

The structured dict / rawdict / json / rawjson formats expose span matrix, text_matrix, ctm, dir, quad, seq, declared_size, and rendered_size; raw characters also expose matrix, quad, rendered_size, seq, and synthetic. The first glyph determines span["size"]; it equals span["rendered_size"] and sqrt(abs(det(matrix))), while declared_size preserves the signed PDF Tf operand. Use each raw character's rendered_size when glyph sizes vary.

matrix / quad use the page's device space, while text_matrix / ctm remain in PDF user space. HTML / XHTML / XML and get_texttrace() retain their existing declared-size semantics, and rotated-page coordinates and geometry-aware span boundaries can differ from PyMuPDF. See the text extraction guide for the field contract and the geometry, size parity, span parity, and performance reports for the measured limits and cost.

Install

pip install pdfspine

pdfspine is on PyPI. OCR works out of the box: the PP-OCRv5 weights ship in the shared ocrspine-models data package — a runtime dependency pip pulls in automatically — so the wheel itself stays lean and no longer embeds them. To build from source instead, see Build & install.

Python 3.12+ is supported. Prebuilt wheels are published for Linux x86-64 / ARM64, macOS Intel / Apple silicon, and Windows x86-64; an sdist is also available for other supported environments.

Quick start

import pdfspine

doc = pdfspine.open("input.pdf")
print(len(doc), "pages", doc.metadata)

page = doc[0]
print(page.get_text())                       # plain text
print(page.search_for("invoice"))            # list[Rect]
page.get_pixmap(dpi=150).save("page1.png")   # render to image

# pdfspine extension: immutable, fail-closed source paint/resource audit.
# `complete` is an accounting result, not a semantic or visual qualification.
paint = page.get_paint_profile()
print(paint.version, paint.complete, paint.diagnostics)

tables = page.find_tables()
for t in tables.tables:
    print(t.to_markdown())                    # or t.to_html() for merged cells
    for row in t.slots:                       # native, TATR and ONNX share this API
        print([(slot.state, slot.origin) for slot in row])

# Optional: pip install "pdfspine[tatr]", then prefetch the pinned checkpoints.
# vision_tables = page.find_tables(strategy="vision", backend="tatr")

doc.save("output.pdf", garbage=4, deflate=True)
doc.save_html("output.html")                 # complete UTF-8 HTML5 document
md = doc.to_markdown()                       # PDF → Markdown (pdfspine extension)
doc.save_markdown("output.md")               # UTF-8 Markdown for RAG / LLM

# Markdown → PDF (pdfspine original extension — not part of the PyMuPDF surface)
pdfspine.markdown_to_pdf("# Title\n\nHello **Markdown**!").save("hello.pdf")

Existing PyMuPDF code often runs unchanged via the opt-in compat shim:

import pdfspine.fitz as fitz                  # the shim, no global-name collision
doc = fitz.open("input.pdf")
text = doc[0].get_text("dict")

# Or make the literal `import fitz` resolve to the shim (one-time opt-in):
import pdfspine
pdfspine.install_fitz_shim()
import fitz                                    # now -> pdfspine's fitz shim

A default install does not claim the global fitz / pymupdf names, so it is safe alongside a real PyMuPDF; install_fitz_shim() uses setdefault and never clobbers a PyMuPDF you imported first.

Command line:

pdfspine info report.pdf
pdfspine text report.pdf --pages 1-3 --format json -o out.json
pdfspine render report.pdf --dpi 200 -o images/
pdfspine merge a.pdf b.pdf -o merged.pdf

Accuracy

Validated against an objective ground-truth harness and with PyMuPDF (fitz) as the differential oracle (clean-room: the AGPL oracle is run locally only and never committed). See docs/BENCHMARKS.md and the conformance/gt/ reports for the dated, reproducible evidence.

  • In the dated 58-document benchmark, pdfspine's aggregate mean edit-similarity, token-F1, word-set-Jaccard, and reading-order scores trail fitz by 0.2–1.4 percentage points. It reaches parity on selected born-digital metrics and beats fitz on Arabic / RTL (correct bidi reordering).
  • Rendering is at/near parity with fitz (page-image SSIM 0.984 mean / 0.989 median over a 46-document sample, 2026-06-21). Speed, per the dated 2026-09-05 conformance/BENCH.md: open 1.3× and text extraction 2.3× faster than fitz; rendering about 1.3× slower (11.40 vs 8.93 ms per page at 150 dpi).
  • OCR beats fitz on CJK scans: the pure-Rust PaddleOCR engine (PP-OCRv5, with weights from the shared ocrspine-models package) outperforms fitz's OCR path on Chinese/Japanese/Korean documents.
  • Real-corpus robustness: open rate 100%, 0 panics/hangs, re-saved files 100% qpdf --check-clean across the public-domain US-government corpus.

Remaining accuracy work (reading-order residuals, Type0/Type3 glyph-rendering edges, broader CJK) is tracked in docs/PRD-NEXT.md.

Build & install

Requirements: Rust (pinned to 1.96.0 by rust-toolchain.toml), Python ≥ 3.12, maturin ≥ 1.12,<2. uv recommended.

uv venv .venv && source .venv/bin/activate
maturin develop                 # build + install the extension in-place
python -c "import pdfspine; print(pdfspine.__version__)"
# redistributable wheel:
maturin build --release         # -> target/wheels/

Building from source needs a C/asm compiler. The bundled pure-Rust PaddleOCR engine depends on tract, which compiles target-specific assembly kernels at build time: a C compiler (cc/clang) on Linux/macOS, or the MSVC Build Tools (incl. ml64.exe) on Windows. Prebuilt PyPI wheels need none of this. To build a fully C-free library, compile the Rust crates with --no-default-features (drops the paddle-ocr feature). The wheel no longer embeds the OCR models — they ship in the shared ocrspine-models package (a runtime dependency).

Architecture

A Cargo workspace with a strict dependency DAG; the Python bindings touch exactly one façade crate, and core logic is split into independently testable units.

                  py-bindings   (PyO3 cdylib -> pdfspine._core, abi3-py311)
                       │
                       ▼
                    pdf-api      facade / re-exports
        ┌──────────┬───┴────┬──────────┐
        ▼          ▼        ▼          ▼
    pdf-text   pdf-edit  pdf-image  pdf-render
        │          │        │          │
        └────┬─────┘        │     (fonts, text)
             ▼              │
         pdf-fonts ◄────────┘
             ▼
         pdf-core   ◄────────  pdf-crypto
Crate Responsibility
pdf-core object model, lexer/parser, xref, repair, filters, writer, geometry
pdf-crypto Standard security handler (RC4 / AES-128 / AES-256)
pdf-fonts font mapping (encodings / ToUnicode / CMap / widths)
pdf-text content-stream interpreter, get_text, search, find_tables
pdf-edit page ops, merge, annotations / forms, metadata / TOC, redaction, OCG
pdf-image image documents, image-XObject codecs, Pixmap
pdf-render tiny-skia rasterizer → Pixmap, DisplayList, SVG
pdf-api unified ergonomic façade
py-bindings PyO3 wrappers → the _core extension module

Develop / test

./ci.sh                                      # complete local/CI/pre-push gate
# Individual focused checks remain available:
python scripts/quality_gate.py --help
python conformance/run_validation.py …       # real-corpus accuracy harness

The gate runs rust → extension → python → drift → artifacts. The extension phase fingerprints the Rust inputs (crates/**, Cargo.toml, Cargo.lock, pyproject.toml, rust-toolchain*) against .gate/extension.stamp and re-runs maturin develop --release when they differ, so pytest never runs against a stale _core. Skip it with --skip-extension-check or PDFSPINE_GATE_SKIP_EXTENSION=1.

pdfspine is built strictly test-first (red → green → refactor → harden); the per-function test plan is in docs/test-case-catalog.md.

Documentation

Guide + API reference + PyMuPDF migration guide: build the docs site with mkdocs serve (see mkdocs.yml / docs/). The authoritative design lives in PRD.md.

License

Apache-2.0 — see LICENSE and NOTICE. All third-party dependencies are permissive (MIT / Apache-2.0 / BSD / Zlib / …); the shipped graph is CI-verified free of copyleft.

Metadata

Release files for pdfspine 0.11.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pdfspine 0.11.2
File Size Uploaded
pdfspine-0.11.2.tar.gz 5.2 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for pdfspine 0.11.2
File
pdfspine-0.11.2-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details
pdfspine-0.11.2-cp311-abi3-manylinux_2_28_aarch64.whl CPython 3.11 abi3 Linux glibc 2.28+ ARM64 Details
pdfspine-0.11.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details
pdfspine-0.11.2-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details
pdfspine-0.11.2-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details

Total release size: 65.2 MB

Release files / pdfspine-0.11.2.tar.gz

Download URL pdfspine-0.11.2.tar.gz
Size 5.2 MB
Tags Source
SHA-256 checksum
How to use checksums
f822c3fc1058844d30d7599e45265333558d7c6f1201fab5104cccd9ece95f6c
BLAKE2b-256 checksum
How to use checksums
68989b07820f6b6e7df86debf526ba88a0a15f1f3affc53d2679e4edf505be93
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / pdfspine-0.11.2-cp311-abi3-win_amd64.whl

Download URL pdfspine-0.11.2-cp311-abi3-win_amd64.whl
Size 12.6 MB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
b9dc4f65ab6ef94c9dfb14080ac006bb020181d94dd35e4617e3f12e0a6b779b
BLAKE2b-256 checksum
How to use checksums
59afaa4499fc3caf385040df16ad194a71f8002ab00d559d32b84c20e08a87dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / pdfspine-0.11.2-cp311-abi3-manylinux_2_28_aarch64.whl

Download URL pdfspine-0.11.2-cp311-abi3-manylinux_2_28_aarch64.whl
Size 11.4 MB
Tags CPython 3.11 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
5b125b54ca0abdd4db357ece8f06d46c4d925e59229ddef15c3415bfee301b74
BLAKE2b-256 checksum
How to use checksums
595b49bd7f2c78bc4b993b38bdde269353c1309319995681018de425c7347fd0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / pdfspine-0.11.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pdfspine-0.11.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 12.5 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
53e786e437ccab966a10ef9e715070f7f0f37ae7a11ea1da7fe2cfe4d7d33fd7
BLAKE2b-256 checksum
How to use checksums
ccb78cf60029d87ea90d78e8dcefa405a04d026e49227f43fdfb7ff4a5463a9a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / pdfspine-0.11.2-cp311-abi3-macosx_11_0_arm64.whl

Download URL pdfspine-0.11.2-cp311-abi3-macosx_11_0_arm64.whl
Size 11.3 MB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
31976c2dffc090842225e91e3fa4d59def406999ac058b6577824c10fbfaab5c
BLAKE2b-256 checksum
How to use checksums
504ef20b4a9b9d6422f869268c500a6a7f676cfe7843d33b2ea721f8ccaa074c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / pdfspine-0.11.2-cp311-abi3-macosx_10_12_x86_64.whl

Download URL pdfspine-0.11.2-cp311-abi3-macosx_10_12_x86_64.whl
Size 12.1 MB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
e2547533735cd1c3b0a61b8d821014cb4ec935c91e18f5bdebdac9c7290f0c7a
BLAKE2b-256 checksum
How to use checksums
d99f1a863847b7145f2d71aebc93963aa095c2909e92d79948434aac2b4b520a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.11.2 This release

6 release files

0.11.1

6 release files

0.11.0

6 release files

0.10.0

6 release files

0.9.1

6 release files

0.9.0

6 release files

0.8.0

6 release files

0.7.1

6 release files

0.7.0

6 release files

0.6.1

6 release files

0.6.0

6 release files

0.5.0

6 release files

0.4.1

6 release files

0.4.0

7 release files

0.3.0

6 release files

0.2.0

7 release files

0.1.2

7 release files

0.1.1

5 release files

0.0.6

5 release files

0.0.5

5 release files

0.0.4

5 release files

0.0.3

5 release files

0.0.2

5 release files

0.0.1

2 release 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