1use std::collections::{BTreeMap, BTreeSet};
23use std::sync::Arc;
24
25use pdfrum_common::PageIndex;
26use pdfrum_object::{Dict, ObjRef, Object, Resolve, names};
27use pdfrum_page::PageEdit;
28use pdfrum_parser::Document;
29
30#[derive(Debug, Clone)]
36pub struct EditDoc<'a> {
37 base: &'a Document,
38 overlay: BTreeMap<u32, Arc<Object>>,
41 removed: BTreeSet<u32>,
43 next_num: u32,
45 info: Option<ObjRef>,
49}
50
51impl<'a> EditDoc<'a> {
52 #[must_use]
54 pub fn new(base: &'a Document) -> Self {
55 Self {
56 base,
57 overlay: BTreeMap::new(),
58 removed: BTreeSet::new(),
59 next_num: base.xref().last_object_number().saturating_add(1),
62 info: None,
63 }
64 }
65
66 #[must_use]
68 pub fn base(&self) -> &'a Document {
69 self.base
70 }
71
72 pub(crate) fn trailer(&self) -> Dict {
75 let mut trailer = self.base.trailer().clone();
76 if let Some(info) = self.info {
77 trailer.insert(names::INFO.clone(), Object::Ref(info));
78 }
79 trailer
80 }
81
82 pub(crate) fn set_info(&mut self, r: ObjRef) {
84 self.info = Some(r);
85 }
86
87 pub fn add(&mut self, obj: Object) -> ObjRef {
90 let num = self.next_num;
91 self.next_num = self.next_num.saturating_add(1);
92 self.overlay.insert(num, Arc::new(obj));
93 self.removed.remove(&num);
94 ObjRef::new(num, 0)
95 }
96
97 pub fn replace(&mut self, r: ObjRef, obj: Object) {
101 self.overlay.insert(r.num, Arc::new(obj));
102 self.removed.remove(&r.num);
103 self.next_num = self.next_num.max(r.num.saturating_add(1));
104 }
105
106 pub fn remove(&mut self, r: ObjRef) {
108 self.overlay.remove(&r.num);
109 self.removed.insert(r.num);
110 }
111
112 #[must_use]
114 pub fn is_removed(&self, num: u32) -> bool {
115 self.removed.contains(&num)
116 }
117
118 pub fn edited(&self) -> impl Iterator<Item = (u32, &Arc<Object>)> {
121 self.overlay.iter().map(|(n, o)| (*n, o))
122 }
123
124 #[must_use]
126 pub fn is_edited(&self, num: u32) -> bool {
127 self.overlay.contains_key(&num)
128 }
129
130 #[must_use]
132 pub fn last_object_number(&self) -> u32 {
133 let base = self.base.xref().last_object_number();
134 self.overlay
135 .keys()
136 .next_back()
137 .copied()
138 .unwrap_or(0)
139 .max(base)
140 }
141
142 pub fn page_state(
154 &self,
155 index: PageIndex,
156 ) -> Result<Option<(ObjRef, Dict, Dict)>, pdfrum_parser::Error> {
157 let page = self.base().page(index)?;
158 let Some(reference) = page.reference else {
159 return Ok(None);
160 };
161 let dict = self
162 .fetch(reference)
163 .ok()
164 .as_deref()
165 .and_then(Object::as_dict)
166 .cloned()
167 .unwrap_or_else(|| page.dict.clone());
168 let resources = dict
169 .dict(crate::names::RESOURCES, &self)
170 .or_else(|| {
171 page.inherited(crate::names::RESOURCES, &self)?
172 .resolve(&self)
173 .ok()?
174 .as_dict()
175 .cloned()
176 })
177 .unwrap_or_default();
178 Ok(Some((reference, dict, resources)))
179 }
180
181 pub fn apply_page(
190 &mut self,
191 page: &PageEdit,
192 shared: &crate::ShareCounts,
193 ) -> Result<(), pdfrum_parser::Error> {
194 let Some((reference, dict, resources)) = self.page_state(page.index())? else {
195 return Ok(());
197 };
198 let Some(rewrite) = crate::regenerate(page.graph(), &resources, self) else {
199 return Ok(());
200 };
201 crate::apply_rewrite(self, reference, &dict, &rewrite, shared);
202 Ok(())
203 }
204}
205
206impl Resolve for EditDoc<'_> {
207 fn fetch(&self, r: ObjRef) -> Result<Arc<Object>, pdfrum_object::Error> {
208 if self.removed.contains(&r.num) {
209 return Ok(Arc::new(Object::Null));
210 }
211 if let Some(obj) = self.overlay.get(&r.num) {
212 return Ok(Arc::clone(obj));
213 }
214 self.base.fetch(r)
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::EditDoc;
221 use pdfrum_object::{ObjRef, Object, Resolve};
222 use pdfrum_parser::{Document, LoadOptions, load};
223 use std::sync::Arc;
224
225 fn doc() -> Document {
226 let file = b"%PDF-1.7\n\
2271 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n\
2282 0 obj\n<< /Type /Pages /Count 1 /Kids [3 0 R] >>\nendobj\n\
2293 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n\
230trailer\n<< /Root 1 0 R /Size 4 >>\n";
231 load(Arc::from(&file[..]), &LoadOptions::default()).expect("opens")
232 }
233
234 #[test]
235 fn an_unedited_view_reads_straight_through() {
236 let base = doc();
237 let edit = EditDoc::new(&base);
238 let catalog = edit.fetch(ObjRef::new(1, 0)).expect("catalog");
239 assert!(catalog.as_dict().is_some());
240 assert!(edit.edited().next().is_none());
241 }
242
243 #[test]
245 fn added_objects_take_fresh_numbers() {
246 let base = doc();
247 let mut edit = EditDoc::new(&base);
248 let first = edit.add(Object::Int(1));
249 let second = edit.add(Object::Int(2));
250 assert!(first.num > base.xref().last_object_number());
251 assert_eq!(second.num, first.num + 1);
252 assert_eq!(first.generation, 0);
253 assert_eq!(*edit.fetch(first).expect("added"), Object::Int(1));
254 }
255
256 #[test]
257 fn the_overlay_answers_before_the_base() {
258 let base = doc();
259 let mut edit = EditDoc::new(&base);
260 let page = ObjRef::new(3, 0);
261 assert!(edit.fetch(page).expect("page").as_dict().is_some());
262 edit.replace(page, Object::Int(99));
263 assert_eq!(*edit.fetch(page).expect("replaced"), Object::Int(99));
264 assert!(base.fetch(page).expect("page").as_dict().is_some());
266 }
267
268 #[test]
271 fn a_removed_object_reads_as_null() {
272 let base = doc();
273 let mut edit = EditDoc::new(&base);
274 let page = ObjRef::new(3, 0);
275 edit.remove(page);
276 assert!(edit.fetch(page).expect("null").is_null());
277 assert!(edit.is_removed(3));
278 assert!(!edit.is_edited(3));
279 }
280
281 #[test]
282 fn replacing_a_removed_object_brings_it_back() {
283 let base = doc();
284 let mut edit = EditDoc::new(&base);
285 let page = ObjRef::new(3, 0);
286 edit.remove(page);
287 edit.replace(page, Object::Int(7));
288 assert!(!edit.is_removed(3));
289 assert_eq!(*edit.fetch(page).expect("back"), Object::Int(7));
290 }
291
292 #[test]
293 fn edits_come_back_in_ascending_number_order() {
294 let base = doc();
295 let mut edit = EditDoc::new(&base);
296 edit.replace(ObjRef::new(9, 0), Object::Int(9));
297 edit.replace(ObjRef::new(2, 0), Object::Int(2));
298 edit.replace(ObjRef::new(5, 0), Object::Int(5));
299 let nums: Vec<u32> = edit.edited().map(|(n, _)| n).collect();
300 assert_eq!(nums, vec![2, 5, 9]);
301 }
302
303 #[test]
306 fn replacing_past_the_end_moves_the_allocator() {
307 let base = doc();
308 let mut edit = EditDoc::new(&base);
309 edit.replace(ObjRef::new(100, 0), Object::Int(1));
310 assert_eq!(edit.add(Object::Int(2)).num, 101);
311 assert_eq!(edit.last_object_number(), 101);
312 }
313}