rustypaper
Structure-aware conversion of born-digital scientific PDFs to Markdown, Typst and JSON. Fast, CPU-only, no models.
The good open-source converters (Marker, MinerU, Docling, Nougat) are Python stacks that want a GPU; GROBID is CPU-only and fast but is a JVM service that emits TEI and ignores maths. The Rust crates that exist are generic text extractors with no notion of a paper. This aims at the gap: structure-aware, maths-aware, CPU-only, single binary.
Status: feature complete. Converts one- and two-column papers to Markdown, Typst, plain text or JSON, with reading order, figures, tables, mathematics and references.
Install
cargo install rustypaper # the command-line tool
rustypaper = "0.1" # the library
pip install rustypaper # the Python bindings
Nothing else to install. The default build reads PDFs with rustium-pdf, a pure-Rust interpreter, so there is no native library to fetch, point an environment variable at, or match versions with. The CLI is a single static binary and the wheel is self-contained.
pdfium — the Chromium PDF engine, behind FFI — remains available as an opt-in feature. It is the reference the pure-Rust backend is measured against, and an escape hatch for documents rustium cannot yet read. Asking for it means supplying it:
cargo build --release --no-default-features --features pdfium
scripts/fetch-pdfium.sh # or set PDFIUM_DYNAMIC_LIB_PATH yourself
Getting started
scripts/fetch-corpus.sh # evaluation corpus of arXiv papers, not committed
scripts/build.sh # cargo build --release, plus installing the Python extension
# Convert.
./target/release/rustypaper convert corpus/resnet.pdf
./target/release/rustypaper convert corpus/resnet.pdf --format typst --assets figures/
./target/release/rustypaper convert corpus/*.pdf --out out/ # batch
./target/release/rustypaper convert paper.pdf --caveman=hard # -25% words for LLM ingestion
# Diagnostics.
./target/release/rustypaper probe corpus/resnet.pdf --pages # counts, fonts, detected gutters
./target/release/rustypaper text corpus/resnet.pdf --geometry # reconstructed lines
./target/release/rustypaper dump corpus/resnet.pdf --page 0 --pretty
probe prints per-page counts, the font histogram and the detected gutters. Gutters are the
first thing to check when a two-column paper comes out interleaved; the font histogram is the
first thing to check when text comes out wrong.
Design
Read docs/ARCHITECTURE.md for the pipeline, the coordinate convention,
and the eight things M0 and M1 discovered the hard way — most importantly that pdfium-render's
thread_safe feature does no locking at all.
The short version: the backend produces a PageRaw of glyphs, paths and images, and every later
stage is a pass over an IR. The Document JSON is the contract; Markdown, Typst and plain text
are renderings of it.
| speed | 3.8–7.0 ms/page, single process |
| corpus | 10 papers: ML, pure maths, physics, biology, statistics |
| memory | 17–31 MB peak for a whole paper |
| footprint | a 2.3 MB static binary, no native library, no models |
Unicode repair turned out not to be needed — glyph-name fallback already resolves TeX ligatures, so the tables the plan budgeted for were dropped. De-hyphenation could not work the way the plan assumed either: soft line-break hyphens are removed before the text reaches the pipeline, so there is no hyphen to key off. Words split across a break are rejoined using the document's own vocabulary instead, which needs no word list and knows the paper's jargon.
Maths is reconstructed geometrically, MaxTract-style, from exact glyph identities and positions rather than by OCR — a born-digital PDF hands you perfect character information, so image-to- LaTeX models are solving a problem this pipeline does not have. Equations carry a confidence score and fall back to a rendered crop rather than emitting confident-looking nonsense.
Scanned documents are explicitly out of scope: extract fails with Error::Scanned rather than
pretending.
Milestones
| status | ||
|---|---|---|
| M0 | Backend, PageRaw, CLI, corpus, thread-safety spike |
done |
| M1 | Lines/words, furniture removal, columns, reading order → Markdown | done |
| M2 | Figures, captions, footnotes, lists, de-hyphenation | done |
| M3 | Tables | done |
| M4 | Maths detection and reconstruction | done |
| M5 | References and citation linking | done |
| M6 | Typst emitter, performance pass, batch mode | done |
Python
The core is Rust; the tooling around it is Python, because evaluation, corpus management and comparison against other converters are scripting jobs.
scripts/build.sh
PYTHONPATH=python python3 -c "
import rustypaper
print(rustypaper.to_markdown('corpus/resnet.pdf')[:80])
doc = rustypaper.to_document('corpus/resnet.pdf') # the document model as a dict
"
ScannedDocument is raised for image-only PDFs, so callers can route those to an OCR pipeline
instead. Conversion releases the GIL.
Evaluation
Quality is measured, not eyeballed. Papers submitted to arXiv as TeX source come with the prose their PDF was rendered from, which is free ground truth for exactly this document class — for the subset of papers that have it. PDF-only submissions have none, and are reported as skipped rather than scored.
cd eval && PYTHONPATH=.:../python python3 -m rustypaper_eval
Current scores across the nine scorable papers:
| metric | value | what it says |
|---|---|---|
| prose bigram recall | 0.894 | prose comes out right, in the right order |
| equation recall | 0.375 | most display equations are not found |
| equation fidelity | 0.557 | those that are found are roughly half right |
| tables | 57 found / 44 in source | badly distributed: 0 on one paper, over-detected on another |
The maths numbers are the honest state of the differentiator, and they are the project's
weakest point — see docs/ARCHITECTURE.md. See
eval/README.md for what the metrics mean and why plain edit distance is the
wrong primary measure here.
Testing
cargo test # unit, robustness and corpus tests; corpus tests
# skip if corpus/ is empty
python3 -m unittest discover -s eval/tests # the eval harness's own tests
cd eval && PYTHONPATH=.:../python python3 -m rustypaper_eval --baseline baseline.json
Integration tests live in rustypaper/tests/corpus.rs and run against real papers. They skip
rather than fail when the corpus is absent, so a fresh clone is green.
Licence
MIT OR Apache-2.0.
That covers everything a default build contains, the published crate and the published wheels included: the dependency tree is Rust, and rustium-pdf is MIT OR Apache-2.0 as well.
The pdfium feature is the exception, and only for whoever turns it on. pdfium is BSD-3-Clause
and vendors further third-party code (FreeType, libjpeg-turbo, zlib, ICU and others) under
their own terms. After running scripts/fetch-pdfium.sh those notices are in
vendor/pdfium/licenses/, and vendor/pdfium/LICENSE-packaging is the MIT licence of
pdfium-binaries, which builds and packages it.
If you redistribute a binary that bundles libpdfium.so, ship vendor/pdfium/licenses/ with
it. Nothing released from this repository does.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
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 rustypaper-0.1.1.tar.gz.
File metadata
- Download URL: rustypaper-0.1.1.tar.gz
- Upload date:
- Size: 121.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8701cbea6c12f8fb24fc88b27f18ac1236e82725ab3382ce66314efcad74a73f
|
|
| MD5 |
e9f2511b507e1ee67b8b71aa369ef5a1
|
|
| BLAKE2b-256 |
e874270d39feaee532f88e06d8d0c16dee31cdda690b84aeaa5fb147ed87a472
|
Provenance
The following attestation bundles were made for rustypaper-0.1.1.tar.gz:
Publisher:
release.yml on pgarrett-scripps/rustypaper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustypaper-0.1.1.tar.gz -
Subject digest:
8701cbea6c12f8fb24fc88b27f18ac1236e82725ab3382ce66314efcad74a73f - Sigstore transparency entry: 2330443645
- Sigstore integration time:
-
Permalink:
pgarrett-scripps/rustypaper@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pgarrett-scripps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustypaper-0.1.1-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: rustypaper-0.1.1-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 1.0 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de7e2431b98d3deeb662dab16f5120e047619cc8f6232f92b52b087aee20bc13
|
|
| MD5 |
559760383cc669076a0e7fdbfbd376a6
|
|
| BLAKE2b-256 |
df73a8c9e58a08986445d3526e7326b3bccdcf010cec8f34b872f8ccf03de897
|
Provenance
The following attestation bundles were made for rustypaper-0.1.1-cp39-abi3-win_amd64.whl:
Publisher:
release.yml on pgarrett-scripps/rustypaper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustypaper-0.1.1-cp39-abi3-win_amd64.whl -
Subject digest:
de7e2431b98d3deeb662dab16f5120e047619cc8f6232f92b52b087aee20bc13 - Sigstore transparency entry: 2330443938
- Sigstore integration time:
-
Permalink:
pgarrett-scripps/rustypaper@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pgarrett-scripps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustypaper-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: rustypaper-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 1.2 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f69e53a40cd345a89d31ee5966ab3bf9e4435fc5fc1ce9291c1c8ca8e402c356
|
|
| MD5 |
73a5dccf55d67892f1147fc789b288cb
|
|
| BLAKE2b-256 |
d8a43128c96cdf40231cc78f0077dc85288fbfb920424bbe6f087fa0026dc458
|
Provenance
The following attestation bundles were made for rustypaper-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on pgarrett-scripps/rustypaper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustypaper-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
f69e53a40cd345a89d31ee5966ab3bf9e4435fc5fc1ce9291c1c8ca8e402c356 - Sigstore transparency entry: 2330443730
- Sigstore integration time:
-
Permalink:
pgarrett-scripps/rustypaper@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pgarrett-scripps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustypaper-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: rustypaper-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1a902a8967a122526c2656db08681262bd2cabb6994280ac263ba7920e776af
|
|
| MD5 |
3924c31047974e2364a828f8fd21377a
|
|
| BLAKE2b-256 |
6b9f8f43df2fc171478d35e120d2179582cac2851caa9433a2c4826b0dfe78f1
|
Provenance
The following attestation bundles were made for rustypaper-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on pgarrett-scripps/rustypaper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustypaper-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
b1a902a8967a122526c2656db08681262bd2cabb6994280ac263ba7920e776af - Sigstore transparency entry: 2330443838
- Sigstore integration time:
-
Permalink:
pgarrett-scripps/rustypaper@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pgarrett-scripps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustypaper-0.1.1-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: rustypaper-0.1.1-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.0 MB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c13039e4e2b74e83cb822b5ce18c0b161faa7ae5701c67d6af0cf8f8f66b0494
|
|
| MD5 |
acafbbe1d0e9ad2dd28b48d5b8ad1184
|
|
| BLAKE2b-256 |
d5510d5e7d0222b4fe8b08e754024dadd54561991fa82ea2356deaf4c00bc307
|
Provenance
The following attestation bundles were made for rustypaper-0.1.1-cp39-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on pgarrett-scripps/rustypaper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustypaper-0.1.1-cp39-abi3-macosx_11_0_arm64.whl -
Subject digest:
c13039e4e2b74e83cb822b5ce18c0b161faa7ae5701c67d6af0cf8f8f66b0494 - Sigstore transparency entry: 2330444032
- Sigstore integration time:
-
Permalink:
pgarrett-scripps/rustypaper@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pgarrett-scripps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustypaper-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: rustypaper-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.9+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
410497e0704215ff3664446f1e8b5eb0dede649f1fa97df193081b62c7c88993
|
|
| MD5 |
8bf5c39e836ede0d096e7cda3fd9f861
|
|
| BLAKE2b-256 |
91ae167e41faf8a2cddec94d934f88df1785c82738ec00cb8859414cd1e71ccf
|
Provenance
The following attestation bundles were made for rustypaper-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl:
Publisher:
release.yml on pgarrett-scripps/rustypaper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustypaper-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl -
Subject digest:
410497e0704215ff3664446f1e8b5eb0dede649f1fa97df193081b62c7c88993 - Sigstore transparency entry: 2330444230
- Sigstore integration time:
-
Permalink:
pgarrett-scripps/rustypaper@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pgarrett-scripps
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@592f1f27d7bb09b7487b88e8f3234691b42e18b5 -
Trigger Event:
push
-
Statement type: