use crate::Fragment;
use sauron::{html::attributes::*, Node};
/// A tree of fragments where a fragment can contain other fragments
/// when those fragments are inside in this fragment
/// The main purpose of this struct is for tagging fragments
/// such as rect and circles to have a CellText fragment inside that are special
/// text commands such as css classes, which the user can style the containing fragment
#[derive(Debug, Clone, PartialEq)]
pub struct FragmentTree {
fragment: Fragment,
css_tag: Vec,
enclosing: Vec,
}
impl FragmentTree {
pub(crate) fn new(fragment: Fragment) -> Self {
FragmentTree {
fragment,
css_tag: vec![],
enclosing: vec![],
}
}
fn can_fit(&self, other: &Self) -> bool {
self.fragment.can_fit(&other.fragment)
}
/// check if this fragment can fit to this fragment tree.
/// this also check if any of the children of this tree can fit
/// the fragment
fn enclose(&mut self, other: &Self) -> bool {
if self.can_fit(other) {
self.enclosing.push(other.clone());
return true;
} else {
for child in &mut self.enclosing {
if child.enclose(other) {
return true;
}
}
false
}
}
/// Try to put the other fragment somwhere in the tree, but traversing the depth first.
/// This is needed for accurately tagging which shapes by putting the right cell_text into
/// it's direct parent instead of just checking whether the text is bounded by some shapes.
fn enclose_deep_first(&mut self, other: &Self) -> bool {
for child in &mut self.enclosing {
if child.enclose_deep_first(other) {
return true;
}
}
if self.can_fit(other) {
let css_tags = other.fragment.as_css_tag();
if !css_tags.is_empty() {
self.css_tag.extend(css_tags);
} else {
self.enclosing.push(other.clone());
}
true
} else {
false
}
}
pub(crate) fn enclose_fragments(fragments: Vec) -> Vec {
let fragment_trees: Vec = fragments
.into_iter()
.map(|frag| FragmentTree::new(frag))
.collect();
Self::enclose_recursive(fragment_trees)
}
pub(crate) fn enclose_recursive(fragment_trees: Vec) -> Vec {
let original_len = fragment_trees.len();
let merged = Self::second_pass_enclose(fragment_trees);
if merged.len() < original_len {
Self::enclose_recursive(merged)
} else {
merged
}
}
/// make all the fragments a fragment tree and try to fit each other
fn second_pass_enclose(fragment_trees: Vec) -> Vec {
let mut new_trees: Vec = vec![];
for frag_tree in fragment_trees {
let is_enclosed = new_trees
.iter_mut()
.rev()
.any(|new_tree| new_tree.enclose_deep_first(&frag_tree));
if !is_enclosed {
new_trees.push(frag_tree);
}
}
new_trees
}
/// convert back into fragments
fn into_nodes(self) -> Vec> {
let mut nodes = vec![];
let mut fragment_node: Node = self.fragment.into();
let _css_tag_len = self.css_tag.len();
fragment_node =
fragment_node.merge_attributes(vec![classes(self.css_tag)]);
nodes.push(fragment_node);
for child in self.enclosing {
nodes.extend(child.into_nodes())
}
nodes
}
/// convert fragments to node, where cell_text and text may become
/// css class of the contain fragment
pub(crate) fn fragments_to_node(
fragments: Vec,
) -> Vec> {
let fragment_trees: Vec =
Self::enclose_fragments(fragments);
fragment_trees
.into_iter()
.flat_map(|frag_tree| frag_tree.into_nodes())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
buffer::Cell,
fragment::{rect, CellText},
Point,
};
#[test]
fn test_enclose() {
let mut rect1 = FragmentTree::new(rect(
Point::new(0.0, 0.0),
Point::new(10.0, 10.0),
false,
false,
));
let rect2 = FragmentTree::new(rect(
Point::new(1.0, 1.0),
Point::new(9.0, 9.0),
false,
false,
));
let text1 = FragmentTree::new(Fragment::CellText(CellText::new(
Cell::new(2, 2),
"{doc}".to_string(),
)));
let text2 = FragmentTree::new(Fragment::CellText(CellText::new(
Cell::new(2, 2),
"This is a hello world!".to_string(),
)));
assert!(rect1.enclose(&rect2));
assert!(rect1.enclose(&text1));
assert!(!rect1.enclose(&text2));
dbg!(rect1);
}
#[test]
fn test_enclose_recursive() {
let rect1 =
rect(Point::new(0.0, 0.0), Point::new(10.0, 10.0), false, false);
let rect2 =
rect(Point::new(1.0, 1.0), Point::new(9.0, 9.0), false, false);
let text1 = Fragment::CellText(CellText::new(
Cell::new(2, 2),
"{doc}".to_string(),
));
let text2 = Fragment::CellText(CellText::new(
Cell::new(2, 2),
"This is a hello world!".to_string(),
));
let fragments = vec![rect1, rect2, text1, text2];
let fragment_trees = FragmentTree::enclose_fragments(fragments);
dbg!(&fragment_trees);
assert_eq!(
fragment_trees,
vec![
FragmentTree {
fragment: rect(
Point::new(0.0, 0.0),
Point::new(10.0, 10.0),
false,
false
),
css_tag: vec![],
enclosing: vec![FragmentTree {
fragment: rect(
Point::new(1.0, 1.0),
Point::new(9.0, 9.0),
false,
false
),
css_tag: vec!["doc".to_string()],
enclosing: vec![],
},],
},
FragmentTree {
fragment: Fragment::CellText(CellText::new(
Cell::new(2, 2),
"This is a hello world!".to_string(),
)),
css_tag: vec![],
enclosing: vec![],
},
]
);
}
#[test]
fn test_enclose_recursive_different_order() {
let rect1 =
rect(Point::new(0.0, 0.0), Point::new(10.0, 10.0), false, false);
let rect2 =
rect(Point::new(1.0, 1.0), Point::new(9.0, 9.0), false, false);
let text1 = Fragment::CellText(CellText::new(
Cell::new(2, 2),
"{doc}".to_string(),
));
let text2 = Fragment::CellText(CellText::new(
Cell::new(2, 2),
"This is a hello world!".to_string(),
));
let fragments = vec![rect1, rect2, text1, text2];
let fragment_trees = FragmentTree::enclose_fragments(fragments);
dbg!(&fragment_trees);
assert_eq!(
fragment_trees,
vec![
FragmentTree {
fragment: rect(
Point::new(0.0, 0.0),
Point::new(10.0, 10.0),
false,
false
),
css_tag: vec![],
enclosing: vec![FragmentTree {
fragment: rect(
Point::new(1.0, 1.0),
Point::new(9.0, 9.0),
false,
false
),
css_tag: vec!["doc".to_string()],
enclosing: vec![],
},],
},
FragmentTree {
fragment: Fragment::CellText(CellText::new(
Cell::new(2, 2),
"This is a hello world!".to_string(),
)),
css_tag: vec![],
enclosing: vec![],
},
]
);
}
}