lopdf
| Language | Rust (1.88+) |
|---|---|
| License | MIT |
| Repository | github.com/J-F-Liu/lopdf |
| Documentation | docs.rs/lopdf |
| Depends on | pure Rust crates (flate2, nom, rayon by default, AES and RC4 for encryption) |
| Read / write | yes / yes: object model, pages, text extraction, merge, delete, rotate, compress, object streams, decryption |
lopdf parses a PDF into a Document: a map of object IDs to Object values (dictionaries, arrays, streams, references) plus the trailer. You can walk and change any object, then save. On top of the object model it has helpers for common jobs: get_pages, extract_text, delete_pages, renumber_objects (to merge documents), compress, prune_objects, and save_modern to write object streams and an xref stream. It opens encrypted files with an empty or known password.
lopdf works at the level of the PDF spec: creating content means writing content-stream operators, and merging needs a few dozen lines (see the merge example in the README). It doesn’t render pages. Text extraction is basic and works for simple fonts.
Installation#
cargo add lopdf
Basic usage: extract text from a PDF#
use lopdf::Document;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let doc = Document::load("in.pdf")?;
let pages = doc.get_pages(); // BTreeMap<page number, ObjectId>, numbered from 1
println!("{} pages, PDF {}", pages.len(), doc.version);
for &number in pages.keys() {
let text = doc.extract_text(&[number])?;
println!("--- page {number} ---\n{text}");
}
Ok(())
}
How to create a PDF with text#
use lopdf::content::{Content, Operation};
use lopdf::{dictionary, Document, Object, Stream};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut doc = Document::with_version("1.5");
let pages_id = doc.new_object_id();
let font_id = doc.add_object(dictionary! {
"Type" => "Font",
"Subtype" => "Type1",
"BaseFont" => "Helvetica",
});
let resources_id = doc.add_object(dictionary! {
"Font" => dictionary! { "F1" => font_id },
});
let content = Content {
operations: vec![
Operation::new("BT", vec![]),
Operation::new("Tf", vec!["F1".into(), 24.into()]),
Operation::new("Td", vec![72.into(), 770.into()]),
Operation::new("Tj", vec![Object::string_literal("Hello, PDF!")]),
Operation::new("ET", vec![]),
],
};
let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode()?));
let page_id = doc.add_object(dictionary! {
"Type" => "Page",
"Parent" => pages_id,
"Contents" => content_id,
"Resources" => resources_id,
"MediaBox" => vec![0.into(), 0.into(), 595.into(), 842.into()],
});
doc.objects.insert(pages_id, Object::Dictionary(dictionary! {
"Type" => "Pages",
"Kids" => vec![page_id.into()],
"Count" => 1,
}));
let catalog_id = doc.add_object(dictionary! {
"Type" => "Catalog",
"Pages" => pages_id,
});
doc.trailer.set("Root", catalog_id);
doc.compress();
doc.save("hello.pdf")?;
Ok(())
}
How to delete and rotate pages#
use lopdf::Document;
let mut doc = Document::load("in.pdf")?;
doc.delete_pages(&[2]); // page numbers start at 1
for (_, page_id) in doc.get_pages() {
let page = doc.get_object_mut(page_id)?.as_dict_mut()?;
let rotation = page.get(b"Rotate").and_then(|o| o.as_i64()).unwrap_or(0);
page.set("Rotate", (rotation + 90) % 360);
}
doc.prune_objects(); // drop objects no longer referenced
doc.save("out.pdf")?;
Rotate can also be inherited from a parent Pages node; check it there for files that set it on the tree.
How to read PDF metadata#
let doc = Document::load("in.pdf")?;
let info = doc
.trailer
.get(b"Info")
.and_then(|o| o.as_reference())
.and_then(|id| doc.get_dictionary(id));
if let Ok(info) = info {
if let Ok(title) = info.get(b"Title") {
println!("Title: {:?}", lopdf::decode_text_string(title));
}
}
How to open an encrypted PDF#
Files with an empty user password are decrypted automatically by Document::load. Otherwise pass the password:
let doc = Document::load_with_password("secret.pdf", "user")?;
println!("{}", doc.extract_text(&[1])?);
How to save a smaller file with object streams#
let mut doc = Document::load("in.pdf")?;
let mut file = std::fs::File::create("out.pdf")?;
doc.save_modern(&mut file)?; // object streams + cross-reference stream
SaveOptions::builder() with doc.save_with_options controls object streams, xref streams and the compression level.
Tips#
- To merge documents, renumber each one with
doc.renumber_objects_with(max_id)and combine their objects and page trees; the README has a complete merge example. - Enable the
asyncfeature for Tokio-based loading, andembed_imageorfont_embeddingto add images and TrueType fonts. - Use
Document::load_mem(&bytes)for PDFs already in memory.