use std::fmt::Display; use std::io::Write; /// An SVG document pub struct SVG { stuff: Vec>, size: (f64, f64), } fn xml_tag(w: &mut Vec, name: &str, attrs: &[(&str, &dyn 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, &dyn 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(()) } 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", &w), ("height", &h), ], ); 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.5"), ("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")], ); } } 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 draw_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) } }