Skip to main content

otf_pdf_syntax/
object.rs

1//! The PDF object model (ISO 32000-2 §7.3).
2//!
3//! Names and strings are bytes, never `String`: PDF does not promise any
4//! encoding for either, and a lossy conversion would corrupt round-trips.
5
6use std::borrow::Borrow;
7use std::collections::BTreeMap;
8use std::fmt;
9use std::sync::Arc;
10
11/// An indirect object's identity: object number and generation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct ObjId {
14    /// Object number. Zero is never a real object.
15    pub num: u32,
16    /// Generation number.
17    pub generation: u16,
18}
19
20impl ObjId {
21    /// An id from its two parts.
22    pub const fn new(num: u32, generation: u16) -> Self {
23        Self { num, generation }
24    }
25}
26
27impl fmt::Display for ObjId {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "{} {} R", self.num, self.generation)
30    }
31}
32
33/// A name object, without its leading `/` and with `#xx` escapes resolved.
34#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
35pub struct Name(pub Vec<u8>);
36
37impl Name {
38    /// A name from its bytes.
39    pub fn new(bytes: impl Into<Vec<u8>>) -> Self {
40        Self(bytes.into())
41    }
42
43    /// The name's bytes.
44    pub fn as_bytes(&self) -> &[u8] {
45        &self.0
46    }
47}
48
49impl Borrow<[u8]> for Name {
50    fn borrow(&self) -> &[u8] {
51        &self.0
52    }
53}
54
55impl From<&[u8]> for Name {
56    fn from(bytes: &[u8]) -> Self {
57        Self(bytes.to_vec())
58    }
59}
60
61impl<const N: usize> From<&[u8; N]> for Name {
62    fn from(bytes: &[u8; N]) -> Self {
63        Self(bytes.to_vec())
64    }
65}
66
67impl From<&str> for Name {
68    fn from(s: &str) -> Self {
69        Self(s.as_bytes().to_vec())
70    }
71}
72
73impl PartialEq<[u8]> for Name {
74    fn eq(&self, other: &[u8]) -> bool {
75        self.0 == other
76    }
77}
78
79impl<const N: usize> PartialEq<&[u8; N]> for Name {
80    fn eq(&self, other: &&[u8; N]) -> bool {
81        self.0 == other.as_slice()
82    }
83}
84
85impl fmt::Debug for Name {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "/{}", String::from_utf8_lossy(&self.0))
88    }
89}
90
91/// A string object's bytes, and whether it was written in hex.
92#[derive(Clone, PartialEq, Eq, Hash, Default)]
93pub struct PdfString {
94    /// The string's bytes, with escapes resolved.
95    pub bytes: Vec<u8>,
96    /// Whether it was written as `<hex>` rather than `(literal)`.
97    pub hex: bool,
98}
99
100impl PdfString {
101    /// A literal string.
102    pub fn literal(bytes: impl Into<Vec<u8>>) -> Self {
103        Self {
104            bytes: bytes.into(),
105            hex: false,
106        }
107    }
108
109    /// A hex string.
110    pub fn hex(bytes: impl Into<Vec<u8>>) -> Self {
111        Self {
112            bytes: bytes.into(),
113            hex: true,
114        }
115    }
116}
117
118impl fmt::Debug for PdfString {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        if self.hex {
121            write!(f, "<")?;
122            for b in &self.bytes {
123                write!(f, "{b:02x}")?;
124            }
125            write!(f, ">")
126        } else {
127            write!(f, "({})", String::from_utf8_lossy(&self.bytes))
128        }
129    }
130}
131
132/// A dictionary. Keys are kept sorted, so iteration (and anything written
133/// from it) is deterministic. A repeated key keeps its last value.
134#[derive(Clone, PartialEq, Default)]
135pub struct Dict(BTreeMap<Name, Object>);
136
137impl Dict {
138    /// An empty dictionary.
139    pub fn new() -> Self {
140        Self::default()
141    }
142
143    /// The value for `key`, if present. A `null` value counts as absent
144    /// (ISO 32000-2 §7.3.7).
145    pub fn get(&self, key: &[u8]) -> Option<&Object> {
146        self.0.get(key).filter(|v| !matches!(v, Object::Null))
147    }
148
149    /// Sets `key` to `value`, returning the previous value.
150    pub fn insert(&mut self, key: impl Into<Name>, value: impl Into<Object>) -> Option<Object> {
151        self.0.insert(key.into(), value.into())
152    }
153
154    /// Removes `key`, returning its value.
155    pub fn remove(&mut self, key: &[u8]) -> Option<Object> {
156        self.0.remove(key)
157    }
158
159    /// Whether `key` is present with a non-null value.
160    pub fn contains_key(&self, key: &[u8]) -> bool {
161        self.get(key).is_some()
162    }
163
164    /// The number of entries.
165    pub fn len(&self) -> usize {
166        self.0.len()
167    }
168
169    /// Whether there are no entries.
170    pub fn is_empty(&self) -> bool {
171        self.0.is_empty()
172    }
173
174    /// Entries in key order.
175    pub fn iter(&self) -> impl Iterator<Item = (&Name, &Object)> {
176        self.0.iter()
177    }
178
179    /// The value for `key` if it is a name.
180    pub fn get_name(&self, key: &[u8]) -> Option<&[u8]> {
181        self.get(key).and_then(Object::as_name)
182    }
183
184    /// The value for `key` if it is an integer.
185    pub fn get_int(&self, key: &[u8]) -> Option<i64> {
186        self.get(key).and_then(Object::as_int)
187    }
188
189    /// The value for `key` if it is a dictionary (or a stream's dictionary).
190    pub fn get_dict(&self, key: &[u8]) -> Option<&Dict> {
191        self.get(key).and_then(Object::as_dict)
192    }
193
194    /// The value for `key` if it is an array.
195    pub fn get_array(&self, key: &[u8]) -> Option<&[Object]> {
196        self.get(key).and_then(Object::as_array)
197    }
198
199    /// The value for `key` if it is an indirect reference.
200    pub fn get_ref(&self, key: &[u8]) -> Option<ObjId> {
201        self.get(key).and_then(Object::as_ref)
202    }
203
204    /// Whether `/Type` is `ty`.
205    pub fn is_type(&self, ty: &[u8]) -> bool {
206        self.get_name(b"Type") == Some(ty)
207    }
208}
209
210impl fmt::Debug for Dict {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        f.debug_map().entries(self.0.iter()).finish()
213    }
214}
215
216impl FromIterator<(Name, Object)> for Dict {
217    fn from_iter<I: IntoIterator<Item = (Name, Object)>>(iter: I) -> Self {
218        Self(iter.into_iter().collect())
219    }
220}
221
222/// A stream: its dictionary and its bytes exactly as stored in the file,
223/// still encoded and (if the file is encrypted) still encrypted.
224#[derive(Clone, PartialEq)]
225pub struct Stream {
226    /// The stream dictionary.
227    pub dict: Dict,
228    /// The raw bytes between `stream` and `endstream`.
229    pub raw: Arc<[u8]>,
230}
231
232impl fmt::Debug for Stream {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        write!(f, "{:?} stream[{} bytes]", self.dict, self.raw.len())
235    }
236}
237
238/// Any PDF object.
239#[derive(Debug, Clone, PartialEq, Default)]
240pub enum Object {
241    /// `null`.
242    #[default]
243    Null,
244    /// `true` or `false`.
245    Bool(bool),
246    /// An integer.
247    Int(i64),
248    /// A real number.
249    Real(f64),
250    /// A string.
251    String(PdfString),
252    /// A name.
253    Name(Name),
254    /// An array.
255    Array(Vec<Object>),
256    /// A dictionary.
257    Dict(Dict),
258    /// A stream.
259    Stream(Stream),
260    /// An indirect reference.
261    Ref(ObjId),
262}
263
264impl Object {
265    /// The kind of object, for messages.
266    pub fn type_name(&self) -> &'static str {
267        match self {
268            Self::Null => "null",
269            Self::Bool(_) => "boolean",
270            Self::Int(_) => "integer",
271            Self::Real(_) => "real",
272            Self::String(_) => "string",
273            Self::Name(_) => "name",
274            Self::Array(_) => "array",
275            Self::Dict(_) => "dictionary",
276            Self::Stream(_) => "stream",
277            Self::Ref(_) => "reference",
278        }
279    }
280
281    /// The value if this is a boolean.
282    pub fn as_bool(&self) -> Option<bool> {
283        match self {
284            Self::Bool(b) => Some(*b),
285            _ => None,
286        }
287    }
288
289    /// The value if this is an integer, or a real with no fractional part.
290    pub fn as_int(&self) -> Option<i64> {
291        match self {
292            Self::Int(i) => Some(*i),
293            Self::Real(r) if r.fract() == 0.0 && r.abs() < 9.0e15 => Some(*r as i64),
294            _ => None,
295        }
296    }
297
298    /// The value as a number, if it is an integer or a real.
299    pub fn as_f64(&self) -> Option<f64> {
300        match self {
301            Self::Int(i) => Some(*i as f64),
302            Self::Real(r) => Some(*r),
303            _ => None,
304        }
305    }
306
307    /// The bytes if this is a name.
308    pub fn as_name(&self) -> Option<&[u8]> {
309        match self {
310            Self::Name(n) => Some(n.as_bytes()),
311            _ => None,
312        }
313    }
314
315    /// The bytes if this is a string.
316    pub fn as_string(&self) -> Option<&[u8]> {
317        match self {
318            Self::String(s) => Some(&s.bytes),
319            _ => None,
320        }
321    }
322
323    /// The elements if this is an array.
324    pub fn as_array(&self) -> Option<&[Object]> {
325        match self {
326            Self::Array(a) => Some(a),
327            _ => None,
328        }
329    }
330
331    /// The dictionary if this is a dictionary or a stream.
332    pub fn as_dict(&self) -> Option<&Dict> {
333        match self {
334            Self::Dict(d) => Some(d),
335            Self::Stream(s) => Some(&s.dict),
336            _ => None,
337        }
338    }
339
340    /// The stream if this is one.
341    pub fn as_stream(&self) -> Option<&Stream> {
342        match self {
343            Self::Stream(s) => Some(s),
344            _ => None,
345        }
346    }
347
348    /// The target if this is an indirect reference.
349    pub fn as_ref(&self) -> Option<ObjId> {
350        match self {
351            Self::Ref(id) => Some(*id),
352            _ => None,
353        }
354    }
355}
356
357impl From<bool> for Object {
358    fn from(b: bool) -> Self {
359        Self::Bool(b)
360    }
361}
362
363impl From<i64> for Object {
364    fn from(i: i64) -> Self {
365        Self::Int(i)
366    }
367}
368
369impl From<f64> for Object {
370    fn from(r: f64) -> Self {
371        Self::Real(r)
372    }
373}
374
375impl From<Name> for Object {
376    fn from(n: Name) -> Self {
377        Self::Name(n)
378    }
379}
380
381impl From<PdfString> for Object {
382    fn from(s: PdfString) -> Self {
383        Self::String(s)
384    }
385}
386
387impl From<Dict> for Object {
388    fn from(d: Dict) -> Self {
389        Self::Dict(d)
390    }
391}
392
393impl From<Vec<Object>> for Object {
394    fn from(a: Vec<Object>) -> Self {
395        Self::Array(a)
396    }
397}
398
399impl From<ObjId> for Object {
400    fn from(id: ObjId) -> Self {
401        Self::Ref(id)
402    }
403}
404
405impl From<Stream> for Object {
406    fn from(s: Stream) -> Self {
407        Self::Stream(s)
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414
415    #[test]
416    fn dict_null_is_absent() {
417        let mut d = Dict::new();
418        d.insert("A", Object::Null);
419        d.insert("B", 3i64);
420        assert!(d.get(b"A").is_none());
421        assert!(!d.contains_key(b"A"));
422        assert_eq!(d.get_int(b"B"), Some(3));
423        assert_eq!(d.len(), 2);
424    }
425
426    #[test]
427    fn dict_last_key_wins_and_is_sorted() {
428        let d: Dict = [
429            (Name::from("Z"), Object::Int(1)),
430            (Name::from("A"), Object::Int(2)),
431            (Name::from("Z"), Object::Int(3)),
432        ]
433        .into_iter()
434        .collect();
435        let keys: Vec<_> = d.iter().map(|(k, _)| k.as_bytes().to_vec()).collect();
436        assert_eq!(keys, vec![b"A".to_vec(), b"Z".to_vec()]);
437        assert_eq!(d.get_int(b"Z"), Some(3));
438    }
439
440    #[test]
441    fn typed_accessors() {
442        let mut d = Dict::new();
443        d.insert("Type", Name::from("Page"));
444        d.insert("Kids", vec![Object::Ref(ObjId::new(4, 0))]);
445        d.insert("Parent", ObjId::new(2, 0));
446        d.insert("Res", Dict::new());
447        assert!(d.is_type(b"Page"));
448        assert_eq!(d.get_array(b"Kids").map(<[_]>::len), Some(1));
449        assert_eq!(d.get_ref(b"Parent"), Some(ObjId::new(2, 0)));
450        assert!(d.get_dict(b"Res").is_some());
451        assert_eq!(d.remove(b"Res"), Some(Object::Dict(Dict::new())));
452    }
453
454    #[test]
455    fn numbers() {
456        assert_eq!(Object::Real(3.0).as_int(), Some(3));
457        assert_eq!(Object::Real(3.5).as_int(), None);
458        assert_eq!(Object::Real(1e300).as_int(), None);
459        assert_eq!(Object::Int(2).as_f64(), Some(2.0));
460        assert_eq!(Object::Null.as_f64(), None);
461    }
462
463    #[test]
464    fn debug_forms() {
465        assert_eq!(format!("{:?}", Name::from("A B")), "/A B");
466        assert_eq!(format!("{:?}", PdfString::hex(vec![0xab, 1])), "<ab01>");
467        assert_eq!(format!("{:?}", PdfString::literal("hi")), "(hi)");
468        assert_eq!(ObjId::new(3, 1).to_string(), "3 1 R");
469        assert_eq!(Object::Bool(true).type_name(), "boolean");
470    }
471}