diff options
Diffstat (limited to 'packages/svgbob/src/buffer/fragment_buffer/fragment_tree.rs')
-rw-r--r-- | packages/svgbob/src/buffer/fragment_buffer/fragment_tree.rs | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/packages/svgbob/src/buffer/fragment_buffer/fragment_tree.rs b/packages/svgbob/src/buffer/fragment_buffer/fragment_tree.rs new file mode 100644 index 0000000..714beba --- /dev/null +++ b/packages/svgbob/src/buffer/fragment_buffer/fragment_tree.rs @@ -0,0 +1,273 @@ +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<String>, + enclosing: Vec<FragmentTree>, +} + +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<Fragment>) -> Vec<Self> { + let fragment_trees: Vec<Self> = fragments + .into_iter() + .map(|frag| FragmentTree::new(frag)) + .collect(); + Self::enclose_recursive(fragment_trees) + } + + pub(crate) fn enclose_recursive(fragment_trees: Vec<Self>) -> Vec<Self> { + 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<Self>) -> Vec<Self> { + let mut new_trees: Vec<Self> = 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<MSG>(self) -> Vec<Node<MSG>> { + let mut nodes = vec![]; + let mut fragment_node: Node<MSG> = 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<MSG>( + fragments: Vec<Fragment>, + ) -> Vec<Node<MSG>> { + let fragment_trees: Vec<FragmentTree> = + 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![], + }, + ] + ); + } +} |