Add XMLWriter abstraction for SVG generator utility
Getty Ritter
7 years ago
| 5 | 5 | |
| 6 | 6 | const MAX_OUTPUT_FILES: u32 = 500; |
| 7 | 7 | |
| 8 |
/// An SVG document |
|
| 8 | /// An SVG document, which has a fixed size (computed in inches) as | |
| 9 | /// well as a collection of drawn figures. | |
| 9 | 10 | pub struct SVG { |
| 10 | 11 | stuff: Vec<Box<AsSVG>>, |
| 11 | 12 | size: (f64, f64), |
| 12 | 13 | } |
| 13 | 14 | |
| 14 | 15 | #[derive(Copy, Clone)] |
| 15 |
|
|
| 16 | struct Inches { amt: f64 } | |
| 16 | 17 | |
| 17 | 18 | impl Display for Inches { |
| 18 | 19 | fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { |
| 23 | 24 | |
| 24 | 25 | fn inches(amt: f64) -> Inches { Inches { amt } } |
| 25 | 26 | |
| 26 | fn xml_tag(w: &mut Write, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> { | |
| 27 | write!(w, "<{}", name)?; | |
| 28 | for &(k, v) in attrs { | |
| 29 | write!(w, " {}=\"{}\"", k, v)?; | |
| 30 | } | |
| 31 | writeln!(w, "/>")?; | |
| 32 | Ok(()) | |
| 33 | } | |
| 34 | ||
| 35 | fn xml_open(w: &mut Write, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> { | |
| 36 | write!(w, "<{}", name)?; | |
| 37 | for &(k, v) in attrs { | |
| 38 | write!(w, " {}=\"{}\"", k, v)?; | |
| 39 | } | |
| 40 | writeln!(w, ">")?; | |
| 41 | Ok(()) | |
| 42 | } | |
| 43 | ||
| 44 | fn xml_close(w: &mut Write, name: &str) -> io::Result<()> { | |
| 45 | write!(w, "</{}>", name)?; | |
| 46 | Ok(()) | |
| 27 | pub struct XMLWriter<'a>{ | |
| 28 | writer: &'a mut Write, | |
| 29 | } | |
| 30 | ||
| 31 | impl<'a> XMLWriter<'a> { | |
| 32 | /// create an entire closed tag with the given attributes | |
| 33 | pub fn tag(&mut self, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> { | |
| 34 | write!(self.writer, "<{}", name)?; | |
| 35 | for &(k, v) in attrs { | |
| 36 | write!(self.writer, " {}=\"{}\"", k, v)?; | |
| 37 | } | |
| 38 | writeln!(self.writer, "/>")?; | |
| 39 | Ok(()) | |
| 40 | } | |
| 41 | ||
| 42 | /// create an open tag with the given attributes | |
| 43 | pub fn open(&mut self, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> { | |
| 44 | write!(self.writer, "<{}", name)?; | |
| 45 | for &(k, v) in attrs { | |
| 46 | write!(self.writer, " {}=\"{}\"", k, v)?; | |
| 47 | } | |
| 48 | writeln!(self.writer, ">")?; | |
| 49 | Ok(()) | |
| 50 | } | |
| 51 | ||
| 52 | /// close a tag with the given attributes | |
| 53 | pub fn close(&mut self, name: &str) -> io::Result<()> { | |
| 54 | write!(self.writer, "</{}>", name)?; | |
| 55 | Ok(()) | |
| 56 | } | |
| 57 | ||
| 58 | pub fn block<F>(&mut self, name: &str, attrs: &[(&str, &Display)], cb: F) -> io::Result<()> | |
| 59 | where F: FnOnce(&mut XMLWriter<'a>) -> io::Result<()> | |
| 60 | { | |
| 61 | write!(self.writer, "<{}", name)?; | |
| 62 | for &(k, v) in attrs { | |
| 63 | write!(self.writer, " {}=\"{}\"", k, v)?; | |
| 64 | } | |
| 65 | writeln!(self.writer, ">")?; | |
| 66 | cb(self)?; | |
| 67 | write!(self.writer, "</{}>", name)?; | |
| 68 | Ok(()) | |
| 69 | } | |
| 70 | ||
| 47 | 71 | } |
| 48 | 72 | |
| 49 | 73 | /// Create a new empty SVG document of the specified width and height |
| 86 | 110 | n += 1; |
| 87 | 111 | path = format!("output/{}{:05}.svg", p, n); |
| 88 | 112 | } |
| 113 | eprintln!("writing to {}", path); | |
| 89 | 114 | OpenOptions::new().write(true).create_new(true).open(&path)? |
| 90 | 115 | }; |
| 91 | 116 | self.write_svg(&mut file) |
| 93 | 118 | |
| 94 | 119 | |
| 95 | 120 | pub fn write_svg<W: Write>(self, buf: &mut W) -> io::Result<()> { |
| 96 | let (w, h) = self.size; | |
| 97 | writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?; | |
| 98 | xml_open( | |
| 99 | buf, "svg", | |
| 100 | &[("xmlns", &"http://www.w3.org/2000/svg"), | |
| 101 | ("version", &"1.1"), | |
| 102 | ("width", &inches(w)), | |
| 103 | ("height", &inches(h)), | |
| 104 | ("viewBox", &format!("0 0 {} {}", w, h)), | |
| 105 | ("stroke-width", &"0.0001in"), | |
| 106 | ], | |
| 107 | )?; | |
| 108 | for elem in self.stuff { | |
| 109 | elem.draw_svg(buf)?; | |
| 110 | } | |
| 111 | xml_close(buf, "svg") | |
| 121 | let (w, h) = self.size; | |
| 122 | writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?; | |
| 123 | let mut xml = XMLWriter { writer: buf }; | |
| 124 | xml.block( | |
| 125 | "svg", | |
| 126 | &[("xmlns", &"http://www.w3.org/2000/svg"), | |
| 127 | ("version", &"1.1"), | |
| 128 | ("width", &inches(w)), | |
| 129 | ("height", &inches(h)), | |
| 130 | ("viewBox", &format!("0 0 {} {}", w, h)), | |
| 131 | ("stroke-width", &"0.0001in"), | |
| 132 | ], | |
| 133 | |mut b| { | |
| 134 | for elem in self.stuff { | |
| 135 | elem.draw_svg(&mut b)?; | |
| 136 | } | |
| 137 | Ok(()) | |
| 138 | } | |
| 139 | ) | |
| 112 | 140 | } |
| 113 | 141 | |
| 114 | 142 | /// Add a new drawable thing to this SVG document |
| 122 | 150 | /// The AsSVG trait represents things which can be rendered to an SVG |
| 123 | 151 | /// canvas. |
| 124 | 152 | pub trait AsSVG { |
| 125 |
fn draw_svg(&self, buf: &mut |
|
| 153 | fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()>; | |
| 126 | 154 | // This is a bit of a hack to make sure that all of our types live |
| 127 | 155 | // long enough: it's now on the implementer of AsSVG to box it up |
| 128 | 156 | // and make sure that we can keep a trait object around. |
| 138 | 166 | } |
| 139 | 167 | |
| 140 | 168 | impl AsSVG for Line { |
| 141 |
fn draw_svg(&self, buf: &mut |
|
| 169 | fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> { | |
| 142 | 170 | if self.points.len() == 0 { |
| 143 | 171 | // if there are no later points, then we just draw a |
| 144 | 172 | // 'point' at the starting position, which is just a tiny |
| 145 | 173 | // circle |
| 146 | 174 | let (x, y) = self.start; |
| 147 | xml_tag( | |
| 148 | buf, "circle", | |
| 175 | buf.tag("circle", | |
| 149 | 176 | &[("cx", &x), |
| 150 | 177 | ("cy", &y), |
| 151 | 178 | ("r", &"0.01"), |
| 166 | 193 | String::from_utf8(path).unwrap() |
| 167 | 194 | }; |
| 168 | 195 | |
| 169 | xml_tag( | |
| 170 | buf, "path", | |
| 196 | buf.tag("path", | |
| 171 | 197 | &[("d", &path), |
| 172 | 198 | ("stroke", &"black"), |
| 173 | 199 | ("fill", &"none"), |
| 212 | 238 | } |
| 213 | 239 | |
| 214 | 240 | impl AsSVG for Rect { |
| 215 |
fn draw_svg(&self, buf: &mut |
|
| 241 | fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> { | |
| 216 | 242 | let (x, y) = self.position; |
| 217 | 243 | let (w, h) = self.size; |
| 218 | xml_tag( | |
| 219 | buf, "rect", | |
| 244 | buf.tag("rect", | |
| 220 | 245 | &[("x", &x), |
| 221 | 246 | ("y", &y), |
| 222 | 247 | ("width", &w), |
| 243 | 268 | } |
| 244 | 269 | |
| 245 | 270 | impl AsSVG for Circle { |
| 246 |
fn draw_svg(&self, buf: &mut |
|
| 271 | fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> { | |
| 247 | 272 | let (x, y) = self.position; |
| 248 | 273 | let r = self.radius; |
| 249 | xml_tag( | |
| 250 | buf, "circle", | |
| 274 | buf.tag( | |
| 275 | "circle", | |
| 251 | 276 | &[("cx", &x), |
| 252 | 277 | ("cy", &y), |
| 253 | 278 | ("r", &r), |