Read and write PDF metadata and linearize documents

This guide covers specific PDF operations for developers using the PDFluent SDK. Learn to manage document properties and optimise for the web.

Add PDFluent to your project

Add the pdfluent crate to Cargo.toml.

toml
[dependencies]
pdfluent = "1.0.0-beta.18"

Read PDF metadata in Rust

Read title, author, subject, keywords, producer, creator and timestamps from any PDF's document-information dictionary.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("report.pdf")?;
    let meta = doc.metadata();
    println!("title: {:?}", meta.title);
    println!("author: {:?}", meta.author);
    Ok(())
}
  1. Open the PDF

    Open the document. Metadata is cached on the PdfDocument and read lazily from the Info dictionary on first access.

    rust
    use pdfluent::prelude::*;
    
    let doc = PdfDocument::open("report.pdf")?;
  2. Call doc.metadata()

    metadata() returns a Metadata struct — a plain snapshot with public fields. There's no Result to unwrap for reads; missing entries surface as None / empty Vec.

    rust
    let meta = doc.metadata();
    println!("title = {:?}", meta.title);
    println!("author = {:?}", meta.author);
  3. Inspect every standard field

    Metadata exposes title, author, subject, keywords (Vec<String>), producer, creator, creation_date and modification_date. Dates are PDF D-format strings (e.g. "D:20260421103000+02'00'") — parse them through your preferred date library if you need a DateTime.

    rust
    let meta = doc.metadata();
    if let Some(ref t) = meta.title { println!("T: {}", t); }
    if let Some(ref a) = meta.author { println!("A: {}", a); }
    for k in &meta.keywords { println!("K: {}", k); }
  4. Bulk-read for a directory of PDFs

    Loop over files. Dropping the document at the end of each iteration keeps memory bounded across large batches.

    rust
    use pdfluent::prelude::*;
    use std::fs;
    
    for entry in fs::read_dir("./inbox")? {
        let path = entry?.path();
        if path.extension().map(|e| e == "pdf").unwrap_or(false) {
            match PdfDocument::open(&path) {
                Ok(doc) => {
                    let m = doc.metadata();
                    println!(
                        "{}: {} — {}",
                        path.display(),
                        m.title.as_deref().unwrap_or("(no title)"),
                        m.author.as_deref().unwrap_or("(no author)"),
                    );
                }
                Err(e) => eprintln!("{}: {}", path.display(), e),
            }
        }
    }
  • Metadata.title and Metadata.author are Option<String>; a document may have no title or no author set.
  • Metadata.keywords is Vec<String>, parsed from the PDF's /Keywords entry — an empty vector means no keywords.
  • creation_date and modification_date are PDF D-format strings; conversion to chrono::DateTime is an application-side concern.
  • The 1.0 SDK exposes the Info-dictionary surface on Metadata. Full XMP metadata read (structured RDF) is tracked for a later release.

Write PDF metadata in Rust

