diff options
author | Jovansonlee Cesar <ivanceras@gmail.com> | 2018-07-29 16:29:24 +0800 |
---|---|---|
committer | Jovansonlee Cesar <ivanceras@gmail.com> | 2018-07-29 16:29:24 +0800 |
commit | d053c331bf2c1dd76c4def1825dbee55e018619c (patch) | |
tree | 3d0d298c6b91575c1575d6e133607a2c9b5a2110 | |
parent | 6310e44a44c14c12238dcd35f3f496b3e20814d8 (diff) |
refactored to modularized components to its corresponding file
-rw-r--r-- | svgbob/src/block.rs | 32 | ||||
-rw-r--r-- | svgbob/src/box_drawing.rs | 6 | ||||
-rw-r--r-- | svgbob/src/element.rs | 312 | ||||
-rw-r--r-- | svgbob/src/focus_char.rs (renamed from svgbob/src/patterns.rs) | 375 | ||||
-rw-r--r-- | svgbob/src/fragments.rs | 49 | ||||
-rw-r--r-- | svgbob/src/grid.rs | 820 | ||||
-rw-r--r-- | svgbob/src/lib.rs | 1275 | ||||
-rw-r--r-- | svgbob/src/loc.rs | 122 | ||||
-rw-r--r-- | svgbob/src/loc_block.rs | 250 | ||||
-rw-r--r-- | svgbob/src/location.rs | 162 | ||||
-rw-r--r-- | svgbob/src/optimizer.rs | 14 | ||||
-rw-r--r-- | svgbob/src/point.rs | 37 | ||||
-rw-r--r-- | svgbob/src/point_block.rs | 50 | ||||
-rw-r--r-- | svgbob/src/properties.rs | 196 | ||||
-rw-r--r-- | svgbob/src/settings.rs | 99 | ||||
-rw-r--r-- | svgbob/src/svg_element.rs | 28 |
16 files changed, 1958 insertions, 1869 deletions
diff --git a/svgbob/src/block.rs b/svgbob/src/block.rs new file mode 100644 index 0000000..11f0d79 --- /dev/null +++ b/svgbob/src/block.rs @@ -0,0 +1,32 @@ + +/// exact location of point +/// relative to the Character Block +/// The block is divided in to 5x5 small blocks +#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq)] +pub enum Block { + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, +} diff --git a/svgbob/src/box_drawing.rs b/svgbob/src/box_drawing.rs index 5a8e782..e84fa7f 100644 --- a/svgbob/src/box_drawing.rs +++ b/svgbob/src/box_drawing.rs @@ -1,10 +1,10 @@ -use fragments::Block; -use fragments::Block::{A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y}; +use block::Block; +use block::Block::{A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y}; use fragments::Fragment; use fragments::{arc, line}; -use properties::PointBlock; +use point_block::PointBlock; use properties::Properties; pub fn box_drawing(ch: &char) -> (Vec<Block>, Vec<Fragment>) { diff --git a/svgbob/src/element.rs b/svgbob/src/element.rs new file mode 100644 index 0000000..5676f2a --- /dev/null +++ b/svgbob/src/element.rs @@ -0,0 +1,312 @@ +use std::cmp::Ordering; +use svg_element::SvgElement; +use svg::Node; +use svg::node::element::{ + Circle as SvgCircle, + Definitions, + Line as SvgLine, + Marker, + Path as SvgPath, + Rectangle as SvgRect, + Style, + Text as SvgText, +}; +use grid::svg_escape; +use point::collinear; +use settings::Settings; + +use point::Point; +use loc::Loc; +use element::{ + Stroke::{Solid,Dashed}, + ArcFlag::{Minor,Major}, + Feature::{Arrow,ArrowStart,Circle}, +}; +use unicode_width::UnicodeWidthStr; + +#[derive(Debug, Clone, PartialEq, PartialOrd )] +pub enum Element { + Circle(Point, f32, String), + Line(Point, Point, Stroke, Vec<Feature>), + Arc(Point, Point, f32, ArcFlag, bool, Stroke, Vec<Feature>), + Text(Loc, String), + Path(Point, Point, String, Stroke), +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] +pub enum Stroke { + Solid, + Dashed, +} + + +#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] +pub enum Feature { + ArrowStart, // start arrow + Arrow, //end + Circle, //start +} + + + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub enum ArcFlag { + Major, + Minor, +} + +impl Ord for Element{ + fn cmp(&self, other: &Self) -> Ordering{ + if let Some(order) = self.partial_cmp(&other){ + return order + } + Ordering::Less + } +} +impl Eq for Element{ +} + +pub fn line(a: &Point, b: &Point) -> Element { + Element::Line(a.clone(), b.clone(), Solid, vec![]) +} + +pub fn solid_circle(c: &Point, r: f32) -> Element { + Element::Circle(c.clone(), r, "solid".to_string()) +} + +pub fn arrow_arc(a: &Point, b: &Point, r: f32) -> Element { + Element::Arc(a.clone(), b.clone(), r, Minor, false, Solid, vec![Arrow]) +} + +pub fn arrow_sweep_arc(a: &Point, b: &Point, r: f32) -> Element { + Element::Arc(a.clone(), b.clone(), r.clone(), Minor, true, Solid, vec![Arrow]) +} + +pub fn arc(a: &Point, b: &Point, r: f32) -> Element { + Element::Arc(a.clone(), b.clone(), r, Minor, false, Solid, vec![]) +} + +pub fn arc_major(a: &Point, b: &Point, r: f32) -> Element { + Element::Arc(a.clone(), b.clone(), r, Major, false, Solid, vec![]) +} + +pub fn open_circle(c: &Point, r: f32) -> Element { + Element::Circle(c.clone(), r.clone(), "open".to_string()) +} + +pub fn arrow_line(s: &Point, e: &Point) -> Element { + Element::Line(s.clone(), e.clone(), Solid, vec![Arrow]) +} + +pub fn start_arrow_line(s: &Point, e: &Point) -> Element { + Element::Line(s.clone(), e.clone(), Solid, vec![ArrowStart, Arrow,Circle]) +} + +pub fn text(loc: &Loc, txt: &str) -> Element { + Element::Text(loc.clone(), svg_escape(txt)) +} + +pub fn blank_text(loc: &Loc) -> Element { + text(loc, " ".into()) +} + +impl Element { + // if this element can reduce the other, return the new reduced element + // for line it has to be collinear and in can connect start->end->start + // for text, the other text should apear on the right side of this text + pub fn reduce(&self, other: &Element) -> Option<Element> { + match *self { + Element::Line(ref s, ref e, ref stroke, ref feature) => { + match *other { + Element::Line(ref s2, ref e2, ref stroke2, ref feature2) => { + // note: dual 3 point check for trully collinear lines + if collinear(s, e, s2) + && collinear(s, e, e2) + && stroke == stroke2{ + + // line1 line2 + // s-----e s2-----e2 + // s----------------e2 + if e == s2 { + // ----- + // o---- + let cond1 = feature.is_empty() || (feature.contains(&Circle) && feature.len() == 1); + // ------ + // ------> + let cond2 = feature2.is_empty() || (feature2.contains(&Arrow) && feature2.len() ==1); + if cond1 && cond2{ + return Some(Element::Line( + s.clone(), + e2.clone(), + stroke.clone(), + feature2.clone() + )); + } + } + // line1 line2 + // s------e e2-------s2 + // s-------------------s2 + else if e == e2{ + // ------- -------- + // o------ --------- + if (feature.is_empty() || (feature.contains(&Circle) && feature.len() == 1)) + && feature2.is_empty(){ + return Some(Element::Line( + s.clone(), + s2.clone(), + stroke.clone(), + feature2.clone() + )); + } + } + // line1 line2 + // e------s s2------e2 + // s------------------e2 + else if s == s2{ + // ------- ------- + // ------- -------> + if feature.is_empty() + && (feature2.is_empty() || (feature2.contains(&Arrow) && feature2.len() ==1 )){ + return Some(Element::Line( + e.clone(), + e2.clone(), + stroke.clone(), + feature2.clone() + )); + } + } + // line1 line2 + // e------s e2------s2 + // e---------------------s2 + // + else if s == e2{ + // ----- ----- + // ----- ----o + // <---- ----- + // <---- ----o + let cond1 = feature.is_empty() || (feature.contains(&Arrow) && feature.len() == 1); + let cond2 = feature2.is_empty() || (feature2.contains(&Circle) && feature2.len() == 1); + if cond1 && cond2{ + return Some(Element::Line( + s2.clone(), + e.clone(), + stroke.clone(), + feature.clone(), + )); + } + } + } + return None; + } + _ => None, + } + } + Element::Text(ref loc, ref text) => { + match *other { + Element::Text(ref loc2, ref text2) => { + // reduce if other is next to it + let uwidth = text.width() as i32; + if loc.y == loc2.y && loc.x + uwidth == loc2.x { + let merged_text = text.clone() + text2; + let reduced = Some(Element::Text(loc.clone(), merged_text)); + reduced + } else { + None + } + } + _ => None, + } + } + _ => None, + } + } + + /// convert drawing element to SVG element + pub fn to_svg(&self, settings: &Settings) -> SvgElement { + match *self { + Element::Circle(ref c, r, ref class) => { + let svg_circle = SvgCircle::new() + .set("class", class.clone()) + .set("cx", c.x) + .set("cy", c.y) + .set("r", r); + + SvgElement::Circle(svg_circle) + } + Element::Line(ref s, ref e, ref stroke, ref features) => { + let mut svg_line = SvgLine::new() + .set("x1", s.x) + .set("y1", s.y) + .set("x2", e.x) + .set("y2", e.y); + for feature in features{ + match *feature { + Arrow => { + svg_line.assign("marker-end", "url(#triangle)"); + } + ArrowStart => { + svg_line.assign("marker-start", "url(#triangle)"); + } + Circle => { + svg_line.assign("marker-start", "url(#circle)"); + } + }; + } + match *stroke { + Solid => (), + Dashed => { + svg_line.assign("stroke-dasharray", (3, 3)); + svg_line.assign("fill", "none"); + } + }; + + SvgElement::Line(svg_line) + } + Element::Arc(ref s, ref e, radius, ref arc_flag, sweep, _, ref features) => { + let sweept = if sweep { "1" } else { "0" }; + let arc_flag = match *arc_flag { + Major => "1", + Minor => "0", + }; + let d = format!( + "M {} {} A {} {} 0 {} {} {} {}", + s.x, s.y, radius, radius, arc_flag, sweept, e.x, e.y + ); + let mut svg_arc = SvgPath::new().set("d", d).set("fill", "none"); + for feature in features{ + match *feature { + Arrow => { + svg_arc.assign("marker-end", "url(#triangle)"); + } + ArrowStart => { + svg_arc.assign("marker-start", "url(#triangle)"); + } + Circle => { + svg_arc.assign("marker-start", "url(#circle)"); + } + }; + } + SvgElement::Path(svg_arc) + } + Element::Text(ref loc, ref string) => { + let sx = loc.x as f32 * settings.text_width + settings.text_width / 8.0; + let sy = loc.y as f32 * settings.text_height + settings.text_height * 3.0 / 4.0; + let mut svg_text = SvgText::new().set("x", sx).set("y", sy); + let text_node = svg::node::Text::new(string.to_string()); + svg_text.append(text_node); + SvgElement::Text(svg_text) + } + Element::Path(_, _, ref d, ref stroke) => { + let mut path = SvgPath::new().set("d", d.to_owned()).set("fill", "none"); + + match *stroke { + Solid => (), + Dashed => { + path.assign("stroke-dasharray", (3, 3)); + } + }; + SvgElement::Path(path) + } + } + } +} diff --git a/svgbob/src/patterns.rs b/svgbob/src/focus_char.rs index ac7d2cc..cfadb2b 100644 --- a/svgbob/src/patterns.rs +++ b/svgbob/src/focus_char.rs @@ -1,252 +1,21 @@ -use properties::Characteristic; -use properties::Signal; -use Element; -use Grid; -use Loc; -use Point; -use Settings; - -use fragments::Block; -use fragments::Block::{A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y}; -use properties::Location; -use properties::PointBlock; - +use block::Block; use fragments::Fragment; +use loc::Loc; +use grid::Grid; +use properties::{ + Properties, + Signal::{self,Weak,Medium,Strong}, + Can::{self,ConnectTo,Is,IsStrongAll}, + Characteristic, +}; +use point_block::PointBlock; +use point::Point; +use loc_block::LocBlock; +use element::Element; use fragments::Fragment::Text; - -use properties::Can; -use properties::Properties; -use properties::Signal::{Medium, Strong, Weak}; - -use {arc, arrow_line, start_arrow_line, line, open_circle, solid_circle, text}; - -use properties::Can::{ConnectTo, Is, IsStrongAll}; - -struct LocBlock { - loc: Loc, - settings: Settings, -} - -impl LocBlock { - fn text_width(&self) -> f32 { - self.settings.text_width - } - - fn text_height(&self) -> f32 { - self.settings.text_height - } - - fn loc_x(&self) -> f32 { - self.loc.x as f32 - } - - fn loc_y(&self) -> f32 { - self.loc.y as f32 - } - - /// 1 unit in x dimension is 1/4 of the textwidth - /// used in calculating the radius, adjustments - /// in the Fragment PointBlock - fn unit_x(&self) -> f32 { - self.text_width() * 1.0 / 4.0 - } - - /// 1 unit along y dimension is 1/4 of the textwidth - #[allow(unused)] - fn unit_y(&self) -> f32 { - self.text_height() * 1.0 / 4.0 - } - - /// x coordinate on increment of 1/4 of text width - fn x0(&self) -> f32 { - self.loc_x() * self.text_width() - } - - fn x1(&self) -> f32 { - (self.loc_x() + 1.0 / 4.0) * self.text_width() - } - - fn x2(&self) -> f32 { - (self.loc_x() + 1.0 / 2.0) * self.text_width() - } - - fn x3(&self) -> f32 { - (self.loc_x() + 3.0 / 4.0) * self.text_width() - } - - fn x4(&self) -> f32 { - (self.loc_x() + 1.0) * self.text_width() - } - - /// y coordinate on increment of 1/4 of text_height - fn y0(&self) -> f32 { - self.loc_y() * self.text_height() - } - - fn y1(&self) -> f32 { - (self.loc_y() + 1.0 / 4.0) * self.text_height() - } - - fn y2(&self) -> f32 { - (self.loc_y() + 1.0 / 2.0) * self.text_height() - } - - fn y3(&self) -> f32 { - (self.loc_y() + 3.0 / 4.0) * self.text_height() - } - - fn y4(&self) -> f32 { - (self.loc_y() + 1.0) * self.text_height() - } - - /// 1st row a,b,c,d,e - fn a(&self) -> Point { - Point::new(self.x0(), self.y0()) - } - - fn b(&self) -> Point { - Point::new(self.x1(), self.y0()) - } - - fn c(&self) -> Point { - Point::new(self.x2(), self.y0()) - } - - fn d(&self) -> Point { - Point::new(self.x3(), self.y0()) - } - - fn e(&self) -> Point { - Point::new(self.x4(), self.y0()) - } - - /// 2nd row f,g,h,i,j - fn f(&self) -> Point { - Point::new(self.x0(), self.y1()) - } - - fn g(&self) -> Point { - Point::new(self.x1(), self.y1()) - } - - fn h(&self) -> Point { - Point::new(self.x2(), self.y1()) - } - - fn i(&self) -> Point { - Point::new(self.x3(), self.y1()) - } - - fn j(&self) -> Point { - Point::new(self.x4(), self.y1()) - } - - /// 3rd row k,l,m,n,o - fn k(&self) -> Point { - Point::new(self.x0(), self.y2()) - } - - fn l(&self) -> Point { - Point::new(self.x1(), self.y2()) - } - - fn m(&self) -> Point { - Point::new(self.x2(), self.y2()) - } - - fn n(&self) -> Point { - Point::new(self.x3(), self.y2()) - } - - fn o(&self) -> Point { - Point::new(self.x4(), self.y2()) - } - - /// 4th row p,q,r,s,t - fn p(&self) -> Point { - Point::new(self.x0(), self.y3()) - } - - fn q(&self) -> Point { - Point::new(self.x1(), self.y3()) - } - - fn r(&self) -> Point { - Point::new(self.x2(), self.y3()) - } - - fn s(&self) -> Point { - Point::new(self.x3(), self.y3()) - } - - fn t(&self) -> Point { - Point::new(self.x4(), self.y3()) - } - - /// 5th row u,v,w,x,y - fn u(&self) -> Point { - Point::new(self.x0(), self.y4()) - } - - fn v(&self) -> Point { - Point::new(self.x1(), self.y4()) - } - - fn w(&self) -> Point { - Point::new(self.x2(), self.y4()) - } - - fn x(&self) -> Point { - Point::new(self.x3(), self.y4()) - } - - fn y(&self) -> Point { - Point::new(self.x4(), self.y4()) - } - - pub fn to_point(&self, pb: &PointBlock) -> Point { - // move loc to the additional location relative to itself - let loc = if let Some(ref pbloc) = pb.location { - self.loc.from_location(&pbloc) - } else { - self.loc.clone() - }; - let lb = LocBlock { - loc: loc, - settings: self.settings.clone(), - }; - let mut p = match pb.block { - A => lb.a(), - B => lb.b(), - C => lb.c(), - D => lb.d(), - E => lb.e(), - F => lb.f(), - G => lb.g(), - H => lb.h(), - I => lb.i(), - J => lb.j(), - K => lb.k(), - L => lb.l(), - M => lb.m(), - N => lb.n(), - O => lb.o(), - P => lb.p(), - Q => lb.q(), - R => lb.r(), - S => lb.s(), - T => lb.t(), - U => lb.u(), - V => lb.v(), - W => lb.w(), - X => lb.x(), - Y => lb.y(), - }; - let unit = self.unit_x(); - p.adjust(pb.adjust_x * unit, pb.adjust_y * unit); - p - } -} +use element::{line,arrow_line,start_arrow_line,arc,open_circle,solid_circle,text}; +use location::Location; +use settings::Settings; #[derive(Debug, Clone)] pub struct FocusChar<'g> { @@ -583,115 +352,3 @@ impl<'g> FocusChar<'g> { self.get(&self.loc.bottom_right()) } } - -#[cfg(test)] -mod test { - use super::super::Loc; - use super::super::Settings; - use super::FocusChar; - use super::Grid; - use fragments::Direction::*; - use properties::Location; - - use fragments::Block::{O, U, Y}; - - #[test] - fn test_adjascent() { - let g = Grid::from_str("a统öo͡͡͡", &Settings::compact()); - let fc = FocusChar::new(&Loc::new(1, 0), &g); - println!("{:?}", fc); - assert!(fc.left().is('a')); - assert!(fc.right().right().is('ö')); - } - - #[test] - fn test100() { - // ._ - let g = Grid::from_str(".-", &Settings::separate_lines()); - let fc = FocusChar::new(&Loc::new(0, 0), &g); - let (frags, _consumed) = fc.get_fragments(); - println!("frags: {:?}", frags); - assert!(fc.is_intensified(&O)); - assert!(fc.can_be_strong_block(&O)); - } - - #[test] - fn test_location() { - // ._ - let g = Grid::from_str(".-", &Settings::separate_lines()); - let fc = FocusChar::new(&Loc::new(0, 0), &g); - let (_frags, _consumed) = fc.get_fragments(); - let go_right = fc.from_location(&Location::go(Right)); - let right = fc.right(); - let right2 = fc.in_right(2); - let mut right2_loop = fc.clone(); - for _ in 0..2 { - right2_loop = right2_loop.in_right(1); - } - println!("in right 2: {:?}", right2.loc); - println!("in right 2 loop: {:?}", right2_loop.loc); - assert_eq!(right2.loc, right2_loop.loc); - assert_eq!(go_right.loc, right.loc); - } - - #[test] - fn test_loc() { - let g = Grid::from_str("", &Settings::separate_lines()); - let fc = FocusChar::new(&Loc::new(0, 0), &g); - let right = fc.right(); - let in_right = fc.in_right(1); - assert_eq!(Loc::new(1, 0), right.loc); - assert_eq!(Loc::new(1, 0), in_right.loc); - } - - #[test] - fn test1() { - // ._ - let g = Grid::from_str("._", &Settings::separate_lines()); - let fc = FocusChar::new(&Loc::new(0, 0), &g); - println!("focus char: {:#?}", fc); - let (frags, _consumed) = fc.get_fragments(); - println!("frags: {:?}", frags); - assert!(!fc.is_intensified(&U)); - assert!(fc.is_intensified(&Y)); - } - #[test] - fn test2() { - // ._ - let g = Grid::from_str("._", &Settings::separate_lines()); - let fc = FocusChar::new(&Loc::new(1, 0), &g); - println!("focus char: {:#?}", fc); - let (frags, _consumed) = fc.get_fragments(); - println!("frags: {:?}", frags); - assert!(!fc.is_intensified(&Y)); - assert!(!fc.is_intensified(&U)); - assert!(fc.can_be_strong_block(&Y)); - assert!(fc.can_be_strong_block(&U)); - } - - #[test] - fn test_no_character() { - use properties::Properties; - use {FocusChar, Grid, Loc, Settings}; - - let g = Grid::from_str(".l", &Settings::separate_lines()); - let fc = FocusChar::new(&Loc::new(1, 0), &g); - println!("focus char: {:#?}", fc); - let character = fc.ch.get_characteristic(); - println!("character: {:#?}", character); - assert!(character.is_none()); - } - - #[test] - fn test_has_character() { - use properties::Properties; - use {FocusChar, Grid, Loc, Settings}; - - let g = Grid::from_str(".╦", &Settings::separate_lines()); - let fc = FocusChar::new(&Loc::new(1, 0), &g); - println!("focus char: {:#?}", fc); - let character = fc.ch.get_characteristic(); - println!("character: {:#?}", character); - assert!(character.is_some()); - } -} diff --git a/svgbob/src/fragments.rs b/svgbob/src/fragments.rs index c5c71fd..dc485e2 100644 --- a/svgbob/src/fragments.rs +++ b/svgbob/src/fragments.rs @@ -1,39 +1,8 @@ use self::Fragment::{Arc, ArrowLine, Line, OpenCircle, SolidCircle, StartArrowLine}; -use properties::PointBlock; +use point_block::PointBlock; use std::cmp::Ordering; -/// exact location of point -/// relative to the Character Block -/// The block is divided in to 5x5 small blocks -#[derive(Debug, Clone, Copy, PartialOrd, PartialEq, Ord, Eq)] -pub enum Block { - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, -} /// These are non-final drawing elements /// Lines most likely fall on the collinear line @@ -69,19 +38,3 @@ pub fn solid_circle(c: &PointBlock, r: i32) -> Fragment { SolidCircle(c.clone(), r) } -/// 8 directions which a character can connect to -/// \|/ -/// -+- -/// /|\ - -#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)] -pub enum Direction { - Top, - Bottom, - Left, - Right, - TopLeft, - TopRight, - BottomLeft, - BottomRight, -} diff --git a/svgbob/src/grid.rs b/svgbob/src/grid.rs new file mode 100644 index 0000000..d8fb129 --- /dev/null +++ b/svgbob/src/grid.rs @@ -0,0 +1,820 @@ +use loc::Loc; +use focus_char::FocusChar; +use optimizer::Optimizer; +use svg_element::SvgElement; +use svg::node::element::SVG; +use svg::Node; +use svg::node::element::{ + Circle as SvgCircle, + Definitions, + Line as SvgLine, + Marker, + Path as SvgPath, + Rectangle as SvgRect, + Style, + Text as SvgText, +}; +use element::Element; +use pom::TextInput; +use pom::parser::{sym,none_of}; +use settings::Settings; +use unicode_width::UnicodeWidthChar; + +#[derive(Debug)] +pub struct Grid { + pub settings: Settings, + index: Vec<Vec<String>>, + text_elm: Vec<(usize, usize, String)>, +} +impl Grid { + /// instantiate a grid from input ascii text + /// Issues: + /// 1. 2-width, 5 bytes, single character i.e. 统 + /// 2. 1-width, 2 bytes, single character i.e. ö + /// 3. 1-width, 3 bytes, single character i.e. o͡͡͡ + pub fn from_str(s: &str, settings: &Settings) -> Grid { + let lines: Vec<&str> = s.lines().collect(); + let mut rows: Vec<Vec<String>> = Vec::with_capacity(lines.len()); + let mut text_elm: Vec<(usize, usize, String)> = vec![]; + for (y, line) in lines.iter().enumerate() { + let (line, escaped_texts): (String, Vec<(usize, String)>) = exclude_escaped_text(line); + let mut row: Vec<String> = Vec::with_capacity(line.chars().count()); + for (x, escaped) in escaped_texts { + text_elm.push((x, y, svg_escape(&escaped))); + } + for ch in line.chars() { + if let Some(1) = ch.width() { + row.push(format!("{}", ch)); + } else if let Some(2) = ch.width() { + row.push(format!("{}", ch)); + // HACK: push a blank to the next cell, + //in order to make this character twice as + // big and aligns the next succeeding characters on + // this row + row.push(format!("\0")); + } + // if zero width char, append it to the previous string + else if let Some(0) = ch.width() { + let prev: Option<String> = row.pop(); + match prev { + Some(mut prev) => { + prev.push(ch); + row.push(prev); + } + None => (), + } + } + } + rows.push(row); + } + Grid { + settings: settings.clone(), + index: rows, + text_elm: text_elm, + } + } + + /// reassemble the Grid content into a string + /// trimming unneeded whitespace to the right for every line + pub fn to_string(&self) -> String { + let mut buff = String::new(); + let mut do_ln = false; + for row in self.index.iter() { + let mut line = String::new(); + if do_ln { + //first line don't do \n + buff.push('\n'); + } else { + do_ln = true; + } + for cell in row { + if cell == "\0" { +; //easy make over the full-width hack of the string + } else { + line.push_str(cell); + } + } + buff.push_str(&line); + } + buff + } + + pub fn rows(&self) -> usize { + self.index.len() + } + + /// get the maximum row len + pub fn columns(&self) -> usize { + self.index.iter().map(|r| r.len()).max().unwrap_or(0) + } + + /// get a character at this location + /// widths are computed since there are + /// characters that spans 2 columns + /// and characters that has 0 width + /// + pub fn get(&self, loc: &Loc) -> Option<&String> { + match self.index.get(loc.y as usize) { + Some(row) => row.get(loc.x as usize), + None => None, + } + } + + /// put a text into this location + /// prepare the grid for this location first + pub fn put(&mut self, loc: &Loc, s: &str) { + let new_loc = self.accomodate(loc); + if let Some(row) = self.index.get_mut(new_loc.y as usize) { + if let Some(cell) = row.get_mut(new_ |