Skip to main content

fop_layout/area/
area_tree.rs

1//! Area tree structure
2//!
3//! Similar to the FO tree, uses arena allocation for areas.
4
5use super::Area;
6use std::fmt;
7
8/// Index-based handle to an area in the tree
9#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
10pub struct AreaId(usize);
11
12impl AreaId {
13    /// Get the raw index value
14    #[inline]
15    pub fn index(self) -> usize {
16        self.0
17    }
18
19    /// Create an AreaId from a raw index
20    #[inline]
21    pub const fn from_index(index: usize) -> Self {
22        Self(index)
23    }
24}
25
26impl fmt::Display for AreaId {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(f, "Area({})", self.0)
29    }
30}
31
32/// A node in the area tree
33pub struct AreaNode {
34    /// The area data
35    pub area: Area,
36
37    /// Parent area ID
38    pub parent: Option<AreaId>,
39
40    /// First child area ID
41    pub first_child: Option<AreaId>,
42
43    /// Next sibling area ID
44    pub next_sibling: Option<AreaId>,
45}
46
47impl AreaNode {
48    /// Create a new area node
49    pub fn new(area: Area) -> Self {
50        Self {
51            area,
52            parent: None,
53            first_child: None,
54            next_sibling: None,
55        }
56    }
57
58    /// Check if this node has children
59    #[inline]
60    pub fn has_children(&self) -> bool {
61        self.first_child.is_some()
62    }
63}
64
65/// Area tree - arena-allocated tree of areas
66pub struct AreaTree {
67    nodes: Vec<AreaNode>,
68    /// Footnotes collected for current page, mapped by page ID
69    footnotes: std::collections::HashMap<AreaId, Vec<AreaId>>,
70    /// Document language from xml:lang on fo:root (for PDF /Lang entry)
71    pub document_lang: Option<String>,
72}
73
74impl AreaTree {
75    /// Create a new empty area tree
76    pub fn new() -> Self {
77        Self {
78            nodes: Vec::new(),
79            footnotes: std::collections::HashMap::new(),
80            document_lang: None,
81        }
82    }
83
84    /// Create an area tree with preallocated capacity
85    pub fn with_capacity(capacity: usize) -> Self {
86        Self {
87            nodes: Vec::with_capacity(capacity),
88            footnotes: std::collections::HashMap::new(),
89            document_lang: None,
90        }
91    }
92
93    /// Add an area to the tree
94    pub fn add_area(&mut self, area: Area) -> AreaId {
95        let id = AreaId(self.nodes.len());
96        self.nodes.push(AreaNode::new(area));
97        id
98    }
99
100    /// Get a reference to an area by ID
101    pub fn get(&self, id: AreaId) -> Option<&AreaNode> {
102        self.nodes.get(id.0)
103    }
104
105    /// Get a mutable reference to an area by ID
106    pub fn get_mut(&mut self, id: AreaId) -> Option<&mut AreaNode> {
107        self.nodes.get_mut(id.0)
108    }
109
110    /// Get the number of areas in the tree
111    #[inline]
112    pub fn len(&self) -> usize {
113        self.nodes.len()
114    }
115
116    /// Check if the tree is empty
117    #[inline]
118    pub fn is_empty(&self) -> bool {
119        self.nodes.is_empty()
120    }
121
122    /// Append a child area to a parent
123    pub fn append_child(&mut self, parent: AreaId, child: AreaId) -> Result<(), String> {
124        if child.0 >= self.nodes.len() {
125            return Err(format!("Child area {} does not exist", child.0));
126        }
127        if parent.0 >= self.nodes.len() {
128            return Err(format!("Parent area {} does not exist", parent.0));
129        }
130
131        // Set parent of child
132        self.nodes[child.0].parent = Some(parent);
133
134        // Update parent's children list
135        let parent_node = &mut self.nodes[parent.0];
136        if let Some(first_child) = parent_node.first_child {
137            // Find last sibling and append
138            let mut last_sibling = first_child;
139            while let Some(next) = self.nodes[last_sibling.0].next_sibling {
140                last_sibling = next;
141            }
142            self.nodes[last_sibling.0].next_sibling = Some(child);
143        } else {
144            // First child
145            parent_node.first_child = Some(child);
146        }
147
148        Ok(())
149    }
150
151    /// Get all children of an area
152    pub fn children(&self, parent: AreaId) -> Vec<AreaId> {
153        let mut children = Vec::new();
154        if let Some(node) = self.get(parent) {
155            let mut current = node.first_child;
156            while let Some(child_id) = current {
157                children.push(child_id);
158                current = self.get(child_id).and_then(|n| n.next_sibling);
159            }
160        }
161        children
162    }
163
164    /// Iterate over all areas
165    pub fn iter(&self) -> impl Iterator<Item = (AreaId, &AreaNode)> {
166        self.nodes
167            .iter()
168            .enumerate()
169            .map(|(i, node)| (AreaId(i), node))
170    }
171
172    /// Get the root area (first area in tree)
173    pub fn root(&self) -> Option<(AreaId, &AreaNode)> {
174        if self.nodes.is_empty() {
175            None
176        } else {
177            Some((AreaId(0), &self.nodes[0]))
178        }
179    }
180
181    /// Add a footnote to a page
182    pub fn add_footnote(&mut self, page_id: AreaId, footnote_id: AreaId) {
183        self.footnotes.entry(page_id).or_default().push(footnote_id);
184    }
185
186    /// Get footnotes for a page
187    pub fn get_footnotes(&self, page_id: AreaId) -> Option<&Vec<AreaId>> {
188        self.footnotes.get(&page_id)
189    }
190
191    /// Find the nearest Page ancestor for a given area (or the area itself if it's a Page)
192    pub fn find_page_ancestor(&self, area_id: AreaId) -> Option<AreaId> {
193        let mut current = area_id;
194        loop {
195            if let Some(node) = self.get(current) {
196                if node.area.area_type == crate::area::AreaType::Page {
197                    return Some(current);
198                }
199                match node.parent {
200                    Some(parent_id) => current = parent_id,
201                    None => return None,
202                }
203            } else {
204                return None;
205            }
206        }
207    }
208
209    /// Serialize the area tree as an indented text tree for debugging
210    pub fn serialize(&self) -> String {
211        let mut output = String::new();
212        // Walk all parentless roots to handle multi-page-sequence documents
213        // where each page-sequence creates a separate top-level Page area.
214        for (id, node) in self.iter() {
215            if node.parent.is_none() {
216                self.serialize_node(id, 0, &mut output);
217            }
218        }
219        output
220    }
221
222    /// Recursively serialize a node and its children
223    fn serialize_node(&self, id: AreaId, depth: usize, output: &mut String) {
224        if let Some(node) = self.get(id) {
225            let indent = "  ".repeat(depth);
226            let area = &node.area;
227            let type_name = format!("{:?}", area.area_type);
228            let geom = &area.geometry;
229            output.push_str(&format!(
230                "{}{} ({:.1}pt,{:.1}pt {:.1}pt x {:.1}pt)\n",
231                indent,
232                type_name,
233                geom.x.to_pt(),
234                geom.y.to_pt(),
235                geom.width.to_pt(),
236                geom.height.to_pt()
237            ));
238            // If there's text content, show it
239            if let Some(text) = area.text_content() {
240                let preview: String = text.chars().take(40).collect();
241                output.push_str(&format!("{}  Text {:?}\n", indent, preview));
242            }
243            for child_id in self.children(id) {
244                self.serialize_node(child_id, depth + 1, output);
245            }
246        }
247    }
248
249    /// Calculate total height of footnotes for a page
250    pub fn footnote_height(&self, page_id: AreaId) -> fop_types::Length {
251        use fop_types::Length;
252
253        let mut total_height = Length::ZERO;
254
255        if let Some(footnotes) = self.get_footnotes(page_id) {
256            // Add separator height (1pt line + 6pt spacing)
257            if !footnotes.is_empty() {
258                total_height += Length::from_pt(7.0);
259            }
260
261            // Add height of each footnote
262            for &footnote_id in footnotes {
263                if let Some(node) = self.get(footnote_id) {
264                    total_height += node.area.height();
265                }
266            }
267        }
268
269        total_height
270    }
271
272    /// Compute structural differences between two area trees.
273    ///
274    /// Returns a list of difference descriptions. An empty list means the trees
275    /// are structurally identical.
276    pub fn diff(&self, other: &AreaTree) -> Vec<String> {
277        let mut diffs = Vec::new();
278        match (self.root(), other.root()) {
279            (None, None) => {}
280            (Some(_), None) => diffs.push("self has root, other has no root".to_string()),
281            (None, Some(_)) => diffs.push("self has no root, other has root".to_string()),
282            (Some((id1, _)), Some((id2, _))) => {
283                self.diff_nodes(id1, other, id2, "", &mut diffs);
284            }
285        }
286        diffs
287    }
288
289    fn diff_nodes(
290        &self,
291        id1: AreaId,
292        other: &AreaTree,
293        id2: AreaId,
294        path: &str,
295        diffs: &mut Vec<String>,
296    ) {
297        let (node1, node2) = match (self.get(id1), other.get(id2)) {
298            (Some(n1), Some(n2)) => (n1, n2),
299            _ => {
300                diffs.push(format!("Node missing at path: {}", path));
301                return;
302            }
303        };
304
305        // Compare area types
306        if std::mem::discriminant(&node1.area.area_type)
307            != std::mem::discriminant(&node2.area.area_type)
308        {
309            diffs.push(format!(
310                "Area type mismatch at {}: {:?} vs {:?}",
311                path, node1.area.area_type, node2.area.area_type
312            ));
313        }
314
315        // Compare text content
316        if node1.area.text_content() != node2.area.text_content() {
317            diffs.push(format!(
318                "Text content mismatch at {}: {:?} vs {:?}",
319                path,
320                node1.area.text_content(),
321                node2.area.text_content()
322            ));
323        }
324
325        // Compare children count
326        let children1 = self.children(id1);
327        let children2 = other.children(id2);
328        if children1.len() != children2.len() {
329            diffs.push(format!(
330                "Children count mismatch at {}: {} vs {}",
331                path,
332                children1.len(),
333                children2.len()
334            ));
335        }
336
337        // Recurse into children
338        let min_len = children1.len().min(children2.len());
339        for i in 0..min_len {
340            let child_path = format!("{}/{}", path, i);
341            self.diff_nodes(children1[i], other, children2[i], &child_path, diffs);
342        }
343    }
344}
345
346impl Default for AreaTree {
347    fn default() -> Self {
348        Self::new()
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355    use crate::area::AreaType;
356    use fop_types::{Length, Point, Rect, Size};
357
358    fn make_rect(w: f64, h: f64) -> Rect {
359        Rect::from_point_size(
360            Point::ZERO,
361            Size::new(Length::from_pt(w), Length::from_pt(h)),
362        )
363    }
364
365    #[test]
366    fn test_area_tree_creation() {
367        let tree = AreaTree::new();
368        assert_eq!(tree.len(), 0);
369        assert!(tree.is_empty());
370    }
371
372    #[test]
373    fn test_add_area() {
374        let mut tree = AreaTree::new();
375        let area = Area::new(AreaType::Page, make_rect(210.0, 297.0));
376        let id = tree.add_area(area);
377
378        assert_eq!(tree.len(), 1);
379        assert_eq!(id.index(), 0);
380        assert!(tree.get(id).is_some());
381    }
382
383    #[test]
384    fn test_parent_child() {
385        let mut tree = AreaTree::new();
386
387        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
388        let block = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
389
390        tree.append_child(page, block)
391            .expect("test: should succeed");
392
393        assert_eq!(
394            tree.get(block).expect("test: should succeed").parent,
395            Some(page)
396        );
397        assert_eq!(
398            tree.get(page).expect("test: should succeed").first_child,
399            Some(block)
400        );
401    }
402
403    #[test]
404    fn test_multiple_children() {
405        let mut tree = AreaTree::new();
406
407        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
408        let block1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
409        let block2 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
410        let block3 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
411
412        tree.append_child(page, block1)
413            .expect("test: should succeed");
414        tree.append_child(page, block2)
415            .expect("test: should succeed");
416        tree.append_child(page, block3)
417            .expect("test: should succeed");
418
419        let children = tree.children(page);
420        assert_eq!(children.len(), 3);
421        assert_eq!(children[0], block1);
422        assert_eq!(children[1], block2);
423        assert_eq!(children[2], block3);
424    }
425
426    #[test]
427    fn test_find_page_ancestor() {
428        let mut tree = AreaTree::new();
429
430        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
431        let region = tree.add_area(Area::new(AreaType::Region, make_rect(160.0, 247.0)));
432        let block = tree.add_area(Area::new(AreaType::Block, make_rect(160.0, 20.0)));
433
434        tree.append_child(page, region)
435            .expect("test: should succeed");
436        tree.append_child(region, block)
437            .expect("test: should succeed");
438
439        // block -> region -> page: should find the page
440        assert_eq!(tree.find_page_ancestor(block), Some(page));
441
442        // region -> page: should find the page
443        assert_eq!(tree.find_page_ancestor(region), Some(page));
444
445        // page itself: should return itself
446        assert_eq!(tree.find_page_ancestor(page), Some(page));
447    }
448
449    #[test]
450    fn test_footnote_tracking() {
451        let mut tree = AreaTree::new();
452
453        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
454        let footnote = tree.add_area(Area::new(AreaType::Footnote, make_rect(160.0, 12.0)));
455
456        // Initially no footnotes
457        assert!(tree.get_footnotes(page).is_none());
458
459        // Add a footnote
460        tree.add_footnote(page, footnote);
461        let footnotes = tree.get_footnotes(page).expect("test: should succeed");
462        assert_eq!(footnotes.len(), 1);
463        assert_eq!(footnotes[0], footnote);
464
465        // Footnote height should be > 0 (including separator height)
466        let height = tree.footnote_height(page);
467        assert!(height > fop_types::Length::ZERO);
468    }
469    #[test]
470    fn test_area_tree_serialize_empty() {
471        let tree = AreaTree::new();
472        let s = tree.serialize();
473        // Empty tree should produce an empty string or minimal output — must not panic
474        let _ = s;
475    }
476
477    #[test]
478    fn test_area_tree_serialize_single_page() {
479        let mut tree = AreaTree::new();
480        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
481        let _ = page;
482
483        let s = tree.serialize();
484        // Should mention Page type in some form
485        assert!(
486            !s.is_empty(),
487            "Serialize should produce non-empty output for a page"
488        );
489        assert!(
490            s.contains("Page"),
491            "Serialize should mention AreaType::Page"
492        );
493    }
494
495    #[test]
496    fn test_area_tree_serialize_nested_areas() {
497        let mut tree = AreaTree::new();
498        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
499        let region = tree.add_area(Area::new(AreaType::Region, make_rect(160.0, 247.0)));
500        let block = tree.add_area(Area::new(AreaType::Block, make_rect(160.0, 20.0)));
501        tree.append_child(page, region)
502            .expect("test: should succeed");
503        tree.append_child(region, block)
504            .expect("test: should succeed");
505
506        let s = tree.serialize();
507        assert!(s.contains("Page"), "Should contain Page");
508        assert!(s.contains("Region"), "Should contain Region");
509        assert!(s.contains("Block"), "Should contain Block");
510    }
511
512    #[test]
513    fn test_area_tree_diff_two_empty_trees() {
514        let tree1 = AreaTree::new();
515        let tree2 = AreaTree::new();
516        let diffs = tree1.diff(&tree2);
517        assert!(
518            diffs.is_empty(),
519            "Two empty trees should have no differences"
520        );
521    }
522
523    #[test]
524    fn test_area_tree_diff_self_has_root_other_does_not() {
525        let mut tree1 = AreaTree::new();
526        let tree2 = AreaTree::new();
527        tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
528
529        let diffs = tree1.diff(&tree2);
530        assert!(
531            !diffs.is_empty(),
532            "Should detect that self has root but other does not"
533        );
534        assert!(
535            diffs[0].contains("no root"),
536            "Diff message should mention missing root"
537        );
538    }
539
540    #[test]
541    fn test_area_tree_diff_other_has_root_self_does_not() {
542        let tree1 = AreaTree::new();
543        let mut tree2 = AreaTree::new();
544        tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
545
546        let diffs = tree1.diff(&tree2);
547        assert!(
548            !diffs.is_empty(),
549            "Should detect that other has root but self does not"
550        );
551    }
552
553    #[test]
554    fn test_area_tree_diff_identical_single_page() {
555        let mut tree1 = AreaTree::new();
556        let mut tree2 = AreaTree::new();
557        tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
558        tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
559
560        let diffs = tree1.diff(&tree2);
561        assert!(
562            diffs.is_empty(),
563            "Identical single-page trees should have no diffs"
564        );
565    }
566
567    #[test]
568    fn test_area_tree_diff_different_root_type() {
569        let mut tree1 = AreaTree::new();
570        let mut tree2 = AreaTree::new();
571        tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
572        tree2.add_area(Area::new(AreaType::Region, make_rect(210.0, 297.0)));
573
574        let diffs = tree1.diff(&tree2);
575        assert!(
576            !diffs.is_empty(),
577            "Different area types should produce a diff"
578        );
579        assert!(
580            diffs
581                .iter()
582                .any(|d| d.contains("type") || d.contains("Area")),
583            "Diff should describe type mismatch"
584        );
585    }
586
587    #[test]
588    fn test_area_tree_diff_different_child_count() {
589        let mut tree1 = AreaTree::new();
590        let mut tree2 = AreaTree::new();
591
592        let page1 = tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
593        let child1 = tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 20.0)));
594        tree1
595            .append_child(page1, child1)
596            .expect("test: should succeed");
597
598        let _page2 = tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
599        // tree2 has no children
600
601        let diffs = tree1.diff(&tree2);
602        assert!(
603            !diffs.is_empty(),
604            "Different child counts should produce a diff"
605        );
606    }
607
608    #[test]
609    fn test_area_tree_diff_identical_with_text_content() {
610        let mut tree1 = AreaTree::new();
611        let mut tree2 = AreaTree::new();
612
613        let mut area1 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
614        area1.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
615        let mut area2 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
616        area2.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
617
618        tree1.add_area(area1);
619        tree2.add_area(area2);
620
621        let diffs = tree1.diff(&tree2);
622        assert!(
623            diffs.is_empty(),
624            "Trees with identical text content should have no diffs"
625        );
626    }
627
628    #[test]
629    fn test_area_tree_diff_different_text_content() {
630        let mut tree1 = AreaTree::new();
631        let mut tree2 = AreaTree::new();
632
633        let mut area1 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
634        area1.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
635        let mut area2 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
636        area2.content = Some(crate::area::types::AreaContent::Text("World".to_string()));
637
638        tree1.add_area(area1);
639        tree2.add_area(area2);
640
641        let diffs = tree1.diff(&tree2);
642        assert!(
643            !diffs.is_empty(),
644            "Different text content should produce a diff"
645        );
646        assert!(
647            diffs.iter().any(|d| d.contains("Text")),
648            "Diff should mention text content"
649        );
650    }
651}
652
653#[cfg(test)]
654mod extended_tests {
655    use super::*;
656    use crate::area::AreaType;
657    use fop_types::{Length, Point, Rect, Size};
658
659    fn make_rect(w: f64, h: f64) -> Rect {
660        Rect::from_point_size(
661            Point::ZERO,
662            Size::new(Length::from_pt(w), Length::from_pt(h)),
663        )
664    }
665
666    // ---- AreaId tests ----
667
668    #[test]
669    fn test_area_id_index() {
670        let mut tree = AreaTree::new();
671        let a0 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
672        let a1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
673        assert_eq!(a0.index(), 0);
674        assert_eq!(a1.index(), 1);
675    }
676
677    #[test]
678    fn test_area_id_from_index() {
679        let id = AreaId::from_index(42);
680        assert_eq!(id.index(), 42);
681    }
682
683    #[test]
684    fn test_area_id_display() {
685        let id = AreaId::from_index(7);
686        assert_eq!(format!("{}", id), "Area(7)");
687    }
688
689    // ---- AreaNode tests ----
690
691    #[test]
692    fn test_area_node_has_children_initially_false() {
693        let node = AreaNode::new(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
694        assert!(!node.has_children());
695    }
696
697    #[test]
698    fn test_area_node_has_children_after_append() {
699        let mut tree = AreaTree::new();
700        let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
701        let child = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
702        tree.append_child(parent, child)
703            .expect("test: should succeed");
704
705        let parent_node = tree.get(parent).expect("test: should succeed");
706        assert!(parent_node.has_children());
707    }
708
709    // ---- AreaTree size tracking tests ----
710
711    #[test]
712    fn test_area_tree_with_capacity() {
713        let tree = AreaTree::with_capacity(100);
714        assert!(tree.is_empty());
715    }
716
717    #[test]
718    fn test_area_tree_len_tracks_additions() {
719        let mut tree = AreaTree::new();
720        assert_eq!(tree.len(), 0);
721        tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
722        assert_eq!(tree.len(), 1);
723        tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
724        assert_eq!(tree.len(), 2);
725    }
726
727    // ---- get / get_mut tests ----
728
729    #[test]
730    fn test_get_mut_can_modify_area() {
731        let mut tree = AreaTree::new();
732        let id = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
733
734        {
735            let node = tree.get_mut(id).expect("test: should succeed");
736            node.area.geometry.width = Length::from_pt(200.0);
737        }
738
739        let node = tree.get(id).expect("test: should succeed");
740        assert_eq!(node.area.geometry.width, Length::from_pt(200.0));
741    }
742
743    #[test]
744    fn test_get_nonexistent_returns_none() {
745        let tree = AreaTree::new();
746        let bogus_id = AreaId::from_index(999);
747        assert!(tree.get(bogus_id).is_none());
748        // NOTE: get_mut would panic for out-of-bounds on Vec, so we skip it
749    }
750
751    // ---- children ordering tests ----
752
753    #[test]
754    fn test_children_ordering_preserved() {
755        let mut tree = AreaTree::new();
756        let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
757        let c1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 20.0)));
758        let c2 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 30.0)));
759        let c3 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 40.0)));
760
761        tree.append_child(parent, c1).expect("test: should succeed");
762        tree.append_child(parent, c2).expect("test: should succeed");
763        tree.append_child(parent, c3).expect("test: should succeed");
764
765        let children = tree.children(parent);
766        assert_eq!(children.len(), 3);
767        assert_eq!(children[0], c1);
768        assert_eq!(children[1], c2);
769        assert_eq!(children[2], c3);
770    }
771
772    #[test]
773    fn test_children_returns_empty_for_leaf() {
774        let mut tree = AreaTree::new();
775        let leaf = tree.add_area(Area::new(AreaType::Text, make_rect(100.0, 12.0)));
776        let children = tree.children(leaf);
777        assert!(children.is_empty());
778    }
779
780    // ---- iter and root tests ----
781
782    #[test]
783    fn test_iter_over_all_areas() {
784        let mut tree = AreaTree::new();
785        tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
786        tree.add_area(Area::new(AreaType::Block, make_rect(200.0, 50.0)));
787        tree.add_area(Area::new(AreaType::Block, make_rect(300.0, 50.0)));
788
789        let count = tree.iter().count();
790        assert_eq!(count, 3);
791    }
792
793    #[test]
794    fn test_root_returns_first_area() {
795        let mut tree = AreaTree::new();
796        let first_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
797        tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
798
799        let (root_id, root_node) = tree.root().expect("test: should succeed");
800        assert_eq!(root_id, first_id);
801        assert_eq!(root_node.area.area_type, AreaType::Page);
802    }
803
804    #[test]
805    fn test_root_empty_tree_returns_none() {
806        let tree = AreaTree::new();
807        assert!(tree.root().is_none());
808    }
809
810    // ---- footnote tracking tests ----
811
812    #[test]
813    fn test_footnote_height_zero_with_no_footnotes() {
814        let mut tree = AreaTree::new();
815        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
816        let height = tree.footnote_height(page_id);
817        assert_eq!(height, Length::ZERO);
818    }
819
820    #[test]
821    fn test_footnote_tracking_multiple_footnotes() {
822        let mut tree = AreaTree::new();
823        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
824
825        let fn1 = tree.add_area(Area::new(AreaType::Footnote, make_rect(400.0, 30.0)));
826        let fn2 = tree.add_area(Area::new(AreaType::Footnote, make_rect(400.0, 20.0)));
827
828        tree.add_footnote(page_id, fn1);
829        tree.add_footnote(page_id, fn2);
830
831        let footnotes = tree.get_footnotes(page_id).expect("test: should succeed");
832        assert_eq!(footnotes.len(), 2);
833
834        let total_height = tree.footnote_height(page_id);
835        // footnote_height adds 7pt for separator line + 6pt spacing when footnotes present
836        // Total = 30 + 20 + 7 = 57pt
837        assert_eq!(total_height, Length::from_pt(57.0));
838    }
839
840    #[test]
841    fn test_get_footnotes_returns_none_for_page_without_footnotes() {
842        let mut tree = AreaTree::new();
843        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
844        assert!(tree.get_footnotes(page_id).is_none());
845    }
846
847    // ---- find_page_ancestor tests ----
848
849    #[test]
850    fn test_find_page_ancestor_direct_child() {
851        let mut tree = AreaTree::new();
852        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
853        let block_id = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
854        tree.append_child(page_id, block_id)
855            .expect("test: should succeed");
856
857        let ancestor = tree.find_page_ancestor(block_id);
858        assert_eq!(ancestor, Some(page_id));
859    }
860
861    #[test]
862    fn test_find_page_ancestor_deep_nesting() {
863        let mut tree = AreaTree::new();
864        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
865        let region_id = tree.add_area(Area::new(AreaType::Region, make_rect(450.0, 700.0)));
866        let block_id = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
867        let text_id = tree.add_area(Area::new(AreaType::Text, make_rect(300.0, 12.0)));
868
869        tree.append_child(page_id, region_id)
870            .expect("test: should succeed");
871        tree.append_child(region_id, block_id)
872            .expect("test: should succeed");
873        tree.append_child(block_id, text_id)
874            .expect("test: should succeed");
875
876        let ancestor = tree.find_page_ancestor(text_id);
877        assert_eq!(ancestor, Some(page_id));
878    }
879
880    #[test]
881    fn test_find_page_ancestor_returns_none_for_page_itself() {
882        let mut tree = AreaTree::new();
883        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
884        // A Page's ancestor is itself – the function walks *up* looking for Page type
885        // If the area IS the page, the result depends on implementation
886        // Just ensure it doesn't panic
887        let _result = tree.find_page_ancestor(page_id);
888    }
889
890    // ---- serialize tests ----
891
892    #[test]
893    fn test_serialize_produces_non_empty_string_for_non_empty_tree() {
894        let mut tree = AreaTree::new();
895        tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
896        let serialized = tree.serialize();
897        assert!(!serialized.is_empty());
898    }
899
900    #[test]
901    fn test_serialize_empty_tree_is_empty_string() {
902        let tree = AreaTree::new();
903        let serialized = tree.serialize();
904        assert!(serialized.is_empty());
905    }
906
907    #[test]
908    fn test_serialize_contains_area_type() {
909        let mut tree = AreaTree::new();
910        tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
911        let serialized = tree.serialize();
912        assert!(
913            serialized.contains("Page"),
914            "Serialized tree should mention 'Page'"
915        );
916    }
917
918    // ---- diff tests ----
919
920    #[test]
921    fn test_diff_same_single_area_no_diffs() {
922        let mut tree1 = AreaTree::new();
923        let mut tree2 = AreaTree::new();
924        tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
925        tree2.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
926
927        let diffs = tree1.diff(&tree2);
928        assert!(
929            diffs.is_empty(),
930            "Identical trees should have no diffs, got: {:?}",
931            diffs
932        );
933    }
934
935    #[test]
936    fn test_diff_different_area_types_produces_diff() {
937        let mut tree1 = AreaTree::new();
938        let mut tree2 = AreaTree::new();
939        tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
940        tree2.add_area(Area::new(AreaType::Line, make_rect(100.0, 50.0)));
941
942        let diffs = tree1.diff(&tree2);
943        assert!(
944            !diffs.is_empty(),
945            "Different area types should produce a diff"
946        );
947    }
948
949    #[test]
950    fn test_diff_is_reflexive() {
951        let mut tree = AreaTree::new();
952        let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
953        let child = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
954        tree.append_child(parent, child)
955            .expect("test: should succeed");
956
957        // Diffing a tree against itself should produce no diffs
958        // We need a clone — create an identical tree instead
959        let mut tree2 = AreaTree::new();
960        let parent2 = tree2.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
961        let child2 = tree2.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
962        tree2
963            .append_child(parent2, child2)
964            .expect("test: should succeed");
965
966        let diffs = tree.diff(&tree2);
967        assert!(diffs.is_empty(), "Identical trees should have no diffs");
968    }
969
970    // ---- append_child error case ----
971
972    #[test]
973    fn test_append_child_to_nonexistent_parent_returns_error() {
974        let mut tree = AreaTree::new();
975        let child = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
976        let bogus_parent = AreaId::from_index(999);
977        let result = tree.append_child(bogus_parent, child);
978        assert!(
979            result.is_err(),
980            "Appending to nonexistent parent should fail"
981        );
982    }
983}