From d053c331bf2c1dd76c4def1825dbee55e018619c Mon Sep 17 00:00:00 2001 From: Jovansonlee Cesar Date: Sun, 29 Jul 2018 16:29:24 +0800 Subject: refactored to modularized components to its corresponding file --- svgbob/src/block.rs | 32 ++ svgbob/src/box_drawing.rs | 6 +- svgbob/src/element.rs | 312 +++++++++++ svgbob/src/focus_char.rs | 354 +++++++++++++ svgbob/src/fragments.rs | 49 +- svgbob/src/grid.rs | 820 +++++++++++++++++++++++++++++ svgbob/src/lib.rs | 1275 +-------------------------------------------- svgbob/src/loc.rs | 122 +++++ svgbob/src/loc_block.rs | 250 +++++++++ svgbob/src/location.rs | 162 ++++++ svgbob/src/optimizer.rs | 14 +- svgbob/src/patterns.rs | 697 ------------------------- svgbob/src/point.rs | 37 ++ svgbob/src/point_block.rs | 50 ++ svgbob/src/properties.rs | 196 +------ svgbob/src/settings.rs | 99 ++++ svgbob/src/svg_element.rs | 28 + 17 files changed, 2296 insertions(+), 2207 deletions(-) create mode 100644 svgbob/src/block.rs create mode 100644 svgbob/src/element.rs create mode 100644 svgbob/src/focus_char.rs create mode 100644 svgbob/src/grid.rs create mode 100644 svgbob/src/loc.rs create mode 100644 svgbob/src/loc_block.rs create mode 100644 svgbob/src/location.rs delete mode 100644 svgbob/src/patterns.rs create mode 100644 svgbob/src/point.rs create mode 100644 svgbob/src/point_block.rs create mode 100644 svgbob/src/settings.rs create mode 100644 svgbob/src/svg_element.rs 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, Vec) { 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), + Arc(Point, Point, f32, ArcFlag, bool, Stroke, Vec), + 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 { + 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/focus_char.rs b/svgbob/src/focus_char.rs new file mode 100644 index 0000000..cfadb2b --- /dev/null +++ b/svgbob/src/focus_char.rs @@ -0,0 +1,354 @@ +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 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> { + loc: Loc, + ch: char, + grid: &'g Grid, +} + +impl<'g> FocusChar<'g> { + pub fn new(loc: &Loc, grid: &'g Grid) -> Self { + let s: Option<&String> = grid.get(loc); + // if there is a text in this location, take the first char as the focus char + let ch = match s { + Some(s) => s.chars().nth(0).unwrap_or('\0'), + None => '\0', + }; + + Self { + loc: loc.clone(), + ch: ch, + grid: grid, + } + } + + /// get the text of self char, including complex block + /// concatenated with multiple strings in utf8 encoding + fn text(&self) -> String { + match self.grid.get(&self.loc) { + Some(s) => s.to_owned(), + None => "".to_string(), + } + } + + /// get the focus char at this location + fn get(&self, loc: &Loc) -> Self { + FocusChar::new(loc, self.grid) + } + + /// if the character matches given argument + pub fn is(&self, ch: char) -> bool { + self.ch.is(ch) + } + + /// if character is any character in the string + pub fn any(&self, s: &str) -> bool { + self.ch.any(s) + } + + fn used_as_text(&self) -> bool { + if self.is_text_surrounded() { + // not if it can strongly connect to 4 directions + if self.can_strongly_connect(&Block::O) || self.can_strongly_connect(&Block::K) + || self.can_strongly_connect(&Block::C) + || self.can_strongly_connect(&Block::W) + || self.can_strongly_connect(&Block::U) + || self.can_strongly_connect(&Block::Y) + { + false + } else { + true + } + } else { + false + } + } + + fn is_text_surrounded(&self) -> bool { + self.left().ch.is_alphanumeric() || self.right().ch.is_alphanumeric() + } + + pub fn is_null(&self) -> bool { + self.is('\0') + } + + pub fn is_blank(&self) -> bool { + self.is_null() || self.is(' ') + } + + /////////////////////////////////// + // + // can strongly or mediumly connect + // + /////////////////////////////////// + + fn can_pass_medium_connect(&self, block: &Block) -> bool { + self.can_strongly_connect(block) || self.can_medium_connect(block) + } + + fn can_pass_weakly_connect(&self, block: &Block) -> bool { + self.can_strongly_connect(block) || self.can_medium_connect(block) + || self.can_weakly_connect(block) + } + + pub fn can_strongly_connect(&self, block: &Block) -> bool { + self.ch.can_connect(&Strong, block) + } + + fn can_medium_connect(&self, block: &Block) -> bool { + self.ch.can_connect(&Medium, block) + } + + fn can_weakly_connect(&self, block: &Block) -> bool { + self.ch.can_connect(&Weak, block) + } + + fn point(&self, pb: &PointBlock) -> Point { + self.loc_block().to_point(pb) + } + + fn loc_block(&self) -> LocBlock { + LocBlock { + loc: self.loc.to_owned(), + settings: self.get_settings(), + } + } + + fn to_element(&self, frag: Fragment) -> Element { + let unit_x = self.loc_block().unit_x(); + match frag { + Fragment::Line(p1, p2) => line(&self.point(&p1), &self.point(&p2)), + Fragment::ArrowLine(p1, p2) => arrow_line(&self.point(&p1), &self.point(&p2)), + + Fragment::StartArrowLine(p1, p2) => start_arrow_line(&self.point(&p1), &self.point(&p2)), + + Fragment::Arc(p1, p2, m) => arc(&self.point(&p1), &self.point(&p2), m as f32 * unit_x), + + Fragment::OpenCircle(c, m) => open_circle(&self.point(&c), m as f32 * unit_x), + + Fragment::SolidCircle(c, m) => solid_circle(&self.point(&c), m as f32 * unit_x), + Fragment::Text(s) => text(&self.loc, &s), + } + } + + /// TODO: optimize this by getting accumulating the location + /// and convert it into loc in 1 call + /// then get the focus char at this location; + fn from_location(&self, location: &Location) -> FocusChar<'g> { + let loc = self.loc.from_location(location); + self.get(&loc) + } + + fn can_block_pass_connect(&self, block: &Block, signal: &Signal) -> bool { + match *signal { + Strong => self.can_strongly_connect(block), + Medium => self.can_pass_medium_connect(block), + Weak => self.can_pass_weakly_connect(block), + } + } + + pub fn get_elements(&self) -> (Vec, Vec) { + let (fragments, consumed_location) = self.get_fragments(); + let mut elements: Vec = fragments + .into_iter() + .map(|frag| self.to_element(frag)) + .collect(); + let consumed_loc: Vec = consumed_location + .into_iter() + .map(|location| self.loc.from_location(&location)) + .collect(); + elements.sort(); + elements.dedup(); + (elements, consumed_loc) + } + + fn is_satisfied(&self, can: &Can) -> bool { + match *can { + ConnectTo(ref cond_block, ref signal) => { + self.can_block_pass_connect(&cond_block, signal) + } + Is(char) => self.is(char), + IsStrongAll(ref blocks) => blocks.iter().all(|b| self.is_strong_block(&b)), + } + } + + /// check to see if this specified block for this focused + /// char is intensified to be strong + fn is_intensified(&self, arg_block: &Block) -> bool { + let character: Option = self.ch.get_characteristic(); + if let Some(character) = character { + character.intensify.iter().any(|&(ref block, ref cond)| { + let fc = self.from_location(&cond.loc); + block == arg_block && fc.is_satisfied(&cond.can) + }) + } else { + false + } + } + + fn can_be_strong_block(&self, block: &Block) -> bool { + if self.is_strong_block(block) { + true + } else if self.is_intensified(block) { + true + } else { + false + } + } + + fn is_strong_block(&self, block: &Block) -> bool { + let character: Option = self.ch.get_characteristic(); + if let Some(character) = character { + if character.is_strong_block(block) { + return true; + } + } + false + } + + fn get_fragments(&self) -> (Vec, Vec) { + let character: Option = self.ch.get_characteristic(); + let mut elm: Vec = vec![]; + let mut consumed: Vec = vec![]; + + let mut matched_intended = false; + + let enable_intended_behavior = true; + let enable_default_properties = true; + + if let Some(character) = character { + // intended behaviors when signals are strong + // after applying the intensifiers + // do only when enhancements is not matched + if enable_intended_behavior { + for &(ref blocks, ref fragments) in &character.intended_behavior { + let meet = blocks.iter().all(|ref b| self.can_be_strong_block(&b)); + if meet && !self.used_as_text() { + elm.extend(fragments.clone()); + matched_intended = true; + } + } + } + + // default behaviors + // add only when signal is strong + // or the signal has been intensified to strong + let mut matched = false; + if enable_default_properties { + if !matched_intended { + for &(ref block, ref signal, ref fragments) in &character.properties { + // draw when a strong block and not used as text + if self.is_strong_block(&block) && !self.used_as_text() { + elm.extend(fragments.clone()); + matched = true; + } + // draw when used as text but intensified + else if self.is_intensified(&block) && !self.used_as_text() { + elm.extend(fragments.clone()); + matched = true; + } + } + } + } + if !matched && !matched_intended + && !self.is_blank() + { + elm.push(Text(self.text())); + } + } else { + if !self.is_blank() { + // This is to disconnect words + elm.push(Text(self.text())); + } + } + elm.sort(); + elm.dedup(); + consumed.sort(); + consumed.dedup(); + (elm, consumed) + } + + fn get_settings(&self) -> Settings { + self.grid.settings.clone() + } + + pub fn top(&self) -> Self { + self.get(&self.loc.top()) + } + + pub fn bottom(&self) -> Self { + self.get(&self.loc.bottom()) + } + + pub fn left(&self) -> Self { + self.get(&self.loc.left()) + } + + pub fn in_left(&self, n: usize) -> Self { + let mut fc = self.left(); + for _i in 0..n - 1 { + fc = fc.left(); + } + fc + } + pub fn in_right(&self, n: usize) -> Self { + let mut fc = self.right(); + for _i in 0..n - 1 { + fc = fc.right(); + } + fc + } + + pub fn in_top(&self, n: usize) -> Self { + let mut fc = self.top(); + for _i in 0..n - 1 { + fc = fc.top(); + } + fc + } + pub fn in_bottom(&self, n: usize) -> Self { + let mut fc = self.bottom(); + for _i in 0..n - 1 { + fc = fc.bottom(); + } + fc + } + + pub fn right(&self) -> Self { + self.get(&self.loc.right()) + } + + pub fn top_left(&self) -> Self { + self.get(&self.loc.top_left()) + } + + pub fn top_right(&self) -> Self { + self.get(&self.loc.top_right()) + } + + pub fn bottom_left(&self) -> Self { + self.get(&self.loc.bottom_left()) + } + + pub fn bottom_right(&self) -> Self { + self.get(&self.loc.bottom_right()) + } +} 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>, + 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::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 = 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 = 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_loc.x as usize) { + *cell = s.to_owned(); + } else { + panic!("no cell on this {}", new_loc.x); + } + } else { + panic!("no row on this {}", new_loc.y); + } + } + + /// insert a new line to at this point + pub fn insert_line(&mut self, line: usize) { + self.accomodate(&Loc::new(0, line as i32)); + self.index.insert(line, vec![]); + } + + /// join this line to the previous line + pub fn join_line(&mut self, line: usize) { + let mut row = self.index.remove(line); + self.index + .get_mut(line - 1) + .map(|prev| prev.append(&mut row)); + } + + /// get the line len at this index + pub fn get_line_len(&self, line: usize) -> Option { + self.index.get(line).map(|r| r.len()) + } + + /// prepare the grid to accomodate this loc + /// if loc.y < 0 => insert abs(loc.y) rows at element 0 to self.index + /// if loc.y > row.y => append (loc.y-row.y) rows to the self.x + /// if loc.x < 0 => insert abs(loc.x) columns at element 0, to all rows + /// if loc.x > row.x => append (loc.x-row.x) elements to the row + /// returns the corrected location, -1,-1 will be on 0,0 + pub fn accomodate(&mut self, loc: &Loc) -> Loc { + let mut new_loc = loc.clone(); + if loc.y < 0 { + let lack_row = (0 - loc.y) as usize; // 0 - -5 = 5 + for _ in 0..lack_row { + self.index.insert(0, vec![]); + } + new_loc.y = 0; + } + if loc.x < 0 { + let lack_cell = (0 - loc.x) as usize; + let add_cells: String = " ".repeat(lack_cell); + // insert add_cells to all rows at 0 + for row in self.index.iter_mut() { + row.insert(0, add_cells.clone()); + } + new_loc.x = 0; + } + + // check again using the new location adjusted + // for missing cells + if new_loc.y >= self.index.len() as i32 { + let lack_row = new_loc.y - self.index.len() as i32 + 1; + let mut add_rows: Vec> = Vec::with_capacity(lack_row as usize); + for _ in 0..lack_row { + add_rows.push(vec![]); + } + self.index.append(&mut add_rows); + } + // IMPORTANT NOTE: + // using new_loc as adjusted when -negative + // is important since the priliminary rows inserted + // are just empty rows + if let Some(row) = self.index.get_mut(new_loc.y as usize) { + if new_loc.x >= row.len() as i32 { + let lack_cell = new_loc.x - row.len() as i32 + 1; + let mut add_cells: Vec = Vec::with_capacity(lack_cell as usize); + for _ in 0..lack_cell { + add_cells.push(" ".to_string()); // use space for empty cells + } + (&mut *row).append(&mut add_cells); + } + } + new_loc + } + + /// Vector arranged in row x col + pub fn get_text_in_range(&self, loc1: &Loc, loc2: &Loc) -> Vec>> { + let x1 = std::cmp::min(loc1.x, loc2.x); + let y1 = std::cmp::min(loc1.y, loc2.y); + let x2 = std::cmp::max(loc2.x, loc1.x); + let y2 = std::cmp::max(loc2.y, loc1.y); + let mut text = Vec::with_capacity((y2 - y1 + 1) as usize); + for j in y1..y2 + 1 { + let mut row = Vec::with_capacity((x2 - x1 + 1) as usize); + for i in x1..x2 + 1 { + let loc = Loc::new(i, j); + let cell = self.get(&loc); + row.push(cell); + } + text.push(row); + } + text + } + + pub fn get_all_text(&self) -> Vec>> { + let loc1 = Loc::new(0, 0); + let loc2 = Loc::new((self.columns() - 1) as i32, (self.rows() - 1) as i32); + self.get_text_in_range(&loc1, &loc2) + } + + /// get the focus char at this location + pub fn get_focuschar(&self, loc: &Loc) -> FocusChar { + FocusChar::new(&loc, self) + } + + /// vector of each elements arranged in rows x columns + /// returns all the elements and the consumed location + fn get_all_elements(&self) -> (Vec>>, Vec) { + let mut rows: Vec>> = Vec::with_capacity(self.index.len()); + let mut all_consumed_loc: Vec = vec![]; + let mut y = 0; + for line in &self.index { + let mut x = 0; + let mut row: Vec> = Vec::with_capacity(line.len()); + for _ in line { + let loc = Loc::new(x, y); + let focus_char = self.get_focuschar(&loc); + let (cell_elements, consumed_loc) = focus_char.get_elements(); + all_consumed_loc.extend(consumed_loc); + row.push(cell_elements); + x += 1; + } + rows.push(row); + y += 1; + } + (rows, all_consumed_loc) + } + + fn get_escaped_text_elements(&self) -> Vec { + self.text_elm + .iter() + .map(|&(x, y, ref text)| Element::Text(Loc::new(x as i32, y as i32), text.to_owned())) + .collect() + } + + /// each component has its relative location retain + /// use this info for optimizing svg by checking closest neigbor + fn get_svg_nodes(&self) -> Vec { + let mut nodes = vec![]; + let (mut elements, consumed_loc) = self.get_all_elements(); + let text_elm = self.get_escaped_text_elements(); + elements.push(vec![text_elm]); + let input = if self.settings.optimize { + let mut optimizer = Optimizer::new(elements, consumed_loc); + let mut optimized_elements = optimizer.optimize(&self.settings); + optimized_elements + } else { + // flatten Vec>> to Vec + elements + .into_iter() + .flat_map(|elm| elm.into_iter().flat_map(|e2| e2)) + .collect() + }; + for elem in input { + let element: SvgElement = elem.to_svg(&self.settings); + nodes.push(element); + } + nodes + } + + pub fn get_svg_nodes_only(&self) -> String { + let nodes = self.get_svg_nodes(); + let mut svg = String::new(); + for node in nodes { + match node { + SvgElement::Circle(circle) => { + svg.push_str(&circle.to_string()); + } + SvgElement::Line(line) => { + svg.push_str(&line.to_string()); + } + SvgElement::Path(path) => { + svg.push_str(&path.to_string()); + } + SvgElement::Text(text) => { + svg.push_str(&text.to_string()); + } + } + } + svg + } + + pub fn get_size(&self) -> (f32, f32) { + let width = self.settings.text_width * self.columns() as f32; + let height = self.settings.text_height * self.rows() as f32; + (width, height) + } + + /// get the generated svg according to the settings specified + pub fn get_svg(&self) -> SVG { + let nodes = self.get_svg_nodes(); + let (width, height) = self.get_size(); + let mut svg = SVG::new(); + + if let Some(ref id) = self.settings.id { + svg.assign("id", id.to_owned()); + } + if let Some(ref class) = self.settings.class { + svg.assign("class", class.to_owned()); + } + svg.assign("font-size", self.settings.font_size); + svg.assign("font-family", self.settings.font_family.to_owned()); + svg.assign("width", width); + svg.assign("height", height); + + + svg.append(get_defs()); + svg.append(get_styles(&self.settings)); + + + let rect = SvgRect::new() + .set("x", 0) + .set("y", 0) + .set("fill", self.settings.background_color.to_string()) + .set("width", width) + .set("height", height); + svg.append(rect); + + for node in nodes { + match node { + SvgElement::Circle(circle) => { + svg.append(circle); + } + SvgElement::Line(line) => { + svg.append(line); + } + SvgElement::Path(path) => { + svg.append(path); + } + SvgElement::Text(text) => { + svg.append(text); + } + } + } + svg + } +} + +fn get_defs() -> Definitions { + let mut defs = Definitions::new(); + defs.append(arrow_marker()); + defs +} + +fn get_styles(settings: &Settings) -> Style { + let style = format!( + r#" + line, path {{ + stroke: {stroke_color}; + stroke-width: {stroke_width}; + stroke-opacity: 1; + fill-opacity: 1; + stroke-linecap: round; + stroke-linejoin: miter; + }} + circle {{ + stroke: black; + stroke-width: {stroke_width}; + stroke-opacity: 1; + fill-opacity: 1; + stroke-linecap: round; + stroke-linejoin: miter; + }} + circle.solid {{ + fill:{stroke_color}; + }} + circle.open {{ + fill:{background_color}; + }} + tspan.head{{ + fill: none; + stroke: none; + }} + "#, + stroke_width = settings.stroke_width, + stroke_color = &settings.stroke_color, + background_color = &settings.background_color + ); + Style::new(style) +} + +fn arrow_marker() -> Marker { + let mut marker = Marker::new() + .set("id", "triangle") + .set("viewBox", "0 0 50 20") + .set("refX", 15) + .set("refY", 10) + .set("markerUnits", "strokeWidth") + .set("markerWidth", 10) + .set("markerHeight", 10) + .set("orient", "auto"); + + let path = SvgPath::new().set("d", "M 0 0 L 30 10 L 0 20 z"); + marker.append(path); + marker +} + +//copied from https://github.com/rust-lang/rust/blob/master/src/librustdoc/html/escape.rs +//just adding for \0 +pub fn svg_escape(arg: &str) -> String { + use std::fmt; + + /// Wrapper struct which will emit the HTML-escaped version of the contained + /// string when passed to a format string. + pub struct Escape<'a>(pub &'a str); + + impl<'a> fmt::Display for Escape<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + // Because the internet is always right, turns out there's not that many + // characters to escape: http://stackoverflow.com/questions/7381974 + let Escape(s) = *self; + let pile_o_bits = s; + let mut last = 0; + for (i, ch) in s.bytes().enumerate() { + match ch as char { + '<' | '>' | '&' | '\'' | '"' | '\0' => { + fmt.write_str(&pile_o_bits[last..i])?; + let s = match ch as char { + '>' => ">", + '<' => "<", + '&' => "&", + '\'' => "'", + '"' => """, + '\0' => "", + _ => unreachable!(), + }; + fmt.write_str(s)?; + last = i + 1; + } + _ => {} + } + } + + if last < s.len() { + fmt.write_str(&pile_o_bits[last..])?; + } + Ok(()) + } + }; + let escaped = Escape(arg); + format!("{}", escaped) +} + +fn exclude_escaped_text(line: &str) -> (String, Vec<(usize, String)>) { + let mut input = TextInput::new(line); + let parsed = line_parse().parse(&mut input); + let mut buffer = String::new(); + let mut text_elm: Vec<(usize, String)> = vec![]; + if let Ok(parsed) = parsed { + let mut index = 0; + if !parsed.is_empty() { + for (start, end) in parsed { + let escaped = &line[start + 1..end]; + let recons = &line[index..start]; + text_elm.push((start, escaped.to_string())); + buffer.push_str(recons); + buffer.push_str(&" ".repeat(end + 1 - start)); + index = end + 1; + } + buffer.push_str(&line[index..line.len()]); + } else { + buffer.push_str(line); + } + } + (buffer, text_elm) +} + +#[test] +fn test_escaped_string() { + let input3 = r#"The "qu/i/ck" brown "fox\"s" jumps over the lazy "do|g""#; + let mut raw3 = TextInput::new(input3); + let output3 = line_parse().parse(&mut raw3); + println!("output3: {:?}", output3); + //assert_eq!(Ok(vec![(4, 12), (20, 27), (49, 54)]), output3); + let mut matches = vec![]; + let mut recons = String::new(); + let mut text_elm: Vec<(usize, String)> = vec![]; + let mut index = 0; + if let Ok(output) = output3 { + for (start, end) in output { + println!("matches: {}", &input3[start..end + 1]); + matches.push(input3[start..end + 1].to_string()); + let slice = &input3[index..start]; + recons.push_str(slice); + recons.push_str(&" ".repeat(end + 1 - start)); + text_elm.push((start, input3[start + 1..end].to_string())); + index = end + 1; + } + } + println!("input3: {}", input3); + println!("recons: {}", recons); + println!("escaped: {:?}", text_elm); + assert_eq!(vec![r#""qu/i/ck""#, r#""fox\"s""#, r#""do|g""#], matches); + assert_eq!(input3.len(), recons.len()); +} + +#[test] +fn test_escaped_multiline_string() { + let input3 = r#"The "qu/i/ck brown fox \njumps over the lazy do|g""#; + let mut raw3 = TextInput::new(input3); + let output3 = line_parse().parse(&mut raw3); + println!("output3: {:?}", output3); + assert_eq!(Ok(vec![(4, 49)]), output3); + let mut matches = vec![]; + let mut recons = String::new(); + let mut text_elm: Vec<(usize, String)> = vec![]; + let mut index = 0; + if let Ok(output) = output3 { + for (start, end) in output { + println!("matches: {}", &input3[start..end + 1]); + matches.push(input3[start..end + 1].to_string()); + let slice = &input3[index..start]; + recons.push_str(slice); + recons.push_str(&" ".repeat(end + 1 - start)); + text_elm.push((start, input3[start + 1..end].to_string())); + index = end + 1; + } + } + println!("input3: {}", input3); + println!("recons: {}", recons); + println!("escaped: {:?}", text_elm); + assert_eq!( + vec![r#""qu/i/ck brown fox \njumps over the lazy do|g""#], + matches + ); + assert_eq!(input3.len(), recons.len()); +} + +fn escape_string() -> pom::parser::Parser<'static, char, (usize, usize)> { + let escape_sequence = sym('\\') * sym('"'); //escape sequence \" + let char_string = escape_sequence | none_of("\""); + let escaped_string_end = sym('"') * char_string.repeat(0..).pos() - sym('"'); + none_of("\"").repeat(0..).pos() + escaped_string_end - none_of("\"").repeat(0..).discard() +} + +fn line_parse() -> pom::parser::Parser<'static, char, Vec<(usize, usize)>> { + escape_string().repeat(0..) +} + +#[cfg(test)] +mod test_lib { + use super::Grid; + use super::Loc; + use super::Settings; + + #[test] + fn test_grid() { + let g = Grid::from_str("a统öo͡͡͡", &Settings::compact()); + println!("{:?}", g.index); + assert_eq!( + g.index, + vec![vec![ + "a".to_string(), + "统".to_string(), + "\u{0}".to_string(), + "ö".to_string(), + "o͡͡͡".to_string(), + ]] + ); + } + + #[test] + fn test_text_in_range() { + let txt = " +1234567890 +abcdefghij +klmnopqrst +uvwxyz1234 +567890abcd + "; + let g = Grid::from_str(txt, &Settings::compact()); + let loc = Loc::new(4, 3); // at 'o' + let (loc1, loc2) = loc.get_range(2, 1); + let text = g.get_text_in_range(&loc1, &loc2); + assert_eq!( + text, + vec![ + vec![ + Some(&"c".to_string()), + Some(&"d".to_string()), + Some(&"e".to_string()), + Some(&"f".to_string()), + Some(&"g".to_string()), + ], + vec![ + Some(&"m".to_string()), + Some(&"n".to_string()), + Some(&"o".to_string()), + Some(&"p".to_string()), + Some(&"q".to_string()), + ], + vec![ + Some(&"w".to_string()), + Some(&"x".to_string()), + Some(&"y".to_string()), + Some(&"z".to_string()), + Some(&"1".to_string()), + ], + ] + ); + } + + #[test] + fn test_to_string() { + let txt = "The quick brown fox +jumps over + the lazy dog. + ]"; + let g = Grid::from_str(txt, &Settings::compact()); + assert_eq!(txt, &*g.to_string()); + } + #[test] + fn test_to_trimmed_string() { + let txt = " + +The quick brown fox + +jumps over + + the lazy dog. + + "; + let g = Grid::from_str(txt, &Settings::compact()); + assert_eq!(txt, &*g.to_string()); + } + + #[test] + fn test_insert_text() { + let txt = "1234567890 +abcdefghij +klmnopqrst +uvwxyz1234 +567890abcd"; + let mut g = Grid::from_str(txt, &Settings::compact()); + g.put(&Loc::new(-1, -1), "-"); + let expected = "- + 1234567890 + abcdefghij + klmnopqrst + uvwxyz1234 + 567890abcd"; + assert_eq!(expected, &*g.to_string()); + } + + #[test] + fn test_insert_text_after() { + let txt = "\ +1234567890 +abcdefghij +klmnopqrst +uvwxyz1234 +567890abcd\ +"; + let mut g = Grid::from_str(txt, &Settings::compact()); + g.put(&Loc::new(11, 5), "1"); + let expected = "\ +1234567890 +abcdefghij +klmnopqrst +uvwxyz1234 +567890abcd + 1\ +"; + assert_eq!(expected, &*g.to_string()); + } + + #[test] + fn test_slash0_space() { + let txt = "件hello统"; + let g = Grid::from_str(txt, &Settings::compact()); + let s = g.to_string(); + assert_eq!(txt, s); + } + +} + +#[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/lib.rs b/svgbob/src/lib.rs index a093075..19b7d0e 100644 --- a/svgbob/src/lib.rs +++ b/svgbob/src/lib.rs @@ -30,6 +30,7 @@ //! //! //#![deny(warnings)] +#![feature(extern_prelude)] extern crate pom; #[cfg(test)] #[macro_use] @@ -37,39 +38,25 @@ extern crate pretty_assertions; extern crate svg; extern crate unicode_width; -use pom::parser::*; -use pom::TextInput; - -use self::Feature::{Arrow,ArrowStart}; -use self::Feature::Circle; -use self::Stroke::Dashed; -use self::Stroke::Solid; -use fragments::Direction::{Bottom, BottomLeft, BottomRight, Left, Right, Top, TopLeft, TopRight}; -use optimizer::Optimizer; -use patterns::FocusChar; -use properties::Location; -use svg::node::element::Circle as SvgCircle; -use svg::node::element::Definitions; -use svg::node::element::Line as SvgLine; -use svg::node::element::Marker; -use svg::node::element::Path as SvgPath; -use svg::node::element::Rectangle as SvgRect; -use svg::node::element::Style; -use svg::node::element::Text as SvgText; +use grid::Grid; +use settings::Settings; use svg::node::element::SVG; -use svg::Node; -use unicode_width::UnicodeWidthChar; -use unicode_width::UnicodeWidthStr; -use std::cmp::Ordering; - -use ArcFlag::{Major, Minor}; mod optimizer; -mod patterns; - mod box_drawing; mod fragments; mod properties; +mod settings; +mod svg_element; +mod element; +mod grid; +mod point; +mod location; +mod loc; +mod point_block; +mod block; +mod focus_char; +mod loc_block; /// generate an SVG from the ascii text input /// @@ -85,1247 +72,13 @@ pub fn to_svg(input: &str) -> SVG { Grid::from_str(&input, &Settings::default()).get_svg() } -/// optimization options: -/// 1. None -> Fastest, but not correct looking (paths and text are not reduced) -/// 2. Fast -> Fast and correct looking (text are reduced) -/// 3. All -> Correct looking but slow (paths and text are reduced) -#[derive(Debug, Clone)] -pub struct Settings { - pub text_width: f32, - pub text_height: f32, - /// do optimization? if false then every piece are disconnected - optimize: bool, - /// if optmization is enabled, - /// true means all reduceable paths will be in 1 path definition - compact_path: bool, - /// the svg class of the generated svg - pub class: Option, - /// the id of the generated svg - pub id: Option, - /// the font family used for text (default: arial) - pub font_family: String, - /// the font size used for text (default: 14) - pub font_size: f32, - /// stroke width for all lines (default: 2.0) - pub stroke_width: f32, - /// stroke color, default black - pub stroke_color: String, - /// background color: default white - pub background_color: String -} - -impl Settings { - pub fn set_size(&mut self, text_width: f32, text_height: f32) { - self.text_width = text_width; - self.text_height = text_height; - } - - pub fn scale(&mut self, scale: f32) { - self.text_width = self.text_width * scale; - self.text_height = self.text_height * scale; - self.font_size = self.font_size * scale; - self.stroke_width = self.stroke_width * scale; - } - - pub fn no_optimization() -> Settings { - let mut settings = Settings::default(); - settings.optimize = false; - settings.compact_path = false; - settings - } - - pub fn separate_lines() -> Settings { - let mut settings = Settings::default(); - settings.optimize = true; - settings.compact_path = false; - settings - } - - pub fn compact() -> Settings { - let mut settings = Settings::default(); - settings.optimize = true; - settings.compact_path = true; - settings - } - - fn set_id(&mut self, id: String) { - self.id = Some(id); - } - - fn set_class(&mut self, class: String) { - self.class = Some(class); - } - - pub fn set_selector(&mut self, id: Option, class: Option) { - if let Some(id) = id { - self.set_id(id); - } - if let Some(class) = class { - self.set_class(class); - } - } -} - -impl Default for Settings { - fn default() -> Settings { - Settings { - text_width: 8.0, - text_height: 16.0, - optimize: true, - compact_path: true, - class: Some("bob".to_string()), - id: None, - font_family: "arial".to_string(), - font_size: 14.0, - stroke_width: 2.0, - stroke_color: "black".into(), - background_color: "white".into(), - } - } -} - -enum SvgElement { - Circle(SvgCircle), - Line(SvgLine), - Path(SvgPath), - Text(SvgText), -} - -impl std::fmt::Debug for SvgElement { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - match *self { - SvgElement::Circle(ref c) => writeln!(fmt, "{}", c.to_string()), - SvgElement::Line(ref l) => writeln!(fmt, "{}", l.to_string()), - SvgElement::Path(ref p) => writeln!(fmt, "{}", p.to_string()), - SvgElement::Text(ref t) => writeln!(fmt, "{}", t.to_string()), - } - } -} - -#[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, PartialOrd, PartialEq, Clone)] -pub struct Point { - x: f32, - y: f32, -} -impl Ord for Point{ - fn cmp(&self, other:&Point) -> Ordering{ - if let Some(order) = self.partial_cmp(other){ - return order - } - Ordering::Less - } -} -impl Eq for Point{ -} - -impl Point { - fn new(x: f32, y: f32) -> Point { - Point { x: x, y: y } - } - fn adjust(&mut self, x: f32, y: f32) { - self.x += x; - self.y += y; - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq)] -pub struct Loc { - pub x: i32, - pub y: i32, -} - -impl Ord for Loc{ - fn cmp(&self, other:&Loc) -> Ordering{ - if let Some(order) = self.partial_cmp(other){ - return order - } - Ordering::Less - } -} - -impl Loc { - pub fn new(x: i32, y: i32) -> Loc { - Loc { x: x, y: y } - } - - pub fn from_location(&self, location: &Location) -> Loc { - let mut loc = self.clone(); - for &(ref direction, step) in &location.0 { - for _ in 0..step { - match *direction { - TopLeft => { - loc = loc.top().left(); - } - Top => { - loc = loc.top(); - } - TopRight => { - loc = loc.top().right(); - } - Left => { - loc = loc.left(); - } - Right => { - loc = loc.right(); - } - BottomLeft => { - loc = loc.bottom().left(); - } - Bottom => { - loc = loc.bottom(); - } - BottomRight => { - loc = loc.bottom().right(); - } - }; - } - } - loc - } - - pub fn top(&self) -> Loc { - Loc { - x: self.x, - y: self.y - 1, - } - } - pub fn left(&self) -> Loc { - Loc { - x: self.x - 1, - y: self.y, - } - } - pub fn bottom(&self) -> Loc { - Loc { - x: self.x, - y: self.y + 1, - } - } - pub fn right(&self) -> Loc { - Loc { - x: self.x + 1, - y: self.y, - } - } - - pub fn top_left(&self) -> Loc { - Loc { - x: self.x - 1, - y: self.y - 1, - } - } - - pub fn top_right(&self) -> Loc { - Loc { - x: self.x + 1, - y: self.y - 1, - } - } - - pub fn bottom_left(&self) -> Loc { - Loc { - x: self.x - 1, - y: self.y + 1, - } - } - - pub fn bottom_right(&self) -> Loc { - Loc { - x: self.x + 1, - y: self.y + 1, - } - } - - /// make a lower and upper bound loc with - /// ry units top + ry units bottom - /// rx units left + rx units right - pub fn get_range(&self, rx: i32, ry: i32) -> (Loc, Loc) { - let loc1 = Loc::new(self.x - rx, self.y - ry); - let loc2 = Loc::new(self.x + rx, self.y + ry); - (loc1, loc2) - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub enum ArcFlag { - Major, - Minor, -} - -#[derive(Debug, Clone, PartialEq, PartialOrd )] -pub enum Element { - Circle(Point, f32, String), - Line(Point, Point, Stroke, Vec), - Arc(Point, Point, f32, ArcFlag, bool, Stroke, Vec), - Text(Loc, String), - Path(Point, Point, String, Stroke), -} - -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_st