Set title, author, subject and keywords on any PDF via the MetadataMut builder. Changes are buffered until commit(), then flushed to the document.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let mut doc = PdfDocument::open("report.pdf")?;
    doc.metadata_mut()
        .set_title("Q4 Report")
        .set_author("Finance Team")
        .commit()?;
    doc.save("report-tagged.pdf")?;
    Ok(())
}
  1. Open the PDF for mutation

    Open with a mutable binding. MetadataMut borrows &mut on the document for the duration of the builder chain.

    rust
    use pdfluent::prelude::*;
    
    let mut doc = PdfDocument::open("report.pdf")?;
  2. Chain setters via metadata_mut()

    metadata_mut() returns a MetadataMut builder. Each setter returns &mut Self so you can chain them. Changes are buffered locally — nothing is written until commit().

    rust
    let mut meta = doc.metadata_mut();
    meta.set_title("Q3 Financial Report")
        .set_author("Finance Team")
        .set_subject("Quarterly earnings")
        .set_keywords(&["finance", "q3", "2026"]);
  3. Commit to flush changes into the document

    commit() writes the buffered changes to the Info dictionary. It returns Result<()>; call it explicitly so you can handle write errors. MetadataMut also flushes on drop, but in that path errors are silenced.

    rust
    doc.metadata_mut()
        .set_title("Q3 Financial Report")
        .set_author("Finance Team")
        .commit()?;
  4. Save the tagged document

    save() writes the PDF to disk. The metadata changes are part of that write; no separate flush step required.

    rust
    doc.save("report_tagged.pdf")?;
  • metadata_mut() is infallible — a PDF always has an Info dictionary slot, created lazily by commit() if absent.
  • The 1.0 setter surface is set_title, set_author, set_subject, set_keywords. Producer, creator, creation_date and modification_date are read-only in 1.0 (they're written by PDFluent at save time).
  • keywords takes a &[&str] and is serialised joined with commas in the PDF's /Keywords entry, which is the common convention.
  • Non-ASCII values (titles with accented characters, CJK text) round-trip correctly — PDFluent picks UTF-16BE with BOM when needed.
  • Writing metadata does not require any specific capability; it's part of the core SDK surface and available at every tier.

Write XMP metadata to a PDF in Rust

Write Dublin Core, XMP Basic, and custom XMP metadata packets to a PDF. XMP metadata is readable by search engines, DAM systems, and archival tools.

rust
use pdfluent::{PdfDocument, XmpMetadata};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut doc = PdfDocument::open("whitepaper.pdf")?;

    let xmp = XmpMetadata::new()
        .title("PDFluent Technical Whitepaper")
        .creator("Engineering Team")
        .description("Architecture overview of the PDFluent Rust SDK")
        .subject(vec!["PDF", "Rust", "SDK"])
        .rights("Copyright 2025 PDFluent")
        .language("en-US");

    doc.set_xmp_metadata(xmp)?;
    doc.save("whitepaper_with_xmp.pdf")?;
    Ok(())
}
  1. Open the PDF

    Load the document to which you want to add XMP metadata.

    rust
    use pdfluent::PdfDocument;
    
    let mut doc = PdfDocument::open("report.pdf")?;
  2. Build the XMP metadata object

    XmpMetadata provides setters for Dublin Core and XMP Basic properties. All fields are optional.

    rust
    use pdfluent::XmpMetadata;
    
    let xmp = XmpMetadata::new()
        .title("Annual Report 2025")
        .creator("Finance Department")
        .description("Consolidated financial statements for fiscal year 2025")
        .subject(vec!["Finance", "Annual Report", "2025"])
        .publisher("Acme Corp")
        .rights("All rights reserved")
        .language("en-GB")
        .creation_date("2025-03-01T09:00:00Z")
        .modify_date("2025-04-14T15:30:00Z");
  3. Add a custom XMP namespace and property

    Register a custom namespace to store application-specific metadata alongside the standard Dublin Core fields.

    rust
    let xmp = xmp
        .custom_namespace("http://ns.acme.com/pdf/1.0/", "acme")
        .custom_property("acme:documentId", "DOC-2025-0042")
        .custom_property("acme:department", "Legal")
        .custom_property("acme:confidentiality", "Internal");
  4. Write the metadata and save

    set_xmp_metadata() serialises the XMP packet and embeds it in the PDF. Existing XMP metadata is replaced.

    rust
    doc.set_xmp_metadata(xmp)?;
    doc.save("report_with_xmp.pdf")?;
    println!("XMP metadata written.");
  • XMP metadata in PDFs is stored as an XML packet in the /Metadata stream of the document catalog.
  • Setting XMP metadata does not change the DocInfo dictionary (/Author, /Title, etc.). Use doc.set_info() to set both.
  • XMP supports multi-language values via xml:lang attributes. Use .title_lang("fr-FR", "Rapport Annuel") for localised titles.
  • After calling set_xmp_metadata(), any existing digital signature becomes invalid. Set metadata before signing.

Read the PDF spec version from a document in Rust

Useful for pre-flight checks, compatibility filtering, and document auditing pipelines.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("file.pdf")?;
    let v = doc.version();
    println!("PDF {}.{}", v.major, v.minor);
    Ok(())
}
  1. Open the document and call pdf_version()

    pdf_version() reads the %PDF-x.y header from the first 8 bytes of the file. It does not require full document parsing.

    rust
    use pdfluent::PdfDocument;
    
    let doc = PdfDocument::open("document.pdf")?;
    let version = doc.version();
    
    println!("{}.{}", version.major, version.minor);
  2. Compare versions with the PdfVersion enum

    Use predefined constants to write readable version checks. PdfVersion implements PartialOrd.

    rust
    let v = doc.version();
    
    match (v.major, v.minor) {
        (1, 0) => println!("Very old document"),
        (1, 4) => println!("PDF 1.4 - supports transparency"),
        (1, 5) => println!("PDF 1.5 - supports object streams"),
        (1, 6) => println!("PDF 1.6 - supports AES-128"),
        (1, 7) => println!("PDF 1.7 - supports AES-256"),
        (2, 0) => println!("PDF 2.0 - latest standard"),
        _ => println!("Other version: {}.{}", v.major, v.minor),
    }
  3. Read version without fully opening the document

    Use PdfDocument::peek_version() to read only the header bytes. This is faster when you need to filter files before loading them.

    rust
    use pdfluent::PdfDocument;
    
    let doc = PdfDocument::open("document.pdf")?;
    let version = doc.version();
    println!("PDF {}.{}", version.major, version.minor);
    
    // Only proceed for documents that are at least PDF 1.6
    if (version.major, version.minor) >= (1, 6) {
        // ...
    }
  • The version in the %PDF header is the declared version. Some tools update the DocumentCatalog Version entry without changing the header. PDFluent returns the higher of the two values.
  • PDF 2.0 (ISO 32000-2) is the current standard. PDF 1.7 (ISO 32000-1) is still the most common version in real-world documents.
  • Versions below 1.4 do not support transparency groups. Versions below 1.5 do not support cross-reference streams.

