Save a PDF
You’ll save an edited PDF, either by appending your changes to the original bytes or by writing the whole file again. The choice matters when a file is signed, was damaged, or is password-protected, because each one limits how it can be saved.
This example, from Edit a PDF, asks for an incremental save, which keeps the original bytes at the start of the result, unchanged:
// Generate a document, then open its bytes, edit them and save incrementally. The edit changes// the Info dictionary in one transaction, and the incremental save keeps the generated bytes as// they are, with one revision appended.import { Builder, type BuilderOptions, Document, Font } from "@laminapdf/sdk";
/** * The generated file followed by the edit's revision. Needs `init({ text: true })`. * * @param options - can fix the file ID, the producer and the creation date. * @param editedAt - the modification date to record, as a PDF date string. */export function generateThenEdit(options: BuilderOptions = {}, editedAt?: string): Uint8Array<ArrayBuffer> { return edit(generate(options), editedAt);}
/** A one-page draft, saved (a new document is always saved as a full rewrite). */export function generate(options: BuilderOptions = {}): Uint8Array<ArrayBuffer> { using b = new Builder(options); const page = b.page("A4"); page.surface().text("Draft report", { font: Font.standard("Helvetica"), size: 20, at: [72, 96] }); page.finish(); b.metadata({ title: "Draft report" }); using doc = b.finish(); return doc.save();}
/** Opens `bytes`, edits the Info dictionary in one transaction (which `undo` could revert) and saves incrementally. */function edit(bytes: Uint8Array, editedAt?: string): Uint8Array<ArrayBuffer> { using doc = Document.open(bytes); doc.transact((tx) => tx.setInfo({ title: "Final report", author: "Ada Lovelace" }, editedAt)); return doc.save({ mode: "incremental" });}//! Generate a document, then open its bytes, edit them and save incrementally.//!//! The edit changes the Info dictionary with an op (undoable and serialisable), and the//! incremental save keeps the generated bytes as they are, with one revision appended.//!//! ```sh//! cargo run -p laminapdf --example generate_then_edit -- [out-dir] # writes <out-dir>/generate_then_edit.pdf//! ```
use std::error::Error;use std::path::PathBuf;
use laminapdf::{ BuildOptions, Builder, Document, InfoPatch, Metadata, Op, OpenOptions, PageSize, PdfDate, PointPt, Pt, SaveMode, SaveOptions, SetInfo, Standard14,};
fn main() -> Result<(), Box<dyn Error>> { let out = PathBuf::from(std::env::args().nth(1).unwrap_or_else(|| "target/examples-out".into())); std::fs::create_dir_all(&out)?; let bytes = build(BuildOptions::new(), None)?; std::fs::write(out.join("generate_then_edit.pdf"), bytes)?; Ok(())}
/// The generated file followed by the edit's revision. `edited_at` is recorded as the/// modification date; `options` can fix the file ID, the producer and the creation date.pub(crate) fn build(options: BuildOptions, edited_at: Option<PdfDate>) -> Result<Vec<u8>, Box<dyn Error>> { let generated = generate(options)?; edit(generated, edited_at)}
/// A one-page draft, saved (a new document is always saved as a full rewrite).pub(crate) fn generate(options: BuildOptions) -> Result<Vec<u8>, Box<dyn Error>> { let mut b = Builder::new(options); let helvetica = b.standard_font(Standard14::Helvetica)?; let mut page = b.page(PageSize::A4); page.surface().text( "Draft report", &helvetica, Pt::new(20.0), PointPt::from_points(72.0, 96.0), )?; page.finish()?; b.metadata(Metadata::default().with_title("Draft report")); Ok(b.finish()?.save(&SaveOptions::new())?.bytes)}
/// Opens `bytes`, edits the Info dictionary in one transaction (which `undo` could revert) and/// saves incrementally: `bytes` unchanged, then the appended revision.fn edit(bytes: Vec<u8>, edited_at: Option<PdfDate>) -> Result<Vec<u8>, Box<dyn Error>> { let mut doc = Document::open_bytes(bytes, OpenOptions::new())?; let patch = InfoPatch::new() .title(Some("Final report")) .author(Some("Ada Lovelace")); doc.apply(vec![Op::from(SetInfo::new(patch, edited_at))])?; let options = SaveOptions::new().with_mode(SaveMode::Incremental); Ok(doc.save(&options)?.bytes)}Choose a save mode
Section titled “Choose a save mode”Every save either appends a revision to the bytes you opened (an incremental save) or writes the whole file again (a full rewrite). You pick one with the mode option:
mode |
What it writes |
|---|---|
auto (the default) |
Incremental when that’s allowed, otherwise a full rewrite |
incremental |
The original bytes unchanged, then one appended revision |
fullRewrite |
The whole file again |
doc.save(options) returns the bytes, and doc.saveWithReport(options) also says what it wrote. doc.saveJob() does the same work in steps, so a page can save a large file without freezing. The same file with the same edits always saves to the same bytes.
When a file refuses a save
Section titled “When a file refuses a save”Every save makes a plan before it writes. The plan reads the file’s save constraint: what its signatures, permissions, repairs and encryption allow. Then it picks a mode or refuses:
- A signed file saves only incrementally, and only with edits its signatures allow. Anything else fails with
would-invalidate-signatures. PassinvalidateSignatures: trueto save anyway, which removes the broken signatures from the output. - A repaired file, one Lamina PDF had to fix as it opened, can’t safely take an appended revision. It needs a full rewrite, so asking for
incrementalfails withrequires-rewrite. - An edited encrypted file saves only as a full rewrite without its encryption, and only when you pass
removeEncryption: true. Otherwise every mode fails withencrypted-base. - An unchanged document always saves incrementally, which returns its bytes unchanged.