Skip to main content

pdfboss

A PDF engine written from scratch in Rust — parse, extract text, rasterize to PNG. One core, a CLI, and pythonic bindings.

CI python-ci PyPI Rust 2021 MIT OR Apache-2.0


Motivation

Reading a PDF shouldn't mean linking a C library. pdfboss is a clean-room reader built straight from the ISO 32000 specification: no C dependencies, no bindings to anyone else's engine — just safe Rust with a small, obvious API. The same core powers a CLI and a native Python extension, so a script and a service share one implementation.

It is a lenient reader: real-world files are damaged, and pdfboss recovers rather than refuses — reconstructing broken cross-reference tables, tolerating wrong stream lengths, and skipping garbage operators instead of erroring out.

Install

Python

pip install pdfboss

Prebuilt abi3 wheels (CPython ≥ 3.12) for Linux and macOS; no toolchain required.

Rust

cargo add pdfboss-core pdfboss-text pdfboss-output pdfboss-render pdfboss-aio pdfboss-tui   # library crates
cargo install pdfboss-cli                                                                   # the `pdfboss` binary

Usage

CLI

pdfboss info    report.pdf                 # version, page count, sizes, metadata
pdfboss text    report.pdf --page 2        # extract text (omit --page for all)
pdfboss md      report.pdf                 # markdown: headings, lists, tables from layout
pdfboss render  report.pdf --page 1 -o page.png --scale 2.0
pdfboss obj     report.pdf 5               # pretty-print object 5

Explorer subcommands, each accepting a local path or an http(s):// URL (range-fetched, never downloaded whole):

pdfboss json    report.pdf                    # dump the document as a JSON value tree
pdfboss json    report.pdf --layout           # ...plus per-page layout blocks
pdfboss hex     report.pdf obj:5              # hexdump the file or a selected element
pdfboss q       report.pdf '.header.version'  # jq-style queries over the JSON tree
pdfboss tui     report.pdf                    # interactive terminal explorer

Python

import pdfboss

doc = pdfboss.Document("report.pdf")       # or Document(data=raw_bytes)
print(doc.page_count, doc.version, doc.metadata)

page = doc[0]
print(page.width, page.height, page.rotation)
text = page.extract_text()                 # or doc.extract_text() for all pages
md   = doc.extract_markdown()              # headings, lists and tables inferred from layout
png  = page.render(scale=2.0)              # PNG bytes

for element in doc.elements():             # lazy: physical + logical, byte spans included
    print(element.kind, element.span)

# Async access over files or http(s) URLs, without reading the whole document.
doc = await pdfboss.AsyncDocument.open_url("https://example.com/report.pdf")
async for element in doc.elements():
    print(element.kind, element.value)

Rust

use pdfboss_core::Document;

let doc = Document::open("report.pdf")?;
let page = doc.page(0)?;

let text = pdfboss_output::extract_text(&doc, &page)?;
let markdown = pdfboss_output::extract_markdown(&doc)?;
let pixmap = pdfboss_render::render_page(&doc, &page, 2.0)?;
pixmap.save_png("page.png")?;

What's inside

Crate Responsibility
pdfboss-core Tokenizer, object model, stream filters, cross-references, object streams, document & page tree, content-stream operators
pdfboss-text Simple and CID/Type0 fonts, standard encodings, ToUnicode CMaps, positional text spans
pdfboss-output Layout analysis over those spans — lines, columns, headings, lists, tables, repeated page headers — rendered as plain text or Markdown
pdfboss-jpx JPEG 2000 decoder for JPXDecode image streams, implemented from ITU-T T.800
pdfboss-render Anti-aliased vector rasterizer — paths, fills, strokes, clipping, color, images, glyph outlines — to RGBA/PNG
pdfboss-aio Async I/O: range-fetching document access over files or HTTP, without reading the whole file
pdfboss-cli The pdfboss command-line tool
pdfboss-tui Interactive terminal explorer (pdfboss tui), built on pdfboss-aio
pdfboss-py PyO3 extension module (pdfboss._pdfboss) built with maturin

