Skip to main content

pdf_lib_rs/core/objects/
pdf_dict.rs

1use std::fmt;
2use std::collections::BTreeMap;
3use crate::core::syntax::CharCodes;
4use super::pdf_name::PdfName;
5use super::pdf_object::{PdfObject, PdfObjectTrait};
6
7/// A PDF dictionary object, e.g., `<< /Type /Page /MediaBox [ 0 0 612 792 ] >>`.
8///
9/// Uses BTreeMap for deterministic key ordering (sorted alphabetically).
10#[derive(Debug, Clone)]
11pub struct PdfDict {
12    dict: BTreeMap<String, (PdfName, PdfObject)>,
13}
14
15impl PdfDict {
16    pub fn new() -> Self {
17        PdfDict {
18            dict: BTreeMap::new(),
19        }
20    }
21
22    pub fn set(&mut self, key: PdfName, value: PdfObject) {
23        let key_str = key.as_string().to_string();
24        self.dict.insert(key_str, (key, value));
25    }
26
27    pub fn get(&self, key: &PdfName) -> Option<&PdfObject> {
28        self.dict.get(key.as_string()).map(|(_, v)| v)
29    }
30
31    pub fn has(&self, key: &PdfName) -> bool {
32        self.dict.contains_key(key.as_string())
33    }
34
35    pub fn delete(&mut self, key: &PdfName) {
36        self.dict.remove(key.as_string());
37    }
38
39    pub fn keys(&self) -> Vec<&PdfName> {
40        self.dict.values().map(|(k, _)| k).collect()
41    }
42
43    pub fn values(&self) -> Vec<&PdfObject> {
44        self.dict.values().map(|(_, v)| v).collect()
45    }
46
47    pub fn entries(&self) -> Vec<(&PdfName, &PdfObject)> {
48        self.dict.values().map(|(k, v)| (k, v)).collect()
49    }
50
51    pub fn len(&self) -> usize {
52        self.dict.len()
53    }
54
55    pub fn is_empty(&self) -> bool {
56        self.dict.is_empty()
57    }
58}
59
60impl Default for PdfDict {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66impl PartialEq for PdfDict {
67    fn eq(&self, _other: &Self) -> bool {
68        false
69    }
70}
71
72impl fmt::Display for PdfDict {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        writeln!(f, "<<")?;
75        for (key, value) in self.dict.values() {
76            writeln!(f, "{} {}", key, value)?;
77        }
78        write!(f, ">>")
79    }
80}
81
82impl PdfObjectTrait for PdfDict {
83    fn size_in_bytes(&self) -> usize {
84        // "<<\n" + entries + ">>"
85        let mut size = 5; // "<<\n" + ">>"
86        for (key, value) in self.dict.values() {
87            size += key.size_in_bytes() + 1 + value.size_in_bytes() + 1; // key + space + value + newline
88        }
89        size
90    }
91
92    fn copy_bytes_into(&self, buffer: &mut [u8], offset: usize) -> usize {
93        let initial_offset = offset;
94        let mut off = offset;
95
96        buffer[off] = CharCodes::LessThan;
97        off += 1;
98        buffer[off] = CharCodes::LessThan;
99        off += 1;
100        buffer[off] = CharCodes::Newline;
101        off += 1;
102
103        for (key, value) in self.dict.values() {
104            off += key.copy_bytes_into(buffer, off);
105            buffer[off] = CharCodes::Space;
106            off += 1;
107            off += value.copy_bytes_into(buffer, off);
108            buffer[off] = CharCodes::Newline;
109            off += 1;
110        }
111
112        buffer[off] = CharCodes::GreaterThan;
113        off += 1;
114        buffer[off] = CharCodes::GreaterThan;
115        off += 1;
116
117        off - initial_offset
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crate::core::objects::PdfNumber;
125
126    #[test]
127    fn can_set_and_get() {
128        let mut dict = PdfDict::new();
129        let key = PdfName::of("Type");
130        dict.set(key.clone(), PdfObject::Name(PdfName::of("Page")));
131        assert!(dict.has(&PdfName::of("Type")));
132        assert!(!dict.has(&PdfName::of("Missing")));
133    }
134
135    #[test]
136    fn can_delete() {
137        let mut dict = PdfDict::new();
138        dict.set(
139            PdfName::of("Foo"),
140            PdfObject::Number(PdfNumber::of(42.0)),
141        );
142        assert!(dict.has(&PdfName::of("Foo")));
143        dict.delete(&PdfName::of("Foo"));
144        assert!(!dict.has(&PdfName::of("Foo")));
145    }
146
147    #[test]
148    fn can_enumerate_entries() {
149        let mut dict = PdfDict::new();
150        dict.set(PdfName::of("A"), PdfObject::Number(PdfNumber::of(1.0)));
151        dict.set(PdfName::of("B"), PdfObject::Number(PdfNumber::of(2.0)));
152        assert_eq!(dict.len(), 2);
153        assert_eq!(dict.keys().len(), 2);
154    }
155
156    #[test]
157    fn can_be_serialized() {
158        let mut dict = PdfDict::new();
159        dict.set(PdfName::of("Type"), PdfObject::Name(PdfName::of("Page")));
160        let size = dict.size_in_bytes();
161        let mut buffer = vec![0u8; size];
162        dict.copy_bytes_into(&mut buffer, 0);
163        let result = String::from_utf8(buffer).unwrap();
164        assert!(result.starts_with("<<\n"));
165        assert!(result.ends_with(">>"));
166        assert!(result.contains("/Type /Page"));
167    }
168}