#[macro_use]
extern crate log;
use hex::FromHex;
use regex::{Regex, RegexBuilder};
use std::collections::HashMap;
use svg::node::element::path::{Command, Data, Position};
use svg::node::element::tag::Type;
use svg::parser::{Event, Parser};
pub mod layers;
pub mod tikz;
pub mod tiled;
pub mod raw;
#[derive(Debug, Clone, Copy)]
pub enum Marker {
ArrowStart,
ArrowEnd,
}
impl Marker {
fn from_inkscape_id(s: &str) -> Option<Self> {
let arrow_start = RegexBuilder::new("^arrow.*start$")
.case_insensitive(true)
.build()
.unwrap();
let arrow_end = RegexBuilder::new("^arrow.*end$")
.case_insensitive(true)
.build()
.unwrap();
if arrow_start.is_match(s) {
Some(Marker::ArrowStart)
} else if arrow_end.is_match(s) {
Some(Marker::ArrowEnd)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct Style {
pub stroke_color: Option<[u8; 3]>,
pub fill_color: Option<[u8; 3]>,
pub opacity: f32,
pub fill_opacity: f32,
pub dash_array: Vec<f32>,
pub stroke_width: Option<f32>,
pub marker_start: Option<Marker>,
pub marker_end: Option<Marker>,
pub open: bool,
}
pub fn f(x: f32) -> f32 {
(x * 10.).round() / 100.
}
#[derive(Debug, Clone)]
pub enum PathElement {
Move {
x: f32,
y: f32,
},
Line {
x: f32,
y: f32,
},
Bezier3 {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
x: f32,
y: f32,
},
}
#[derive(Debug, Clone)]
pub enum Element {
Path {
close: bool,
path: Vec<PathElement>,
style: Style,
},
Ellipse {
cx: f32,
cy: f32,
rx: f32,
ry: f32,
style: Style,
},
Text {
x: f32,
y: f32,
text: String,
},
Rectangle {
style: Style,
x0: f32,
y0: f32,
x1: f32,
y1: f32,
},
BeginGroup {
id: String,
},
EndGroup,
}
impl Default for Style {
fn default() -> Self {
Style {
stroke_color: None,
fill_color: None,
opacity: 1.,
fill_opacity: 1.,
dash_array: Vec::new(),
stroke_width: None,
marker_start: None,
marker_end: None,
open: false,
}
}
}
impl Style {
fn parse(st: &str, markers: &HashMap<String, Marker>) -> Self {
let mut style = Style::default();
let stroke = Regex::new("^stroke *: *((#(.*))|(.*))").unwrap();
let fill = Regex::new("^fill *: *((#(.*))|(.*))").unwrap();
let opacity = Regex::new("^opacity *: *(.*)").unwrap();
let fill_opacity = Regex::new("^fill-opacity *: *(.*)").unwrap();
let dash_array = Regex::new("^stroke-dasharray *: *(.*)").unwrap();
let stroke_width = Regex::new("^stroke-width *: *(.*)").unwrap();
let marker_start = Regex::new("^marker-start *: *url\\(#(.*)\\)").unwrap();
let marker_end = Regex::new("^marker-end *: *url\\(#(.*)\\)").unwrap();
for st in st.split(";") {
let st = st.trim();
debug!("st = {:?}", st);
if let Some(m) = stroke.captures(st) {
debug!("m = {:?}", m);
if let Some(c) = m.get(3) {
let c = FromHex::from_hex(c.as_str()).unwrap();
style.stroke_color = Some(c);
} else {
style.stroke_color = None
}
}
if let Some(m) = fill.captures(st) {
if let Some(c) = m.get(3) {
let c = FromHex::from_hex(c.as_str()).unwrap();
style.fill_color = Some(c);
} else {
style.fill_color = None
}
}
if let Some(m) = opacity.captures(st) {
let c = m.get(1).unwrap();
style.opacity = c.as_str().parse().unwrap()
}
if let Some(m) = fill_opacity.captures(st) {
let c = m.get(1).unwrap();
style.fill_opacity = c.as_str().parse().unwrap()
}
if let Some(m) = marker_start.captures(st) {
let c = m.get(1).unwrap();
style.marker_start = markers.get(c.as_str()).map(|x| *x)
}
if let Some(m) = marker_end.captures(st) {
let c = m.get(1).unwrap();
style.marker_end = markers.get(c.as_str()).map(|x| *x)
}
if let Some(m) = dash_array.captures(st) {
let c = m.get(1).unwrap();
style.dash_array.clear();
for c in c.as_str().split(",") {
if let Ok(c) = c.trim().parse() {
style.dash_array.push(c)
} else {
style.dash_array.clear();
break;
}
}
}
if let Some(m) = stroke_width.captures(st) {
let c = m.get(1).unwrap();
if let Ok(c) = c.as_str().parse() {
style.stroke_width = Some(c)
}
}
}
style
}
pub fn output<W: std::io::Write>(&self, mut buf: W) -> Result<(), failure::Error> {
let mut open = false;
if let Some(ref s) = self.stroke_color {
if !open {
write!(buf, "[")?;
open = true
} else {
write!(buf, ",")?
}
write!(
buf,
"draw={{rgb,255:red,{}; green,{}; blue,{}}}",
s[0], s[1], s[2]
)?
} else {
if !open {
write!(buf, "[")?;
open = true
} else {
write!(buf, ",")?
}
write!(buf, "draw=none")?
}
if let Some(ref s) = self.fill_color {
if !open {
write!(buf, "[")?;
open = true
} else {
write!(buf, ",")?
}
write!(
buf,
"fill={{rgb,255:red,{}; green,{}; blue,{}}}",
s[0], s[1], s[2]
)?
}
if self.opacity != 1. {
if !open {
write!(buf, "[")?;
open = true
} else {
write!(buf, ",")?
}
write!(buf, "opacity={}", self.opacity)?
}
if self.fill_opacity != 1. {
if !open {
write!(buf, "[")?;
open = true
} else {
write!(buf, ",")?
}
write!(buf, "fill opacity={}", self.fill_opacity)?
}
if !self.dash_array.is_empty() {
if !open {
write!(buf, "[")?;
open = true
} else {
write!(buf, ",")?
}
write!(buf, "dashed")?
}
if self.marker_start.is_some() || self.marker_end.is_some() {
if !open {
write!(buf, "[")?;
open = true
} else {
write!(buf, ",")?
}
match self.marker_start {
Some(Marker::ArrowStart) => write!(buf, "<")?,
Some(Marker::ArrowEnd) => write!(buf, ">")?,
None => {}
}
write!(buf, "-")?;
match self.marker_end {
Some(Marker::ArrowStart) => write!(buf, "<")?,
Some(Marker::ArrowEnd) => write!(buf, ">")?,
None => {}
}
}
if let Some(w) = self.stroke_width {
if w != 1. {
if !open {
write!(buf, "[")?;
open = true
} else {
write!(buf, ",")?
}
}
if w >= 3. {
write!(buf, "very thick")?
} else if w >= 2. {
write!(buf, "thick")?
} else if w < 0.5 {
write!(buf, "thin")?
} else if w < 0.1 {
write!(buf, "very thin")?
}
}
if open {
write!(buf, "]")?;
}
Ok(())
}
pub fn reset(&mut self) {
self.opacity = 1.;
self.fill_opacity = 1.;
self.fill_color = None;
self.stroke_color = None;
self.dash_array.clear();
self.stroke_width = None;
self.marker_start = None;
self.marker_end = None;
}
}
fn bezier3(x0: f32, y0: f32, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) -> PathElement {
let vx1 = x1 - x0;
let vy1 = y1 - y0;
let vx2 = x - x2;
let vy2 = y - y2;
let vx = x - x0;
let vy = y - y0;
if ((vx1 / vx) * 100.).round() == ((vy1 / vy) * 100.).round()
&& vx1 / vx >= 0.
&& vx1 / vx <= 1.
&& ((vx2 / vx) * 100.).round() == ((vy2 / vy) * 100.).round()
&& vx2 / vx <= 0.
&& vx2 / vx >= -1.
{
PathElement::Line { x, y }
} else {
PathElement::Bezier3 {
x1,
y1,
x2,
y2,
x,
y,
}
}
}
pub trait Processor: Sized {
fn process(&mut self, _element: Element) -> Result<(), failure::Error> {
Ok(())
}
fn finish(&mut self) -> Result<(), failure::Error> {
Ok(())
}
fn tiled<'a>(&'a mut self, style: tiled::TiledStyle) -> tiled::Tiled<'a, Self> {
tiled::Tiled::new(self, style)
}
fn layers<'a>(&'a mut self, layers: &'a [&'a str]) -> layers::Layers<'a, Self> {
layers::Layers::new(layers, self)
}
}
pub fn process_file<Pa: AsRef<std::path::Path>, P: Processor>(
input: Pa,
processor: &mut P,
) -> Result<(), failure::Error> {
process(svg::open(input)?, processor)
}
pub fn process<P: Processor>(
parser: Parser,
processor: &mut P,
) -> Result<(), failure::Error> {
let mut in_text = None;
let mut markers = HashMap::new();
let mut bs = 0;
for event in parser {
match event {
Event::Tag("defs", Type::Start, _) => {
bs += 1;
}
Event::Tag("defs", Type::End, _) => {
bs -= 1;
}
Event::Tag("marker", Type::Start, attributes) => {
if let (Some(id), Some(inkscape)) =
(attributes.get("id"), attributes.get("inkscape:stockid"))
{
if let Some(marker) = Marker::from_inkscape_id(inkscape) {
markers.insert(id.to_string(), marker);
}
}
bs += 1;
}
Event::Tag("marker", Type::End, _) => {
bs -= 1;
}
_ if bs > 0 => {}
Event::Tag("path", _, attributes) => {
debug!("PATH {:?}", attributes);
let data = attributes.get("d").unwrap();
let data = Data::parse(data).unwrap();
let mut path = Vec::new();
let mut close = false;
let (mut x, mut y) = (0., 0.);
for command in data.iter() {
match command {
&Command::Move(Position::Absolute, ref b) => {
debug!("move {:?}", b);
x = b[0];
y = b[1];
path.push(PathElement::Move { x, y });
let mut i = 2;
while i < b.len() {
x = b[i];
y = b[i + 1];
path.push(PathElement::Line { x, y });
i += 2;
}
}
&Command::Move(Position::Relative, ref b) => {
debug!("move {:?}", b);
if path.is_empty() {
x = b[0];
y = b[1];
} else {
x += b[0];
y += b[1];
}
path.push(PathElement::Move { x, y });
let mut i = 2;
while i < b.len() {
x += b[i];
y += b[i + 1];
path.push(PathElement::Line { x, y });
i += 2;
}
}
&Command::Line(Position::Absolute, ref b) => {
debug!("line {:?}", b);
let mut i = 0;
while i < b.len() {
x = b[i];
y = b[i + 1];
path.push(PathElement::Line { x, y });
i += 2;
}
}
&Command::Line(Position::Relative, ref b) => {
debug!("line {:?}", b);
let mut i = 0;
while i < b.len() {
x += b[i];
y += b[i + 1];
path.push(PathElement::Line { x, y });
i += 2;
}
}
&Command::CubicCurve(Position::Absolute, ref b) => {
debug!("bezier3 {:?}", b);
path.push(bezier3(x, y, b[0], b[1], b[2], b[3], b[4], b[5]));
x = b[4];
y = b[5];
}
&Command::CubicCurve(Position::Relative, ref b) => {
debug!("line {:?}", b);
path.push(bezier3(
x,
y,
x + b[0],
y + b[1],
x + b[2],
y + b[3],
x + b[4],
y + b[5],
));
x += b[4];
y += b[5];
}
&Command::HorizontalLine(Position::Absolute, ref b) => {
debug!("hline {:?}", b);
x = b[0];
path.push(PathElement::Line { x, y });
}
&Command::HorizontalLine(Position::Relative, ref b) => {
debug!("hline {:?}", b);
x += b[0];
path.push(PathElement::Line { x, y });
}
&Command::VerticalLine(Position::Absolute, ref b) => {
debug!("vline {:?}", b);
y = b[0];
path.push(PathElement::Line { x, y });
}
&Command::VerticalLine(Position::Relative, ref b) => {
debug!("vline {:?}", b);
y += b[0];
path.push(PathElement::Line { x, y });
}
&Command::Close => {
debug!("close");
close = true;
}
e => debug!("{:?}", e),
}
}
debug!("path = {:?}", path);
processor.process(Element::Path {
style: if let Some(st) = attributes.get("style") {
Style::parse(st, &markers)
} else {
Style::default()
},
close,
path,
})?
}
Event::Tag("ellipse", _, attributes) => {
let cx: f32 = attributes.get("cx").unwrap().parse().unwrap();
let cy: f32 = attributes.get("cy").unwrap().parse().unwrap();
let rx: f32 = attributes.get("rx").unwrap().parse().unwrap();
let ry: f32 = attributes.get("ry").unwrap().parse().unwrap();
processor.process(Element::Ellipse {
cx,
cy,
rx,
ry,
style: if let Some(st) = attributes.get("style") {
Style::parse(st, &markers)
} else {
Style::default()
},
})?
}
Event::Tag("g", Type::Start, attributes) => {
debug!("{:?}", attributes);
processor.process(Element::BeginGroup {
id: attributes
.get("inkscape:label")
.map(|x| x.to_string())
.unwrap_or(String::new()),
})?
}
Event::Tag("g", Type::End, _) => {
processor.process(Element::EndGroup)?
}
Event::Tag("text", Type::Start, attributes) => {
in_text = Some(Element::Text {
x: attributes
.get("x")
.and_then(|x| x.parse().ok())
.unwrap_or(0.),
y: attributes
.get("y")
.and_then(|x| x.parse().ok())
.unwrap_or(0.),
text: String::new(),
});
}
Event::Tag("text", Type::End, _) => processor.process(in_text.take().unwrap())?,
Event::Tag("tspan", Type::Start, _) => {}
Event::Tag("tspan", Type::End, _) => {}
Event::Text(t) => {
debug!("TEXT {:?}", t);
if let Some(Element::Text { ref mut text, .. }) = in_text {
text.clear();
text.push_str(t);
}
}
Event::Tag(tag, t, attributes) => {
debug!("TAG {:?} {:?} {:?}", tag, t, attributes);
}
_ => {}
}
}
processor.finish()
}