gdritter repos gunpowder_treason / 5df598d
Add XMLWriter abstraction for SVG generator utility Getty Ritter 6 years ago
1 changed file(s) with 76 addition(s) and 51 deletion(s). Collapse all Expand all
55
66 const MAX_OUTPUT_FILES: u32 = 500;
77
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.
910 pub struct SVG {
1011 stuff: Vec<Box<AsSVG>>,
1112 size: (f64, f64),
1213 }
1314
1415 #[derive(Copy, Clone)]
15 pub struct Inches { amt: f64 }
16 struct Inches { amt: f64 }
1617
1718 impl Display for Inches {
1819 fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
2324
2425 fn inches(amt: f64) -> Inches { Inches { amt } }
2526
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
4771 }
4872
4973 /// Create a new empty SVG document of the specified width and height
86110 n += 1;
87111 path = format!("output/{}{:05}.svg", p, n);
88112 }
113 eprintln!("writing to {}", path);
89114 OpenOptions::new().write(true).create_new(true).open(&path)?
90115 };
91116 self.write_svg(&mut file)
93118
94119
95120 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 )
112140 }
113141
114142 /// Add a new drawable thing to this SVG document
122150 /// The AsSVG trait represents things which can be rendered to an SVG
123151 /// canvas.
124152 pub trait AsSVG {
125 fn draw_svg(&self, buf: &mut Write) -> io::Result<()>;
153 fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()>;
126154 // This is a bit of a hack to make sure that all of our types live
127155 // long enough: it's now on the implementer of AsSVG to box it up
128156 // and make sure that we can keep a trait object around.
138166 }
139167
140168 impl AsSVG for Line {
141 fn draw_svg(&self, buf: &mut Write) -> io::Result<()> {
169 fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
142170 if self.points.len() == 0 {
143171 // if there are no later points, then we just draw a
144172 // 'point' at the starting position, which is just a tiny
145173 // circle
146174 let (x, y) = self.start;
147 xml_tag(
148 buf, "circle",
175 buf.tag("circle",
149176 &[("cx", &x),
150177 ("cy", &y),
151178 ("r", &"0.01"),
166193 String::from_utf8(path).unwrap()
167194 };
168195
169 xml_tag(
170 buf, "path",
196 buf.tag("path",
171197 &[("d", &path),
172198 ("stroke", &"black"),
173199 ("fill", &"none"),
212238 }
213239
214240 impl AsSVG for Rect {
215 fn draw_svg(&self, buf: &mut Write) -> io::Result<()> {
241 fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
216242 let (x, y) = self.position;
217243 let (w, h) = self.size;
218 xml_tag(
219 buf, "rect",
244 buf.tag("rect",
220245 &[("x", &x),
221246 ("y", &y),
222247 ("width", &w),
243268 }
244269
245270 impl AsSVG for Circle {
246 fn draw_svg(&self, buf: &mut Write) -> io::Result<()> {
271 fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
247272 let (x, y) = self.position;
248273 let r = self.radius;
249 xml_tag(
250 buf, "circle",
274 buf.tag(
275 "circle",
251276 &[("cx", &x),
252277 ("cy", &y),
253278 ("r", &r),