pdfboss.dev

Fast PDF text extraction and rendering, from scratch in Rust.

pdfboss parses PDFs, extracts text and Markdown, reads forms, bookmarks and attachments, rasterizes pages to PNG, PPM, BMP or JPEG, and merges, splits, rotates, watermarks and encrypts documents. One Rust core behind a CLI and Python bindings, built clean-room from the ISO 32000 spec: safe Rust, no C dependencies, no bindings to another engine. MIT OR Apache-2.0.

$pip install pdfboss$cargo install pdfboss-cli

Prebuilt abi3 wheels for CPython 3.12+, no toolchain required.

The toolkit

Point it at a path or a URL.

The CLI ships info, text, md, render, images and tui, editing via merge, split, rotate, overlay, meta, rewrite, encrypt and decrypt, creation via create blank|text|images|md|manifest, and explorer subcommands json, hex, q (jq-style queries) and obj. Each takes a local path or an http(s) URL, fetched in byte ranges rather than downloaded whole.

Python API →Command line →Rust crates →

$ pdfboss info quarterly.pdf
$ pdfboss md quarterly.pdf
$ pdfboss render quarterly.pdf
$ pdfboss images quarterly.pdf
# explore a remote file without downloading it
$ pdfboss json https://example.com/report.pdf
$ pdfboss q quarterly.pdf '.header.version'
$ pdfboss create text notes.txt -o out.pdf
$ pdfboss merge q1.pdf q2.pdf -o h1.pdf
$ pdfboss encrypt h1.pdf -o locked.pdf --user-password s3cret
$ pdfboss tui quarterly.pdf
report.pypdfboss-py
import pdfboss

doc = pdfboss.Document("report.pdf")
text = doc.extract_text()
md = doc.extract_markdown()
png = doc[0].render(scale=2.0)  # PNG bytes; format="ppm", "bmp" or "jpeg" too
images = doc[0].extract_images()
fields = doc.form_fields()  # .name, .field_type, .text, .checked
toc = doc.outline()  # bookmarks; also page_labels(), embedded_files()
merged = pdfboss.write.merge([a, b])  # also split, rotate, encrypt, watermark

# async, straight off the wire
doc = await pdfboss.AsyncDocument.open_url(
    "https://example.com/report.pdf"
)

The explorer

See inside any PDF.

pdfboss tui opens the whole file as a lazy tree: pages with their fonts, images and content streams on the left, a pretty-printing inspector, live page preview and tracking hexdump on the right. The same element model answers q (jq-style queries over the document as a JSON tree) and hex (a hexdump of any object, xref section or byte range), and all of it works over an http(s) URL fetched in byte ranges.

pdfboss tui quarterly.pdf
The pdfboss terminal explorer: an element tree with a page's six fonts expanded, a rasterized page preview, and a hexdump of the selected object's decoded bytes
~/reports · pdfboss
$ pdfboss q quarterly.pdf -r '.pages[0].fonts[].base_font'
SFProDisplay-Bold-Identity-H
SFProDisplay-Light-Identity-H
SFProDisplay-Light-Identity-H
SFProDisplay-Semibold-Identity-H
SFProDisplay-Light-Identity-H
SFProDisplay-Bold-Identity-H
$ pdfboss q quarterly.pdf '.objects | length'
47
$ pdfboss hex quarterly.pdf trailer
0000a7ed  34 37 20 30 20 6f 62 6a  0a 3c 3c 0a 2f 53 69 7a  |47 0 obj.<<./Siz|
0000a7fd  65 20 34 38 0a 2f 52 6f  6f 74 20 32 20 30 20 52  |e 48./Root 2 0 R|
0000a80d  0a 2f 49 6e 66 6f 20 33  20 30 20 52 0a 2f 46 69  |./Info 3 0 R./Fi|
0000a81d  6c 74 65 72 20 2f 46 6c  61 74 65 44 65 63 6f 64  |lter /FlateDecod|
0000a82d  65 0a 2f 54 79 70 65 20  2f 58 52 65 66 0a 2f 4c  |e./Type /XRef./L|
$ pdfboss hex https://example.com/report.pdf obj:5

