1use 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
14const MAX_ELEMENT_DEPTH: usize = 64;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct MarkedContentId {
23 pub parents: u32,
24 pub mcid: u32,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum ContentItem {
35 Sequence(MarkedContentId),
36 Object(ObjRef),
37}
38
39#[derive(Debug, Clone, PartialEq)]
43pub struct PlacedItem {
44 pub item: ContentItem,
45 pub placement: Placement,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum StandardKind {
53 Grouping,
56 BlockLevel,
59 InlineLevel,
61 Illustration,
63}
64
65#[allow(clippy::upper_case_acronyms)]
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub enum StandardType {
72 Document,
74 Part,
75 Art,
76 Sect,
77 Div,
78 BlockQuote,
79 Caption,
80 TOC,
81 TOCI,
82 Index,
83 NonStruct,
84 Private,
85 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 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 Figure,
123 Formula,
124 Form,
125}
126
127impl StandardType {
128 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 pub fn from_name(name: &str) -> Option<StandardType> {
185 StandardType::ALL.into_iter().find(|t| t.name() == name)
186 }
187
188 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
305pub struct StructureElement {
306 pub standard_type: StandardType,
307 pub object: ObjRef,
308 pub revision: i64,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
318pub enum StandardOwner {
319 Layout,
321 List,
323 PrintField,
325 Table,
327 Xml100,
329 Html32,
331 Html401,
333 Oeb10,
335 Rtf105,
337 Css100,
339 Css200,
341}
342
343impl StandardOwner {
344 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 pub fn from_name(name: &str) -> Option<StandardOwner> {
363 StandardOwner::ALL
364 .into_iter()
365 .find(|owner| owner.name() == name)
366 }
367
368 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#[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 pub fn standard_owner(&self) -> Option<StandardOwner> {
404 StandardOwner::from_name(&self.owner)
405 }
406}
407
408#[derive(Debug, Clone, PartialEq)]
415pub struct Placement {
416 pub rank: u32,
417 pub structure_type: Option<String>,
419 pub mapped_type: Option<String>,
422 pub standard_type: Option<StandardType>,
424 pub path: Vec<StructureElement>,
428 pub alt: Option<String>,
431 pub lang: Option<String>,
434 pub expansion: Option<String>,
438 pub attributes: Vec<AttributeObject>,
443}
444
445impl Placement {
446 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#[derive(Debug)]
467pub struct StructureTree {
468 root: Arc<Dict>,
470 root_ref: Option<ObjRef>,
471 caches: Mutex<Caches>,
475 role_map: FastMap<String, String>,
478 class_map: FastMap<String, Object>,
481 parent_entries: Arc<FastMap<i64, Object>>,
486}
487
488impl Clone for StructureTree {
489 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 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 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 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 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 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 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 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 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 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 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 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#[derive(Debug, Default)]
838struct Caches {
839 dicts: FastMap<ObjRef, Option<Arc<Dict>>>,
840 paths: FastMap<ObjRef, Option<Arc<Vec<u32>>>>,
843 parents: FastMap<u32, Option<Arc<Vec<Object>>>>,
845 ancestries: FastMap<ObjRef, Arc<Ancestry>>,
848}
849
850#[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
864type Ancestry = Vec<Ancestor>;
867
868fn 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
881fn 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
899async 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
918fn 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
930struct Walk<'a, S> {
934 src: &'a S,
935 page_ref: Option<ObjRef>,
936 root_ref: Option<ObjRef>,
937 class_map: &'a FastMap<String, Object>,
939 parent_entries: &'a FastMap<i64, Object>,
941 caches: Caches,
943}
944
945impl<S: AsyncObjectSource> Walk<'_, S> {
946 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
1375async 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
1388fn 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
1399fn 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
1408fn 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
1418fn 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 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 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 #[test]
1497 fn structure_types_are_read_and_mapped_through_the_role_map() {
1498 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 #[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 #[test]
1576 fn placements_carry_the_standard_typed_ancestry() {
1577 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 #[test]
1669 fn placements_carry_the_nearest_alternate_description() {
1670 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 #[test]
1710 fn placements_carry_the_nearest_expansion() {
1711 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 #[test]
1754 fn placements_carry_the_nearest_language() {
1755 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 #[test]
1788 fn placements_carry_the_elements_attribute_objects() {
1789 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 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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[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 (
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 #[test]
2050 fn marked_content_references_name_their_page() {
2051 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 #[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 #[test]
2108 fn a_form_key_ranks_alongside_the_page() {
2109 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 #[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 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 #[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 #[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 #[test]
2242 fn the_object_reference_page_overrides_the_elements() {
2243 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 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 #[test]
2288 fn an_annotation_the_tree_does_not_hold_is_no_content_item() {
2289 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 #[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 #[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 assert!(content_items(&doc).is_empty());
2381 }
2382
2383 #[test]
2385 fn a_reference_naming_a_stream_matches_only_that_streams_sequence() {
2386 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 #[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 #[test]
2452 fn the_counts_follow_the_parent_tree_without_reading_the_elements() {
2453 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 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 #[test]
2485 fn a_second_page_reuses_what_the_first_walk_read() {
2486 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}