Skip to main content

rustypaper

Structure-aware conversion of born-digital scientific PDFs to Markdown, Typst, JSON and plain text. Headings, reading order, figures, tables, equations and references, on a CPU, with no models and no native libraries.

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

pip install rustypaper
rustypaper convert paper.pdf
python -m rustypaper convert paper.pdf

The Python installation includes the same command line interface as the Rust executable. All commands and options are shared, including batch conversion, figure extraction, and diagnostics. No Rust compiler is needed when installing a supported platform wheel.

Standalone executables are available on the GitHub Releases page. Download the archive for your platform, verify it against SHA256SUMS, extract it, and put rustypaper (or rustypaper.exe) on your PATH. These executables do not need Python.

Platform Targets
Linux with glibc 2.35 or newer x86_64, aarch64
macOS Intel, Apple Silicon
Windows x86_64

Alternatively, use cargo-binstall to download a release executable, or Cargo to compile it locally:

cargo binstall rustypaper
cargo install rustypaper

For the Rust library:

rustypaper = "0.2"

Nothing else to install. PDFs are read with rustium-pdf, a pure-Rust interpreter, so there is no native library to fetch, point an environment variable at, or match versions with. ldd on the binary shows libc, libm and libgcc and nothing else; the wheel is the extension module and the Python package around it, with no C library travelling beside it.

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       # -24% 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, the PageSource boundary, and the things the corpus taught this converter the hard way.

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 2.5–8.7 ms/page, single process
corpus 16 papers in 10 template families: ML, pure maths, physics, biology, medicine, statistics
memory 13–30 MB peak for a whole paper
footprint a 3.2 MB 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 needed a second mechanism the plan did not anticipate: the page is read as written, so a soft line-break hyphen is usually there and is preferred when it is, but where a document leaves none the words split across the break are rejoined using the document's own vocabulary, 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
for section in doc['sections']:                     # the outline, with block ranges
    print(section['level'], section['title'], section['start'], section['end'])

markdown, doc = rustypaper.convert('corpus/resnet.pdf')   # both, from one pipeline run
print(rustypaper.to_typst('corpus/resnet.pdf')[:80])
print(rustypaper.to_text('corpus/resnet.pdf')[:80])
"

sections is the document's outline: each entry has a title (None for the front matter that precedes every heading), a level, a half-open start/end range into blocks that includes its nested children, and the pages it spans. A consumer that wants the methods section can slice the blocks rather than re-deriving structure from the Markdown.

ScannedDocument is raised for image-only PDFs, so callers can route those to an OCR pipeline instead. Conversion releases the GIL, so several threads convert in parallel.

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 fifteen scorable papers:

metric value what it says
prose bigram recall 0.900 prose comes out right, in the right order
equation recall 0.565 most display equations are found; templates vary widely
equation fidelity 0.677 the found ones are mostly right, not wholly right
tables 62 found / 90 in source five papers complete; ImageNet's 9/26 is the largest gap
references 971 found / 1174 in source eight bibliographies complete; topological's 163/368 is a column gap, not a parsing one
sections 206 matched / 264 in source the deficit is subsections set bold at body size

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
PYTHONPATH=python pytest python/tests -q      # the Python surface, against the corpus
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 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.

Metadata

Release files for rustypaper 0.2.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 rustypaper 0.2.2
File Size Uploaded
rustypaper-0.2.2.tar.gz 162.2 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for rustypaper 0.2.2
File
rustypaper-0.2.2-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
rustypaper-0.2.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64 Details
rustypaper-0.2.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details
rustypaper-0.2.2-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details
rustypaper-0.2.2-cp39-abi3-macosx_10_12_x86_64.whl CPython 3.9 abi3 macOS 10.12+ x86-64 Details

Total release size: 7.3 MB

Release files / rustypaper-0.2.2.tar.gz

Download URL rustypaper-0.2.2.tar.gz
Size 162.2 kB
Tags Source
SHA-256 checksum
How to use checksums
06a5b704b50b049942c688fc44f9203ecd55f92fb7260d78c57c68c786045c48
BLAKE2b-256 checksum
How to use checksums
a6973166b44ee81340a0552e7a1f40203d7f17e195e6f73b20fdef4c14eb07dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / rustypaper-0.2.2-cp39-abi3-win_amd64.whl

Download URL rustypaper-0.2.2-cp39-abi3-win_amd64.whl
Size 1.4 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
b4f0763113af74cbf50e09bc52d91817b465248c2f4e945830c72811d437d16d
BLAKE2b-256 checksum
How to use checksums
66c92f4a36c7d1c6999cf8444a1f6068a52e74c84ab1ecbc260f4697cab28545
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / rustypaper-0.2.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL rustypaper-0.2.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.5 MB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
36ebd16808c77fa325db80660ad92b28de61bd3c4f3f67593e32ac57c94979b9
BLAKE2b-256 checksum
How to use checksums
0f242a310b48b431c86e0d86f75e0257d1e40de82ecec57e95b358063146dbf3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / rustypaper-0.2.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL rustypaper-0.2.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.5 MB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
287dcf0c8aa008438610ca0b3b58678297c09e1d9d94ded0cbb48563dc02c79a
BLAKE2b-256 checksum
How to use checksums
b03686e4e7d111440abc0c7570b19d426d6d7b3a9fce8af286851b19cf449b33
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / rustypaper-0.2.2-cp39-abi3-macosx_11_0_arm64.whl

Download URL rustypaper-0.2.2-cp39-abi3-macosx_11_0_arm64.whl
Size 1.3 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
76f0790a6938322f0eeb2916ea0924e389b5c8ce430bbaac57581809a7cca43b
BLAKE2b-256 checksum
How to use checksums
64e2a742bd6575c524a98d4fbfea4295bf518512b272dcca8bebf7cdd186baa9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / rustypaper-0.2.2-cp39-abi3-macosx_10_12_x86_64.whl

Download URL rustypaper-0.2.2-cp39-abi3-macosx_10_12_x86_64.whl
Size 1.4 MB
Tags CPython 3.9 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
9e0c4eee58c7b783643593850a6fbd4928df28c9ca57f5b267d112be899ed221
BLAKE2b-256 checksum
How to use checksums
38505579d5fc58c37c125d14091ae3367eb3a4890d51427bae06f541e81e752a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.2 This release

6 release files

0.2.1

6 release files

0.2.0

6 release files

0.1.1

6 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