Remote documents

Open PDFs straight off the server.

Every explorer command takes an http(s) URL where it takes a path. The document opens over ranged requests that fetch only the bytes the parser touches, batched adaptively: misses landing near each other double the batch, up to 8 MiB, and a far jump halves it. While the open runs, stderr draws a coverage map of exactly which stretches of the file have arrived, erased the moment the document is ready. A server that ignores Range costs one full download instead, reported with a progress bar.

pdfboss tui https://archive.org/…/tafsir.pdf
pdfboss tui opening a 6,610-page, 228 MiB PDF from archive.org: a coverage map fills in as ranged requests land, then the explorer opens on the live document
range-less fallback · python3 -m http.server
$ pdfboss q 'http://127.0.0.1:8180/tafsir.pdf' '.header.version'
pdfboss: server ignores Range requests, downloading the whole file (228.3 MiB)
[=============>                ]  45%  102.9 MiB/228.3 MiB

Composition

Documents compose with a pipe.

pdfboss.write builds PDFs from elements: pages and their text, paragraphs, images and links join with |, metadata, bookmarks and attachments slot into the document the same way, and one to_bytes() writes deterministic output. Prefer prose? CommonMark+GFM composes into a CSS-themed PDF, from the CLI, Python or Rust.

cover.pypdfboss.write
from pdfboss.write import Link, Metadata, Page, Paragraph, Pdf, Standard14, Text

cover = (
    Page(size="a4")
    | Text("Q3 Report", at=(72, 770), font=Standard14.HELVETICA_BOLD, size=28)
    | Paragraph("Prepared for the board. Revenue held, ...", rect=(72, 640, 420, 740))
    | Link(rect=(72, 60, 200, 80), url="https://example.com/q3")
)
data = (Pdf() | Metadata(title="Q3 Report") | cover).to_bytes()

# or compose markdown with a CSS theme
pdf = pdfboss.md.to_pdf(open("notes.md").read())
The top of the composed page: a Q3 Report heading with a wrapped paragraph below it, rendered by pdfboss from the code on the left

the composed page, rendered back by pdfboss render

~/reports · pdfboss create
# the same composition, from the shell
$ pdfboss create md notes.md -o out.pdf
$ pdfboss create md notes.md -o out.pdf --theme brand.css
$ pdfboss create manifest q3.toml -o out.pdf
$ pdfboss create images a.png b.jpg -o out.pdf

Benchmarks

Measured on real PDFs.

The fastest library measured on text extraction, open + parse, and rendering, including against the C-backed PyMuPDF.

10,312 pages/s

Text extraction: ~24× PyMuPDF, ~36× pdf_oxide.

397,000 pages/s

Open + parse: ~2.3× pdf_oxide.

161.9 pages/s

Rendering the mixed corpus: ~35% ahead of pdfium itself.

65.2 pages/s

On a 544-page JBIG2 scanned book, fastest of the four measured.

Full benchmarks, charts and method →

Under the hood

Thirteen crates, one implementation.

No borrowed decoders. pdfboss ships its own JPEG 2000 (ITU-T T.800), JBIG2, CCITT and ICC codecs, an anti-aliased vector rasterizer, layout analysis to plain text and Markdown, PDF creation, async range-fetching I/O, a CLI and TUI, and PyO3 bindings. Every crate is on crates.io under MIT OR Apache-2.0.

Browse the Rust crates →

Reporting

“Rendering is lenient and it says so.”

Broken files still open

Real-world PDFs are messy, so the reader is lenient by design: it reconstructs broken cross-reference tables, tolerates wrong stream lengths, and skips garbage operators instead of refusing the file.

Nothing is dropped silently

Content the renderer cannot read is skipped so the rest of the page still rasterizes, and every dropped or approximated item lands in a report: render warnings on stderr, render_reporting in Rust, Page.render_reporting() in Python.

Checked against the standard

A ledger written in Lean lists every clause of ISO 32000-1 with the status pdfboss claims for it. CI proves each claim against citations in the source and the tests, so the conformance chapter always matches the code.