Thread writer abstraction through AsSVG
Getty Ritter
6 years ago
1 | 1 | use std::fmt::Display; |
2 | 2 | use std::fmt::{Formatter, Error}; |
3 | use std::io; | |
3 | 4 | use std::io::Write; |
4 | 5 | |
5 | 6 | /// An SVG document |
20 | 21 | |
21 | 22 | fn inches(amt: f64) -> Inches { Inches { amt } } |
22 | 23 | |
23 | fn xml_tag(w: &mut Vec<u8>, name: &str, attrs: &[(&str, &Display)]) { | |
24 | write!(w, "<{}", name); | |
24 | fn xml_tag(w: &mut Write, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> { | |
25 | write!(w, "<{}", name)?; | |
25 | 26 | for &(k, v) in attrs { |
26 | write!(w, " {}=\"{}\"", k, v); | |
27 | } | |
28 | writeln!(w, "/>"); | |
29 | } | |
30 | ||
31 | fn xml_open(w: &mut Vec<u8>, name: &str, attrs: &[(&str, &Display)]) { | |
32 |
|
|
27 | write!(w, " {}=\"{}\"", k, v)?; | |
28 | } | |
29 | writeln!(w, "/>")?; | |
30 | Ok(()) | |
31 | } | |
32 | ||
33 | fn xml_open(w: &mut Write, name: &str, attrs: &[(&str, &Display)]) -> io::Result<()> { | |
34 | write!(w, "<{}", name)?; | |
33 | 35 | for &(k, v) in attrs { |
34 | write!(w, " {}=\"{}\"", k, v); | |
35 | } | |
36 | writeln!(w, ">"); | |
37 | } | |
38 | ||
39 | fn xml_close(w: &mut Vec<u8>, name: &str) { | |
40 |
|
|
36 | write!(w, " {}=\"{}\"", k, v)?; | |
37 | } | |
38 | writeln!(w, ">")?; | |
39 | Ok(()) | |
40 | } | |
41 | ||
42 | fn xml_close(w: &mut Write, name: &str) -> io::Result<()> { | |
43 | write!(w, "</{}>", name)?; | |
44 | Ok(()) | |
41 | 45 | } |
42 | 46 | |
43 | 47 | /// Create a new empty SVG document of the specified width and height |
50 | 54 | |
51 | 55 | impl SVG { |
52 | 56 | /// Print this SVG document to stdout |
53 | pub fn to_stdout(self) { | |
54 | let buf = self.to_bytes(); | |
55 | println!("{}", String::from_utf8(buf).unwrap()); | |
56 | } | |
57 | ||
58 | /// Write this SVG document to a file | |
59 | pub fn to_file<W: Write>(self, w: &mut W) -> Result<(), std::io::Error> { | |
60 | let buf = self.to_bytes(); | |
61 | w.write(&buf)?; | |
62 | Ok(()) | |
63 | } | |
64 | ||
65 |
pub fn to_ |
|
57 | pub fn to_stdout(self) -> io::Result<()> { | |
58 | self.write_svg(&mut std::io::stdout()) | |
59 | } | |
60 | ||
61 | pub fn to_bytes(self) -> io::Result<Vec<u8>> { | |
66 | 62 | let mut buf = Vec::new(); |
63 | self.write_svg(&mut buf)?; | |
64 | Ok(buf) | |
65 | } | |
66 | ||
67 | pub fn write_svg<W: Write>(self, buf: &mut W) -> io::Result<()> { | |
67 | 68 | let (w, h) = self.size; |
68 |
writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>") |
|
69 | writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?; | |
69 | 70 | xml_open( |
70 |
|
|
71 | buf, "svg", | |
71 | 72 | &[("xmlns", &"http://www.w3.org/2000/svg"), |
72 | 73 | ("version", &"1.1"), |
73 | 74 | ("width", &inches(w)), |
75 | 76 | ("viewBox", &format!("0 0 {} {}", w, h)), |
76 | 77 | ("stroke-width", &"0.0001in"), |
77 | 78 | ], |
78 |
) |
|
79 | )?; | |
79 | 80 | for elem in self.stuff { |
80 |
elem.draw_svg( |
|
81 | elem.draw_svg(buf)?; | |
81 | 82 | } |
82 | xml_close(&mut buf, "svg"); | |
83 | buf | |
83 | xml_close(buf, "svg") | |
84 | 84 | } |
85 | 85 | |
86 | 86 | /// Add a new drawable thing to this SVG document |
94 | 94 | /// The AsSVG trait represents things which can be rendered to an SVG |
95 | 95 | /// canvas. |
96 | 96 | pub trait AsSVG { |
97 |
fn draw_svg(&self, buf: &mut |
|
97 | fn draw_svg(&self, buf: &mut Write) -> io::Result<()>; | |
98 | 98 | // This is a bit of a hack to make sure that all of our types live |
99 | 99 | // long enough: it's now on the implementer of AsSVG to box it up |
100 | 100 | // and make sure that we can keep a trait object around. |
110 | 110 | } |
111 | 111 | |
112 | 112 | impl AsSVG for Line { |
113 |
fn draw_svg(&self, buf: &mut |
|
113 | fn draw_svg(&self, buf: &mut Write) -> io::Result<()> { | |
114 | 114 | if self.points.len() == 0 { |
115 | 115 | // if there are no later points, then we just draw a |
116 | 116 | // 'point' at the starting position, which is just a tiny |
120 | 120 | buf, "circle", |
121 | 121 | &[("cx", &x), |
122 | 122 | ("cy", &y), |
123 |
("r", &"0.01 |
|
123 | ("r", &"0.01"), | |
124 | 124 | ("fill", &"black"), |
125 | 125 | ], |
126 |
) |
|
126 | ) | |
127 | 127 | } else { |
128 | 128 | // Otherwise, we draw a path, which mean assembling this |
129 | 129 | // somewhat wonky path field |
131 | 131 | let path = { |
132 | 132 | let mut path = Vec::new(); |
133 | 133 | let (x, y) = self.start; |
134 |
write!(&mut path, "M{} {}", x, y) |
|
134 | write!(&mut path, "M{} {}", x, y)?; | |
135 | 135 | for &(x, y) in self.points.iter() { |
136 |
write!(&mut path, " L{} {}", x, y) |
|
136 | write!(&mut path, " L{} {}", x, y)?; | |
137 | 137 | } |
138 | 138 | String::from_utf8(path).unwrap() |
139 | 139 | }; |
140 | 140 | |
141 | 141 | xml_tag( |
142 | 142 | buf, "path", |
143 | &[("d", &path), ("stroke", &"black")], | |
144 | ); | |
143 | &[("d", &path), | |
144 | ("stroke", &"black"), | |
145 | ("fill", &"none"), | |
146 | ], | |
147 | ) | |
145 | 148 | } |
146 | 149 | } |
147 | 150 | |
181 | 184 | } |
182 | 185 | |
183 | 186 | impl AsSVG for Rect { |
184 |
fn draw_svg(&self, buf: &mut |
|
187 | fn draw_svg(&self, buf: &mut Write) -> io::Result<()> { | |
185 | 188 | let (x, y) = self.position; |
186 | 189 | let (w, h) = self.size; |
187 | 190 | xml_tag( |
193 | 196 | ("stroke", &"black"), |
194 | 197 | ("fill", &"white"), |
195 | 198 | ] |
196 |
) |
|
199 | ) | |
197 | 200 | } |
198 | 201 | |
199 | 202 | fn consume(self) -> Box<AsSVG> { |
212 | 215 | } |
213 | 216 | |
214 | 217 | impl AsSVG for Circle { |
215 |
fn draw_svg(&self, buf: &mut |
|
218 | fn draw_svg(&self, buf: &mut Write) -> io::Result<()> { | |
216 | 219 | let (x, y) = self.position; |
217 | 220 | let r = self.radius; |
218 | 221 | xml_tag( |
223 | 226 | ("stroke", &"black"), |
224 | 227 | ("fill", &"white"), |
225 | 228 | ] |
226 |
) |
|
229 | ) | |
227 | 230 | } |
228 | 231 | |
229 | 232 | fn consume(self) -> Box<AsSVG> { |