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 pub(crate) glyph_faces: Vec<crate::font::glyph::GlyphFace>,
54}
55
56impl<'a> EditDoc<'a> {
57 #[must_use]
59 pub fn new(base: &'a Document) -> Self {
60 Self {
61 base,
62 overlay: BTreeMap::new(),
63 removed: BTreeSet::new(),
64 next_num: base.xref().last_object_number().saturating_add(1),
67 info: None,
68 glyph_faces: Vec::new(),
69 }
70 }
71
72 #[must_use]
74 pub fn base(&self) -> &'a Document {
75 self.base
76 }
77
78 pub(crate) fn trailer(&self) -> Dict {
81 let mut trailer = self.base.trailer().clone();
82 if let Some(info) = self.info {
83 trailer.insert(names::INFO.clone(), Object::Ref(info));
84 }
85 trailer
86 }
87
88 pub(crate) fn set_info(&mut self, r: ObjRef) {
90 self.info = Some(r);
91 }
92
93 pub fn add(&mut self, obj: Object) -> ObjRef {
96 let num = self.next_num;
97 self.next_num = self.next_num.saturating_add(1);
98 self.overlay.insert(num, Arc::new(obj));
99 self.removed.remove(&num);
100 ObjRef::new(num, 0)
101 }
102
103 pub fn replace(&mut self, r: ObjRef, obj: Object) {
107 self.overlay.insert(r.num, Arc::new(obj));
108 self.removed.remove(&r.num);
109 self.next_num = self.next_num.max(r.num.saturating_add(1));
110 }
111
112 pub fn remove(&mut self, r: ObjRef) {
114 self.overlay.remove(&r.num);
115 self.removed.insert(r.num);
116 }
117
118 #[must_use]
120 pub fn is_removed(&self, num: u32) -> bool {
121 self.removed.contains(&num)
122 }
123
124 pub fn edited(&self) -> impl Iterator<Item = (u32, &Arc<Object>)> {
127 self.overlay.iter().map(|(n, o)| (*n, o))
128 }
129
130 #[must_use]
132 pub fn is_edited(&self, num: u32) -> bool {
133 self.overlay.contains_key(&num)
134 }
135
136 #[must_use]
138 pub fn last_object_number(&self) -> u32 {
139 let base = self.base.xref().last_object_number();
140 self.overlay
141 .keys()
142 .next_back()
143 .copied()
144 .unwrap_or(0)
145 .max(base)
146 }
147
148 pub fn page_state(
160 &self,
161 index: impl Into<PageIndex>,
162 ) -> Result<Option<(ObjRef, Dict, Dict)>, Error> {
163 let index = index.into();
164 let page = self
165 .base()
166 .page(index)
167 .map_err(|_| Error::PageIndexOutOfRange(index))?;
168 let Some(reference) = page.reference else {
169 return Ok(None);
170 };
171 let dict = self
172 .fetch(reference)
173 .ok()
174 .as_deref()
175 .and_then(Object::as_dict)
176 .cloned()
177 .unwrap_or_else(|| page.dict.clone());
178 let resources = dict
179 .dict(crate::names::RESOURCES, &self)
180 .or_else(|| {
181 page.inherited(crate::names::RESOURCES, &self)?
182 .resolve(&self)
183 .ok()?
184 .as_dict()
185 .cloned()
186 })
187 .unwrap_or_default();
188 Ok(Some((reference, dict, resources)))
189 }
190
191 pub fn apply_page(
200 &mut self,
201 page: &PageEdit,
202 shared: &crate::ShareCounts,
203 ) -> Result<(), Error> {
204 let Some((reference, dict, resources)) = self.page_state(page.index())? else {
205 return Ok(());
207 };
208 let Some(rewrite) = crate::regenerate(page.graph(), &resources, self) else {
209 return Ok(());
210 };
211 crate::apply_rewrite(self, reference, &dict, &rewrite, shared);
212 Ok(())
213 }
214}
215
216impl Resolve for EditDoc<'_> {
217 fn fetch(&self, r: ObjRef) -> Result<Arc<Object>, pdfrum_object::Error> {
218 if self.removed.contains(&r.num) {
219 return Ok(Arc::new(Object::Null));
220 }
221 if let Some(obj) = self.overlay.get(&r.num) {
222 return Ok(Arc::clone(obj));
223 }
224 self.base.fetch(r)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::EditDoc;
231 use pdfrum_object::{ObjRef, Object, Resolve};
232 use pdfrum_parser::{Document, LoadOptions, load};
233 use std::sync::Arc;
234
235 fn doc() -> Document {
236 let file = b"%PDF-1.7\n\
2371 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n\
2382 0 obj\n<< /Type /Pages /Count 1 /Kids [3 0 R] >>\nendobj\n\
2393 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n\
240trailer\n<< /Root 1 0 R /Size 4 >>\n";
241 load(Arc::from(&file[..]), &LoadOptions::default()).expect("opens")
242 }
243
244 #[test]
245 fn an_unedited_view_reads_straight_through() {
246 let base = doc();
247 let edit = EditDoc::new(&base);
248 let catalog = edit.fetch(ObjRef::new(1, 0)).expect("catalog");
249 assert!(catalog.as_dict().is_some());
250 assert!(edit.edited().next().is_none());
251 }
252
253 #[test]
255 fn added_objects_take_fresh_numbers() {
256 let base = doc();
257 let mut edit = EditDoc::new(&base);
258 let first = edit.add(Object::Int(1));
259 let second = edit.add(Object::Int(2));
260 assert!(first.num > base.xref().last_object_number());
261 assert_eq!(second.num, first.num + 1);
262 assert_eq!(first.generation, 0);
263 assert_eq!(*edit.fetch(first).expect("added"), Object::Int(1));
264 }
265
266 #[test]
267 fn the_overlay_answers_before_the_base() {
268 let base = doc();
269 let mut edit = EditDoc::new(&base);
270 let page = ObjRef::new(3, 0);
271 assert!(edit.fetch(page).expect("page").as_dict().is_some());
272 edit.replace(page, Object::Int(99));
273 assert_eq!(*edit.fetch(page).expect("replaced"), Object::Int(99));
274 assert!(base.fetch(page).expect("page").as_dict().is_some());
276 }
277
278 #[test]
281 fn a_removed_object_reads_as_null() {
282 let base = doc();
283 let mut edit = EditDoc::new(&base);
284 let page = ObjRef::new(3, 0);
285 edit.remove(page);
286 assert!(edit.fetch(page).expect("null").is_null());
287 assert!(edit.is_removed(3));
288 assert!(!edit.is_edited(3));
289 }
290
291 #[test]
292 fn replacing_a_removed_object_brings_it_back() {
293 let base = doc();
294 let mut edit = EditDoc::new(&base);
295 let page = ObjRef::new(3, 0);
296 edit.remove(page);
297 edit.replace(page, Object::Int(7));
298 assert!(!edit.is_removed(3));
299 assert_eq!(*edit.fetch(page).expect("back"), Object::Int(7));
300 }
301
302 #[test]
303 fn edits_come_back_in_ascending_number_order() {
304 let base = doc();
305 let mut edit = EditDoc::new(&base);
306 edit.replace(ObjRef::new(9, 0), Object::Int(9));
307 edit.replace(ObjRef::new(2, 0), Object::Int(2));
308 edit.replace(ObjRef::new(5, 0), Object::Int(5));
309 let nums: Vec<u32> = edit.edited().map(|(n, _)| n).collect();
310 assert_eq!(nums, vec![2, 5, 9]);
311 }
312
313 #[test]
316 fn replacing_past_the_end_moves_the_allocator() {
317 let base = doc();
318 let mut edit = EditDoc::new(&base);
319 edit.replace(ObjRef::new(100, 0), Object::Int(1));
320 assert_eq!(edit.add(Object::Int(2)).num, 101);
321 assert_eq!(edit.last_object_number(), 101);
322 }
323}