1use std::collections::BTreeMap;
9
10use lopdf::{Dictionary, Document, Object, ObjectId};
11
12#[derive(Debug, thiserror::Error)]
13pub enum MergeError {
14 #[error("no input documents")]
15 Empty,
16 #[error("input {index} could not be parsed: {message}")]
17 Parse { index: usize, message: String },
18 #[error("input {0} is encrypted")]
19 Encrypted(usize),
20 #[error("page {0} is out of range")]
21 PageOutOfRange(u32),
22 #[error("serialize failed: {0}")]
23 Write(String),
24}
25
26const INHERITED: [&[u8]; 4] = [b"Resources", b"MediaBox", b"CropBox", b"Rotate"];
27
28fn load(index: usize, bytes: &[u8]) -> Result<Document, MergeError> {
29 let doc = Document::load_mem(bytes).map_err(|e| MergeError::Parse {
30 index,
31 message: e.to_string(),
32 })?;
33 if doc.is_encrypted() {
34 return Err(MergeError::Encrypted(index));
35 }
36 Ok(doc)
37}
38
39fn materialize_inherited(doc: &mut Document, page_id: ObjectId) {
40 let mut found: Vec<(&[u8], Object)> = Vec::new();
41 let own = match doc.get_object(page_id).and_then(|o| o.as_dict()) {
42 Ok(d) => d.clone(),
43 Err(_) => return,
44 };
45 for key in INHERITED {
46 if own.has(key) {
47 continue;
48 }
49 let mut cursor = own.get(b"Parent").ok().and_then(|p| p.as_reference().ok());
50 let mut hops = 0;
51 while let Some(id) = cursor {
52 hops += 1;
53 if hops > 64 {
54 break;
55 }
56 let Ok(node) = doc.get_object(id).and_then(|o| o.as_dict()) else {
57 break;
58 };
59 if let Ok(value) = node.get(key) {
60 found.push((key, value.clone()));
61 break;
62 }
63 cursor = node.get(b"Parent").ok().and_then(|p| p.as_reference().ok());
64 }
65 }
66 if let Ok(Object::Dictionary(dict)) = doc.get_object_mut(page_id) {
67 for (key, value) in found {
68 dict.set(key, value);
69 }
70 }
71}
72
73pub fn merge_pdfs(inputs: &[&[u8]]) -> Result<Vec<u8>, MergeError> {
75 if inputs.is_empty() {
76 return Err(MergeError::Empty);
77 }
78 let mut max_id: u32 = 1;
79 let mut page_ids: Vec<ObjectId> = Vec::new();
80 let mut objects: BTreeMap<ObjectId, Object> = BTreeMap::new();
81 for (index, bytes) in inputs.iter().enumerate() {
82 let mut doc = load(index, bytes)?;
83 let pages: Vec<ObjectId> = doc.get_pages().into_values().collect();
84 for id in &pages {
85 materialize_inherited(&mut doc, *id);
86 }
87 doc.renumber_objects_with(max_id);
89 max_id = doc.max_id + 1;
90 page_ids.extend(doc.get_pages().into_values());
91 objects.extend(doc.objects);
92 }
93 assemble(objects, page_ids)
94}
95
96pub fn select_pages(source: &[u8], pages: &[u32]) -> Result<Vec<u8>, MergeError> {
98 if pages.is_empty() {
99 return Err(MergeError::Empty);
100 }
101 let mut doc = load(0, source)?;
102 let map = doc.get_pages();
103 for id in map.values().copied().collect::<Vec<_>>() {
104 materialize_inherited(&mut doc, id);
105 }
106 let mut picked = Vec::with_capacity(pages.len());
107 let mut objects = doc.objects.clone();
108 let mut next = doc.max_id;
109 for &p in pages {
110 let id = *map.get(&p).ok_or(MergeError::PageOutOfRange(p))?;
111 if picked.contains(&id) {
112 next += 1;
114 let new_id = (next, 0);
115 let copy = objects
116 .get(&id)
117 .cloned()
118 .ok_or(MergeError::PageOutOfRange(p))?;
119 objects.insert(new_id, copy);
120 picked.push(new_id);
121 } else {
122 picked.push(id);
123 }
124 }
125 assemble(objects.into_iter().collect(), picked)
126}
127
128fn assemble(
129 objects: BTreeMap<ObjectId, Object>,
130 page_ids: Vec<ObjectId>,
131) -> Result<Vec<u8>, MergeError> {
132 let mut merged = Document::with_version("1.7");
133 merged.objects = objects;
134 merged.max_id = merged.objects.keys().map(|id| id.0).max().unwrap_or(1);
135 let pages_id = merged.new_object_id();
136 let kids: Vec<Object> = page_ids.iter().map(|id| Object::Reference(*id)).collect();
137 let mut pages_dict = Dictionary::new();
138 pages_dict.set("Type", Object::Name(b"Pages".to_vec()));
139 pages_dict.set("Count", Object::Integer(kids.len() as i64));
140 pages_dict.set("Kids", Object::Array(kids));
141 merged
142 .objects
143 .insert(pages_id, Object::Dictionary(pages_dict));
144 for id in &page_ids {
145 if let Ok(Object::Dictionary(dict)) = merged.get_object_mut(*id) {
146 dict.set("Parent", Object::Reference(pages_id));
147 }
148 }
149 let mut catalog = Dictionary::new();
150 catalog.set("Type", Object::Name(b"Catalog".to_vec()));
151 catalog.set("Pages", Object::Reference(pages_id));
152 let catalog_id = merged.add_object(Object::Dictionary(catalog));
153 merged.trailer.set("Root", catalog_id);
154 merged.prune_objects();
155 merged.renumber_objects();
156 merged.compress();
157 let mut out = Vec::new();
158 merged
159 .save_to(&mut out)
160 .map_err(|e| MergeError::Write(e.to_string()))?;
161 Ok(out)
162}