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
30use crate::Error;
31
32#[derive(Debug, Clone)]
38pub struct EditDoc<'a> {
39 base: &'a Document,
40 overlay: BTreeMap<u32, Arc<Object>>,
43 removed: BTreeSet<u32>,
45 next_num: u32,
47 info: Option<ObjRef>,
51}
52
53impl<'a> EditDoc<'a> {
54 #[must_use]
56 pub fn new(base: &'a Document) -> Self {
57 Self {
58 base,
59 overlay: BTreeMap::new(),
60 removed: BTreeSet::new(),
61 next_num: base.xref().last_object_number().saturating_add(1),
64 info: None,
65 }
66 }
67
68 #[must_use]
70 pub fn base(&self) -> &'a Document {
71 self.base
72 }
73
74 pub(crate) fn trailer(&self) -> Dict {
77 let mut trailer = self.base.trailer().clone();
78 if let Some(info) = self.info {
79 trailer.insert(names::INFO.clone(), Object::Ref(info));
80 }
81 trailer
82 }
83
84 pub(crate) fn set_info(&mut self, r: ObjRef) {
86 self.info = Some(r);
87 }
88
89 pub fn add(&mut self, obj: Object) -> ObjRef {
92 let num = self.next_num;
93 self.next_num = self.next_num.saturating_add(1);
94 self.overlay.insert(num, Arc::new(obj));
95 self.removed.remove(&num);
96 ObjRef::new(num, 0)
97 }
98
99 pub fn replace(&mut self, r: ObjRef, obj: Object) {
103 self.overlay.insert(r.num, Arc::new(obj));
104 self.removed.remove(&r.num);
105 self.next_num = self.next_num.max(r.num.saturating_add(1));
106 }
107
108 pub fn remove(&mut self, r: ObjRef) {
110 self.overlay.remove(&r.num);
111 self.removed.insert(r.num);
112 }
113
114 #[must_use]
116 pub fn is_removed(&self, num: u32) -> bool {
117 self.removed.contains(&num)
118 }
119
120 pub fn edited(&self) -> impl Iterator<Item = (u32, &Arc<Object>)> {
123 self.overlay.iter().map(|(n, o)| (*n, o))
124 }
125
126 #[must_use]
128 pub fn is_edited(&self, num: u32) -> bool {
129 self.overlay.contains_key(&num)
130 }
131
132 #[must_use]
134 pub fn last_object_number(&self) -> u32 {
135 let base = self.base.xref().last_object_number();
136 self.overlay
137 .keys()
138 .next_back()
139 .copied()
140 .unwrap_or(0)
141 .max(base)
142 }
143
144 pub fn page_state(
156 &self,
157 index: impl Into<PageIndex>,
158 ) -> Result<Option<(ObjRef, Dict, Dict)>, Error> {
159 let index = index.into();
160 let page = self
161 .base()
162 .page(index)
163 .map_err(|_| Error::PageIndexOutOfRange(index))?;
164 let Some(reference) = page.reference else {
165 return Ok(None);
166 };
167 let dict = self
168 .fetch(reference)
169 .ok()
170 .as_deref()
171 .and_then(Object::as_dict)
172 .cloned()
173 .unwrap_or_else(|| page.dict.clone());
174 let resources = dict
175 .dict(crate::names::RESOURCES, &self)
176 .or_else(|| {
177 page.inherited(crate::names::RESOURCES, &self)?
178 .resolve(&self)
179 .ok()?
180 .as_dict()
181 .cloned()
182 })
183 .unwrap_or_default();
184 Ok(Some((reference, dict, resources)))
185 }
186
187 pub fn apply_page(
196 &mut self,
197 page: &PageEdit,
198 shared: &crate::ShareCounts,
199 ) -> Result<(), Error> {
200 let Some((reference, dict, resources)) = self.page_state(page.index())? else {
201 return Ok(());
203 };
204 let Some(rewrite) = crate::regenerate(page.graph(), &resources, self) else {
205 return Ok(());
206 };
207 crate::apply_rewrite(self, reference, &dict, &rewrite, shared);
208 Ok(())
209 }
210}
211
212impl Resolve for EditDoc<'_> {
213 fn fetch(&self, r: ObjRef) -> Result<Arc<Object>, pdfrum_object::Error> {
214 if self.removed.contains(&r.num) {
215 return Ok(Arc::new(Object::Null));
216 }
217 if let Some(obj) = self.overlay.get(&r.num) {
218 return Ok(Arc::clone(obj));
219 }
220 self.base.fetch(r)
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::EditDoc;
227 use pdfrum_object::{ObjRef, Object, Resolve};
228 use pdfrum_parser::{Document, LoadOptions, load};
229 use std::sync::Arc;
230
231 fn doc() -> Document {
232 let file = b"%PDF-1.7\n\
2331 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n\
2342 0 obj\n<< /Type /Pages /Count 1 /Kids [3 0 R] >>\nendobj\n\
2353 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n\
236trailer\n<< /Root 1 0 R /Size 4 >>\n";
237 load(Arc::from(&file[..]), &LoadOptions::default()).expect("opens")
238 }
239
240 #[test]
241 fn an_unedited_view_reads_straight_through() {
242 let base = doc();
243 let edit = EditDoc::new(&base);
244 let catalog = edit.fetch(ObjRef::new(1, 0)).expect("catalog");
245 assert!(catalog.as_dict().is_some());
246 assert!(edit.edited().next().is_none());
247 }
248
249 #[test]
251 fn added_objects_take_fresh_numbers() {
252 let base = doc();
253 let mut edit = EditDoc::new(&base);
254 let first = edit.add(Object::Int(1));
255 let second = edit.add(Object::Int(2));
256 assert!(first.num > base.xref().last_object_number());
257 assert_eq!(second.num, first.num + 1);
258 assert_eq!(first.generation, 0);
259 assert_eq!(*edit.fetch(first).expect("added"), Object::Int(1));
260 }
261
262 #[test]
263 fn the_overlay_answers_before_the_base() {
264 let base = doc();
265 let mut edit = EditDoc::new(&base);
266 let page = ObjRef::new(3, 0);
267 assert!(edit.fetch(page).expect("page").as_dict().is_some());
268 edit.replace(page, Object::Int(99));
269 assert_eq!(*edit.fetch(page).expect("replaced"), Object::Int(99));
270 assert!(base.fetch(page).expect("page").as_dict().is_some());
272 }
273
274 #[test]
277 fn a_removed_object_reads_as_null() {
278 let base = doc();
279 let mut edit = EditDoc::new(&base);
280 let page = ObjRef::new(3, 0);
281 edit.remove(page);
282 assert!(edit.fetch(page).expect("null").is_null());
283 assert!(edit.is_removed(3));
284 assert!(!edit.is_edited(3));
285 }
286
287 #[test]
288 fn replacing_a_removed_object_brings_it_back() {
289 let base = doc();
290 let mut edit = EditDoc::new(&base);
291 let page = ObjRef::new(3, 0);
292 edit.remove(page);
293 edit.replace(page, Object::Int(7));
294 assert!(!edit.is_removed(3));
295 assert_eq!(*edit.fetch(page).expect("back"), Object::Int(7));
296 }
297
298 #[test]
299 fn edits_come_back_in_ascending_number_order() {
300 let base = doc();
301 let mut edit = EditDoc::new(&base);
302 edit.replace(ObjRef::new(9, 0), Object::Int(9));
303 edit.replace(ObjRef::new(2, 0), Object::Int(2));
304 edit.replace(ObjRef::new(5, 0), Object::Int(5));
305 let nums: Vec<u32> = edit.edited().map(|(n, _)| n).collect();
306 assert_eq!(nums, vec![2, 5, 9]);
307 }
308
309 #[test]
312 fn replacing_past_the_end_moves_the_allocator() {
313 let base = doc();
314 let mut edit = EditDoc::new(&base);
315 edit.replace(ObjRef::new(100, 0), Object::Int(1));
316 assert_eq!(edit.add(Object::Int(2)).num, 101);
317 assert_eq!(edit.last_object_number(), 101);
318 }
319}