zpdf-writer 0.10.0

Incremental PDF writer for appending annotations and modifications
Documentation

zpdf-writer — PDF Editing via Incremental Updates

New crate providing PDF editing capabilities through incremental updates (append-only modifications that preserve the original file).

Features

1. Core Infrastructure (lib.rs)

  • IncrementalWriter: Main API for PDF editing
    • Pending updates map (ObjectId → PdfObject)
    • Automatic xref stream support (writes /Type /XRef when original uses it)
    • /Info and /ID inheritance fix (reads back through /Prev chain)
    • write<W: Write + Seek>() serializes to any seekable writer

2. Metadata Editing (metadata.rs)

  • InfoUpdate: Flexible metadata updates
    • Option<Option<String>> pattern: None = no change, Some(None) = delete, Some(Some(s)) = set
    • Supports: title, author, subject, keywords, creator, producer
    • Auto-adds /ModDate in UTC
  • PDF text string encoding: UTF-16BE for non-ASCII, literal for ASCII

3. Form Filling (forms.rs)

  • FormFiller: AcroForm field manipulation
    • Parses field tree (recursive /Kids, handles nesting)
    • set(name, value) updates field value + appearance
    • Text fields: sets /V + regenerates /AP /N appearance stream
    • Checkboxes: sets /V + /AS to /Yes or /Off, renders checkmark/X
    • Read-only fields warned and skipped

4. Page Operations (pages.rs)

  • rotate_page(index, degrees): Cumulative rotation (0/90/180/270)
  • delete_pages(indices): Batch deletion, updates /Kids and /Count
  • reorder_pages(new_order): Arbitrary page reordering

5. Content Stamping (stamp.rs)

  • StampItem: Text or image overlays
    • Text: font/size/color/position (Standard 14 fonts)
    • Image: JPEG (DCTDecode passthrough), RGB8 (FlateDecode), RGBA8 (with /SMask)
  • stamp_page: Wraps all items in Form XObject, appends via q/Q sandwich
    • [q <original> Q q /ZPDFStampN Do Q] isolates stamp in separate graphics state
    • Merges /Resources, auto-renames on collision
  • jpeg_dimensions: SOF0/1/2 parser for dimension extraction

6. CLI Commands (zpdf-cli)

  • zpdf fill <in.pdf> --set NAME=VALUE -o <out.pdf> (+ --list to enumerate fields)
  • zpdf pages <in.pdf> --rotate PAGES:DEG --delete LIST --order LIST -o <out.pdf>
  • zpdf set-meta <in.pdf> --title S --author S ... -o <out.pdf>
  • zpdf stamp <in.pdf> -p N --text STR --at X,Y --font F --size S --color R,G,B -o <out.pdf>
  • All support --password <pw>, enforce input ≠ output

Architecture

zpdf-writer
  ├─ lib.rs           IncrementalWriter, xref serialization, trailer inheritance
  ├─ metadata.rs      InfoUpdate, PDF text string encoding
  ├─ forms.rs         FormFiller, field tree parsing, appearance generation
  ├─ pages.rs         rotate/delete/reorder operations
  ├─ stamp.rs         StampItem, Form XObject wrapping, JPEG parsing
  └─ serialize.rs     Low-level PDF object/xref/trailer serialization

zpdf (facade)
  └─ Re-exports: IncrementalWriter, FormFiller, InfoUpdate, StampItem, StampImage

zpdf-cli
  └─ Commands: fill, pages, set-meta, stamp (+ shared utilities)

Dependencies

  • zpdf-document: Reuses generate_widget_appearance, standard_font_dict, escape_text
  • zpdf-parser/core: Object model, PdfFile for xref/trailer reading
  • flate2: FlateDecode compression (existing dependency)
  • Pure Rust, zero new C dependencies

Testing

  • Unit tests: UTF-16BE encoding, JPEG SOF parsing
  • Manual end-to-end: Verified metadata, rotation, stamping via zpdf info and rendering
  • Robustness: ParseLimits enforced, field tree cycle detection, JPEG marker validation

Example Usage

use zpdf::{IncrementalWriter, InfoUpdate, StampItem};
use std::fs::File;

// Edit metadata
let mut writer = IncrementalWriter::new(pdf_bytes)?;
writer.set_info(&InfoUpdate {
    title: Some(Some("My Document".into())),
    author: Some(Some("John Doe".into())),
    ..Default::default()
})?;
writer.write(&mut File::create("out.pdf")?)?;

// Stamp text
let mut writer = IncrementalWriter::new(pdf_bytes)?;
writer.stamp_page(0, &[StampItem::Text {
    text: "CONFIDENTIAL".into(),
    x: 200.0, y: 700.0,
    font: "Helvetica-Bold".into(),
    size: 36.0,
    color: (1.0, 0.0, 0.0),
}])?;
writer.write(&mut File::create("stamped.pdf")?)?;

CLI Examples

# Set metadata
zpdf set-meta input.pdf --title "Report" --author "Alice" -o output.pdf

# Fill form
zpdf fill form.pdf --set "Name=John Doe" --set "Agree=true" -o filled.pdf

# Rotate and delete pages
zpdf pages doc.pdf --rotate 0,2-5:90 --delete 10 -o edited.pdf

# Stamp watermark
zpdf stamp doc.pdf -p 1 --text "DRAFT" --at 300,400 --size 48 --color 1,0,0 -o draft.pdf

Implementation Notes

  1. Incremental updates preserve digital signatures (though edits may invalidate them — CLI warns)
  2. q/Q sandwich for stamps ensures unbalanced original streams don't corrupt output
  3. Inherited /Resources handling: Dereferences and merges rather than replacing
  4. xref stream auto-detection: Matches original format (table vs stream)
  5. Form appearance regeneration: Uses Standard 14 fonts (no embedding needed)
  6. Page tree recursion: Handles nested /Pages nodes correctly

Future Enhancements (not yet implemented)

  • Image stamp support (currently only text)
  • Multi-page stamping (currently one page at a time)
  • Form field creation (currently only fills existing fields)
  • Annotation editing (currently read-only)
  • Encryption/decryption on write