Supported: classic, stream, and hybrid cross-references with recovery scanning · object streams · FlateDecode, LZWDecode, ASCII85Decode, ASCIIHexDecode, RunLengthDecode + PNG/TIFF predictors · DCTDecode (JPEG) images · JPXDecode (JPEG 2000) images — JP2 containers and raw codestreams, every progression order, both wavelets, palettes, and /SMaskInData alpha (ITU-T T.800) · CCITTFaxDecode scans — Group 3 one-dimensional, Group 3 mixed and Group 4 coding (ITU-T T.4/T.6) · JBIG2Decode scans — generic regions, symbol dictionaries and text regions, arithmetic- or Huffman-coded, MMR-coded generic regions and collective bitmaps, immediate generic refinement regions, with or without /JBIG2Globals · Standard-handler decryption — RC4 and AES-128/256 (empty user password) · page-tree attribute inheritance · text extraction with ToUnicode and WinAnsi/MacRoman/Standard encodings · Markdown output with headings, lists, emphasis and pipe/HTML tables inferred from the page layout · rasterization of paths, fills (nonzero & even-odd), strokes, transforms, clipping, image/form XObjects, and the glyph outlines of every embedded font program (TrueType, CFF, Type1, Type3), with optional substitution for non-embedded simple fonts · lazy element iteration over physical (objects, xref sections, trailer, with byte spans) and logical (pages, fonts, images, annotations, content operators) elements.

Benchmarks

Text and parsing

Against other Python PDF libraries over 40 real-world PDFs (best-of-3 per file, aggregated over the files every library handled; pages/sec, higher is faster):

pdfboss vs. Python PDF libraries

pdfboss is the fastest library measured on both operations — including against the C-backed PyMuPDF. On text extraction it reaches 9,000 pages/s versus PyMuPDF's 449 (≈20×), and 95–500× the pure-Python readers; since 0.9.0 doc.extract_text() fans pages out across cores, so the gap over the sequential libraries widened from the ≈7× measured before that landed. On open + parse it reaches 357,000 pages/s versus PyMuPDF's 99,000 (≈3.6×): lazy page-tree loading means opening a document reads only its declared page count instead of parsing every page dictionary up front, so opening is close to free and the ratio says more about what the others do eagerly than about pdfboss. Rendering is not compared on this corpus — a few faces still go unpainted (see Limitations), so timing it against full renderers would flatter pdfboss for work it skipped. The scanned-document benchmark below is the render comparison, and it is fair precisely because a scan has no glyphs in it.

Numbers are machine-dependent; reproduce with benchmarks/bench.py.

Extraction quality

Speed without fidelity is worthless, so extraction quality is measured too — on opendataloader-bench (200 real-world PDFs), the corpus PDF-to-Markdown engines publish their comparisons on. The row below scores pdfboss's default plain-text output, so the comparable metric is NID — reading-order similarity against the ground truth, 0–1, higher is better. The table/heading metrics score Markdown structure that plain text cannot express; the Markdown adapter behind pdfboss md is new and is not scored in this table.

Engine Reading order (NID) Output Time (200 docs)
pdf-inspector 0.2.6 0.915 Markdown 0.44s
liteparse 2.10.1 0.913 Markdown 0.75s
opendataloader 2.2.1 0.902 Markdown 2.57s
pymupdf4llm 0.2.0 0.886 Markdown 17.12s
pdfboss 0.868 plain text 0.16s
markitdown 0.1.5 0.844 Markdown 16.17s

pdfboss reads the whole corpus in 0.16 seconds — 2.7× faster than the fastest Markdown engine measured on the same machine — and holds a reading-order score in the middle of that field: per document it beats pdf-inspector's NID on 105 of the 200 files, ties on 23 and loses on 72, with the difference concentrated in table regions, where a Markdown engine's structured output matches the ground truth more closely than flowed text can. Two-column layouts are read column-major, justified text keeps its word spacing, and ligatures and small-caps variants decode through the full Adobe Glyph List conventions.

Quality rows come from the benchmark's own evaluator over all 200 documents; pdf-inspector and pdfboss timings were measured together on an Apple M3 Pro under the benchmark's protocol (median of five single-process runs after a warm-up), the other engines' timings are the ones published with the corpus from an Apple M4 Pro — read them as order-of-magnitude context, not a same-machine race.

