Skip to main content

flatten_xfa/
flatten_xfa.rs

1//! Flatten an XFA PDF to a static PDF content stream.
2//!
3//! # Usage
4//!
5//! ```text
6//! cargo run --example flatten_xfa -- <input.pdf> <output.pdf>
7//! ```
8//!
9//! The example:
10//! 1. Reads the input PDF bytes.
11//! 2. Detects whether the PDF is encrypted; exits with code 2 if so.
12//! 3. Flattens all XFA and AcroForm content to static content streams.
13//! 4. Writes the output PDF.
14//! 5. Prints the page count of the output.
15
16use std::error::Error;
17use std::path::PathBuf;
18
19fn main() -> Result<(), Box<dyn Error>> {
20    let args: Vec<String> = std::env::args().collect();
21    if args.len() < 3 {
22        eprintln!("usage: flatten_xfa <input.pdf> <output.pdf>");
23        std::process::exit(1);
24    }
25
26    let input = PathBuf::from(&args[1]);
27    let output = PathBuf::from(&args[2]);
28
29    let pdf_bytes =
30        std::fs::read(&input).map_err(|e| format!("failed to read {}: {e}", input.display()))?;
31
32    // Early check: encrypted PDFs cannot be flattened without a password.
33    if pdf_xfa::is_pdf_encrypted(&pdf_bytes) {
34        eprintln!(
35            "SKIP: {} is encrypted — supply a decrypted copy",
36            input.display()
37        );
38        std::process::exit(2);
39    }
40
41    let (flattened, metadata) = pdf_xfa::flatten_xfa_to_pdf_with_metadata(&pdf_bytes)
42        .map_err(|e| format!("flatten failed: {e}"))?;
43
44    std::fs::write(&output, &flattened)
45        .map_err(|e| format!("failed to write {}: {e}", output.display()))?;
46
47    // Count pages in the output using lopdf.
48    let page_count = count_pages(&flattened).unwrap_or(0);
49
50    println!(
51        "Flattened {} -> {} ({page_count} page{}, quality: {})",
52        input.display(),
53        output.display(),
54        if page_count == 1 { "" } else { "s" },
55        metadata.output_quality.as_str(),
56    );
57
58    Ok(())
59}
60
61/// Return the page count of a PDF byte slice.
62fn count_pages(pdf_bytes: &[u8]) -> Option<usize> {
63    let doc = lopdf::Document::load_mem(pdf_bytes).ok()?;
64    Some(doc.page_iter().count())
65}