1use std::fmt;
8
9#[derive(Clone, PartialEq)]
11pub enum PdfObj {
12 Null,
13 Bool(bool),
14 Int(i64),
15 Real(f64),
16 Name(Vec<u8>),
18 Str(Vec<u8>),
20 Array(Vec<PdfObj>),
21 Dict(PdfDict),
22 Stream {
24 dict: PdfDict,
25 data_offset: usize,
26 data_len: usize,
27 },
28 Ref(u32, u16),
30}
31
32impl PdfObj {
33 pub fn as_int(&self) -> Option<i64> {
35 match self {
36 PdfObj::Int(n) => Some(*n),
37 _ => None,
38 }
39 }
40
41 pub fn as_f64(&self) -> Option<f64> {
43 match self {
44 PdfObj::Int(n) => Some(*n as f64),
45 PdfObj::Real(f) => Some(*f),
46 _ => None,
47 }
48 }
49
50 pub fn as_name(&self) -> Option<&[u8]> {
52 match self {
53 PdfObj::Name(n) => Some(n),
54 _ => None,
55 }
56 }
57
58 pub fn as_bool(&self) -> Option<bool> {
60 match self {
61 PdfObj::Bool(b) => Some(*b),
62 _ => None,
63 }
64 }
65
66 pub fn as_str(&self) -> Option<&[u8]> {
68 match self {
69 PdfObj::Str(s) => Some(s),
70 _ => None,
71 }
72 }
73
74 pub fn as_array(&self) -> Option<&[PdfObj]> {
76 match self {
77 PdfObj::Array(a) => Some(a),
78 _ => None,
79 }
80 }
81
82 pub fn as_dict(&self) -> Option<&PdfDict> {
84 match self {
85 PdfObj::Dict(d) => Some(d),
86 PdfObj::Stream { dict, .. } => Some(dict),
87 _ => None,
88 }
89 }
90
91 pub fn as_ref(&self) -> Option<(u32, u16)> {
93 match self {
94 PdfObj::Ref(n, g) => Some((*n, *g)),
95 _ => None,
96 }
97 }
98
99 pub fn to_ref(&self) -> Option<(u32, u16)> {
102 self.as_ref()
103 }
104}
105
106impl fmt::Debug for PdfObj {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match self {
109 PdfObj::Null => write!(f, "null"),
110 PdfObj::Bool(b) => write!(f, "{b}"),
111 PdfObj::Int(n) => write!(f, "{n}"),
112 PdfObj::Real(r) => write!(f, "{r}"),
113 PdfObj::Name(n) => write!(f, "/{}", String::from_utf8_lossy(n)),
114 PdfObj::Str(s) => write!(f, "({:?})", String::from_utf8_lossy(s)),
115 PdfObj::Array(a) => f.debug_list().entries(a.iter()).finish(),
116 PdfObj::Dict(d) => write!(f, "{d:?}"),
117 PdfObj::Stream { dict, data_len, .. } => {
118 write!(f, "{dict:?} stream({data_len} bytes)")
119 }
120 PdfObj::Ref(n, g) => write!(f, "{n} {g} R"),
121 }
122 }
123}
124
125#[derive(Clone, PartialEq, Default)]
127pub struct PdfDict(Vec<(Vec<u8>, PdfObj)>);
128
129impl PdfDict {
130 pub fn new() -> Self {
131 Self(Vec::new())
132 }
133
134 pub fn get(&self, key: &[u8]) -> Option<&PdfObj> {
136 self.0.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v)
137 }
138
139 pub fn get_name(&self, key: &[u8]) -> Option<&[u8]> {
141 self.get(key).and_then(|v| v.as_name())
142 }
143
144 pub fn get_int(&self, key: &[u8]) -> Option<i64> {
146 self.get(key).and_then(|v| v.as_int())
147 }
148
149 pub fn get_f64(&self, key: &[u8]) -> Option<f64> {
151 self.get(key).and_then(|v| v.as_f64())
152 }
153
154 pub fn get_bool(&self, key: &[u8]) -> Option<bool> {
156 self.get(key).and_then(|v| v.as_bool())
157 }
158
159 pub fn get_array(&self, key: &[u8]) -> Option<&[PdfObj]> {
161 self.get(key).and_then(|v| v.as_array())
162 }
163
164 pub fn get_dict(&self, key: &[u8]) -> Option<&PdfDict> {
166 self.get(key).and_then(|v| v.as_dict())
167 }
168
169 pub fn get_ref(&self, key: &[u8]) -> Option<(u32, u16)> {
171 self.get(key).and_then(|v| v.as_ref())
172 }
173
174 pub fn get_derefable(&self, key: &[u8]) -> Option<&PdfObj> {
176 self.get(key)
177 }
178
179 pub fn insert(&mut self, key: Vec<u8>, val: PdfObj) {
181 if self.0.len() < 100 {
185 if let Some(entry) = self.0.iter_mut().find(|(k, _)| k == &key) {
186 entry.1 = val;
187 return;
188 }
189 }
190 self.0.push((key, val));
191 }
192
193 pub fn entries(&self) -> &[(Vec<u8>, PdfObj)] {
195 &self.0
196 }
197
198 pub fn into_entries(self) -> Vec<(Vec<u8>, PdfObj)> {
200 self.0
201 }
202
203 pub fn from_entries(entries: Vec<(Vec<u8>, PdfObj)>) -> Self {
205 PdfDict(entries)
206 }
207
208 pub fn len(&self) -> usize {
210 self.0.len()
211 }
212
213 pub fn is_empty(&self) -> bool {
215 self.0.is_empty()
216 }
217}
218
219impl fmt::Debug for PdfDict {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 write!(f, "<<")?;
222 for (k, v) in &self.0 {
223 write!(f, " /{} {v:?}", String::from_utf8_lossy(k))?;
224 }
225 write!(f, " >>")
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn dict_get_and_insert() {
235 let mut d = PdfDict::new();
236 d.insert(b"Type".to_vec(), PdfObj::Name(b"Page".to_vec()));
237 d.insert(b"Count".to_vec(), PdfObj::Int(5));
238
239 assert_eq!(d.get_name(b"Type"), Some(b"Page".as_slice()));
240 assert_eq!(d.get_int(b"Count"), Some(5));
241 assert_eq!(d.get(b"Missing"), None);
242 assert_eq!(d.len(), 2);
243 }
244
245 #[test]
246 fn dict_insert_replaces() {
247 let mut d = PdfDict::new();
248 d.insert(b"Key".to_vec(), PdfObj::Int(1));
249 d.insert(b"Key".to_vec(), PdfObj::Int(2));
250 assert_eq!(d.get_int(b"Key"), Some(2));
251 assert_eq!(d.len(), 1);
252 }
253
254 #[test]
255 fn obj_as_f64_int_and_real() {
256 assert_eq!(PdfObj::Int(42).as_f64(), Some(42.0));
257 assert_eq!(PdfObj::Real(2.5).as_f64(), Some(2.5));
258 assert_eq!(PdfObj::Null.as_f64(), None);
259 }
260
261 #[test]
262 fn obj_as_dict_from_stream() {
263 let mut d = PdfDict::new();
264 d.insert(b"Length".to_vec(), PdfObj::Int(100));
265 let obj = PdfObj::Stream {
266 dict: d.clone(),
267 data_offset: 0,
268 data_len: 100,
269 };
270 assert!(obj.as_dict().is_some());
271 assert_eq!(obj.as_dict().unwrap().get_int(b"Length"), Some(100));
272 }
273}