Add XMLWriter abstraction for SVG generator utility
Getty Ritter
6 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), |