Skip to main content

pdfboss_core/
structure.rs

1//! Structure-tree reading order (ISO 32000-1 §14.7): where a page's
2//! marked-content sequences sit in the document's logical structure, so a
3//! tagged page can be read in the order its author declared, and which
4//! structure type the element holding each sequence declares (§14.7.3).
5
6use std::sync::{Arc, Mutex};
7
8use crate::document::Page;
9use crate::hash::{FastMap, FastSet};
10use crate::object::{Dict, ObjRef, Object};
11use crate::source::AsyncObjectSource;
12use crate::tree::resolved_dict;
13
14/// Maximum number of ancestors walked from an element up to the root.
15/// Deeper ancestry reads as malformed and leaves the element unranked.
16const MAX_ELEMENT_DEPTH: usize = 64;
17
18/// One marked-content sequence: its `/MCID`, and the `/StructParents` key of
19/// the content stream it appeared in: the page's, or a form XObject's own
20/// when the form declares one (§14.7.4.4).
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct MarkedContentId {
23    pub parents: u32,
24    pub mcid: u32,
25}
26
27/// One content item of a structure element (§14.7.4): a marked-content
28/// sequence of a content stream, or a whole object, an annotation or an
29/// XObject, that the element holds through an object reference dictionary
30/// (`/Type /OBJR`, §14.7.4.3).
31///
32/// Covers ISO 32000-1 §14.7.4 and §14.7.4.3.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum ContentItem {
35    Sequence(MarkedContentId),
36    Object(ObjRef),
37}
38
39/// A content item with its place in the structure tree.
40///
41/// Covers ISO 32000-1 §14.7.4.
42#[derive(Debug, Clone, PartialEq)]
43pub struct PlacedItem {
44    pub item: ContentItem,
45    pub placement: Placement,
46}
47
48/// The four groups §14.8.4 sorts the standard structure types into, one per
49/// clause that defines them; the block-level and inline-level ones are the
50/// two the basic layout model of §14.8.3 lays out.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum StandardKind {
53    /// §14.8.4.2: elements that group other elements and hold no content
54    /// of their own.
55    Grouping,
56    /// §14.8.4.3: paragraphs, headings, lists and tables, laid out as
57    /// blocks.
58    BlockLevel,
59    /// §14.8.4.4: elements within a block's text.
60    InlineLevel,
61    /// §14.8.4.5: figures, formulas and forms.
62    Illustration,
63}
64
65/// The standard structure types of ISO 32000-1 §14.8.4, each variant spelled
66/// as the standard spells the `/S` name it stands for.
67///
68/// Covers ISO 32000-1 §14.8.4.
69#[allow(clippy::upper_case_acronyms)]
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub enum StandardType {
72    // Grouping elements (§14.8.4.2).
73    Document,
74    Part,
75    Art,
76    Sect,
77    Div,
78    BlockQuote,
79    Caption,
80    TOC,
81    TOCI,
82    Index,
83    NonStruct,
84    Private,
85    // Block-level structure elements (§14.8.4.3).
86    P,
87    H,
88    H1,
89    H2,
90    H3,
91    H4,
92    H5,
93    H6,
94    L,
95    LI,
96    Lbl,
97    LBody,
98    Table,
99    TR,
100    TH,
101    TD,
102    THead,
103    TBody,
104    TFoot,
105    // Inline-level structure elements (§14.8.4.4).
106    Span,
107    Quote,
108    Note,
109    Reference,
110    BibEntry,
111    Code,
112    Link,
113    Annot,
114    Ruby,
115    RB,
116    RT,
117    RP,
118    Warichu,
119    WT,
120    WP,
121    // Illustration elements (§14.8.4.5).
122    Figure,
123    Formula,
124    Form,
125}
126
127impl StandardType {
128    /// Every standard type, grouping elements first, in the standard's order.
129    pub const ALL: [StandardType; 49] = [
130        StandardType::Document,
131        StandardType::Part,
132        StandardType::Art,
133        StandardType::Sect,
134        StandardType::Div,
135        StandardType::BlockQuote,
136        StandardType::Caption,
137        StandardType::TOC,
138        StandardType::TOCI,
139        StandardType::Index,
140        StandardType::NonStruct,
141        StandardType::Private,
142        StandardType::P,
143        StandardType::H,
144        StandardType::H1,
145        StandardType::H2,
146        StandardType::H3,
147        StandardType::H4,
148        StandardType::H5,
149        StandardType::H6,
150        StandardType::L,
151        StandardType::LI,
152        StandardType::Lbl,
153        StandardType::LBody,
154        StandardType::Table,
155        StandardType::TR,
156        StandardType::TH,
157        StandardType::TD,
158        StandardType::THead,
159        StandardType::TBody,
160        StandardType::TFoot,
161        StandardType::Span,
162        StandardType::Quote,
163        StandardType::Note,
164        StandardType::Reference,
165        StandardType::BibEntry,
166        StandardType::Code,
167        StandardType::Link,
168        StandardType::Annot,
169        StandardType::Ruby,
170        StandardType::RB,
171        StandardType::RT,
172        StandardType::RP,
173        StandardType::Warichu,
174        StandardType::WT,
175        StandardType::WP,
176        StandardType::Figure,
177        StandardType::Formula,
178        StandardType::Form,
179    ];
180
181    /// The standard type a structure type name stands for, `None` for a name
182    /// outside the standard set. Names are case-sensitive; a document's own
183    /// types reach the standard set through the role map (§14.7.3), not here.
184    pub fn from_name(name: &str) -> Option<StandardType> {
185        StandardType::ALL.into_iter().find(|t| t.name() == name)
186    }
187
188    /// The name as the standard spells it.
189    pub fn name(self) -> &'static str {
190        match self {
191            StandardType::Document => "Document",
192            StandardType::Part => "Part",
193            StandardType::Art => "Art",
194            StandardType::Sect => "Sect",
195            StandardType::Div => "Div",
196            StandardType::BlockQuote => "BlockQuote",
197            StandardType::Caption => "Caption",
198            StandardType::TOC => "TOC",
199            StandardType::TOCI => "TOCI",
200            StandardType::Index => "Index",
201            StandardType::NonStruct => "NonStruct",
202            StandardType::Private => "Private",
203            StandardType::P => "P",
204            StandardType::H => "H",
205            StandardType::H1 => "H1",
206            StandardType::H2 => "H2",
207            StandardType::H3 => "H3",
208            StandardType::H4 => "H4",
209            StandardType::H5 => "H5",
210            StandardType::H6 => "H6",
211            StandardType::L => "L",
212            StandardType::LI => "LI",
213            StandardType::Lbl => "Lbl",
214            StandardType::LBody => "LBody",
215            StandardType::Table => "Table",
216            StandardType::TR => "TR",
217            StandardType::TH => "TH",
218            StandardType::TD => "TD",
219            StandardType::THead => "THead",
220            StandardType::TBody => "TBody",
221            StandardType::TFoot => "TFoot",
222            StandardType::Span => "Span",
223            StandardType::Quote => "Quote",
224            StandardType::Note => "Note",
225            StandardType::Reference => "Reference",
226            StandardType::BibEntry => "BibEntry",
227            StandardType::Code => "Code",
228            StandardType::Link => "Link",
229            StandardType::Annot => "Annot",
230            StandardType::Ruby => "Ruby",
231            StandardType::RB => "RB",
232            StandardType::RT => "RT",
233            StandardType::RP => "RP",
234            StandardType::Warichu => "Warichu",
235            StandardType::WT => "WT",
236            StandardType::WP => "WP",
237            StandardType::Figure => "Figure",
238            StandardType::Formula => "Formula",
239            StandardType::Form => "Form",
240        }
241    }
242
243    /// The clause of §14.8.4 that defines the type.
244    pub fn kind(self) -> StandardKind {
245        match self {
246            StandardType::Document
247            | StandardType::Part
248            | StandardType::Art
249            | StandardType::Sect
250            | StandardType::Div
251            | StandardType::BlockQuote
252            | StandardType::Caption
253            | StandardType::TOC
254            | StandardType::TOCI
255            | StandardType::Index
256            | StandardType::NonStruct
257            | StandardType::Private => StandardKind::Grouping,
258            StandardType::P
259            | StandardType::H
260            | StandardType::H1
261            | StandardType::H2
262            | StandardType::H3
263            | StandardType::H4
264            | StandardType::H5
265            | StandardType::H6
266            | StandardType::L
267            | StandardType::LI
268            | StandardType::Lbl
269            | StandardType::LBody
270            | StandardType::Table
271            | StandardType::TR
272            | StandardType::TH
273            | StandardType::TD
274            | StandardType::THead
275            | StandardType::TBody
276            | StandardType::TFoot => StandardKind::BlockLevel,
277            StandardType::Span
278            | StandardType::Quote
279            | StandardType::Note
280            | StandardType::Reference
281            | StandardType::BibEntry
282            | StandardType::Code
283            | StandardType::Link
284            | StandardType::Annot
285            | StandardType::Ruby
286            | StandardType::RB
287            | StandardType::RT
288            | StandardType::RP
289            | StandardType::Warichu
290            | StandardType::WT
291            | StandardType::WP => StandardKind::InlineLevel,
292            StandardType::Figure | StandardType::Formula | StandardType::Form => {
293                StandardKind::Illustration
294            }
295        }
296    }
297}
298
299/// One structure element on a placement's path: its standard type and the
300/// object holding it, so two neighbouring elements of one type are told
301/// apart, and its revision number.
302///
303/// Covers ISO 32000-1 §14.8.4.3.
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
305pub struct StructureElement {
306    pub standard_type: StandardType,
307    pub object: ObjRef,
308    /// The element's `/R` (§14.7.5.3), 0 when it declares none.
309    pub revision: i64,
310}
311
312/// The standard attribute owners of ISO 32000-1 Table 331: the four owners
313/// the standard defines attributes for, and the seven document formats whose
314/// own attributes an attribute object may carry.
315///
316/// Covers ISO 32000-1 §14.8.5.2.
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
318pub enum StandardOwner {
319    /// `Layout`: layout attributes (§14.8.5.4).
320    Layout,
321    /// `List`: the list attribute (§14.8.5.5).
322    List,
323    /// `PrintField`: print-field attributes (§14.8.5.6).
324    PrintField,
325    /// `Table`: table attributes (§14.8.5.7).
326    Table,
327    /// `XML-1.00`: attributes of XML 1.00.
328    Xml100,
329    /// `HTML-3.2`: attributes of HTML 3.2.
330    Html32,
331    /// `HTML-4.01`: attributes of HTML 4.01.
332    Html401,
333    /// `OEB-1.0`: attributes of the Open eBook 1.0 format.
334    Oeb10,
335    /// `RTF-1.05`: attributes of RTF 1.05.
336    Rtf105,
337    /// `CSS-1.00`: attributes of CSS 1.00.
338    Css100,
339    /// `CSS-2.00`: attributes of CSS 2.00.
340    Css200,
341}
342
343impl StandardOwner {
344    /// Every standard owner, in the order of Table 331.
345    pub const ALL: [StandardOwner; 11] = [
346        StandardOwner::Layout,
347        StandardOwner::List,
348        StandardOwner::PrintField,
349        StandardOwner::Table,
350        StandardOwner::Xml100,
351        StandardOwner::Html32,
352        StandardOwner::Html401,
353        StandardOwner::Oeb10,
354        StandardOwner::Rtf105,
355        StandardOwner::Css100,
356        StandardOwner::Css200,
357    ];
358
359    /// The standard owner an `/O` name stands for, `None` for a producer's
360    /// own owner (or `UserProperties`, §14.7.5.4, which is not one of them).
361    /// Names are case-sensitive.
362    pub fn from_name(name: &str) -> Option<StandardOwner> {
363        StandardOwner::ALL
364            .into_iter()
365            .find(|owner| owner.name() == name)
366    }
367
368    /// The name as the standard spells it.
369    pub fn name(self) -> &'static str {
370        match self {
371            StandardOwner::Layout => "Layout",
372            StandardOwner::List => "List",
373            StandardOwner::PrintField => "PrintField",
374            StandardOwner::Table => "Table",
375            StandardOwner::Xml100 => "XML-1.00",
376            StandardOwner::Html32 => "HTML-3.2",
377            StandardOwner::Html401 => "HTML-4.01",
378            StandardOwner::Oeb10 => "OEB-1.0",
379            StandardOwner::Rtf105 => "RTF-1.05",
380            StandardOwner::Css100 => "CSS-1.00",
381            StandardOwner::Css200 => "CSS-2.00",
382        }
383    }
384}
385
386/// One attribute object of a structure element (§14.7.5): the element it
387/// belongs to, its owner (`/O`, empty when it names none), the revision
388/// number that follows it in an `/A` or `/C` array (0 when none does,
389/// §14.7.5.3), and its entries as written, `/O` included.
390///
391/// Covers ISO 32000-1 §14.7.5.
392#[derive(Debug, Clone, PartialEq)]
393pub struct AttributeObject {
394    pub element: ObjRef,
395    pub owner: String,
396    pub revision: i64,
397    pub entries: Dict,
398}
399
400impl AttributeObject {
401    /// The standard owner the object's `/O` names (§14.8.5.2), `None` for a
402    /// producer's own.
403    pub fn standard_owner(&self) -> Option<StandardOwner> {
404        StandardOwner::from_name(&self.owner)
405    }
406}
407
408/// Where one marked-content sequence sits in the tree: its rank in the
409/// tree's depth-first order, and the structure type of the element holding
410/// it, as written, after the root's `/RoleMap`, and as a standard type,
411/// with the standard-typed elements above it.
412///
413/// Covers ISO 32000-1 §14.7.3 and §14.8.4.
414#[derive(Debug, Clone, PartialEq)]
415pub struct Placement {
416    pub rank: u32,
417    /// The element's `/S`, as the file writes it.
418    pub structure_type: Option<String>,
419    /// `structure_type` followed through the role map until a name the map
420    /// has no entry for; the same name when the map never names it.
421    pub mapped_type: Option<String>,
422    /// The standard type `mapped_type` names, `None` when it names none.
423    pub standard_type: Option<StandardType>,
424    /// The element and its ancestors that have a standard type, the root's
425    /// child first and the element itself last; an ancestor of no standard
426    /// type is skipped, its children keeping their place.
427    pub path: Vec<StructureElement>,
428    /// The alternate description (`/Alt`, §14.9.3) of the element, or of
429    /// the nearest ancestor that has one, decoded as a text string.
430    pub alt: Option<String>,
431    /// The language (`/Lang`, §14.9.2) of the element, or of the nearest
432    /// ancestor that declares one; `None` leaves the document's own.
433    pub lang: Option<String>,
434    /// The expansion (`/E`, §14.9.5) of the abbreviation or acronym the
435    /// element's text is, or the nearest ancestor's, decoded as a text
436    /// string.
437    pub expansion: Option<String>,
438    /// Every attribute object of the element and its ancestors (§14.7.5),
439    /// the root's child's first; for one element, the objects its `/C`
440    /// classes name come before its direct `/A` objects, so a later object
441    /// overrides an earlier one (see [`Placement::attribute`]).
442    pub attributes: Vec<AttributeObject>,
443}
444
445impl Placement {
446    /// The value `key` takes for `element` among its attribute objects of
447    /// `owner`: the last object that has the key wins, so a direct `/A`
448    /// object overrides a class's and a later class an earlier one, the
449    /// order §14.8.5.3 gives the standard attributes. Inheritance from an
450    /// ancestor is the caller's, since only some attributes inherit.
451    ///
452    /// Covers ISO 32000-1 §14.7.5, §14.7.5.2, §14.8.5 and §14.8.5.3.
453    pub fn attribute(&self, element: ObjRef, owner: &str, key: &str) -> Option<&Object> {
454        self.attributes
455            .iter()
456            .rev()
457            .filter(|a| a.element == element && a.owner == owner)
458            .find_map(|a| a.entries.get(key))
459    }
460}
461
462/// The document's structure tree root (`/StructTreeRoot`), loaded once per
463/// document and asked per page where that page's marked content sits in
464/// the tree. `/MarkInfo` is never consulted: a tree with leaves counts,
465/// whatever the file says about itself.
466#[derive(Debug)]
467pub struct StructureTree {
468    /// The root dictionary, shared by every fork of the tree.
469    root: Arc<Dict>,
470    root_ref: Option<ObjRef>,
471    /// What the page walks have read so far: element dictionaries, paths
472    /// and ancestries are the same for every page, so a walk takes them
473    /// over and hands them back when it is done.
474    caches: Mutex<Caches>,
475    /// The root's `/RoleMap`: structure type names to the names they stand
476    /// for (§14.7.3), entries whose value is not a name dropped.
477    role_map: FastMap<String, String>,
478    /// The root's `/ClassMap`: attribute class names to their attribute
479    /// objects, as written (§14.7.5.2).
480    class_map: FastMap<String, Object>,
481    /// Every entry of the root's `/ParentTree` (§14.7.4.4) by key, values
482    /// as written: an array of element references for a `/StructParents`
483    /// key, one element reference for a `/StructParent` key. Read once, so
484    /// no page searches the number tree again; shared by every fork.
485    parent_entries: Arc<FastMap<i64, Object>>,
486}
487
488impl Clone for StructureTree {
489    /// A fork: the same tree over an empty cache, which the copy fills on
490    /// its own. The root and the parent entries are shared, not copied, so
491    /// a page walk fanned out over threads forks the tree once per worker
492    /// and the workers contend on nothing.
493    fn clone(&self) -> StructureTree {
494        StructureTree {
495            root: Arc::clone(&self.root),
496            root_ref: self.root_ref,
497            caches: Mutex::new(Caches::default()),
498            role_map: self.role_map.clone(),
499            class_map: self.class_map.clone(),
500            parent_entries: Arc::clone(&self.parent_entries),
501        }
502    }
503}
504
505impl PartialEq for StructureTree {
506    /// Two trees are equal when they were read from the same root, whatever
507    /// either has cached since.
508    fn eq(&self, other: &StructureTree) -> bool {
509        self.root == other.root
510            && self.root_ref == other.root_ref
511            && self.role_map == other.role_map
512            && self.class_map == other.class_map
513            && self.parent_entries == other.parent_entries
514    }
515}
516
517impl StructureTree {
518    /// Loads the catalog's `/StructTreeRoot`, or `None` when the document
519    /// declares none, or when the entry is missing or unreadable, which
520    /// leaves every page in content order.
521    ///
522    /// Covers ISO 32000-1 §14.7.2.
523    pub async fn load_with<S: AsyncObjectSource>(src: &S, trailer: &Dict) -> Option<StructureTree> {
524        let root = trailer.get("Root")?;
525        let catalog = src.resolve(root).await.ok()?;
526        let entry = catalog.as_dict()?.get("StructTreeRoot")?;
527        let root_ref = entry.as_ref();
528        let resolved = src.resolve(entry).await.ok()?;
529        let root = resolved.as_dict()?.clone();
530        let role_map = match root.get("RoleMap") {
531            Some(entry) => role_map_of(resolved_dict(src, entry).await),
532            None => FastMap::default(),
533        };
534        let class_map = match root.get("ClassMap") {
535            Some(entry) => class_map_of(resolved_dict(src, entry).await),
536            None => FastMap::default(),
537        };
538        let parent_entries = match root.get("ParentTree") {
539            Some(entry) => parent_entries_of(src, resolved_dict(src, entry).await).await,
540            None => FastMap::default(),
541        };
542        Some(StructureTree {
543            root: Arc::new(root),
544            root_ref,
545            caches: Mutex::new(Caches::default()),
546            role_map,
547            class_map,
548            parent_entries: Arc::new(parent_entries),
549        })
550    }
551
552    /// `name` followed through the root's `/RoleMap` until a name the map
553    /// has no entry for. The map is meant to reach a standard structure type
554    /// (§14.8.4) in one step; a chain is followed in case a mapped name is
555    /// itself mapped, and a cycle leaves the name as written.
556    ///
557    /// Covers ISO 32000-1 §14.7.3.
558    pub fn mapped_type(&self, name: &str) -> String {
559        let mut seen: FastSet<&str> = FastSet::default();
560        let mut current = name;
561        while let Some(next) = self.role_map.get(current) {
562            if !seen.insert(current) {
563                return name.to_string();
564            }
565            current = next;
566        }
567        current.to_string()
568    }
569
570    /// The ranks of [`StructureTree::place_with`] alone: one page's
571    /// marked-content sequences by their position in the tree's depth-first
572    /// order, 0 for the first the tree reaches.
573    ///
574    /// Covers ISO 32000-1 §14.8.2 and §14.8.2.3.
575    pub async fn ranks_with<S: AsyncObjectSource>(
576        &self,
577        src: &S,
578        page: &Page,
579        ids: &[MarkedContentId],
580    ) -> FastMap<MarkedContentId, u32> {
581        self.place_with(src, page, ids)
582            .await
583            .into_iter()
584            .map(|(id, placement)| (id, placement.rank))
585            .collect()
586    }
587
588    /// Places `ids`, one page's marked-content sequences, in the tree: each
589    /// gets its rank in the tree's depth-first order (0 for the first the
590    /// tree reaches, and so on) and the structure type of the element holding
591    /// it. An id the tree never reaches (untagged content, a key the parent
592    /// tree lacks, an element whose ancestry is broken) is absent, so an
593    /// empty map means the page has no leaves in the tree.
594    ///
595    /// The lookup goes through the parent tree (`/ParentTree`, keyed by
596    /// `/StructParents`) and each element's `/P` chain, so it costs the page's
597    /// own elements, never a walk of the whole tree.
598    ///
599    /// Covers ISO 32000-1 §14.7.3, §14.8.2 and §14.8.2.3.
600    pub async fn place_with<S: AsyncObjectSource>(
601        &self,
602        src: &S,
603        page: &Page,
604        ids: &[MarkedContentId],
605    ) -> FastMap<MarkedContentId, Placement> {
606        let items: Vec<ContentItem> = ids.iter().copied().map(ContentItem::Sequence).collect();
607        self.place_items_with(src, page, &items)
608            .await
609            .into_iter()
610            .filter_map(|(item, placement)| match item {
611                ContentItem::Sequence(id) => Some((id, placement)),
612                ContentItem::Object(_) => None,
613            })
614            .collect()
615    }
616
617    /// Every content item of `page` the tree reaches, in the tree's
618    /// depth-first order: the marked-content sequences of the page's own
619    /// content stream (every entry of the parent tree's array under the
620    /// page's `/StructParents`) and the annotations of its `/Annots` that
621    /// carry a `/StructParent`. Ranks are shared, so an annotation's place
622    /// among the sequences is the place §14.8.2.3.2 gives it: right before
623    /// the sequence that follows it in the tree. The sequences of a form
624    /// XObject with its own `/StructParents` are not enumerated here; pass
625    /// them to [`StructureTree::place_items_with`] when a walk of the page
626    /// content has found them.
627    ///
628    /// Covers ISO 32000-1 §14.7.4, §14.7.4.3, §14.7.4.4 and §14.8.2.3.2.
629    pub async fn content_items_with<S: AsyncObjectSource>(
630        &self,
631        src: &S,
632        page: &Page,
633    ) -> Vec<PlacedItem> {
634        let mut walk = self.walk(src, page);
635        let mut items: Vec<ContentItem> = Vec::new();
636        let page_dict = page.dict();
637        let parents = page_dict
638            .get_int("StructParents")
639            .and_then(|n| u32::try_from(n).ok());
640        if let Some(parents) = parents {
641            if let Some(entries) = walk.parent_array(parents).await {
642                items.extend(
643                    entries
644                        .iter()
645                        .enumerate()
646                        .filter(|(_, entry)| entry.as_ref().is_some())
647                        .map(|(mcid, _)| {
648                            ContentItem::Sequence(MarkedContentId {
649                                parents,
650                                mcid: mcid as u32,
651                            })
652                        }),
653                );
654            }
655        }
656        for r in annotation_refs(src, page_dict).await {
657            let Some(dict) = walk.dict(r).await else {
658                continue;
659            };
660            if dict.get("StructParent").is_some() {
661                items.push(ContentItem::Object(r));
662            }
663        }
664        let placed_items = walk.place(self, &items).await;
665        self.store(walk);
666        let mut placed: Vec<PlacedItem> = placed_items
667            .into_iter()
668            .map(|(item, placement)| PlacedItem { item, placement })
669            .collect();
670        placed.sort_by_key(|placed| placed.placement.rank);
671        placed
672    }
673
674    /// How many content items the parent tree files for `page`: the
675    /// non-null entries of the array under the page's `/StructParents`, and
676    /// the annotations of `/Annots` whose `/StructParent` the tree has an
677    /// entry for. Read from the tree's entries alone, so no element is
678    /// parsed, which makes it a summary count rather than a placement: a
679    /// malformed file whose entries name elements that do not hold the
680    /// items counts higher here than [`StructureTree::content_items_with`]
681    /// lists.
682    ///
683    /// Covers ISO 32000-1 §14.7.4.4.
684    pub async fn content_item_counts_with<S: AsyncObjectSource>(
685        &self,
686        src: &S,
687        page: &Page,
688    ) -> (usize, usize) {
689        let page_dict = page.dict();
690        let mut sequences = 0;
691        let parents = page_dict
692            .get_int("StructParents")
693            .and_then(|key| self.parent_entries.get(&key));
694        if let Some(entry) = parents {
695            if let Ok(Object::Array(entries)) = src.resolve(entry).await {
696                sequences = entries.iter().filter(|e| e.as_ref().is_some()).count();
697            }
698        }
699        let mut objects = 0;
700        for r in annotation_refs(src, page_dict).await {
701            let Ok(Object::Dict(dict)) = src.get(r).await else {
702                continue;
703            };
704            let filed = dict
705                .get_int("StructParent")
706                .is_some_and(|key| self.parent_entries.contains_key(&key));
707            objects += usize::from(filed);
708        }
709        (sequences, objects)
710    }
711
712    /// Places `items`, content items of `page`, in the tree: each gets its
713    /// rank in the tree's depth-first order among the items given (0 for
714    /// the first the tree reaches, and so on) and the structure type of the
715    /// element holding it. A sequence is found through the parent tree's
716    /// array under its `/StructParents` key, an object through the parent
717    /// tree's entry under the object's own `/StructParent` (§14.7.4.4) and
718    /// then the object reference among the element's kids (§14.7.4.3). An
719    /// item the tree never reaches is absent.
720    ///
721    /// Covers ISO 32000-1 §14.7.4, §14.7.4.3 and §14.7.4.4.
722    pub async fn place_items_with<S: AsyncObjectSource>(
723        &self,
724        src: &S,
725        page: &Page,
726        items: &[ContentItem],
727    ) -> FastMap<ContentItem, Placement> {
728        let mut walk = self.walk(src, page);
729        let placed = walk.place(self, items).await;
730        self.store(walk);
731        placed
732    }
733
734    /// A walk of `page` over this tree's parent entries, carrying what
735    /// earlier walks read; [`StructureTree::store`] hands that back.
736    fn walk<'a, S: AsyncObjectSource>(&'a self, src: &'a S, page: &Page) -> Walk<'a, S> {
737        let caches = std::mem::take(&mut *self.caches.lock().unwrap_or_else(|e| e.into_inner()));
738        Walk {
739            src,
740            page_ref: page.object_ref(),
741            root_ref: self.root_ref,
742            class_map: &self.class_map,
743            parent_entries: &self.parent_entries,
744            caches,
745        }
746    }
747
748    /// Keeps what `walk` read for the pages that follow. A walk that ran
749    /// alongside another adds its entries to the ones already back.
750    fn store<S>(&self, walk: Walk<'_, S>) {
751        let mut caches = self.caches.lock().unwrap_or_else(|e| e.into_inner());
752        if caches.dicts.is_empty() {
753            *caches = walk.caches;
754            return;
755        }
756        caches.dicts.extend(walk.caches.dicts);
757        caches.paths.extend(walk.caches.paths);
758        caches.parents.extend(walk.caches.parents);
759        caches.ancestries.extend(walk.caches.ancestries);
760    }
761}
762
763impl<S: AsyncObjectSource> Walk<'_, S> {
764    /// [`StructureTree::place_items_with`] over this walk: the sort keys of
765    /// every item the tree reaches, then ranks by key order.
766    ///
767    /// Covers ISO 32000-1 §14.7.3, §14.8.2 and §14.8.2.3.
768    async fn place(
769        &mut self,
770        tree: &StructureTree,
771        items: &[ContentItem],
772    ) -> FastMap<ContentItem, Placement> {
773        let mut placed: FastMap<ContentItem, Placement> = FastMap::default();
774        let mut keyed: Vec<(ContentItem, Vec<u32>, Arc<Ancestry>)> = Vec::new();
775        let mut seen: FastSet<ContentItem> = FastSet::default();
776        for item in items {
777            if !seen.insert(*item) {
778                continue;
779            }
780            let found = match item {
781                ContentItem::Sequence(id) => self.key_of(*id).await,
782                ContentItem::Object(r) => self.object_key_of(*r).await,
783            };
784            let Some((key, ancestry)) = found else {
785                continue;
786            };
787            keyed.push((*item, key, ancestry));
788        }
789        keyed.sort_by(|a, b| a.1.cmp(&b.1));
790        for (rank, (id, _, ancestry)) in keyed.into_iter().enumerate() {
791            let structure_type = ancestry.last().and_then(|a| a.structure_type.clone());
792            let mapped_type = structure_type.as_deref().map(|s| tree.mapped_type(s));
793            let standard_type = mapped_type.as_deref().and_then(StandardType::from_name);
794            let path = ancestry
795                .iter()
796                .filter_map(|ancestor| {
797                    let name = ancestor.structure_type.as_deref()?;
798                    let standard_type = StandardType::from_name(&tree.mapped_type(name))?;
799                    Some(StructureElement {
800                        standard_type,
801                        object: ancestor.object,
802                        revision: ancestor.revision,
803                    })
804                })
805                .collect();
806            let alt = ancestry.iter().rev().find_map(|a| a.alt.clone());
807            let lang = ancestry.iter().rev().find_map(|a| a.lang.clone());
808            let expansion = ancestry.iter().rev().find_map(|a| a.expansion.clone());
809            let attributes = ancestry
810                .iter()
811                .flat_map(|ancestor| ancestor.attributes.iter().cloned())
812                .collect();
813            placed.insert(
814                id,
815                Placement {
816                    rank: rank as u32,
817                    structure_type,
818                    mapped_type,
819                    standard_type,
820                    path,
821                    alt,
822                    lang,
823                    expansion,
824                    attributes,
825                },
826            );
827        }
828        placed
829    }
830}
831
832/// What a walk reads that holds for every page of the document: element
833/// dictionaries, each element's kid-index path from the root, the parent
834/// tree's arrays by key, and each element's ancestry. Kept on the tree
835/// between walks, so the elements above a page's own (a document or a
836/// section, with kids by the thousand) are read once, not once per page.
837#[derive(Debug, Default)]
838struct Caches {
839    dicts: FastMap<ObjRef, Option<Arc<Dict>>>,
840    /// Each element's kid-index path from the root, or `None` once its
841    /// ancestry proved unwalkable.
842    paths: FastMap<ObjRef, Option<Arc<Vec<u32>>>>,
843    /// The parent tree's array for each `/StructParents` key seen.
844    parents: FastMap<u32, Option<Arc<Vec<Object>>>>,
845    /// Each element's ancestry, climbed once however many content items
846    /// the element holds.
847    ancestries: FastMap<ObjRef, Arc<Ancestry>>,
848}
849
850/// One element on the way from a marked-content sequence up to the root:
851/// its object, its `/S` as written, its `/Alt` and `/Lang` decoded, its
852/// `/R` and its attribute objects.
853#[derive(Debug)]
854struct Ancestor {
855    object: ObjRef,
856    structure_type: Option<String>,
857    alt: Option<String>,
858    lang: Option<String>,
859    expansion: Option<String>,
860    revision: i64,
861    attributes: Vec<AttributeObject>,
862}
863
864/// An element and its ancestors up to the root, the root's child first and
865/// the element last.
866type Ancestry = Vec<Ancestor>;
867
868/// The `/ClassMap` dictionary as class name to attribute objects, the
869/// objects kept as written and read when an element names the class.
870///
871/// Covers ISO 32000-1 §14.7.5.2.
872fn class_map_of(dict: Option<Dict>) -> FastMap<String, Object> {
873    let Some(dict) = dict else {
874        return FastMap::default();
875    };
876    dict.iter()
877        .map(|(key, value)| (key.0.clone(), value.clone()))
878        .collect()
879}
880
881/// One attribute object as read: its `/O` owner (empty when it names none)
882/// and its entries, at revision 0 until an array says otherwise. A
883/// `/UserProperties` object arrives like any other, its `/P` array
884/// uninterpreted.
885///
886/// Covers ISO 32000-1 §14.7.5 and §14.7.5.4.
887fn attribute_object(element: ObjRef, entries: Dict) -> AttributeObject {
888    AttributeObject {
889        element,
890        owner: entries
891            .get_name("O")
892            .map(|n| n.0.clone())
893            .unwrap_or_default(),
894        revision: 0,
895        entries,
896    }
897}
898
899/// Every entry of the parent tree by key, values as written; the first
900/// entry wins when a malformed tree repeats a key, as a search would find
901/// it first.
902///
903/// Covers ISO 32000-1 §14.7.4.4.
904async fn parent_entries_of<S: AsyncObjectSource>(
905    src: &S,
906    dict: Option<Dict>,
907) -> FastMap<i64, Object> {
908    let Some(dict) = dict else {
909        return FastMap::default();
910    };
911    let mut entries: FastMap<i64, Object> = FastMap::default();
912    for (key, value) in crate::tree::entries::<i64, S>(src, &dict).await {
913        entries.entry(key).or_insert(value);
914    }
915    entries
916}
917
918/// The `/RoleMap` dictionary as name-to-name pairs.
919///
920/// Covers ISO 32000-1 §14.7.3.
921fn role_map_of(dict: Option<Dict>) -> FastMap<String, String> {
922    let Some(dict) = dict else {
923        return FastMap::default();
924    };
925    dict.iter()
926        .filter_map(|(key, value)| Some((key.0.clone(), value.as_name()?.0.clone())))
927        .collect()
928}
929
930/// One page's walk through the tree: the dictionaries it has already read
931/// and the paths it has already computed, so a paragraph's ancestry is
932/// walked once for every marked-content sequence it contains.
933struct Walk<'a, S> {
934    src: &'a S,
935    page_ref: Option<ObjRef>,
936    root_ref: Option<ObjRef>,
937    /// The root's `/ClassMap`, for elements that name attribute classes.
938    class_map: &'a FastMap<String, Object>,
939    /// The parent tree's entries by key, as the tree read them once.
940    parent_entries: &'a FastMap<i64, Object>,
941    /// The tree's caches, taken over for the walk and handed back after.
942    caches: Caches,
943}
944
945impl<S: AsyncObjectSource> Walk<'_, S> {
946    /// The sort key of one marked-content sequence, its element's path from
947    /// the root then its own index among the element's kids, with the
948    /// element's ancestry and its structure types (`/S`, §14.7.3).
949    async fn key_of(&mut self, id: MarkedContentId) -> Option<(Vec<u32>, Arc<Ancestry>)> {
950        let elements = self.parent_array(id.parents).await?;
951        let element = elements.get(id.mcid as usize)?.as_ref()?;
952        let path = self.path_of(element).await?;
953        let dict = self.dict(element).await?;
954        let index = self.mcid_index(&dict, id).await?;
955        let mut key = Vec::with_capacity(path.len() + 1);
956        key.extend_from_slice(&path);
957        key.push(index);
958        Some((key, self.ancestry(element).await))
959    }
960
961    /// The sort key of one whole object, an annotation or an XObject: the
962    /// parent tree's entry under the object's own `/StructParent` is its
963    /// element (§14.7.4.4), and the object reference dictionary naming it
964    /// among the element's kids gives its index (§14.7.4.3).
965    ///
966    /// Covers ISO 32000-1 §14.7.4.3 and §14.7.4.4.
967    async fn object_key_of(&mut self, object: ObjRef) -> Option<(Vec<u32>, Arc<Ancestry>)> {
968        let dict = self.dict(object).await?;
969        let key = u32::try_from(dict.get_int("StructParent")?).ok()?;
970        let element = self.parent_element(key)?;
971        let path = self.path_of(element).await?;
972        let dict = self.dict(element).await?;
973        let index = self.object_index(&dict, object).await?;
974        let mut key = Vec::with_capacity(path.len() + 1);
975        key.extend_from_slice(&path);
976        key.push(index);
977        Some((key, self.ancestry(element).await))
978    }
979
980    /// The parent tree's entry for a `/StructParent` key: a reference to
981    /// the element holding the object as a content item. An array there
982    /// (a `/StructParents` entry named by mistake) is no element.
983    ///
984    /// Covers ISO 32000-1 §14.7.4.4.
985    fn parent_element(&self, key: u32) -> Option<ObjRef> {
986        self.parent_entries
987            .get(&i64::from(key))
988            .and_then(Object::as_ref)
989    }
990
991    /// The index among `element`'s kids of the object reference dictionary
992    /// (§14.7.4.3, Table 325) whose `/Obj` is `object` and whose page is
993    /// this one: the dictionary's own `/Pg`, or the element's when it has
994    /// none. A direct dictionary is taken first; only when there is none
995    /// are the indirect kids read.
996    ///
997    /// Covers ISO 32000-1 §14.7.4.3.
998    async fn object_index(&mut self, element: &Dict, object: ObjRef) -> Option<u32> {
999        let kids = kids_of(element);
1000        let page_ref = self.page_ref;
1001        let element_page = element.get_ref("Pg");
1002        let refers = |d: &Dict| {
1003            d.get_ref("Obj") == Some(object) && on_page(d.get_ref("Pg").or(element_page), page_ref)
1004        };
1005        let direct = kids.iter().position(|kid| match kid {
1006            Object::Dict(d) => refers(d),
1007            _ => false,
1008        });
1009        if let Some(index) = direct {
1010            return u32::try_from(index).ok();
1011        }
1012        for (index, kid) in kids.iter().enumerate() {
1013            let Some(r) = kid.as_ref() else {
1014                continue;
1015            };
1016            let Some(dict) = self.dict(r).await else {
1017                continue;
1018            };
1019            if refers(&dict) {
1020                return u32::try_from(index).ok();
1021            }
1022        }
1023        None
1024    }
1025
1026    /// Whether a marked-content reference dictionary (§14.7.4.2, Table 324)
1027    /// is the one for `id`: its `/MCID` matches, its page (its own `/Pg`,
1028    /// or the element's when it has none) is this one, and when it names
1029    /// the content stream the sequence sits in (`/Stm`), that stream's
1030    /// `/StructParents` is the key the sequence was filed under, so a
1031    /// form's sequence and the page's cannot stand in for each other. A
1032    /// stream without the key is not held against the match.
1033    ///
1034    /// Covers ISO 32000-1 §14.7.4.2.
1035    async fn is_reference_for(
1036        &mut self,
1037        dict: &Dict,
1038        id: MarkedContentId,
1039        element_page: Option<ObjRef>,
1040    ) -> bool {
1041        if !is_mcr(dict, id.mcid) || !on_page(dict.get_ref("Pg").or(element_page), self.page_ref) {
1042            return false;
1043        }
1044        let Some(stream) = dict.get_ref("Stm") else {
1045            return true;
1046        };
1047        let Some(stream) = self.dict(stream).await else {
1048            return true;
1049        };
1050        match stream.get_int("StructParents") {
1051            Some(parents) => u32::try_from(parents).is_ok_and(|p| p == id.parents),
1052            None => true,
1053        }
1054    }
1055
1056    /// The element and its ancestors up to the root, the root's child first
1057    /// and the element itself last, each with its `/S`, `/Alt`, `/Lang`,
1058    /// `/R` and attribute objects: what a placement's structure type, path,
1059    /// description, language and attributes are read from. The climb stops
1060    /// at the root, at a missing `/P`, or after [`MAX_ELEMENT_DEPTH`]
1061    /// elements; the dictionaries are the ones [`Walk::path_of`] already
1062    /// read.
1063    ///
1064    /// Covers ISO 32000-1 §14.7.3, §14.7.5, §14.8.4.3, §14.9.2, §14.9.3 and
1065    /// §14.9.5.
1066    async fn ancestry(&mut self, element: ObjRef) -> Arc<Ancestry> {
1067        if let Some(cached) = self.caches.ancestries.get(&element) {
1068            return cached.clone();
1069        }
1070        let chain = Arc::new(self.climb(element).await);
1071        self.caches.ancestries.insert(element, chain.clone());
1072        chain
1073    }
1074
1075    /// [`Walk::ancestry`] without the cache: the climb itself.
1076    async fn climb(&mut self, element: ObjRef) -> Ancestry {
1077        let mut chain: Ancestry = Vec::new();
1078        let mut current = element;
1079        for _ in 0..MAX_ELEMENT_DEPTH {
1080            let Some(dict) = self.dict(current).await else {
1081                break;
1082            };
1083            let alt = match dict.get("Alt") {
1084                Some(alt) => self.text_string(alt).await,
1085                None => None,
1086            };
1087            let lang = match dict.get("Lang") {
1088                Some(lang) => self.text_string(lang).await,
1089                None => None,
1090            };
1091            let expansion = match dict.get("E") {
1092                Some(expansion) => self.text_string(expansion).await,
1093                None => None,
1094            };
1095            let attributes = self.attribute_objects(current, &dict).await;
1096            chain.push(Ancestor {
1097                object: current,
1098                structure_type: dict.get_name("S").map(|n| n.0.clone()),
1099                alt,
1100                lang,
1101                expansion,
1102                revision: dict.get_int("R").unwrap_or(0),
1103                attributes,
1104            });
1105            let Some(parent) = dict.get("P").and_then(Object::as_ref) else {
1106                break;
1107            };
1108            if self.is_root(parent).await {
1109                break;
1110            }
1111            current = parent;
1112        }
1113        chain.reverse();
1114        chain
1115    }
1116
1117    /// The attribute objects of one element (§14.7.5): those its `/C`
1118    /// classes name in the root's `/ClassMap` (§14.7.5.2) first, then its
1119    /// direct `/A` objects, so a later object overrides an earlier one.
1120    ///
1121    /// Covers ISO 32000-1 §14.7.5 and §14.7.5.2.
1122    async fn attribute_objects(&mut self, element: ObjRef, dict: &Dict) -> Vec<AttributeObject> {
1123        let mut out = Vec::new();
1124        if let Some(classes) = dict.get("C") {
1125            self.class_attributes(element, classes, &mut out).await;
1126        }
1127        if let Some(direct) = dict.get("A") {
1128            self.direct_attributes(element, direct, &mut out).await;
1129        }
1130        out
1131    }
1132
1133    /// `value` as attribute objects: one dictionary, or an array of
1134    /// dictionaries, indirect ones resolved, each optionally followed by an
1135    /// integer, its revision number; anything else in the array is skipped.
1136    ///
1137    /// Covers ISO 32000-1 §14.7.5 and §14.7.5.3.
1138    async fn direct_attributes(
1139        &mut self,
1140        element: ObjRef,
1141        value: &Object,
1142        out: &mut Vec<AttributeObject>,
1143    ) {
1144        let Ok(resolved) = self.src.resolve(value).await else {
1145            return;
1146        };
1147        let items = match resolved {
1148            Object::Dict(dict) => {
1149                out.push(attribute_object(element, dict));
1150                return;
1151            }
1152            Object::Array(items) => items,
1153            _ => return,
1154        };
1155        let start = out.len();
1156        for item in items {
1157            if let Some(revision) = item.as_int() {
1158                if out.len() > start {
1159                    if let Some(last) = out.last_mut() {
1160                        last.revision = revision;
1161                    }
1162                }
1163                continue;
1164            }
1165            let Ok(resolved) = self.src.resolve(&item).await else {
1166                continue;
1167            };
1168            if let Object::Dict(dict) = resolved {
1169                out.push(attribute_object(element, dict));
1170            }
1171        }
1172    }
1173
1174    /// `value` as attribute class names, one or an array each optionally
1175    /// followed by a revision number that then applies to every object the
1176    /// class brought; a name the root's `/ClassMap` lacks brings nothing.
1177    ///
1178    /// Covers ISO 32000-1 §14.7.5.2 and §14.7.5.3.
1179    async fn class_attributes(
1180        &mut self,
1181        element: ObjRef,
1182        value: &Object,
1183        out: &mut Vec<AttributeObject>,
1184    ) {
1185        let Ok(resolved) = self.src.resolve(value).await else {
1186            return;
1187        };
1188        let names = match resolved {
1189            Object::Name(_) => vec![resolved],
1190            Object::Array(items) => items,
1191            _ => return,
1192        };
1193        let mut added = 0..0;
1194        for item in names {
1195            if let Some(revision) = item.as_int() {
1196                for attribute in &mut out[added.clone()] {
1197                    attribute.revision = revision;
1198                }
1199                continue;
1200            }
1201            let Some(name) = item.as_name() else {
1202                continue;
1203            };
1204            let Some(class) = self.class_map.get(&name.0).cloned() else {
1205                continue;
1206            };
1207            let start = out.len();
1208            self.direct_attributes(element, &class, out).await;
1209            added = start..out.len();
1210        }
1211    }
1212
1213    /// A text string entry (§7.9.2.2), resolved and decoded; `None` for
1214    /// anything but a string.
1215    async fn text_string(&mut self, value: &Object) -> Option<String> {
1216        let resolved = self.src.resolve(value).await.ok()?;
1217        Some(crate::object::decode_text_string(resolved.as_str_bytes()?))
1218    }
1219
1220    /// The parent tree's entry for a `/StructParents` key: the array whose
1221    /// index is a marked-content id and whose value is that id's element.
1222    ///
1223    /// Covers ISO 32000-1 §14.7.4.4.
1224    async fn parent_array(&mut self, key: u32) -> Option<Arc<Vec<Object>>> {
1225        if let Some(cached) = self.caches.parents.get(&key) {
1226            return cached.clone();
1227        }
1228        let found = match self.parent_entries.get(&i64::from(key)) {
1229            Some(entry) => match self.src.resolve(entry).await.ok()? {
1230                Object::Array(items) => Some(Arc::new(items)),
1231                _ => None,
1232            },
1233            None => None,
1234        };
1235        self.caches.parents.insert(key, found.clone());
1236        found
1237    }
1238
1239    async fn dict(&mut self, r: ObjRef) -> Option<Arc<Dict>> {
1240        if let Some(cached) = self.caches.dicts.get(&r) {
1241            return cached.clone();
1242        }
1243        let loaded = match self.src.get(r).await.ok()? {
1244            Object::Dict(dict) => Some(Arc::new(dict)),
1245            Object::Stream(stream) => Some(Arc::new(stream.dict)),
1246            _ => None,
1247        };
1248        self.caches.dicts.insert(r, loaded.clone());
1249        loaded
1250    }
1251
1252    /// An element's kid-index path from the root: the ancestry is followed
1253    /// up through `/P` until it reaches the structure tree root, then each
1254    /// ancestor's index among its parent's kids is read on the way back down.
1255    /// Every ancestor's own path is remembered as a by-product.
1256    ///
1257    /// Covers ISO 32000-1 §14.7.2.
1258    async fn path_of(&mut self, element: ObjRef) -> Option<Arc<Vec<u32>>> {
1259        if let Some(cached) = self.caches.paths.get(&element) {
1260            return cached.clone();
1261        }
1262        let mut chain: Vec<ObjRef> = vec![element];
1263        // The ancestor the climb stopped at, with its path: the root
1264        // itself, or an ancestor whose path an earlier climb computed.
1265        let mut stop: Option<(ObjRef, bool, Arc<Vec<u32>>)> = None;
1266        let mut current = element;
1267        for _ in 0..MAX_ELEMENT_DEPTH {
1268            let Some(dict) = self.dict(current).await else {
1269                break;
1270            };
1271            let Some(parent) = dict.get("P").and_then(Object::as_ref) else {
1272                break;
1273            };
1274            if self.is_root(parent).await {
1275                stop = Some((parent, true, Arc::new(Vec::new())));
1276                break;
1277            }
1278            if let Some(cached) = self.caches.paths.get(&parent) {
1279                stop = cached.clone().map(|path| (parent, false, path));
1280                break;
1281            }
1282            chain.push(parent);
1283            current = parent;
1284        }
1285        let Some((mut parent_ref, mut parent_is_root, known)) = stop else {
1286            for r in chain {
1287                self.caches.paths.insert(r, None);
1288            }
1289            return None;
1290        };
1291        // From the topmost unresolved ancestor down to the element itself.
1292        let mut path: Vec<u32> = (*known).clone();
1293        let mut resolved: Option<Arc<Vec<u32>>> = None;
1294        for child in chain.into_iter().rev() {
1295            let index = if parent_is_root {
1296                self.root_kid_index(child).await
1297            } else {
1298                match self.dict(parent_ref).await {
1299                    Some(parent) => kid_index(&parent, child),
1300                    None => None,
1301                }
1302            };
1303            let Some(index) = index else {
1304                self.caches.paths.insert(child, None);
1305                return None;
1306            };
1307            path.push(index);
1308            let shared = Arc::new(path.clone());
1309            self.caches.paths.insert(child, Some(shared.clone()));
1310            resolved = Some(shared);
1311            parent_ref = child;
1312            parent_is_root = false;
1313        }
1314        resolved
1315    }
1316
1317    /// Whether `r` is the structure tree root: the reference the catalog
1318    /// named, or failing that a dictionary typed `/StructTreeRoot`.
1319    async fn is_root(&mut self, r: ObjRef) -> bool {
1320        if self.root_ref == Some(r) {
1321            return true;
1322        }
1323        let Some(dict) = self.dict(r).await else {
1324            return false;
1325        };
1326        dict.get_name("Type")
1327            .is_some_and(|n| n.0 == "StructTreeRoot")
1328    }
1329
1330    /// The index of a top-level element among the root's `/K` kids.
1331    async fn root_kid_index(&mut self, child: ObjRef) -> Option<u32> {
1332        let root_ref = self.root_ref?;
1333        let root = self.dict(root_ref).await?;
1334        kid_index(&root, child)
1335    }
1336
1337    /// The index among `element`'s kids of the marked-content sequence
1338    /// numbered `mcid` on this page: a bare integer (the element's `/Pg`
1339    /// page) or a marked-content reference dictionary naming the page. A
1340    /// direct match is taken first; only when there is none are the
1341    /// indirect kids read, in case the reference dictionary is one of them.
1342    ///
1343    /// Covers ISO 32000-1 §14.7.4 and §14.7.4.2.
1344    async fn mcid_index(&mut self, element: &Dict, id: MarkedContentId) -> Option<u32> {
1345        let kids = kids_of(element);
1346        let page_ref = self.page_ref;
1347        let element_page = element.get_ref("Pg");
1348        for (index, kid) in kids.iter().enumerate() {
1349            let found = match kid {
1350                Object::Int(n) => {
1351                    u32::try_from(*n).is_ok_and(|n| n == id.mcid) && on_page(element_page, page_ref)
1352                }
1353                Object::Dict(d) => self.is_reference_for(d, id, element_page).await,
1354                _ => false,
1355            };
1356            if found {
1357                return u32::try_from(index).ok();
1358            }
1359        }
1360        for (index, kid) in kids.iter().enumerate() {
1361            let Some(r) = kid.as_ref() else {
1362                continue;
1363            };
1364            let Some(dict) = self.dict(r).await else {
1365                continue;
1366            };
1367            if self.is_reference_for(&dict, id, element_page).await {
1368                return u32::try_from(index).ok();
1369            }
1370        }
1371        None
1372    }
1373}
1374
1375/// The indirect references in a page's `/Annots`, in order. A direct
1376/// annotation dictionary has no reference an object reference dictionary
1377/// could name, so it is skipped.
1378async fn annotation_refs<S: AsyncObjectSource>(src: &S, page: &Dict) -> Vec<ObjRef> {
1379    let Some(annots) = page.get("Annots") else {
1380        return Vec::new();
1381    };
1382    let Ok(Object::Array(items)) = src.resolve(annots).await else {
1383        return Vec::new();
1384    };
1385    items.iter().filter_map(Object::as_ref).collect()
1386}
1387
1388/// Whether a kid's `/Pg` names this page. Either side unknown reads as a
1389/// match: a page inlined into `/Kids` has no reference to compare, and a
1390/// bare integer kid under an element without `/Pg` has nothing to compare
1391/// against.
1392fn on_page(pg: Option<ObjRef>, page_ref: Option<ObjRef>) -> bool {
1393    match (pg, page_ref) {
1394        (Some(pg), Some(page)) => pg == page,
1395        _ => true,
1396    }
1397}
1398
1399/// Whether `dict` is the marked-content reference for `mcid`.
1400///
1401/// Covers ISO 32000-1 §14.7.4.2.
1402fn is_mcr(dict: &Dict, mcid: u32) -> bool {
1403    dict.get_int("MCID")
1404        .and_then(|n| u32::try_from(n).ok())
1405        .is_some_and(|n| n == mcid)
1406}
1407
1408/// An element's `/K` as a list: a single kid stands alone, an array is its
1409/// items, nothing is empty.
1410fn kids_of(element: &Dict) -> &[Object] {
1411    match element.get("K") {
1412        Some(Object::Array(items)) => items,
1413        Some(single) => std::slice::from_ref(single),
1414        None => &[],
1415    }
1416}
1417
1418/// The index of `child` among `parent`'s kids, by reference identity.
1419fn kid_index(parent: &Dict, child: ObjRef) -> Option<u32> {
1420    let index = kids_of(parent)
1421        .iter()
1422        .position(|kid| kid.as_ref() == Some(child))?;
1423    u32::try_from(index).ok()
1424}
1425
1426#[cfg(test)]
1427mod tests {
1428    use super::*;
1429    use crate::{block_on, Document, Immediate};
1430    use pdfboss_testkit::PdfBuilder;
1431
1432    fn id(parents: u32, mcid: u32) -> MarkedContentId {
1433        MarkedContentId { parents, mcid }
1434    }
1435
1436    /// A one-page document whose catalog names object 10 as the structure
1437    /// tree root; `objects` supplies the tree (10 and up) and `page_extra`
1438    /// lands in the page dictionary.
1439    fn tagged_doc(page_extra: &str, objects: &[(u32, &str)]) -> Document {
1440        let mut b = PdfBuilder::new();
1441        b.object(
1442            1,
1443            "<< /Type /Catalog /Pages 2 0 R /StructTreeRoot 10 0 R >>",
1444        );
1445        b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1446        b.object(
1447            3,
1448            &format!("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] {page_extra} >>"),
1449        );
1450        for (num, body) in objects {
1451            b.object(*num, body);
1452        }
1453        Document::load(b.build(1)).expect("load")
1454    }
1455
1456    /// Two paragraphs on one page, the left one holding ids 0 and 2, the
1457    /// right one 1 and 3: tree order is 0, 2, 1, 3.
1458    fn two_paragraphs(parent_tree: &str) -> Document {
1459        tagged_doc(
1460            "/StructParents 0",
1461            &[
1462                (
1463                    10,
1464                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
1465                ),
1466                (
1467                    11,
1468                    "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R 14 0 R] >>",
1469                ),
1470                (12, parent_tree),
1471                (
1472                    13,
1473                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [0 2] >>",
1474                ),
1475                (
1476                    14,
1477                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [1 3] >>",
1478                ),
1479            ],
1480        )
1481    }
1482
1483    fn ranks(doc: &Document, ids: &[MarkedContentId]) -> FastMap<MarkedContentId, u32> {
1484        let tree = doc.structure_tree().expect("tree");
1485        let page = doc.page(0).unwrap();
1486        block_on(tree.ranks_with(&Immediate(doc), &page, ids))
1487    }
1488
1489    fn placements(doc: &Document, ids: &[MarkedContentId]) -> FastMap<MarkedContentId, Placement> {
1490        let tree = doc.structure_tree().expect("tree");
1491        let page = doc.page(0).unwrap();
1492        block_on(tree.place_with(&Immediate(doc), &page, ids))
1493    }
1494
1495    // Covers ISO 32000-1 §14.7.3.
1496    #[test]
1497    fn structure_types_are_read_and_mapped_through_the_role_map() {
1498        // Four elements typed Para, Sub, Loop1 and P. The role map, an
1499        // indirect object, maps Para to P, Sub to Head and Head to H1, and
1500        // Loop1 and Loop2 to each other.
1501        let doc = tagged_doc(
1502            "/StructParents 0",
1503            &[
1504                (
1505                    10,
1506                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R /RoleMap 20 0 R >>",
1507                ),
1508                (
1509                    11,
1510                    "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R 14 0 R 15 0 R 16 0 R] >>",
1511                ),
1512                (12, "<< /Nums [0 [13 0 R 14 0 R 15 0 R 16 0 R]] >>"),
1513                (
1514                    13,
1515                    "<< /Type /StructElem /S /Para /P 11 0 R /Pg 3 0 R /K [0] >>",
1516                ),
1517                (
1518                    14,
1519                    "<< /Type /StructElem /S /Sub /P 11 0 R /Pg 3 0 R /K [1] >>",
1520                ),
1521                (
1522                    15,
1523                    "<< /Type /StructElem /S /Loop1 /P 11 0 R /Pg 3 0 R /K [2] >>",
1524                ),
1525                (
1526                    16,
1527                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [3] >>",
1528                ),
1529                (
1530                    20,
1531                    "<< /Para /P /Sub /Head /Head /H1 /Loop1 /Loop2 /Loop2 /Loop1 >>",
1532                ),
1533            ],
1534        );
1535        let placed = placements(&doc, &[id(0, 3), id(0, 2), id(0, 1), id(0, 0)]);
1536        let typed = |n: u32| {
1537            let p = &placed[&id(0, n)];
1538            (p.structure_type.as_deref(), p.mapped_type.as_deref())
1539        };
1540        assert_eq!(typed(0), (Some("Para"), Some("P")));
1541        assert_eq!(typed(1), (Some("Sub"), Some("H1")));
1542        assert_eq!(typed(2), (Some("Loop1"), Some("Loop1")));
1543        assert_eq!(typed(3), (Some("P"), Some("P")));
1544        assert_eq!(placed[&id(0, 3)].rank, 3);
1545        assert_eq!(placed[&id(0, 0)].standard_type, Some(StandardType::P));
1546        assert_eq!(placed[&id(0, 1)].standard_type, Some(StandardType::H1));
1547        assert_eq!(placed[&id(0, 2)].standard_type, None);
1548        let tree = doc.structure_tree().unwrap();
1549        assert_eq!(tree.mapped_type("Head"), "H1");
1550        assert_eq!(tree.mapped_type("Span"), "Span");
1551    }
1552
1553    // Covers ISO 32000-1 §14.7.3.
1554    #[test]
1555    fn an_element_without_a_type_places_with_none() {
1556        let doc = tagged_doc(
1557            "/StructParents 0",
1558            &[
1559                (
1560                    10,
1561                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
1562                ),
1563                (11, "<< /Type /StructElem /P 10 0 R /Pg 3 0 R /K [0] >>"),
1564                (12, "<< /Nums [0 [11 0 R]] >>"),
1565            ],
1566        );
1567        let placed = placements(&doc, &[id(0, 0)]);
1568        assert_eq!(placed[&id(0, 0)].rank, 0);
1569        assert_eq!(placed[&id(0, 0)].structure_type, None);
1570        assert_eq!(placed[&id(0, 0)].mapped_type, None);
1571        assert_eq!(placed[&id(0, 0)].standard_type, None);
1572    }
1573
1574    // Covers ISO 32000-1 §14.7.3 and §14.8.4.3.
1575    #[test]
1576    fn placements_carry_the_standard_typed_ancestry() {
1577        // Document > Sect > H holds id 0; Document > L > LI > Lbl holds 1
1578        // and > LBody > P holds 2; a Sidebar element of no standard type
1579        // holds a P with id 3.
1580        let doc = tagged_doc(
1581            "/StructParents 0",
1582            &[
1583                (
1584                    10,
1585                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
1586                ),
1587                (
1588                    11,
1589                    "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R 15 0 R 20 0 R] >>",
1590                ),
1591                (12, "<< /Nums [0 [14 0 R 17 0 R 19 0 R 21 0 R]] >>"),
1592                (13, "<< /Type /StructElem /S /Sect /P 11 0 R /K [14 0 R] >>"),
1593                (
1594                    14,
1595                    "<< /Type /StructElem /S /H /P 13 0 R /Pg 3 0 R /K [0] >>",
1596                ),
1597                (15, "<< /Type /StructElem /S /L /P 11 0 R /K [16 0 R] >>"),
1598                (
1599                    16,
1600                    "<< /Type /StructElem /S /LI /P 15 0 R /K [17 0 R 18 0 R] >>",
1601                ),
1602                (
1603                    17,
1604                    "<< /Type /StructElem /S /Lbl /P 16 0 R /Pg 3 0 R /K [1] >>",
1605                ),
1606                (
1607                    18,
1608                    "<< /Type /StructElem /S /LBody /P 16 0 R /K [19 0 R] >>",
1609                ),
1610                (
1611                    19,
1612                    "<< /Type /StructElem /S /P /P 18 0 R /Pg 3 0 R /K [2] >>",
1613                ),
1614                (
1615                    20,
1616                    "<< /Type /StructElem /S /Sidebar /P 11 0 R /K [21 0 R] >>",
1617                ),
1618                (
1619                    21,
1620                    "<< /Type /StructElem /S /P /P 20 0 R /Pg 3 0 R /K [3] >>",
1621                ),
1622            ],
1623        );
1624        let placed = placements(&doc, &[id(0, 0), id(0, 1), id(0, 2), id(0, 3)]);
1625        let kinds = |n: u32| -> Vec<StandardType> {
1626            placed[&id(0, n)]
1627                .path
1628                .iter()
1629                .map(|e| e.standard_type)
1630                .collect()
1631        };
1632        let objects = |n: u32| -> Vec<u32> {
1633            placed[&id(0, n)]
1634                .path
1635                .iter()
1636                .map(|e| e.object.num)
1637                .collect()
1638        };
1639        assert_eq!(
1640            kinds(0),
1641            [StandardType::Document, StandardType::Sect, StandardType::H]
1642        );
1643        assert_eq!(objects(0), [11, 13, 14]);
1644        assert_eq!(
1645            kinds(1),
1646            [
1647                StandardType::Document,
1648                StandardType::L,
1649                StandardType::LI,
1650                StandardType::Lbl
1651            ]
1652        );
1653        assert_eq!(
1654            kinds(2),
1655            [
1656                StandardType::Document,
1657                StandardType::L,
1658                StandardType::LI,
1659                StandardType::LBody,
1660                StandardType::P
1661            ]
1662        );
1663        assert_eq!(kinds(3), [StandardType::Document, StandardType::P]);
1664        assert_eq!(objects(3), [11, 21]);
1665    }
1666
1667    // Covers ISO 32000-1 §14.9.3.
1668    #[test]
1669    fn placements_carry_the_nearest_alternate_description() {
1670        // A Figure with /Alt holding a P with id 0; a P with no /Alt anywhere
1671        // above it holding id 1; a Span with a UTF-16 /Alt holding id 2.
1672        let doc = tagged_doc(
1673            "/StructParents 0",
1674            &[
1675                (
1676                    10,
1677                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
1678                ),
1679                (
1680                    11,
1681                    "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R 15 0 R 16 0 R] >>",
1682                ),
1683                (12, "<< /Nums [0 [14 0 R 15 0 R 16 0 R]] >>"),
1684                (
1685                    13,
1686                    "<< /Type /StructElem /S /Figure /P 11 0 R /Alt (A chart) /K [14 0 R] >>",
1687                ),
1688                (
1689                    14,
1690                    "<< /Type /StructElem /S /P /P 13 0 R /Pg 3 0 R /K [0] >>",
1691                ),
1692                (
1693                    15,
1694                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [1] >>",
1695                ),
1696                (
1697                    16,
1698                    "<< /Type /StructElem /S /Span /P 11 0 R /Pg 3 0 R /K [2] /Alt <FEFF00E9> >>",
1699                ),
1700            ],
1701        );
1702        let placed = placements(&doc, &[id(0, 0), id(0, 1), id(0, 2)]);
1703        assert_eq!(placed[&id(0, 0)].alt.as_deref(), Some("A chart"));
1704        assert_eq!(placed[&id(0, 1)].alt, None);
1705        assert_eq!(placed[&id(0, 2)].alt.as_deref(), Some("\u{e9}"));
1706    }
1707
1708    // Covers ISO 32000-1 §14.9.5.
1709    #[test]
1710    fn placements_carry_the_nearest_expansion() {
1711        // A Span with /E holding a Span with id 0; a P with no /E anywhere
1712        // above it holding id 1; a Span with a UTF-16 /E holding id 2.
1713        let doc = tagged_doc(
1714            "/StructParents 0",
1715            &[
1716                (
1717                    10,
1718                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
1719                ),
1720                (
1721                    11,
1722                    "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R 15 0 R 16 0 R] >>",
1723                ),
1724                (12, "<< /Nums [0 [14 0 R 15 0 R 16 0 R]] >>"),
1725                (
1726                    13,
1727                    "<< /Type /StructElem /S /Span /P 11 0 R /E (Portable Document Format) /K [14 0 R] >>",
1728                ),
1729                (
1730                    14,
1731                    "<< /Type /StructElem /S /Span /P 13 0 R /Pg 3 0 R /K [0] >>",
1732                ),
1733                (
1734                    15,
1735                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [1] >>",
1736                ),
1737                (
1738                    16,
1739                    "<< /Type /StructElem /S /Span /P 11 0 R /Pg 3 0 R /K [2] /E <FEFF00C9> >>",
1740                ),
1741            ],
1742        );
1743        let placed = placements(&doc, &[id(0, 0), id(0, 1), id(0, 2)]);
1744        assert_eq!(
1745            placed[&id(0, 0)].expansion.as_deref(),
1746            Some("Portable Document Format")
1747        );
1748        assert_eq!(placed[&id(0, 1)].expansion, None);
1749        assert_eq!(placed[&id(0, 2)].expansion.as_deref(), Some("\u{c9}"));
1750    }
1751
1752    // Covers ISO 32000-1 §14.9.2 and §14.9.2.3.
1753    #[test]
1754    fn placements_carry_the_nearest_language() {
1755        // The Document element says en; the second paragraph says de for
1756        // itself; the tree of `two_paragraphs` says nothing at all.
1757        let doc = tagged_doc(
1758            "/StructParents 0",
1759            &[
1760                (
1761                    10,
1762                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
1763                ),
1764                (
1765                    11,
1766                    "<< /Type /StructElem /S /Document /P 10 0 R /Lang (en) /K [13 0 R 14 0 R] >>",
1767                ),
1768                (12, "<< /Nums [0 [13 0 R 14 0 R]] >>"),
1769                (
1770                    13,
1771                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [0] >>",
1772                ),
1773                (
1774                    14,
1775                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /Lang (de) /K [1] >>",
1776                ),
1777            ],
1778        );
1779        let placed = placements(&doc, &[id(0, 0), id(0, 1)]);
1780        assert_eq!(placed[&id(0, 0)].lang.as_deref(), Some("en"));
1781        assert_eq!(placed[&id(0, 1)].lang.as_deref(), Some("de"));
1782        let untagged = two_paragraphs("<< /Nums [0 [13 0 R 14 0 R 13 0 R 14 0 R]] >>");
1783        assert_eq!(placements(&untagged, &[id(0, 0)])[&id(0, 0)].lang, None);
1784    }
1785
1786    // Covers ISO 32000-1 §14.7.5, §14.7.5.2 and §14.7.5.3.
1787    #[test]
1788    fn placements_carry_the_elements_attribute_objects() {
1789        // Cell 14 has one direct attribute object and revision 1; cell 15 a
1790        // class from the root's /ClassMap, numbered 4 in its /C array, and a
1791        // direct object overriding one of the class's entries; cell 16 an
1792        // /A array whose first object is followed by its revision number and
1793        // whose second is indirect; paragraph 17 has none.
1794        let doc = tagged_doc(
1795            "/StructParents 0",
1796            &[
1797                (
1798                    10,
1799                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R \
1800                     /ClassMap << /Wide << /O /Table /ColSpan 3 /RowSpan 2 >> >> >>",
1801                ),
1802                (
1803                    11,
1804                    "<< /Type /StructElem /S /Table /P 10 0 R /K [13 0 R 17 0 R] >>",
1805                ),
1806                (12, "<< /Nums [0 [14 0 R 15 0 R 16 0 R 17 0 R]] >>"),
1807                (
1808                    13,
1809                    "<< /Type /StructElem /S /TR /P 11 0 R /K [14 0 R 15 0 R 16 0 R] >>",
1810                ),
1811                (
1812                    14,
1813                    "<< /Type /StructElem /S /TD /P 13 0 R /Pg 3 0 R /K [0] /R 1 \
1814                     /A << /O /Table /ColSpan 2 >> >>",
1815                ),
1816                (
1817                    15,
1818                    "<< /Type /StructElem /S /TD /P 13 0 R /Pg 3 0 R /K [1] \
1819                     /C [/Wide 4] /A << /O /Table /ColSpan 1 >> >>",
1820                ),
1821                (
1822                    16,
1823                    "<< /Type /StructElem /S /TD /P 13 0 R /Pg 3 0 R /K [2] \
1824                     /A [<< /O /Layout /Placement /Block >> 2 20 0 R] >>",
1825                ),
1826                (
1827                    17,
1828                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [3] >>",
1829                ),
1830                (20, "<< /O /Table /RowSpan 3 >>"),
1831            ],
1832        );
1833        let placed = placements(&doc, &[id(0, 0), id(0, 1), id(0, 2), id(0, 3)]);
1834        let cell = |num: u32| ObjRef { num, gen: 0 };
1835        let int = |value: Option<&Object>| value.and_then(Object::as_int);
1836        let objects = |p: &Placement, element: ObjRef| -> Vec<(String, i64)> {
1837            p.attributes
1838                .iter()
1839                .filter(|a| a.element == element)
1840                .map(|a| (a.owner.clone(), a.revision))
1841                .collect()
1842        };
1843
1844        let p0 = &placed[&id(0, 0)];
1845        assert_eq!(int(p0.attribute(cell(14), "Table", "ColSpan")), Some(2));
1846        assert_eq!(p0.attribute(cell(14), "Table", "RowSpan"), None);
1847        assert_eq!(p0.attributes.len(), 1, "the row and table carry none");
1848        assert_eq!(p0.path.last().unwrap().revision, 1);
1849
1850        // Class objects come first, direct objects after and win.
1851        let p1 = &placed[&id(0, 1)];
1852        assert_eq!(
1853            objects(p1, cell(15)),
1854            [("Table".to_string(), 4), ("Table".to_string(), 0)]
1855        );
1856        assert_eq!(int(p1.attribute(cell(15), "Table", "ColSpan")), Some(1));
1857        assert_eq!(int(p1.attribute(cell(15), "Table", "RowSpan")), Some(2));
1858        assert_eq!(p1.path.last().unwrap().revision, 0);
1859
1860        let p2 = &placed[&id(0, 2)];
1861        assert_eq!(
1862            objects(p2, cell(16)),
1863            [("Layout".to_string(), 2), ("Table".to_string(), 0)]
1864        );
1865        assert_eq!(int(p2.attribute(cell(16), "Table", "RowSpan")), Some(3));
1866        assert_eq!(
1867            p2.attribute(cell(16), "Layout", "Placement")
1868                .and_then(Object::as_name)
1869                .map(|n| n.0.as_str()),
1870            Some("Block")
1871        );
1872        assert_eq!(p2.attribute(cell(16), "Table", "ColSpan"), None);
1873
1874        assert!(placed[&id(0, 3)].attributes.is_empty());
1875    }
1876
1877    // Covers ISO 32000-1 §14.8.5.2.
1878    #[test]
1879    fn standard_attribute_owners_are_recognized_by_name() {
1880        assert_eq!(
1881            StandardOwner::from_name("Table"),
1882            Some(StandardOwner::Table)
1883        );
1884        assert_eq!(
1885            StandardOwner::from_name("XML-1.00"),
1886            Some(StandardOwner::Xml100)
1887        );
1888        assert_eq!(StandardOwner::from_name("Acme"), None);
1889        assert_eq!(StandardOwner::from_name("table"), None);
1890        assert_eq!(StandardOwner::ALL.len(), 11);
1891        for owner in StandardOwner::ALL {
1892            assert_eq!(
1893                StandardOwner::from_name(owner.name()),
1894                Some(owner),
1895                "{}",
1896                owner.name()
1897            );
1898        }
1899        let object = AttributeObject {
1900            element: ObjRef { num: 1, gen: 0 },
1901            owner: "Layout".to_string(),
1902            revision: 0,
1903            entries: Dict::default(),
1904        };
1905        assert_eq!(object.standard_owner(), Some(StandardOwner::Layout));
1906        let own = AttributeObject {
1907            owner: "Acme".to_string(),
1908            ..object
1909        };
1910        assert_eq!(own.standard_owner(), None);
1911    }
1912
1913    // Covers ISO 32000-1 §14.8.4, §14.8.4.2, §14.8.4.3, §14.8.4.4 and
1914    // §14.8.4.5.
1915    #[test]
1916    fn standard_types_are_recognized_by_name_and_grouped_by_clause() {
1917        assert_eq!(StandardType::from_name("H1"), Some(StandardType::H1));
1918        assert_eq!(StandardType::H1.kind(), StandardKind::BlockLevel);
1919        assert_eq!(
1920            StandardType::from_name("TOCI").map(StandardType::kind),
1921            Some(StandardKind::Grouping)
1922        );
1923        assert_eq!(
1924            StandardType::from_name("LBody").map(StandardType::kind),
1925            Some(StandardKind::BlockLevel)
1926        );
1927        assert_eq!(
1928            StandardType::from_name("TFoot").map(StandardType::kind),
1929            Some(StandardKind::BlockLevel)
1930        );
1931        assert_eq!(
1932            StandardType::from_name("Ruby").map(StandardType::kind),
1933            Some(StandardKind::InlineLevel)
1934        );
1935        assert_eq!(
1936            StandardType::from_name("Formula").map(StandardType::kind),
1937            Some(StandardKind::Illustration)
1938        );
1939        // Names are case-sensitive and a document's own types are not standard.
1940        assert_eq!(StandardType::from_name("h1"), None);
1941        assert_eq!(StandardType::from_name("Para"), None);
1942        assert_eq!(StandardType::ALL.len(), 49);
1943        for t in StandardType::ALL {
1944            assert_eq!(StandardType::from_name(t.name()), Some(t), "{}", t.name());
1945        }
1946        let grouping = StandardType::ALL
1947            .iter()
1948            .filter(|t| t.kind() == StandardKind::Grouping)
1949            .count();
1950        assert_eq!(grouping, 12);
1951    }
1952
1953    // Covers ISO 32000-1 §7.7.2.
1954    #[test]
1955    fn no_struct_tree_root_means_no_tree() {
1956        let mut b = PdfBuilder::new();
1957        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1958        b.object(2, "<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
1959        b.object(3, "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>");
1960        let doc = Document::load(b.build(1)).unwrap();
1961        assert!(doc.structure_tree().is_none());
1962    }
1963
1964    // Covers ISO 32000-1 §14.7.2 and §7.9.7.
1965    #[test]
1966    fn ranks_follow_the_tree_not_the_ids() {
1967        let doc = two_paragraphs("<< /Nums [0 [13 0 R 14 0 R 13 0 R 14 0 R]] >>");
1968        let ranks = ranks(&doc, &[id(0, 2), id(0, 3), id(0, 0), id(0, 1)]);
1969        assert_eq!(ranks[&id(0, 0)], 0);
1970        assert_eq!(ranks[&id(0, 2)], 1);
1971        assert_eq!(ranks[&id(0, 1)], 2);
1972        assert_eq!(ranks[&id(0, 3)], 3);
1973    }
1974
1975    // Covers ISO 32000-1 §14.7.4, §14.7.4.4 and §7.9.7.
1976    #[test]
1977    fn parent_tree_kids_and_limits_are_descended() {
1978        let doc = tagged_doc(
1979            "/StructParents 7",
1980            &[
1981                (
1982                    10,
1983                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
1984                ),
1985                (
1986                    11,
1987                    "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R] >>",
1988                ),
1989                (12, "<< /Kids [15 0 R 16 0 R] >>"),
1990                (
1991                    13,
1992                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [0] >>",
1993                ),
1994                (15, "<< /Limits [0 3] /Nums [0 [] 3 []] >>"),
1995                (16, "<< /Limits [7 9] /Nums [7 [13 0 R] 9 []] >>"),
1996            ],
1997        );
1998        let ranks = ranks(&doc, &[id(7, 0)]);
1999        assert_eq!(ranks[&id(7, 0)], 0);
2000    }
2001
2002    // Covers ISO 32000-1 §14.7.4.4.
2003    #[test]
2004    fn a_page_without_a_parent_tree_entry_has_no_ranks() {
2005        let doc = two_paragraphs("<< /Nums [5 [13 0 R]] >>");
2006        assert!(ranks(&doc, &[id(0, 0), id(0, 1)]).is_empty());
2007    }
2008
2009    #[test]
2010    fn untagged_and_out_of_range_ids_are_absent() {
2011        let doc = two_paragraphs("<< /Nums [0 [13 0 R 14 0 R 13 0 R 14 0 R]] >>");
2012        let ranks = ranks(&doc, &[id(0, 0), id(0, 9), id(4, 0)]);
2013        assert_eq!(ranks.len(), 1);
2014        assert_eq!(ranks[&id(0, 0)], 0);
2015    }
2016
2017    // Covers ISO 32000-1 §14.7.2.
2018    #[test]
2019    fn a_broken_ancestry_leaves_only_that_element_unranked() {
2020        let doc = tagged_doc(
2021            "/StructParents 0",
2022            &[
2023                (
2024                    10,
2025                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2026                ),
2027                (
2028                    11,
2029                    "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R] >>",
2030                ),
2031                (12, "<< /Nums [0 [13 0 R 14 0 R]] >>"),
2032                (
2033                    13,
2034                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [0] >>",
2035                ),
2036                // Not among its parent's kids: its path cannot be read.
2037                (
2038                    14,
2039                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [1] >>",
2040                ),
2041            ],
2042        );
2043        let ranks = ranks(&doc, &[id(0, 0), id(0, 1)]);
2044        assert_eq!(ranks.len(), 1);
2045        assert_eq!(ranks[&id(0, 0)], 0);
2046    }
2047
2048    // Covers ISO 32000-1 §14.7.4 and §14.7.4.2.
2049    #[test]
2050    fn marked_content_references_name_their_page() {
2051        // One element spanning two pages: the same id number on each. The
2052        // reference naming this page wins the index, the other is skipped.
2053        let doc = tagged_doc(
2054            "/StructParents 0",
2055            &[
2056                (
2057                    10,
2058                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2059                ),
2060                (
2061                    11,
2062                    "<< /Type /StructElem /S /P /P 10 0 R \
2063                     /K [<< /Type /MCR /Pg 99 0 R /MCID 0 >> 15 0 R] >>",
2064                ),
2065                (12, "<< /Nums [0 [11 0 R]] >>"),
2066                (15, "<< /Type /MCR /Pg 3 0 R /MCID 0 >>"),
2067            ],
2068        );
2069        let ranks = ranks(&doc, &[id(0, 0)]);
2070        assert_eq!(ranks[&id(0, 0)], 0);
2071        let tree = doc.structure_tree().unwrap();
2072        let page = doc.page(0).unwrap();
2073        let mut walk = Walk {
2074            src: &Immediate(&doc),
2075            page_ref: page.object_ref(),
2076            root_ref: Some(ObjRef { num: 10, gen: 0 }),
2077            class_map: &tree.class_map,
2078            parent_entries: &tree.parent_entries,
2079            caches: Caches::default(),
2080        };
2081        let element = block_on(walk.dict(ObjRef { num: 11, gen: 0 })).unwrap();
2082        assert_eq!(block_on(walk.mcid_index(&element, id(0, 0))), Some(1));
2083        drop(tree);
2084    }
2085
2086    // Covers ISO 32000-1 §14.7.4.2.
2087    #[test]
2088    fn a_bare_integer_on_another_page_is_not_this_page() {
2089        let doc = tagged_doc(
2090            "/StructParents 0",
2091            &[
2092                (
2093                    10,
2094                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2095                ),
2096                (
2097                    11,
2098                    "<< /Type /StructElem /S /P /P 10 0 R /Pg 99 0 R /K [0] >>",
2099                ),
2100                (12, "<< /Nums [0 [11 0 R]] >>"),
2101            ],
2102        );
2103        assert!(ranks(&doc, &[id(0, 0)]).is_empty());
2104    }
2105
2106    // Covers ISO 32000-1 §14.7.4.4.
2107    #[test]
2108    fn a_form_key_ranks_alongside_the_page() {
2109        // The form's marked content (key 1) is a child of the second
2110        // paragraph; the page's (key 0) fills the first.
2111        let doc = tagged_doc(
2112            "/StructParents 0",
2113            &[
2114                (
2115                    10,
2116                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2117                ),
2118                (
2119                    11,
2120                    "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R 14 0 R] >>",
2121                ),
2122                (12, "<< /Nums [0 [13 0 R] 1 [14 0 R]] >>"),
2123                (
2124                    13,
2125                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [0] >>",
2126                ),
2127                (
2128                    14,
2129                    "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [0] >>",
2130                ),
2131            ],
2132        );
2133        let ranks = ranks(&doc, &[id(1, 0), id(0, 0)]);
2134        assert_eq!(ranks[&id(0, 0)], 0);
2135        assert_eq!(ranks[&id(1, 0)], 1);
2136    }
2137
2138    // Covers ISO 32000-1 §14.7.2.
2139    #[test]
2140    fn a_single_kid_needs_no_array() {
2141        let doc = tagged_doc(
2142            "/StructParents 0",
2143            &[
2144                (
2145                    10,
2146                    "<< /Type /StructTreeRoot /K 11 0 R /ParentTree 12 0 R >>",
2147                ),
2148                (11, "<< /Type /StructElem /S /P /P 10 0 R /Pg 3 0 R /K 0 >>"),
2149                (12, "<< /Nums [0 [11 0 R]] >>"),
2150            ],
2151        );
2152        assert_eq!(ranks(&doc, &[id(0, 0)])[&id(0, 0)], 0);
2153    }
2154
2155    fn r(num: u32) -> ObjRef {
2156        ObjRef { num, gen: 0 }
2157    }
2158
2159    fn content_items(doc: &Document) -> Vec<PlacedItem> {
2160        let tree = doc.structure_tree().expect("tree");
2161        let page = doc.page(0).unwrap();
2162        block_on(tree.content_items_with(&Immediate(doc), &page))
2163    }
2164
2165    fn items_of(placed: &[PlacedItem]) -> Vec<(ContentItem, u32)> {
2166        placed.iter().map(|p| (p.item, p.placement.rank)).collect()
2167    }
2168
2169    /// A page with two paragraphs and a link between them: paragraph 13
2170    /// holds sequence 0, the Link element 14 holds the annotation 20 through
2171    /// `objr` (its `/K`), paragraph 15 holds sequence 1. The annotation
2172    /// carries `/StructParent 1`, the parent tree maps 0 to the page's array
2173    /// and 1 to the Link element.
2174    fn linked_doc(objr: &str, extra: &[(u32, &str)]) -> Document {
2175        let mut objects = vec![
2176            (
2177                10,
2178                "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2179            ),
2180            (
2181                11,
2182                "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R 14 0 R 15 0 R] >>",
2183            ),
2184            (12, "<< /Nums [0 [13 0 R 15 0 R] 1 14 0 R] >>"),
2185            (
2186                13,
2187                "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [0] >>",
2188            ),
2189            (
2190                15,
2191                "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [1] >>",
2192            ),
2193            (
2194                20,
2195                "<< /Type /Annot /Subtype /Link /Rect [0 0 10 10] /StructParent 1 >>",
2196            ),
2197        ];
2198        let link = format!("<< /Type /StructElem /S /Link /P 11 0 R /Pg 3 0 R /K {objr} >>");
2199        objects.push((14, link.as_str()));
2200        objects.extend_from_slice(extra);
2201        tagged_doc("/StructParents 0 /Annots [20 0 R]", &objects)
2202    }
2203
2204    // Covers ISO 32000-1 §14.7.4.3, §14.7.4.4 and §14.8.2.3.2.
2205    #[test]
2206    fn an_annotation_takes_its_place_between_the_sequences() {
2207        let doc = linked_doc("[<< /Type /OBJR /Obj 20 0 R >>]", &[]);
2208        let placed = content_items(&doc);
2209        assert_eq!(
2210            items_of(&placed),
2211            vec![
2212                (ContentItem::Sequence(id(0, 0)), 0),
2213                (ContentItem::Object(r(20)), 1),
2214                (ContentItem::Sequence(id(0, 1)), 2),
2215            ]
2216        );
2217        let link = &placed[1].placement;
2218        assert_eq!(link.structure_type.as_deref(), Some("Link"));
2219        assert_eq!(link.standard_type, Some(StandardType::Link));
2220        assert_eq!(
2221            link.path
2222                .iter()
2223                .map(|e| e.standard_type)
2224                .collect::<Vec<_>>(),
2225            vec![StandardType::Document, StandardType::Link]
2226        );
2227    }
2228
2229    // Covers ISO 32000-1 §14.7.4.3.
2230    #[test]
2231    fn an_indirect_object_reference_is_read_after_the_direct_kids() {
2232        let doc = linked_doc(
2233            "[21 0 R]",
2234            &[(21, "<< /Type /OBJR /Obj 20 0 R /Pg 3 0 R >>")],
2235        );
2236        let placed = content_items(&doc);
2237        assert_eq!(items_of(&placed)[1], (ContentItem::Object(r(20)), 1));
2238    }
2239
2240    // Covers ISO 32000-1 §14.7.4.3.
2241    #[test]
2242    fn the_object_reference_page_overrides_the_elements() {
2243        // The Link element sits on page 99; only its object reference says
2244        // this page, which is what counts.
2245        let doc = tagged_doc(
2246            "/StructParents 0 /Annots [20 0 R]",
2247            &[
2248                (
2249                    10,
2250                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2251                ),
2252                (
2253                    11,
2254                    "<< /Type /StructElem /S /Link /P 10 0 R /Pg 99 0 R \
2255                     /K [<< /Type /OBJR /Obj 20 0 R /Pg 3 0 R >>] >>",
2256                ),
2257                (12, "<< /Nums [1 11 0 R] >>"),
2258                (20, "<< /Type /Annot /Subtype /Link /StructParent 1 >>"),
2259            ],
2260        );
2261        assert_eq!(
2262            items_of(&content_items(&doc)),
2263            vec![(ContentItem::Object(r(20)), 0)]
2264        );
2265        // Without its own page the reference inherits the element's, and
2266        // page 99 is not this page.
2267        let doc = tagged_doc(
2268            "/StructParents 0 /Annots [20 0 R]",
2269            &[
2270                (
2271                    10,
2272                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2273                ),
2274                (
2275                    11,
2276                    "<< /Type /StructElem /S /Link /P 10 0 R /Pg 99 0 R \
2277                     /K [<< /Type /OBJR /Obj 20 0 R >>] >>",
2278                ),
2279                (12, "<< /Nums [1 11 0 R] >>"),
2280                (20, "<< /Type /Annot /Subtype /Link /StructParent 1 >>"),
2281            ],
2282        );
2283        assert!(content_items(&doc).is_empty());
2284    }
2285
2286    // Covers ISO 32000-1 §14.7.4.3 and §14.7.4.4.
2287    #[test]
2288    fn an_annotation_the_tree_does_not_hold_is_no_content_item() {
2289        // 21 has no /StructParent, 22's key is missing from the parent
2290        // tree, 23's element has no reference naming it, and the direct
2291        // dictionary has no reference to name.
2292        let doc = tagged_doc(
2293            "/StructParents 0 /Annots [20 0 R 21 0 R 22 0 R 23 0 R << /Subtype /Link /StructParent 1 >>]",
2294            &[
2295                (
2296                    10,
2297                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2298                ),
2299                (
2300                    11,
2301                    "<< /Type /StructElem /S /Link /P 10 0 R /Pg 3 0 R \
2302                     /K [<< /Type /OBJR /Obj 20 0 R >>] >>",
2303                ),
2304                (12, "<< /Nums [1 11 0 R 3 11 0 R] >>"),
2305                (20, "<< /Type /Annot /Subtype /Link /StructParent 1 >>"),
2306                (21, "<< /Type /Annot /Subtype /Link >>"),
2307                (22, "<< /Type /Annot /Subtype /Link /StructParent 2 >>"),
2308                (23, "<< /Type /Annot /Subtype /Link /StructParent 3 >>"),
2309            ],
2310        );
2311        assert_eq!(
2312            items_of(&content_items(&doc)),
2313            vec![(ContentItem::Object(r(20)), 0)]
2314        );
2315    }
2316
2317    // Covers ISO 32000-1 §14.7.4.3 and §14.8.2.3.2.
2318    #[test]
2319    fn a_link_element_orders_its_sequence_and_annotation_by_kid_index() {
2320        let doc = tagged_doc(
2321            "/StructParents 0 /Annots [20 0 R]",
2322            &[
2323                (
2324                    10,
2325                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2326                ),
2327                (
2328                    11,
2329                    "<< /Type /StructElem /S /Link /P 10 0 R /Pg 3 0 R \
2330                     /K [<< /Type /OBJR /Obj 20 0 R >> << /Type /MCR /MCID 0 >>] >>",
2331                ),
2332                (12, "<< /Nums [0 [11 0 R] 1 11 0 R] >>"),
2333                (20, "<< /Type /Annot /Subtype /Link /StructParent 1 >>"),
2334            ],
2335        );
2336        assert_eq!(
2337            items_of(&content_items(&doc)),
2338            vec![
2339                (ContentItem::Object(r(20)), 0),
2340                (ContentItem::Sequence(id(0, 0)), 1),
2341            ]
2342        );
2343    }
2344
2345    // Covers ISO 32000-1 §14.7.4.3.
2346    #[test]
2347    fn a_form_xobject_is_placed_when_handed_in() {
2348        let doc = tagged_doc(
2349            "/StructParents 0",
2350            &[
2351                (
2352                    10,
2353                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2354                ),
2355                (
2356                    11,
2357                    "<< /Type /StructElem /S /Figure /P 10 0 R /Pg 3 0 R \
2358                     /K [<< /Type /OBJR /Obj 30 0 R >>] >>",
2359                ),
2360                (12, "<< /Nums [5 11 0 R] >>"),
2361                (
2362                    30,
2363                    "<< /Type /XObject /Subtype /Form /BBox [0 0 1 1] /StructParent 5 /Length 0 >>\nstream\n\nendstream",
2364                ),
2365            ],
2366        );
2367        let tree = doc.structure_tree().unwrap();
2368        let page = doc.page(0).unwrap();
2369        let placed = block_on(tree.place_items_with(
2370            &Immediate(&doc),
2371            &page,
2372            &[ContentItem::Object(r(30)), ContentItem::Object(r(30))],
2373        ));
2374        assert_eq!(placed.len(), 1);
2375        assert_eq!(
2376            placed[&ContentItem::Object(r(30))].standard_type,
2377            Some(StandardType::Figure)
2378        );
2379        // Not in /Annots, so the page enumeration does not find it.
2380        assert!(content_items(&doc).is_empty());
2381    }
2382
2383    // Covers ISO 32000-1 §14.7.4.2.
2384    #[test]
2385    fn a_reference_naming_a_stream_matches_only_that_streams_sequence() {
2386        // The element holds the form's sequence 0 (kid 0, an MCR naming the
2387        // form) and the page's sequence 0 (kid 1, a bare integer). Both are
2388        // numbered 0; the MCR's /Stm tells them apart.
2389        let doc = tagged_doc(
2390            "/StructParents 0",
2391            &[
2392                (
2393                    10,
2394                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2395                ),
2396                (
2397                    11,
2398                    "<< /Type /StructElem /S /P /P 10 0 R /Pg 3 0 R \
2399                     /K [<< /Type /MCR /Stm 30 0 R /MCID 0 >> 0] >>",
2400                ),
2401                (12, "<< /Nums [0 [11 0 R] 1 [11 0 R]] >>"),
2402                (
2403                    30,
2404                    "<< /Type /XObject /Subtype /Form /BBox [0 0 1 1] /StructParents 1 /Length 0 >>\nstream\n\nendstream",
2405                ),
2406            ],
2407        );
2408        let ranks = ranks(&doc, &[id(0, 0), id(1, 0)]);
2409        assert_eq!(ranks[&id(1, 0)], 0);
2410        assert_eq!(ranks[&id(0, 0)], 1);
2411    }
2412
2413    // Covers ISO 32000-1 §14.7.4 and §14.7.4.4.
2414    #[test]
2415    fn the_page_enumeration_skips_null_entries_and_needs_a_parent_tree() {
2416        let doc = tagged_doc(
2417            "/StructParents 0",
2418            &[
2419                (
2420                    10,
2421                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2422                ),
2423                (
2424                    11,
2425                    "<< /Type /StructElem /S /P /P 10 0 R /Pg 3 0 R /K [0 2] >>",
2426                ),
2427                (12, "<< /Nums [0 [11 0 R null 11 0 R]] >>"),
2428            ],
2429        );
2430        assert_eq!(
2431            items_of(&content_items(&doc)),
2432            vec![
2433                (ContentItem::Sequence(id(0, 0)), 0),
2434                (ContentItem::Sequence(id(0, 2)), 1),
2435            ]
2436        );
2437        let doc = tagged_doc(
2438            "/StructParents 0",
2439            &[(10, "<< /Type /StructTreeRoot /K [] >>")],
2440        );
2441        assert!(content_items(&doc).is_empty());
2442    }
2443
2444    fn counts(doc: &Document) -> (usize, usize) {
2445        let tree = doc.structure_tree().expect("tree");
2446        let page = doc.page(0).unwrap();
2447        block_on(tree.content_item_counts_with(&Immediate(doc), &page))
2448    }
2449
2450    // Covers ISO 32000-1 §14.7.4.4.
2451    #[test]
2452    fn the_counts_follow_the_parent_tree_without_reading_the_elements() {
2453        // Two sequences and one filed annotation: the same as the placement.
2454        let doc = linked_doc("[<< /Type /OBJR /Obj 20 0 R >>]", &[]);
2455        assert_eq!(counts(&doc), (2, 1));
2456        assert_eq!(content_items(&doc).len(), 3);
2457        // 21 has no key and 22's key is missing from the tree, so neither is
2458        // filed; 23's key names an element without a reference to it, which
2459        // the count cannot see and the placement leaves out.
2460        let doc = tagged_doc(
2461            "/StructParents 0 /Annots [20 0 R 21 0 R 22 0 R 23 0 R]",
2462            &[
2463                (
2464                    10,
2465                    "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2466                ),
2467                (
2468                    11,
2469                    "<< /Type /StructElem /S /Link /P 10 0 R /Pg 3 0 R \
2470                     /K [<< /Type /OBJR /Obj 20 0 R >>] >>",
2471                ),
2472                (12, "<< /Nums [0 [11 0 R null] 1 11 0 R 3 11 0 R] >>"),
2473                (20, "<< /Type /Annot /Subtype /Link /StructParent 1 >>"),
2474                (21, "<< /Type /Annot /Subtype /Link >>"),
2475                (22, "<< /Type /Annot /Subtype /Link /StructParent 2 >>"),
2476                (23, "<< /Type /Annot /Subtype /Link /StructParent 3 >>"),
2477            ],
2478        );
2479        assert_eq!(counts(&doc), (1, 2));
2480        assert_eq!(content_items(&doc).len(), 1);
2481    }
2482
2483    // Covers ISO 32000-1 §14.7.2.
2484    #[test]
2485    fn a_second_page_reuses_what_the_first_walk_read() {
2486        // Two pages under one Document element; the second walk finds the
2487        // shared ancestor in the cache the first one stored.
2488        let mut b = PdfBuilder::new();
2489        b.object(
2490            1,
2491            "<< /Type /Catalog /Pages 2 0 R /StructTreeRoot 10 0 R >>",
2492        );
2493        b.object(2, "<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>");
2494        b.object(
2495            3,
2496            "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /StructParents 0 >>",
2497        );
2498        b.object(
2499            4,
2500            "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /StructParents 1 >>",
2501        );
2502        b.object(
2503            10,
2504            "<< /Type /StructTreeRoot /K [11 0 R] /ParentTree 12 0 R >>",
2505        );
2506        b.object(
2507            11,
2508            "<< /Type /StructElem /S /Document /P 10 0 R /K [13 0 R 14 0 R] >>",
2509        );
2510        b.object(12, "<< /Nums [0 [13 0 R] 1 [14 0 R]] >>");
2511        b.object(
2512            13,
2513            "<< /Type /StructElem /S /P /P 11 0 R /Pg 3 0 R /K [0] >>",
2514        );
2515        b.object(
2516            14,
2517            "<< /Type /StructElem /S /P /P 11 0 R /Pg 4 0 R /K [0] >>",
2518        );
2519        let doc = Document::load(b.build(1)).expect("load");
2520        let tree = doc.structure_tree().expect("tree");
2521        let first = block_on(tree.content_items_with(&Immediate(&doc), &doc.page(0).unwrap()));
2522        assert_eq!(first.len(), 1);
2523        assert!(tree.caches.lock().unwrap().dicts.contains_key(&r(11)));
2524        let second = block_on(tree.content_items_with(&Immediate(&doc), &doc.page(1).unwrap()));
2525        assert_eq!(second.len(), 1);
2526        assert_eq!(second[0].placement.path.len(), 2);
2527        assert_eq!(second[0].placement.path[0].object, r(11));
2528        assert_eq!(second[0].placement.path[1].object, r(14));
2529        assert_eq!(tree.clone(), tree);
2530    }
2531}