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 /XRefwhen original uses it) /Infoand/IDinheritance fix (reads back through/Prevchain)write<W: Write + Seek>()serializes to any seekable writer
- Pending updates map (
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
/ModDatein 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 /Nappearance stream - Checkboxes: sets
/V+/ASto/Yesor/Off, renders checkmark/X - Read-only fields warned and skipped
- Parses field tree (recursive
4. Page Operations (pages.rs)
- rotate_page(index, degrees): Cumulative rotation (0/90/180/270)
- delete_pages(indices): Batch deletion, updates
/Kidsand/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>(+--listto 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,
PdfFilefor 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 infoand rendering - Robustness: ParseLimits enforced, field tree cycle detection, JPEG marker validation
Example Usage
use ;
use File;
// Edit metadata
let mut writer = new?;
writer.set_info?;
writer.write?;
// Stamp text
let mut writer = new?;
writer.stamp_page?;
writer.write?;
CLI Examples
# Set metadata
# Fill form
# Rotate and delete pages
# Stamp watermark
Implementation Notes
- Incremental updates preserve digital signatures (though edits may invalidate them — CLI warns)
- q/Q sandwich for stamps ensures unbalanced original streams don't corrupt output
- Inherited
/Resourceshandling: Dereferences and merges rather than replacing - xref stream auto-detection: Matches original format (table vs stream)
- Form appearance regeneration: Uses Standard 14 fonts (no embedding needed)
- Page tree recursion: Handles nested
/Pagesnodes 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