This guide covers specific PDF operations for developers using the PDFluent SDK. Learn to manage document properties and optimise for the web.
Add the pdfluent crate to Cargo.toml.
[dependencies]
pdfluent = "1.0.0-beta.18"Read title, author, subject, keywords, producer, creator and timestamps from any PDF's document-information dictionary.
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(())
}Open the document. Metadata is cached on the PdfDocument and read lazily from the Info dictionary on first access.
use pdfluent::prelude::*;
let doc = PdfDocument::open("report.pdf")?;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.
let meta = doc.metadata();
println!("title = {:?}", meta.title);
println!("author = {:?}", meta.author);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.
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); }Loop over files. Dropping the document at the end of each iteration keeps memory bounded across large batches.
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),
}
}
}Set title, author, subject and keywords on any PDF via the MetadataMut builder. Changes are buffered until commit(), then flushed to the document.
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(())
}Open with a mutable binding. MetadataMut borrows &mut on the document for the duration of the builder chain.
use pdfluent::prelude::*;
let mut doc = PdfDocument::open("report.pdf")?;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().
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"]);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.
doc.metadata_mut()
.set_title("Q3 Financial Report")
.set_author("Finance Team")
.commit()?;save() writes the PDF to disk. The metadata changes are part of that write; no separate flush step required.
doc.save("report_tagged.pdf")?;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.
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(())
}Load the document to which you want to add XMP metadata.
use pdfluent::PdfDocument;
let mut doc = PdfDocument::open("report.pdf")?;XmpMetadata provides setters for Dublin Core and XMP Basic properties. All fields are optional.
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");Register a custom namespace to store application-specific metadata alongside the standard Dublin Core fields.
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");set_xmp_metadata() serialises the XMP packet and embeds it in the PDF. Existing XMP metadata is replaced.
doc.set_xmp_metadata(xmp)?;
doc.save("report_with_xmp.pdf")?;
println!("XMP metadata written.");Useful for pre-flight checks, compatibility filtering, and document auditing pipelines.
use pdfluent::PdfDocument;
fn main() -> pdfluent::Result<()> {
let doc = PdfDocument::open("file.pdf")?;
let v = doc.version();
println!("PDF {}.{}", v.major, v.minor);
Ok(())
}pdf_version() reads the %PDF-x.y header from the first 8 bytes of the file. It does not require full document parsing.
use pdfluent::PdfDocument;
let doc = PdfDocument::open("document.pdf")?;
let version = doc.version();
println!("{}.{}", version.major, version.minor);Use predefined constants to write readable version checks. PdfVersion implements PartialOrd.
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),
}Use PdfDocument::peek_version() to read only the header bytes. This is faster when you need to filter files before loading them.
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) {
// ...
}Read the linearization dictionary from the start of a PDF file to determine if it is structured for fast web delivery.
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(())
}Linearization is checked by inspecting the first object in the file. No full parse is required.
let doc = PdfDocument::open("file.pdf")?;is_linearized() reads the first cross-reference table and checks for the /Linearized dictionary key.
if doc.is_linearized() {
println!("Linearized.");
} else {
println!("Not linearized.");
}The linearization dictionary contains hints used by HTTP range-request-based PDF viewers. linearization_info() exposes the key fields.
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());
}If the file has been modified after linearization, the hint offsets may be stale. validate_linearization() checks offsets against the actual file structure.
let valid = doc.validate_linearization()?;
if !valid {
println!("Warning: linearization hints are out of date.");
println!("Re-linearize for optimal web performance.");
}Call doc.linearize() to produce a linearized copy. This is typically done as the final step before publishing.
if !doc.is_linearized() {
let mut doc = PdfDocument::open("file.pdf")?;
doc.linearize()?;
doc.save("web_optimized.pdf")?;
}Restructure a PDF so the first page is available before the full file downloads. Known as "Fast Web View" in Acrobat.
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(())
}Load the document. Linearization rearranges the internal file structure.
use pdfluent::PdfDocument;
let mut doc = PdfDocument::open("catalogue.pdf")?;LinearizeOptions lets you control hint tables and resource ordering. The defaults work well for most documents.
use pdfluent::LinearizeOptions;
let opts = LinearizeOptions::default()
.primary_page_hint_stream(true) // helps browsers fetch only page 1 first
.reorder_resources_by_page(true);Call linearize() on the document. The internal structure is rearranged so the first page and its resources appear at the beginning of the file.
doc.linearize(opts)?;
// Verify the result
println!("Linearized: {}", doc.is_linearized());Write to a new file. Serve this file from your web server with byte-range request support enabled.
doc.save("catalogue_linear.pdf")?;
println!("Ready to serve via HTTP with Range support");