summaryrefslogtreecommitdiffstats
path: root/svgbob/src/buffer/cell_buffer/span.rs
diff options
context:
space:
mode:
Diffstat (limited to 'svgbob/src/buffer/cell_buffer/span.rs')
-rw-r--r--svgbob/src/buffer/cell_buffer/span.rs591
1 files changed, 591 insertions, 0 deletions
diff --git a/svgbob/src/buffer/cell_buffer/span.rs b/svgbob/src/buffer/cell_buffer/span.rs
new file mode 100644
index 0000000..e9dd29e
--- /dev/null
+++ b/svgbob/src/buffer/cell_buffer/span.rs
@@ -0,0 +1,591 @@
+use crate::{
+ buffer::{
+ cell_buffer::Contacts,
+ fragment_buffer::fragment::{marker_line, Line, Polygon, PolygonTag},
+ CellGrid, FragmentBuffer, Property, PropertyBuffer, StringBuffer,
+ },
+ fragment,
+ fragment::{Circle, Marker},
+ map::{circle_map, UNICODE_FRAGMENTS},
+ Cell, Fragment,
+};
+use itertools::Itertools;
+use std::{
+ fmt,
+ ops::{Deref, DerefMut},
+};
+
+/// A describes where a char came from relative to the source ascii text
+/// The primary purpose of span is to group adjacent cell together
+#[derive(Clone)]
+pub struct Span(Vec<(Cell, char)>);
+
+impl Deref for Span {
+ type Target = Vec<(Cell, char)>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for Span {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl Span {
+ pub(super) fn new(cell: Cell, ch: char) -> Self {
+ Span(vec![(cell, ch)])
+ }
+
+ pub(super) fn is_adjacent(&self, cell: &Cell) -> bool {
+ self.iter().rev().any(|(ex_cell, _)| ex_cell.is_adjacent(cell))
+ }
+
+ /// if any cell of this span is adjacent to any cell of the other
+ /// Use .rev() to check the last cell of this Span agains the first cell of the other Span
+ /// They have a high change of matching faster
+ pub(super) fn can_merge(&self, other: &Self) -> bool {
+ self.iter()
+ .rev()
+ .any(|(cell, _)| other.iter().any(|(other_cell, _)| cell.is_adjacent(other_cell)))
+ }
+
+ pub(super) fn merge(&mut self, other: &Self) {
+ self.extend_from_slice(&*other)
+ }
+
+ /// returns the top_left most cell which aligns the top most and the left most cell.
+ pub(crate) fn bounds(&self) -> Option<(Cell, Cell)> {
+ if let Some((min_y, max_y)) = self.iter().map(|(cell, _)| cell.y).minmax().into_option() {
+ if let Some((min_x, max_x)) = self.iter().map(|(cell, _)| cell.x).minmax().into_option()
+ {
+ Some((Cell::new(min_x, min_y), Cell::new(max_x, max_y)))
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+
+ /// shift the cells relative to the top_left most bound
+ pub(crate) fn localize(self) -> Self {
+ if let Some((tl, _br)) = self.bounds() {
+ let mut new_self = Span(vec![]);
+ for (cell, ch) in self.iter() {
+ let local_cell = tl.localize_cell(*cell);
+ new_self.push((local_cell, *ch));
+ }
+ new_self
+ } else {
+ self
+ }
+ }
+
+ /// Grouping cell by adjacents are not enough
+ ///
+ /// grouping them together when they are actually connected
+ /// is the most approprivate way of grouping
+ /// Span just provides an optimization of the number
+ /// of elements to be checked.
+ /// Only elements on the same span are checked to see if they
+ /// belong on the same group
+ ///
+ pub(crate) fn get_contacts(self) -> Vec<Contacts> {
+ let localize_self = self.localize();
+ let fb: FragmentBuffer = (&localize_self).into();
+
+ let mut groups: Vec<Contacts> = vec![];
+ let merged_fragments = fb.merge_fragments();
+ for fragment in merged_fragments.into_iter() {
+ let belongs_to_group = groups.iter_mut().rev().any(|group| {
+ if group.is_contacting_frag(&fragment) {
+ group.as_mut().push(fragment.clone());
+ true
+ } else {
+ false
+ }
+ });
+ if !belongs_to_group {
+ groups.push(Contacts::new(fragment))
+ }
+ }
+ Self::group_recursive(groups)
+ }
+
+ fn group_recursive(groups: Vec<Contacts>) -> Vec<Contacts> {
+ let original_len = groups.len();
+ let grouped = Self::second_pass_groupable(groups);
+ // continue calling group recursive until the original len
+ // has not decreased
+ if grouped.len() < original_len { Self::group_recursive(grouped) } else { grouped }
+ }
+
+ fn second_pass_groupable(groups: Vec<Contacts>) -> Vec<Contacts> {
+ let mut new_groups: Vec<Contacts> = vec![];
+ for group in groups.into_iter() {
+ let is_grouped = new_groups.iter_mut().any(|new_group| {
+ if new_group.is_contacting(&group) {
+ new_group.as_mut().extend_from_slice(group.as_ref());
+ true
+ } else {
+ false
+ }
+ });
+ if !is_grouped {
+ new_groups.push(group);
+ }
+ }
+ new_groups
+ }
+
+ /// First phase of endorsing to shapes, in this case, rects and rounded_rects
+ ///
+ /// This function is calling on endorse methods that is applicable
+ /// to fragments that are touching, to be promoted to a shape.
+ /// These includes: rect, roundedrect,
+ fn endorse_rects(groups: Vec<Contacts>) -> (Vec<Fragment>, Vec<Contacts>) {
+ let mut fragments = vec![];
+ let mut un_endorsed_rect: Vec<Contacts> = vec![];
+ for group in groups {
+ if let Some(fragment) = group.endorse_rects() {
+ fragments.push(fragment);
+ } else {
+ un_endorsed_rect.push(group);
+ }
+ }
+ (fragments, un_endorsed_rect)
+ }
+
+ /// [X](Done) TODO: this is trying to match all the members of this contact
+ /// to each specific circle, this can be improve by checking
+ /// subsets of the contacts to match the circles.
+ ///
+ /// This function is calling on endorse algorithmn on fragments that
+ /// are neighbors, but not necessarily touching to be promoted to a shape.
+ /// These includes: circle, arc, and line with arrow heads.
+ fn endorse_circles_and_arcs(groups: Vec<Contacts>) -> (Vec<Fragment>, Vec<Contacts>) {
+ let mut fragments = vec![];
+ let mut un_endorsed_circles: Vec<Contacts> = vec![];
+ if let Some((circle, unmatched)) = circle_map::endorse_circle(&groups) {
+ fragments.push(circle.clone().into());
+ for um in unmatched {
+ un_endorsed_circles.push(groups[um].clone());
+ }
+ } else if let Some(arc) = circle_map::endorse_arc(&groups) {
+ fragments.push(arc.clone().into());
+ } else {
+ un_endorsed_circles.extend(groups)
+ }
+ (fragments, un_endorsed_circles)
+ }
+
+ /// convert this span into fragments applying endorsement
+ /// of group into fragments
+ ///
+ /// returns (fragments, contacts) -
+ /// The first element of the tuple: `fragments` are the resulting fragment after
+ /// the endorsement such as rect, rounded rect from lines and arcs.
+ ///
+ /// The second element of the tuple: `contacts` are fragments that are touching together
+ /// but can not form a fragment shape. These will be grouped in the svg nodes
+ /// to keep them go together, when dragged (editing)
+ pub(crate) fn endorse(self) -> (Vec<Fragment>, Vec<Contacts>) {
+ let (top_left, _) = self.bounds().expect("mut have bounds");
+ let groups: Vec<Contacts> = self.get_contacts();
+ // 1st phase, successful_endorses fragments, unendorsed one)
+ let (mut fragments, mut un_endorsed) = Self::endorse_rects(groups);
+ // 2nd phase, try to endorse to circles and arcs from the rejects of the 1st phase
+ let (mut circle_fragments, mut un_endorsed) = Self::endorse_circles_and_arcs(un_endorsed);
+
+ fragments.extend(circle_fragments);
+ (
+ fragments.iter().map(|frag| frag.absolute_position(top_left)).collect(),
+ un_endorsed.iter().map(|group| group.absolute_position(top_left)).collect(),
+ )
+ }
+
+ /// create a span of the cells that is inside of the start and end bound cells
+ pub(crate) fn extract(&self, bound1: Cell, bound2: Cell) -> Self {
+ Span(
+ self.iter()
+ .map(|(cell, ch)| (*cell, *ch))
+ .filter(|(cell, _ch)| cell.is_bounded(bound1, bound2))
+ .collect(),
+ )
+ }
+}
+
+/// create a property buffer for all the cells of this span
+impl<'p> Into<PropertyBuffer<'p>> for &Span {
+ fn into(self) -> PropertyBuffer<'p> {
+ let mut pb = PropertyBuffer::new();
+ for (cell, ch) in self.iter() {
+ if let Some(property) = Property::from_char(*ch) {
+ pb.insert(*cell, property);
+ } else {
+ }
+ }
+ pb
+ }
+}
+
+/// First we crate a property buffer based on the cell,char content of this span
+/// and then based on the property, we extract the accurate fragments
+///
+/// If a character has no property, try to see if has equivalent fragments from unicode_map
+/// otherwise add it to the fragment_buffer as a text fragment
+impl Into<FragmentBuffer> for &Span {
+ fn into(self) -> FragmentBuffer {
+ let pb: PropertyBuffer = self.into();
+ let mut fb: FragmentBuffer = (&pb).into();
+ for (cell, ch) in self.iter() {
+ if pb.get(cell).is_none() {
+ if let Some(fragments) = UNICODE_FRAGMENTS.get(ch) {
+ fb.add_fragments_to_cell(*cell, fragments.clone());
+ } else {
+ fb.add_fragment_to_cell(*cell, fragment::cell_text(*ch));
+ }
+ }
+ }
+ fb
+ }
+}
+
+
+impl fmt::Display for Span {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut buffer = StringBuffer::new();
+ if let Some((tl, _br)) = self.bounds() {
+ for (cell, ch) in self.iter() {
+ if *ch != '\0' && !ch.is_whitespace() {
+ let local = tl.localize_cell(*cell);
+ buffer.add_char(local.x, local.y, *ch);
+ }
+ }
+ }
+ write!(f, "{}", buffer.to_string())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ buffer::{
+ fragment::{Circle, Rect},
+ CellBuffer,
+ },
+ Point,
+ };
+
+
+
+ #[test]
+ fn test_bounds() {
+ let art = r#"
+.--------.
+|________|
+ "#;
+ let buffer = CellBuffer::from(art);
+ let adjacents = buffer.group_adjacents();
+ assert_eq!(1, adjacents.len());
+ let (min, max) = adjacents[0].bounds().unwrap();
+ assert_eq!(min, Cell::new(0, 1));
+ assert_eq!(max, Cell::new(9, 2));
+ }
+
+ #[test]
+ fn test_localize() {
+ let art = r#"
+
+
+
+ .--------.
+ |________|
+ "#;
+ let buffer = CellBuffer::from(art);
+ let adjacents = buffer.group_adjacents();
+ assert_eq!(1, adjacents.len());
+ let (min, max) = adjacents[0].bounds().unwrap();
+ assert_eq!(min, Cell::new(8, 4));
+ assert_eq!(max, Cell::new(17, 5));
+
+ let localize = adjacents[0].clone().localize();
+ let (min, max) = localize.bounds().unwrap();
+ assert_eq!(min, Cell::new(0, 0));
+ assert_eq!(max, Cell::new(9, 1));
+ }
+
+ #[test]
+ fn test_1span_1group() {
+ let art = r#"
++--------+
+| |
++--------+
+ "#;
+ let buffer = CellBuffer::from(art);
+ println!("buffer: {}", buffer);
+ let mut adjacents = buffer.group_adjacents();
+ println!("There are {} adjacents", adjacents.len());
+ assert_eq!(1, adjacents.len());
+ let span = adjacents.remove(0);
+ let (top_left, _) = span.bounds().unwrap();
+ assert_eq!(top_left, Cell::new(0, 1));
+ let groups: Vec<Contacts> = span.get_contacts();
+ for (i, group) in groups.iter().enumerate() {
+ println!("group {} is: \n{}", i, group);
+ }
+ assert_eq!(groups.len(), 1);
+ let rect = groups[0].endorse_rects().unwrap();
+ let expected =
+ Fragment::Rect(Rect::new(Point::new(0.5, 1.0), Point::new(9.5, 5.0), false, false));
+ assert_eq!(rect, expected);
+ }
+
+ #[test]
+ /// 2 spans and 1 group for each span
+ fn test_2spans_1group_for_each_span() {
+ let art = r#"
++--------+
+| |
++--------+
+
++--------+
+| |
++--------+
+ "#;
+ let buffer = CellBuffer::from(art);
+ println!("buffer: {}", buffer);
+ let mut spans = buffer.group_adjacents();
+ println!("There are {} adjacents", spans.len());
+ assert_eq!(2, spans.len());
+ let span2 = spans.remove(1);
+ let span1 = spans.remove(0);
+
+ let (bound1, _) = span1.bounds().unwrap();
+ let (bound2, _) = span2.bounds().unwrap();
+ assert_eq!(bound1, Cell::new(0, 1));
+ assert_eq!(bound2, Cell::new(0, 5));
+ let groups1: Vec<Contacts> = span1.get_contacts();
+ let groups2: Vec<Contacts> = span2.get_contacts();
+ assert_eq!(groups1.len(), 1);
+ assert_eq!(groups2.len(), 1);
+
+ let rect1 = groups1[0].endorse_rects().unwrap();
+ let rect2 = groups2[0].endorse_rects().unwrap();
+ assert_eq!(
+ rect1,
+ Fragment::Rect(Rect::new(Point::new(0.5, 1.0), Point::new(9.5, 5.0), false, false))
+ );
+ assert_eq!(
+ rect2,
+ Fragment::Rect(Rect::new(Point::new(0.5, 1.0), Point::new(9.5, 5.0), false, false))
+ );
+ }
+
+ #[test]
+ /// 2 spans and 1 group for each span
+ fn test_1spans_2group_for_each_span() {
+ let art = r#"
+.--------.
+| |
+'--------'
+.--------.
+| |
+'--------'
+ "#;
+ let buffer = CellBuffer::from(art);
+ println!("buffer: {}", buffer);
+ let mut spans = buffer.group_adjacents();
+ println!("There are {} adjacents", spans.len());
+ assert_eq!(1, spans.len());
+ let span1 = spans.remove(0);
+ let (bound1, _) = span1.bounds().unwrap();
+ assert_eq!(bound1, Cell::new(0, 1));
+ let groups: Vec<Contacts> = span1.get_contacts();
+ assert_eq!(groups.len(), 2);
+
+ let rect1 = groups[0].endorse_rects().unwrap();
+ let rect2 = groups[1].endorse_rects().unwrap();
+ assert_eq!(
+ rect1,
+ Fragment::Rect(Rect::rounded_new(
+ Point::new(0.5, 1.0),
+ Point::new(9.5, 5.0),
+ false,
+ 0.5,
+ false,
+ ))
+ );
+ assert_eq!(
+ rect2,
+ Fragment::Rect(Rect::rounded_new(
+ Point::new(0.5, 7.0),
+ Point::new(9.5, 11.0),
+ false,
+ 0.5,
+ false
+ ))
+ );
+ }
+
+ #[test]
+ fn test_endorse_circle() {
+ let art = r#"
+ .--.
+ ( )
+ `--'
+ "#;
+ let buffer = CellBuffer::from(art);
+ println!("buffer: {}", buffer);
+ let mut adjacents = buffer.group_adjacents();
+ println!("There are {} adjacents", adjacents.len());
+ assert_eq!(1, adjacents.len());
+ let span = adjacents.remove(0);
+ let (top_left, _) = span.bounds().unwrap();
+ assert_eq!(top_left, Cell::new(8, 1));
+ let (mut fragments, _groups) = span.endorse();
+ for (i, frag) in fragments.iter().enumerate() {
+ println!("frag {}:\n{}", i, frag);
+ }
+ assert_eq!(fragments.len(), 1);
+ let circle = fragments.remove(0);
+ assert_eq!(circle, Fragment::Circle(Circle::new(Point::new(11.0, 5.0), 2.5, false)));
+ }
+
+ #[test]
+ fn test_endorse_circle_with_rect() {
+ let art = r#"
+ .--.
+ ( )
+ `--'
+ +---------+
+ | |
+ +---------+
+
+ "#;
+ let buffer = CellBuffer::from(art);
+ println!("buffer: {}", buffer);
+ let mut adjacents = buffer.group_adjacents();
+ println!("There are {} adjacents", adjacents.len());
+ assert_eq!(1, adjacents.len());
+ let span1 = adjacents.remove(0);
+ let (top_left1, _) = span1.bounds().unwrap();
+ assert_eq!(top_left1, Cell::new(8, 1));
+ let (mut fragments, _groups) = span1.endorse();
+ assert_eq!(fragments.len(), 2);
+
+ let rect = fragments.remove(0);
+ assert!(rect.is_rect());
+ assert_eq!(
+ rect,
+ Fragment::Rect(Rect::new(Point::new(9.5, 9.0), Point::new(19.5, 13.0), false, false))
+ );
+
+ let circle = fragments.remove(0);
+ assert!(circle.is_circle());
+ assert_eq!(circle, Fragment::Circle(Circle::new(Point::new(11.0, 5.0), 2.5, false)));
+ }
+
+ #[test]
+ fn test_endorse_with_big_circle() {
+ let art = r#"
+
+
+ _.-''''''-._
+ ,' `.
+ / \
+ . .
+ | |
+ | |
+ | |
+ \ /
+ `._ _.'
+ '-......-'
+
+ "#;
+ let buffer = CellBuffer::from(art);
+ println!("buffer: {}", buffer);
+ let mut adjacents = buffer.group_adjacents();
+ println!("There are {} adjacents", adjacents.len());
+ assert_eq!(1, adjacents.len());
+ let span1 = adjacents.remove(0);
+ let (top_left1, _) = span1.bounds().unwrap();
+ assert_eq!(top_left1, Cell::new(12, 3));
+ let (mut fragments, _groups) = span1.endorse();
+ assert_eq!(fragments.len(), 1);
+
+
+ let circle = fragments.remove(0);
+ assert!(circle.is_circle());
+ assert_eq!(circle, Fragment::Circle(Circle::new(Point::new(22.0, 17.0), 9.5, false)));
+ }
+
+ #[test]
+ fn test_endorse_with_big_circle_extra_match() {
+ let art = r#"
+
+
+ _.-''''''-._
+ ,' `.
+ / \
+ . .
+ | |
+ |------------------|
+ | |
+ \ /
+ `._ _.'
+ '-......-'
+
+ "#;
+ let buffer = CellBuffer::from(art);
+ println!("buffer: {}", buffer);
+ let mut adjacents = buffer.group_adjacents();
+ println!("There are {} adjacents", adjacents.len());
+ assert_eq!(1, adjacents.len());
+ let span1 = adjacents.remove(0);
+ let (top_left1, _) = span1.bounds().unwrap();
+ assert_eq!(top_left1, Cell::new(12, 3));
+ let (fragments, groups) = span1.endorse();
+ assert_eq!(fragments.len(), 1);
+ assert_eq!(groups.len(), 1);
+ for (i, fragment) in groups.iter().enumerate() {
+ println!("fragment {}: \n{}", i, fragment);
+ }
+ }
+
+ #[test]
+ fn test_absolute_positions() {
+ let art = r#"
+ This is some sort of a paragraph
+ with long words such as supercalifragilisticexpialiducios
+ and pneumonoultramicrospocisilicovulcanoconiosis
+
+ Loren ipsum is a second paragrapah.
+ This is in one span since all the letters
+ are adjacent since they are right next to
+ each other taking account the top and bottom
+ neighbor cell
+ "#;
+ let cell_buffer = CellBuffer::from(art);
+ let mut spans: Vec<Span> = cell_buffer.group_adjacents();
+ assert_eq!(spans.len(), 2);
+ let span1 = spans.remove(1);
+ let span0 = spans.remove(0);
+ let (top_left0, _br) = span0.bounds().unwrap();
+ assert_eq!(top_left0, Cell::new(8, 1));
+ let (top_left1, _br) = span1.bounds().unwrap();
+ assert_eq!(top_left1, Cell::new(8, 5));
+
+ let groups1 = span1.get_contacts();
+ let since = groups1[11].as_ref()[0].as_cell_text().unwrap();
+ assert_eq!(since.content, "since");
+ assert_eq!(since.start, Cell::new(20, 1));
+ let abs_since = since.absolute_position(top_left1);
+ assert_eq!(abs_since.content, "since");
+ assert_eq!(abs_since.start, Cell::new(28, 6));
+ }
+}