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        if let Some((root_id, _)) = self.root() {
213            self.serialize_node(root_id, 0, &mut output);
214        }
215        output
216    }
217
218    /// Recursively serialize a node and its children
219    fn serialize_node(&self, id: AreaId, depth: usize, output: &mut String) {
220        if let Some(node) = self.get(id) {
221            let indent = "  ".repeat(depth);
222            let area = &node.area;
223            let type_name = format!("{:?}", area.area_type);
224            let geom = &area.geometry;
225            output.push_str(&format!(
226                "{}{} ({:.1}pt,{:.1}pt {:.1}pt x {:.1}pt)\n",
227                indent,
228                type_name,
229                geom.x.to_pt(),
230                geom.y.to_pt(),
231                geom.width.to_pt(),
232                geom.height.to_pt()
233            ));
234            // If there's text content, show it
235            if let Some(text) = area.text_content() {
236                let preview: String = text.chars().take(40).collect();
237                output.push_str(&format!("{}  Text {:?}\n", indent, preview));
238            }
239            for child_id in self.children(id) {
240                self.serialize_node(child_id, depth + 1, output);
241            }
242        }
243    }
244
245    /// Calculate total height of footnotes for a page
246    pub fn footnote_height(&self, page_id: AreaId) -> fop_types::Length {
247        use fop_types::Length;
248
249        let mut total_height = Length::ZERO;
250
251        if let Some(footnotes) = self.get_footnotes(page_id) {
252            // Add separator height (1pt line + 6pt spacing)
253            if !footnotes.is_empty() {
254                total_height += Length::from_pt(7.0);
255            }
256
257            // Add height of each footnote
258            for &footnote_id in footnotes {
259                if let Some(node) = self.get(footnote_id) {
260                    total_height += node.area.height();
261                }
262            }
263        }
264
265        total_height
266    }
267
268    /// Compute structural differences between two area trees.
269    ///
270    /// Returns a list of difference descriptions. An empty list means the trees
271    /// are structurally identical.
272    pub fn diff(&self, other: &AreaTree) -> Vec<String> {
273        let mut diffs = Vec::new();
274        match (self.root(), other.root()) {
275            (None, None) => {}
276            (Some(_), None) => diffs.push("self has root, other has no root".to_string()),
277            (None, Some(_)) => diffs.push("self has no root, other has root".to_string()),
278            (Some((id1, _)), Some((id2, _))) => {
279                self.diff_nodes(id1, other, id2, "", &mut diffs);
280            }
281        }
282        diffs
283    }
284
285    fn diff_nodes(
286        &self,
287        id1: AreaId,
288        other: &AreaTree,
289        id2: AreaId,
290        path: &str,
291        diffs: &mut Vec<String>,
292    ) {
293        let (node1, node2) = match (self.get(id1), other.get(id2)) {
294            (Some(n1), Some(n2)) => (n1, n2),
295            _ => {
296                diffs.push(format!("Node missing at path: {}", path));
297                return;
298            }
299        };
300
301        // Compare area types
302        if std::mem::discriminant(&node1.area.area_type)
303            != std::mem::discriminant(&node2.area.area_type)
304        {
305            diffs.push(format!(
306                "Area type mismatch at {}: {:?} vs {:?}",
307                path, node1.area.area_type, node2.area.area_type
308            ));
309        }
310
311        // Compare text content
312        if node1.area.text_content() != node2.area.text_content() {
313            diffs.push(format!(
314                "Text content mismatch at {}: {:?} vs {:?}",
315                path,
316                node1.area.text_content(),
317                node2.area.text_content()
318            ));
319        }
320
321        // Compare children count
322        let children1 = self.children(id1);
323        let children2 = other.children(id2);
324        if children1.len() != children2.len() {
325            diffs.push(format!(
326                "Children count mismatch at {}: {} vs {}",
327                path,
328                children1.len(),
329                children2.len()
330            ));
331        }
332
333        // Recurse into children
334        let min_len = children1.len().min(children2.len());
335        for i in 0..min_len {
336            let child_path = format!("{}/{}", path, i);
337            self.diff_nodes(children1[i], other, children2[i], &child_path, diffs);
338        }
339    }
340}
341
342impl Default for AreaTree {
343    fn default() -> Self {
344        Self::new()
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351    use crate::area::AreaType;
352    use fop_types::{Length, Point, Rect, Size};
353
354    fn make_rect(w: f64, h: f64) -> Rect {
355        Rect::from_point_size(
356            Point::ZERO,
357            Size::new(Length::from_pt(w), Length::from_pt(h)),
358        )
359    }
360
361    #[test]
362    fn test_area_tree_creation() {
363        let tree = AreaTree::new();
364        assert_eq!(tree.len(), 0);
365        assert!(tree.is_empty());
366    }
367
368    #[test]
369    fn test_add_area() {
370        let mut tree = AreaTree::new();
371        let area = Area::new(AreaType::Page, make_rect(210.0, 297.0));
372        let id = tree.add_area(area);
373
374        assert_eq!(tree.len(), 1);
375        assert_eq!(id.index(), 0);
376        assert!(tree.get(id).is_some());
377    }
378
379    #[test]
380    fn test_parent_child() {
381        let mut tree = AreaTree::new();
382
383        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
384        let block = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
385
386        tree.append_child(page, block)
387            .expect("test: should succeed");
388
389        assert_eq!(
390            tree.get(block).expect("test: should succeed").parent,
391            Some(page)
392        );
393        assert_eq!(
394            tree.get(page).expect("test: should succeed").first_child,
395            Some(block)
396        );
397    }
398
399    #[test]
400    fn test_multiple_children() {
401        let mut tree = AreaTree::new();
402
403        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
404        let block1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
405        let block2 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
406        let block3 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
407
408        tree.append_child(page, block1)
409            .expect("test: should succeed");
410        tree.append_child(page, block2)
411            .expect("test: should succeed");
412        tree.append_child(page, block3)
413            .expect("test: should succeed");
414
415        let children = tree.children(page);
416        assert_eq!(children.len(), 3);
417        assert_eq!(children[0], block1);
418        assert_eq!(children[1], block2);
419        assert_eq!(children[2], block3);
420    }
421
422    #[test]
423    fn test_find_page_ancestor() {
424        let mut tree = AreaTree::new();
425
426        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
427        let region = tree.add_area(Area::new(AreaType::Region, make_rect(160.0, 247.0)));
428        let block = tree.add_area(Area::new(AreaType::Block, make_rect(160.0, 20.0)));
429
430        tree.append_child(page, region)
431            .expect("test: should succeed");
432        tree.append_child(region, block)
433            .expect("test: should succeed");
434
435        // block -> region -> page: should find the page
436        assert_eq!(tree.find_page_ancestor(block), Some(page));
437
438        // region -> page: should find the page
439        assert_eq!(tree.find_page_ancestor(region), Some(page));
440
441        // page itself: should return itself
442        assert_eq!(tree.find_page_ancestor(page), Some(page));
443    }
444
445    #[test]
446    fn test_footnote_tracking() {
447        let mut tree = AreaTree::new();
448
449        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
450        let footnote = tree.add_area(Area::new(AreaType::Footnote, make_rect(160.0, 12.0)));
451
452        // Initially no footnotes
453        assert!(tree.get_footnotes(page).is_none());
454
455        // Add a footnote
456        tree.add_footnote(page, footnote);
457        let footnotes = tree.get_footnotes(page).expect("test: should succeed");
458        assert_eq!(footnotes.len(), 1);
459        assert_eq!(footnotes[0], footnote);
460
461        // Footnote height should be > 0 (including separator height)
462        let height = tree.footnote_height(page);
463        assert!(height > fop_types::Length::ZERO);
464    }
465    #[test]
466    fn test_area_tree_serialize_empty() {
467        let tree = AreaTree::new();
468        let s = tree.serialize();
469        // Empty tree should produce an empty string or minimal output — must not panic
470        let _ = s;
471    }
472
473    #[test]
474    fn test_area_tree_serialize_single_page() {
475        let mut tree = AreaTree::new();
476        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
477        let _ = page;
478
479        let s = tree.serialize();
480        // Should mention Page type in some form
481        assert!(
482            !s.is_empty(),
483            "Serialize should produce non-empty output for a page"
484        );
485        assert!(
486            s.contains("Page"),
487            "Serialize should mention AreaType::Page"
488        );
489    }
490
491    #[test]
492    fn test_area_tree_serialize_nested_areas() {
493        let mut tree = AreaTree::new();
494        let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
495        let region = tree.add_area(Area::new(AreaType::Region, make_rect(160.0, 247.0)));
496        let block = tree.add_area(Area::new(AreaType::Block, make_rect(160.0, 20.0)));
497        tree.append_child(page, region)
498            .expect("test: should succeed");
499        tree.append_child(region, block)
500            .expect("test: should succeed");
501
502        let s = tree.serialize();
503        assert!(s.contains("Page"), "Should contain Page");
504        assert!(s.contains("Region"), "Should contain Region");
505        assert!(s.contains("Block"), "Should contain Block");
506    }
507
508    #[test]
509    fn test_area_tree_diff_two_empty_trees() {
510        let tree1 = AreaTree::new();
511        let tree2 = AreaTree::new();
512        let diffs = tree1.diff(&tree2);
513        assert!(
514            diffs.is_empty(),
515            "Two empty trees should have no differences"
516        );
517    }
518
519    #[test]
520    fn test_area_tree_diff_self_has_root_other_does_not() {
521        let mut tree1 = AreaTree::new();
522        let tree2 = AreaTree::new();
523        tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
524
525        let diffs = tree1.diff(&tree2);
526        assert!(
527            !diffs.is_empty(),
528            "Should detect that self has root but other does not"
529        );
530        assert!(
531            diffs[0].contains("no root"),
532            "Diff message should mention missing root"
533        );
534    }
535
536    #[test]
537    fn test_area_tree_diff_other_has_root_self_does_not() {
538        let tree1 = AreaTree::new();
539        let mut tree2 = AreaTree::new();
540        tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
541
542        let diffs = tree1.diff(&tree2);
543        assert!(
544            !diffs.is_empty(),
545            "Should detect that other has root but self does not"
546        );
547    }
548
549    #[test]
550    fn test_area_tree_diff_identical_single_page() {
551        let mut tree1 = AreaTree::new();
552        let mut tree2 = AreaTree::new();
553        tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
554        tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
555
556        let diffs = tree1.diff(&tree2);
557        assert!(
558            diffs.is_empty(),
559            "Identical single-page trees should have no diffs"
560        );
561    }
562
563    #[test]
564    fn test_area_tree_diff_different_root_type() {
565        let mut tree1 = AreaTree::new();
566        let mut tree2 = AreaTree::new();
567        tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
568        tree2.add_area(Area::new(AreaType::Region, make_rect(210.0, 297.0)));
569
570        let diffs = tree1.diff(&tree2);
571        assert!(
572            !diffs.is_empty(),
573            "Different area types should produce a diff"
574        );
575        assert!(
576            diffs
577                .iter()
578                .any(|d| d.contains("type") || d.contains("Area")),
579            "Diff should describe type mismatch"
580        );
581    }
582
583    #[test]
584    fn test_area_tree_diff_different_child_count() {
585        let mut tree1 = AreaTree::new();
586        let mut tree2 = AreaTree::new();
587
588        let page1 = tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
589        let child1 = tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 20.0)));
590        tree1
591            .append_child(page1, child1)
592            .expect("test: should succeed");
593
594        let _page2 = tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
595        // tree2 has no children
596
597        let diffs = tree1.diff(&tree2);
598        assert!(
599            !diffs.is_empty(),
600            "Different child counts should produce a diff"
601        );
602    }
603
604    #[test]
605    fn test_area_tree_diff_identical_with_text_content() {
606        let mut tree1 = AreaTree::new();
607        let mut tree2 = AreaTree::new();
608
609        let mut area1 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
610        area1.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
611        let mut area2 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
612        area2.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
613
614        tree1.add_area(area1);
615        tree2.add_area(area2);
616
617        let diffs = tree1.diff(&tree2);
618        assert!(
619            diffs.is_empty(),
620            "Trees with identical text content should have no diffs"
621        );
622    }
623
624    #[test]
625    fn test_area_tree_diff_different_text_content() {
626        let mut tree1 = AreaTree::new();
627        let mut tree2 = AreaTree::new();
628
629        let mut area1 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
630        area1.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
631        let mut area2 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
632        area2.content = Some(crate::area::types::AreaContent::Text("World".to_string()));
633
634        tree1.add_area(area1);
635        tree2.add_area(area2);
636
637        let diffs = tree1.diff(&tree2);
638        assert!(
639            !diffs.is_empty(),
640            "Different text content should produce a diff"
641        );
642        assert!(
643            diffs.iter().any(|d| d.contains("Text")),
644            "Diff should mention text content"
645        );
646    }
647}
648
649#[cfg(test)]
650mod extended_tests {
651    use super::*;
652    use crate::area::AreaType;
653    use fop_types::{Length, Point, Rect, Size};
654
655    fn make_rect(w: f64, h: f64) -> Rect {
656        Rect::from_point_size(
657            Point::ZERO,
658            Size::new(Length::from_pt(w), Length::from_pt(h)),
659        )
660    }
661
662    // ---- AreaId tests ----
663
664    #[test]
665    fn test_area_id_index() {
666        let mut tree = AreaTree::new();
667        let a0 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
668        let a1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
669        assert_eq!(a0.index(), 0);
670        assert_eq!(a1.index(), 1);
671    }
672
673    #[test]
674    fn test_area_id_from_index() {
675        let id = AreaId::from_index(42);
676        assert_eq!(id.index(), 42);
677    }
678
679    #[test]
680    fn test_area_id_display() {
681        let id = AreaId::from_index(7);
682        assert_eq!(format!("{}", id), "Area(7)");
683    }
684
685    // ---- AreaNode tests ----
686
687    #[test]
688    fn test_area_node_has_children_initially_false() {
689        let node = AreaNode::new(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
690        assert!(!node.has_children());
691    }
692
693    #[test]
694    fn test_area_node_has_children_after_append() {
695        let mut tree = AreaTree::new();
696        let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
697        let child = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
698        tree.append_child(parent, child)
699            .expect("test: should succeed");
700
701        let parent_node = tree.get(parent).expect("test: should succeed");
702        assert!(parent_node.has_children());
703    }
704
705    // ---- AreaTree size tracking tests ----
706
707    #[test]
708    fn test_area_tree_with_capacity() {
709        let tree = AreaTree::with_capacity(100);
710        assert!(tree.is_empty());
711    }
712
713    #[test]
714    fn test_area_tree_len_tracks_additions() {
715        let mut tree = AreaTree::new();
716        assert_eq!(tree.len(), 0);
717        tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
718        assert_eq!(tree.len(), 1);
719        tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
720        assert_eq!(tree.len(), 2);
721    }
722
723    // ---- get / get_mut tests ----
724
725    #[test]
726    fn test_get_mut_can_modify_area() {
727        let mut tree = AreaTree::new();
728        let id = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
729
730        {
731            let node = tree.get_mut(id).expect("test: should succeed");
732            node.area.geometry.width = Length::from_pt(200.0);
733        }
734
735        let node = tree.get(id).expect("test: should succeed");
736        assert_eq!(node.area.geometry.width, Length::from_pt(200.0));
737    }
738
739    #[test]
740    fn test_get_nonexistent_returns_none() {
741        let tree = AreaTree::new();
742        let bogus_id = AreaId::from_index(999);
743        assert!(tree.get(bogus_id).is_none());
744        // NOTE: get_mut would panic for out-of-bounds on Vec, so we skip it
745    }
746
747    // ---- children ordering tests ----
748
749    #[test]
750    fn test_children_ordering_preserved() {
751        let mut tree = AreaTree::new();
752        let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
753        let c1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 20.0)));
754        let c2 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 30.0)));
755        let c3 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 40.0)));
756
757        tree.append_child(parent, c1).expect("test: should succeed");
758        tree.append_child(parent, c2).expect("test: should succeed");
759        tree.append_child(parent, c3).expect("test: should succeed");
760
761        let children = tree.children(parent);
762        assert_eq!(children.len(), 3);
763        assert_eq!(children[0], c1);
764        assert_eq!(children[1], c2);
765        assert_eq!(children[2], c3);
766    }
767
768    #[test]
769    fn test_children_returns_empty_for_leaf() {
770        let mut tree = AreaTree::new();
771        let leaf = tree.add_area(Area::new(AreaType::Text, make_rect(100.0, 12.0)));
772        let children = tree.children(leaf);
773        assert!(children.is_empty());
774    }
775
776    // ---- iter and root tests ----
777
778    #[test]
779    fn test_iter_over_all_areas() {
780        let mut tree = AreaTree::new();
781        tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
782        tree.add_area(Area::new(AreaType::Block, make_rect(200.0, 50.0)));
783        tree.add_area(Area::new(AreaType::Block, make_rect(300.0, 50.0)));
784
785        let count = tree.iter().count();
786        assert_eq!(count, 3);
787    }
788
789    #[test]
790    fn test_root_returns_first_area() {
791        let mut tree = AreaTree::new();
792        let first_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
793        tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
794
795        let (root_id, root_node) = tree.root().expect("test: should succeed");
796        assert_eq!(root_id, first_id);
797        assert_eq!(root_node.area.area_type, AreaType::Page);
798    }
799
800    #[test]
801    fn test_root_empty_tree_returns_none() {
802        let tree = AreaTree::new();
803        assert!(tree.root().is_none());
804    }
805
806    // ---- footnote tracking tests ----
807
808    #[test]
809    fn test_footnote_height_zero_with_no_footnotes() {
810        let mut tree = AreaTree::new();
811        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
812        let height = tree.footnote_height(page_id);
813        assert_eq!(height, Length::ZERO);
814    }
815
816    #[test]
817    fn test_footnote_tracking_multiple_footnotes() {
818        let mut tree = AreaTree::new();
819        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
820
821        let fn1 = tree.add_area(Area::new(AreaType::Footnote, make_rect(400.0, 30.0)));
822        let fn2 = tree.add_area(Area::new(AreaType::Footnote, make_rect(400.0, 20.0)));
823
824        tree.add_footnote(page_id, fn1);
825        tree.add_footnote(page_id, fn2);
826
827        let footnotes = tree.get_footnotes(page_id).expect("test: should succeed");
828        assert_eq!(footnotes.len(), 2);
829
830        let total_height = tree.footnote_height(page_id);
831        // footnote_height adds 7pt for separator line + 6pt spacing when footnotes present
832        // Total = 30 + 20 + 7 = 57pt
833        assert_eq!(total_height, Length::from_pt(57.0));
834    }
835
836    #[test]
837    fn test_get_footnotes_returns_none_for_page_without_footnotes() {
838        let mut tree = AreaTree::new();
839        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
840        assert!(tree.get_footnotes(page_id).is_none());
841    }
842
843    // ---- find_page_ancestor tests ----
844
845    #[test]
846    fn test_find_page_ancestor_direct_child() {
847        let mut tree = AreaTree::new();
848        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
849        let block_id = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
850        tree.append_child(page_id, block_id)
851            .expect("test: should succeed");
852
853        let ancestor = tree.find_page_ancestor(block_id);
854        assert_eq!(ancestor, Some(page_id));
855    }
856
857    #[test]
858    fn test_find_page_ancestor_deep_nesting() {
859        let mut tree = AreaTree::new();
860        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
861        let region_id = tree.add_area(Area::new(AreaType::Region, make_rect(450.0, 700.0)));
862        let block_id = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
863        let text_id = tree.add_area(Area::new(AreaType::Text, make_rect(300.0, 12.0)));
864
865        tree.append_child(page_id, region_id)
866            .expect("test: should succeed");
867        tree.append_child(region_id, block_id)
868            .expect("test: should succeed");
869        tree.append_child(block_id, text_id)
870            .expect("test: should succeed");
871
872        let ancestor = tree.find_page_ancestor(text_id);
873        assert_eq!(ancestor, Some(page_id));
874    }
875
876    #[test]
877    fn test_find_page_ancestor_returns_none_for_page_itself() {
878        let mut tree = AreaTree::new();
879        let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
880        // A Page's ancestor is itself – the function walks *up* looking for Page type
881        // If the area IS the page, the result depends on implementation
882        // Just ensure it doesn't panic
883        let _result = tree.find_page_ancestor(page_id);
884    }
885
886    // ---- serialize tests ----
887
888    #[test]
889    fn test_serialize_produces_non_empty_string_for_non_empty_tree() {
890        let mut tree = AreaTree::new();
891        tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
892        let serialized = tree.serialize();
893        assert!(!serialized.is_empty());
894    }
895
896    #[test]
897    fn test_serialize_empty_tree_is_empty_string() {
898        let tree = AreaTree::new();
899        let serialized = tree.serialize();
900        assert!(serialized.is_empty());
901    }
902
903    #[test]
904    fn test_serialize_contains_area_type() {
905        let mut tree = AreaTree::new();
906        tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
907        let serialized = tree.serialize();
908        assert!(
909            serialized.contains("Page"),
910            "Serialized tree should mention 'Page'"
911        );
912    }
913
914    // ---- diff tests ----
915
916    #[test]
917    fn test_diff_same_single_area_no_diffs() {
918        let mut tree1 = AreaTree::new();
919        let mut tree2 = AreaTree::new();
920        tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
921        tree2.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
922
923        let diffs = tree1.diff(&tree2);
924        assert!(
925            diffs.is_empty(),
926            "Identical trees should have no diffs, got: {:?}",
927            diffs
928        );
929    }
930
931    #[test]
932    fn test_diff_different_area_types_produces_diff() {
933        let mut tree1 = AreaTree::new();
934        let mut tree2 = AreaTree::new();
935        tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
936        tree2.add_area(Area::new(AreaType::Line, make_rect(100.0, 50.0)));
937
938        let diffs = tree1.diff(&tree2);
939        assert!(
940            !diffs.is_empty(),
941            "Different area types should produce a diff"
942        );
943    }
944
945    #[test]
946    fn test_diff_is_reflexive() {
947        let mut tree = AreaTree::new();
948        let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
949        let child = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
950        tree.append_child(parent, child)
951            .expect("test: should succeed");
952
953        // Diffing a tree against itself should produce no diffs
954        // We need a clone — create an identical tree instead
955        let mut tree2 = AreaTree::new();
956        let parent2 = tree2.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
957        let child2 = tree2.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
958        tree2
959            .append_child(parent2, child2)
960            .expect("test: should succeed");
961
962        let diffs = tree.diff(&tree2);
963        assert!(diffs.is_empty(), "Identical trees should have no diffs");
964    }
965
966    // ---- append_child error case ----
967
968    #[test]
969    fn test_append_child_to_nonexistent_parent_returns_error() {
970        let mut tree = AreaTree::new();
971        let child = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
972        let bogus_parent = AreaId::from_index(999);
973        let result = tree.append_child(bogus_parent, child);
974        assert!(
975            result.is_err(),
976            "Appending to nonexistent parent should fail"
977        );
978    }
979}