pdfboss-core 2.12.0

PDF parser in pure Rust: syntax, objects, filters, cross-references and the document model (ISO 32000)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
//! Name trees and number trees (ISO 32000-1 §7.9.6 and §7.9.7): sorted
//! key-value maps spread over `/Kids` nodes, each labelled with the
//! `/Limits` its keys fall in, so one key can be found without reading the
//! whole tree, or every pair listed leaf by leaf.

use crate::hash::FastSet;
use crate::object::{decode_text_string, Dict, ObjRef, Object};
use crate::source::AsyncObjectSource;

/// Maximum nodes visited in one walk: past it the walk gives up, so a
/// `/Kids` graph that a visited set cannot catch (direct dictionaries
/// repeated by value) still cannot spin.
pub const MAX_TREE_NODES: usize = 4096;

/// A tree's key type: how a leaf pair's key or a `/Limits` bound reads
/// from its object, and which node entry holds the leaf pairs.
pub trait TreeKey: Ord + Sized {
    /// The leaf entry: `Names` in a name tree, `Nums` in a number tree.
    const PAIRS: &'static str;

    /// Reads a key from a resolved object; `None` for the wrong type.
    fn from_object(o: &Object) -> Option<Self>;
}

/// Name tree keys are strings compared byte by byte, shorter keys before
/// longer ones with the same prefix, whatever their encoding; `Vec<u8>`'s
/// ordering is exactly that.
///
/// Covers ISO 32000-1 §7.9.6.
impl TreeKey for Vec<u8> {
    const PAIRS: &'static str = "Names";

    fn from_object(o: &Object) -> Option<Self> {
        o.as_str_bytes().map(<[u8]>::to_vec)
    }
}

/// Number tree keys are integers in numerical order.
///
/// Covers ISO 32000-1 §7.9.7.
impl TreeKey for i64 {
    const PAIRS: &'static str = "Nums";

    fn from_object(o: &Object) -> Option<Self> {
        o.as_int()
    }
}

/// Looks `key` up in the tree rooted at `root`: the root's own pairs
/// first, then the `/Kids` whose `/Limits` admit the key, depth first. The
/// value comes back unresolved, so a stream stays an indirect reference.
/// Malformed nodes are skipped, a node without readable limits is searched
/// regardless, a kid already visited is not entered again, and the walk
/// stops after [`MAX_TREE_NODES`] nodes.
///
/// Covers ISO 32000-1 §7.9.6 and §7.9.7.
pub async fn lookup<K: TreeKey, S: AsyncObjectSource>(
    src: &S,
    root: &Dict,
    key: &K,
) -> Option<Object> {
    let mut walk = Walk::default();
    let mut pending: Vec<Dict> = vec![root.clone()];
    while let Some(node) = pending.pop() {
        if !walk.enter() {
            return None;
        }
        for (k, value) in pairs::<K, S>(src, &node).await {
            if k == *key {
                return Some(value);
            }
        }
        for kid in kids(src, &node).await.iter().rev() {
            let Some(kid) = walk.first_visit(src, kid).await else {
                continue;
            };
            if within_limits(src, &kid, key).await {
                pending.push(kid);
            }
        }
    }
    None
}

/// Every key-value pair in the tree rooted at `root`, in tree order: a
/// node's own pairs, then each kid's in turn. Values come back unresolved.
/// Malformed nodes are skipped, a kid already visited is not entered again,
/// and the walk stops after [`MAX_TREE_NODES`] nodes.
///
/// Covers ISO 32000-1 §7.9.6 and §7.9.7.
pub async fn entries<K: TreeKey, S: AsyncObjectSource>(src: &S, root: &Dict) -> Vec<(K, Object)> {
    let mut walk = Walk::default();
    let mut found = Vec::new();
    let mut pending: Vec<Dict> = vec![root.clone()];
    while let Some(node) = pending.pop() {
        if !walk.enter() {
            break;
        }
        found.extend(pairs::<K, S>(src, &node).await);
        for kid in kids(src, &node).await.iter().rev() {
            if let Some(kid) = walk.first_visit(src, kid).await {
                pending.push(kid);
            }
        }
    }
    found
}

