use std::fmt::{self, Display, Formatter}; use std::fs::OpenOptions; use std::io::{self, Write}; /// An SVG document pub struct SVG { stuff: Vec>, size: (f64, f64), } #[derive(Copy, Clone)] pub struct Inches { amt: f64 } impl Display for Inches { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { self.amt.fmt(f)?; write!(f, "in") } } fn inches(amt: f64) -> Inches { Inches { amt } } fn xml_tag(w: &mut Vec, name: &str, attrs: &[(&str, &Display)]) { write!(w, "<{}", name); for &(k, v) in attrs { write!(w, " {}=\"{}\"", k, v); } writeln!(w, "/>"); } fn xml_open(w: &mut Vec, name: &str, attrs: &[(&str, &Display)]) { write!(w, "<{}", name); for &(k, v) in attrs { write!(w, " {}=\"{}\"", k, v); } writeln!(w, ">"); } fn xml_close(w: &mut Vec, name: &str) { write!(w, "", name); } /// Create a new empty SVG document of the specified width and height pub fn svg(w: f64, h: f64) -> SVG { SVG { stuff: Vec::new(), size: (w, h), } } impl SVG { /// Print this SVG document to stdout pub fn to_stdout(self) { let buf = self.to_bytes(); println!("{}", String::from_utf8(buf).unwrap()); } /// Write this SVG document to a file pub fn to_file(self, w: &mut W) -> Result<(), std::io::Error> { let buf = self.to_bytes(); w.write(&buf)?; Ok(()) } /// Print this SVG document to stdout pub fn output(self, p: &str) -> Result<(), std::io::Error> { let mut file = { let mut n = 0u32; let mut path = format!("output/{}{:05}.svg", p, n); let mut f = OpenOptions::new().write(true).create_new(true).open(&path); loop { match f { Ok(_) => break, Err(ref e) if e.kind() != io::ErrorKind::AlreadyExists => return Err(io::Error::new(e.kind(), "failed to create file")), _ => (), } n += 1; path = format!("output/{}{:05}.svg", p, n); f = OpenOptions::new().write(true).create_new(true).open(&path); } f.unwrap() }; let buf = self.to_bytes(); file.write(&buf)?; Ok(()) } pub fn to_bytes(self) -> Vec { let mut buf = Vec::new(); let (w, h) = self.size; writeln!(buf, ""); xml_open( &mut buf, "svg", &[("xmlns", &"http://www.w3.org/2000/svg"), ("version", &"1.1"), ("width", &inches(w)), ("height", &inches(h)), ("viewBox", &format!("0 0 {} {}", w, h)), ("stroke-width", &"0.0001in"), ], ); for elem in self.stuff { elem.draw_svg(&mut buf); } xml_close(&mut buf, "svg"); buf } /// Add a new drawable thing to this SVG document pub fn add(&mut self, t: T) { self.stuff.push(t.consume()) } } /// The AsSVG trait represents things which can be rendered to an SVG /// canvas. pub trait AsSVG { fn draw_svg(&self, buf: &mut Vec); // This is a bit of a hack to make sure that all of our types live // long enough: it's now on the implementer of AsSVG to box it up // and make sure that we can keep a trait object around. fn consume(self) -> Box; } /// A Line is a sequence of points. It'll be represented as a single /// dot if it has only one point, and otherwise will be drawn as a /// line between them. pub struct Line { start: (f64, f64), points: Vec<(f64, f64)>, } impl AsSVG for Line { fn draw_svg(&self, buf: &mut Vec) { if self.points.len() == 0 { // if there are no later points, then we just draw a // 'point' at the starting position, which is just a tiny // circle let (x, y) = self.start; xml_tag( buf, "circle", &[("cx", &x), ("cy", &y), ("r", &"0.01in"), ("fill", &"black"), ], ); } else { // Otherwise, we draw a path, which mean assembling this // somewhat wonky path field let path = { let mut path = Vec::new(); let (x, y) = self.start; write!(&mut path, "M{} {}", x, y); for &(x, y) in self.points.iter() { write!(&mut path, " L{} {}", x, y); } String::from_utf8(path).unwrap() }; xml_tag( buf, "path", &[("d", &path), ("stroke", &"black"), ("fill", &"none")], ); } } fn consume(self) -> Box { Box::new(self) } } /// Create a new line at the given starting point pub fn line(x: f64, y: f64) -> Line { Line::new(x, y) } impl Line { /// Create a new line at the given starting point pub fn new(x: f64, y: f64) -> Line { Line { start: (x, y), points: Vec::new(), } } /// Draw a line segment from this point to another point pub fn to(mut self, x: f64, y: f64) -> Line { self.points.push((x, y)); self } } pub struct Rect { position: (f64, f64), size: (f64, f64), } pub fn rect(position: (f64, f64), size: (f64, f64)) -> Rect { Rect { position, size } } impl AsSVG for Rect { fn draw_svg(&self, buf: &mut Vec) { let (x, y) = self.position; let (w, h) = self.size; xml_tag( buf, "rect", &[("x", &x), ("y", &y), ("width", &w), ("height", &h), ("stroke", &"black"), ("fill", &"white"), ] ); } fn consume(self) -> Box { Box::new(self) } } pub struct Circle { position: (f64, f64), radius: f64, } pub fn circle(position: (f64, f64), radius: f64) -> Circle { Circle { position, radius } } impl AsSVG for Circle { fn draw_svg(&self, buf: &mut Vec) { let (x, y) = self.position; let r = self.radius; xml_tag( buf, "circle", &[("cx", &x), ("cy", &y), ("r", &r), ("stroke", &"black"), ("fill", &"white"), ] ); } fn consume(self) -> Box { Box::new(self) } }