Skip to main content

hayro_syntax/object/
mod.rs

1//! Parsing and reading from PDF objects.
2
3pub use crate::object::array::Array;
4pub use crate::object::date::DateTime;
5pub use crate::object::dict::Dict;
6pub use crate::object::name::Name;
7use crate::object::name::skip_name_like;
8pub use crate::object::null::Null;
9pub use crate::object::number::Number;
10pub use crate::object::rect::Rect;
11pub use crate::object::r#ref::{MaybeRef, ObjRef};
12pub use crate::object::stream::Stream;
13pub use crate::object::string::String;
14use crate::reader::Reader;
15use crate::reader::{Readable, ReaderContext, ReaderExt, Skippable};
16use core::fmt::{Debug, Display, Formatter};
17
18const MAX_OBJECT_NESTING_DEPTH: usize = 64;
19
20mod bool;
21mod date;
22mod null;
23mod number;
24mod rect;
25mod r#ref;
26mod string;
27mod tuple;
28
29pub(crate) mod indirect;
30pub(crate) mod name;
31
32pub mod array;
33pub mod dict;
34pub mod stream;
35
36/// A trait for PDF objects.
37pub(crate) trait ObjectLike<'a>: TryFrom<Object<'a>> + Readable<'a> + Debug + Clone {}
38
39/// A trait for PDF objects that can be borrowed directly from an [`Object`].
40pub(crate) trait ObjectRefLike<'a>: Sized {
41    fn cast_ref<'b>(obj: &'b Object<'a>) -> Option<&'b Self>;
42}
43
44/// A primitive PDF object.
45#[derive(Debug, Clone, PartialEq)]
46pub enum Object<'a> {
47    /// A null object.
48    Null(Null),
49    /// A boolean object.
50    Boolean(bool),
51    /// A number object.
52    Number(Number),
53    /// A string object.
54    String(String<'a>),
55    /// A name object.
56    Name(Name<'a>),
57    /// A dict object.
58    Dict(Dict<'a>),
59    /// An array object.
60    Array(Array<'a>),
61    /// A stream object.
62    // Can only be an indirect object in theory and thus comes with some caveats,
63    // but we just treat it the same.
64    Stream(Stream<'a>),
65}
66
67impl Display for Object<'_> {
68    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
69        match self {
70            Self::Null(value) => Display::fmt(value, f),
71            Self::Boolean(value) => Display::fmt(value, f),
72            Self::Number(value) => Display::fmt(value, f),
73            Self::String(value) => Display::fmt(value, f),
74            Self::Name(value) => Display::fmt(value, f),
75            Self::Dict(value) => Display::fmt(value, f),
76            Self::Array(value) => Display::fmt(value, f),
77            Self::Stream(value) => Display::fmt(value, f),
78        }
79    }
80}
81
82impl<'a> Object<'a> {
83    /// Try casting the object to a specific subtype.
84    pub(crate) fn cast<T>(self) -> Option<T>
85    where
86        T: ObjectLike<'a>,
87    {
88        self.try_into().ok()
89    }
90
91    /// Try casting the object to a reference of the specific type.
92    pub(crate) fn cast_ref<T>(&self) -> Option<&T>
93    where
94        T: ObjectRefLike<'a>,
95    {
96        T::cast_ref(self)
97    }
98
99    /// Try casting the object to a dict.
100    #[inline(always)]
101    pub fn into_dict(self) -> Option<Dict<'a>> {
102        self.cast()
103    }
104
105    /// Try casting the object to a name.
106    #[inline(always)]
107    pub fn into_name(self) -> Option<Name<'a>> {
108        self.cast()
109    }
110
111    /// Try casting the object to the null object.
112    #[inline(always)]
113    pub fn into_null(self) -> Option<Null> {
114        self.cast()
115    }
116
117    /// Try casting the object to a bool.
118    #[inline(always)]
119    pub fn into_bool(self) -> Option<bool> {
120        self.cast()
121    }
122
123    /// Try casting the object to a string.
124    #[inline(always)]
125    pub fn into_string(self) -> Option<String<'a>> {
126        self.cast()
127    }
128
129    /// Try casting the object to a stream.
130    #[inline(always)]
131    pub fn into_stream(self) -> Option<Stream<'a>> {
132        self.cast()
133    }
134
135    /// Try casting the object to an array.
136    #[inline(always)]
137    pub fn into_array(self) -> Option<Array<'a>> {
138        self.cast()
139    }
140
141    /// Try casting the object to a u8.
142    #[inline(always)]
143    pub fn into_u8(self) -> Option<u8> {
144        self.cast()
145    }
146
147    /// Try casting the object to a u16.
148    #[inline(always)]
149    pub fn into_u16(self) -> Option<u16> {
150        self.cast()
151    }
152
153    /// Try casting the object to a f32.
154    #[inline(always)]
155    pub fn into_f32(self) -> Option<f32> {
156        self.cast()
157    }
158
159    /// Try casting the object to a i32.
160    #[inline(always)]
161    pub fn into_i32(self) -> Option<i32> {
162        self.cast()
163    }
164
165    /// Try casting the object to a number.
166    #[inline(always)]
167    pub fn into_number(self) -> Option<Number> {
168        self.cast()
169    }
170}
171
172impl<'a> ObjectLike<'a> for Object<'a> {}
173impl<'a> ObjectRefLike<'a> for Object<'a> {
174    fn cast_ref(obj: &Self) -> Option<&Self> {
175        Some(obj)
176    }
177}
178
179impl Skippable for Object<'_> {
180    fn skip(r: &mut Reader<'_>, is_content_stream: bool) -> Option<()> {
181        Self::skip_with_depth(r, is_content_stream, 0)
182    }
183
184    fn skip_with_depth(r: &mut Reader<'_>, is_content_stream: bool, depth: usize) -> Option<()> {
185        match r.peek_byte()? {
186            b'n' => Null::skip(r, is_content_stream),
187            b't' | b'f' => bool::skip(r, is_content_stream),
188            b'/' => Name::skip(r, is_content_stream),
189            b'<' => match r.peek_bytes(2)? {
190                // A stream can never appear in a dict/array, so it should never be skipped.
191                b"<<" => Dict::skip_with_depth(r, is_content_stream, depth),
192                _ => String::skip(r, is_content_stream),
193            },
194            b'(' => String::skip(r, is_content_stream),
195            b'.' | b'+' | b'-' | b'0'..=b'9' => Number::skip(r, is_content_stream),
196            b'[' => Array::skip_with_depth(r, is_content_stream, depth),
197            // See test case operator-in-TJ-array-0: Be lenient and skip content operators in
198            // array
199            _ => skip_name_like(r, false),
200        }
201    }
202}
203
204impl<'a> Readable<'a> for Object<'a> {
205    fn read(r: &mut Reader<'a>, ctx: &ReaderContext<'a>) -> Option<Self> {
206        let object = match r.peek_byte()? {
207            b'n' => Self::Null(Null::read(r, ctx)?),
208            b't' | b'f' => Self::Boolean(bool::read(r, ctx)?),
209            b'/' => Self::Name(Name::read(r, ctx)?),
210            b'<' => match r.peek_bytes(2)? {
211                b"<<" => {
212                    let mut cloned = r.clone();
213                    let dict = Dict::read(&mut cloned, ctx)?;
214                    cloned.skip_white_spaces_and_comments();
215
216                    if cloned.forward_tag(b"stream").is_some() {
217                        Object::Stream(Stream::read(r, ctx)?)
218                    } else {
219                        r.jump(cloned.offset());
220
221                        Object::Dict(dict)
222                    }
223                }
224                _ => Self::String(String::read(r, ctx)?),
225            },
226            b'(' => Self::String(String::read(r, ctx)?),
227            b'.' | b'+' | b'-' | b'0'..=b'9' => Self::Number(Number::read(r, ctx)?),
228            b'[' => Self::Array(Array::read(r, ctx)?),
229            // See the comment in `skip`.
230            _ => {
231                skip_name_like(r, false)?;
232                Self::Null(Null)
233            }
234        };
235
236        Some(object)
237    }
238}
239
240/// A trait for objects that can be parsed from a simple byte stream.
241pub trait FromBytes<'a>: Sized {
242    /// Try to read the object from the given bytes.
243    fn from_bytes(b: &'a [u8]) -> Option<Self>;
244}
245
246impl<'a, T: Readable<'a>> FromBytes<'a> for T {
247    fn from_bytes(b: &'a [u8]) -> Option<Self> {
248        Self::from_bytes_impl(b)
249    }
250}
251
252/// An identifier for a PDF object.
253#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
254pub struct ObjectIdentifier {
255    /// The object number.
256    pub obj_number: i32,
257    /// The generation number.
258    pub gen_number: i32,
259}
260
261impl ObjectIdentifier {
262    /// Create a new `ObjectIdentifier`.
263    pub fn new(obj_number: i32, gen_number: i32) -> Self {
264        Self {
265            obj_number,
266            gen_number,
267        }
268    }
269}
270
271impl Readable<'_> for ObjectIdentifier {
272    fn read(r: &mut Reader<'_>, _: &ReaderContext<'_>) -> Option<Self> {
273        let obj_number = r.read_without_context::<i32>()?;
274        r.skip_white_spaces_and_comments();
275        let gen_number = r.read_without_context::<i32>()?;
276        r.skip_white_spaces_and_comments();
277        r.forward_tag(b"obj")?;
278
279        Some(Self {
280            obj_number,
281            gen_number,
282        })
283    }
284}
285
286impl Skippable for ObjectIdentifier {
287    fn skip(r: &mut Reader<'_>, _: bool) -> Option<()> {
288        r.skip::<i32>(false)?;
289        r.skip_white_spaces_and_comments();
290        r.skip::<i32>(false)?;
291        r.skip_white_spaces_and_comments();
292        r.forward_tag(b"obj")?;
293
294        Some(())
295    }
296}
297
298/// A convenience function that extracts a dict and a stream from an object.
299/// If the object is just a dictionary, it will return `None` for the stream.
300/// If the object is a stream, it will return its dictionary as well as the stream
301/// itself.
302pub fn dict_or_stream<'a, 'b>(
303    obj: &'b Object<'a>,
304) -> Option<(&'b Dict<'a>, Option<&'b Stream<'a>>)> {
305    if let Some(stream) = obj.cast_ref::<Stream<'a>>() {
306        Some((stream.dict(), Some(stream)))
307    } else {
308        obj.cast_ref::<Dict<'a>>().map(|dict| (dict, None))
309    }
310}
311
312mod macros {
313    macro_rules! object {
314        ($t:ident $(<$l:lifetime>),*, $s:ident) => {
315            impl<'a> TryFrom<Object<'a>> for $t$(<$l>),* {
316                type Error = ();
317
318                fn try_from(value: Object<'a>) -> core::result::Result<Self, Self::Error> {
319                    match value {
320                        Object::$s(b) => Ok(b),
321                        _ => Err(()),
322                    }
323                }
324            }
325
326            impl<'a> crate::object::ObjectLike<'a> for $t$(<$l>),* {}
327
328            impl<'a> crate::object::ObjectRefLike<'a> for $t$(<$l>),* {
329                fn cast_ref<'b>(obj: &'b crate::object::Object<'a>) -> Option<&'b Self> {
330                    match obj {
331                        crate::object::Object::$s(b) => Some(b),
332                        _ => None,
333                    }
334                }
335            }
336        };
337    }
338
339    pub(crate) use object;
340}
341
342#[cfg(test)]
343mod tests {
344    use crate::object::{Array, Dict, Name, Null, Number, Object, Stream, String};
345    use crate::reader::Reader;
346    use crate::reader::{ReaderContext, ReaderExt};
347
348    fn object_impl(data: &[u8]) -> Option<Object<'_>> {
349        let mut r = Reader::new(data);
350        r.read_with_context::<Object<'_>>(&ReaderContext::dummy())
351    }
352
353    #[test]
354    fn deeply_nested_arrays() {
355        let data = format!("{}0{}", "[".repeat(5000), "]".repeat(5000));
356        assert!(object_impl(data.as_bytes()).is_none());
357    }
358
359    #[test]
360    fn deeply_nested_dictionaries() {
361        let data = format!("{}0{}", "<< /X ".repeat(5000), " >>".repeat(5000));
362        assert!(object_impl(data.as_bytes()).is_none());
363    }
364
365    #[test]
366    fn null() {
367        assert!(matches!(object_impl(b"null").unwrap(), Object::Null(_)));
368    }
369
370    #[test]
371    fn bool() {
372        assert!(matches!(object_impl(b"true").unwrap(), Object::Boolean(_)));
373    }
374
375    #[test]
376    fn number() {
377        assert!(matches!(object_impl(b"34.5").unwrap(), Object::Number(_)));
378    }
379
380    #[test]
381    fn string_1() {
382        assert!(matches!(object_impl(b"(Hi)").unwrap(), Object::String(_)));
383    }
384
385    #[test]
386    fn string_2() {
387        assert!(matches!(object_impl(b"<34>").unwrap(), Object::String(_)));
388    }
389
390    #[test]
391    fn name() {
392        assert!(matches!(object_impl(b"/Name").unwrap(), Object::Name(_)));
393    }
394
395    #[test]
396    fn dict() {
397        assert!(matches!(
398            object_impl(b"<</Entry 45>>").unwrap(),
399            Object::Dict(_)
400        ));
401    }
402
403    #[test]
404    fn array() {
405        assert!(matches!(object_impl(b"[45]").unwrap(), Object::Array(_)));
406    }
407
408    #[test]
409    fn stream() {
410        assert!(matches!(
411            object_impl(b"<< /Length 3 >> stream\nabc\nendstream").unwrap(),
412            Object::Stream(_)
413        ));
414    }
415
416    #[test]
417    fn display() {
418        let cases: &[(&[u8], &str)] = &[
419            (b"null", "null"),
420            (b"true", "true"),
421            (b"34.5", "34.5"),
422            (b"(Hi)", "\"Hi\""),
423            (b"/Name", "/Name"),
424            (b"[45]", "[45]"),
425            (b"<</Entry 45>>", "<< /Entry 45 >>"),
426            (b"<< /Length 3 >> stream\nabc\nendstream", "Stream (len: 3)"),
427        ];
428
429        for (input, expected) in cases {
430            assert_eq!(format!("{}", object_impl(input).unwrap()), *expected);
431        }
432    }
433
434    #[test]
435    #[cfg(target_pointer_width = "64")]
436    fn object_sizes() {
437        assert_eq!(size_of::<Object<'_>>(), 40);
438        assert_eq!(size_of::<Array<'_>>(), 32);
439        assert_eq!(size_of::<Dict<'_>>(), 8);
440        assert_eq!(size_of::<Name<'_>>(), 32);
441        assert_eq!(size_of::<Null>(), 0);
442        assert_eq!(size_of::<Number>(), 16);
443        assert_eq!(size_of::<Stream<'_>>(), 24);
444        assert_eq!(size_of::<String<'_>>(), 32);
445    }
446}