Thread writer abstraction through AsSVG
Getty Ritter
7 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> { |