|
@@ -5,14 +5,15 @@ use std::path::Path;
|
|
|
|
|
|
const MAX_OUTPUT_FILES: u32 = 500;
|
|
const MAX_OUTPUT_FILES: u32 = 500;
|
|
|
|
|
|
-/// An SVG document
|
|
|
|
|
|
+/// An SVG document, which has a fixed size (computed in inches) as
|
|
|
|
+/// well as a collection of drawn figures.
|
|
pub struct SVG {
|
|
pub struct SVG {
|
|
stuff: Vec<Box<AsSVG>>,
|
|
stuff: Vec<Box<AsSVG>>,
|
|
size: (f64, f64),
|
|
size: (f64, f64),
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Copy, Clone)]
|
|
#[derive(Copy, Clone)]
|
|
-pub struct Inches { amt: f64 }
|
|
|
|
|
|
+struct Inches { amt: f64 }
|
|
|
|
|
|
impl Display for Inches {
|
|
impl Display for Inches {
|
|
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
|
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
|
@@ -23,27 +24,50 @@ impl Display for Inches {
|
|
|
|
|
|
fn inches(amt: f64) -> Inches { Inches { amt } }
|
|
fn inches(amt: f64) -> Inches { Inches { amt } }
|
|
|
|
|
|
-fn xml_tag(w: &mut Write, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> {
|
|
|
|
- write!(w, "<{}", name)?;
|
|
|
|
- for &(k, v) in attrs {
|
|
|
|
- write!(w, " {}=\"{}\"", k, v)?;
|
|
|
|
- }
|
|
|
|
- writeln!(w, "/>")?;
|
|
|
|
- Ok(())
|
|
|
|
|
|
+pub struct XMLWriter<'a>{
|
|
|
|
+ writer: &'a mut Write,
|
|
}
|
|
}
|
|
|
|
|
|
-fn xml_open(w: &mut Write, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> {
|
|
|
|
- write!(w, "<{}", name)?;
|
|
|
|
- for &(k, v) in attrs {
|
|
|
|
- write!(w, " {}=\"{}\"", k, v)?;
|
|
|
|
|
|
+impl<'a> XMLWriter<'a> {
|
|
|
|
+ /// create an entire closed tag with the given attributes
|
|
|
|
+ pub fn tag(&mut self, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> {
|
|
|
|
+ write!(self.writer, "<{}", name)?;
|
|
|
|
+ for &(k, v) in attrs {
|
|
|
|
+ write!(self.writer, " {}=\"{}\"", k, v)?;
|
|
|
|
+ }
|
|
|
|
+ writeln!(self.writer, "/>")?;
|
|
|
|
+ Ok(())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// create an open tag with the given attributes
|
|
|
|
+ pub fn open(&mut self, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> {
|
|
|
|
+ write!(self.writer, "<{}", name)?;
|
|
|
|
+ for &(k, v) in attrs {
|
|
|
|
+ write!(self.writer, " {}=\"{}\"", k, v)?;
|
|
|
|
+ }
|
|
|
|
+ writeln!(self.writer, ">")?;
|
|
|
|
+ Ok(())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// close a tag with the given attributes
|
|
|
|
+ pub fn close(&mut self, name: &str) -> io::Result<()> {
|
|
|
|
+ write!(self.writer, "</{}>", name)?;
|
|
|
|
+ Ok(())
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pub fn block<F>(&mut self, name: &str, attrs: &[(&str, &Display)], cb: F) -> io::Result<()>
|
|
|
|
+ where F: FnOnce(&mut XMLWriter<'a>) -> io::Result<()>
|
|
|
|
+ {
|
|
|
|
+ write!(self.writer, "<{}", name)?;
|
|
|
|
+ for &(k, v) in attrs {
|
|
|
|
+ write!(self.writer, " {}=\"{}\"", k, v)?;
|
|
|
|
+ }
|
|
|
|
+ writeln!(self.writer, ">")?;
|
|
|
|
+ cb(self)?;
|
|
|
|
+ write!(self.writer, "</{}>", name)?;
|
|
|
|
+ Ok(())
|
|
}
|
|
}
|
|
- writeln!(w, ">")?;
|
|
|
|
- Ok(())
|
|
|
|
-}
|
|
|
|
|
|
|
|
-fn xml_close(w: &mut Write, name: &str) -> io::Result<()> {
|
|
|
|
- write!(w, "</{}>", name)?;
|
|
|
|
- Ok(())
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/// Create a new empty SVG document of the specified width and height
|
|
/// Create a new empty SVG document of the specified width and height
|
|
@@ -86,6 +110,7 @@ impl SVG {
|
|
n += 1;
|
|
n += 1;
|
|
path = format!("output/{}{:05}.svg", p, n);
|
|
path = format!("output/{}{:05}.svg", p, n);
|
|
}
|
|
}
|
|
|
|
+ eprintln!("writing to {}", path);
|
|
OpenOptions::new().write(true).create_new(true).open(&path)?
|
|
OpenOptions::new().write(true).create_new(true).open(&path)?
|
|
};
|
|
};
|
|
self.write_svg(&mut file)
|
|
self.write_svg(&mut file)
|
|
@@ -93,22 +118,25 @@ impl SVG {
|
|
|
|
|
|
|
|
|
|
pub fn write_svg<W: Write>(self, buf: &mut W) -> io::Result<()> {
|
|
pub fn write_svg<W: Write>(self, buf: &mut W) -> io::Result<()> {
|
|
- let (w, h) = self.size;
|
|
|
|
- writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
|
|
|
|
- xml_open(
|
|
|
|
- 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(buf)?;
|
|
|
|
- }
|
|
|
|
- xml_close(buf, "svg")
|
|
|
|
|
|
+ let (w, h) = self.size;
|
|
|
|
+ writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
|
|
|
|
+ let mut xml = XMLWriter { writer: buf };
|
|
|
|
+ xml.block(
|
|
|
|
+ "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"),
|
|
|
|
+ ],
|
|
|
|
+ |mut b| {
|
|
|
|
+ for elem in self.stuff {
|
|
|
|
+ elem.draw_svg(&mut b)?;
|
|
|
|
+ }
|
|
|
|
+ Ok(())
|
|
|
|
+ }
|
|
|
|
+ )
|
|
}
|
|
}
|
|
|
|
|
|
/// Add a new drawable thing to this SVG document
|
|
/// Add a new drawable thing to this SVG document
|
|
@@ -122,7 +150,7 @@ impl SVG {
|
|
/// The AsSVG trait represents things which can be rendered to an SVG
|
|
/// The AsSVG trait represents things which can be rendered to an SVG
|
|
/// canvas.
|
|
/// canvas.
|
|
pub trait AsSVG {
|
|
pub trait AsSVG {
|
|
- fn draw_svg(&self, buf: &mut Write) -> io::Result<()>;
|
|
|
|
|
|
+ fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()>;
|
|
// This is a bit of a hack to make sure that all of our types live
|
|
// 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
|
|
// 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.
|
|
// and make sure that we can keep a trait object around.
|
|
@@ -138,14 +166,13 @@ pub struct Line {
|
|
}
|
|
}
|
|
|
|
|
|
impl AsSVG for Line {
|
|
impl AsSVG for Line {
|
|
- fn draw_svg(&self, buf: &mut Write) -> io::Result<()> {
|
|
|
|
|
|
+ fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
|
|
if self.points.len() == 0 {
|
|
if self.points.len() == 0 {
|
|
// if there are no later points, then we just draw a
|
|
// if there are no later points, then we just draw a
|
|
// 'point' at the starting position, which is just a tiny
|
|
// 'point' at the starting position, which is just a tiny
|
|
// circle
|
|
// circle
|
|
let (x, y) = self.start;
|
|
let (x, y) = self.start;
|
|
- xml_tag(
|
|
|
|
- buf, "circle",
|
|
|
|
|
|
+ buf.tag("circle",
|
|
&[("cx", &x),
|
|
&[("cx", &x),
|
|
("cy", &y),
|
|
("cy", &y),
|
|
("r", &"0.01"),
|
|
("r", &"0.01"),
|
|
@@ -166,8 +193,7 @@ impl AsSVG for Line {
|
|
String::from_utf8(path).unwrap()
|
|
String::from_utf8(path).unwrap()
|
|
};
|
|
};
|
|
|
|
|
|
- xml_tag(
|
|
|
|
- buf, "path",
|
|
|
|
|
|
+ buf.tag("path",
|
|
&[("d", &path),
|
|
&[("d", &path),
|
|
("stroke", &"black"),
|
|
("stroke", &"black"),
|
|
("fill", &"none"),
|
|
("fill", &"none"),
|
|
@@ -212,11 +238,10 @@ pub fn rect(position: (f64, f64), size: (f64, f64)) -> Rect {
|
|
}
|
|
}
|
|
|
|
|
|
impl AsSVG for Rect {
|
|
impl AsSVG for Rect {
|
|
- fn draw_svg(&self, buf: &mut Write) -> io::Result<()> {
|
|
|
|
|
|
+ fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
|
|
let (x, y) = self.position;
|
|
let (x, y) = self.position;
|
|
let (w, h) = self.size;
|
|
let (w, h) = self.size;
|
|
- xml_tag(
|
|
|
|
- buf, "rect",
|
|
|
|
|
|
+ buf.tag("rect",
|
|
&[("x", &x),
|
|
&[("x", &x),
|
|
("y", &y),
|
|
("y", &y),
|
|
("width", &w),
|
|
("width", &w),
|
|
@@ -243,11 +268,11 @@ pub fn circle(position: (f64, f64), radius: f64) -> Circle {
|
|
}
|
|
}
|
|
|
|
|
|
impl AsSVG for Circle {
|
|
impl AsSVG for Circle {
|
|
- fn draw_svg(&self, buf: &mut Write) -> io::Result<()> {
|
|
|
|
|
|
+ fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
|
|
let (x, y) = self.position;
|
|
let (x, y) = self.position;
|
|
let r = self.radius;
|
|
let r = self.radius;
|
|
- xml_tag(
|
|
|
|
- buf, "circle",
|
|
|
|
|
|
+ buf.tag(
|
|
|
|
+ "circle",
|
|
&[("cx", &x),
|
|
&[("cx", &x),
|
|
("cy", &y),
|
|
("cy", &y),
|
|
("r", &r),
|
|
("r", &r),
|