summaryrefslogtreecommitdiffstats
path: root/svgbob/src/element.rs
diff options
context:
space:
mode:
Diffstat (limited to 'svgbob/src/element.rs')
-rw-r--r--svgbob/src/element.rs312
1 files changed, 312 insertions, 0 deletions
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)
+ }
+ }
+ }
+}