pdf_oxide: a Rust PDF core with 19 bindings, benchmarked at 0.8ms mean
The fastest PDF library for Python and Rust. Text extraction, image extraction, markdown conversion, PDF creation & editing. 0.8ms mean, 5× faster than industry leaders, 100% pass rate on 3,830 PDFs. MIT/Apache-2.0.
At a glance
- What is it?
- pdf_oxide ships text extraction, image extraction, markdown conversion, creation and editing from one Rust core, exposed through 19 language bindings, a CLI and an MCP server. The performance table is the pitch; the release notes are where the real engineering shows.
- Who is it for?
- Adopt pdf_oxide if you extract text, images or markdown at volume and can pin a version, because the binding breadth means one core serves a Python service and a Go worker without two parsing stacks. Do not adopt it if you need OCR (the benchmark explicitly excludes it), if you depend on a binding with no CI guarantee, or if you want a stable API surface: this project is still at 0.3.x and shipped 144 correctness fixes in a single release.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem pdf_oxide targets: one parser, many runtimes
Most teams that process PDFs end up with two or three parsers. A Python service uses one library, a Go worker uses another, and the browser uses a third, so the same document extracts differently depending on which path it took. pdf_oxide attacks that duplication directly. The repository describes a Rust core with bindings for 19 languages, and the v0.3.69 release notes list C++, Swift, Kotlin, Dart, R, Julia, Zig, Scala, Clojure, Objective-C and Elixir as additions built over a stable C ABI, each with its own CI workflow, api-coverage tests and runnable examples. The intended user is not someone who occasionally reads a PDF. It is a team running extraction as a service, where throughput and output consistency both matter. The README frames the same point from the other side: text extraction, image extraction, creation and editing in one library, rather than a parser plus a separate writer plus a separate converter. Whether the bindings behave identically is a separate question, and one the release notes suggest is still being worked through, since v0.3.77 had to extend search-index control to every first-party binding because it had landed in Rust first.
How the Rust core and the C ABI layer fit together
The architecture visible in the material is a Rust core plus thin per-language wrappers over a C ABI. The Rust API is the reference shape: `PdfDocument::open` returns a document, and `extract_text`, `extract_images` and `to_markdown` take a page index. The Python binding mirrors that with a context manager, page iteration and lazy properties. In the example, `page.text`, `page.chars`, `page.words`, `page.lines`, `page.tables` and `page.images` are all described as lazy, which matters because a page-oriented API that eagerly parsed every representation would make the 0.8ms mean impossible. The Python object supports negative indexing and exposes `page.width` and `page.height` in points. Markdown conversion takes options (`detect_headings=True` in Python, `Default::default()` in Rust), so heading detection is opt-in rather than automatic. The core also holds a per-page search-index cache, added in 0.3.76 alongside `prepare_search()` and `clear_search_index()`. That release note is the most informative line in the whole set: before it, repeated `search()` calls on the same document cost O(searches x full extraction). If you call search in a loop, the cache is the difference between usable and not.
Installing it: pip, cargo, brew and the CLI
The README gives four entry points. Python is `pip install pdf_oxide`, then `from pdf_oxide import PdfDocument`. Rust is `pdf_oxide = "0.3"` in `Cargo.toml`, with `PdfDocument::open`, `extract_text(0)`, `extract_images(0)` and `to_markdown(0, Default::default())`. The CLI installs on macOS via `brew install yfedoseev/tap/pdf-oxide` and exposes subcommands: `pdf-oxide text document.pdf`, `pdf-oxide markdown document.pdf -o output.md`, `pdf-oxide search document.pdf "pattern"`, `pdf-oxide merge a.pdf b.pdf -o combined.pdf`. The MCP server for AI assistants ships in the same Homebrew formula and is configured with a `mcpServers` block whose command is `crgx` and whose args are `["pdf_oxide_mcp@latest"]`. That last detail is worth pausing on. The MCP configuration pulls `pdf_oxide_mcp@latest` through a runner rather than invoking a pinned local binary, so the server your assistant talks to can change without your configuration changing. If you run this in a controlled environment, that is a supply-chain decision you should make deliberately rather than by copy-pasting the snippet. Note also that the README's own badges and the repository metadata disagree on licence: the README says MIT in several places, the badge says MIT OR Apache-2.0, and the repository lists Apache-2.0. Treat the dual MIT/Apache-2.0 grant as the claim and verify the `LICENSE` files in the tree before you rely on either.
What the benchmark table actually measures, and what it does not
The performance claim is specific enough to critique. The README states the corpus is 3,830 PDFs from veraPDF, Mozilla pdf.js and DARPA SafeDocs, run single-threaded with a 60s timeout and no warm-up, text extraction only, no OCR. pdf_oxide is listed at 0.8ms mean and 9ms p99 with a 100% pass rate. PyMuPDF is 4.6ms mean, pypdfium2 4.1ms, pdfplumber 23.2ms, pypdf 12.1ms. In the Rust table, lopdf is faster at 0.3ms mean but is marked as having no built-in text extraction, so it is not doing the same job. That distinction is the one to hold onto: a mean of 0.8ms across a corpus dominated by PDF/A compliance files says little about a 400-page scanned annual report, and the README itself notes the corpus is 2,907 veraPDF files out of 3,830. The pass-rate column is more interesting than the timing column. The README states the 7 non-passing files are intentionally broken fixtures (missing header, fuzz-corrupted catalogs, invalid xref streams) and that the project counts 100% on all valid PDFs. That is a reasonable definition, but it is the project's own definition, published by the project, on a corpus the project selected. The text-quality claim, 99.5% parity against PyMuPDF and pypdfium2, is the number I would want to reproduce on my own documents before trusting the speed number.
Where it breaks: the 0.3.x churn and the binding gap
The release history is the strongest argument for caution. v0.3.78 is titled "Correctness under audit" and describes 144 defects across rendering, text, reading order, files and colour. That is a large number of correctness fixes in one release, and it tells you the parser was producing wrong output in cases that a pass/fail benchmark would not catch. A pass rate measures whether extraction completed and returned something, not whether the reading order was right. v0.3.77 fixed `/Artifact`-tagged content being silently dropped with no way to opt back in, which means running headers and footers had been disappearing from output. v0.3.76 fixed CCITT `/ImageMask` XObjects misreading compressed data as raw stencil rows, multi-input DeviceN tint transforms, and spatial table-cell ownership at singleton-span boundaries. Each of those is a class of document that previously produced quietly wrong text or images. If your pipeline feeds an LLM or a search index, silent wrongness is worse than a crash. The binding gap is the second limitation. The README claims 19 bindings, but releases repeatedly show features landing in Rust first and reaching other languages later (search-index control in 0.3.77, PDF/A conversion in Java in 0.3.76). The README invites you to open an issue if you want another language, which is an honest signal about where maintenance attention sits. The Rust core is the maintained surface. A binding is a promise that someone keeps the wrapper in step.
Alternatives: PyMuPDF and pypdfium2 take different positions
The obvious alternative for Python work is PyMuPDF, and the difference is not only speed. PyMuPDF is AGPL-3.0 per the README's own table, which forces a licensing decision on anyone shipping a closed-source service; pdf_oxide is offered under MIT or Apache-2.0. PyMuPDF is also a mature, widely deployed renderer with rasterisation and a much longer track record, while pdf_oxide's benchmark is text extraction only and its release notes show rendering defects being fixed as recently as 0.3.78. If you need to rasterise pages or draw, that maturity matters more than a 5x mean. pypdfium2 is the closer comparison on licence: Apache-2.0, 4.1ms mean, 99.2% pass rate in the same table. It wraps PDFium, the Chrome PDF engine, so its behaviour is tied to a large C++ codebase maintained elsewhere, whereas pdf_oxide is a from-scratch Rust parser you can read end to end. That is the real trade: PDFium gives you years of rendering bug fixes you did not write; pdf_oxide gives you a small, auditable core and a permissive licence, with the correctness work still visibly in progress. For Rust specifically, `pdf_extract` and `unpdf` are listed with basic extraction and lower pass rates (91.5% and 95.1%), so the comparison there is less about features than about how much parsing you are willing to write yourself.
Maintenance cost, licence terms and what to pin
Version cadence is fast. The three most recent releases in the material span 2026-07-27 to 2026-09-08, roughly six weeks for 0.3.76 through 0.3.78, and the repository's last push matches the latest release. That pace is good for bug fixing and bad for API stability. At 0.3.x, semver permits breaking changes in a minor bump, so `pdf_oxide = "0.3"` in `Cargo.toml` and `pdf_oxide` on PyPI will both float you onto whatever shipped most recently. Pin exact versions in both ecosystems and read the release notes before moving, because the notes are where behaviour changes are disclosed (the `/Artifact` change in 0.3.77 altered output for anyone relying on the old default). On licence: the README, the badge and the repository metadata do not agree, so check the `LICENSE` files in the tree rather than the prose. If the dual MIT/Apache-2.0 grant holds, the practical effect is that you can use it in commercial and closed-source products without the AGPL question that PyMuPDF raises. That is a statement about what the licence permits, not legal advice, and if your organisation has a review process, the disagreement between the README and the repository metadata is exactly the kind of thing that process exists to resolve.
Editorial conclusion
Adopt pdf_oxide if you extract text, images or markdown at volume and can pin a version, because the binding breadth means one core serves a Python service and a Go worker without two parsing stacks. Do not adopt it if you need OCR (the benchmark explicitly excludes it), if you depend on a binding with no CI guarantee, or if you want a stable API surface: this project is still at 0.3.x and shipped 144 correctness fixes in a single release. Before committing, run `pdf-oxide text` and `pdf-oxide markdown` over your own worst documents and compare the output against your current tool, then check the CI workflow for whichever binding you intend to ship.
Community notes