Scanned documents

Scans are the other half of the world's PDFs, and they are a different workload: one full-page bilevel image per page, JBIG2- or CCITT-coded, with no text operators at all. Rendering is comparable there — with no glyphs to paint, every library draws the same picture — so it gets its own benchmark, over a 544-page JBIG2 book (1994 × 2832 samples per page) rasterized to PNG at 1:1.

Library pages/sec Ink on page 1
pdfboss 88.1 4.83%
pdfplumber (via pdfium) 57.8 4.87%
PyMuPDF 54.5 4.82%
pypdfium2 52.6 4.85%

pdfboss is the fastest of the four here, at about 1.5× the C-backed renderers — and the only one of them with no C in it. Compare the four rows against each other rather than against another machine's: all four are timed in one pass, and that ratio has landed within a few percent of 1.5× on every run (1.49× to 1.56×), across absolute numbers that varied by half as the machine warmed and cooled.

What is left is the codec itself. Four fifths of the time goes to the JBIG2 arithmetic decoder and the context formation feeding it, and that part is a serial dependency chain — every decision needs the interval state the previous one wrote, and every pixel's context contains the pixels just decoded — so it neither vectorizes nor parallelizes. The rest was arithmetic that did not need doing: expanding a packed scan into eight times its size in RGBA before sampling a fraction of it, blending opaque pixels through an alpha formula that returns them unchanged, and walking bitmaps a pixel at a time where a row of bytes would do.

The ink column is what makes the timings mean anything: a library that cannot decode a scan's codec usually hands back a blank page instead of raising, and a blank page benchmarks superbly. Agreeing coverage says all four decoded the same picture. They do not agree pixel for pixel — each downsamples 1994 × 2832 samples onto a 462 × 663 page with its own resampling.

Reproduce with benchmarks/bench_scans.py.

In the browser

pdfarena races pdfboss against hayro, pdf.js and PDFium on any PDF you drop in, with pdfboss and hayro compiled to WebAssembly. Each engine renders in its own web worker, the stopwatch wraps only the render call, and every challenger is pixel-diffed against pdf.js as the reference. Nothing gets uploaded; the whole benchmark runs in your browser.

Limitations

Glyph painting is staged in tiers, selected with --fonts. The default, all-embedded, paints every embedded font program — TrueType, CFF, Type1 and Type3. embedded-only restricts that to TrueType, and full additionally substitutes a replacement face for a non-embedded simple font, from either a directory you supply or the compiled-in OFL Croscore set (behind the substitute-fonts feature). Standard-14 advance widths come from the Adobe Core-14 AFM tables when a substitute is used, behind the PDF's own /Widths.

What still does not paint: /Symbol and /ZapfDingbats have no license-clean substitute, so they stay blank at every tier rather than borrowing an unrelated face's glyphs. A bold sans substitute is not visually distinct from regular weight. And non-embedded text left unpainted at all-embedded is not yet advanced through the AFM tables, so its positioning drifts.

JBIG2Decode covers generic regions (all four templates, with TPGDON, arithmetic or MMR-coded), symbol dictionaries and text regions in both the arithmetic and the Huffman variant, immediate generic refinement regions (both templates, with TPGRON), and custom code table segments. That is what scanners actually emit, but it is not the whole standard, and the rest is refused rather than approximated — a stream using refinement inside a symbol dictionary or a text region, an intermediate region of any kind, pattern dictionaries or halftone regions fails with a message naming the feature, so a scan that will not decode says why on the first try.

JPXDecode implements ITU-T T.800 (JPEG 2000 Part 1) with known approximations, each reported as a render warning rather than passed off silently: embedded ICC profiles are not interpreted — colour is approximated from the channel count — and sYCC conversion is approximate; Part 2 (ISO/IEC 15444-2) extensions are tolerated in the container but not decoded; and every output sample is normalized to 8 bits per channel, so sources deeper than 8 bits (the spec allows up to 38) decode with their extra precision dropped.

