123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- use std::fmt::{self, Display, Formatter};
- use std::fs::OpenOptions;
- use std::io::{self, Write};
- /// An SVG document
- pub struct SVG {
- stuff: Vec<Box<AsSVG>>,
- 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<u8>, 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<u8>, 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<u8>, 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<W: Write>(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<u8> {
- let mut buf = Vec::new();
- let (w, h) = self.size;
- writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
- 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<T: AsSVG>(&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<u8>);
- // 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<AsSVG>;
- }
- /// 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<u8>) {
- 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<AsSVG> {
- 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<u8>) {
- 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<AsSVG> {
- 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<u8>) {
- 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<AsSVG> {
- Box::new(self)
- }
- }
|