/// The bookkeeping of one walk: the kids entered so far, by reference, and
/// the node budget.
#[derive(Default)]
struct Walk {
    visited: FastSet<ObjRef>,
    entered: usize,
}

impl Walk {
    /// Counts a node against the budget; `false` once it is spent.
    fn enter(&mut self) -> bool {
        self.entered += 1;
        self.entered <= MAX_TREE_NODES
    }

    /// The kid as a dictionary the first time its reference is seen; `None`
    /// for a repeat, or for a kid that is not a dictionary.
    async fn first_visit<S: AsyncObjectSource>(&mut self, src: &S, kid: &Object) -> Option<Dict> {
        if let Some(r) = kid.as_ref() {
            if !self.visited.insert(r) {
                return None;
            }
        }
        resolved_dict(src, kid).await
    }
}

/// A node's `/Kids`, unresolved; empty when absent or not an array.
async fn kids<S: AsyncObjectSource>(src: &S, node: &Dict) -> Vec<Object> {
    let Some(kids) = node.get("Kids") else {
        return Vec::new();
    };
    match src.resolve(kids).await {
        Ok(Object::Array(kids)) => kids,
        _ => Vec::new(),
    }
}

/// A node's leaf pairs (`/Names` or `/Nums`) whose keys read as `K`,
/// values unresolved; a pair whose key does not is skipped.
async fn pairs<K: TreeKey, S: AsyncObjectSource>(src: &S, node: &Dict) -> Vec<(K, Object)> {
    let Some(pairs) = node.get(K::PAIRS) else {
        return Vec::new();
    };
    let Ok(Object::Array(pairs)) = src.resolve(pairs).await else {
        return Vec::new();
    };
    let mut found = Vec::with_capacity(pairs.len() / 2);
    for [key, value] in pairs.as_chunks::<2>().0 {
        if let Some(key) = key_of(src, key).await {
            found.push((key, value.clone()));
        }
    }
    found
}

/// Whether `key` falls in a node's `/Limits`; a node without readable
/// limits is searched regardless.
///
/// Covers ISO 32000-1 §7.9.6 and §7.9.7.
async fn within_limits<K: TreeKey, S: AsyncObjectSource>(src: &S, node: &Dict, key: &K) -> bool {
    let Some(limits) = node.get("Limits") else {
        return true;
    };
    let Ok(Object::Array(limits)) = src.resolve(limits).await else {
        return true;
    };
    let (Some(lo), Some(hi)) = (limits.first(), limits.get(1)) else {
        return true;
    };
    match (key_of::<K, S>(src, lo).await, key_of::<K, S>(src, hi).await) {
        (Some(lo), Some(hi)) => lo <= *key && *key <= hi,
        _ => true,
    }
}

/// A key or limit read from its object, resolving an indirect one.
async fn key_of<K: TreeKey, S: AsyncObjectSource>(src: &S, o: &Object) -> Option<K> {
    if let Some(key) = K::from_object(o) {
        return Some(key);
    }
    K::from_object(&src.resolve(o).await.ok()?)
}

/// Resolves `o` to a dictionary, a stream's dictionary included.
pub(crate) async fn resolved_dict<S: AsyncObjectSource>(src: &S, o: &Object) -> Option<Dict> {
    match src.resolve(o).await.ok()? {
        Object::Dict(dict) => Some(dict),
        Object::Stream(stream) => Some(stream.dict),
        _ => None,
    }
}

/// The entries of one dictionary, each resolved through the source when it
/// is an indirect object.
pub(crate) struct Entries<'a, S> {
    pub(crate) src: &'a S,
    pub(crate) dict: &'a Dict,
}

