Skip to main content

pdfboss_write/
update.rs

1//! Incremental updates to an existing file (ISO 32000-1 §7.5.6): the base
2//! bytes stay in place and an update section appends new and replaced
3//! objects plus a cross-reference section chained to the base's by `/Prev`,
4//! in the base's own cross-reference style.
5
6use std::io::Write;
7
8use flate2::write::ZlibEncoder;
9use flate2::Compression;
10use pdfboss_core::crypt::Sha256;
11use pdfboss_core::object::decode_text_string;
12use pdfboss_core::xref::{parse_section_at, startxref};
13use pdfboss_core::{Dict, Document, FastMap, Name, ObjRef, Object, Page, Stream, XrefKind};
14
15use crate::error::{Error, Result};
16use crate::importer::{rect_array, Importer};
17use crate::pdf::{text_string, Date, Metadata};
18use crate::ser::{serialize_dict, serialize_object};
19use crate::writer::{WriteOptions, Writer, XrefStyle};
20
21/// The first name tried for the resource every page draws the overlay
22/// form under. [`free_form_name`] falls back to `PdfbossWatermark2`,
23/// `PdfbossWatermark3`, ... when this one is already taken.
24const FORM_NAME: &str = "PdfbossWatermark";
25
26/// The first name in the series `PdfbossWatermark`, `PdfbossWatermark2`,
27/// ... whose `/XObject` entry is free in every marked page of `pages`.
28/// Overlaying a file that already carries a mark under an earlier name in
29/// the series draws its own form under the next free one instead of
30/// replacing the earlier entry and leaving its `Do` operators pointing at
31/// the new form. `pages` is fetched once by the caller and reused for the
32/// mark loop that follows: [`Document::page`] materializes a page's
33/// effective resources on every call, so probing by re-fetching would
34/// double that cost across the whole document.
35fn free_form_name(base: &Document, pages: &[Page]) -> Result<String> {
36    let mut candidate = FORM_NAME.to_string();
37    let mut next = 2;
38    while xobject_name_taken(base, pages, &candidate)? {
39        candidate = format!("{FORM_NAME}{next}");
40        next += 1;
41    }
42    Ok(candidate)
43}
44
45/// Whether any page in `pages` that is marked (has an [`ObjRef`] of its
46/// own) already carries an `/XObject` resource named `candidate`,
47/// resolving each page's effective resources the same way
48/// [`marked_page_dict`] and [`watermark_placed`] do before adding their
49/// own entry. A page inlined into `/Kids`, having no object of its own,
50/// is never marked, so it is skipped: only a marked page's `/XObject`
51/// dictionary can ever collide with the name this picks.
52fn xobject_name_taken(base: &Document, pages: &[Page], candidate: &str) -> Result<bool> {
53    for page in pages {
54        if page.object_ref().is_none() {
55            continue;
56        }
57        let Some(existing) = page.resources.get("XObject") else {
58            continue;
59        };
60        let existing = base.resolve(existing).map_err(core_error)?;
61        let Some(dict) = existing.as_dict() else {
62            continue;
63        };
64        if dict.get(candidate).is_some() {
65            return Ok(true);
66        }
67    }
68    Ok(false)
69}
70
71/// Every page of `base`, in order, fetched once: shared by the name probe
72/// and the mark loop that follows it, so [`Document::page`] materializes
73/// each page's effective resources only once per watermark construction
74/// rather than once for the probe and again for marking.
75fn fetch_pages(base: &Document) -> Result<Vec<Page>> {
76    (0..base.page_count())
77        .map(|index| base.page(index).map_err(core_error))
78        .collect()
79}
80
81/// The content wrappers for one marked page: what precedes the page's own
82/// content and what follows it. Drawing over paints the form after the
83/// content; drawing under paints it first, so the content covers it.
84fn wrapper_streams(form_name: &str, under: bool) -> (Vec<u8>, Vec<u8>) {
85    if under {
86        return (
87            format!("q /{form_name} Do Q\nq\n").into_bytes(),
88            b"Q\n".to_vec(),
89        );
90    }
91    (
92        b"q\n".to_vec(),
93        format!("Q\nq /{form_name} Do Q\n").into_bytes(),
94    )
95}
96
97/// Shared by [`watermark_with`] and [`watermark_under_with`]: writes a fresh
98/// file through the [`Writer`] under `options` instead of appending an
99/// update, wrapping each page's content per [`wrapper_streams`].
100fn watermark_rewrite_placed(
101    base: &Document,
102    overlay: &Document,
103    options: WriteOptions,
104    under: bool,
105) -> Result<Vec<u8>> {
106    let pages = fetch_pages(base)?;
107    let form_name = free_form_name(base, &pages)?;
108    let mut writer = Writer::new(options);
109    let (prefix_bytes, suffix_bytes) = wrapper_streams(&form_name, under);
110    let prefix = writer.put_stream_raw(Dict::new(), prefix_bytes);
111    let suffix = writer.put_stream_raw(Dict::new(), suffix_bytes);
112    let form = overlay_form(&mut writer, overlay)?;
113
114    let trailer = &base.xref().trailer;
115    let root = trailer.get_ref("Root").ok_or(Error::MissingRoot)?;
116    let mut importer = Importer::new(&mut writer, base)?;
117    let new_root = importer.reference(root);
118    let new_info = trailer.get_ref("Info").map(|info| importer.reference(info));
119    for page in &pages {
120        let Some(page_ref) = page.object_ref() else {
121            continue;
122        };
123        let dict = marked_page_dict(&mut importer, base, page, form, prefix, suffix, &form_name)?;
124        importer.substitute(page_ref, dict);
125    }
126    importer.finish()?;
127    if let Some(new_info) = new_info {
128        writer.set_info(new_info);
129    }
130    writer.finish(new_root)
131}
132
133/// Like [`watermark`], but writes a fresh file through the [`Writer`] under
134/// `options` instead of appending an update: every object the base's
135/// catalog reaches is copied over, uncompressed streams are compressed when
136/// `options.compress` is set, and unreachable objects and earlier sections
137/// are left behind, so the result is usually smaller than the base. Both
138/// `base` and `overlay` are refused when locked, through
139/// [`crate::importer::Importer::new`]; a password-opened encrypted `base`
140/// or `overlay` copies its plaintext content across like any unencrypted
141/// source.
142///
143/// Placement is absolute and unscaled, and an unbalanced graphics state in
144/// a page's own content can clip or restyle the overlay, for the same
145/// reasons documented on [`watermark`].
146pub fn watermark_with(
147    base: &Document,
148    overlay: &Document,
149    options: WriteOptions,
150) -> Result<Vec<u8>> {
151    watermark_rewrite_placed(base, overlay, options, false)
152}
153
154/// Like [`watermark_with`], but draws the overlay beneath each page's
155/// content: the form paints first and the page's own content paints over
156/// it, so opaque content covers the overlay instead of the other way
157/// round.
158///
159/// Placement is absolute and unscaled, for the same reason documented on
160/// [`watermark`]. Painting the form first means no page state can reach
161/// it, so the unbalanced-graphics-state risk documented on [`watermark`]
162/// does not apply to this placement.
163pub fn watermark_under_with(
164    base: &Document,
165    overlay: &Document,
166    options: WriteOptions,
167) -> Result<Vec<u8>> {
168    watermark_rewrite_placed(base, overlay, options, true)
169}
170
171/// The marked dictionary for `page`, already fetched from `base` by the
172/// caller: its own dictionary translated into the target with `/Type
173/// /Page` guaranteed, its effective resources gaining the overlay form
174/// under `form_name`, and its content wrapped in `prefix` and `suffix`.
175fn marked_page_dict(
176    importer: &mut Importer,
177    base: &Document,
178    page: &Page,
179    form: ObjRef,
180    prefix: ObjRef,
181    suffix: ObjRef,
182    form_name: &str,
183) -> Result<Object> {
184    let mut dict = importer.copy_dict(page.dict())?;
185    let mut resources = importer.copy_dict(&page.resources)?;
186    let mut xobjects = match page.resources.get("XObject") {
187        Some(existing) => {
188            let existing = base.resolve(existing).map_err(core_error)?;
189            match existing.as_dict() {
190                Some(d) => importer.copy_dict(d)?,
191                None => Dict::new(),
192            }
193        }
194        None => Dict::new(),
195    };
196    xobjects.insert(name(form_name), Object::Ref(form));
197    resources.insert(name("XObject"), Object::Dict(xobjects));
198    dict.insert(name("Resources"), Object::Dict(resources));
199    dict.insert(name("Type"), Object::Name(name("Page")));
200    let mut contents = vec![Object::Ref(prefix)];
201    match page.dict().get("Contents") {
202        Some(Object::Array(items)) => {
203            for item in items {
204                contents.push(importer.copy(item)?);
205            }
206        }
207        Some(Object::Ref(r)) => match base.get(*r).map_err(core_error)? {
208            Object::Array(items) => {
209                for item in &items {
210                    contents.push(importer.copy(item)?);
211                }
212            }
213            _ => contents.push(Object::Ref(importer.reference(*r))),
214        },
215        _ => {}
216    }
217    contents.push(Object::Ref(suffix));
218    dict.insert(name("Contents"), Object::Array(contents));
219    Ok(Object::Dict(dict))
220}
221
222/// The overlay's first page as a form XObject, filled directly into
223/// `writer`: its media box as the bounding box, its decoded content
224/// deflated, its resources imported from `overlay`.
225fn overlay_form(writer: &mut Writer, overlay: &Document) -> Result<ObjRef> {
226    let page = overlay.page(0).map_err(core_error)?;
227    let content = page.content(overlay).map_err(core_error)?;
228    let resources = {
229        let mut importer = Importer::new(writer, overlay)?;
230        let resources = importer.copy_dict(&page.resources)?;
231        importer.finish()?;
232        resources
233    };
234    let mut dict = Dict::new();
235    dict.insert(name("Type"), Object::Name(name("XObject")));
236    dict.insert(name("Subtype"), Object::Name(name("Form")));
237    dict.insert(name("FormType"), Object::Int(1));
238    dict.insert(name("BBox"), rect_array(page.media_box));
239    dict.insert(name("Resources"), Object::Dict(resources));
240    dict.insert(name("Filter"), Object::Name(name("FlateDecode")));
241    let form = writer.reserve();
242    writer.fill(
243        form,
244        Object::Stream(Stream {
245            dict,
246            data: deflate(&content),
247        }),
248    )?;
249    Ok(form)
250}
251
252/// Shared by [`watermark`] and [`watermark_under`]: draws the first page of
253/// `overlay` over (or, when `under` is set, beneath) every page of `base`,
254/// returning `base`'s bytes followed by an incremental update: the overlay
255/// page as one form XObject (its resources copied into the base's object
256/// space), and each page's dictionary rewritten with that form in its
257/// resources, under the first free name [`free_form_name`] finds, and its
258/// content wrapped per [`wrapper_streams`]. Pages inlined directly into
259/// `/Kids`, having no object of their own, are left as they are. An
260/// encrypted `base` is refused: its new strings and streams would need
261/// encrypting too. An encrypted `overlay` is refused as well: its
262/// decrypted content would otherwise copy across into the plain update
263/// section.
264fn watermark_placed(base: &Document, overlay: &Document, under: bool) -> Result<Vec<u8>> {
265    let pages = fetch_pages(base)?;
266    let form_name = free_form_name(base, &pages)?;
267    let mut update = Update::new(base)?;
268    let form = update.overlay.import_form(overlay)?;
269    let (prefix_bytes, suffix_bytes) = wrapper_streams(&form_name, under);
270    let prefix = update
271        .overlay
272        .put(Object::Stream(plain_stream(prefix_bytes)));
273    let suffix = update
274        .overlay
275        .put(Object::Stream(plain_stream(suffix_bytes)));
276    for page in &pages {
277        let Some(page_ref) = page.object_ref() else {
278            continue;
279        };
280        let mut dict = page.dict().clone();
281        let mut resources = page.resources.clone();
282        let mut xobjects = match resources.get("XObject") {
283            Some(existing) => base
284                .resolve(existing)
285                .map_err(core_error)?
286                .as_dict()
287                .cloned()
288                .unwrap_or_default(),
289            None => Dict::new(),
290        };
291        xobjects.insert(name(&form_name), Object::Ref(form));
292        resources.insert(name("XObject"), Object::Dict(xobjects));
293        dict.insert(name("Resources"), Object::Dict(resources));
294        let mut contents = vec![Object::Ref(prefix)];
295        match dict.get("Contents").cloned() {
296            Some(Object::Array(items)) => contents.extend(items),
297            Some(Object::Ref(r)) => match base.get(r).map_err(core_error)? {
298                Object::Array(items) => contents.extend(items),
299                _ => contents.push(Object::Ref(r)),
300            },
301            _ => {}
302        }
303        contents.push(Object::Ref(suffix));
304        dict.insert(name("Contents"), Object::Array(contents));
305        update.set(page_ref, Object::Dict(dict));
306    }
307    update.bytes()
308}
309
310/// Draws the first page of `overlay` over every page of `base`, returning
311/// `base`'s bytes followed by an incremental update: the overlay page as
312/// one form XObject (its resources copied into the base's object space),
313/// and each page's dictionary rewritten with that form in its resources
314/// and its content wrapped in `q … Q` before the form is drawn. Pages
315/// inlined directly into `/Kids`, having no object of their own, are left
316/// as they are. An encrypted `base` is refused: its new strings and
317/// streams would need encrypting too. An encrypted `overlay` is refused
318/// as well: its decrypted content would otherwise copy across into the
319/// plain update section.
320///
321/// Placement is absolute and unscaled: the overlay page draws at its own
322/// coordinates on every page of `base`, with no scaling to that page's
323/// size, and the overlay page's `/Rotate` and `/CropBox` are not applied.
324/// Because the form paints last, a page whose own content leaves
325/// unbalanced graphics state (an unclosed clip or transform) can clip or
326/// restyle the overlay, since the wrapper's one closing `Q` cannot undo
327/// it.
328pub fn watermark(base: &Document, overlay: &Document) -> Result<Vec<u8>> {
329    watermark_placed(base, overlay, false)
330}
331
332/// Like [`watermark`], but draws the overlay beneath each page's content:
333/// the form paints first and the page's own content paints over it, so
334/// opaque content covers the overlay instead of the other way round.
335///
336/// The same absolute, unscaled placement documented on [`watermark`]
337/// applies here too. Painting the form first, before any of the page's
338/// own operators run, means no page state can reach it, so the
339/// unbalanced-graphics-state risk documented on [`watermark`] does not
340/// apply to this placement.
341pub fn watermark_under(base: &Document, overlay: &Document) -> Result<Vec<u8>> {
342    watermark_placed(base, overlay, true)
343}
344
345/// Stages `by` degrees of rotation, clockwise, on each of `pages` (0-based
346/// indices) into `update`: a clone of the page's own leaf dictionary, its
347/// `/Rotate` set to its current effective rotation plus `by`, normalized
348/// with `rem_euclid(360)`. The staged dictionary is untranslated: it
349/// keeps its own `/Parent`, so it stays exactly where it was in the page
350/// tree. A page with no object of its own (inlined directly into
351/// `/Kids`) cannot be staged this way: refused, naming its 1-based page
352/// number. `by` must be a multiple of 90; anything else is refused before
353/// any page is touched.
354pub fn rotate_pages(update: &mut Update, pages: &[usize], by: i32) -> Result<()> {
355    if by % 90 != 0 {
356        return Err(Error::Other(
357            "rotation must be a multiple of 90 degrees".to_string(),
358        ));
359    }
360    for &index in pages {
361        let page = update.doc.page(index).map_err(core_error)?;
362        let Some(page_ref) = page.object_ref() else {
363            return Err(Error::Other(format!(
364                "page {} is inlined into /Kids and cannot be edited in place",
365                index + 1
366            )));
367        };
368        let mut dict = page.dict().clone();
369        let rotate = (page.rotate + by).rem_euclid(360);
370        dict.insert(name("Rotate"), Object::Int(i64::from(rotate)));
371        update.set(page_ref, Object::Dict(dict));
372    }
373    Ok(())
374}
375
376/// The facts about a base document an update needs, read once from its
377/// trailer and its own newest cross-reference section: refuses an
378/// encrypted base or one missing `/Root` or a `startxref` to chain from.
379#[derive(Debug, Clone)]
380pub struct OverlayBase {
381    /// Byte offset of the base's own newest cross-reference section, named
382    /// as the appended section's `/Prev`.
383    pub prev: u64,
384    /// Style of that newest section, read from the section itself rather
385    /// than the merged trailer (a hybrid base's merged trailer carries
386    /// `/Type /XRef` inherited from its `/XRefStm`, even though its newest
387    /// section, per `startxref`, is the classic table).
388    pub kind: XrefStyle,
389    /// The next free object number: the base's declared `/Size`, raised to
390    /// one past its highest addressed object number.
391    pub size: u32,
392    /// The base's catalog.
393    pub root: ObjRef,
394    /// The base's document information dictionary, when present.
395    pub info: Option<ObjRef>,
396    /// The base trailer's `/ID` array, cloned.
397    pub id: Option<Object>,
398}
399
400impl OverlayBase {
401    /// Reads `doc`'s trailer and newest cross-reference section: the
402    /// section's offset and kind come from `doc.xref().newest_section()`,
403    /// already recorded while core loaded the file, falling back to
404    /// re-deriving them from `startxref` and `parse_section_at` when the
405    /// document has none (a recovery-scan base refuses with
406    /// [`Error::MissingStartxref`] on this fallback path). Refuses any
407    /// encrypted `doc` outright, wider than
408    /// [`crate::importer::Importer::new`]'s own locked-only refusal:
409    /// appending onto an already-encrypted base is a feature this crate
410    /// does not yet implement, so every encrypted base is refused here for
411    /// now, password-opened or not.
412    pub fn from_document(doc: &Document) -> Result<OverlayBase> {
413        if doc.is_encrypted() {
414            return Err(Error::EncryptedBase);
415        }
416        let trailer = &doc.xref().trailer;
417        let root = trailer.get_ref("Root").ok_or(Error::MissingRoot)?;
418        let (prev, kind) = match doc.xref().newest_section() {
419            Some(section) => (section.offset, xref_style(section.kind)),
420            None => {
421                let offset = startxref(doc.bytes()).ok_or(Error::MissingStartxref)?;
422                let kind = xref_style(
423                    parse_section_at(doc.bytes(), offset)
424                        .map_err(core_error)?
425                        .kind,
426                );
427                (offset as u64, kind)
428            }
429        };
430        let highest = doc.xref().iter().map(|(num, _)| num).max().unwrap_or(0);
431        let declared = trailer.get_int("Size").unwrap_or(0).max(0) as u32;
432        Ok(OverlayBase {
433            prev,
434            kind,
435            size: declared.max(highest + 1),
436            root,
437            info: trailer.get_ref("Info"),
438            id: trailer.get("ID").cloned(),
439        })
440    }
441}
442
443/// The [`XrefStyle`] an appended section should copy for a base whose
444/// newest section is `kind`.
445fn xref_style(kind: XrefKind) -> XrefStyle {
446    match kind {
447        XrefKind::Table => XrefStyle::Table,
448        XrefKind::Stream => XrefStyle::Stream,
449    }
450}
451
452/// One recorded change against an object number: a new or replacement body
453/// from [`Overlay::set`], or a free marker from [`Overlay::remove`].
454#[derive(Debug, Clone)]
455enum Change {
456    Set(Object),
457    Free,
458}
459
460/// An update section under construction over an [`OverlayBase`]: which
461/// objects it holds, new ones numbered from the base's first free number.
462#[derive(Debug, Clone)]
463pub struct Overlay {
464    base: OverlayBase,
465    next: u32,
466    objects: Vec<(ObjRef, Change)>,
467    imported: FastMap<ObjRef, ObjRef>,
468    info: Option<ObjRef>,
469}
470
471impl Overlay {
472    /// An empty update section over `base`, numbering new objects from its
473    /// first free number.
474    pub fn new(base: OverlayBase) -> Overlay {
475        let next = base.size;
476        Overlay {
477            base,
478            next,
479            objects: Vec::new(),
480            imported: FastMap::default(),
481            info: None,
482        }
483    }
484
485    /// Sets an object under its own number, whether new or a replacement
486    /// of one already in the base. Raises the next free number past `r`
487    /// when `r` was not already reserved, so a later `reserve`/`put` never
488    /// collides with a caller-chosen number. A no-op for object number 0:
489    /// it is already the free list's own permanent head, represented by
490    /// this section's synthetic entry-0 row whenever any other object is
491    /// freed, and a `set` row for it would collide with that row. Symmetric
492    /// with [`Overlay::remove`]'s guard.
493    pub fn set(&mut self, r: ObjRef, obj: Object) {
494        if r.num == 0 {
495            return;
496        }
497        self.next = self.next.max(r.num.saturating_add(1));
498        self.objects.push((r, Change::Set(obj)));
499    }
500
501    /// Marks `r` free: the appended section's cross-reference data chains
502    /// it into entry 0's free list, in whichever style the base uses. Its
503    /// generation for reuse is `r.gen` advanced by one (saturating at
504    /// 65535, the field's own limit), per the classic table's convention
505    /// for a deleted entry's row. A no-op for object number 0: it is
506    /// already the free list's own permanent head, represented by this
507    /// section's synthetic entry-0 row whenever any other object is freed.
508    pub fn remove(&mut self, r: ObjRef) {
509        if r.num == 0 {
510            return;
511        }
512        self.next = self.next.max(r.num.saturating_add(1));
513        let gen = r.gen.saturating_add(1);
514        self.objects
515            .push((ObjRef { num: r.num, gen }, Change::Free));
516    }
517
518    /// Allocates the next free object number without storing anything
519    /// under it yet.
520    pub fn reserve(&mut self) -> ObjRef {
521        let r = ObjRef {
522            num: self.next,
523            gen: 0,
524        };
525        self.next += 1;
526        r
527    }
528
529    /// Adds a new object under the next free number.
530    pub fn put(&mut self, obj: Object) -> ObjRef {
531        let r = self.reserve();
532        self.set(r, obj);
533        r
534    }
535
536    /// Registers the document information dictionary for the appended
537    /// section's trailer, overriding the base's own.
538    pub fn set_info(&mut self, r: ObjRef) {
539        self.info = Some(r);
540    }
541
542    /// Whether nothing has been set or removed yet.
543    pub fn is_empty(&self) -> bool {
544        self.objects.is_empty()
545    }
546
547    /// The appended section alone: every set object at `start` plus its
548    /// position within this section, then a cross-reference section in the
549    /// base's style naming the base's section as `/Prev`. Refused when no
550    /// object has been set. A number recorded more than once (repeated
551    /// `set`, or `set` and `remove` on the same reference) keeps only its
552    /// last-recorded change, so the appended cross-reference data never
553    /// carries two rows for one number.
554    pub fn section(&self, start: u64) -> Result<Vec<u8>> {
555        if self.is_empty() {
556            return Err(Error::EmptyUpdate);
557        }
558        let mut last: FastMap<u32, usize> = FastMap::default();
559        for (index, (r, _)) in self.objects.iter().enumerate() {
560            last.insert(r.num, index);
561        }
562        let mut winners: Vec<usize> = last.into_values().collect();
563        winners.sort_by_key(|&index| self.objects[index].0.num);
564        let mut out = Vec::new();
565        let mut rows: Vec<Row> = Vec::with_capacity(winners.len() + 1);
566        let mut freed: Vec<ObjRef> = Vec::new();
567        for index in winners {
568            let (r, change) = &self.objects[index];
569            match change {
570                Change::Set(obj) => {
571                    rows.push(Row::InFile(*r, start as usize + out.len()));
572                    write_indirect(&mut out, *r, obj)?;
573                }
574                Change::Free => freed.push(*r),
575            }
576        }
577        if !freed.is_empty() {
578            freed.sort_by_key(|r| r.num);
579            let head = freed.first().map_or(0, |r| r.num);
580            rows.push(Row::Free {
581                num: 0,
582                gen: 65535,
583                next: head,
584            });
585            for (index, r) in freed.iter().enumerate() {
586                let next = freed.get(index + 1).map_or(0, |n| n.num);
587                rows.push(Row::Free {
588                    num: r.num,
589                    gen: r.gen,
590                    next,
591                });
592            }
593        }
594        rows.sort_by_key(Row::num);
595
596        let mut trailer = Dict::new();
597        trailer.insert(name("Root"), Object::Ref(self.base.root));
598        if let Some(info) = self.info.or(self.base.info) {
599            trailer.insert(name("Info"), Object::Ref(info));
600        }
601        if let Some(id) = rotated_id(&self.base, &out, &freed) {
602            trailer.insert(name("ID"), id);
603        }
604        trailer.insert(name("Prev"), Object::Int(self.base.prev as i64));
605        match self.base.kind {
606            XrefStyle::Stream => finish_stream(&mut out, start, rows, trailer, self.next)?,
607            XrefStyle::Table => finish_table(&mut out, start, &rows, trailer, self.next)?,
608        }
609        Ok(out)
610    }
611
612    /// The overlay's first page as a form XObject in the base's object
613    /// space: its media box as the form's bounding box, its decoded content
614    /// as the form's stream, and its resource graph deep-copied and
615    /// renumbered. Refuses any encrypted `overlay` outright, wider than
616    /// [`crate::importer::Importer::new`]'s own locked-only refusal:
617    /// appending onto an encrypted base is a feature this crate does not
618    /// yet implement, so every encrypted overlay is refused here for now,
619    /// password-opened or not.
620    pub(crate) fn import_form(&mut self, overlay: &Document) -> Result<ObjRef> {
621        if overlay.is_encrypted() {
622            return Err(Error::EncryptedBase);
623        }
624        let page = overlay.page(0).map_err(core_error)?;
625        let content = page.content(overlay).map_err(core_error)?;
626        let resources = self.import_object(overlay, &Object::Dict(page.resources.clone()))?;
627        let mut dict = Dict::new();
628        dict.insert(name("Type"), Object::Name(name("XObject")));
629        dict.insert(name("Subtype"), Object::Name(name("Form")));
630        dict.insert(name("FormType"), Object::Int(1));
631        dict.insert(name("BBox"), rect_array(page.media_box));
632        dict.insert(name("Resources"), resources);
633        dict.insert(name("Filter"), Object::Name(name("FlateDecode")));
634        Ok(self.put(Object::Stream(Stream {
635            dict,
636            data: deflate(&content),
637        })))
638    }
639
640    /// A deep copy of `obj` from `source` into the update: every reference
641    /// it reaches becomes a new object here, each source object copied once
642    /// however many times it is referenced. Streams keep their encoded
643    /// bytes and filters; their `/Length` is rewritten on emission.
644    pub(crate) fn import_object(&mut self, source: &Document, obj: &Object) -> Result<Object> {
645        Ok(match obj {
646            Object::Ref(r) => {
647                if let Some(copied) = self.imported.get(r) {
648                    return Ok(Object::Ref(*copied));
649                }
650                let copied = self.reserve();
651                self.imported.insert(*r, copied);
652                let body = source.get(*r).map_err(core_error)?;
653                let body = self.import_object(source, &body)?;
654                self.objects.push((copied, Change::Set(body)));
655                Object::Ref(copied)
656            }
657            Object::Dict(d) => Object::Dict(self.import_dict(source, d)?),
658            Object::Array(items) => Object::Array(
659                items
660                    .iter()
661                    .map(|item| self.import_object(source, item))
662                    .collect::<Result<Vec<Object>>>()?,
663            ),
664            Object::Stream(s) => {
665                let mut dict = s.dict.clone();
666                dict.remove("Length");
667                Object::Stream(Stream {
668                    dict: self.import_dict(source, &dict)?,
669                    data: s.data.clone(),
670                })
671            }
672            other => other.clone(),
673        })
674    }
675
676    pub(crate) fn import_dict(&mut self, source: &Document, dict: &Dict) -> Result<Dict> {
677        let mut out = Dict::new();
678        for (key, value) in dict.iter() {
679            out.insert(key.clone(), self.import_object(source, value)?);
680        }
681        Ok(out)
682    }
683}
684
685/// Merges `meta` into `existing_info`'s dictionary (or a fresh one, staged
686/// under a newly reserved number, when `existing_info` is `None`): a
687/// `Some` field overwrites its key (`Some(String::new())` writes an empty
688/// string), a `None` field leaves whatever key was already there. The
689/// merged dictionary is staged into `overlay` via `set` and `set_info`.
690///
691/// When `xmp_ref` is `Some`, the merged dictionary is read back into a
692/// [`Metadata`] (text fields via `decode_text_string`, dates via
693/// [`Date::parse_pdf`], an unparseable date simply dropping out) and
694/// staged under `xmp_ref` as a fresh, unfiltered `/Type /Metadata /Subtype
695/// /XML` stream of the crate's XMP packet over that merged value: any XMP
696/// property outside those eight fields is not carried into the new
697/// packet, though the original packet's bytes stay in the base.
698///
699/// Shared by [`Update::set_metadata`] and its asynchronous counterpart.
700pub fn set_metadata_with(
701    overlay: &mut Overlay,
702    existing_info: Option<(ObjRef, Dict)>,
703    xmp_ref: Option<ObjRef>,
704    meta: Metadata,
705) -> Result<()> {
706    let (target, existing_dict) = match existing_info {
707        Some((r, dict)) => (r, Some(dict)),
708        None => (overlay.reserve(), None),
709    };
710    let (dict, merged) = merge_metadata(existing_dict, &meta);
711    overlay.set(target, Object::Dict(dict));
712    overlay.set_info(target);
713    let Some(xmp_ref) = xmp_ref else {
714        return Ok(());
715    };
716    overlay.set(xmp_ref, xmp_metadata_stream(&merged));
717    Ok(())
718}
719
720/// `existing` (`None` starts from an empty dictionary) with every `Some`
721/// field of `meta` applied: a `Some` field overwrites its key
722/// (`Some(String::new())` writes an empty string), a `None` field leaves
723/// whatever key was already there. Also returns that merged dictionary read
724/// back into a [`Metadata`] (text fields via `decode_text_string`, dates via
725/// [`Date::parse_pdf`], an unparseable date simply dropping out), for
726/// [`xmp_metadata_stream`]: any XMP property outside those eight fields is
727/// not carried into a rebuilt packet.
728///
729/// Shared by [`set_metadata_with`] and [`crate::assemble::rewrite_with_metadata`].
730pub(crate) fn merge_metadata(existing: Option<Dict>, meta: &Metadata) -> (Dict, Metadata) {
731    let mut dict = existing.unwrap_or_default();
732    apply_metadata_fields(&mut dict, meta);
733    let merged = metadata_from_info(&dict);
734    (dict, merged)
735}
736
737/// A fresh, unfiltered `/Type /Metadata /Subtype /XML` stream over `meta`'s
738/// XMP packet, ready to stage into an [`Overlay`] or substitute into an
739/// import.
740pub(crate) fn xmp_metadata_stream(meta: &Metadata) -> Object {
741    let mut xmp_dict = Dict::new();
742    xmp_dict.insert(name("Type"), Object::Name(name("Metadata")));
743    xmp_dict.insert(name("Subtype"), Object::Name(name("XML")));
744    Object::Stream(Stream {
745        dict: xmp_dict,
746        data: crate::xmp::packet(meta),
747    })
748}
749
750/// `dict` with every value resolved against `doc`: an indirect value such
751/// as `/Title 12 0 R` becomes the string object it points to, so a field
752/// [`merge_metadata`] keeps (a `None` field in the merge) still reads back
753/// as text rather than silently vanishing from a rebuilt XMP packet. A
754/// value whose reference chain fails to resolve (an unreadable target, or
755/// a cycle) is kept as given.
756pub(crate) fn resolve_dict(doc: &Document, dict: &Dict) -> Dict {
757    let mut out = Dict::new();
758    for (key, value) in dict.iter() {
759        let resolved = doc.resolve(value).unwrap_or_else(|_| value.clone());
760        out.insert(key.clone(), resolved);
761    }
762    out
763}
764
765/// `doc`'s catalog's `/Metadata` entry, when it is an indirect reference.
766/// `None` for a catalog with no `/Metadata`, or one that reads as a direct
767/// stream rather than a reference.
768pub(crate) fn catalog_metadata_ref(doc: &Document, root: ObjRef) -> Option<ObjRef> {
769    let catalog = doc.get(root).ok()?;
770    match catalog.as_dict()?.get("Metadata")? {
771        Object::Ref(r) => Some(*r),
772        _ => None,
773    }
774}
775
776/// Writes every `Some` field of `meta` into `dict` under its `/Info` key;
777/// a `None` field is left untouched.
778fn apply_metadata_fields(dict: &mut Dict, meta: &Metadata) {
779    let texts = [
780        ("Title", &meta.title),
781        ("Author", &meta.author),
782        ("Subject", &meta.subject),
783        ("Keywords", &meta.keywords),
784        ("Creator", &meta.creator),
785        ("Producer", &meta.producer),
786    ];
787    for (key, value) in texts {
788        if let Some(value) = value {
789            dict.insert(name(key), text_string(value));
790        }
791    }
792    let dates = [
793        ("CreationDate", meta.creation_date),
794        ("ModDate", meta.modification_date),
795    ];
796    for (key, value) in dates {
797        if let Some(date) = value {
798            dict.insert(name(key), Object::String(date.to_pdf_string().into_bytes()));
799        }
800    }
801}
802
803/// Reads an `/Info` dictionary back into a [`Metadata`]: text fields via
804/// `decode_text_string`, dates via [`Date::parse_pdf`]. A missing or
805/// unparseable field is simply `None`.
806fn metadata_from_info(dict: &Dict) -> Metadata {
807    Metadata {
808        title: info_text(dict, "Title"),
809        author: info_text(dict, "Author"),
810        subject: info_text(dict, "Subject"),
811        keywords: info_text(dict, "Keywords"),
812        creator: info_text(dict, "Creator"),
813        producer: info_text(dict, "Producer"),
814        creation_date: info_date(dict, "CreationDate"),
815        modification_date: info_date(dict, "ModDate"),
816    }
817}
818
819/// `dict[key]` decoded as a text string, when present and a string.
820fn info_text(dict: &Dict, key: &str) -> Option<String> {
821    Some(decode_text_string(dict.get(key)?.as_str_bytes()?))
822}
823
824/// `dict[key]` decoded and parsed as a PDF date, when present, a string,
825/// and a valid date.
826fn info_date(dict: &Dict, key: &str) -> Option<Date> {
827    Date::parse_pdf(&decode_text_string(dict.get(key)?.as_str_bytes()?))
828}
829
830/// The base's length as an update's write position, plus whether a pad
831/// newline must be inserted first: an object header may not follow
832/// directly after `%%EOF` unless the base already ends on a line
833/// terminator (`\n` or `\r`).
834pub fn start_offset(base: &[u8]) -> (u64, bool) {
835    let pad = !matches!(base.last(), Some(b'\n') | Some(b'\r'));
836    (base.len() as u64 + u64::from(pad), pad)
837}
838
839/// A base document plus the update section being built over it.
840pub struct Update<'a> {
841    doc: &'a Document,
842    overlay: Overlay,
843}
844
845impl<'a> Update<'a> {
846    /// Opens `doc` for an update: refuses an encrypted base or one missing
847    /// `/Root` or a `startxref` to chain the appended section's `/Prev` to.
848    pub fn new(doc: &'a Document) -> Result<Update<'a>> {
849        let base = OverlayBase::from_document(doc)?;
850        Ok(Update {
851            doc,
852            overlay: Overlay::new(base),
853        })
854    }
855
856    /// Sets an object under its own number, whether new or a replacement
857    /// of one already in the base.
858    pub fn set(&mut self, r: ObjRef, obj: Object) {
859        self.overlay.set(r, obj);
860    }
861
862    /// Marks `r` free in the appended section's cross-reference data.
863    pub fn remove(&mut self, r: ObjRef) {
864        self.overlay.remove(r);
865    }
866
867    /// Allocates the next free object number without storing anything
868    /// under it yet.
869    pub fn reserve(&mut self) -> ObjRef {
870        self.overlay.reserve()
871    }
872
873    /// The update section under construction.
874    pub fn overlay(&self) -> &Overlay {
875        &self.overlay
876    }
877
878    /// Merges `meta` into the base document's `/Info` dictionary: the ref
879    /// comes from the overlay's own info ref when a prior call set one,
880    /// else the base's; the dictionary itself always comes from the base
881    /// document (never from a prior call's staged fields, so two calls on
882    /// one `Update` do not compound; on a base without `/Info`, a call
883    /// starts from a fresh dictionary). When the catalog already names an
884    /// XMP packet, it is rewritten from the merged fields. See
885    /// [`set_metadata_with`] for the merge and rewrite rules.
886    pub fn set_metadata(&mut self, meta: Metadata) -> Result<()> {
887        let info_ref = self.overlay.info.or(self.overlay.base.info);
888        let existing_info = info_ref.and_then(|r| {
889            let dict = self.doc.get(r).ok()?.as_dict()?.clone();
890            Some((r, resolve_dict(self.doc, &dict)))
891        });
892        let xmp_ref = catalog_metadata_ref(self.doc, self.overlay.base.root);
893        set_metadata_with(&mut self.overlay, existing_info, xmp_ref, meta)
894    }
895
896    /// The base bytes, whether a pad newline goes before the appended
897    /// section, and the section itself, computed together so a refused
898    /// update (or any other failure) is known before anything is written
899    /// anywhere.
900    fn parts(&self) -> Result<(&[u8], bool, Vec<u8>)> {
901        let base = self.doc.bytes();
902        let (start, pad) = start_offset(base);
903        let section = self.overlay.section(start)?;
904        Ok((base, pad, section))
905    }
906
907    /// Writes the base bytes, a pad newline when the base needs one, and
908    /// the appended section into `out`. The section is built before any
909    /// byte reaches `out`, so a refused update (or any other failure)
910    /// writes nothing at all.
911    pub fn append_into(&self, mut out: impl std::io::Write) -> Result<()> {
912        let (base, pad, section) = self.parts()?;
913        out.write_all(base)?;
914        if pad {
915            out.write_all(b"\n")?;
916        }
917        out.write_all(&section)?;
918        Ok(())
919    }
920
921    /// [`Update::append_into`] to a new file at `path`: the file is
922    /// created only once the update is known to build, so a refused
923    /// update leaves no file behind at all.
924    pub fn save(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
925        let (base, pad, section) = self.parts()?;
926        let mut file = std::fs::File::create(path)?;
927        file.write_all(base)?;
928        if pad {
929            file.write_all(b"\n")?;
930        }
931        file.write_all(&section)?;
932        Ok(())
933    }
934
935    /// The base bytes followed by the update section, as one buffer.
936    pub fn bytes(&self) -> Result<Vec<u8>> {
937        let mut out = Vec::new();
938        self.append_into(&mut out)?;
939        Ok(out)
940    }
941}
942
943/// One row of the appended section's cross-reference data: an object
944/// stored at a byte offset, or a freed number chained to the next free
945/// number in the section's own free list (entry 0 when it is the head).
946#[derive(Debug, Clone, Copy)]
947enum Row {
948    InFile(ObjRef, usize),
949    Free { num: u32, gen: u16, next: u32 },
950}
951
952impl Row {
953    fn num(&self) -> u32 {
954        match self {
955            Row::InFile(r, _) => r.num,
956            Row::Free { num, .. } => *num,
957        }
958    }
959}
960
961/// Splits `rows`, already sorted ascending by object number, into maximal
962/// runs of consecutive numbers, as `(run start index, run length)` pairs.
963/// Shared by the classic table's subsections and the xref stream's
964/// `/Index` pairs, so both group the same way.
965fn contiguous_runs(rows: &[Row]) -> Vec<(usize, usize)> {
966    let mut runs = Vec::new();
967    let mut begin = 0;
968    while begin < rows.len() {
969        let mut end = begin + 1;
970        while end < rows.len() && rows[end].num() == rows[end - 1].num() + 1 {
971            end += 1;
972        }
973        runs.push((begin, end - begin));
974        begin = end;
975    }
976    runs
977}
978
979/// The appended trailer's `/ID`: the base's first half kept verbatim, the
980/// second half replaced by the first 16 bytes of a SHA-256 over the first
981/// half's bytes, the base's `/Prev` offset as little-endian bytes, `body`
982/// (the section's serialized objects, built before its xref part), and
983/// finally each of `freed`'s `(num, gen)` pairs in order (`num` then `gen`,
984/// both little-endian), so a frees-only update, whose `body` is empty,
985/// still rotates by what it freed rather than staying fixed. `None` when
986/// the base carries no `/ID` array with a string first element, in which
987/// case the appended trailer omits the key entirely.
988fn rotated_id(base: &OverlayBase, body: &[u8], freed: &[ObjRef]) -> Option<Object> {
989    let Some(Object::Array(halves)) = &base.id else {
990        return None;
991    };
992    let Some(Object::String(first)) = halves.first() else {
993        return None;
994    };
995    let mut hasher = Sha256::new();
996    hasher.update(first);
997    hasher.update(&base.prev.to_le_bytes());
998    hasher.update(body);
999    for r in freed {
1000        hasher.update(&r.num.to_le_bytes());
1001        hasher.update(&r.gen.to_le_bytes());
1002    }
1003    let digest = hasher.finalize();
1004    Some(Object::Array(vec![
1005        Object::String(first.clone()),
1006        Object::String(digest[..16].to_vec()),
1007    ]))
1008}
1009
1010/// A cross-reference stream as the section's last object: one row per
1011/// object of the update (or per freed number) plus one for the stream
1012/// itself, and `/Index` pairs one per contiguous run of object numbers.
1013fn finish_stream(
1014    out: &mut Vec<u8>,
1015    start: u64,
1016    mut rows: Vec<Row>,
1017    mut dict: Dict,
1018    mut next: u32,
1019) -> Result<()> {
1020    let xref_ref = ObjRef { num: next, gen: 0 };
1021    next += 1;
1022    let xref_offset = start as usize + out.len();
1023    rows.push(Row::InFile(xref_ref, xref_offset));
1024    rows.sort_by_key(Row::num);
1025    let runs = contiguous_runs(&rows);
1026    let mut index = Vec::with_capacity(runs.len() * 2);
1027    for (begin, len) in runs {
1028        index.push(Object::Int(i64::from(rows[begin].num())));
1029        index.push(Object::Int(len as i64));
1030    }
1031    let mut data = Vec::with_capacity(rows.len() * 7);
1032    for row in &rows {
1033        match row {
1034            Row::InFile(r, offset) => {
1035                data.push(1);
1036                data.extend_from_slice(&field_offset(*offset)?.to_be_bytes());
1037                data.extend_from_slice(&r.gen.to_be_bytes());
1038            }
1039            Row::Free {
1040                gen,
1041                next: free_next,
1042                ..
1043            } => {
1044                data.push(0);
1045                data.extend_from_slice(&free_next.to_be_bytes());
1046                data.extend_from_slice(&gen.to_be_bytes());
1047            }
1048        }
1049    }
1050    dict.insert(name("Type"), Object::Name(name("XRef")));
1051    dict.insert(name("Size"), Object::Int(i64::from(next)));
1052    dict.insert(
1053        name("W"),
1054        Object::Array(vec![Object::Int(1), Object::Int(4), Object::Int(2)]),
1055    );
1056    dict.insert(name("Index"), Object::Array(index));
1057    write_indirect(out, xref_ref, &Object::Stream(Stream { dict, data }))?;
1058    out.extend_from_slice(format!("startxref\n{xref_offset}\n%%EOF\n").as_bytes());
1059    Ok(())
1060}
1061
1062/// A classic `xref` table with one subsection per run of consecutive
1063/// object numbers, then the `trailer` dictionary. A freed row uses `f` in
1064/// place of `n`, its first field naming the next free number in the
1065/// section's own chain rather than a byte offset.
1066fn finish_table(
1067    out: &mut Vec<u8>,
1068    start: u64,
1069    rows: &[Row],
1070    mut dict: Dict,
1071    size: u32,
1072) -> Result<()> {
1073    let xref_offset = start as usize + out.len();
1074    out.extend_from_slice(b"xref\n");
1075    for (begin, len) in contiguous_runs(rows) {
1076        out.extend_from_slice(format!("{} {}\n", rows[begin].num(), len).as_bytes());
1077        for row in &rows[begin..begin + len] {
1078            match row {
1079                Row::InFile(r, offset) => out.extend_from_slice(
1080                    format!("{:010} {:05} n \n", table_offset(*offset)?, r.gen).as_bytes(),
1081                ),
1082                Row::Free { gen, next, .. } => {
1083                    out.extend_from_slice(format!("{next:010} {gen:05} f \n").as_bytes())
1084                }
1085            }
1086        }
1087    }
1088    dict.insert(name("Size"), Object::Int(i64::from(size)));
1089    out.extend_from_slice(b"trailer\n");
1090    serialize_dict(&dict, out)?;
1091    out.extend_from_slice(format!("\nstartxref\n{xref_offset}\n%%EOF\n").as_bytes());
1092    Ok(())
1093}
1094
1095/// Emits `num gen obj` through `endobj`; a stream carries a direct
1096/// `/Length` of its stored byte count.
1097fn write_indirect(out: &mut Vec<u8>, r: ObjRef, obj: &Object) -> Result<()> {
1098    out.extend_from_slice(format!("{} {} obj\n", r.num, r.gen).as_bytes());
1099    match obj {
1100        Object::Stream(s) => {
1101            let mut dict = s.dict.clone();
1102            dict.insert(name("Length"), Object::Int(s.data.len() as i64));
1103            serialize_dict(&dict, out)?;
1104            out.extend_from_slice(b"\nstream\n");
1105            out.extend_from_slice(&s.data);
1106            out.extend_from_slice(b"\nendstream\nendobj\n");
1107        }
1108        direct => {
1109            serialize_object(direct, out)?;
1110            out.extend_from_slice(b"\nendobj\n");
1111        }
1112    }
1113    Ok(())
1114}
1115
1116/// An uncompressed stream with no filter of its own.
1117fn plain_stream(data: Vec<u8>) -> Stream {
1118    Stream {
1119        dict: Dict::new(),
1120        data,
1121    }
1122}
1123
1124pub(crate) fn deflate(data: &[u8]) -> Vec<u8> {
1125    let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1126    encoder
1127        .write_all(data)
1128        .expect("writing into a Vec cannot fail");
1129    encoder
1130        .finish()
1131        .expect("finishing an in-memory zlib stream cannot fail")
1132}
1133
1134/// A byte position as the 4-byte offset field of a cross-reference stream.
1135fn field_offset(position: usize) -> Result<u32> {
1136    u32::try_from(position)
1137        .map_err(|_| Error::Other("file offset exceeds the 4-byte xref field".to_string()))
1138}
1139
1140/// A byte position as the 10-digit offset field of a classic xref table.
1141fn table_offset(position: usize) -> Result<usize> {
1142    if position as u64 <= 9_999_999_999 {
1143        return Ok(position);
1144    }
1145    Err(Error::Other(
1146        "file offset exceeds the 10-digit xref table field".to_string(),
1147    ))
1148}
1149
1150fn name(text: &str) -> Name {
1151    Name(text.to_string())
1152}
1153
1154pub(crate) fn core_error(error: pdfboss_core::Error) -> Error {
1155    Error::Other(error.to_string())
1156}