Skip to main content

oxidize_pdf/objects/
primitive.rs

1use crate::objects::Dictionary;
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub struct ObjectId {
6    number: u32,
7    generation: u16,
8}
9
10impl ObjectId {
11    pub fn new(number: u32, generation: u16) -> Self {
12        Self { number, generation }
13    }
14
15    pub fn number(&self) -> u32 {
16        self.number
17    }
18
19    pub fn generation(&self) -> u16 {
20        self.generation
21    }
22}
23
24impl fmt::Display for ObjectId {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        write!(f, "{} {} R", self.number, self.generation)
27    }
28}
29
30#[derive(Debug, Clone, PartialEq)]
31pub enum Object {
32    Null,
33    Boolean(bool),
34    Integer(i64),
35    Real(f64),
36    String(String),
37    /// Binary string — arbitrary bytes written as PDF hex string `<AABB...>`
38    /// Used for encryption hashes (/U, /O, /UE, /OE) and file IDs where
39    /// byte-perfect fidelity is required (ISO 32000-1 §7.3.4.3).
40    ByteString(Vec<u8>),
41    Name(String),
42    Array(Vec<Object>),
43    Dictionary(Dictionary),
44    Stream(Dictionary, Vec<u8>),
45    Reference(ObjectId),
46}
47
48impl Object {
49    pub fn is_null(&self) -> bool {
50        matches!(self, Object::Null)
51    }
52
53    pub fn as_bool(&self) -> Option<bool> {
54        match self {
55            Object::Boolean(b) => Some(*b),
56            _ => None,
57        }
58    }
59
60    pub fn as_integer(&self) -> Option<i64> {
61        match self {
62            Object::Integer(i) => Some(*i),
63            _ => None,
64        }
65    }
66
67    pub fn as_real(&self) -> Option<f64> {
68        match self {
69            Object::Real(f) => Some(*f),
70            Object::Integer(i) => Some(*i as f64),
71            _ => None,
72        }
73    }
74
75    pub fn as_string(&self) -> Option<&str> {
76        match self {
77            Object::String(s) => Some(s),
78            _ => None,
79        }
80    }
81
82    pub fn as_byte_string(&self) -> Option<&[u8]> {
83        match self {
84            Object::ByteString(b) => Some(b),
85            _ => None,
86        }
87    }
88
89    pub fn as_name(&self) -> Option<&str> {
90        match self {
91            Object::Name(n) => Some(n),
92            _ => None,
93        }
94    }
95
96    pub fn as_array(&self) -> Option<&Vec<Object>> {
97        match self {
98            Object::Array(arr) => Some(arr),
99            _ => None,
100        }
101    }
102
103    pub fn as_dict(&self) -> Option<&Dictionary> {
104        match self {
105            Object::Dictionary(dict) => Some(dict),
106            _ => None,
107        }
108    }
109}
110
111impl From<bool> for Object {
112    fn from(b: bool) -> Self {
113        Object::Boolean(b)
114    }
115}
116
117impl From<i32> for Object {
118    fn from(i: i32) -> Self {
119        Object::Integer(i as i64)
120    }
121}
122
123impl From<i64> for Object {
124    fn from(i: i64) -> Self {
125        Object::Integer(i)
126    }
127}
128
129impl From<f32> for Object {
130    fn from(f: f32) -> Self {
131        Object::Real(f as f64)
132    }
133}
134
135impl From<f64> for Object {
136    fn from(f: f64) -> Self {
137        Object::Real(f)
138    }
139}
140
141impl From<String> for Object {
142    fn from(s: String) -> Self {
143        Object::String(s)
144    }
145}
146
147impl From<&str> for Object {
148    fn from(s: &str) -> Self {
149        Object::String(s.to_string())
150    }
151}
152
153impl From<Vec<Object>> for Object {
154    fn from(v: Vec<Object>) -> Self {
155        Object::Array(v)
156    }
157}
158
159impl From<Dictionary> for Object {
160    fn from(d: Dictionary) -> Self {
161        Object::Dictionary(d)
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_object_id_new() {
171        let id = ObjectId::new(42, 5);
172        assert_eq!(id.number(), 42);
173        assert_eq!(id.generation(), 5);
174    }
175
176    #[test]
177    fn test_object_id_display() {
178        let id = ObjectId::new(10, 0);
179        assert_eq!(format!("{id}"), "10 0 R");
180
181        let id2 = ObjectId::new(999, 65535);
182        assert_eq!(format!("{id2}"), "999 65535 R");
183    }
184
185    #[test]
186    fn test_object_id_equality() {
187        let id1 = ObjectId::new(1, 0);
188        let id2 = ObjectId::new(1, 0);
189        let id3 = ObjectId::new(2, 0);
190        let id4 = ObjectId::new(1, 1);
191
192        assert_eq!(id1, id2);
193        assert_ne!(id1, id3);
194        assert_ne!(id1, id4);
195    }
196
197    #[test]
198    fn test_object_null() {
199        let obj = Object::Null;
200        assert!(obj.is_null());
201        assert!(obj.as_bool().is_none());
202        assert!(obj.as_integer().is_none());
203        assert!(obj.as_real().is_none());
204        assert!(obj.as_string().is_none());
205        assert!(obj.as_name().is_none());
206        assert!(obj.as_array().is_none());
207        assert!(obj.as_dict().is_none());
208    }
209
210    #[test]
211    fn test_object_boolean() {
212        let obj_true = Object::Boolean(true);
213        let obj_false = Object::Boolean(false);
214
215        assert!(!obj_true.is_null());
216        assert_eq!(obj_true.as_bool(), Some(true));
217        assert_eq!(obj_false.as_bool(), Some(false));
218        assert!(obj_true.as_integer().is_none());
219    }
220
221    #[test]
222    fn test_object_integer() {
223        let obj = Object::Integer(42);
224
225        assert_eq!(obj.as_integer(), Some(42));
226        assert_eq!(obj.as_real(), Some(42.0));
227        assert!(obj.as_bool().is_none());
228        assert!(obj.as_string().is_none());
229    }
230
231    #[test]
232    fn test_object_real() {
233        let obj = Object::Real(3.14159);
234
235        assert_eq!(obj.as_real(), Some(3.14159));
236        assert!(obj.as_integer().is_none());
237        assert!(obj.as_bool().is_none());
238    }
239
240    #[test]
241    fn test_object_string() {
242        let obj = Object::String("Hello PDF".to_string());
243
244        assert_eq!(obj.as_string(), Some("Hello PDF"));
245        assert!(obj.as_name().is_none());
246        assert!(obj.as_integer().is_none());
247    }
248
249    #[test]
250    fn test_object_name() {
251        let obj = Object::Name("Type".to_string());
252
253        assert_eq!(obj.as_name(), Some("Type"));
254        assert!(obj.as_string().is_none());
255        assert!(obj.as_integer().is_none());
256    }
257
258    #[test]
259    fn test_object_array() {
260        let arr = vec![Object::Integer(1), Object::Integer(2), Object::Integer(3)];
261        let obj = Object::Array(arr.clone());
262
263        assert_eq!(obj.as_array(), Some(&arr));
264        assert!(obj.as_dict().is_none());
265    }
266
267    #[test]
268    fn test_object_dictionary() {
269        let mut dict = Dictionary::new();
270        dict.set("Key", "Value");
271        let obj = Object::Dictionary(dict.clone());
272
273        assert_eq!(obj.as_dict(), Some(&dict));
274        assert!(obj.as_array().is_none());
275    }
276
277    #[test]
278    fn test_object_stream() {
279        let mut dict = Dictionary::new();
280        dict.set("Length", 5);
281        let data = vec![1, 2, 3, 4, 5];
282        let obj = Object::Stream(dict, data);
283
284        // Stream doesn't have as_stream method, but we can pattern match
285        if let Object::Stream(d, data) = obj {
286            assert_eq!(d.get("Length"), Some(&Object::Integer(5)));
287            assert_eq!(data.len(), 5);
288        } else {
289            panic!("Expected Stream object");
290        }
291    }
292
293    #[test]
294    fn test_object_reference() {
295        let id = ObjectId::new(10, 0);
296        let obj = Object::Reference(id);
297
298        // Reference doesn't have as_reference method, but we can pattern match
299        if let Object::Reference(ref_id) = obj {
300            assert_eq!(ref_id, id);
301        } else {
302            panic!("Expected Reference object");
303        }
304    }
305
306    #[test]
307    fn test_from_bool() {
308        let obj: Object = true.into();
309        assert_eq!(obj, Object::Boolean(true));
310
311        let obj2: Object = false.into();
312        assert_eq!(obj2, Object::Boolean(false));
313    }
314
315    #[test]
316    fn test_from_integers() {
317        let obj: Object = 42i32.into();
318        assert_eq!(obj, Object::Integer(42));
319
320        let obj2: Object = 9999i64.into();
321        assert_eq!(obj2, Object::Integer(9999));
322
323        let obj3: Object = (-100i32).into();
324        assert_eq!(obj3, Object::Integer(-100));
325    }
326
327    #[test]
328    fn test_from_floats() {
329        let obj: Object = std::f32::consts::PI.into();
330        if let Object::Real(val) = obj {
331            assert!((val - std::f64::consts::PI).abs() < 0.001);
332        } else {
333            panic!("Expected Real object");
334        }
335
336        let obj2: Object = 2.71828f64.into();
337        assert_eq!(obj2, Object::Real(2.71828));
338    }
339
340    #[test]
341    fn test_from_strings() {
342        let obj: Object = "Hello".into();
343        assert_eq!(obj, Object::String("Hello".to_string()));
344
345        let obj2: Object = String::from("World").into();
346        assert_eq!(obj2, Object::String("World".to_string()));
347    }
348
349    #[test]
350    fn test_from_vec() {
351        let vec = vec![Object::Integer(1), Object::Integer(2)];
352        let obj: Object = vec.clone().into();
353        assert_eq!(obj, Object::Array(vec));
354    }
355
356    #[test]
357    fn test_from_dictionary() {
358        let mut dict = Dictionary::new();
359        dict.set("Test", 123);
360        let obj: Object = dict.clone().into();
361
362        if let Object::Dictionary(d) = obj {
363            assert_eq!(d.get("Test"), Some(&Object::Integer(123)));
364        } else {
365            panic!("Expected Dictionary object");
366        }
367    }
368
369    #[test]
370    fn test_object_equality() {
371        assert_eq!(Object::Null, Object::Null);
372        assert_eq!(Object::Boolean(true), Object::Boolean(true));
373        assert_ne!(Object::Boolean(true), Object::Boolean(false));
374        assert_eq!(Object::Integer(42), Object::Integer(42));
375        assert_ne!(Object::Integer(42), Object::Integer(43));
376        assert_eq!(
377            Object::String("A".to_string()),
378            Object::String("A".to_string())
379        );
380        assert_ne!(
381            Object::String("A".to_string()),
382            Object::String("B".to_string())
383        );
384    }
385}