pdfboss.dev

Rust

PDF parsing and rendering in pure Rust.

pdfboss is a workspace of focused crates sharing one version: a clean-room parser built from ISO 32000 with readers for forms, bookmarks, page labels and attachments, layout analysis to text and Markdown, an anti-aliased rasterizer, in-tree JPEG 2000, JBIG2, CCITT and ICC codecs, async range-fetching I/O, and a deterministic writer that also merges, splits, rotates, watermarks and encrypts existing documents. Safe Rust, no C dependencies, no bindings to another engine.

Install

Add only what you use.

pdfboss-core alone parses; the others build on it. Optional features stay off by default in the library crates: substitute-fonts on pdfboss-render bundles replacement faces for PDFs that do not embed theirs, predefined-cmaps on pdfboss-core compiles in the CJK CMap set, and http on pdfboss-aio enables remote documents over HTTP range requests.

shell
$ cargo add pdfboss-core pdfboss-output pdfboss-render
# optional: pdfboss-text pdfboss-write pdfboss-markdown pdfboss-aio
$ cargo add pdfboss-aio --features http

Quickstart

Open, extract, rasterize.

render_page returns an RGBA Pixmap; save_png and encode_png turn it into a file or bytes. render_page_reporting returns the same pixels plus every dropped or approximated item. Page indexes are 0-based.

main.rs
use pdfboss_core::Document;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = Document::open("report.pdf")?;
    println!("{} pages", doc.page_count());

    let page = doc.page(0)?;
    let text = pdfboss_output::extract_text(&doc, &page)?;
    println!("{text}");

    let pixmap = pdfboss_render::render_page(&doc, &page, 2.0)?;
    pixmap.save_png("page.png")?;
    Ok(())
}

Structure and editing

Read forms and bookmarks, assemble documents.

Document reads the interactive form (form_fields, interactive_form), the bookmark tree (outline), page labels, named destinations, embedded files and viewer preferences; each has an async _with twin over any object source. pdfboss_write assembles through an Importer that renumbers each source object once: merge_documents, split_document, rewrite_document, encrypt_document and decrypt_document, while Update appends an incremental section for rotation, metadata and watermarks.

structure.rs
use pdfboss_core::{Document, Permissions};
use pdfboss_write::{encrypt_document, merge_documents, WriteOptions};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = Document::open("form.pdf")?;
    for field in doc.form_fields() {
        println!("{} = {:?}", field.name, field.value);
    }
    for item in doc.outline() {
        println!("{} ({} children)", item.title, item.children.len());
    }
    for file in doc.embedded_files() {
        std::fs::write(&file.name, doc.embedded_file_data(&file)?)?;
    }

    let appendix = Document::open("appendix.pdf")?;
    let inputs = [(&doc, None), (&appendix, Some(&[0usize, 1][..]))];
    let merged = merge_documents(&inputs, WriteOptions::default())?;
    let permissions = Permissions { modify: false, ..Permissions::all() };
    let locked = encrypt_document(
        &Document::load(merged)?,
        "s3cret",
        "",
        permissions,
        WriteOptions::default(),
    )?;
    std::fs::write("locked.pdf", locked)?;
    Ok(())
}

Design

What the crates promise.

  • Lenient reading: broken cross-reference tables are reconstructed, wrong stream lengths tolerated, garbage operators skipped, and every dropped or approximated item is reported.
  • Lazy page trees: opening a document reads its declared page count instead of parsing every page dictionary up front.
  • Deterministic output from pdfboss-write: the same composition always serializes to the same bytes.
  • Clean-room codecs implemented from their specifications: ITU-T T.800 for JPEG 2000, T.88 for JBIG2, T.4 and T.6 for CCITT, ICC.1:2010 for color.
  • A conformance ledger in Lean: every clause of ISO 32000-1 carries a status, and CI proves each claim against citations in the source and the tests.