pdf_lib_rs/core/objects/
pdf_dict.rs1use std::fmt;
2use std::collections::BTreeMap;
3use crate::core::syntax::CharCodes;
4use super::pdf_name::PdfName;
5use super::pdf_object::{PdfObject, PdfObjectTrait};
6
7#[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 let mut size = 5; for (key, value) in self.dict.values() {
87 size += key.size_in_bytes() + 1 + value.size_in_bytes() + 1; }
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}