Skip to main content

pdfrum_object/
resolve.rs

1//! Indirect-reference lookup: the one seam between the object model and
2//! whatever holds the document's objects.
3//!
4//! **Resolution is one hop.** PDF lets an indirect object's body be itself a
5//! reference (`7 0 obj 8 0 R endobj`); a chain like that reads as a *missing
6//! value* rather than following to object 8. Every typed accessor in this
7//! crate reproduces that — see [`Resolve`] and [`Resolved::as_direct`].
8//!
9//! Cycle safety is the store's problem, not this crate's: accessors resolve
10//! one level by construction and cannot recurse.
11
12use std::ops::Deref;
13use std::sync::Arc;
14
15use crate::{Error, ObjRef, Object};
16
17/// A store that can produce the object behind a reference.
18///
19/// **Resolution is one hop: a reference to a reference is absent.** A typed
20/// accessor that takes a `&impl Resolve` follows at most one `n g R`; if
21/// what it finds there is itself a reference, the value reads as missing
22/// rather than being chased further ([`Resolved::as_direct`]).
23///
24/// Implementations must look up by object *number* alone and ignore the
25/// generation: a `12 3 R` in a real file resolves to whatever object 12 the
26/// cross-reference table delivers, whatever generation it carries.
27/// Unresolvable references — free entries, missing objects, cycles — return
28/// [`Error::UnresolvedRef`] or [`Error::RefLoop`], which every typed accessor
29/// then reads as absence.
30pub trait Resolve {
31    /// The object stored under `r`, or why it could not be produced.
32    ///
33    /// # Errors
34    ///
35    /// [`Error::UnresolvedRef`] when the store has no such object, and
36    /// [`Error::RefLoop`] when the fetch re-entered one already in progress.
37    fn fetch(&self, r: ObjRef) -> Result<Arc<Object>, Error>;
38}
39
40impl<T: Resolve + ?Sized> Resolve for &T {
41    fn fetch(&self, r: ObjRef) -> Result<Arc<Object>, Error> {
42        (**self).fetch(r)
43    }
44}
45
46/// The result of resolving: a direct object stays borrowed, an indirect one
47/// arrives shared from the store.
48///
49/// Behaves like the `Object` it holds through [`Deref`], so callers match on
50/// it without caring which side it came from.
51///
52/// ```
53/// use pdfrum_object::{Object, Resolved};
54///
55/// let obj = Object::Int(42);
56/// let resolved = Resolved::Direct(&obj);
57/// assert_eq!(resolved.as_int(), Some(42));
58/// ```
59#[derive(Debug, Clone)]
60pub enum Resolved<'a> {
61    /// The object was already direct; this borrows it in place.
62    Direct(&'a Object),
63    /// The object came from the store and is shared with it.
64    Indirect(Arc<Object>),
65}
66
67impl Resolved<'_> {
68    /// The object, whichever side it came from.
69    #[must_use]
70    pub fn get(&self) -> &Object {
71        match self {
72            Self::Direct(o) => o,
73            Self::Indirect(o) => o,
74        }
75    }
76
77    /// The object unless it is *itself* a reference.
78    ///
79    /// This is where [`Resolve`]'s one-hop rule is enforced: every typed
80    /// accessor goes through here, so a reference-to-reference chain reads
81    /// as absence.
82    #[must_use]
83    pub fn as_direct(&self) -> Option<&Object> {
84        let obj = self.get();
85        if matches!(obj, Object::Ref(_)) {
86            None
87        } else {
88            Some(obj)
89        }
90    }
91
92    /// Take ownership of the object, cloning a borrowed one.
93    #[must_use]
94    pub fn into_owned(self) -> Object {
95        match self {
96            Self::Direct(o) => o.clone(),
97            Self::Indirect(o) => Arc::unwrap_or_clone(o),
98        }
99    }
100}
101
102impl Deref for Resolved<'_> {
103    type Target = Object;
104
105    fn deref(&self) -> &Object {
106        self.get()
107    }
108}
109
110impl PartialEq for Resolved<'_> {
111    fn eq(&self, other: &Self) -> bool {
112        self.get() == other.get()
113    }
114}
115
116/// A resolver that knows nothing, for reading dictionaries whose references
117/// are irrelevant (or before a store exists).
118///
119/// Every reference is unresolvable, which is exactly how a damaged file's
120/// dangling references behave, so accessors keep working and return their
121/// fallbacks.
122///
123/// ```
124/// use pdfrum_object::{Dict, NoResolve, Object, names};
125///
126/// let dict = Dict::from_pairs([(names::LENGTH.clone(), Object::Int(7))]);
127/// // Direct values still read fine without a store.
128/// assert_eq!(dict.int(names::LENGTH, &NoResolve), Some(7));
129/// ```
130#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
131pub struct NoResolve;
132
133impl Resolve for NoResolve {
134    fn fetch(&self, r: ObjRef) -> Result<Arc<Object>, Error> {
135        Err(Error::UnresolvedRef(r))
136    }
137}