1use std::collections::BTreeMap;
15
16use lopdf::{Dictionary, Document, Object, ObjectId};
17
18#[derive(Debug, thiserror::Error)]
19pub enum MergeError {
20 #[error("no input documents")]
21 Empty,
22 #[error("input {index} could not be parsed: {message}")]
23 Parse { index: usize, message: String },
24 #[error("input {0} is encrypted")]
25 Encrypted(usize),
26 #[error("page {0} is out of range")]
27 PageOutOfRange(u32),
28 #[error("serialize failed: {0}")]
29 Write(String),
30}
31
32const INHERITED: [&[u8]; 4] = [b"Resources", b"MediaBox", b"CropBox", b"Rotate"];
33
34fn load(index: usize, bytes: &[u8]) -> Result<Document, MergeError> {
35 let doc = crate::inspect::load_pdf(bytes).map_err(|e| MergeError::Parse {
38 index,
39 message: e.to_string(),
40 })?;
41 if doc.is_encrypted() {
42 return Err(MergeError::Encrypted(index));
43 }
44 Ok(doc)
45}
46
47type SharedValues = BTreeMap<(ObjectId, usize), Object>;
50
51fn materialize_inherited(doc: &mut Document, page_id: ObjectId, shared: &mut SharedValues) {
56 let (parent, has) = match doc.get_object(page_id).and_then(|o| o.as_dict()) {
58 Ok(page) => (
59 page.get(b"Parent").ok().and_then(|p| p.as_reference().ok()),
60 INHERITED.map(|key| page.has(key)),
61 ),
62 Err(_) => return,
63 };
64 let mut found: Vec<(&[u8], Object)> = Vec::new();
65 for (index, &key) in INHERITED.iter().enumerate() {
66 if has[index] {
67 continue;
68 }
69 let mut holder: Option<ObjectId> = None;
70 let mut cursor = parent;
71 let mut hops = 0;
72 while let Some(id) = cursor {
73 hops += 1;
74 if hops > 64 {
75 break;
76 }
77 let Ok(node) = doc.get_object(id).and_then(|o| o.as_dict()) else {
78 break;
79 };
80 if node.has(key) {
81 holder = Some(id);
82 break;
83 }
84 cursor = node.get(b"Parent").ok().and_then(|p| p.as_reference().ok());
85 }
86 let Some(holder) = holder else { continue };
87 let value = if let Some(value) = shared.get(&(holder, index)).cloned() {
88 value
89 } else {
90 let direct = doc
91 .get_object(holder)
92 .ok()
93 .and_then(|o| o.as_dict().ok())
94 .and_then(|node| node.get(key).ok())
95 .cloned();
96 let value = match direct {
97 None => continue,
98 Some(
100 plain @ (Object::Reference(_)
101 | Object::Integer(_)
102 | Object::Real(_)
103 | Object::Boolean(_)
104 | Object::Null),
105 ) => plain,
106 Some(container) => Object::Reference(doc.add_object(container)),
107 };
108 shared.insert((holder, index), value.clone());
109 value
110 };
111 found.push((key, value));
112 }
113 if let Ok(Object::Dictionary(dict)) = doc.get_object_mut(page_id) {
114 for (key, value) in found {
115 dict.set(key, value);
116 }
117 }
118}
119
120pub fn merge_pdfs(inputs: &[&[u8]]) -> Result<Vec<u8>, MergeError> {
122 if inputs.is_empty() {
123 return Err(MergeError::Empty);
124 }
125 let mut max_id: u32 = 1;
126 let mut page_ids: Vec<ObjectId> = Vec::new();
127 let mut objects: BTreeMap<ObjectId, Object> = BTreeMap::new();
128 for (index, bytes) in inputs.iter().enumerate() {
129 let mut doc = load(index, bytes)?;
130 let pages: Vec<ObjectId> = doc.get_pages().into_values().collect();
131 let mut shared = SharedValues::new();
132 for id in &pages {
133 materialize_inherited(&mut doc, *id, &mut shared);
134 }
135 doc.renumber_objects_with(max_id);
137 max_id = doc.max_id + 1;
138 page_ids.extend(doc.get_pages().into_values());
139 objects.extend(doc.objects);
140 }
141 assemble(objects, page_ids)
142}
143
144pub fn select_pages(source: &[u8], pages: &[u32]) -> Result<Vec<u8>, MergeError> {
146 if pages.is_empty() {
147 return Err(MergeError::Empty);
148 }
149 let mut doc = load(0, source)?;
150 let map = doc.get_pages();
151 let mut shared = SharedValues::new();
152 for id in map.values().copied().collect::<Vec<_>>() {
153 materialize_inherited(&mut doc, id, &mut shared);
154 }
155 let mut picked = Vec::with_capacity(pages.len());
156 let mut objects = doc.objects.clone();
157 let mut next = doc.max_id;
158 for &p in pages {
159 let id = *map.get(&p).ok_or(MergeError::PageOutOfRange(p))?;
160 if picked.contains(&id) {
161 next += 1;
163 let new_id = (next, 0);
164 let copy = objects
165 .get(&id)
166 .cloned()
167 .ok_or(MergeError::PageOutOfRange(p))?;
168 objects.insert(new_id, copy);
169 picked.push(new_id);
170 } else {
171 picked.push(id);
172 }
173 }
174 assemble(objects.into_iter().collect(), picked)
175}
176
177fn assemble(
178 objects: BTreeMap<ObjectId, Object>,
179 page_ids: Vec<ObjectId>,
180) -> Result<Vec<u8>, MergeError> {
181 let mut merged = Document::with_version("1.7");
182 merged.objects = objects;
183 merged.max_id = merged.objects.keys().map(|id| id.0).max().unwrap_or(1);
184 let pages_id = merged.new_object_id();
185 let kids: Vec<Object> = page_ids.iter().map(|id| Object::Reference(*id)).collect();
186 let mut pages_dict = Dictionary::new();
187 pages_dict.set("Type", Object::Name(b"Pages".to_vec()));
188 pages_dict.set("Count", Object::Integer(kids.len() as i64));
189 pages_dict.set("Kids", Object::Array(kids));
190 merged
191 .objects
192 .insert(pages_id, Object::Dictionary(pages_dict));
193 for id in &page_ids {
194 if let Ok(Object::Dictionary(dict)) = merged.get_object_mut(*id) {
195 dict.set("Parent", Object::Reference(pages_id));
196 }
197 }
198 let mut catalog = Dictionary::new();
199 catalog.set("Type", Object::Name(b"Catalog".to_vec()));
200 catalog.set("Pages", Object::Reference(pages_id));
201 let catalog_id = merged.add_object(Object::Dictionary(catalog));
202 merged.trailer.set("Root", catalog_id);
203 merged.prune_objects();
204 merged.renumber_objects();
205 merged.compress();
206 let mut out = Vec::new();
207 merged
208 .save_to(&mut out)
209 .map_err(|e| MergeError::Write(e.to_string()))?;
210 Ok(out)
211}