Deterministic PDF/DOCX parser for RAG — Rust core, Python & TS bindings
Project description
English · 中文
▶ Live playground — drag a PDF, watch it parse in your browser (nothing is uploaded)
Deterministic PDF/DOCX parser for RAG / LLMs — one Rust core, with Python, Node & WASM bindings that produce byte-identical output.
pdfmuse is a precision pre-layer for AI/RAG: it extracts everything a file actually contains — text with exact coordinates, fonts, vector rules, tables, links — fast, robustly, and identically across every binding. It stops cleanly at the ML boundary: OCR and visual layout inference are left to a pluggable backend, so the core stays deterministic with zero ML dependencies. It is not another probabilistic vision model.
Why pdfmuse
| Complete | Keeps the finest-grained chars + coordinates; never silently drops content. |
| Fast | Zero-copy streaming Rust core with a custom O(1) object parser + content tokenizer and per-page parallelism. |
| Robust | A broken page/object never sinks the doc — returns structured errors, never panics (fuzz-tested). |
| Deterministic | Same input → same output. No probabilistic models, no time/RNG in the core path. |
| Consistent | Python / Node / WASM call one Rust core; output is byte-identical (CI-enforced). |
| CJK first-class | CID/Type0 fonts + CMap/ToUnicode in the main path; compatibility codepoints NFKC-normalized for clean search. |
Performance
Two things matter for a RAG pre-layer: how fast, and whether it keeps the content.
Per-document latency — median over 200 runs, a 1-page 242 KB résumé, Apple Silicon:
| engine | time / doc |
|---|---|
| pdfmuse — Rust core | ~1.3 ms |
pdfmuse — @pdfmuse/node (native binding) |
~1.5 ms |
pdfmuse — @pdfmuse/core (WASM) |
~2.2 ms |
| PyMuPDF — mature C library | ~6.8 ms |
| pdfplumber — Python, common RAG choice | ~91 ms |
For the text path use to_text() / to_markdown() — they return a string straight from the Rust core, so Python and Node keep that ~1.3 ms speed (~4× PyMuPDF). parse() returns the full IR (chars + coordinates), which adds host-side deserialization if you consume it as objects.
Across 22 real-world PDFs (resumes, reports, invoices; median of 7 runs, core-to-core, each returning a string):
| vs | result |
|---|---|
| PyMuPDF | ~4× faster — wins every file in the sample |
| pdfplumber | ~28–39× faster |
Content is preserved (median 100% non-whitespace character coverage vs PyMuPDF). Numbers are hardware-dependent — reproduce with benches/ (python benches/compare.py) and eyeball fidelity with examples/visual_check.py.
Install
# Rust
cargo add pdfmuse-core
# Python (abi3 wheels)
pip install pdfmuse
# Node
npm install @pdfmuse/node # native binding
# WASM (browser)
npm install @pdfmuse/core # or build: wasm-pack build crates/pdfmuse-wasm --target web
Usage
CLI (debug/inspection):
pdfmuse parse report.pdf --format md # structured Markdown (headings, tables)
pdfmuse parse report.pdf --format json # full IR (chars, bboxes, blocks, warnings)
Rust:
let data = std::fs::read("report.pdf")?;
let doc = pdfmuse_core::parse(&data, None)?; // auto-detect PDF/DOCX
for page in &doc.pages {
for ch in &page.chars { /* ch.text, ch.bbox {x0,y0,x1,y1}, ch.size */ }
}
let md = pdfmuse_core::to_markdown(&doc);
let chunks = pdfmuse_core::chunk(&doc); // RAG chunks + {page, bbox, heading_path}
Python:
import pdfmuse
doc = pdfmuse.parse(open("report.pdf", "rb").read())
text = "".join(c.text for pg in doc.pages for c in pg.chars)
Node:
const { parse_buffer } = require("@pdfmuse/node");
const doc = JSON.parse(parse_buffer(fs.readFileSync("report.pdf")));
WASM (browser — digital PDFs; scanned pages return a NeedsOcr warning to hand off server-side):
import init, { parse } from "@pdfmuse/core";
await init();
const doc = JSON.parse(parse(new Uint8Array(bytes)));
Integrations
-
LangChain —
langchain-pdfmuse: aPdfmuseLoaderwithsingle/page/elementsmodes. Inelementsmode each chunk carries section-aware metadata (heading_path,bbox,category) — reproducible chunks for RAG.from langchain_pdfmuse import PdfmuseLoader docs = PdfmuseLoader("report.pdf", mode="elements").load()
-
LlamaIndex —
llama-index-readers-pdfmuse: aPdfmuseReaderwith the same modes and section-aware metadata.from llama_index.readers.pdfmuse import PdfmuseReader docs = PdfmuseReader(mode="elements").load_data("report.pdf")
Scope boundary
In the core (deterministic): text + coordinates/font/size/color · vector rules & rects · line/paragraph/column clustering · ruled & whitespace-aligned table reconstruction · full DOCX structure · JSON / Markdown / RAG-chunk output.
Out of the core (pluggable VisionBackend): scanned-page OCR · borderless-table structure recognition · heading/body/caption classification. Text-less (scanned/image) pages are flagged NeedsOcr and left for a backend — see docs/adr/0001-pdf-engine-strategy.md.
Guarding this boundary is what keeps pdfmuse fast, stable, and distinct from vision models.
Layout
crates/
pdfmuse-core/ pure-Rust core: PDF/DOCX → unified IR (parser, tokenizer, layout, output)
pdfmuse-python/ PyO3 (abi3) binding
pdfmuse-node/ napi-rs binding
pdfmuse-wasm/ wasm-bindgen binding
pdfmuse-cli/ debug CLI (`pdfmuse`)
tests/{corpus,snapshots} golden corpus + insta snapshots
tests/parity/ cross-binding byte-identical gate (Python == Node == WASM)
examples/visual_check.py render original ↔ coordinate reconstruction for QA
fuzz/ cargo-fuzz targets (never-panic)
Testing gates
- Snapshot tests (
insta+tests/corpus) - Cross-binding parity CI — Python/Node/WASM output byte-identical (a red gate blocks merge)
- Robustness — mutated/garbage input never panics (
tests/robustness.rs+fuzz/) - CJK correctness suite
Status
Core is feature-complete (milestones M0–M4 + real-world hardening M4.5): PDF + DOCX → unified IR → JSON / Markdown / RAG chunks, three byte-identical bindings, encryption, CJK. Currently in M5 · polish & release. Roadmap and tasks live in Linear (project pdfmuse).
License
Dual-licensed under MIT or Apache-2.0, at your option.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pdfmuse-0.1.5-cp38-abi3-win_amd64.whl.
File metadata
- Download URL: pdfmuse-0.1.5-cp38-abi3-win_amd64.whl
- Upload date:
- Size: 746.1 kB
- Tags: CPython 3.8+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f0c6177db65ce9e84e73759c95a2ae1faa0178118815f1c4cb7c4a7ec5d84e1
|
|
| MD5 |
d98e7a08555dfbeee79d7b885133bb83
|
|
| BLAKE2b-256 |
7317bc81750a22648b1e71c28f62bdf3870e319e7f0f6437d08a1f6beea9a81c
|
Provenance
The following attestation bundles were made for pdfmuse-0.1.5-cp38-abi3-win_amd64.whl:
Publisher:
release.yml on casperkwok/pdfmuse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pdfmuse-0.1.5-cp38-abi3-win_amd64.whl -
Subject digest:
9f0c6177db65ce9e84e73759c95a2ae1faa0178118815f1c4cb7c4a7ec5d84e1 - Sigstore transparency entry: 2048550657
- Sigstore integration time:
-
Permalink:
casperkwok/pdfmuse@e9d00fc8ee2ebd3684f777c5e429ad423c41574d -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/casperkwok
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e9d00fc8ee2ebd3684f777c5e429ad423c41574d -
Trigger Event:
push
-
Statement type:
File details
Details for the file pdfmuse-0.1.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: pdfmuse-0.1.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 906.1 kB
- Tags: CPython 3.8+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2698db11d98b5d68b2375cc1e2c2597c8a63fec2b0746fbaed852002aff3c6ba
|
|
| MD5 |
10fb512d2920bca0ec3ed095af28a4ff
|
|
| BLAKE2b-256 |
d3843ec404a4d6fcb824387efb2151b24eefde7585c9141c1530e1c20c2676e6
|
Provenance
The following attestation bundles were made for pdfmuse-0.1.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on casperkwok/pdfmuse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pdfmuse-0.1.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
2698db11d98b5d68b2375cc1e2c2597c8a63fec2b0746fbaed852002aff3c6ba - Sigstore transparency entry: 2048550666
- Sigstore integration time:
-
Permalink:
casperkwok/pdfmuse@e9d00fc8ee2ebd3684f777c5e429ad423c41574d -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/casperkwok
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e9d00fc8ee2ebd3684f777c5e429ad423c41574d -
Trigger Event:
push
-
Statement type:
File details
Details for the file pdfmuse-0.1.5-cp38-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: pdfmuse-0.1.5-cp38-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 799.3 kB
- Tags: CPython 3.8+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9746540872be87501281cd344d103dc54d4e1a9f4e398cb33aada50df10b33db
|
|
| MD5 |
b572e9b5a5e60b9bb6de67bdc7c31456
|
|
| BLAKE2b-256 |
44323222b0a4fa40ffe40da4add45bb0367761f5f5979a3f6d57f9f8ed9dcdbd
|
Provenance
The following attestation bundles were made for pdfmuse-0.1.5-cp38-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on casperkwok/pdfmuse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pdfmuse-0.1.5-cp38-abi3-macosx_11_0_arm64.whl -
Subject digest:
9746540872be87501281cd344d103dc54d4e1a9f4e398cb33aada50df10b33db - Sigstore transparency entry: 2048550651
- Sigstore integration time:
-
Permalink:
casperkwok/pdfmuse@e9d00fc8ee2ebd3684f777c5e429ad423c41574d -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/casperkwok
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e9d00fc8ee2ebd3684f777c5e429ad423c41574d -
Trigger Event:
push
-
Statement type: