### Create a New PDF Document Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Demonstrates the basic steps to create a new PDF document, add objects like pages and fonts, and set up the document catalog and trailer. This is a foundational example for PDF generation. ```rust use lopdf::dictionary; use lopdf::content::{Content, Operation}; use lopdf::{Document, Object, ObjectId, Stream, Bookmark}; pub fn generate_fake_document() -> Document { 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" => "Courier", }); 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(), 48.into()]), Operation::new("Td", vec![100.into(), 600.into()]), Operation::new("Tj", vec![Object::string_literal("Hello World!")]), Operation::new("ET", vec![]), ], }; let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode().unwrap())); 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()], }); let pages = dictionary! { "Type" => "Pages", "Kids" => vec![page_id.into()], "Count" => 1, }; doc.objects.insert(pages_id, Object::Dictionary(pages)); let catalog_id = doc.add_object(dictionary! { "Type" => "Catalog", "Pages" => pages_id, }); doc.trailer.set("Root", catalog_id); doc } ``` -------------------------------- ### Create a PDF document in Rust Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Initializes a new PDF document, defines fonts and resources, and constructs a page with text content. Requires the lopdf crate. ```rust use lopdf::dictionary; use lopdf::{Document, Object, Stream}; use lopdf::content::{Content, Operation}; // `with_version` specifes the PDF version this document complies with. let mut doc = Document::with_version("1.5"); // Object IDs are used for cross referencing in PDF documents. // `lopdf` helps keep track of them for us. They are simple integers. // Calls to `doc.new_object_id` and `doc.add_object` return an object ID. // "Pages" is the root node of the page tree. let pages_id = doc.new_object_id(); // Fonts are dictionaries. The "Type", "Subtype" and "BaseFont" tags // are straight out of the PDF spec. // // The dictionary macro is a helper that allows complex // key-value relationships to be represented in a simpler // visual manner, similar to a match statement. // A dictionary is implemented as an IndexMap of Vec, and Object let font_id = doc.add_object(dictionary! { // type of dictionary "Type" => "Font", // type of font, type1 is simple postscript font "Subtype" => "Type1", // basefont is postscript name of font for type1 font. // See PDF reference document for more details "BaseFont" => "Courier", }); // Font dictionaries need to be added into resource // dictionaries in order to be used. // Resource dictionaries can contain more than just fonts, // but normally just contains fonts. // Only one resource dictionary is allowed per page tree root. let resources_id = doc.add_object(dictionary! { // Fonts are actually triplely nested dictionaries. Fun! "Font" => dictionary! { // F1 is the font name used when writing text. // It must be unique in the document. It does not // have to be F1 "F1" => font_id, }, }); // `Content` is a wrapper struct around an operations struct that contains // a vector of operations. The operations struct contains a vector of // that match up with a particular PDF operator and operands. // Refer to the PDF spec for more details on the operators and operands // Note, the operators and operands are specified in a reverse order // from how they actually appear in the PDF file itself. let content = Content { operations: vec![ // BT begins a text element. It takes no operands. Operation::new("BT", vec![]), // Tf specifies the font and font size. // Font scaling is complicated in PDFs. // Refer to the spec for more info. // The `into()` methods convert the types into // an enum that represents the basic object types in PDF documents. Operation::new("Tf", vec!["F1".into(), 48.into()]), // Td adjusts the translation components of the text matrix. // When used for the first time after BT, it sets the initial // text position on the page. // Note: PDF documents have Y=0 at the bottom. Thus 600 to print text near the top. Operation::new("Td", vec![100.into(), 600.into()]), // Tj prints a string literal to the page. By default, this is black text that is // filled in. There are other operators that can produce various textual effects and // colors Operation::new("Tj", vec![Object::string_literal("Hello World!")]), // ET ends the text element. Operation::new("ET", vec![]), ], }; // Streams are a dictionary followed by a (possibly encoded) sequence of bytes. // What that sequence of bytes represents, depends on the context. // The stream dictionary is set internally by lopdf and normally doesn't // need to be manually manipulated. It contains keys such as // Length, Filter, DecodeParams, etc. let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode().unwrap())); // Page is a dictionary that represents one page of a PDF file. // Its required fields are "Type", "Parent" and "Contents". let page_id = doc.add_object(dictionary! { "Type" => "Page", "Parent" => pages_id, "Contents" => content_id, }); // Again, "Pages" is the root of the page tree. The ID was already created // at the top of the page, since we needed it to assign to the parent element // of the page dictionary. // // These are just the basic requirements for a page tree root object. // There are also many additional entries that can be added to the dictionary, // if needed. Some of these can also be defined on the page dictionary itself, // and not inherited from the page tree root. let pages = dictionary! { // Type of dictionary "Type" => "Pages", // Vector of page IDs in document. Normally would contain more than one ID // and be produced using a loop of some kind. "Kids" => vec![page_id.into()], // Page count "Count" => 1, // ID of resources dictionary, defined earlier "Resources" => resources_id, // A rectangle that defines the boundaries of the physical or digital media. // This is the "page size". ``` -------------------------------- ### Create a new PDF document in Rust Source: https://context7.com/j-f-liu/lopdf/llms.txt Initializes a new PDF document, adds resources like fonts and content streams, and saves the result to a file. ```rust use lopdf::dictionary; use lopdf::{Document, Object, Stream}; use lopdf::content::{Content, Operation}; // Create a new PDF document with version 1.5 let mut doc = Document::with_version("1.5"); // Create the pages tree root (needed before creating pages) let pages_id = doc.new_object_id(); // Add a font dictionary let font_id = doc.add_object(dictionary! { "Type" => "Font", "Subtype" => "Type1", "BaseFont" => "Courier", }); // Add resources dictionary with the font let resources_id = doc.add_object(dictionary! { "Font" => dictionary! { "F1" => font_id, }, }); // Create page content with text operations let content = Content { operations: vec![ Operation::new("BT", vec![]), // Begin text Operation::new("Tf", vec!["F1".into(), 48.into()]), // Set font F1, size 48 Operation::new("Td", vec![100.into(), 600.into()]), // Move to position Operation::new("Tj", vec![Object::string_literal("Hello World!")]), Operation::new("ET", vec![]), // End text ], }; // Add content stream let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode().unwrap())); // Add page referencing the pages tree let page_id = doc.add_object(dictionary! { "Type" => "Page", "Parent" => pages_id, "Contents" => content_id, }); // Create the pages tree root object let pages = dictionary! { "Type" => "Pages", "Kids" => vec![page_id.into()], "Count" => 1, "Resources" => resources_id, "MediaBox" => vec![0.into(), 0.into(), 595.into(), 842.into()], }; doc.objects.insert(pages_id, Object::Dictionary(pages)); // Create document catalog let catalog_id = doc.add_object(dictionary! { "Type" => "Catalog", "Pages" => pages_id, }); // Set the document root doc.trailer.set("Root", catalog_id); // Compress streams for smaller file size doc.compress(); // Save the document doc.save("output.pdf").unwrap(); ``` -------------------------------- ### Add Bookmarks to PDF with lopdf Source: https://context7.com/j-f-liu/lopdf/llms.txt Shows how to create hierarchical bookmarks (outlines) in a PDF document, including setting titles, colors, styles, and linking them to specific pages. Requires a PDF document to load. ```rust use lopdf::{Document, Bookmark, Object}; let mut doc = Document::load("document.pdf").unwrap(); let pages = doc.get_pages(); // Create a root bookmark pointing to page 1 let page1_id = *pages.get(&1).unwrap(); let bookmark1 = Bookmark::new( "Chapter 1".to_string(), [0.0, 0.0, 0.0], // Black color (RGB) 0, // Style: 0=normal, 1=italic, 2=bold, 3=bold-italic page1_id, ); let chapter1_id = doc.add_bookmark(bookmark1, None); // Create a child bookmark under chapter 1 let page2_id = *pages.get(&2).unwrap(); let section = Bookmark::new( "Section 1.1".to_string(), [0.0, 0.0, 1.0], // Blue color 1, // Italic page2_id, ); doc.add_bookmark(section, Some(chapter1_id)); // Create another root bookmark let page3_id = *pages.get(&3).unwrap(); let bookmark2 = Bookmark::new( "Chapter 2".to_string(), [1.0, 0.0, 0.0], // Red color 2, // Bold page3_id, ); doc.add_bookmark(bookmark2, None); // Adjust bookmarks pointing to zero pages doc.adjust_zero_pages(); // Build the outline structure if let Some(outline_id) = doc.build_outline() { // Add outline to catalog if let Ok(catalog) = doc.catalog_mut() { catalog.set("Outlines", Object::Reference(outline_id)); } } doc.save("with_bookmarks.pdf").unwrap(); ``` -------------------------------- ### Load and inspect PDF documents Source: https://context7.com/j-f-liu/lopdf/llms.txt Demonstrates loading PDFs from various sources including files, memory, and readers, as well as handling encrypted files and metadata extraction. ```rust use lopdf::Document; // Load from file path let doc = Document::load("document.pdf").unwrap(); println!("PDF version: {}", doc.version); println!("Number of pages: {}", doc.get_pages().len()); // Load from memory buffer let pdf_bytes: &[u8] = include_bytes!("document.pdf"); let doc = Document::load_mem(pdf_bytes).unwrap(); // Load from any Read source use std::fs::File; let file = File::open("document.pdf").unwrap(); let doc = Document::load_from(file).unwrap(); // Load encrypted PDF with password let doc = Document::load_with_password("encrypted.pdf", "secret123").unwrap(); // Check if document was encrypted if doc.was_encrypted() { println!("Document was successfully decrypted"); } // Load with custom options use lopdf::LoadOptions; let options = LoadOptions::with_password("password"); let doc = Document::load_with_options("secure.pdf", options).unwrap(); // Quick metadata extraction (faster for large PDFs) let metadata = Document::load_metadata("large.pdf").unwrap(); println!("Title: {:?}", metadata.title); println!("Author: {:?}", metadata.author); println!("Pages: {}", metadata.page_count); ``` -------------------------------- ### Manage PDF Pages with lopdf Source: https://context7.com/j-f-liu/lopdf/llms.txt Demonstrates how to access, iterate, delete, and retrieve resources (fonts, annotations, images) for pages in a PDF document using lopdf. ```rust use lopdf::Document; let mut doc = Document::load("document.pdf").unwrap(); // Get all pages (returns BTreeMap) let pages = doc.get_pages(); println!("Total pages: {}", pages.len()); for (page_num, page_id) in &pages { println!("Page {}: {:?}", page_num, page_id); } // Iterate over pages for page_id in doc.page_iter() { let content = doc.get_page_content(page_id).unwrap(); println!("Page content length: {} bytes", content.len()); } // Delete specific pages doc.delete_pages(&[2, 4, 6]); // Delete pages 2, 4, and 6 // Get page resources and fonts let page_id = *pages.get(&1).unwrap(); let (resources, resource_ids) = doc.get_page_resources(page_id).unwrap(); let fonts = doc.get_page_fonts(page_id).unwrap(); println!("Fonts on page 1: {:?}", fonts.keys().collect::>()); // Get page annotations let annotations = doc.get_page_annotations(page_id).unwrap(); println!("Annotations: {}", annotations.len()); // Get page images (requires iterating XObjects) let images = doc.get_page_images(page_id).unwrap(); for img in images { println!("Image: {}x{}, color: {:?}", img.width, img.height, img.color_space); } ``` -------------------------------- ### Configure SaveOptions for PDF Compression Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Configures fine-grained control over PDF compression settings using the SaveOptions builder. This allows enabling object and cross-reference streams, setting maximum objects per stream, and specifying the zlib compression level. ```rust use lopdf::SaveOptions; let options = SaveOptions::builder() .use_object_streams(true) // Enable object streams (default: false) .use_xref_streams(true) // Enable xref streams (default: false) .max_objects_per_stream(200) // Max objects per stream (default: 100) .compression_level(9) // zlib level 0-9 (default: 6) .build(); ``` -------------------------------- ### Save PDF with Modern Options Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Saves a PDF document using modern, recommended settings for optimal file size and compliance. Ensure the document has content before saving. ```rust use lopdf::{Document, SaveOptions}; use std::fs::File; fn main() -> Result<(), Box> { // Create or load a document let mut doc = Document::with_version("1.5"); // ... add content to document ... // Method 1: Quick modern save (recommended) let mut file = File::create("output.pdf")?; doc.save_modern(&mut file)?; // Method 2: Custom settings for maximum compression let options = SaveOptions::builder() .use_object_streams(true) .use_xref_streams(true) .max_objects_per_stream(200) .compression_level(9) .build(); let mut file2 = File::create("output_max_compressed.pdf")?; doc.save_with_options(&mut file2, options)?; // Compare file sizes (if traditional file exists) if std::path::Path::new("output_traditional.pdf").exists() { let traditional_size = std::fs::metadata("output_traditional.pdf")?.len(); let modern_size = std::fs::metadata("output.pdf")?.len(); let reduction = 100.0 - (modern_size as f64 / traditional_size as f64 * 100.0); println!("Size reduction: {:.1}%", reduction); } Ok(()) } ``` -------------------------------- ### Save PDF with Object Streams (Modern Format) Source: https://context7.com/j-f-liu/lopdf/llms.txt Saves PDFs using object streams for smaller file sizes, compatible with PDF 1.5+. Demonstrates quick saving with defaults and custom compression options. ```rust use lopdf::{Document, SaveOptions}; use std::fs::File; let mut doc = Document::load("input.pdf").unwrap(); // Quick modern save with default settings let mut file = File::create("output_modern.pdf").unwrap(); doc.save_modern(&mut file).unwrap(); // Custom save options for maximum compression let options = SaveOptions::builder() .use_object_streams(true) // Compress objects together .use_xref_streams(true) // Use binary cross-reference streams .max_objects_per_stream(200) // Max objects per stream (default: 100) .compression_level(9) // zlib compression 0-9 (default: 6) .build(); let mut file = File::create("output_compressed.pdf").unwrap(); doc.save_with_options(&mut file, options).unwrap(); // Compare file sizes let original_size = std::fs::metadata("input.pdf").unwrap().len(); let modern_size = std::fs::metadata("output_modern.pdf").unwrap().len(); let reduction = 100.0 - (modern_size as f64 / original_size as f64 * 100.0); println!("Size reduction: {:.1}%", reduction); ``` -------------------------------- ### Create PDF Object Stream Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Constructs a PDF object stream with custom settings for maximum objects and compression level. Objects can be added individually using their object ID and value. ```rust use lopdf::{Object, ObjectStream, dictionary}; # fn main() -> Result<(), Box> { // Create an object stream with custom settings let mut obj_stream = ObjectStream::builder() .max_objects(100) // Maximum objects per stream .compression_level(6) // zlib compression level (0-9) .build(); // Add objects to the stream obj_stream.add_object((1, 0), Object::Integer(42))?; obj_stream.add_object((2, 0), Object::Name(b"Example".to_vec()))?; obj_stream.add_object((3, 0), Object::Dictionary(dictionary! { "Type" => "Font", "Subtype" => "Type1", "BaseFont" => "Helvetica" }))?; // Convert to a stream object let stream = obj_stream.to_stream_object()?; # Ok::<(), Box>(()) # } ``` -------------------------------- ### Create and Manipulate Object Streams Source: https://context7.com/j-f-liu/lopdf/llms.txt Programmatically create object streams for advanced compression control. Add non-stream objects and convert to a stream object for PDF insertion. ```rust use lopdf::{Object, ObjectStream, dictionary}; // Create an object stream with custom settings let mut obj_stream = ObjectStream::builder() .max_objects(100) // Maximum objects per stream .compression_level(6) // zlib compression level (0-9) .build(); // Add objects to the stream (only non-stream objects allowed) obj_stream.add_object((1, 0), Object::Integer(42)).unwrap(); obj_stream.add_object((2, 0), Object::Name(b"Example".to_vec())).unwrap(); obj_stream.add_object((3, 0), Object::Dictionary(dictionary! { "Type" => "Font", "Subtype" => "Type1", "BaseFont" => "Helvetica" })).unwrap(); // Check object count println!("Objects in stream: {}", obj_stream.object_count()); // Convert to a stream object for insertion into PDF let stream = obj_stream.to_stream_object().unwrap(); ``` -------------------------------- ### Work with PDF Objects using lopdf Source: https://context7.com/j-f-liu/lopdf/llms.txt Illustrates creating, adding, retrieving, modifying, deleting, and pruning various PDF object types (null, boolean, integer, real, name, string, array, dictionary, stream) within a PDF document. ```rust use lopdf::{Document, Object, Stream, dictionary}; let mut doc = Document::with_version("1.5"); // Create various object types let null_obj = Object::Null; let bool_obj = Object::Boolean(true); let int_obj = Object::Integer(42); let real_obj = Object::Real(3.14); let name_obj = Object::Name(b"FontName".to_vec()); let string_obj = Object::string_literal("Hello PDF"); // Create arrays let array_obj = Object::Array(vec![ Object::Integer(1), Object::Integer(2), Object::Integer(3), ]); // Create dictionaries using the macro let dict = dictionary! { "Type" => "Example", "Value" => 100, "Flag" => true, "Name" => Object::Name(b"Test".to_vec()), }; // Create streams let content = b"BT /F1 12 Tf (Hello) Tj ET".to_vec(); let stream = Stream::new(dictionary! {}, content); // Add objects to document let dict_id = doc.add_object(dict); let stream_id = doc.add_object(stream); // Get and modify objects let obj = doc.get_object(dict_id).unwrap(); if let Ok(d) = obj.as_dict() { println!("Type: {:?}", d.get(b"Type")); } // Mutable access let obj = doc.get_object_mut(dict_id).unwrap(); if let Ok(d) = obj.as_dict_mut() { d.set("NewKey", "NewValue"); } // Delete objects doc.delete_object(dict_id); // Prune unused objects let removed = doc.prune_objects(); println!("Removed {} unused objects", removed.len()); // Renumber objects (useful after deletions) doc.renumber_objects(); ``` -------------------------------- ### Load and process encrypted PDFs in Rust Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Demonstrates loading an encrypted PDF and verifying decryption status. Supports both synchronous and asynchronous execution modes. ```rust use lopdf::Document; #[cfg(not(feature = "async"))] fn main() -> Result<(), Box> { // Load an encrypted PDF - automatically attempts decryption let doc = Document::load("assets/encrypted.pdf")?; // Check encryption status if doc.is_encrypted() { println!("Document is encrypted"); // Check if decryption was successful if doc.encryption_state.is_some() { println!("Successfully decrypted"); // Now you can work with the document normally let pages = doc.get_pages(); println!("Pages: {}", pages.len()); // Extract text let page_nums: Vec = pages.keys().cloned().collect(); let text = doc.extract_text(&page_nums)?; println!("Text length: {} chars", text.len()); // Access objects for i in 1..=10 { if let Ok(_) = doc.get_object((i, 0)) { println!("Object ({}, 0) accessible", i); } } } else { println!("Decryption failed - password required"); } } Ok(()) } #[cfg(feature = "async")] #[tokio::main] async fn main() -> Result<(), Box> { // Load an encrypted PDF - automatically attempts decryption let doc = Document::load("assets/encrypted.pdf").await?; // Check encryption status if doc.is_encrypted() { println!("Document is encrypted"); // Check if decryption was successful if doc.encryption_state.is_some() { println!("Successfully decrypted"); // Now you can work with the document normally let pages = doc.get_pages(); println!("Pages: {}", pages.len()); // Extract text let page_nums: Vec = pages.keys().cloned().collect(); let text = doc.extract_text(&page_nums)?; println!("Text length: {} chars", text.len()); // Access objects for i in 1..=10 { if let Ok(_) = doc.get_object((i, 0)) { println!("Object ({}, 0) accessible", i); } } } else { println!("Decryption failed - password required"); } } Ok(()) } ``` -------------------------------- ### Compress and Decompress PDF Streams Source: https://context7.com/j-f-liu/lopdf/llms.txt Control stream compression for content manipulation or file size optimization. Use `decompress()` to access plain content and `compress()` to reduce file size. ```rust use lopdf::{Document, Stream, dictionary}; let mut doc = Document::load("document.pdf").unwrap(); // Decompress all streams in the document doc.decompress(); // Now streams can be inspected as plain text for (id, obj) in &doc.objects { if let Ok(stream) = obj.as_stream() { if !stream.is_compressed() { println!("Stream {:?} content: {} bytes", id, stream.content.len()); } } } // Compress all streams doc.compress(); // Work with individual streams let mut stream = Stream::new( dictionary! {}, b"BT /F1 12 Tf (Hello) Tj ET".to_vec(), ); // Compress the stream stream.compress().unwrap(); println!("Compressed: {}", stream.is_compressed()); // Decompress to access content stream.decompress().unwrap(); let plain_content = stream.decompressed_content().unwrap(); println!("Content: {}", String::from_utf8_lossy(&plain_content)); // Get content without modifying stream state let content = stream.get_plain_content().unwrap(); doc.save("processed.pdf").unwrap(); ``` -------------------------------- ### Perform Asynchronous PDF Loading Source: https://context7.com/j-f-liu/lopdf/llms.txt Use asynchronous loading for non-blocking PDF operations, requiring the `async` feature. Supports loading from files, with passwords, and extracting metadata asynchronously. Note that save operations remain synchronous. ```rust // Enable with: lopdf = { version = "0.40", features = ["async"] } use lopdf::Document; #[tokio::main] async fn main() { // Async load from file let doc = Document::load("document.pdf").await.unwrap(); println!("Loaded {} pages", doc.get_pages().len()); // Async load with password let doc = Document::load_with_password("encrypted.pdf", "password").await.unwrap(); // Async metadata extraction let metadata = Document::load_metadata("large.pdf").await.unwrap(); println!("Title: {:?}", metadata.title); // Note: save operations remain synchronous let mut doc = Document::load("input.pdf").await.unwrap(); doc.save("output.pdf").unwrap(); } ``` -------------------------------- ### Save PDF with Object Streams Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Saves a document using modern compression features like object streams and cross-reference streams to reduce file size. ```rust use lopdf::{Document, SaveOptions}; #[cfg(not(feature = "async"))] fn main() -> Result<(), Box> { // Load existing PDF let mut doc = Document::load("input.pdf")?; // Save with modern features (object streams + cross-reference streams) // This typically reduces file size by 11-38% let mut file = std::fs::File::create("output.pdf")?; doc.save_modern(&mut file)?; // For more control, use SaveOptions let options = SaveOptions::builder() .use_object_streams(true) // Enable object streams .use_xref_streams(true) // Enable cross-reference streams .max_objects_per_stream(200) // Max objects per stream (default: 100) .compression_level(9) // Compression level 0-9 (default: 6) .build(); let mut file2 = std::fs::File::create("output_custom.pdf")?; ``` -------------------------------- ### Encrypt and Decrypt PDFs Source: https://context7.com/j-f-liu/lopdf/llms.txt Handles PDF encryption and decryption. Checks encryption status, decrypts with a password, and authenticates passwords without full decryption. ```rust use lopdf::{Document, EncryptionState}; // Load and check encryption status let doc = Document::load("document.pdf").unwrap(); if doc.is_encrypted() { println!("Document is currently encrypted"); } if doc.was_encrypted() { println!("Document was encrypted when loaded (now decrypted)"); } // Decrypt an encrypted PDF with password let mut doc = Document::load("encrypted.pdf").unwrap(); if doc.is_encrypted() { doc.decrypt("user_password").unwrap(); println!("Successfully decrypted"); } // Authenticate passwords without decrypting let doc = Document::load("encrypted.pdf").unwrap(); match doc.authenticate_password("test_password") { Ok(_) => println!("Password is valid"), Err(_) => println!("Invalid password"), } // Check specific password types doc.authenticate_user_password("user_pass").ok(); doc.authenticate_owner_password("owner_pass").ok(); ``` -------------------------------- ### Perform Incremental PDF Updates Source: https://context7.com/j-f-liu/lopdf/llms.txt Support for PDF incremental updates, preserving document history. Load as an `IncrementalDocument` to modify only the new layer, or create a new document from a previous one. ```rust use lopdf::{IncrementalDocument, Document}; // Load as incremental document (preserves original bytes) let mut inc_doc = IncrementalDocument::load("original.pdf").unwrap(); // Access the new document layer let new_doc = &mut inc_doc.new_document; // Make modifications to the new layer only // ... modifications ... // Save preserves all previous versions plus new changes inc_doc.save("updated.pdf").unwrap(); // Create incremental update from existing document let prev_doc = Document::load("base.pdf").unwrap(); let new_doc = Document::new_from_prev(&prev_doc); // new_doc.trailer contains "Prev" pointing to previous xref ``` -------------------------------- ### Decrypt PDF Documents Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Loads an encrypted PDF and attempts automatic decryption using an empty password. ```rust use lopdf::Document; // Load and decrypt PDF documents with empty password #[cfg(not(feature = "async"))] { // Load an encrypted PDF - automatically attempts decryption with empty password let doc = Document::load("assets/encrypted.pdf").unwrap(); // Check if the document is encrypted if doc.is_encrypted() { println!("Document is encrypted"); // The document has been automatically decrypted if the password was empty if doc.encryption_state.is_some() { println!("Successfully decrypted with empty password"); } } // Access decrypted content let pages = doc.get_pages(); println!("Number of pages: {}", pages.len()); // Extract text from decrypted document ``` -------------------------------- ### Merge multiple PDF documents Source: https://context7.com/j-f-liu/lopdf/llms.txt Combines several PDF files into a single document, renumbering objects to avoid conflicts and adding bookmarks for each source document. ```rust use lopdf::{Document, Object, ObjectId, Bookmark}; use std::collections::BTreeMap; // Load documents to merge let documents = vec![ Document::load("doc1.pdf").unwrap(), Document::load("doc2.pdf").unwrap(), Document::load("doc3.pdf").unwrap(), ]; let mut max_id = 1; let mut pagenum = 1; let mut documents_pages = BTreeMap::new(); let mut documents_objects = BTreeMap::new(); let mut document = Document::with_version("1.5"); for mut doc in documents { let mut first = false; doc.renumber_objects_with(max_id); max_id = doc.max_id + 1; documents_pages.extend( doc.get_pages() .into_iter() .map(|(_, object_id)| { if !first { // Add bookmark for first page of each document let bookmark = Bookmark::new( format!("Document {}", pagenum), [0.0, 0.0, 1.0], // Blue color 0, // Normal style object_id, ); document.add_bookmark(bookmark, None); first = true; pagenum += 1; } (object_id, doc.get_object(object_id).unwrap().to_owned()) }) .collect::>(), ); documents_objects.extend(doc.objects); } // Process and merge objects (catalogs, pages trees, etc.) let mut catalog_object: Option<(ObjectId, Object)> = None; let mut pages_object: Option<(ObjectId, Object)> = None; for (object_id, object) in documents_objects.iter() { match object.type_name().unwrap_or(b"") { b"Catalog" => { catalog_object = Some(( catalog_object.map(|(id, _)| id).unwrap_or(*object_id), object.clone(), )); } b"Pages" => { if let Ok(dictionary) = object.as_dict() { let mut dictionary = dictionary.clone(); if let Some((_, ref obj)) = pages_object { if let Ok(old_dict) = obj.as_dict() { dictionary.extend(old_dict); } } pages_object = Some(( pages_object.map(|(id, _)| id).unwrap_or(*object_id), Object::Dictionary(dictionary), )); } } b"Page" | b"Outlines" | b"Outline" => {} _ => { document.objects.insert(*object_id, object.clone()); } } } // Finalize merged document structure let (catalog_id, _) = catalog_object.unwrap(); let (pages_id, pages_obj) = pages_object.unwrap(); // Update pages with all kids if let Ok(dictionary) = pages_obj.as_dict() { let mut dictionary = dictionary.clone(); dictionary.set("Count", documents_pages.len() as u32); dictionary.set( "Kids", documents_pages .keys() .map(|&id| Object::Reference(id)) .collect::>(), ); document.objects.insert(pages_id, Object::Dictionary(dictionary)); } // Insert all pages for (object_id, object) in documents_pages { if let Ok(dictionary) = object.as_dict() { let mut dictionary = dictionary.clone(); dictionary.set("Parent", pages_id); document.objects.insert(object_id, Object::Dictionary(dictionary)); } } document.trailer.set("Root", catalog_id); document.max_id = document.objects.len() as u32; document.renumber_objects(); document.adjust_zero_pages(); // Build bookmarks outline if let Some(outline_id) = document.build_outline() { if let Ok(Object::Dictionary(dict)) = document.get_object_mut(catalog_id) { dict.set("Outlines", Object::Reference(outline_id)); } } document.compress(); document.save("merged.pdf").unwrap(); ``` -------------------------------- ### Insert Images into PDF Source: https://context7.com/j-f-liu/lopdf/llms.txt Embeds images into PDF pages at specified positions and sizes. Supports loading images from files or memory buffers, requires the 'embed_image' feature. ```rust use lopdf::{Document, xobject}; let mut doc = Document::load("document.pdf").unwrap(); let pages = doc.get_pages(); let page_id = *pages.get(&1).unwrap(); // Load and embed an image (requires "embed_image" feature) #[cfg(feature = "embed_image")] { let img_stream = xobject::image("photo.jpg").unwrap(); // Insert image at position (x, y) with size (width, height) doc.insert_image( page_id, img_stream, (100.0, 500.0), // Position (x, y) from bottom-left (200.0, 150.0), // Size (width, height) ).unwrap(); } // Create image from memory buffer #[cfg(feature = "embed_image")] { let image_bytes = std::fs::read("image.png").unwrap(); let img_stream = xobject::image_from(image_bytes).unwrap(); doc.insert_image(page_id, img_stream, (300.0, 500.0), (100.0, 100.0)).unwrap(); } doc.save("with_images.pdf").unwrap(); ``` -------------------------------- ### Merge PDF Document Objects Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Iterates through document objects to merge Pages and Catalog structures, renumbering objects and compressing the final output. ```rust object.clone(), )); } b"Pages" => { // Collect and update a first "Pages" object and use it for the future "Catalog" // We have also to merge all dictionaries of the old and the new "Pages" object if let Ok(dictionary) = object.as_dict() { let mut dictionary = dictionary.clone(); if let Some((_, ref object)) = pages_object { if let Ok(old_dictionary) = object.as_dict() { dictionary.extend(old_dictionary); } } pages_object = Some(( if let Some((id, _)) = pages_object { id } else { *object_id }, Object::Dictionary(dictionary), )); } } b"Page" => {} // Ignored, processed later and separately b"Outlines" => {} // Ignored, not supported yet b"Outline" => {} // Ignored, not supported yet _ => { document.objects.insert(*object_id, object.clone()); } } } // If no "Pages" object found, abort. if pages_object.is_none() { println!("Pages root not found."); return Ok(()); } // Iterate over all "Page" objects and collect into the parent "Pages" created before for (object_id, object) in documents_pages.iter() { if let Ok(dictionary) = object.as_dict() { let mut dictionary = dictionary.clone(); dictionary.set("Parent", pages_object.as_ref().unwrap().0); document .objects .insert(*object_id, Object::Dictionary(dictionary)); } } // If no "Catalog" found, abort. if catalog_object.is_none() { println!("Catalog root not found."); return Ok(()); } let catalog_object = catalog_object.unwrap(); let pages_object = pages_object.unwrap(); // Build a new "Pages" with updated fields if let Ok(dictionary) = pages_object.1.as_dict() { let mut dictionary = dictionary.clone(); // Set new pages count dictionary.set("Count", documents_pages.len() as u32); // Set new "Kids" list (collected from documents pages) for "Pages" dictionary.set( "Kids", documents_pages .into_iter() .map(|(object_id, _)| Object::Reference(object_id)) .collect::>(), ); document .objects .insert(pages_object.0, Object::Dictionary(dictionary)); } // Build a new "Catalog" with updated fields if let Ok(dictionary) = catalog_object.1.as_dict() { let mut dictionary = dictionary.clone(); dictionary.set("Pages", pages_object.0); dictionary.remove(b"Outlines"); // Outlines not supported in merged PDFs document .objects .insert(catalog_object.0, Object::Dictionary(dictionary)); } document.trailer.set("Root", catalog_object.0); // Update the max internal ID as wasn't updated before due to direct objects insertion document.max_id = document.objects.len() as u32; // Reorder all new Document objects document.renumber_objects(); // Set any Bookmarks to the First child if they are not set to a page document.adjust_zero_pages(); // Set all bookmarks to the PDF Object tree then set the Outlines to the Bookmark content map. if let Some(n) = document.build_outline() { if let Ok(Object::Dictionary(dict)) = document.get_object_mut(catalog_object.0) { dict.set("Outlines", Object::Reference(n)); } } document.compress(); // Save the merged PDF. // Store file in current working directory. // Note: Line is excluded when running doc tests if false { document.save("merged.pdf").unwrap(); } Ok(()) ``` -------------------------------- ### Extract Text and Access Objects Source: https://github.com/j-f-liu/lopdf/blob/main/README.md Extracts text from specific pages and iterates through document objects. Requires appropriate feature flags for async operations. ```rust let page_numbers: Vec = pages.keys().cloned().collect(); let text = doc.extract_text(&page_numbers).unwrap(); println!("Extracted {} characters of text", text.len()); // Access individual objects for i in 1..=10 { if let Ok(obj) = doc.get_object((i, 0)) { println!("Successfully accessed object ({}, 0)", i); } } } #[cfg(feature = "async")] { tokio::runtime::Builder::new_current_thread() .build() .expect("Failed to create runtime") .block_on(async move { // Async version let doc = Document::load("assets/encrypted.pdf").await.unwrap(); if doc.is_encrypted() { println!("Document is encrypted"); if doc.encryption_state.is_some() { println!("Successfully decrypted with empty password"); } } let pages = doc.get_pages(); let page_numbers: Vec = pages.keys().cloned().collect(); let text = doc.extract_text(&page_numbers).unwrap(); println!("Extracted {} characters of text", text.len()); }); } ``` -------------------------------- ### Extract Text from PDF Pages Source: https://context7.com/j-f-liu/lopdf/llms.txt Extracts text content from specified PDF pages using font encoding detection and content stream parsing. Requires the `lopdf` crate. ```rust use lopdf::Document; let doc = Document::load("document.pdf").unwrap(); // Get all page numbers let pages = doc.get_pages(); let page_numbers: Vec = pages.keys().cloned().collect(); // Extract text from all pages let text = doc.extract_text(&page_numbers).unwrap(); println!("Extracted text:\n{}", text); // Extract text from specific pages only let text = doc.extract_text(&[1, 3, 5]).unwrap(); // Extract text as separate chunks per page (preserves encoding info) let chunks = doc.extract_text_chunks(&[1, 2]); for (i, chunk) in chunks.iter().enumerate() { match chunk { Ok(text) => println!("Page {} text: {}", i + 1, text), Err(e) => eprintln!("Error on page {}: {:?}", i + 1, e), } } ``` -------------------------------- ### Embed Custom TrueType Fonts in PDF Source: https://context7.com/j-f-liu/lopdf/llms.txt Add TrueType fonts to PDF documents by reading the font file, creating `FontData` with metadata, and adding it to the document. The font can then be used in page resources. ```rust use lopdf::{Document, FontData, dictionary}; // Load font file let font_bytes = std::fs::read("./fonts/MyFont.ttf").unwrap(); // Create font data with metadata let mut font_data = FontData::new(&font_bytes, "MyFont".to_string()); font_data .set_flags(32) // Font flags (symbolic, serif, etc.) .set_font_bbox((0, -200, 1000, 800)) .set_italic_angle(0) .set_ascent(750) .set_descent(-250) .set_cap_height(700) .set_stem_v(80) .set_encoding("WinAnsiEncoding".to_string()); // Create document and add font let mut doc = Document::with_version("1.5"); let font_id = doc.add_font(font_data).unwrap(); // Use the font in page resources let resources_id = doc.add_object(dictionary! { "Font" => dictionary! { "F1" => font_id, }, }); // Create page content using the font // ... (use "F1" in content stream operations) ```