impl<S: AsyncObjectSource> Entries<'_, S> {
    /// The value under `key`, resolved; `None` when absent or unreadable.
    pub(crate) async fn value(&self, key: &str) -> Option<Object> {
        self.src.resolve(self.dict.get(key)?).await.ok()
    }

    /// The boolean under `key`, `None` when absent or not a boolean.
    pub(crate) async fn flag(&self, key: &str) -> Option<bool> {
        self.value(key).await?.as_bool()
    }

    /// The name under `key` mapped through `from_name`.
    pub(crate) async fn named<T>(&self, key: &str, from_name: fn(&str) -> Option<T>) -> Option<T> {
        from_name(&self.value(key).await?.as_name()?.0)
    }

    /// The text string under `key`, decoded (§7.9.2.2); `None` when absent
    /// or not a string.
    pub(crate) async fn text(&self, key: &str) -> Option<String> {
        Some(decode_text_string(self.value(key).await?.as_str_bytes()?))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{block_on, Document, Immediate};
    use pdfboss_testkit::PdfBuilder;

    /// A document whose object 10 is a tree root; `objects` supplies the
    /// tree (10 and up).
    fn tree_doc(objects: &[(u32, &str)]) -> Document {
        let mut b = PdfBuilder::new();
        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
        for (num, body) in objects {
            b.object(*num, body);
        }
        Document::load(b.build(1)).expect("load")
    }

    fn root(doc: &Document) -> Dict {
        doc.get(ObjRef { num: 10, gen: 0 })
            .unwrap()
            .as_dict()
            .cloned()
            .unwrap()
    }

    fn find(doc: &Document, key: &str) -> Option<Object> {
        block_on(lookup(
            &Immediate(doc),
            &root(doc),
            &key.as_bytes().to_vec(),
        ))
    }

    fn keys(doc: &Document) -> Vec<String> {
        block_on(entries::<Vec<u8>, _>(&Immediate(doc), &root(doc)))
            .into_iter()
            .map(|(k, _)| String::from_utf8(k).unwrap())
            .collect()
    }

    /// The clause's own example, abbreviated: a root of three intermediate
    /// nodes, each with leaf nodes, values as indirect integers.
    fn elements() -> Document {
        tree_doc(&[
            (10, "<< /Kids [11 0 R 12 0 R 13 0 R] >>"),
            (
                11,
                "<< /Limits [(Actinium) (Gold)] /Kids [14 0 R 15 0 R] >>",
            ),
            (
                12,
                "<< /Limits [(Hafnium) (Protactinium)] /Kids [16 0 R] >>",
            ),
            (
                13,
                "<< /Limits [(Radium) (Zirconium)] /Names [(Radium) 88 (Zirconium) 40] >>",
            ),
            (
                14,
                "<< /Limits [(Actinium) (Astatine)] /Names [(Actinium) 20 0 R (Astatine) 85] >>",
            ),
            (
                15,
                "<< /Limits [(Gadolinium) (Gold)] /Names [(Gadolinium) 64 (Gold) 21 0 R] >>",
            ),
            (
                16,
                "<< /Limits [(Hafnium) (Hydrogen)] /Names [(Hafnium) 72 (Hydrogen) 1] >>",
            ),
            (20, "89"),
            (21, "79"),
        ])
    }

    // Covers ISO 32000-1 §7.9.6.
    #[test]
    fn name_lookup_descends_kids_by_limits() {
        let doc = elements();
        assert_eq!(find(&doc, "Hydrogen"), Some(Object::Int(1)));
        assert_eq!(find(&doc, "Radium"), Some(Object::Int(88)));
        // Values come back as stored: an indirect value stays a reference.
        assert_eq!(
            find(&doc, "Gold"),
            Some(Object::Ref(ObjRef { num: 21, gen: 0 }))
        );
        assert_eq!(find(&doc, "Gallium"), None, "inside a range, absent");
        assert_eq!(find(&doc, "Zzz"), None, "past every limit");
        assert_eq!(find(&doc, ""), None, "before every limit");
    }

    // Covers ISO 32000-1 §7.9.6.
    #[test]
    fn name_keys_compare_bytewise_shorter_first() {
        // Two leaves whose limits only make sense byte by byte: (a) sorts
        // before (ab), and the UTF-16 key with its byte order mark (0xFE
        // first) sorts after every ASCII key.
        let doc = tree_doc(&[
            (10, "<< /Kids [11 0 R 12 0 R] >>"),
            (11, "<< /Limits [(a) (a)] /Names [(a) 2] >>"),
            (
                12,
                "<< /Limits [(ab) <FEFF00E9>] /Names [(ab) 3 (b) 4 <FEFF00E9> 1] >>",
            ),
        ]);
        assert_eq!(find(&doc, "a"), Some(Object::Int(2)));
        assert_eq!(find(&doc, "ab"), Some(Object::Int(3)));
        assert_eq!(find(&doc, "b"), Some(Object::Int(4)));
        let bom = block_on(lookup(
            &Immediate(&doc),
            &root(&doc),
            &b"\xFE\xFF\x00\xE9".to_vec(),
        ));
        assert_eq!(bom, Some(Object::Int(1)));
        assert_eq!(find(&doc, "aa"), None, "between (a) and (ab), absent");
    }

    // Covers ISO 32000-1 §7.9.6.
    #[test]
    fn entries_list_every_leaf_in_order() {
        let doc = elements();
        assert_eq!(
            keys(&doc),
            [
                "Actinium",
                "Astatine",
                "Gadolinium",
                "Gold",
                "Hafnium",
                "Hydrogen",
                "Radium",
                "Zirconium"
            ]
        );
        // A root that is its own single leaf lists its pairs too.
        let flat = tree_doc(&[(10, "<< /Names [(x) 1 (y) 2] >>")]);
        assert_eq!(keys(&flat), ["x", "y"]);
    }

    // Covers ISO 32000-1 §7.9.6.
    #[test]
    fn malformed_nodes_and_cycles_end_the_walk() {
        // A kid that refers back to the root, a kid that is not a
        // dictionary, a pair whose key is not a string and a leaf without
        // limits (searched regardless).
        let doc = tree_doc(&[
            (10, "<< /Kids [10 0 R 7 11 0 R 12 0 R] >>"),
            (
                11,
                "<< /Limits [(a) (b)] /Names [(a) 1 /NotAKey 9 (b) 2] >>",
            ),
            (12, "<< /Names [(c) 3] >>"),
        ]);
        assert_eq!(find(&doc, "c"), Some(Object::Int(3)));
        assert_eq!(find(&doc, "b"), Some(Object::Int(2)));
        assert_eq!(find(&doc, "zzz"), None);
        assert_eq!(keys(&doc), ["a", "b", "c"], "each leaf once, in order");
    }

    // Covers ISO 32000-1 §7.9.7.
    #[test]
    fn number_lookup_reads_nums_leaves() {
        let doc = tree_doc(&[
            (10, "<< /Kids [11 0 R 12 0 R] >>"),
            (11, "<< /Limits [0 9] /Nums [0 /Zero 9 /Nine] >>"),
            (12, "<< /Limits [10 19] /Nums [10 /Ten] >>"),
        ]);
        let ten = block_on(lookup(&Immediate(&doc), &root(&doc), &10i64));
        assert_eq!(ten, Some(Object::Name(crate::object::Name("Ten".into()))));
        let all: Vec<i64> = block_on(entries::<i64, _>(&Immediate(&doc), &root(&doc)))
            .into_iter()
            .map(|(k, _)| k)
            .collect();
        assert_eq!(all, [0, 9, 10]);
    }
}