Skip to main content

otf_pdf_doc/
store.rs

1//! The copy-on-write object store: an immutable base [`File`] plus an
2//! overlay of changed, added and deleted objects (docs/PLAN.md ยง4, "Overlay
3//! layer"). Reads check the overlay first; the base is never modified, which
4//! is what lets an incremental save copy its bytes unchanged.
5
6use std::collections::BTreeMap;
7use std::sync::Arc;
8
9use otf_pdf_syntax::filters::Decoded;
10use otf_pdf_syntax::xref::XrefEntry;
11use otf_pdf_syntax::{
12    Diagnostics, Dict, Error, File, LimitKind, Limits, ObjId, Object, Result, Stream, Strictness,
13};
14
15/// One overlay entry.
16#[derive(Debug, Clone)]
17pub(crate) enum Change {
18    /// The object was replaced or added.
19    Set(Arc<Object>),
20    /// The object was deleted.
21    Deleted,
22}
23
24/// Base file plus overlay.
25#[derive(Debug)]
26pub struct Store {
27    file: File,
28    overlay: BTreeMap<ObjId, Change>,
29    trailer: Dict,
30    next_num: u32,
31}
32
33impl Store {
34    pub(crate) fn new(file: File) -> Self {
35        let trailer = file.trailer().clone();
36        let highest = file.entries().keys().next_back().copied().unwrap_or(0);
37        let size = trailer
38            .get_int(b"Size")
39            .and_then(|s| u32::try_from(s).ok())
40            .unwrap_or(0);
41        Self {
42            file,
43            overlay: BTreeMap::new(),
44            trailer,
45            next_num: highest.saturating_add(1).max(size).max(1),
46        }
47    }
48
49    /// The base file.
50    pub fn file(&self) -> &File {
51        &self.file
52    }
53
54    /// The working trailer: the base trailer plus edits.
55    pub fn trailer(&self) -> &Dict {
56        &self.trailer
57    }
58
59    /// Sets a trailer entry (`/Info`, `/Root`, `/ID`). `null` removes it.
60    pub fn set_trailer_entry(&mut self, key: &str, value: Object) {
61        if value == Object::Null {
62            self.trailer.remove(key.as_bytes());
63        } else {
64            self.trailer.insert(key, value);
65        }
66    }
67
68    pub(crate) fn overlay(&self) -> &BTreeMap<ObjId, Change> {
69        &self.overlay
70    }
71
72    /// Whether anything was changed.
73    pub fn is_modified(&self) -> bool {
74        !self.overlay.is_empty() || &self.trailer != self.file.trailer()
75    }
76
77    /// The object `id`, from the overlay or the base.
78    pub fn get(&self, id: ObjId) -> Result<Arc<Object>> {
79        match self.overlay.get(&id) {
80            Some(Change::Set(obj)) => Ok(Arc::clone(obj)),
81            Some(Change::Deleted) => Err(Error::MissingObject(id)),
82            None => self.file.get(id),
83        }
84    }
85
86    /// Follows references until a direct object; missing objects and overlong
87    /// chains resolve to `null`.
88    pub fn resolve(&self, object: &Object) -> Object {
89        let mut current = object.clone();
90        for _ in 0..self.file.limits().max_depth {
91            match current {
92                Object::Ref(id) => match self.get(id) {
93                    Ok(o) => current = (*o).clone(),
94                    Err(_) => return Object::Null,
95                },
96                other => return other,
97            }
98        }
99        Object::Null
100    }
101
102    /// Resolves `object` as a dictionary (or a stream's dictionary).
103    pub fn resolve_dict(&self, object: &Object) -> Option<Dict> {
104        match self.resolve(object) {
105            Object::Dict(d) => Some(d),
106            Object::Stream(s) => Some(s.dict),
107            _ => None,
108        }
109    }
110
111    /// Decodes a stream through its filters, resolving indirect filter
112    /// parameters through the overlay.
113    pub fn decode_stream(&self, stream: &Stream) -> Result<Decoded> {
114        let mut dict = stream.dict.clone();
115        for key in [&b"Filter"[..], b"DecodeParms"] {
116            if let Some(v) = dict.get(key).cloned() {
117                let resolved = match self.resolve(&v) {
118                    Object::Array(items) => {
119                        Object::Array(items.iter().map(|i| self.resolve(i)).collect())
120                    }
121                    other => other,
122                };
123                dict.insert(key, resolved);
124            }
125        }
126        otf_pdf_syntax::filters::decode(
127            &stream.raw,
128            &otf_pdf_syntax::filters::filter_chain(&dict),
129            self.file.limits(),
130            self.file.strictness(),
131            Some(self.file.diagnostics_sink()),
132        )
133    }
134
135    /// Every live object id, base and overlay, in order.
136    pub fn object_ids(&self) -> Vec<ObjId> {
137        let mut ids: BTreeMap<u32, ObjId> = BTreeMap::new();
138        for (&num, entry) in self.file.entries() {
139            match *entry {
140                XrefEntry::InUse { generation, .. } => {
141                    ids.insert(num, ObjId::new(num, generation));
142                }
143                XrefEntry::Compressed { .. } => {
144                    ids.insert(num, ObjId::new(num, 0));
145                }
146                XrefEntry::Free => {}
147            }
148        }
149        for (id, change) in &self.overlay {
150            match change {
151                Change::Set(_) => {
152                    ids.insert(id.num, *id);
153                }
154                Change::Deleted => {
155                    ids.remove(&id.num);
156                }
157            }
158        }
159        ids.into_values().collect()
160    }
161
162    /// Replaces (or adds) object `id`.
163    pub fn set(&mut self, id: ObjId, object: Object) {
164        if id.num >= self.next_num {
165            self.next_num = id.num.saturating_add(1);
166        }
167        self.overlay.insert(id, Change::Set(Arc::new(object)));
168    }
169
170    /// Adds a new object and returns its id.
171    pub fn add(&mut self, object: Object) -> Result<ObjId> {
172        let id = self.allocate()?;
173        self.set(id, object);
174        Ok(id)
175    }
176
177    /// Reserves a new object number (generation 0) without an object yet;
178    /// [`Store::set`] fills it. Used to break cycles while copying.
179    pub fn allocate(&mut self) -> Result<ObjId> {
180        if self.next_num > self.file.limits().max_objects {
181            return Err(Error::Limit(LimitKind::Objects));
182        }
183        let id = ObjId::new(self.next_num, 0);
184        self.next_num += 1;
185        // Reserve with null so a cycle back to it during a copy resolves.
186        self.overlay.insert(id, Change::Set(Arc::new(Object::Null)));
187        Ok(id)
188    }
189
190    /// Deletes object `id`.
191    pub fn delete(&mut self, id: ObjId) {
192        self.overlay.insert(id, Change::Deleted);
193    }
194
195    /// The next object number that would be allocated.
196    pub fn next_number(&self) -> u32 {
197        self.next_num
198    }
199
200    /// The limits in force.
201    pub fn limits(&self) -> &Limits {
202        self.file.limits()
203    }
204
205    /// Whether repairs are allowed.
206    pub fn strictness(&self) -> Strictness {
207        self.file.strictness()
208    }
209
210    /// Where repairs are recorded.
211    pub fn diagnostics_sink(&self) -> &Diagnostics {
212        self.file.diagnostics_sink()
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use otf_pdf_syntax::OpenOptions;
220
221    fn store() -> Store {
222        let bytes = b"%PDF-1.7\n1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n\
223            2 0 obj << /Type /Pages /Kids [] /Count 0 >> endobj\n3 0 obj (base) endobj\n"
224            .to_vec();
225        Store::new(File::open(Arc::new(bytes), OpenOptions::default()).unwrap())
226    }
227
228    #[test]
229    fn overlay_shadows_base() {
230        let mut s = store();
231        assert!(!s.is_modified());
232        let three = ObjId::new(3, 0);
233        assert_eq!(s.get(three).unwrap().as_string(), Some(&b"base"[..]));
234        s.set(three, Object::Int(7));
235        assert_eq!(s.get(three).unwrap().as_int(), Some(7));
236        assert_eq!(s.file().get(three).unwrap().as_string(), Some(&b"base"[..]));
237        assert!(s.is_modified());
238        s.delete(three);
239        assert!(s.get(three).is_err());
240        assert_eq!(s.resolve(&Object::Ref(three)), Object::Null);
241        assert_eq!(s.object_ids(), vec![ObjId::new(1, 0), ObjId::new(2, 0)]);
242    }
243
244    #[test]
245    fn allocation_continues_after_the_base() {
246        let mut s = store();
247        let id = s.add(Object::Bool(true)).unwrap();
248        assert_eq!(id, ObjId::new(4, 0));
249        assert_eq!(s.next_number(), 5);
250        let reserved = s.allocate().unwrap();
251        assert_eq!(s.get(reserved).unwrap().as_ref(), &Object::Null);
252        s.set(ObjId::new(10, 0), Object::Null);
253        assert_eq!(s.next_number(), 11);
254        assert_eq!(s.object_ids().len(), 6);
255    }
256
257    #[test]
258    fn allocation_is_limited() {
259        let bytes = b"%PDF-1.7\n1 0 obj << /Type /Catalog >> endobj\n".to_vec();
260        let options = OpenOptions {
261            limits: Limits {
262                max_objects: 3,
263                ..Limits::default()
264            },
265            ..OpenOptions::default()
266        };
267        let mut s = Store::new(File::open(Arc::new(bytes), options).unwrap());
268        assert!(s.add(Object::Null).is_ok());
269        assert!(s.add(Object::Null).is_ok());
270        assert_eq!(s.add(Object::Null), Err(Error::Limit(LimitKind::Objects)));
271    }
272}