Browse Source

Add XMLWriter abstraction for SVG generator utility

Getty Ritter 5 years ago
parent
commit
5df598da42
1 changed files with 72 additions and 47 deletions
  1. 72 47
      src/lib.rs

+ 72 - 47
src/lib.rs

@@ -5,14 +5,15 @@ use std::path::Path;
 
 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 {
     stuff: Vec<Box<AsSVG>>,
     size: (f64, f64),
 }
 
 #[derive(Copy, Clone)]
-pub struct Inches { amt: f64 }
+struct Inches { amt: f64 }
 
 impl Display for Inches {
     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 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
@@ -86,6 +110,7 @@ impl SVG {
                 n += 1;
                 path = format!("output/{}{:05}.svg", p, n);
             }
+            eprintln!("writing to {}", path);
             OpenOptions::new().write(true).create_new(true).open(&path)?
         };
         self.write_svg(&mut file)
@@ -93,22 +118,25 @@ impl SVG {
 
 
    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
@@ -122,7 +150,7 @@ impl SVG {
 /// The AsSVG trait represents things which can be rendered to an SVG
 /// canvas.
 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
     // 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.
@@ -138,14 +166,13 @@ pub struct 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 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",
+            buf.tag("circle",
                 &[("cx", &x),
                   ("cy", &y),
                   ("r", &"0.01"),
@@ -166,8 +193,7 @@ impl AsSVG for Line {
                 String::from_utf8(path).unwrap()
             };
 
-            xml_tag(
-                buf, "path",
+            buf.tag("path",
                 &[("d", &path),
                   ("stroke", &"black"),
                   ("fill", &"none"),
@@ -212,11 +238,10 @@ pub fn rect(position: (f64, f64), size: (f64, f64)) -> 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 (w, h) = self.size;
-        xml_tag(
-            buf, "rect",
+        buf.tag("rect",
             &[("x", &x),
               ("y", &y),
               ("width", &w),
@@ -243,11 +268,11 @@ pub fn circle(position: (f64, f64), radius: f64) -> 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 r = self.radius;
-        xml_tag(
-            buf, "circle",
+        buf.tag(
+            "circle",
             &[("cx", &x),
               ("cy", &y),
               ("r", &r),