use super::Area;
use std::fmt;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct AreaId(usize);
impl AreaId {
#[inline]
pub fn index(self) -> usize {
self.0
}
#[inline]
pub const fn from_index(index: usize) -> Self {
Self(index)
}
}
impl fmt::Display for AreaId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Area({})", self.0)
}
}
pub struct AreaNode {
pub area: Area,
pub parent: Option<AreaId>,
pub first_child: Option<AreaId>,
pub next_sibling: Option<AreaId>,
}
impl AreaNode {
pub fn new(area: Area) -> Self {
Self {
area,
parent: None,
first_child: None,
next_sibling: None,
}
}
#[inline]
pub fn has_children(&self) -> bool {
self.first_child.is_some()
}
}
pub struct AreaTree {
nodes: Vec<AreaNode>,
footnotes: std::collections::HashMap<AreaId, Vec<AreaId>>,
pub document_lang: Option<String>,
}
impl AreaTree {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
footnotes: std::collections::HashMap::new(),
document_lang: None,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
nodes: Vec::with_capacity(capacity),
footnotes: std::collections::HashMap::new(),
document_lang: None,
}
}
pub fn add_area(&mut self, area: Area) -> AreaId {
let id = AreaId(self.nodes.len());
self.nodes.push(AreaNode::new(area));
id
}
pub fn get(&self, id: AreaId) -> Option<&AreaNode> {
self.nodes.get(id.0)
}
pub fn get_mut(&mut self, id: AreaId) -> Option<&mut AreaNode> {
self.nodes.get_mut(id.0)
}
#[inline]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn append_child(&mut self, parent: AreaId, child: AreaId) -> Result<(), String> {
if child.0 >= self.nodes.len() {
return Err(format!("Child area {} does not exist", child.0));
}
if parent.0 >= self.nodes.len() {
return Err(format!("Parent area {} does not exist", parent.0));
}
self.nodes[child.0].parent = Some(parent);
let parent_node = &mut self.nodes[parent.0];
if let Some(first_child) = parent_node.first_child {
let mut last_sibling = first_child;
while let Some(next) = self.nodes[last_sibling.0].next_sibling {
last_sibling = next;
}
self.nodes[last_sibling.0].next_sibling = Some(child);
} else {
parent_node.first_child = Some(child);
}
Ok(())
}
pub fn children(&self, parent: AreaId) -> Vec<AreaId> {
let mut children = Vec::new();
if let Some(node) = self.get(parent) {
let mut current = node.first_child;
while let Some(child_id) = current {
children.push(child_id);
current = self.get(child_id).and_then(|n| n.next_sibling);
}
}
children
}
pub fn iter(&self) -> impl Iterator<Item = (AreaId, &AreaNode)> {
self.nodes
.iter()
.enumerate()
.map(|(i, node)| (AreaId(i), node))
}
pub fn root(&self) -> Option<(AreaId, &AreaNode)> {
if self.nodes.is_empty() {
None
} else {
Some((AreaId(0), &self.nodes[0]))
}
}
pub fn add_footnote(&mut self, page_id: AreaId, footnote_id: AreaId) {
self.footnotes.entry(page_id).or_default().push(footnote_id);
}
pub fn get_footnotes(&self, page_id: AreaId) -> Option<&Vec<AreaId>> {
self.footnotes.get(&page_id)
}
pub fn find_page_ancestor(&self, area_id: AreaId) -> Option<AreaId> {
let mut current = area_id;
loop {
if let Some(node) = self.get(current) {
if node.area.area_type == crate::area::AreaType::Page {
return Some(current);
}
match node.parent {
Some(parent_id) => current = parent_id,
None => return None,
}
} else {
return None;
}
}
}
pub fn serialize(&self) -> String {
let mut output = String::new();
for (id, node) in self.iter() {
if node.parent.is_none() {
self.serialize_node(id, 0, &mut output);
}
}
output
}
fn serialize_node(&self, id: AreaId, depth: usize, output: &mut String) {
if let Some(node) = self.get(id) {
let indent = " ".repeat(depth);
let area = &node.area;
let type_name = format!("{:?}", area.area_type);
let geom = &area.geometry;
output.push_str(&format!(
"{}{} ({:.1}pt,{:.1}pt {:.1}pt x {:.1}pt)\n",
indent,
type_name,
geom.x.to_pt(),
geom.y.to_pt(),
geom.width.to_pt(),
geom.height.to_pt()
));
if let Some(text) = area.text_content() {
let preview: String = text.chars().take(40).collect();
output.push_str(&format!("{} Text {:?}\n", indent, preview));
}
for child_id in self.children(id) {
self.serialize_node(child_id, depth + 1, output);
}
}
}
pub fn footnote_height(&self, page_id: AreaId) -> fop_types::Length {
use fop_types::Length;
let mut total_height = Length::ZERO;
if let Some(footnotes) = self.get_footnotes(page_id) {
if !footnotes.is_empty() {
total_height += Length::from_pt(7.0);
}
for &footnote_id in footnotes {
if let Some(node) = self.get(footnote_id) {
total_height += node.area.height();
}
}
}
total_height
}
pub fn diff(&self, other: &AreaTree) -> Vec<String> {
let mut diffs = Vec::new();
match (self.root(), other.root()) {
(None, None) => {}
(Some(_), None) => diffs.push("self has root, other has no root".to_string()),
(None, Some(_)) => diffs.push("self has no root, other has root".to_string()),
(Some((id1, _)), Some((id2, _))) => {
self.diff_nodes(id1, other, id2, "", &mut diffs);
}
}
diffs
}
fn diff_nodes(
&self,
id1: AreaId,
other: &AreaTree,
id2: AreaId,
path: &str,
diffs: &mut Vec<String>,
) {
let (node1, node2) = match (self.get(id1), other.get(id2)) {
(Some(n1), Some(n2)) => (n1, n2),
_ => {
diffs.push(format!("Node missing at path: {}", path));
return;
}
};
if std::mem::discriminant(&node1.area.area_type)
!= std::mem::discriminant(&node2.area.area_type)
{
diffs.push(format!(
"Area type mismatch at {}: {:?} vs {:?}",
path, node1.area.area_type, node2.area.area_type
));
}
if node1.area.text_content() != node2.area.text_content() {
diffs.push(format!(
"Text content mismatch at {}: {:?} vs {:?}",
path,
node1.area.text_content(),
node2.area.text_content()
));
}
let children1 = self.children(id1);
let children2 = other.children(id2);
if children1.len() != children2.len() {
diffs.push(format!(
"Children count mismatch at {}: {} vs {}",
path,
children1.len(),
children2.len()
));
}
let min_len = children1.len().min(children2.len());
for i in 0..min_len {
let child_path = format!("{}/{}", path, i);
self.diff_nodes(children1[i], other, children2[i], &child_path, diffs);
}
}
}
impl Default for AreaTree {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::area::AreaType;
use fop_types::{Length, Point, Rect, Size};
fn make_rect(w: f64, h: f64) -> Rect {
Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(w), Length::from_pt(h)),
)
}
#[test]
fn test_area_tree_creation() {
let tree = AreaTree::new();
assert_eq!(tree.len(), 0);
assert!(tree.is_empty());
}
#[test]
fn test_add_area() {
let mut tree = AreaTree::new();
let area = Area::new(AreaType::Page, make_rect(210.0, 297.0));
let id = tree.add_area(area);
assert_eq!(tree.len(), 1);
assert_eq!(id.index(), 0);
assert!(tree.get(id).is_some());
}
#[test]
fn test_parent_child() {
let mut tree = AreaTree::new();
let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let block = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
tree.append_child(page, block)
.expect("test: should succeed");
assert_eq!(
tree.get(block).expect("test: should succeed").parent,
Some(page)
);
assert_eq!(
tree.get(page).expect("test: should succeed").first_child,
Some(block)
);
}
#[test]
fn test_multiple_children() {
let mut tree = AreaTree::new();
let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let block1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
let block2 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
let block3 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
tree.append_child(page, block1)
.expect("test: should succeed");
tree.append_child(page, block2)
.expect("test: should succeed");
tree.append_child(page, block3)
.expect("test: should succeed");
let children = tree.children(page);
assert_eq!(children.len(), 3);
assert_eq!(children[0], block1);
assert_eq!(children[1], block2);
assert_eq!(children[2], block3);
}
#[test]
fn test_find_page_ancestor() {
let mut tree = AreaTree::new();
let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let region = tree.add_area(Area::new(AreaType::Region, make_rect(160.0, 247.0)));
let block = tree.add_area(Area::new(AreaType::Block, make_rect(160.0, 20.0)));
tree.append_child(page, region)
.expect("test: should succeed");
tree.append_child(region, block)
.expect("test: should succeed");
assert_eq!(tree.find_page_ancestor(block), Some(page));
assert_eq!(tree.find_page_ancestor(region), Some(page));
assert_eq!(tree.find_page_ancestor(page), Some(page));
}
#[test]
fn test_footnote_tracking() {
let mut tree = AreaTree::new();
let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let footnote = tree.add_area(Area::new(AreaType::Footnote, make_rect(160.0, 12.0)));
assert!(tree.get_footnotes(page).is_none());
tree.add_footnote(page, footnote);
let footnotes = tree.get_footnotes(page).expect("test: should succeed");
assert_eq!(footnotes.len(), 1);
assert_eq!(footnotes[0], footnote);
let height = tree.footnote_height(page);
assert!(height > fop_types::Length::ZERO);
}
#[test]
fn test_area_tree_serialize_empty() {
let tree = AreaTree::new();
let s = tree.serialize();
let _ = s;
}
#[test]
fn test_area_tree_serialize_single_page() {
let mut tree = AreaTree::new();
let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let _ = page;
let s = tree.serialize();
assert!(
!s.is_empty(),
"Serialize should produce non-empty output for a page"
);
assert!(
s.contains("Page"),
"Serialize should mention AreaType::Page"
);
}
#[test]
fn test_area_tree_serialize_nested_areas() {
let mut tree = AreaTree::new();
let page = tree.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let region = tree.add_area(Area::new(AreaType::Region, make_rect(160.0, 247.0)));
let block = tree.add_area(Area::new(AreaType::Block, make_rect(160.0, 20.0)));
tree.append_child(page, region)
.expect("test: should succeed");
tree.append_child(region, block)
.expect("test: should succeed");
let s = tree.serialize();
assert!(s.contains("Page"), "Should contain Page");
assert!(s.contains("Region"), "Should contain Region");
assert!(s.contains("Block"), "Should contain Block");
}
#[test]
fn test_area_tree_diff_two_empty_trees() {
let tree1 = AreaTree::new();
let tree2 = AreaTree::new();
let diffs = tree1.diff(&tree2);
assert!(
diffs.is_empty(),
"Two empty trees should have no differences"
);
}
#[test]
fn test_area_tree_diff_self_has_root_other_does_not() {
let mut tree1 = AreaTree::new();
let tree2 = AreaTree::new();
tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let diffs = tree1.diff(&tree2);
assert!(
!diffs.is_empty(),
"Should detect that self has root but other does not"
);
assert!(
diffs[0].contains("no root"),
"Diff message should mention missing root"
);
}
#[test]
fn test_area_tree_diff_other_has_root_self_does_not() {
let tree1 = AreaTree::new();
let mut tree2 = AreaTree::new();
tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let diffs = tree1.diff(&tree2);
assert!(
!diffs.is_empty(),
"Should detect that other has root but self does not"
);
}
#[test]
fn test_area_tree_diff_identical_single_page() {
let mut tree1 = AreaTree::new();
let mut tree2 = AreaTree::new();
tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let diffs = tree1.diff(&tree2);
assert!(
diffs.is_empty(),
"Identical single-page trees should have no diffs"
);
}
#[test]
fn test_area_tree_diff_different_root_type() {
let mut tree1 = AreaTree::new();
let mut tree2 = AreaTree::new();
tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
tree2.add_area(Area::new(AreaType::Region, make_rect(210.0, 297.0)));
let diffs = tree1.diff(&tree2);
assert!(
!diffs.is_empty(),
"Different area types should produce a diff"
);
assert!(
diffs
.iter()
.any(|d| d.contains("type") || d.contains("Area")),
"Diff should describe type mismatch"
);
}
#[test]
fn test_area_tree_diff_different_child_count() {
let mut tree1 = AreaTree::new();
let mut tree2 = AreaTree::new();
let page1 = tree1.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let child1 = tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 20.0)));
tree1
.append_child(page1, child1)
.expect("test: should succeed");
let _page2 = tree2.add_area(Area::new(AreaType::Page, make_rect(210.0, 297.0)));
let diffs = tree1.diff(&tree2);
assert!(
!diffs.is_empty(),
"Different child counts should produce a diff"
);
}
#[test]
fn test_area_tree_diff_identical_with_text_content() {
let mut tree1 = AreaTree::new();
let mut tree2 = AreaTree::new();
let mut area1 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
area1.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
let mut area2 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
area2.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
tree1.add_area(area1);
tree2.add_area(area2);
let diffs = tree1.diff(&tree2);
assert!(
diffs.is_empty(),
"Trees with identical text content should have no diffs"
);
}
#[test]
fn test_area_tree_diff_different_text_content() {
let mut tree1 = AreaTree::new();
let mut tree2 = AreaTree::new();
let mut area1 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
area1.content = Some(crate::area::types::AreaContent::Text("Hello".to_string()));
let mut area2 = Area::new(AreaType::Text, make_rect(100.0, 12.0));
area2.content = Some(crate::area::types::AreaContent::Text("World".to_string()));
tree1.add_area(area1);
tree2.add_area(area2);
let diffs = tree1.diff(&tree2);
assert!(
!diffs.is_empty(),
"Different text content should produce a diff"
);
assert!(
diffs.iter().any(|d| d.contains("Text")),
"Diff should mention text content"
);
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
use crate::area::AreaType;
use fop_types::{Length, Point, Rect, Size};
fn make_rect(w: f64, h: f64) -> Rect {
Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(w), Length::from_pt(h)),
)
}
#[test]
fn test_area_id_index() {
let mut tree = AreaTree::new();
let a0 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
let a1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
assert_eq!(a0.index(), 0);
assert_eq!(a1.index(), 1);
}
#[test]
fn test_area_id_from_index() {
let id = AreaId::from_index(42);
assert_eq!(id.index(), 42);
}
#[test]
fn test_area_id_display() {
let id = AreaId::from_index(7);
assert_eq!(format!("{}", id), "Area(7)");
}
#[test]
fn test_area_node_has_children_initially_false() {
let node = AreaNode::new(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
assert!(!node.has_children());
}
#[test]
fn test_area_node_has_children_after_append() {
let mut tree = AreaTree::new();
let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let child = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
tree.append_child(parent, child)
.expect("test: should succeed");
let parent_node = tree.get(parent).expect("test: should succeed");
assert!(parent_node.has_children());
}
#[test]
fn test_area_tree_with_capacity() {
let tree = AreaTree::with_capacity(100);
assert!(tree.is_empty());
}
#[test]
fn test_area_tree_len_tracks_additions() {
let mut tree = AreaTree::new();
assert_eq!(tree.len(), 0);
tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
assert_eq!(tree.len(), 1);
tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
assert_eq!(tree.len(), 2);
}
#[test]
fn test_get_mut_can_modify_area() {
let mut tree = AreaTree::new();
let id = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
{
let node = tree.get_mut(id).expect("test: should succeed");
node.area.geometry.width = Length::from_pt(200.0);
}
let node = tree.get(id).expect("test: should succeed");
assert_eq!(node.area.geometry.width, Length::from_pt(200.0));
}
#[test]
fn test_get_nonexistent_returns_none() {
let tree = AreaTree::new();
let bogus_id = AreaId::from_index(999);
assert!(tree.get(bogus_id).is_none());
}
#[test]
fn test_children_ordering_preserved() {
let mut tree = AreaTree::new();
let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let c1 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 20.0)));
let c2 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 30.0)));
let c3 = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 40.0)));
tree.append_child(parent, c1).expect("test: should succeed");
tree.append_child(parent, c2).expect("test: should succeed");
tree.append_child(parent, c3).expect("test: should succeed");
let children = tree.children(parent);
assert_eq!(children.len(), 3);
assert_eq!(children[0], c1);
assert_eq!(children[1], c2);
assert_eq!(children[2], c3);
}
#[test]
fn test_children_returns_empty_for_leaf() {
let mut tree = AreaTree::new();
let leaf = tree.add_area(Area::new(AreaType::Text, make_rect(100.0, 12.0)));
let children = tree.children(leaf);
assert!(children.is_empty());
}
#[test]
fn test_iter_over_all_areas() {
let mut tree = AreaTree::new();
tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
tree.add_area(Area::new(AreaType::Block, make_rect(200.0, 50.0)));
tree.add_area(Area::new(AreaType::Block, make_rect(300.0, 50.0)));
let count = tree.iter().count();
assert_eq!(count, 3);
}
#[test]
fn test_root_returns_first_area() {
let mut tree = AreaTree::new();
let first_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
let (root_id, root_node) = tree.root().expect("test: should succeed");
assert_eq!(root_id, first_id);
assert_eq!(root_node.area.area_type, AreaType::Page);
}
#[test]
fn test_root_empty_tree_returns_none() {
let tree = AreaTree::new();
assert!(tree.root().is_none());
}
#[test]
fn test_footnote_height_zero_with_no_footnotes() {
let mut tree = AreaTree::new();
let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let height = tree.footnote_height(page_id);
assert_eq!(height, Length::ZERO);
}
#[test]
fn test_footnote_tracking_multiple_footnotes() {
let mut tree = AreaTree::new();
let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let fn1 = tree.add_area(Area::new(AreaType::Footnote, make_rect(400.0, 30.0)));
let fn2 = tree.add_area(Area::new(AreaType::Footnote, make_rect(400.0, 20.0)));
tree.add_footnote(page_id, fn1);
tree.add_footnote(page_id, fn2);
let footnotes = tree.get_footnotes(page_id).expect("test: should succeed");
assert_eq!(footnotes.len(), 2);
let total_height = tree.footnote_height(page_id);
assert_eq!(total_height, Length::from_pt(57.0));
}
#[test]
fn test_get_footnotes_returns_none_for_page_without_footnotes() {
let mut tree = AreaTree::new();
let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
assert!(tree.get_footnotes(page_id).is_none());
}
#[test]
fn test_find_page_ancestor_direct_child() {
let mut tree = AreaTree::new();
let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let block_id = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
tree.append_child(page_id, block_id)
.expect("test: should succeed");
let ancestor = tree.find_page_ancestor(block_id);
assert_eq!(ancestor, Some(page_id));
}
#[test]
fn test_find_page_ancestor_deep_nesting() {
let mut tree = AreaTree::new();
let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let region_id = tree.add_area(Area::new(AreaType::Region, make_rect(450.0, 700.0)));
let block_id = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
let text_id = tree.add_area(Area::new(AreaType::Text, make_rect(300.0, 12.0)));
tree.append_child(page_id, region_id)
.expect("test: should succeed");
tree.append_child(region_id, block_id)
.expect("test: should succeed");
tree.append_child(block_id, text_id)
.expect("test: should succeed");
let ancestor = tree.find_page_ancestor(text_id);
assert_eq!(ancestor, Some(page_id));
}
#[test]
fn test_find_page_ancestor_returns_none_for_page_itself() {
let mut tree = AreaTree::new();
let page_id = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let _result = tree.find_page_ancestor(page_id);
}
#[test]
fn test_serialize_produces_non_empty_string_for_non_empty_tree() {
let mut tree = AreaTree::new();
tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let serialized = tree.serialize();
assert!(!serialized.is_empty());
}
#[test]
fn test_serialize_empty_tree_is_empty_string() {
let tree = AreaTree::new();
let serialized = tree.serialize();
assert!(serialized.is_empty());
}
#[test]
fn test_serialize_contains_area_type() {
let mut tree = AreaTree::new();
tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let serialized = tree.serialize();
assert!(
serialized.contains("Page"),
"Serialized tree should mention 'Page'"
);
}
#[test]
fn test_diff_same_single_area_no_diffs() {
let mut tree1 = AreaTree::new();
let mut tree2 = AreaTree::new();
tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
tree2.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
let diffs = tree1.diff(&tree2);
assert!(
diffs.is_empty(),
"Identical trees should have no diffs, got: {:?}",
diffs
);
}
#[test]
fn test_diff_different_area_types_produces_diff() {
let mut tree1 = AreaTree::new();
let mut tree2 = AreaTree::new();
tree1.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
tree2.add_area(Area::new(AreaType::Line, make_rect(100.0, 50.0)));
let diffs = tree1.diff(&tree2);
assert!(
!diffs.is_empty(),
"Different area types should produce a diff"
);
}
#[test]
fn test_diff_is_reflexive() {
let mut tree = AreaTree::new();
let parent = tree.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let child = tree.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
tree.append_child(parent, child)
.expect("test: should succeed");
let mut tree2 = AreaTree::new();
let parent2 = tree2.add_area(Area::new(AreaType::Page, make_rect(595.0, 842.0)));
let child2 = tree2.add_area(Area::new(AreaType::Block, make_rect(400.0, 50.0)));
tree2
.append_child(parent2, child2)
.expect("test: should succeed");
let diffs = tree.diff(&tree2);
assert!(diffs.is_empty(), "Identical trees should have no diffs");
}
#[test]
fn test_append_child_to_nonexistent_parent_returns_error() {
let mut tree = AreaTree::new();
let child = tree.add_area(Area::new(AreaType::Block, make_rect(100.0, 50.0)));
let bogus_parent = AreaId::from_index(999);
let result = tree.append_child(bogus_parent, child);
assert!(
result.is_err(),
"Appending to nonexistent parent should fail"
);
}
}