Not yet supported (they error or degrade gracefully, and are on the roadmap): password-protected documents (the empty user password is handled for both RC4 and AES) · shadings and tiling patterns · the JBIG2 features listed above · the unpainted faces listed above · soft masks and blend modes · annotation appearance streams.

Rendering is lenient: content pdfboss cannot read is skipped so the rest of the page still rasterizes. It says so rather than passing the result off as a faithful render — pdfboss render prints a warning line per dropped item on stderr and annotates its summary, the TUI preview raises a status-bar notice, and the libraries expose the detail through render_page_reporting (Rust) and Page.render_reporting() (Python), which return the pixels plus a report of everything dropped or approximated.

The sync and async APIs are not at parity on encryption: Document/Page (and the CLI's info/text/md/render/obj) decrypt empty-user-password RC4/AES files transparently, as above. AsyncDocument (pdfboss tui, any http(s):// target, and the Python AsyncDocument) currently rejects every encrypted document outright, real password or not — async decryption parity is a tracked follow-up.

Development

cargo test --workspace          # Rust test suite
cargo clippy --workspace --all-targets -- -D warnings
maturin develop                 # build the Python extension into your venv
pytest                          # Python integration tests

License

Dual-licensed under either of

at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual-licensed as above, without any additional terms or conditions.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pdfboss-0.14.0.tar.gz (3.5 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pdfboss-0.14.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.2 MB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ x86-64

pdfboss-0.14.0-cp312-abi3-macosx_11_0_arm64.whl (2.9 MB view details)

Uploaded CPython 3.12+macOS 11.0+ ARM64

File details

Details for the file pdfboss-0.14.0.tar.gz.

File metadata

  • Download URL: pdfboss-0.14.0.tar.gz
  • Upload date:
  • Size: 3.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pdfboss-0.14.0.tar.gz
Algorithm Hash digest
SHA256 728f97386ad697c679b639ec2ae6a637c7a886eda5e5dd17f79693a83dfd1b30
MD5 21d26b5832c61fa62114de91c5eb9f03
BLAKE2b-256 0c17e4bafb1d094c56979a979ecf1bc23a4f4ef3309efec4910e15f4cae5cf21

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfboss-0.14.0.tar.gz:

Publisher: release-please.yaml on 4thel00z/pdfboss

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdfboss-0.14.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pdfboss-0.14.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a332e72241c63c53cde22e0c9c55de6b5daafd7e264430dd51d105431cb91ec2
MD5 4712e0982eb984ae7bec0d8aadb4b343
BLAKE2b-256 fcea1b1090bf6c9b8843fca858c70ad4ffffa54b780b0e6a81ff95e048b74de2

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfboss-0.14.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release-please.yaml on 4thel00z/pdfboss

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdfboss-0.14.0-cp312-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pdfboss-0.14.0-cp312-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 78a4faa8c72ba0496781452b128e9870eeec1d8804a7fca2e8bb4c936f50f024
MD5 90f65f672a0a5b90598357059a05c3c2
BLAKE2b-256 4b99fe93bb572b9cb7da36fbc32bb093881c362a3707502b4e048a9f85ed4f99

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfboss-0.14.0-cp312-abi3-macosx_11_0_arm64.whl:

Publisher: release-please.yaml on 4thel00z/pdfboss

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.2.0

3 files

1.1.0

3 files

1.0.0

3 files

0.25.0

3 files

0.24.0

3 files

0.23.0

3 files

0.22.0

3 files

0.21.1

3 files

0.21.0

3 files

0.20.0

3 files

0.19.1

3 files

0.19.0

3 files

0.18.0

3 files

0.17.1

3 files

0.17.0

3 files

0.16.0

3 files

0.15.0

3 files

This release

0.14.0 This release

3 files

0.13.0

3 files

0.12.1

3 files

0.12.0

3 files

0.11.0

3 files

0.10.0

3 files

0.9.0

3 files

0.8.0

3 files

0.7.2

3 files

0.7.1

3 files

0.7.0

3 files

0.6.0

3 files

0.5.0

3 files

0.4.1

3 files

0.4.0

3 files

0.3.0

3 files

0.2.1

3 files

0.1.0

3 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