1use std::collections::HashMap;
9
10use pdfboss_core::elements::{Element, Span, XrefKind};
11use pdfboss_core::{Dict, Name, ObjRef};
12
13pub type NodeId = usize;
15
16#[derive(Clone, Copy, PartialEq, Eq, Debug)]
18pub enum TreeReq {
19 Physical,
21 Logical,
23 Contents { page: usize },
25}
26
27#[derive(Clone, Copy, PartialEq, Eq, Debug)]
29pub enum LoadState {
30 NotLoaded,
31 Loading,
32 Loaded,
33 Failed,
34}
35
36#[derive(Clone, PartialEq, Debug)]
38pub enum NodeKind {
39 Document,
40 PagesFolder,
41 Page {
42 index: usize,
43 r: ObjRef,
44 },
45 FontsFolder {
46 page: usize,
47 },
48 Font {
49 r: ObjRef,
50 subtype: Name,
51 base_font: Option<Name>,
52 },
53 ImagesFolder {
54 page: usize,
55 },
56 Image {
57 r: ObjRef,
58 width: u32,
59 height: u32,
60 },
61 AnnotationsFolder {
62 page: usize,
63 },
64 Annotation {
65 r: ObjRef,
66 subtype: Name,
67 },
68 ContentsFolder {
69 page: usize,
70 },
71 ContentsStream {
72 r: ObjRef,
73 },
74 ObjectsFolder,
75 Object {
76 r: ObjRef,
77 span: Span,
78 in_objstm: Option<(ObjRef, Span)>,
79 },
80 XrefFolder,
81 XrefSection {
82 kind: XrefKind,
83 span: Span,
84 entries: usize,
85 },
86 StartXref {
87 offset: u64,
88 span: Span,
89 },
90 Eof {
91 span: Span,
92 },
93 Trailer,
94}
95
96#[derive(Clone, Debug)]
98pub struct Node {
99 pub parent: Option<NodeId>,
100 pub kind: NodeKind,
101 pub children: Vec<NodeId>,
102 pub expanded: bool,
103 pub load: LoadState,
104}
105
106#[derive(Clone, Copy, PartialEq, Eq, Debug)]
108pub struct TreeRow {
109 pub id: NodeId,
110 pub depth: usize,
111}
112
113#[derive(Clone, Copy, Debug)]
115struct PageFolders {
116 page_node: NodeId,
117 fonts: NodeId,
118 images: NodeId,
119 annotations: NodeId,
120 contents: NodeId,
121}
122
123pub struct TreeState {
125 pub nodes: Vec<Node>,
126 pub selected: NodeId,
127 pub scroll: usize,
128 pub page_count: usize,
129 pub version: (u8, u8),
130 pub physical: LoadState,
131 pub logical: LoadState,
132 pub header_span: Option<Span>,
133 pub trailer_dict: Option<Dict>,
134 pub trailer_span: Option<Span>,
135 pub root: NodeId,
136 pub pages_folder: NodeId,
137 pub objects_folder: NodeId,
138 pub xref_folder: NodeId,
139 pub trailer_node: NodeId,
140 object_spans: HashMap<u32, (Span, Option<(ObjRef, Span)>)>,
141 page_folders: HashMap<usize, PageFolders>,
142}
143
144impl TreeState {
145 pub fn new(version: (u8, u8), page_count: usize) -> TreeState {
146 let mut tree = TreeState {
147 nodes: Vec::new(),
148 selected: 0,
149 scroll: 0,
150 page_count,
151 version,
152 physical: LoadState::NotLoaded,
153 logical: LoadState::NotLoaded,
154 header_span: None,
155 trailer_dict: None,
156 trailer_span: None,
157 root: 0,
158 pages_folder: 0,
159 objects_folder: 0,
160 xref_folder: 0,
161 trailer_node: 0,
162 object_spans: HashMap::new(),
163 page_folders: HashMap::new(),
164 };
165 tree.root = tree.add(None, NodeKind::Document);
166 tree.nodes[tree.root].expanded = true;
167 tree.pages_folder = tree.add(Some(tree.root), NodeKind::PagesFolder);
168 tree.objects_folder = tree.add(Some(tree.root), NodeKind::ObjectsFolder);
169 tree.xref_folder = tree.add(Some(tree.root), NodeKind::XrefFolder);
170 tree.trailer_node = tree.add(Some(tree.root), NodeKind::Trailer);
171 tree.selected = tree.root;
172 tree
173 }
174
175 pub fn node(&self, id: NodeId) -> &Node {
176 &self.nodes[id]
177 }
178
179 fn add(&mut self, parent: Option<NodeId>, kind: NodeKind) -> NodeId {
180 let id = self.nodes.len();
181 self.nodes.push(Node {
182 parent,
183 kind,
184 children: Vec::new(),
185 expanded: false,
186 load: LoadState::NotLoaded,
187 });
188 if let Some(parent_id) = parent {
189 self.nodes[parent_id].children.push(id);
190 }
191 id
192 }
193
194 pub fn is_branch(&self, id: NodeId) -> bool {
196 matches!(
197 self.nodes[id].kind,
198 NodeKind::Document
199 | NodeKind::PagesFolder
200 | NodeKind::Page { .. }
201 | NodeKind::FontsFolder { .. }
202 | NodeKind::ImagesFolder { .. }
203 | NodeKind::AnnotationsFolder { .. }
204 | NodeKind::ContentsFolder { .. }
205 | NodeKind::ObjectsFolder
206 | NodeKind::XrefFolder
207 )
208 }
209
210 pub fn expand(&mut self, id: NodeId) -> Option<TreeReq> {
215 if !self.is_branch(id) {
216 return None;
217 }
218 self.nodes[id].expanded = true;
219 let kind = self.nodes[id].kind.clone();
220 match kind {
221 NodeKind::PagesFolder
222 if matches!(self.logical, LoadState::NotLoaded | LoadState::Failed) =>
223 {
224 self.logical = LoadState::Loading;
225 Some(TreeReq::Logical)
226 }
227 NodeKind::ObjectsFolder | NodeKind::XrefFolder
228 if matches!(self.physical, LoadState::NotLoaded | LoadState::Failed) =>
229 {
230 self.physical = LoadState::Loading;
231 Some(TreeReq::Physical)
232 }
233 NodeKind::ContentsFolder { page }
234 if matches!(
235 self.nodes[id].load,
236 LoadState::NotLoaded | LoadState::Failed
237 ) =>
238 {
239 self.nodes[id].load = LoadState::Loading;
240 Some(TreeReq::Contents { page })
241 }
242 NodeKind::Document
243 | NodeKind::PagesFolder
244 | NodeKind::Page { .. }
245 | NodeKind::FontsFolder { .. }
246 | NodeKind::ImagesFolder { .. }
247 | NodeKind::AnnotationsFolder { .. }
248 | NodeKind::ContentsFolder { .. }
249 | NodeKind::ObjectsFolder
250 | NodeKind::XrefFolder => None,
251 NodeKind::Font { .. }
252 | NodeKind::Image { .. }
253 | NodeKind::Annotation { .. }
254 | NodeKind::ContentsStream { .. }
255 | NodeKind::Object { .. }
256 | NodeKind::XrefSection { .. }
257 | NodeKind::StartXref { .. }
258 | NodeKind::Eof { .. }
259 | NodeKind::Trailer => None,
260 }
261 }
262
263 pub fn collapse_or_parent(&mut self, id: NodeId) -> bool {
277 if self.is_branch(id) && self.nodes[id].expanded {
278 self.nodes[id].expanded = false;
279 return true;
280 }
281 match self.nodes[id].parent {
282 Some(parent_id) => {
283 self.selected = parent_id;
284 true
285 }
286 None => false,
287 }
288 }
289
290 pub fn apply_batch(&mut self, req: TreeReq, elements: &[Element], done: bool) {
296 match req {
297 TreeReq::Physical if self.physical == LoadState::Loaded => return,
298 TreeReq::Logical if self.logical == LoadState::Loaded => return,
299 TreeReq::Physical | TreeReq::Logical | TreeReq::Contents { .. } => {}
300 }
301 for element in elements {
302 match element {
303 Element::Header { version, span } => {
304 self.version = *version;
305 self.header_span = Some(*span);
306 }
307 Element::IndirectObject {
310 r, span, in_objstm, ..
311 } => {
312 self.object_spans.insert(r.num, (*span, *in_objstm));
313 self.add(
314 Some(self.objects_folder),
315 NodeKind::Object {
316 r: *r,
317 span: *span,
318 in_objstm: *in_objstm,
319 },
320 );
321 }
322 Element::XrefSection {
325 kind,
326 span,
327 entries,
328 } => {
329 self.add(
330 Some(self.xref_folder),
331 NodeKind::XrefSection {
332 kind: *kind,
333 span: *span,
334 entries: *entries,
335 },
336 );
337 }
338 Element::Trailer { dict, span } => {
341 self.trailer_dict = Some(dict.clone());
342 self.trailer_span = Some(*span);
343 }
344 Element::StartXref { offset, span } => {
345 self.add(
346 Some(self.xref_folder),
347 NodeKind::StartXref {
348 offset: *offset,
349 span: *span,
350 },
351 );
352 }
353 Element::Eof { span } => {
354 self.add(Some(self.xref_folder), NodeKind::Eof { span: *span });
355 }
356 Element::Page { index, r } => {
357 let page_node = self.add(
358 Some(self.pages_folder),
359 NodeKind::Page {
360 index: *index,
361 r: *r,
362 },
363 );
364 let fonts = self.add(Some(page_node), NodeKind::FontsFolder { page: *index });
365 let images = self.add(Some(page_node), NodeKind::ImagesFolder { page: *index });
366 let annotations = self.add(
367 Some(page_node),
368 NodeKind::AnnotationsFolder { page: *index },
369 );
370 let contents =
371 self.add(Some(page_node), NodeKind::ContentsFolder { page: *index });
372 self.page_folders.insert(
373 *index,
374 PageFolders {
375 page_node,
376 fonts,
377 images,
378 annotations,
379 contents,
380 },
381 );
382 }
383 Element::Font {
390 page: Some(page),
391 r,
392 subtype,
393 base_font,
394 } => {
395 if let Some(folders) = self.page_folders.get(page).copied() {
396 self.add(
397 Some(folders.fonts),
398 NodeKind::Font {
399 r: *r,
400 subtype: subtype.clone(),
401 base_font: base_font.clone(),
402 },
403 );
404 }
405 }
406 Element::Image {
407 page: Some(page),
408 r,
409 width,
410 height,
411 } => {
412 if let Some(folders) = self.page_folders.get(page).copied() {
413 self.add(
414 Some(folders.images),
415 NodeKind::Image {
416 r: *r,
417 width: *width,
418 height: *height,
419 },
420 );
421 }
422 }
423 Element::Annotation { page, r, subtype } => {
424 if let Some(folders) = self.page_folders.get(page).copied() {
425 self.add(
426 Some(folders.annotations),
427 NodeKind::Annotation {
428 r: *r,
429 subtype: subtype.clone(),
430 },
431 );
432 }
433 }
434 Element::Font { page: None, .. }
437 | Element::Image { page: None, .. }
438 | Element::ContentOp { .. } => {}
439 }
440 }
441 if done {
442 match req {
443 TreeReq::Physical => self.physical = LoadState::Loaded,
444 TreeReq::Logical => {
445 self.logical = LoadState::Loaded;
446 let loaded: Vec<NodeId> = self
447 .page_folders
448 .values()
449 .flat_map(|folders| [folders.fonts, folders.images, folders.annotations])
450 .collect();
451 for id in loaded {
452 self.nodes[id].load = LoadState::Loaded;
453 }
454 }
455 TreeReq::Contents { .. } => {}
456 }
457 }
458 }
459
460 pub fn apply_contents(&mut self, page: usize, refs: &[ObjRef]) {
465 let Some(folders) = self.page_folders.get(&page).copied() else {
466 return;
467 };
468 if self.nodes[folders.contents].load == LoadState::Loaded {
469 return;
470 }
471 for r in refs {
472 self.add(Some(folders.contents), NodeKind::ContentsStream { r: *r });
473 }
474 self.nodes[folders.contents].load = LoadState::Loaded;
475 }
476
477 pub fn mark_failed(&mut self, req: TreeReq) {
479 match req {
480 TreeReq::Physical => self.physical = LoadState::Failed,
481 TreeReq::Logical => self.logical = LoadState::Failed,
482 TreeReq::Contents { page } => {
483 if let Some(folders) = self.page_folders.get(&page).copied() {
484 self.nodes[folders.contents].load = LoadState::Failed;
485 }
486 }
487 }
488 }
489
490 pub fn visible_rows(&self) -> Vec<TreeRow> {
492 let mut rows = Vec::new();
493 self.walk(self.root, 0, &mut rows);
494 rows
495 }
496
497 fn walk(&self, id: NodeId, depth: usize, rows: &mut Vec<TreeRow>) {
498 rows.push(TreeRow { id, depth });
499 if self.nodes[id].expanded {
500 for child in self.nodes[id].children.clone() {
501 self.walk(child, depth + 1, rows);
502 }
503 }
504 }
505
506 pub fn label(&self, id: NodeId) -> String {
508 match &self.nodes[id].kind {
509 NodeKind::Document => {
510 format!("Document · PDF {}.{}", self.version.0, self.version.1)
511 }
512 NodeKind::PagesFolder => format!("Pages ({})", self.page_count),
513 NodeKind::Page { index, .. } => format!("Page {}", index + 1),
514 NodeKind::FontsFolder { .. } => self.folder_label("Fonts", id),
515 NodeKind::Font {
516 r,
517 subtype,
518 base_font,
519 } => {
520 let face = base_font.as_ref().unwrap_or(subtype);
521 format!("{} · {} {}", face.0, r.num, r.gen)
522 }
523 NodeKind::ImagesFolder { .. } => self.folder_label("Images", id),
524 NodeKind::Image { r, width, height } => {
525 format!("{}x{} · {} {}", width, height, r.num, r.gen)
526 }
527 NodeKind::AnnotationsFolder { .. } => self.folder_label("Annotations", id),
528 NodeKind::Annotation { r, subtype } => {
529 format!("{} · {} {}", subtype.0, r.num, r.gen)
530 }
531 NodeKind::ContentsFolder { .. } => self.folder_label("Contents", id),
532 NodeKind::ContentsStream { r } => format!("stream {} {}", r.num, r.gen),
533 NodeKind::ObjectsFolder => match self.physical {
534 LoadState::Loaded => {
535 format!("Objects ({})", self.nodes[id].children.len())
536 }
537 LoadState::NotLoaded | LoadState::Loading | LoadState::Failed => {
538 "Objects".to_string()
539 }
540 },
541 NodeKind::Object { r, .. } => format!("obj {} {}", r.num, r.gen),
542 NodeKind::XrefFolder => match self.physical {
543 LoadState::Loaded => {
544 let secs = self.nodes[id]
545 .children
546 .iter()
547 .filter(|child| {
548 matches!(self.nodes[**child].kind, NodeKind::XrefSection { .. })
549 })
550 .count();
551 format!("Xref ({} secs)", secs)
552 }
553 LoadState::NotLoaded | LoadState::Loading | LoadState::Failed => "Xref".to_string(),
554 },
555 NodeKind::XrefSection { kind, entries, .. } => match kind {
556 XrefKind::Table => format!("xref table · {} entries", entries),
557 XrefKind::Stream => format!("xref stream · {} entries", entries),
558 },
559 NodeKind::StartXref { offset, .. } => format!("startxref → {}", offset),
560 NodeKind::Eof { .. } => "%%EOF".to_string(),
561 NodeKind::Trailer => "Trailer".to_string(),
562 }
563 }
564
565 fn folder_label(&self, name: &str, id: NodeId) -> String {
566 match self.nodes[id].load {
567 LoadState::Loaded => format!("{} ({})", name, self.nodes[id].children.len()),
568 LoadState::NotLoaded | LoadState::Loading | LoadState::Failed => name.to_string(),
569 }
570 }
571
572 fn short_label(&self, id: NodeId) -> String {
574 match &self.nodes[id].kind {
575 NodeKind::Document => "Document".to_string(),
576 NodeKind::PagesFolder => "Pages".to_string(),
577 NodeKind::Page { index, .. } => format!("Page {}", index + 1),
578 NodeKind::FontsFolder { .. } => "Fonts".to_string(),
579 NodeKind::ImagesFolder { .. } => "Images".to_string(),
580 NodeKind::AnnotationsFolder { .. } => "Annotations".to_string(),
581 NodeKind::ContentsFolder { .. } => "Contents".to_string(),
582 NodeKind::ObjectsFolder => "Objects".to_string(),
583 NodeKind::XrefFolder => "Xref".to_string(),
584 NodeKind::Trailer => "Trailer".to_string(),
585 NodeKind::Font { .. }
586 | NodeKind::Image { .. }
587 | NodeKind::Annotation { .. }
588 | NodeKind::ContentsStream { .. }
589 | NodeKind::Object { .. }
590 | NodeKind::XrefSection { .. }
591 | NodeKind::StartXref { .. }
592 | NodeKind::Eof { .. } => self.label(id),
593 }
594 }
595
596 pub fn breadcrumb(&self) -> String {
598 let mut parts = Vec::new();
599 let mut cursor = Some(self.selected);
600 while let Some(id) = cursor {
601 parts.push(self.short_label(id));
602 cursor = self.nodes[id].parent;
603 }
604 parts.reverse();
605 format!("/{}", parts.join("/"))
606 }
607
608 pub fn find_object(&self, r: ObjRef) -> Option<NodeId> {
610 self.nodes[self.objects_folder]
611 .children
612 .iter()
613 .copied()
614 .find(|id| match self.nodes[*id].kind {
615 NodeKind::Object { r: node_ref, .. } => node_ref.num == r.num,
616 NodeKind::Document
617 | NodeKind::PagesFolder
618 | NodeKind::Page { .. }
619 | NodeKind::FontsFolder { .. }
620 | NodeKind::Font { .. }
621 | NodeKind::ImagesFolder { .. }
622 | NodeKind::Image { .. }
623 | NodeKind::AnnotationsFolder { .. }
624 | NodeKind::Annotation { .. }
625 | NodeKind::ContentsFolder { .. }
626 | NodeKind::ContentsStream { .. }
627 | NodeKind::ObjectsFolder
628 | NodeKind::XrefFolder
629 | NodeKind::XrefSection { .. }
630 | NodeKind::StartXref { .. }
631 | NodeKind::Eof { .. }
632 | NodeKind::Trailer => false,
633 })
634 }
635
636 pub fn reveal(&mut self, id: NodeId) {
638 let mut cursor = self.nodes[id].parent;
639 while let Some(parent_id) = cursor {
640 self.nodes[parent_id].expanded = true;
641 cursor = self.nodes[parent_id].parent;
642 }
643 }
644
645 pub fn selection_ref(&self, id: NodeId) -> Option<ObjRef> {
647 match self.nodes[id].kind {
648 NodeKind::Page { r, .. }
649 | NodeKind::Font { r, .. }
650 | NodeKind::Image { r, .. }
651 | NodeKind::Annotation { r, .. }
652 | NodeKind::ContentsStream { r }
653 | NodeKind::Object { r, .. } => Some(r),
654 NodeKind::Document
655 | NodeKind::PagesFolder
656 | NodeKind::FontsFolder { .. }
657 | NodeKind::ImagesFolder { .. }
658 | NodeKind::AnnotationsFolder { .. }
659 | NodeKind::ContentsFolder { .. }
660 | NodeKind::ObjectsFolder
661 | NodeKind::XrefFolder
662 | NodeKind::XrefSection { .. }
663 | NodeKind::StartXref { .. }
664 | NodeKind::Eof { .. }
665 | NodeKind::Trailer => None,
666 }
667 }
668
669 pub fn query(&self, id: NodeId) -> Option<String> {
674 match self.nodes[id].kind {
675 NodeKind::Document => Some(".".to_string()),
676 NodeKind::PagesFolder => Some(".pages".to_string()),
677 NodeKind::Page { index, .. } => Some(format!(".pages[{index}]")),
678 NodeKind::FontsFolder { page } => Some(format!(".pages[{page}].fonts")),
679 NodeKind::ImagesFolder { page } => Some(format!(".pages[{page}].images")),
680 NodeKind::AnnotationsFolder { page } => Some(format!(".pages[{page}].annotations")),
681 NodeKind::ContentsFolder { page } => {
682 let r = self.page_ref(page)?;
683 Some(format!(".objects[\"{} {}\"].value.Contents", r.num, r.gen))
684 }
685 NodeKind::ObjectsFolder => Some(".objects".to_string()),
686 NodeKind::Object { r, .. }
687 | NodeKind::Font { r, .. }
688 | NodeKind::Image { r, .. }
689 | NodeKind::Annotation { r, .. }
690 | NodeKind::ContentsStream { r } => Some(format!(".objects[\"{} {}\"]", r.num, r.gen)),
691 NodeKind::XrefFolder => Some(".xref".to_string()),
692 NodeKind::XrefSection { .. } => {
693 let index = self.nodes[self.xref_folder]
694 .children
695 .iter()
696 .filter(|child| {
697 matches!(self.nodes[**child].kind, NodeKind::XrefSection { .. })
698 })
699 .position(|child| *child == id)?;
700 Some(format!(".xref[{index}]"))
701 }
702 NodeKind::StartXref { .. } => Some(".startxref".to_string()),
703 NodeKind::Trailer => Some(".trailer".to_string()),
704 NodeKind::Eof { .. } => None,
705 }
706 }
707
708 pub fn object_span(&self, num: u32) -> Option<(Span, Option<(ObjRef, Span)>)> {
710 self.object_spans.get(&num).copied()
711 }
712
713 pub fn page_ref(&self, page: usize) -> Option<ObjRef> {
715 let folders = self.page_folders.get(&page)?;
716 if let NodeKind::Page { r, .. } = self.nodes[folders.page_node].kind {
717 Some(r)
718 } else {
719 None
720 }
721 }
722
723 pub fn page_of(&self, id: NodeId) -> Option<usize> {
725 let mut cursor = Some(id);
726 while let Some(node_id) = cursor {
727 let page = match self.nodes[node_id].kind {
728 NodeKind::Page { index, .. } => Some(index),
729 NodeKind::FontsFolder { page }
730 | NodeKind::ImagesFolder { page }
731 | NodeKind::AnnotationsFolder { page }
732 | NodeKind::ContentsFolder { page } => Some(page),
733 NodeKind::Document
734 | NodeKind::PagesFolder
735 | NodeKind::Font { .. }
736 | NodeKind::Image { .. }
737 | NodeKind::Annotation { .. }
738 | NodeKind::ContentsStream { .. }
739 | NodeKind::ObjectsFolder
740 | NodeKind::Object { .. }
741 | NodeKind::XrefFolder
742 | NodeKind::XrefSection { .. }
743 | NodeKind::StartXref { .. }
744 | NodeKind::Eof { .. }
745 | NodeKind::Trailer => None,
746 };
747 if let Some(index) = page {
748 return Some(index);
749 }
750 cursor = self.nodes[node_id].parent;
751 }
752 None
753 }
754
755 fn selected_position(&self, rows: &[TreeRow]) -> usize {
756 rows.iter()
757 .position(|row| row.id == self.selected)
758 .unwrap_or(0)
759 }
760
761 pub fn select_next(&mut self) {
762 let rows = self.visible_rows();
763 let position = self.selected_position(&rows);
764 if position + 1 < rows.len() {
765 self.selected = rows[position + 1].id;
766 }
767 }
768
769 pub fn select_prev(&mut self) {
770 let rows = self.visible_rows();
771 let position = self.selected_position(&rows);
772 if position > 0 {
773 self.selected = rows[position - 1].id;
774 }
775 }
776
777 pub fn select_top(&mut self) {
778 self.selected = self.root;
779 }
780
781 pub fn select_bottom(&mut self) {
782 if let Some(row) = self.visible_rows().last() {
783 self.selected = row.id;
784 }
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use super::*;
791 use pdfboss_core::elements::{Element, Span, XrefKind};
792 use pdfboss_core::{Dict, Name, ObjRef, Object};
793
794 fn obj_ref(num: u32) -> ObjRef {
795 ObjRef { num, gen: 0 }
796 }
797
798 fn physical_batch() -> Vec<Element> {
799 vec![
800 Element::Header {
801 version: (1, 7),
802 span: Span { start: 0, end: 15 },
803 },
804 Element::IndirectObject {
805 r: obj_ref(1),
806 object: Object::Null,
807 span: Span { start: 15, end: 64 },
808 in_objstm: None,
809 },
810 Element::IndirectObject {
811 r: obj_ref(2),
812 object: Object::Null,
813 span: Span {
814 start: 64,
815 end: 120,
816 },
817 in_objstm: Some((obj_ref(9), Span { start: 4, end: 30 })),
818 },
819 Element::XrefSection {
820 kind: XrefKind::Table,
821 span: Span {
822 start: 120,
823 end: 260,
824 },
825 entries: 3,
826 },
827 Element::Trailer {
828 dict: Dict::new(),
829 span: Span {
830 start: 260,
831 end: 300,
832 },
833 },
834 Element::StartXref {
835 offset: 120,
836 span: Span {
837 start: 300,
838 end: 314,
839 },
840 },
841 Element::Eof {
842 span: Span {
843 start: 314,
844 end: 320,
845 },
846 },
847 ]
848 }
849
850 fn logical_batch() -> Vec<Element> {
851 vec![
852 Element::Page {
853 index: 0,
854 r: obj_ref(3),
855 },
856 Element::Font {
857 page: Some(0),
858 r: obj_ref(5),
859 subtype: Name("Type1".to_string()),
860 base_font: Some(Name("Helvetica".to_string())),
861 },
862 Element::Image {
863 page: Some(0),
864 r: obj_ref(7),
865 width: 32,
866 height: 16,
867 },
868 Element::Annotation {
869 page: 0,
870 r: obj_ref(8),
871 subtype: Name("Link".to_string()),
872 },
873 ]
874 }
875
876 #[test]
877 fn new_tree_has_root_and_four_sections() {
878 let tree = TreeState::new((1, 7), 14);
879 let rows = tree.visible_rows();
880 let labels: Vec<String> = rows.iter().map(|row| tree.label(row.id)).collect();
881 assert_eq!(
882 labels,
883 vec![
884 "Document · PDF 1.7",
885 "Pages (14)",
886 "Objects",
887 "Xref",
888 "Trailer",
889 ]
890 );
891 assert_eq!(rows[0].depth, 0);
892 assert_eq!(rows[1].depth, 1);
893 assert!(tree.node(tree.root).expanded);
894 assert!(!tree.node(tree.pages_folder).expanded);
895 }
896
897 #[test]
898 fn physical_batch_populates_objects_xref_and_trailer() {
899 let mut tree = TreeState::new((1, 7), 1);
900 assert_eq!(tree.expand(tree.objects_folder), Some(TreeReq::Physical));
901 assert_eq!(tree.physical, LoadState::Loading);
902 tree.apply_batch(TreeReq::Physical, &physical_batch(), true);
903 assert_eq!(tree.physical, LoadState::Loaded);
904 assert_eq!(tree.label(tree.objects_folder), "Objects (2)");
905 assert_eq!(tree.label(tree.xref_folder), "Xref (1 secs)");
906 assert_eq!(tree.header_span, Some(Span { start: 0, end: 15 }));
907 assert_eq!(
908 tree.trailer_span,
909 Some(Span {
910 start: 260,
911 end: 300
912 })
913 );
914 assert!(tree.trailer_dict.is_some());
915 let object_ids = tree.node(tree.objects_folder).children.clone();
916 assert_eq!(tree.label(object_ids[0]), "obj 1 0");
917 assert_eq!(tree.label(object_ids[1]), "obj 2 0");
918 assert_eq!(
919 tree.object_span(1),
920 Some((Span { start: 15, end: 64 }, None))
921 );
922 assert_eq!(
923 tree.object_span(2),
924 Some((
925 Span {
926 start: 64,
927 end: 120
928 },
929 Some((obj_ref(9), Span { start: 4, end: 30 }))
930 ))
931 );
932 tree.expand(tree.xref_folder);
933 let xref_ids = tree.node(tree.xref_folder).children.clone();
934 let labels: Vec<String> = xref_ids.iter().map(|id| tree.label(*id)).collect();
935 assert_eq!(
936 labels,
937 vec!["xref table · 3 entries", "startxref → 120", "%%EOF"]
938 );
939 }
940
941 #[test]
942 fn query_addresses_every_node_kind_in_the_wire_tree() {
943 let mut tree = TreeState::new((1, 7), 1);
944 tree.apply_batch(TreeReq::Physical, &physical_batch(), true);
945 tree.apply_batch(TreeReq::Logical, &logical_batch(), true);
946 tree.apply_contents(0, &[obj_ref(4)]);
947
948 assert_eq!(tree.query(tree.root).as_deref(), Some("."));
949 assert_eq!(tree.query(tree.pages_folder).as_deref(), Some(".pages"));
950 assert_eq!(tree.query(tree.objects_folder).as_deref(), Some(".objects"));
951 assert_eq!(tree.query(tree.xref_folder).as_deref(), Some(".xref"));
952 assert_eq!(tree.query(tree.trailer_node).as_deref(), Some(".trailer"));
953
954 let objects = tree.node(tree.objects_folder).children.clone();
955 assert_eq!(tree.query(objects[0]).as_deref(), Some(".objects[\"1 0\"]"));
956 assert_eq!(tree.query(objects[1]).as_deref(), Some(".objects[\"2 0\"]"));
957
958 let xref_children = tree.node(tree.xref_folder).children.clone();
959 assert_eq!(tree.query(xref_children[0]).as_deref(), Some(".xref[0]"));
960 assert_eq!(tree.query(xref_children[1]).as_deref(), Some(".startxref"));
961 assert_eq!(tree.query(xref_children[2]), None, "%%EOF has no wire form");
962
963 let page = tree.node(tree.pages_folder).children[0];
964 assert_eq!(tree.query(page).as_deref(), Some(".pages[0]"));
965 for folder in tree.node(page).children.clone() {
966 let expected = match tree.node(folder).kind {
967 NodeKind::FontsFolder { .. } => ".pages[0].fonts",
968 NodeKind::ImagesFolder { .. } => ".pages[0].images",
969 NodeKind::AnnotationsFolder { .. } => ".pages[0].annotations",
970 NodeKind::ContentsFolder { .. } => ".objects[\"3 0\"].value.Contents",
973 ref other => panic!("unexpected page child {other:?}"),
974 };
975 assert_eq!(tree.query(folder).as_deref(), Some(expected));
976 for leaf in tree.node(folder).children.clone() {
977 let expected = match tree.node(leaf).kind {
978 NodeKind::Font { .. } => ".objects[\"5 0\"]",
979 NodeKind::Image { .. } => ".objects[\"7 0\"]",
980 NodeKind::Annotation { .. } => ".objects[\"8 0\"]",
981 NodeKind::ContentsStream { .. } => ".objects[\"4 0\"]",
982 ref other => panic!("unexpected leaf {other:?}"),
983 };
984 assert_eq!(tree.query(leaf).as_deref(), Some(expected));
985 }
986 }
987 }
988
989 #[test]
993 fn xref_query_index_counts_sections_only() {
994 let elements = vec![
995 Element::XrefSection {
996 kind: XrefKind::Table,
997 span: Span { start: 0, end: 10 },
998 entries: 1,
999 },
1000 Element::StartXref {
1001 offset: 0,
1002 span: Span { start: 10, end: 20 },
1003 },
1004 Element::Eof {
1005 span: Span { start: 20, end: 26 },
1006 },
1007 Element::XrefSection {
1008 kind: XrefKind::Stream,
1009 span: Span { start: 26, end: 40 },
1010 entries: 2,
1011 },
1012 ];
1013 let mut tree = TreeState::new((1, 7), 0);
1014 tree.apply_batch(TreeReq::Physical, &elements, true);
1015 let children = tree.node(tree.xref_folder).children.clone();
1016 assert_eq!(tree.query(children[0]).as_deref(), Some(".xref[0]"));
1017 assert_eq!(tree.query(children[3]).as_deref(), Some(".xref[1]"));
1018 }
1019
1020 #[test]
1021 fn expanding_objects_twice_requests_once() {
1022 let mut tree = TreeState::new((1, 7), 1);
1023 assert_eq!(tree.expand(tree.objects_folder), Some(TreeReq::Physical));
1024 assert_eq!(tree.expand(tree.objects_folder), None);
1025 assert_eq!(tree.expand(tree.xref_folder), None, "same physical pass");
1026 }
1027
1028 #[test]
1029 fn logical_batch_builds_page_subtree() {
1030 let mut tree = TreeState::new((1, 7), 1);
1031 assert_eq!(tree.expand(tree.pages_folder), Some(TreeReq::Logical));
1032 tree.apply_batch(TreeReq::Logical, &logical_batch(), true);
1033 assert_eq!(tree.logical, LoadState::Loaded);
1034 let page_id = tree.node(tree.pages_folder).children[0];
1035 assert_eq!(tree.label(page_id), "Page 1");
1036 assert_eq!(tree.page_ref(0), Some(obj_ref(3)));
1037 tree.expand(page_id);
1038 let folder_ids = tree.node(page_id).children.clone();
1039 let labels: Vec<String> = folder_ids.iter().map(|id| tree.label(*id)).collect();
1040 assert_eq!(
1041 labels,
1042 vec!["Fonts (1)", "Images (1)", "Annotations (1)", "Contents"]
1043 );
1044 let font_id = tree.node(folder_ids[0]).children[0];
1045 assert_eq!(tree.label(font_id), "Helvetica · 5 0");
1046 let image_id = tree.node(folder_ids[1]).children[0];
1047 assert_eq!(tree.label(image_id), "32x16 · 7 0");
1048 let annot_id = tree.node(folder_ids[2]).children[0];
1049 assert_eq!(tree.label(annot_id), "Link · 8 0");
1050 assert_eq!(tree.page_of(font_id), Some(0));
1051 }
1052
1053 #[test]
1054 fn contents_folder_requests_and_fills() {
1055 let mut tree = TreeState::new((1, 7), 1);
1056 tree.expand(tree.pages_folder);
1057 tree.apply_batch(TreeReq::Logical, &logical_batch(), true);
1058 let page_id = tree.node(tree.pages_folder).children[0];
1059 tree.expand(page_id);
1060 let contents_id = tree.node(page_id).children[3];
1061 assert_eq!(
1062 tree.expand(contents_id),
1063 Some(TreeReq::Contents { page: 0 })
1064 );
1065 assert_eq!(tree.expand(contents_id), None, "already loading");
1066 tree.apply_contents(0, &[obj_ref(4)]);
1067 assert_eq!(tree.label(contents_id), "Contents (1)");
1068 let stream_id = tree.node(contents_id).children[0];
1069 assert_eq!(tree.label(stream_id), "stream 4 0");
1070 assert_eq!(tree.selection_ref(stream_id), Some(obj_ref(4)));
1071 }
1072
1073 #[test]
1074 fn selection_moves_over_visible_rows_only() {
1075 let mut tree = TreeState::new((1, 7), 1);
1076 tree.apply_batch(TreeReq::Physical, &physical_batch(), true);
1077 assert_eq!(tree.selected, tree.root);
1078 tree.select_next();
1079 assert_eq!(tree.selected, tree.pages_folder);
1080 tree.select_next();
1081 assert_eq!(tree.selected, tree.objects_folder);
1082 tree.select_next();
1084 assert_eq!(tree.selected, tree.xref_folder);
1085 tree.select_next();
1086 assert_eq!(tree.selected, tree.trailer_node);
1087 tree.select_next();
1088 assert_eq!(tree.selected, tree.trailer_node, "clamped at bottom");
1089 tree.select_top();
1090 assert_eq!(tree.selected, tree.root);
1091 tree.select_bottom();
1092 assert_eq!(tree.selected, tree.trailer_node);
1093 tree.select_prev();
1094 assert_eq!(tree.selected, tree.xref_folder);
1095 }
1096
1097 #[test]
1098 fn collapse_or_parent_folds_then_climbs() {
1099 let mut tree = TreeState::new((1, 7), 1);
1100 tree.apply_batch(TreeReq::Physical, &physical_batch(), true);
1101 tree.expand(tree.objects_folder);
1102 let first_object = tree.node(tree.objects_folder).children[0];
1103 tree.selected = first_object;
1104 assert!(
1105 tree.collapse_or_parent(first_object),
1106 "leaf climbs to parent"
1107 );
1108 assert_eq!(tree.selected, tree.objects_folder);
1109 assert!(
1110 tree.collapse_or_parent(tree.objects_folder),
1111 "folds open branch"
1112 );
1113 assert!(!tree.node(tree.objects_folder).expanded);
1114 }
1115
1116 #[test]
1117 fn find_object_and_reveal_expand_ancestors() {
1118 let mut tree = TreeState::new((1, 7), 1);
1119 tree.apply_batch(TreeReq::Physical, &physical_batch(), true);
1120 let id = tree.find_object(obj_ref(2)).expect("object 2 present");
1121 assert!(!tree.node(tree.objects_folder).expanded);
1122 tree.reveal(id);
1123 assert!(tree.node(tree.objects_folder).expanded);
1124 assert!(tree.visible_rows().iter().any(|row| row.id == id));
1125 assert_eq!(tree.find_object(obj_ref(42)), None);
1126 }
1127
1128 #[test]
1129 fn breadcrumb_walks_short_labels() {
1130 let mut tree = TreeState::new((1, 7), 1);
1131 assert_eq!(tree.breadcrumb(), "/Document");
1132 tree.apply_batch(TreeReq::Physical, &physical_batch(), true);
1133 let id = tree.find_object(obj_ref(1)).expect("object 1");
1134 tree.selected = id;
1135 assert_eq!(tree.breadcrumb(), "/Document/Objects/obj 1 0");
1136 tree.selected = tree.trailer_node;
1137 assert_eq!(tree.breadcrumb(), "/Document/Trailer");
1138 }
1139
1140 #[test]
1141 fn mark_failed_records_failure() {
1142 let mut tree = TreeState::new((1, 7), 1);
1143 tree.expand(tree.objects_folder);
1144 tree.mark_failed(TreeReq::Physical);
1145 assert_eq!(tree.physical, LoadState::Failed);
1146 }
1147
1148 #[test]
1149 fn reapplying_a_batch_is_a_no_op() {
1150 let mut tree = TreeState::new((1, 7), 1);
1151 tree.apply_batch(TreeReq::Physical, &physical_batch(), true);
1152 tree.apply_batch(TreeReq::Logical, &logical_batch(), true);
1153 tree.expand(tree.objects_folder);
1154 tree.expand(tree.xref_folder);
1155 tree.expand(tree.pages_folder);
1156 let page_id = tree.node(tree.pages_folder).children[0];
1157 tree.expand(page_id);
1158 let before = tree.visible_rows();
1159
1160 tree.apply_batch(TreeReq::Physical, &physical_batch(), true);
1161 tree.apply_batch(TreeReq::Logical, &logical_batch(), true);
1162 let after = tree.visible_rows();
1163
1164 assert_eq!(
1165 before, after,
1166 "reapplying an already-loaded batch must not duplicate nodes"
1167 );
1168 }
1169
1170 #[test]
1171 fn failed_section_can_be_retried_by_expanding() {
1172 let mut tree = TreeState::new((1, 7), 1);
1173 assert_eq!(tree.expand(tree.objects_folder), Some(TreeReq::Physical));
1174 tree.mark_failed(TreeReq::Physical);
1175 assert_eq!(tree.physical, LoadState::Failed);
1176 assert_eq!(
1177 tree.expand(tree.objects_folder),
1178 Some(TreeReq::Physical),
1179 "re-expanding a failed section must re-request its load"
1180 );
1181 assert_eq!(tree.physical, LoadState::Loading);
1182 }
1183}