Skip to main content

pdf_lib_rs/core/
context.rs

1use std::collections::HashMap;
2use crate::core::document::PdfHeader;
3use crate::core::objects::{PdfObject, PdfRef};
4
5/// PdfContext holds all indirect objects in a PDF document.
6/// It is the central registry for the document's object graph.
7#[derive(Debug)]
8pub struct PdfContext {
9    pub largest_object_number: u32,
10    pub header: PdfHeader,
11    pub trailer_info: TrailerInfo,
12    indirect_objects: HashMap<PdfRefKey, PdfObject>,
13}
14
15/// Key for storing PdfRef in a HashMap.
16#[derive(Debug, Clone, Hash, PartialEq, Eq)]
17struct PdfRefKey {
18    object_number: u32,
19    generation_number: u16,
20}
21
22impl From<&PdfRef> for PdfRefKey {
23    fn from(r: &PdfRef) -> Self {
24        PdfRefKey {
25            object_number: r.object_number,
26            generation_number: r.generation_number,
27        }
28    }
29}
30
31/// Trailer information.
32#[derive(Debug, Default)]
33pub struct TrailerInfo {
34    pub root: Option<PdfObject>,
35    pub encrypt: Option<PdfObject>,
36    pub info: Option<PdfObject>,
37    pub id: Option<PdfObject>,
38}
39
40impl PdfContext {
41    pub fn create() -> Self {
42        PdfContext {
43            largest_object_number: 0,
44            header: PdfHeader::for_version(1, 7),
45            trailer_info: TrailerInfo::default(),
46            indirect_objects: HashMap::new(),
47        }
48    }
49
50    /// Assign an object to a reference.
51    pub fn assign(&mut self, pdf_ref: &PdfRef, object: PdfObject) {
52        if pdf_ref.object_number > self.largest_object_number {
53            self.largest_object_number = pdf_ref.object_number;
54        }
55        self.indirect_objects.insert(PdfRefKey::from(pdf_ref), object);
56    }
57
58    /// Get the next available reference.
59    pub fn next_ref(&mut self) -> PdfRef {
60        self.largest_object_number += 1;
61        PdfRef::of(self.largest_object_number, 0)
62    }
63
64    /// Register an object and return its reference.
65    pub fn register(&mut self, object: PdfObject) -> PdfRef {
66        let pdf_ref = self.next_ref();
67        self.assign(&pdf_ref, object);
68        pdf_ref
69    }
70
71    /// Look up an indirect object by reference.
72    pub fn lookup(&self, pdf_ref: &PdfRef) -> Option<&PdfObject> {
73        self.indirect_objects.get(&PdfRefKey::from(pdf_ref))
74    }
75
76    /// Delete an indirect object.
77    pub fn delete(&mut self, pdf_ref: &PdfRef) -> bool {
78        self.indirect_objects.remove(&PdfRefKey::from(pdf_ref)).is_some()
79    }
80
81    /// Enumerate all indirect objects, sorted by object number.
82    pub fn enumerate_indirect_objects(&self) -> Vec<(PdfRef, &PdfObject)> {
83        let mut entries: Vec<_> = self
84            .indirect_objects
85            .iter()
86            .map(|(k, v)| (PdfRef::of(k.object_number, k.generation_number), v))
87            .collect();
88        entries.sort_by_key(|(r, _)| r.object_number);
89        entries
90    }
91
92    /// Get number of indirect objects.
93    pub fn object_count(&self) -> usize {
94        self.indirect_objects.len()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::core::objects::{PdfNumber, PdfName};
102
103    #[test]
104    fn can_create_context() {
105        let ctx = PdfContext::create();
106        assert_eq!(ctx.largest_object_number, 0);
107        assert_eq!(ctx.object_count(), 0);
108    }
109
110    #[test]
111    fn can_assign_and_lookup() {
112        let mut ctx = PdfContext::create();
113        let r = PdfRef::of(1, 0);
114        ctx.assign(&r, PdfObject::Number(PdfNumber::of(42.0)));
115        assert!(ctx.lookup(&r).is_some());
116        assert_eq!(ctx.largest_object_number, 1);
117    }
118
119    #[test]
120    fn can_register_objects() {
121        let mut ctx = PdfContext::create();
122        let r1 = ctx.register(PdfObject::Name(PdfName::of("Foo")));
123        let r2 = ctx.register(PdfObject::Name(PdfName::of("Bar")));
124        assert_eq!(r1.object_number, 1);
125        assert_eq!(r2.object_number, 2);
126        assert_eq!(ctx.object_count(), 2);
127    }
128
129    #[test]
130    fn can_delete_objects() {
131        let mut ctx = PdfContext::create();
132        let r = ctx.register(PdfObject::Number(PdfNumber::of(1.0)));
133        assert!(ctx.delete(&r));
134        assert!(ctx.lookup(&r).is_none());
135    }
136
137    #[test]
138    fn can_enumerate_objects() {
139        let mut ctx = PdfContext::create();
140        ctx.register(PdfObject::Number(PdfNumber::of(1.0)));
141        ctx.register(PdfObject::Number(PdfNumber::of(2.0)));
142        let objects = ctx.enumerate_indirect_objects();
143        assert_eq!(objects.len(), 2);
144        assert_eq!(objects[0].0.object_number, 1);
145        assert_eq!(objects[1].0.object_number, 2);
146    }
147}