How-to guides/Compression

Compress a PDF in Rust

Shrink a PDF in-memory with CompressOptions. Three presets cover the common cases: strict (default), lossy, and archival.

rust
use pdfluent::prelude::*;

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

    let report = doc.compress(CompressOptions::strict())?;

    println!("streams compressed:     {}", report.streams_compressed);
    println!("duplicates deduplicated: {}", report.streams_deduplicated);
    println!("unused objects removed:  {}", report.unused_removed);
    if let Some(fs) = &report.font_subset {
        println!("fonts subsetted:         {} / {}", fs.fonts_subsetted, fs.fonts_processed);
        println!("bytes saved (fonts):     {}", fs.bytes_saved);
    }

    doc.save_with(
        "report_compressed.pdf",
        SaveOptions::new().with_overwrite(true),
    )?;
    Ok(())
}
Install:cargo add pdfluent@1.0.0-beta.5Download SDK →

Step by step

1

Open the PDF with a mutable binding

compress takes &mut self and rewrites the in-memory document. You save afterwards to persist.

rust
use pdfluent::prelude::*;

let mut doc = PdfDocument::open("report.pdf")?;
2

Pick a preset

CompressOptions::strict() is the default and enables every pass: font subsetting, stream compression, duplicate-stream deduplication, unused-object removal. CompressOptions::lossy() matches strict() today; it reserves the slot for 1.1 lossy image downsampling. CompressOptions::archival() keeps unused objects (safer for incremental updates and signed appearance streams).

rust
// full stack — recommended default
let opts = CompressOptions::strict();

// reserved for 1.1 lossy passes; today identical to strict
let opts = CompressOptions::lossy();

// keep unused objects — safest for signed / incremental-update docs
let opts = CompressOptions::archival();
3

Run compress and read the CompressReport

compress returns a CompressReport with counters for each pass. font_subset is an Option<FontSubsetReport> — None when font subsetting is disabled, Some with per-pass counters otherwise.

rust
let report = doc.compress(CompressOptions::strict())?;

println!("streams compressed: {}", report.streams_compressed);
println!("streams deduplicated: {}", report.streams_deduplicated);
println!("unused removed: {}", report.unused_removed);
if let Some(fs) = &report.font_subset {
    println!("fonts subsetted: {} of {}", fs.fonts_subsetted, fs.fonts_processed);
    println!("font bytes saved: {}", fs.bytes_saved);
}
4

Save the compressed output

save_with lets you opt into overwrite. Without with_overwrite(true), the SDK refuses to clobber an existing file (RFC 0001 §1.2). Point it at a new filename to skip the flag.

rust
doc.save_with(
    "report_compressed.pdf",
    SaveOptions::new().with_overwrite(true),
)?;

Notes and tips

  • compress is idempotent — running it twice on the same document produces byte-identical output on the second pass (up to writer nondeterminism).
  • subset_fonts (bundled into compress) never increases font stream size — if a font can't be reduced, it's left untouched.
  • For signed documents, prefer CompressOptions::archival() so unused objects remain addressable from the incremental-update chain.
  • Compression runs entirely in-process — no external tools, no subprocess. Memory usage peaks around 2× the input during the pass.

Why PDFluent for this

Pure Rust

No JVM, no runtime, no DLL dependencies. Ships as a single native binary or WASM module.

Memory safe

Rust's ownership model prevents buffer overflows and use-after-free. No segfaults in PDF parsing.

Runs anywhere

Same code runs server-side, in Docker, on AWS Lambda, on Cloudflare Workers, or in the browser via WASM.

Frequently asked questions