All formats

lopdf

LanguageRust (1.88+)
LicenseMIT
Repositorygithub.com/J-F-Liu/lopdf
Documentationdocs.rs/lopdf
Depends onpure Rust crates (flate2, nom, rayon by default, AES and RC4 for encryption)
Read / writeyes / 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#

Shell
cargo add lopdf

Basic usage: extract text from a PDF#

Rust
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#

Rust
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#

Rust
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#

Rust
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:

Rust
let doc = Document::load_with_password("secret.pdf", "user")?;
println!("{}", doc.extract_text(&[1])?);

How to save a smaller file with object streams#

Rust
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#