pdfboss_write/importer.rs
1//! Transplanting a source document's object graph into a [`Writer`] (ISO
2//! 32000 ยง7.3.10, indirect references): [`Importer`] renumbers every
3//! reference it meets once and drains the resulting queue iteratively, so a
4//! long chain of references costs no stack, and a caller may substitute a
5//! translated body for any object before the drain reaches it.
6
7use pdfboss_core::{Dict, Document, FastMap, Name, ObjRef, Object, Rect, Stream};
8
9use crate::error::{Error, Result};
10use crate::update::{core_error, deflate};
11use crate::writer::Writer;
12
13/// Copies one source document's reachable object graph into a [`Writer`].
14/// Every reference met through [`Importer::reference`] or [`Importer::copy`]
15/// is reserved a target number once and queued, so repeated imports from the
16/// same source dedup by source object number: one `Importer` per source
17/// document, and one `Writer` accepts several `Importer`s in sequence.
18pub struct Importer<'w, 's> {
19 writer: &'w mut Writer,
20 source: &'s Document,
21 map: FastMap<ObjRef, ObjRef>,
22 pending: Vec<ObjRef>,
23 substitutions: FastMap<ObjRef, Object>,
24 compress: bool,
25}
26
27impl<'w, 's> Importer<'w, 's> {
28 /// Opens `source` for import into `writer`. Refuses a locked source:
29 /// `Document::get` only decrypts transparently once a working
30 /// password configured a decryptor, so copying from a locked source
31 /// would emit raw ciphertext into a plain target with no `/Encrypt`
32 /// of its own. A password-opened encrypted document copies fine: its
33 /// content already reads as plaintext through `Document::get`, exactly
34 /// what [`crate::encrypt_document`] relies on to re-encrypt it under
35 /// new passwords.
36 pub fn new(writer: &'w mut Writer, source: &'s Document) -> Result<Importer<'w, 's>> {
37 // The public load path already refuses a wrong or missing password
38 // before any `Document` exists; this check is a second safeguard,
39 // for a `Document` constructed some other way.
40 if source.is_locked() {
41 return Err(Error::EncryptedBase);
42 }
43 let compress = writer.compress();
44 Ok(Importer {
45 writer,
46 source,
47 map: FastMap::default(),
48 pending: Vec::new(),
49 substitutions: FastMap::default(),
50 compress,
51 })
52 }
53
54 /// The target number for source reference `r`, reserved and queued on
55 /// first sight.
56 pub fn reference(&mut self, r: ObjRef) -> ObjRef {
57 if let Some(copied) = self.map.get(&r) {
58 return *copied;
59 }
60 let copied = self.writer.reserve();
61 self.map.insert(r, copied);
62 self.pending.push(r);
63 copied
64 }
65
66 /// A copy of `obj` (source-space) with every reference renumbered into
67 /// the target: the existing private translation, made public.
68 pub fn copy(&mut self, obj: &Object) -> Result<Object> {
69 Ok(match obj {
70 Object::Ref(r) => Object::Ref(self.reference(*r)),
71 Object::Dict(d) => Object::Dict(self.copy_dict(d)?),
72 Object::Array(items) => Object::Array(
73 items
74 .iter()
75 .map(|item| self.copy(item))
76 .collect::<Result<Vec<Object>>>()?,
77 ),
78 Object::Stream(_) => return Err(Error::NestedStream),
79 other => other.clone(),
80 })
81 }
82
83 /// A copy of `dict`'s direct structure with every reference mapped.
84 pub(crate) fn copy_dict(&mut self, dict: &Dict) -> Result<Dict> {
85 let mut out = Dict::new();
86 for (key, value) in dict.iter() {
87 out.insert(key.clone(), self.copy(value)?);
88 }
89 Ok(out)
90 }
91
92 /// A stream body: its dictionary copied without `/Length` (the writer
93 /// sets it), its data compressed when asked and not already filtered.
94 fn copy_stream(&mut self, stream: &Stream) -> Result<Object> {
95 let mut dict = stream.dict.clone();
96 dict.remove("Length");
97 let mut dict = self.copy_dict(&dict)?;
98 let data = if self.compress && dict.get("Filter").is_none() {
99 dict.insert(name("Filter"), Object::Name(name("FlateDecode")));
100 deflate(&stream.data)
101 } else {
102 stream.data.clone()
103 };
104 Ok(Object::Stream(Stream { dict, data }))
105 }
106
107 /// Replaces the source object's body during the transplant. The
108 /// body is TARGET-space and drain fills it verbatim, no
109 /// renumbering: the caller translates any source refs into it via
110 /// `reference`/`copy` first, and may use refs of objects already
111 /// in the writer directly.
112 ///
113 /// Must be called before `finish` drains `r`: once `r` is popped off
114 /// the pending queue and filled, a later `substitute` for it has no
115 /// effect. `page` cannot trigger this hazard: it only substitutes a
116 /// reference it has itself just queued for the first time.
117 pub fn substitute(&mut self, r: ObjRef, body: Object) {
118 self.substitutions.insert(r, body);
119 }
120
121 /// Drains the pending queue; called by `page`/`document` before
122 /// returning, public for callers that mixed `reference` in.
123 pub fn finish(&mut self) -> Result<()> {
124 while let Some(r) = self.pending.pop() {
125 let target = self.map[&r];
126 let body = match self.substitutions.remove(&r) {
127 Some(body) => body,
128 None => match self.source.get(r).map_err(core_error)? {
129 Object::Stream(s) => self.copy_stream(&s)?,
130 other => self.copy(&other)?,
131 },
132 };
133 self.writer.fill(target, body)?;
134 }
135 Ok(())
136 }
137
138 /// The whole reachable graph from the source catalog; returns the
139 /// new root ref. Substitutions apply.
140 pub fn document(&mut self) -> Result<ObjRef> {
141 let root = self
142 .source
143 .xref()
144 .trailer
145 .get_ref("Root")
146 .ok_or(Error::MissingRoot)?;
147 let new_root = self.reference(root);
148 self.finish()?;
149 Ok(new_root)
150 }
151
152 /// Page `index` as a self-contained object under `parent`
153 /// (target-space): old `/Parent` replaced with `parent`, effective
154 /// `/Resources` and `/MediaBox` materialized, `/Rotate` when non-zero,
155 /// `/CropBox` when it differs from the media box, `/Type /Page` always
156 /// present. Returns the page's new ref. The source `/Parent` is dropped
157 /// before translation, so none of the source's page tree (siblings,
158 /// ancestors) rides along as unreachable objects in the target.
159 ///
160 /// ISO 32000 gives a page exactly one parent, so every call gets its
161 /// own page object: importing the same source index again (for a
162 /// second parent, or a repeat in an assembled document) never reuses
163 /// an earlier call's object, even though the resources and content
164 /// beneath keep deduping through this `Importer`'s map. The one
165 /// exception is the very first time a given indirect source page is
166 /// seen: that call keeps the page's source-graph identity (so an
167 /// `/Annots` `/P` back-reference elsewhere in the graph resolves to
168 /// this same object rather than a duplicate). Pages inlined into
169 /// `/Kids` (no object of their own) always get a fresh object.
170 pub fn page(&mut self, index: usize, parent: ObjRef) -> Result<ObjRef> {
171 let page = self.source.page(index).map_err(core_error)?;
172 let mut dict = page.dict().clone();
173 dict.remove("Parent");
174 dict.insert(name("Type"), Object::Name(name("Page")));
175 dict.insert(name("Resources"), Object::Dict(page.resources.clone()));
176 dict.insert(name("MediaBox"), rect_array(page.media_box));
177 if page.rotate != 0 {
178 dict.insert(name("Rotate"), Object::Int(i64::from(page.rotate)));
179 }
180 if page.crop_box != page.media_box {
181 dict.insert(name("CropBox"), rect_array(page.crop_box));
182 }
183 let mut translated = self.copy_dict(&dict)?;
184 translated.insert(name("Parent"), Object::Ref(parent));
185 let target = match page.object_ref() {
186 Some(r) if !self.map.contains_key(&r) => {
187 let target = self.reference(r);
188 self.substitute(r, Object::Dict(translated));
189 target
190 }
191 _ => {
192 let target = self.writer.reserve();
193 self.writer.fill(target, Object::Dict(translated))?;
194 target
195 }
196 };
197 self.finish()?;
198 Ok(target)
199 }
200}
201
202/// A rectangle as a PDF `[x0 y0 x1 y1]` array of reals.
203pub(crate) fn rect_array(rect: Rect) -> Object {
204 Object::Array(
205 [rect.x0, rect.y0, rect.x1, rect.y1]
206 .iter()
207 .map(|v| Object::Real(widen(*v)))
208 .collect(),
209 )
210}
211
212/// `value` widened to `f64` through its own shortest round-trip decimal,
213/// rather than a raw bit widening: a plain `f64::from(f32)` keeps every
214/// bit of the `f32`'s binary value, which usually needs far more decimal
215/// digits to print than the `f32` itself means (`0.1_f32` widens to
216/// `0.10000000149011612`, not `0.1`). Formatting the `f32` first and
217/// reparsing produces the `f64` an `Object::Real` should hold: the one
218/// whose decimal digits match what the source number meant.
219fn widen(value: f32) -> f64 {
220 value
221 .to_string()
222 .parse()
223 .expect("a float's own Display output parses back as a float")
224}
225
226fn name(text: &str) -> Name {
227 Name(text.to_string())
228}
229
230#[cfg(test)]
231mod tests {
232 use pdfboss_core::Rect;
233
234 use super::*;
235 use crate::ser::serialize_object;
236
237 fn real_at(array: &Object, index: usize) -> String {
238 let Object::Array(items) = array else {
239 panic!("expected an array, got {array:?}");
240 };
241 let mut out = Vec::new();
242 serialize_object(&items[index], &mut out).expect("a real serializes");
243 String::from_utf8(out).expect("real syntax is ASCII")
244 }
245
246 #[test]
247 fn rect_array_keeps_the_f32_shortest_decimal() {
248 let rect = Rect::new(0.1, 0.0, 300.0, 400.0);
249 let array = rect_array(rect);
250 assert_eq!(real_at(&array, 0), "0.1");
251 }
252}