pdf (pdf-rs)
| Language | Rust |
|---|---|
| License | MIT |
| Repository | github.com/pdf-rs/pdf |
| Documentation | docs.rs/pdf |
| Depends on | pure Rust crates; optional cache feature (on by default) for object caching |
| Read / write | yes / experimental |
The pdf crate, developed as pdf-rs, maps PDF objects to typed Rust structs with a derive macro: Catalog, PageTree, Page, Resources, Font, XObject, InfoDict, FieldDictionary and many more. Instead of looking up dictionary keys by name, you access fields like page.media_box or info.title. It decodes streams (Flate, LZW, DCT passthrough, CCITT and others), parses content streams into operations, and gives access to embedded fonts and images. Companion projects render pages (pdf_render) and inspect objects.
It’s a good base for tools that analyze PDFs. Writing and modifying files is still experimental, it has no built-in text extraction, and some unusual files fail to parse (encrypted files in particular); for robust reading use lopdf or pdfium-render.
Installation#
cargo add pdf
Basic usage: open a PDF and read metadata#
use pdf::file::FileOptions;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = FileOptions::cached().open("in.pdf")?;
println!("{} pages", file.num_pages());
if let Some(ref info) = file.trailer.info_dict {
if let Some(ref title) = info.title {
println!("Title: {}", title.to_string_lossy());
}
if let Some(ref producer) = info.producer {
println!("Producer: {}", producer.to_string_lossy());
}
}
Ok(())
}
How to list pages, page sizes and fonts#
for (i, page) in file.pages().enumerate() {
let page = page?;
let mb = page.media_box()?; // inherited from the page tree if needed
println!("page {}: {} x {} pt, rotate {:?}", i + 1, mb.right - mb.left, mb.top - mb.bottom, page.rotate);
let resources = page.resources()?;
for name in resources.fonts.keys() {
println!(" font {}", name.as_str());
}
}
How to extract embedded images#
use pdf::enc::StreamFilter;
use pdf::object::{Resolve, XObject};
let resolver = file.resolver();
let page = file.get_page(0)?; // pages are numbered from 0
let resources = page.resources()?;
for (name, &r) in resources.xobjects.iter() {
if let XObject::Image(ref img) = *resolver.get(r)? {
let (data, filter) = img.raw_image_data(&resolver)?;
let ext = match filter {
Some(StreamFilter::DCTDecode(_)) => "jpg",
Some(StreamFilter::JPXDecode) => "jp2",
_ => "raw",
};
std::fs::write(format!("{}.{}", name.as_str(), ext), &data)?;
println!("{}: {}x{}", name.as_str(), img.width, img.height);
}
}
How to list form fields#
let catalog = file.get_root();
if let Some(ref forms) = catalog.forms {
for field in forms.fields.iter() {
println!("{:?} = {:?}", field.name, field.value);
}
}
Tips#
FileOptions::cached().password(b"secret").open(path)opens an encrypted file; AES-256 files failed to parse in testing with 0.10, so check your inputs.FileOptions::uncached()uses less memory when you read each object only once.- The repository’s
examples/folder (read,content,metadata,names) shows more of the API.