summaryrefslogtreecommitdiffstats
path: root/packages/svgbob/src/buffer/fragment_buffer/fragment.rs
diff options
context:
space:
mode:
Diffstat (limited to 'packages/svgbob/src/buffer/fragment_buffer/fragment.rs')
-rw-r--r--packages/svgbob/src/buffer/fragment_buffer/fragment.rs951
1 files changed, 951 insertions, 0 deletions
diff --git a/packages/svgbob/src/buffer/fragment_buffer/fragment.rs b/packages/svgbob/src/buffer/fragment_buffer/fragment.rs
new file mode 100644
index 0000000..2080f5f
--- /dev/null
+++ b/packages/svgbob/src/buffer/fragment_buffer/fragment.rs
@@ -0,0 +1,951 @@
+use crate::{buffer::Cell, map::unicode_map::FRAGMENTS_UNICODE, Point};
+pub use crate::{Property, Settings, Signal};
+pub use arc::Arc;
+pub use circle::Circle;
+pub use line::Line;
+pub use marker_line::{Marker, MarkerLine};
+use parry2d::bounding_volume::BoundingVolume;
+use parry2d::query::PointQuery;
+use parry2d::shape::ConvexPolygon;
+use parry2d::{
+ bounding_volume::AABB,
+ math::Isometry,
+ query::intersection_test,
+ shape::{Polyline, Segment, Shape},
+};
+pub use polygon::{Polygon, PolygonTag};
+pub use rect::Rect;
+use sauron::Node;
+use std::{cmp::Ordering, fmt};
+pub use text::{CellText, Text};
+
+mod arc;
+mod circle;
+mod line;
+mod marker_line;
+mod polygon;
+mod rect;
+mod text;
+
+/// ```ignore
+/// 0 1 2 3 4 B C D
+/// 0┌─┬─┬─┬─┐ A┌─┬─┬─┬─┐E
+/// 1├─┼─┼─┼─┤ │ │ │ │ │
+/// 2├─┼─┼─┼─┤ F├─G─H─I─┤J
+/// 3├─┼─┼─┼─┤ │ │ │ │ │
+/// 4├─┼─┼─┼─┤ K├─L─M─N─┤O
+/// 5├─┼─┼─┼─┤ │ │ │ │ │
+/// 6├─┼─┼─┼─┤ P├─Q─R─S─┤T
+/// 7├─┼─┼─┼─┤ │ │ │ │ │
+/// 8└─┴─┴─┴─┘ U└─┴─┴─┴─┘Y
+/// ``` V W X
+
+#[derive(Debug, Clone)]
+pub enum Fragment {
+ Line(Line),
+ MarkerLine(MarkerLine),
+ Circle(Circle),
+ Arc(Arc),
+ Polygon(Polygon),
+ Rect(Rect),
+ // cell base
+ CellText(CellText),
+ // point base
+ Text(Text),
+}
+
+/// get the boundary of a fragment
+/// this is used for sorting the fragments
+/// in a consistent sorted order
+pub trait Bounds {
+ fn bounds(&self) -> (Point, Point);
+
+ fn mins(&self) -> Point {
+ self.bounds().0
+ }
+
+ fn maxs(&self) -> Point {
+ self.bounds().1
+ }
+}
+
+impl Fragment {
+ /// get the character that matches the shape present on this cell
+ pub fn match_unicode(fragments: &Vec<Self>) -> Option<char> {
+ let mut sorted_shapes = fragments.clone();
+ sorted_shapes.sort();
+ //assert!(sorted_shapes.is_sorted());
+ FRAGMENTS_UNICODE.get(&sorted_shapes).map(|c| *c)
+ }
+
+ /// check to see if this fragment is a line and that line
+ /// can completely overlap line a b
+ /// TODO: only checking for solid, also expose API for broken
+ pub(in crate) fn line_overlap(&self, a: Point, b: Point) -> bool {
+ match self {
+ Fragment::Line(line) => line.overlaps(a, b),
+ _ => false,
+ }
+ }
+
+ /// check if any of the fragment end point is touching p
+ pub(in crate) fn has_endpoint(&self, p: Point) -> bool {
+ match self {
+ Fragment::Line(line) => line.has_endpoint(p),
+ Fragment::Arc(arc) => arc.has_endpoint(p),
+ _ => false,
+ }
+ }
+
+ /// check to see if this fragment is an arc
+ /// overlaps from point a to b
+ pub(in crate) fn arcs_to(&self, a: Point, b: Point) -> bool {
+ match self {
+ Fragment::Arc(arc) => arc.arcs_to(a, b),
+ _ => false,
+ }
+ }
+
+ /// merge this fragment to the other fragment if it is possible
+ /// returns None if the fragment can not be merge
+ pub fn merge(&self, other: &Self, settings: &Settings) -> Option<Self> {
+ match (self, other) {
+ // line and line
+ (Fragment::Line(line), Fragment::Line(other_line)) => {
+ if let Some(merged_line) = line.merge(other_line) {
+ Some(Fragment::Line(merged_line))
+ } else {
+ None
+ }
+ }
+
+ // line and polygon
+ (Fragment::Line(line), Fragment::Polygon(polygon)) => {
+ if settings.merge_line_with_shapes {
+ line.merge_line_polygon(polygon)
+ } else {
+ None
+ }
+ }
+
+ // polygon and line
+ (Fragment::Polygon(polygon), Fragment::Line(line)) => {
+ if settings.merge_line_with_shapes {
+ line.merge_line_polygon(polygon)
+ } else {
+ None
+ }
+ }
+
+ // line and marker_line
+ (Fragment::Line(line), Fragment::MarkerLine(mline)) => {
+ if settings.merge_line_with_shapes {
+ line.merge_marker_line(mline)
+ } else {
+ None
+ }
+ }
+ // marker_line and line
+ (Fragment::MarkerLine(mline), Fragment::Line(line)) => {
+ if settings.merge_line_with_shapes {
+ line.merge_marker_line(mline)
+ } else {
+ None
+ }
+ }
+ (Fragment::MarkerLine(mline), Fragment::Polygon(polygon)) => {
+ if settings.merge_line_with_shapes {
+ mline.merge_polygon(polygon)
+ } else {
+ None
+ }
+ }
+ // line and circle
+ (Fragment::Line(line), Fragment::Circle(circle)) => {
+ if settings.merge_line_with_shapes {
+ line.merge_circle(circle)
+ } else {
+ None
+ }
+ }
+
+ // circle and line
+ (Fragment::Circle(circle), Fragment::Line(line)) => {
+ if settings.merge_line_with_shapes {
+ line.merge_circle(circle)
+ } else {
+ None
+ }
+ }
+ // cell_text and cell_text
+ (Fragment::CellText(ctext), Fragment::CellText(other_ctext)) => {
+ if let Some(merged_ctext) = ctext.merge(other_ctext) {
+ Some(Fragment::CellText(merged_ctext))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
+ }
+
+ /// check whether the other fragment is can be fit in this fragment
+ pub(crate) fn can_fit(&self, other: &Self) -> bool {
+ let (tl, br) = self.bounds();
+ let (other_tl, other_br) = other.bounds();
+ tl.x <= other_tl.x
+ && tl.y <= other_tl.y
+ && br.x >= other_br.x
+ && br.y >= other_br.y
+ }
+
+ /// merge fragments recursively until it hasn't changed the number of fragments
+ pub(crate) fn merge_recursive(
+ fragments: Vec<Self>,
+ settings: &Settings,
+ ) -> Vec<Self> {
+ let original_len = fragments.len();
+ let merged = Self::second_pass_merge(fragments, settings);
+ // if has merged continue merging untila nothing can be merged
+ if merged.len() < original_len {
+ Self::merge_recursive(merged, settings)
+ } else {
+ merged
+ }
+ }
+
+ /// second pass merge is operating on fragments comparing to other spans
+ fn second_pass_merge(
+ fragments: Vec<Self>,
+ settings: &Settings,
+ ) -> Vec<Self> {
+ let mut new_groups: Vec<Self> = vec![];
+ for fragment in fragments.into_iter() {
+ let is_merged = new_groups.iter_mut().rev().any(|new_group| {
+ if let Some(new_merged) = new_group.merge(&fragment, settings) {
+ *new_group = new_merged;
+ true
+ } else {
+ false
+ }
+ });
+ if !is_merged {
+ new_groups.push(fragment);
+ }
+ }
+ new_groups
+ }
+
+ /// are lines axis align and parallel
+ pub(in crate) fn is_aabb_parallel(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Fragment::Line(line), Fragment::Line(other)) => {
+ line.is_aabb_parallel(other)
+ }
+ (_, _) => false,
+ }
+ }
+
+ #[allow(unused)]
+ pub(in crate) fn is_aabb_perpendicular(&self, other: &Self) -> bool {
+ match (self, other) {
+ (Fragment::Line(line), Fragment::Line(other)) => {
+ line.is_aabb_perpendicular(other)
+ }
+ (_, _) => false,
+ }
+ }
+
+ /// check if this fragment is touching the other fragment
+ /// therefore can be in a group together
+ pub(in crate) fn is_contacting(&self, other: &Self) -> bool {
+ match self {
+ Fragment::Line(line) => match other {
+ Fragment::Line(other) => line.is_touching(other),
+ Fragment::Arc(other_arc) => line.is_touching_arc(other_arc),
+ Fragment::Polygon(polygon) => {
+ line.merge_line_polygon(polygon).is_some()
+ }
+ Fragment::Circle(circle) => line.is_touching_circle(circle),
+ _ => false,
+ },
+ Fragment::Polygon(polygon) => match other {
+ Fragment::Line(other) => {
+ other.merge_line_polygon(polygon).is_some()
+ }
+ _ => false,
+ },
+ Fragment::Arc(arc) => match other {
+ Fragment::Arc(other_arc) => arc.is_touching(other_arc),
+ Fragment::Line(other_line) => other_line.is_touching_arc(arc),
+ _ => false,
+ },
+ Fragment::Circle(circle) => match other {
+ Fragment::Line(other) => other.is_touching_circle(circle),
+ _ => false,
+ },
+ Fragment::CellText(ctext) => match other {
+ Fragment::CellText(other_ctext) => {
+ ctext.is_contacting(other_ctext)
+ }
+ _ => false,
+ },
+ _ => false,
+ }
+ }
+
+ pub fn hit(&self, start: Point, end: Point) -> bool {
+ self.is_intersecting(&AABB::new(*start, *end))
+ }
+
+ /// check if this fragment is intersecting with this bounding box
+ /// Note: if intersection logic requires testing the solid shape inside the polygon
+ /// use the ConvexPolygon of each shape instead of Polyline
+ pub fn is_intersecting(&self, bbox: &AABB) -> bool {
+ let points = vec![
+ *Point::new(bbox.mins.x, bbox.mins.y),
+ *Point::new(bbox.maxs.x, bbox.mins.y),
+ *Point::new(bbox.maxs.x, bbox.maxs.y),
+ *Point::new(bbox.mins.x, bbox.maxs.y),
+ ];
+ let bbox = Polyline::new(points, None);
+ let identity = Isometry::identity();
+ match self {
+ Fragment::Line(line) => {
+ let segment: Segment = line.clone().into();
+ let res =
+ intersection_test(&identity, &segment, &identity, &bbox)
+ .expect("must pass");
+ println!("res: {}", res);
+ res
+ }
+ Fragment::Rect(rect) => {
+ let polyline: Polyline = rect.clone().into();
+ intersection_test(&identity, &polyline, &identity, &bbox)
+ .expect("must pass")
+ }
+ Fragment::Circle(circle) => {
+ let polyline: Polyline = circle.clone().into();
+ intersection_test(&identity, &polyline, &identity, &bbox)
+ .expect("must pass")
+ }
+ _ => false,
+ }
+ }
+
+ /// check if this fragment can be contain in the specified bounding box `bbox`
+ pub fn is_inside(&self, bbox: &AABB) -> bool {
+ let (start, end) = self.bounds();
+ let frag_bound = AABB::new(*start, *end);
+ bbox.contains(&frag_bound)
+ }
+
+ /// recompute the end points of this fragment
+ /// offset by the cell location
+ pub fn absolute_position(&self, cell: Cell) -> Self {
+ match self {
+ Fragment::Line(line) => {
+ Fragment::Line(line.absolute_position(cell))
+ }
+ Fragment::MarkerLine(marker_line) => {
+ Fragment::MarkerLine(marker_line.absolute_position(cell))
+ }
+ Fragment::Circle(circle) => {
+ Fragment::Circle(circle.absolute_position(cell))
+ }
+ Fragment::Arc(arc) => Fragment::Arc(arc.absolute_position(cell)),
+ Fragment::Polygon(polygon) => {
+ Fragment::Polygon(polygon.absolute_position(cell))
+ }
+ Fragment::Rect(rect) => {
+ Fragment::Rect(rect.absolute_position(cell))
+ }
+ Fragment::Text(text) => {
+ Fragment::Text(text.absolute_position(cell))
+ }
+ Fragment::CellText(ctext) => {
+ Fragment::CellText(ctext.absolute_position(cell))
+ }
+ }
+ }
+
+ /// enlarge or shrink this fragment at scale
+ pub fn scale(&self, scale: f32) -> Self {
+ match self {
+ Fragment::Line(line) => Fragment::Line(line.scale(scale)),
+ Fragment::MarkerLine(marker_line) => {
+ Fragment::MarkerLine(marker_line.scale(scale))
+ }
+ Fragment::Circle(circle) => Fragment::Circle(circle.scale(scale)),
+ Fragment::Arc(arc) => Fragment::Arc(arc.scale(scale)),
+ Fragment::Polygon(polygon) => {
+ Fragment::Polygon(polygon.scale(scale))
+ }
+ Fragment::Rect(rect) => Fragment::Rect(rect.scale(scale)),
+ Fragment::Text(text) => Fragment::Text(text.scale(scale)),
+ // the CellText is converted into text fragment first, then scaled
+ Fragment::CellText(ctext) => {
+ Fragment::Text(Into::<Text>::into(ctext.clone()).scale(scale))
+ }
+ }
+ }
+
+ pub fn align(&self) -> Self {
+ match self {
+ Fragment::Line(line) => Fragment::Line(line.align()),
+ Fragment::MarkerLine(marker_line) => {
+ Fragment::MarkerLine(marker_line.align())
+ }
+ Fragment::Circle(circle) => Fragment::Circle(circle.clone()),
+ Fragment::Arc(arc) => Fragment::Arc(arc.clone()),
+ Fragment::Polygon(polygon) => Fragment::Polygon(polygon.clone()),
+ Fragment::Rect(rect) => Fragment::Rect(rect.clone()),
+ Fragment::Text(text) => Fragment::Text(text.clone()),
+ // the CellText is converted into text fragment first, then scaled
+ Fragment::CellText(ctext) => {
+ Fragment::Text(Into::<Text>::into(ctext.clone()))
+ }
+ }
+ }
+
+ /// rank for additional cmp comparison
+ /// so as not match different shapes if their lower bounds
+ /// and upper bounds matched
+ fn rank(&self) -> u8 {
+ match self {
+ Fragment::Line(_) => 10,
+ Fragment::MarkerLine(_) => 20,
+ Fragment::Circle(_) => 30,
+ Fragment::Arc(_) => 40,
+ Fragment::Polygon { .. } => 50,
+ Fragment::Rect(_) => 60,
+ Fragment::Text(_) => 70,
+ Fragment::CellText(_) => 80,
+ }
+ }
+
+ pub fn as_line(&self) -> Option<&Line> {
+ match self {
+ Fragment::Line(ref line) => Some(line),
+ _ => None,
+ }
+ }
+
+ pub fn as_rect(&self) -> Option<&Rect> {
+ match self {
+ Fragment::Rect(ref rect) => Some(rect),
+ _ => None,
+ }
+ }
+
+ pub fn as_polygon(&self) -> Option<&Polygon> {
+ match self {
+ Fragment::Polygon(polygon) => Some(polygon),
+ _ => None,
+ }
+ }
+
+ pub fn as_arc(&self) -> Option<&Arc> {
+ match self {
+ Fragment::Arc(ref arc) => Some(arc),
+ _ => None,
+ }
+ }
+
+ pub fn as_cell_text(&self) -> Option<&CellText> {
+ match self {
+ Fragment::CellText(ref ctext) => Some(ctext),
+ _ => None,
+ }
+ }
+
+ pub fn as_text(&self) -> Option<&Text> {
+ match self {
+ Fragment::Text(ref text) => Some(text),
+ _ => None,
+ }
+ }
+
+ /// if this is a cell text and is wrapped in braces then it is a css
+ /// tag for the container
+ pub fn as_css_tag(&self) -> Vec<String> {
+ let input_text: Option<&str> =
+ if let Some(cell_text) = self.as_cell_text() {
+ Some(&cell_text.content)
+ } else if let Some(text) = self.as_text() {
+ Some(&text.text)
+ } else {
+ None
+ };
+
+ if let Some(input_text) = input_text {
+ if let Ok(tags) = crate::util::parser::parse_css_tag(&input_text) {
+ tags
+ } else {
+ vec![]
+ }
+ } else {
+ vec![]
+ }
+ }
+
+ pub fn as_circle(&self) -> Option<&Circle> {
+ match self {
+ Fragment::Circle(circle) => Some(circle),
+ _ => None,
+ }
+ }
+
+ pub fn is_circle(&self) -> bool {
+ match self {
+ Fragment::Circle(_) => true,
+ _ => false,
+ }
+ }
+
+ pub fn is_rect(&self) -> bool {
+ match self {
+ Fragment::Rect(_) => true,
+ _ => false,
+ }
+ }
+
+ pub fn is_text(&self) -> bool {
+ match self {
+ Fragment::Text(_) => true,
+ _ => false,
+ }
+ }
+
+ pub fn is_cell_text(&self) -> bool {
+ match self {
+ Fragment::CellText(_) => true,
+ _ => false,
+ }
+ }
+
+ pub fn is_broken(&self) -> bool {
+ match self {
+ Fragment::Line(line) => line.is_broken(),
+ Fragment::Rect(rect) => rect.is_broken(),
+ _ => false,
+ }
+ }
+}
+
+impl Bounds for Fragment {
+ fn bounds(&self) -> (Point, Point) {
+ match self {
+ Fragment::Line(line) => line.bounds(),
+ //TODO: also add the bounds for both of the markers
+ Fragment::MarkerLine(mline) => mline.bounds(),
+ Fragment::Circle(circle) => circle.bounds(),
+ Fragment::Arc(arc) => arc.bounds(),
+ Fragment::Polygon(polygon) => polygon.bounds(),
+ Fragment::Rect(rect) => rect.bounds(),
+ Fragment::Text(text) => text.bounds(),
+ Fragment::CellText(ctext) => ctext.bounds(),
+ }
+ }
+}
+
+impl<MSG> Into<Node<MSG>> for Fragment {
+ fn into(self) -> Node<MSG> {
+ match self {
+ Fragment::Line(line) => line.into(),
+ Fragment::MarkerLine(marker_line) => marker_line.into(),
+ Fragment::Circle(circle) => circle.into(),
+ Fragment::Arc(arc) => arc.into(),
+ Fragment::Polygon(polygon) => polygon.into(),
+ Fragment::Rect(rect) => rect.into(),
+ Fragment::Text(text) => text.into(),
+ Fragment::CellText(ctext) => ctext.into(),
+ }
+ }
+}
+
+impl fmt::Display for Fragment {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Fragment::Line(line) => write!(f, "{}", line),
+ Fragment::MarkerLine(marker_line) => write!(f, "{}", marker_line),
+ Fragment::Circle(circle) => write!(f, "{}", circle),
+ Fragment::Arc(arc) => write!(f, "{}", arc),
+ Fragment::Polygon(polygon) => write!(f, "{}", polygon),
+ Fragment::Rect(rect) => write!(f, "{}", rect),
+ Fragment::Text(text) => write!(f, "{}", text),
+ Fragment::CellText(ctext) => write!(f, "{}", ctext),
+ }
+ }
+}
+
+pub fn line(a: Point, b: Point) -> Fragment {
+ Fragment::Line(Line::new(a, b, false))
+}
+
+pub fn marker_line(
+ a: Point,
+ b: Point,
+ is_broken: bool,
+ start_marker: Option<Marker>,
+ end_marker: Option<Marker>,
+) -> Fragment {
+ Fragment::MarkerLine(MarkerLine::new(
+ a,
+ b,
+ is_broken,
+ start_marker,
+ end_marker,
+ ))
+}
+
+pub fn broken_line(a: Point, b: Point) -> Fragment {
+ Fragment::Line(Line::new(a, b, true))
+}
+
+pub fn circle(c: Point, r: f32, is_filled: bool) -> Fragment {
+ Fragment::Circle(Circle::new(c, r, is_filled))
+}
+
+pub fn arc(a: Point, b: Point, r: f32) -> Fragment {
+ Fragment::Arc(Arc::new(a, b, r))
+}
+
+pub fn arc_with_sweep(
+ a: Point,
+ b: Point,
+ r: f32,
+ sweep_flag: bool,
+) -> Fragment {
+ Fragment::Arc(Arc::new_with_sweep(a, b, r, sweep_flag))
+}
+
+pub fn polygon(
+ points: Vec<Point>,
+ is_filled: bool,
+ tags: Vec<PolygonTag>,
+) -> Fragment {
+ Fragment::Polygon(Polygon::new(points, is_filled, tags))
+}
+
+pub fn rect(
+ start: Point,
+ end: Point,
+ is_filled: bool,
+ is_broken: bool,
+) -> Fragment {
+ Fragment::Rect(Rect::new(start, end, is_filled, is_broken))
+}
+
+pub fn rounded_rect(
+ start: Point,
+ end: Point,
+ is_filled: bool,
+ radius: f32,
+ is_broken: bool,
+) -> Fragment {
+ Fragment::Rect(Rect::rounded_new(start, end, is_filled, radius, is_broken))
+}
+
+/// creates a cell text meant to be stored
+/// in a cell of a fragment_buffer,
+pub fn cell_text(ch: char) -> Fragment {
+ Fragment::CellText(CellText::new(Cell::new(0, 0), ch.to_string()))
+}
+
+pub fn text(s: String) -> Fragment {
+ Fragment::Text(Text::new(Point::new(0.0, 0.0), s))
+}
+
+pub fn lines_to_fragments(lines: Vec<Line>) -> Vec<Fragment> {
+ lines.into_iter().map(|line| Fragment::Line(line)).collect()
+}
+
+impl From<Line> for Fragment {
+ fn from(line: Line) -> Self {
+ Fragment::Line(line)
+ }
+}
+
+impl From<Rect> for Fragment {
+ fn from(rect: Rect) -> Self {
+ Fragment::Rect(rect)
+ }
+}
+
+impl From<Text> for Fragment {
+ fn from(text: Text) -> Self {
+ Fragment::Text(text)
+ }
+}
+
+impl Into<Fragment> for CellText {
+ fn into(self) -> Fragment {
+ Fragment::CellText(self)
+ }
+}
+
+impl From<Circle> for Fragment {
+ fn from(circle: Circle) -> Self {
+ Fragment::Circle(circle)
+ }
+}
+
+impl From<Arc> for Fragment {
+ fn from(arc: Arc) -> Self {
+ Fragment::Arc(arc)
+ }
+}
+
+impl Eq for Fragment {}
+
+impl Ord for Fragment {
+ fn cmp(&self, other: &Self) -> Ordering {
+ match (self, other) {
+ (Fragment::Line(line), Fragment::Line(other)) => line.cmp(other),
+ (Fragment::Arc(arc), Fragment::Arc(other)) => arc.cmp(other),
+ (Fragment::Circle(circle), Fragment::Circle(other)) => {
+ circle.cmp(other)
+ }
+ (Fragment::Polygon(polygon), Fragment::Polygon(other_polygon)) => {
+ polygon.cmp(other_polygon)
+ } //Note: the tags are not compared here
+ (Fragment::Rect(rect), Fragment::Rect(other)) => rect.cmp(other),
+ (Fragment::Text(text), Fragment::Text(other)) => text.cmp(other),
+ (Fragment::CellText(ctext), Fragment::CellText(other)) => {
+ ctext.cmp(other)
+ }
+ _ => self
+ .mins()
+ .cmp(&other.mins())
+ .then(self.maxs().cmp(&other.maxs()))
+ .then(self.rank().cmp(&other.rank())),
+ }
+ }
+}
+
+impl PartialOrd for Fragment {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl PartialEq for Fragment {
+ fn eq(&self, other: &Self) -> bool {
+ self.cmp(other) == Ordering::Equal
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::buffer::CellGrid;
+
+ #[test]
+ fn test_can_fit() {
+ 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(),
+ ));
+ assert!(rect1.can_fit(&rect2));
+ assert!(rect1.can_fit(&text1));
+ assert!(rect2.can_fit(&text1));
+ assert!(!rect1.can_fit(&text2));
+ assert!(!rect2.can_fit(&text2));
+ }
+
+ #[test]
+ fn test_recursive_merge() {
+ let k = CellGrid::k();
+ let o = CellGrid::o();
+ let c = CellGrid::c();
+ let m = CellGrid::m();
+ let w = CellGrid::w();
+ let fragments1 = vec![line(k, m), line(m, o), line(c, m), line(m, w)];
+ println!("before merged:");
+ for frag in &fragments1 {
+ println!("{}", frag);
+ }
+ let mut expected = vec![line(k, o), line(c, w)];
+ expected.sort();
+ let mut merged_fragments1 =
+ Fragment::merge_recursive(fragments1, &Settings::default());
+ merged_fragments1.sort();
+ assert_eq!(merged_fragments1.len(), 2);
+ println!("after merged:");
+ for frag in &merged_fragments1 {
+ println!("{}", frag);
+ }
+ assert_eq!(merged_fragments1, expected);
+ }
+
+ #[test]
+ fn test_can_merge() {
+ let k = CellGrid::k();
+ let l = CellGrid::l();
+ let m = CellGrid::m();
+ let n = CellGrid::n();
+ let o = CellGrid::o();
+ let j = CellGrid::j();
+ let mut settings = Settings::default();
+ settings.merge_line_with_shapes = true;
+
+ assert!(line(k, m).merge(&line(m, o), &settings).is_some()); // collinear and connected
+ assert!(!line(k, l).merge(&line(n, o), &settings).is_some()); //collinear but not connected
+ assert!(!line(k, o).merge(&line(o, j), &settings).is_some()); // connected but not collinear
+ }
+
+ /*
+ #[test]
+ fn merge_unicode_triangle_and_line() {
+ let arrow = '▶';
+ let entry = crate::map::UNICODE_FRAGMENTS
+ .get(&arrow)
+ .expect("must have a fragement");
+ let a = CellGrid::a();
+ let y = CellGrid::y();
+
+ let polygon = entry[0].absolute_position(Cell::new(0, 0));
+ let diagonal: Fragment = Line::new_noswap(y, a, false).into();
+ let diagonal = diagonal.absolute_position(Cell::new(1, 1));
+
+ println!("polygon: {:#?}", polygon);
+ println!("diagonal: {:#?}", diagonal);
+ let mut settings = Settings::default();
+
+ settings.merge_line_with_shapes = true;
+ let merged = polygon.merge(&diagonal, &settings);
+
+ let expected = marker_line(
+ Point::new(2.0, 4.0),
+ Point::new(0.0, 0.0),
+ false,
+ None,
+ Some(Marker::Arrow),
+ );
+ assert_eq!(Some(expected), merged);
+ }
+ */
+
+ #[test]
+ fn merge_line_and_circle() {
+ let a = CellGrid::a();
+ let m = CellGrid::m();
+ let y = CellGrid::y();
+
+ let circle = circle(m, Cell::unit(2), false);
+
+ let circle = circle.absolute_position(Cell::new(0, 0));
+ let diagonal: Fragment = Line::new_noswap(a, y, false).into();
+ let diagonal = diagonal.absolute_position(Cell::new(1, 1));
+
+ println!("circle: {:#?}", circle);
+ println!("diagonal: {:#?}", diagonal);
+
+ let mut settings = Settings::default();
+ settings.merge_line_with_shapes = true;
+ let merged = circle.merge(&diagonal, &settings);
+
+ let expected = marker_line(
+ Point::new(2.0, 4.0),
+ Point::new(0.5, 1.0),
+ false,
+ None,
+ Some(Marker::BigOpenCircle),
+ );
+ assert_eq!(Some(expected), merged);
+ }
+
+ #[test]
+ fn line_overlaps() {
+ let line = Line::new(CellGrid::a(), CellGrid::b(), false);
+ println!("line: {}", line);
+ assert!(line.overlaps(CellGrid::a(), CellGrid::b()));
+ assert!(!line.overlaps(CellGrid::d(), CellGrid::e()));
+ }
+
+ #[test]
+ fn line_overlap2() {
+ let ko = line(CellGrid::k(), CellGrid::o());
+ assert!(ko.line_overlap(CellGrid::n(), CellGrid::o()));
+ assert!(ko.line_overlap(CellGrid::n(), CellGrid::o()));
+ }
+
+ #[test]
+ fn line_overlap3() {
+ let km = line(CellGrid::k(), CellGrid::m());
+ assert!(km.line_overlap(CellGrid::l(), CellGrid::m()));
+ assert!(!km.line_overlap(CellGrid::n(), CellGrid::o()));
+ assert!(!km.line_overlap(CellGrid::m(), CellGrid::o()));
+ assert!(!km.line_overlap(CellGrid::l(), CellGrid::o()));
+ }
+
+ #[test]
+ fn equal_lines() {
+ assert_eq!(
+ line(CellGrid::a(), CellGrid::y()),
+ line(CellGrid::y(), CellGrid::a())
+ );
+ assert_eq!(
+ line(CellGrid::k(), CellGrid::o()),
+ line(CellGrid::o(), CellGrid::k())
+ );
+ }
+
+ #[test]
+ fn test_sort_lines() {
+ let mut lines1 = vec![
+ line(CellGrid::a(), CellGrid::e()),
+ line(CellGrid::u(), CellGrid::y()),
+ line(CellGrid::k(), CellGrid::o()),
+ ];
+
+ let sorted = vec![
+ line(CellGrid::a(), CellGrid::e()),
+ line(CellGrid::k(), CellGrid::o()),
+ line(CellGrid::u(), CellGrid::y()),
+ ];
+
+ assert_ne!(lines1, sorted);
+ lines1.sort();
+ assert_eq!(lines1, sorted);
+ }
+
+ #[test]
+ fn test_sort_lines2() {
+ let mut lines1 = vec![
+ line(CellGrid::m(), CellGrid::e()),
+ line(CellGrid::a(), CellGrid::y()),
+ line(CellGrid::k(), CellGrid::o()),
+ ];
+
+ let sorted = vec![
+ line(CellGrid::a(), CellGrid::y()),
+ line(CellGrid::m(), CellGrid::e()),
+ line(CellGrid::k(), CellGrid::o()),
+ ];
+ assert_ne!(lines1, sorted);
+ lines1.sort();
+ assert_eq!(lines1, sorted);
+ }
+
+ #[test]
+ fn test_hit() {
+ let line1 = line(CellGrid::a(), CellGrid::y());
+ assert!(line1.hit(CellGrid::g(), CellGrid::s()));
+
+ let rect_gs = rect(CellGrid::g(), CellGrid::s(), false, false);
+ assert!(!rect_gs.hit(CellGrid::a(), CellGrid::y()));
+
+ let rect1 = rect(CellGrid::a(), CellGrid::y(), false, false);
+ assert!(!rect1.hit(CellGrid::g(), CellGrid::s()));
+ }
+}