Detect if a PDF is linearized (web-optimized) in Rust

Read the linearization dictionary from the start of a PDF file to determine if it is structured for fast web delivery.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("file.pdf")?;

    if doc.is_linearized() {
        println!("PDF is linearized (web-optimized).");
        if let Some(info) = doc.linearization_info() {
            println!("File length hint: {}", info.file_length());
            println!("First page end:   {}", info.first_page_end_offset());
        }
    } else {
        println!("PDF is not linearized.");
    }

    Ok(())
}
  1. Open the PDF

    Linearization is checked by inspecting the first object in the file. No full parse is required.

    rust
    let doc = PdfDocument::open("file.pdf")?;
  2. Check the linearization flag

    is_linearized() reads the first cross-reference table and checks for the /Linearized dictionary key.

    rust
    if doc.is_linearized() {
        println!("Linearized.");
    } else {
        println!("Not linearized.");
    }
  3. Read linearization parameters

    The linearization dictionary contains hints used by HTTP range-request-based PDF viewers. linearization_info() exposes the key fields.

    rust
    if let Some(info) = doc.linearization_info() {
        println!("File length:       {}", info.file_length());
        println!("First page number: {}", info.first_page_number());
        println!("First page end:    {}", info.first_page_end_offset());
        println!("Hint stream start: {:?}", info.hint_stream_offset());
    }
  4. Validate the linearization hints

    If the file has been modified after linearization, the hint offsets may be stale. validate_linearization() checks offsets against the actual file structure.

    rust
    let valid = doc.validate_linearization()?;
    if !valid {
        println!("Warning: linearization hints are out of date.");
        println!("Re-linearize for optimal web performance.");
    }
  5. Linearize a non-linearized PDF

    Call doc.linearize() to produce a linearized copy. This is typically done as the final step before publishing.

    rust
    if !doc.is_linearized() {
        let mut doc = PdfDocument::open("file.pdf")?;
        doc.linearize()?;
        doc.save("web_optimized.pdf")?;
    }
  • A PDF modified after linearization becomes de-linearized. The /Linearized key is still present but the hints are stale and should not be trusted.
  • Linearization only matters for HTTP range-request delivery. For local file access or downloads, it has no performance benefit.
  • Linearized PDFs typically have the first page objects near the start of the file, allowing browsers to display the first page before the full download completes.
  • validate_linearization() is a read operation and is safe to call on any PDF, linearized or not.

Linearize a PDF for faster web loading in Rust

Restructure a PDF so the first page is available before the full file downloads. Known as "Fast Web View" in Acrobat.

rust
use pdfluent::{PdfDocument, LinearizeOptions};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut doc = PdfDocument::open("brochure.pdf")?;

    doc.linearize(LinearizeOptions::default())?;
    doc.save("brochure_linear.pdf")?;

    println!("PDF is now linearized for fast web view");
    Ok(())
}
  1. Open the PDF

    Load the document. Linearization rearranges the internal file structure.

    rust
    use pdfluent::PdfDocument;
    
    let mut doc = PdfDocument::open("catalogue.pdf")?;
  2. Configure linearization options

    LinearizeOptions lets you control hint tables and resource ordering. The defaults work well for most documents.

    rust
    use pdfluent::LinearizeOptions;
    
    let opts = LinearizeOptions::default()
        .primary_page_hint_stream(true) // helps browsers fetch only page 1 first
        .reorder_resources_by_page(true);
  3. Apply linearization

    Call linearize() on the document. The internal structure is rearranged so the first page and its resources appear at the beginning of the file.

    rust
    doc.linearize(opts)?;
    
    // Verify the result
    println!("Linearized: {}", doc.is_linearized());
  4. Save the linearized PDF

    Write to a new file. Serve this file from your web server with byte-range request support enabled.

    rust
    doc.save("catalogue_linear.pdf")?;
    println!("Ready to serve via HTTP with Range support");
  • Linearization is only effective when the file is served over HTTP with byte-range requests enabled (Accept-Ranges: bytes).
  • Any modification after linearization (adding pages, annotations, etc.) removes the linearization. Re-linearize before re-serving.
  • Linearization increases file size by a few percent due to added hint tables. The trade-off is faster first-page display.
  • Use is_linearized() to check whether a PDF is already linearized before processing it again.