otf-pdf 0.1.0

A from-scratch toolkit to parse, edit, sign, verify, generate, validate and render PDFs.
Documentation

PDF

Read, edit, encrypt, sanitize and extract text from PDFs: pure Rust, built for untrusted input

crates.io | Docs | API guide | Embedding guide

An Open Tech Foundation project

A PDF is opened lazily: objects are parsed on first use, damaged files are repaired (and every repair reported), and edits go into a copy-on-write overlay, so a save can rewrite the file cleanly or append just the changes.

use otf_pdf::{Document, OpenOptions, SaveOptions, TextExtractor};

let doc = Document::open(std::fs::read("in.pdf")?, OpenOptions::default())?;
println!("{} pages, title {:?}", doc.page_count()?, doc.info().title);
println!("{}", TextExtractor::new(&doc).page_text(0)?);

let mut doc = doc;
doc.delete_pages(&[0])?;
std::fs::write("out.pdf", doc.save(&SaveOptions::default())?)?;

[!NOTE] Pre-1.0. The API can change between minor versions; see the changelog.

What it does

Area
๐Ÿ“– Read PDF 1.0โ€“2.0; classic and stream xrefs, object streams, incremental updates; page tree with inheritance; Info and XMP metadata, outlines, attachments
๐Ÿ” Decrypt Standard security handler R2โ€“R6 (RC4, AES-128, AES-256), user and owner passwords; empty passwords open automatically
๐Ÿฉน Repair Rebuilds broken xrefs, recovers trailers, catalogs and page trees, fixes wrong stream lengths; every repair is a reported diagnostic
โœ๏ธ Edit Merge, split, extract, reorder, delete, rotate and crop pages; outlines, named destinations, metadata (Info and XMP kept in sync), attachments
๐Ÿ’พ Save Full save (garbage-collected, compressed, object streams) or incremental save (original bytes kept as a prefix); deterministic output
๐Ÿ”’ Encrypt AES-256 (R6) with permissions; legacy RC4/AES-128 are read, never written
๐Ÿงน Sanitize Remove JavaScript, launch/submit actions, attachments, XFA, multimedia and, optionally, links and metadata
๐Ÿ”ค Extract text Lines and words with positions; Type1, TrueType, Type3 and CID fonts, ToUnicode, CMaps, Adobe CJK collections; form field text

Not yet: creating PDFs from scratch, rendering, forms filling, signatures, PDF/A. See the plan.

Why

Need How it is met
๐Ÿฆ€ Pure Rust No C libraries; built from scratch on a small, pinned dependency set (RustCrypto, flate2, Fontations)
๐Ÿ›ก๏ธ Safe on hostile input unsafe is forbidden; malformed files are errors or repairs, never panics; every resource has a limit and a CPU budget hook; seven fuzz targets
๐Ÿงช Checked against references 3,842 real-world test files; files we write are verified by qpdf and poppler; text extraction scored against poppler and known source text
๐Ÿ”Œ Embeddable No I/O, clock, randomness or global state of its own; input through a Source trait, output through a Sink, entropy injected; types are Send + Sync

Quality, measured

Check Result
Open with every page enumerated 3,832 of 3,842 corpus files; page counts match poppler
Open โ†’ save โ†’ open, page content identical Every file that opens
Files written, checked by qpdf and poppler 695 files, 0 problems
Decryption, known plaintext (qpdf-encrypted, R2โ€“R6) 11 of 11
Text: character error rate vs poppler (Latin, CJK) 0.35%, 0.0%
Text: character error rate vs source (Hindi, Bengali, Tamil, Telugu, Chinese, Japanese, Korean) 0.0%

Install

[dependencies]
otf-pdf = "0.1"

Rust 1.88 or later. The otf-pdf-cli crate in the repository is a development tool (otf-pdf info|pages|text|save|merge|sanitize โ€ฆ), not published.

License

Apache-2.0. See NOTICE for the font data from pdf.js and Adobe.