Skip to main content

pdfrum_object/
object.rs

1//! The [`Object`] enum and the coercions every accessor is built from.
2//!
3//! A coercion that does not apply to a variant yields that variant's
4//! fallback rather than failing: an array has no string spelling, a name has
5//! no numeric value.
6
7// PDFium reaches these through virtual methods with defaults on the base
8// class — `GetString()` on an array returns `""`, `GetInteger()` on a name
9// returns `0`, and so on. Here they are exhaustive matches, so adding a
10// variant makes every coercion fail to compile until it is considered.
11
12use std::collections::HashSet;
13
14use crate::number::{fmt_int, fmt_number, narrow_to_signed32, truncate_to_signed32, widen_to_f32};
15use crate::{Array, Dict, Error, Name, PdfString, Resolve, Resolved, Stream};
16
17/// An indirect object's identity: the pair a `N G obj` header carries and a
18/// cross-reference entry keys.
19///
20/// A reference token (`12 3 R`) carries a generation too, but resolution
21/// ignores it — see [`Resolve`]. The generation is kept because the
22/// cross-reference table and the writer both need it.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
24pub struct ObjRef {
25    /// The object number.
26    pub num: u32,
27    /// The generation number.
28    ///
29    /// Spelled out rather than `gen`, which Rust 2024 reserves.
30    pub generation: u16,
31}
32
33impl ObjRef {
34    // The object number no object may have; a cross-reference entry naming
35    // it is a broken entry.
36    //
37    // **Private on purpose**: no sentinel belongs in a public surface.
38    // PDFium exports this as `kInvalidObjNum`, but the PDF spec
39    // reserves no such number — the 8388607-object limit simply puts it out
40    // of reach — so a caller has nothing to compare against and no reason
41    // to. `ObjRef::is_invalid` is the whole public surface: the sentinel
42    // keeps existing in the parse, where the bytes contain it, and is asked
43    // about rather than handed over.
44    const INVALID_NUM: u32 = 0xFFFF_FFFF;
45
46    /// A reference to `num` at `generation`.
47    #[must_use]
48    pub const fn new(num: u32, generation: u16) -> Self {
49        Self { num, generation }
50    }
51
52    /// Whether the object number is the reserved invalid one.
53    #[must_use]
54    pub const fn is_invalid(self) -> bool {
55        self.num == Self::INVALID_NUM
56    }
57}
58
59/// A PDF object (ISO 32000-1 §7.3).
60///
61/// The eight basic types plus streams, plus a reference standing in for an
62/// indirect object. `Int` and `Real` are separate variants because the
63/// distinction is observable: an integer and a real that happen to be equal
64/// serialize differently and read back differently through the integer
65/// accessors.
66///
67/// ```
68/// use pdfrum_object::{Object, PdfString};
69///
70/// assert_eq!(Object::Int(1245).as_int(), Some(1245));
71/// assert_eq!(Object::Real(9.5).number(), Some(9.5));
72/// assert_eq!(Object::Bool(true).as_bool(), Some(true));
73/// // A name has no numeric value.
74/// assert_eq!(Object::Name("Foo".into()).number(), None);
75/// // ...but every object has a string spelling, empty for most.
76/// assert_eq!(Object::Str(PdfString::literal(b"hi")).to_byte_string(), b"hi");
77/// assert_eq!(Object::Null.to_byte_string(), b"");
78/// ```
79#[derive(Debug, Clone, PartialEq)]
80pub enum Object {
81    /// The `null` object.
82    Null,
83    /// `true` or `false`.
84    Bool(bool),
85    /// An integer.
86    ///
87    /// Holds the *mathematical* value. Every integer a conforming lexer can
88    /// produce lies in `-2^31 ..= 2^32 - 1`
89    /// (see [`INT_RANGE`](crate::INT_RANGE)) — larger literals fold to zero
90    /// during parsing. Reading it back has two flavours,
91    /// [`as_int`](Object::as_int) and [`number`](Object::number), which
92    /// disagree above `i32::MAX`; see [`narrow_to_signed32`](crate::narrow_to_signed32).
93    Int(i64),
94    /// A real number. `f32` rather than `f64` to match the precision the
95    /// oracle parses, formats and renders with.
96    Real(f32),
97    /// A string, in either syntax.
98    Str(PdfString),
99    /// A name.
100    Name(Name),
101    /// An array.
102    Array(Array),
103    /// A dictionary.
104    Dict(Dict),
105    /// A stream: a dictionary with bytes attached.
106    ///
107    /// Boxed because it is the one wide payload — a `Dict` plus a `ByteSpan`
108    /// is 56 bytes where every other payload is 24 — and the one that never
109    /// sits in the hot structures: ISO 32000-1 §7.3.8.1 forbids a stream as
110    /// a direct array element or dictionary value, so the box is only
111    /// dereferenced on the indirect-object path. It takes `Object` from 56
112    /// bytes to 32 and a dictionary pair from 80 to 56.
113    Stream(Box<Stream>),
114    /// A reference to an indirect object.
115    Ref(ObjRef),
116}
117
118impl Object {
119    /// The boolean value, only for an actual boolean.
120    ///
121    /// `Int(1)` is deliberately *not* a boolean: the type check happens
122    /// before any coercion, so a file that writes `1` for a flag reads as
123    /// "absent, use the default".
124    #[must_use]
125    pub fn as_bool(&self) -> Option<bool> {
126        match self {
127            Self::Bool(b) => Some(*b),
128            _ => None,
129        }
130    }
131
132    /// The integer value of any object that has one, in the C-integer view.
133    ///
134    /// Booleans count as 0 and 1, reals truncate toward zero (saturating, NaN
135    /// to 0), and everything else has no integer value. Note this is not a
136    /// type test — use [`Object::as_number`] for "is this a number".
137    #[must_use]
138    pub fn as_int(&self) -> Option<i64> {
139        match self {
140            Self::Bool(b) => Some(i64::from(*b)),
141            Self::Int(v) => Some(narrow_to_signed32(*v)),
142            Self::Real(v) => Some(truncate_to_signed32(*v)),
143            Self::Null
144            | Self::Str(_)
145            | Self::Name(_)
146            | Self::Array(_)
147            | Self::Dict(_)
148            | Self::Stream(_)
149            | Self::Ref(_) => None,
150        }
151    }
152
153    /// The numeric value of a number, coercing integers to `f32`.
154    ///
155    /// Only numbers have one — unlike [`Object::as_int`], a boolean does not
156    /// count.
157    #[must_use]
158    pub fn number(&self) -> Option<f32> {
159        match self {
160            Self::Int(v) => Some(widen_to_f32(*v)),
161            Self::Real(v) => Some(*v),
162            Self::Null
163            | Self::Bool(_)
164            | Self::Str(_)
165            | Self::Name(_)
166            | Self::Array(_)
167            | Self::Dict(_)
168            | Self::Stream(_)
169            | Self::Ref(_) => None,
170        }
171    }
172
173    /// The number itself, for accessors that type-check before coercing.
174    #[must_use]
175    pub fn as_number(&self) -> Option<&Self> {
176        match self {
177            Self::Int(_) | Self::Real(_) => Some(self),
178            _ => None,
179        }
180    }
181
182    /// The string, only for an actual string object.
183    #[must_use]
184    pub fn as_string(&self) -> Option<&PdfString> {
185        match self {
186            Self::Str(s) => Some(s),
187            _ => None,
188        }
189    }
190
191    /// The name, only for an actual name object.
192    #[must_use]
193    pub fn as_name(&self) -> Option<&Name> {
194        match self {
195            Self::Name(n) => Some(n),
196            _ => None,
197        }
198    }
199
200    /// The array, only for an actual array.
201    #[must_use]
202    pub fn as_array(&self) -> Option<&Array> {
203        match self {
204            Self::Array(a) => Some(a),
205            _ => None,
206        }
207    }
208
209    /// The dictionary — of a dictionary object, or of a stream.
210    ///
211    /// Streams answer with their own dictionary, which is what lets page-tree
212    /// and cross-reference code read `/Type` off either kind of object
213    /// without branching.
214    #[must_use]
215    pub fn as_dict(&self) -> Option<&Dict> {
216        match self {
217            Self::Dict(d) => Some(d),
218            Self::Stream(s) => Some(&s.dict),
219            _ => None,
220        }
221    }
222
223    /// The stream, only for an actual stream.
224    #[must_use]
225    pub fn as_stream(&self) -> Option<&Stream> {
226        match self {
227            Self::Stream(s) => Some(s),
228            _ => None,
229        }
230    }
231
232    /// The reference, only for an actual reference.
233    #[must_use]
234    pub fn as_ref_id(&self) -> Option<ObjRef> {
235        match self {
236            Self::Ref(r) => Some(*r),
237            _ => None,
238        }
239    }
240
241    /// Whether this is the null object.
242    #[must_use]
243    pub fn is_null(&self) -> bool {
244        matches!(self, Self::Null)
245    }
246
247    /// The object's byte-string spelling.
248    ///
249    /// Booleans spell `true`/`false`, numbers spell as the writer would, a
250    /// string yields its bytes and a name its decoded bytes. Everything else
251    /// — null, arrays, dictionaries, streams, references — has no spelling
252    /// and yields empty.
253    #[must_use]
254    pub fn to_byte_string(&self) -> Vec<u8> {
255        match self {
256            Self::Bool(true) => b"true".to_vec(),
257            Self::Bool(false) => b"false".to_vec(),
258            Self::Int(v) => fmt_int(*v).into_bytes(),
259            Self::Real(v) => fmt_number(*v).into_bytes(),
260            Self::Str(s) => s.bytes.to_vec(),
261            Self::Name(n) => n.as_bytes().to_vec(),
262            Self::Null | Self::Array(_) | Self::Dict(_) | Self::Stream(_) | Self::Ref(_) => {
263                Vec::new()
264            }
265        }
266    }
267
268    /// The object read as text: strings and names decode, everything else
269    /// yields empty.
270    ///
271    /// A stream's text needs its filters applied first, which this crate
272    /// cannot do — the reader composes decoding with
273    /// [`decode_text`](crate::decode_text) instead.
274    #[must_use]
275    pub fn to_text(&self) -> String {
276        match self {
277            Self::Str(s) => s.as_text().into_owned(),
278            Self::Name(n) => n.as_text().into_owned(),
279            Self::Null
280            | Self::Bool(_)
281            | Self::Int(_)
282            | Self::Real(_)
283            | Self::Array(_)
284            | Self::Dict(_)
285            | Self::Stream(_)
286            | Self::Ref(_) => String::new(),
287        }
288    }
289
290    /// Resolve one level: a reference becomes the object the store holds,
291    /// anything else is already itself.
292    ///
293    /// The result may still be a reference — an indirect object whose body is
294    /// `8 0 R` resolves to that reference and is *not* chased further, which
295    /// is why typed accessors go through [`Resolved::as_direct`].
296    ///
297    /// # Errors
298    ///
299    /// Whatever the store reports for an unresolvable reference.
300    ///
301    /// ```
302    /// # use std::sync::Arc;
303    /// # use pdfrum_object::{NoResolve, ObjRef, Object};
304    /// let direct = Object::Real(1.5);
305    /// assert_eq!(direct.resolve(&NoResolve).unwrap().number(), Some(1.5));
306    /// // Without a store every reference is dangling.
307    /// assert!(Object::Ref(ObjRef::new(4, 0)).resolve(&NoResolve).is_err());
308    /// ```
309    pub fn resolve<'a>(&'a self, r: &impl Resolve) -> Result<Resolved<'a>, Error> {
310        match self {
311            Self::Ref(id) => Ok(Resolved::Indirect(r.fetch(*id)?)),
312            _ => Ok(Resolved::Direct(self)),
313        }
314    }
315
316    /// Deep-copy the object with every reference replaced by what it points
317    /// at, dropping the edges that would close a cycle. Only a reference back
318    /// to an *ancestor* is a cycle; siblings may share substructure and both
319    /// copies survive. A cut edge **disappears** — the key or element is
320    /// omitted rather than becoming null — and an unresolvable reference
321    /// disappears the same way, indistinguishably.
322    ///
323    /// A reference to a stream flattens into the stream itself, stored
324    /// *directly* in the dictionary or array that held it, with its **raw**,
325    /// still-encoded bytes and its `/Filter` intact: ISO 32000-1 §7.3.8.1
326    /// constrains a *file*, not these in-memory types.
327    ///
328    /// ```
329    /// # use std::collections::HashMap;
330    /// # use std::sync::Arc;
331    /// # use pdfrum_object::{Array, Error, ObjRef, Object, Resolve};
332    /// # struct Store(HashMap<u32, Arc<Object>>);
333    /// # impl Resolve for Store {
334    /// #     fn fetch(&self, r: ObjRef) -> Result<Arc<Object>, Error> {
335    /// #         self.0.get(&r.num).cloned().ok_or(Error::UnresolvedRef(r))
336    /// #     }
337    /// # }
338    /// let store = Store(HashMap::from([(7, Arc::new(Object::Int(42)))]));
339    /// let array = Object::Array(Array::from_iter([Object::Ref(ObjRef::new(7, 0))]));
340    /// assert_eq!(
341    ///     array.clone_direct(&store),
342    ///     Object::Array(Array::from_iter([Object::Int(42)])),
343    /// );
344    /// ```
345    #[must_use]
346    pub fn clone_direct(&self, r: &impl Resolve) -> Self {
347        // A cut edge and a dangling reference are indistinguishable because
348        // `CPDF_Reference::CloneNonCyclic` returns `nullptr` for both.
349        // Flattening a stream into a container matches
350        // `CPDF_Dictionary::CloneNonCyclic`, whose loop inserts into `map_`
351        // directly and so bypasses the `CHECK(!IsStream())` that guards the
352        // ordinary setters; the raw bytes are
353        // `CPDF_Stream::CloneNonCyclic`'s `LoadAllDataRaw`.
354        let mut ancestors = HashSet::new();
355        clone_flattened(self, r, &mut ancestors).unwrap_or(Self::Null)
356    }
357}
358
359/// Deep-copy `obj` flattening references, cutting edges back to `ancestors`.
360/// `None` means "this edge closes a cycle or dangles" — the caller drops it.
361fn clone_flattened(
362    obj: &Object,
363    r: &impl Resolve,
364    ancestors: &mut HashSet<ObjRef>,
365) -> Option<Object> {
366    match obj {
367        Object::Ref(id) => {
368            if !ancestors.insert(*id) {
369                return None;
370            }
371            let target = r.fetch(*id).ok();
372            let cloned = target
373                .as_deref()
374                .and_then(|t| clone_flattened(t, r, &mut ancestors.clone()));
375            ancestors.remove(id);
376            cloned
377        }
378        Object::Array(a) => Some(Object::Array(
379            a.iter()
380                .filter_map(|e| clone_flattened(e, r, &mut ancestors.clone()))
381                .collect(),
382        )),
383        Object::Dict(d) => Some(Object::Dict(
384            d.iter()
385                .filter_map(|(k, v)| {
386                    clone_flattened(v, r, &mut ancestors.clone()).map(|v| (k.clone(), v))
387                })
388                .collect(),
389        )),
390        Object::Stream(s) => {
391            let dict = s
392                .dict
393                .iter()
394                .filter_map(|(k, v)| {
395                    clone_flattened(v, r, &mut ancestors.clone()).map(|v| (k.clone(), v))
396                })
397                .collect();
398            Some(Object::Stream(Box::new(Stream::new(dict, s.data.clone()))))
399        }
400        other => Some(other.clone()),
401    }
402}
403
404impl From<bool> for Object {
405    fn from(v: bool) -> Self {
406        Self::Bool(v)
407    }
408}
409
410impl From<i64> for Object {
411    fn from(v: i64) -> Self {
412        Self::Int(v)
413    }
414}
415
416impl From<i32> for Object {
417    fn from(v: i32) -> Self {
418        Self::Int(i64::from(v))
419    }
420}
421
422impl From<f32> for Object {
423    fn from(v: f32) -> Self {
424        Self::Real(v)
425    }
426}
427
428impl From<Name> for Object {
429    fn from(v: Name) -> Self {
430        Self::Name(v)
431    }
432}
433
434impl From<PdfString> for Object {
435    fn from(v: PdfString) -> Self {
436        Self::Str(v)
437    }
438}
439
440impl From<Array> for Object {
441    fn from(v: Array) -> Self {
442        Self::Array(v)
443    }
444}
445
446impl From<Dict> for Object {
447    fn from(v: Dict) -> Self {
448        Self::Dict(v)
449    }
450}
451
452impl From<Stream> for Object {
453    fn from(v: Stream) -> Self {
454        Self::Stream(Box::new(v))
455    }
456}
457
458impl From<ObjRef> for Object {
459    fn from(v: ObjRef) -> Self {
460        Self::Ref(v)
461    }
462}