| 1 |
use std::fmt::Display;
|
| 2 |
use std::io::Write;
|
| 3 |
|
| 4 |
/// An SVG document
|
| 5 |
pub struct SVG {
|
| 6 |
stuff: Vec<Box<dyn AsSVG>>,
|
| 7 |
size: (f64, f64),
|
| 8 |
}
|
| 9 |
|
| 10 |
fn xml_tag(w: &mut Vec<u8>, name: &str, attrs: &[(&str, &dyn Display)]) {
|
| 11 |
write!(w, "<{}", name);
|
| 12 |
for (k, v) in attrs {
|
| 13 |
write!(w, " {}=\"{}\"", k, v);
|
| 14 |
}
|
| 15 |
writeln!(w, "/>");
|
| 16 |
}
|
| 17 |
|
| 18 |
fn xml_open(w: &mut Vec<u8>, name: &str, attrs: &[(&str, &dyn Display)]) {
|
| 19 |
write!(w, "<{}", name);
|
| 20 |
for (k, v) in attrs {
|
| 21 |
write!(w, " {}=\"{}\"", k, v);
|
| 22 |
}
|
| 23 |
writeln!(w, ">");
|
| 24 |
}
|
| 25 |
|
| 26 |
fn xml_close(w: &mut Vec<u8>, name: &str) {
|
| 27 |
write!(w, "<{}/>", name);
|
| 28 |
}
|
| 29 |
|
| 30 |
/// Create a new empty SVG document of the specified width and height
|
| 31 |
pub fn svg(w: f64, h: f64) -> SVG {
|
| 32 |
SVG {
|
| 33 |
stuff: Vec::new(),
|
| 34 |
size: (w, h),
|
| 35 |
}
|
| 36 |
}
|
| 37 |
|
| 38 |
impl SVG {
|
| 39 |
/// Print this SVG document to stdout
|
| 40 |
pub fn to_stdout(self) {
|
| 41 |
let buf = self.to_bytes();
|
| 42 |
println!("{}", String::from_utf8(buf).unwrap());
|
| 43 |
}
|
| 44 |
|
| 45 |
/// Write this SVG document to a file
|
| 46 |
pub fn to_file<W: Write>(self, w: &mut W) -> Result<(), std::io::Error> {
|
| 47 |
let buf = self.to_bytes();
|
| 48 |
w.write(&buf)?;
|
| 49 |
Ok(())
|
| 50 |
}
|
| 51 |
|
| 52 |
pub fn to_bytes(self) -> Vec<u8> {
|
| 53 |
let mut buf = Vec::new();
|
| 54 |
let (w, h) = self.size;
|
| 55 |
writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
| 56 |
xml_open(
|
| 57 |
&mut buf, "svg",
|
| 58 |
&[("xmlns", &"http://www.w3.org/2000/svg"),
|
| 59 |
("version", &"1.1"),
|
| 60 |
("width", &w),
|
| 61 |
("height", &h),
|
| 62 |
],
|
| 63 |
);
|
| 64 |
for elem in self.stuff {
|
| 65 |
elem.draw_svg(&mut buf);
|
| 66 |
}
|
| 67 |
xml_close(&mut buf, "svg");
|
| 68 |
buf
|
| 69 |
}
|
| 70 |
|
| 71 |
/// Add a new drawable thing to this SVG document
|
| 72 |
pub fn add<T: AsSVG>(&mut self, t: T) {
|
| 73 |
self.stuff.push(t.consume())
|
| 74 |
}
|
| 75 |
}
|
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
/// The AsSVG trait represents things which can be rendered to an SVG
|
| 80 |
/// canvas.
|
| 81 |
pub trait AsSVG {
|
| 82 |
fn draw_svg(&self, buf: &mut Vec<u8>);
|
| 83 |
// This is a bit of a hack to make sure that all of our types live
|
| 84 |
// long enough: it's now on the implementer of AsSVG to box it up
|
| 85 |
// and make sure that we can keep a trait object around.
|
| 86 |
fn consume(self) -> Box<AsSVG>;
|
| 87 |
}
|
| 88 |
|
| 89 |
/// A Line is a sequence of points. It'll be represented as a single
|
| 90 |
/// dot if it has only one point, and otherwise will be drawn as a
|
| 91 |
/// line between them.
|
| 92 |
pub struct Line {
|
| 93 |
start: (f64, f64),
|
| 94 |
points: Vec<(f64, f64)>,
|
| 95 |
}
|
| 96 |
|
| 97 |
impl AsSVG for Line {
|
| 98 |
fn draw_svg(&self, buf: &mut Vec<u8>) {
|
| 99 |
if self.points.len() == 0 {
|
| 100 |
// if there are no later points, then we just draw a
|
| 101 |
// 'point' at the starting position, which is just a tiny
|
| 102 |
// circle
|
| 103 |
let (x, y) = self.start;
|
| 104 |
xml_tag(
|
| 105 |
buf, "circle",
|
| 106 |
&[("cx", &x),
|
| 107 |
("cy", &y),
|
| 108 |
("r", &"0.5"),
|
| 109 |
("fill", &"black"),
|
| 110 |
],
|
| 111 |
);
|
| 112 |
} else {
|
| 113 |
// Otherwise, we draw a path, which mean assembling this
|
| 114 |
// somewhat wonky path field
|
| 115 |
|
| 116 |
let path = {
|
| 117 |
let mut path = Vec::new();
|
| 118 |
let (x, y) = self.start;
|
| 119 |
write!(&mut path, "M{} {}", x, y);
|
| 120 |
for (x, y) in self.points.iter() {
|
| 121 |
write!(&mut path, " L{} {}", x, y);
|
| 122 |
}
|
| 123 |
String::from_utf8(path).unwrap()
|
| 124 |
};
|
| 125 |
|
| 126 |
xml_tag(
|
| 127 |
buf, "path",
|
| 128 |
&[("d", &path), ("stroke", &"black")],
|
| 129 |
);
|
| 130 |
}
|
| 131 |
}
|
| 132 |
|
| 133 |
fn consume(self) -> Box<AsSVG> {
|
| 134 |
Box::new(self)
|
| 135 |
}
|
| 136 |
}
|
| 137 |
|
| 138 |
/// Create a new line at the given starting point
|
| 139 |
pub fn line(x: f64, y: f64) -> Line {
|
| 140 |
Line::new(x, y)
|
| 141 |
}
|
| 142 |
|
| 143 |
impl Line {
|
| 144 |
/// Create a new line at the given starting point
|
| 145 |
pub fn new(x: f64, y: f64) -> Line {
|
| 146 |
Line {
|
| 147 |
start: (x, y),
|
| 148 |
points: Vec::new(),
|
| 149 |
}
|
| 150 |
}
|
| 151 |
|
| 152 |
/// Draw a line segment from this point to another point
|
| 153 |
pub fn draw_to(mut self, x: f64, y: f64) -> Line {
|
| 154 |
self.points.push((x, y));
|
| 155 |
self
|
| 156 |
}
|
| 157 |
}
|
| 158 |
|
| 159 |
pub struct Rect {
|
| 160 |
position: (f64, f64),
|
| 161 |
size: (f64, f64),
|
| 162 |
}
|
| 163 |
|
| 164 |
pub fn rect(position: (f64, f64), size: (f64, f64)) -> Rect {
|
| 165 |
Rect { position, size }
|
| 166 |
}
|
| 167 |
|
| 168 |
impl AsSVG for Rect {
|
| 169 |
fn draw_svg(&self, buf: &mut Vec<u8>) {
|
| 170 |
let (x, y) = self.position;
|
| 171 |
let (w, h) = self.size;
|
| 172 |
xml_tag(
|
| 173 |
buf, "rect",
|
| 174 |
&[("x", &x),
|
| 175 |
("y", &y),
|
| 176 |
("width", &w),
|
| 177 |
("height", &h),
|
| 178 |
("stroke", &"black"),
|
| 179 |
("fill", &"white"),
|
| 180 |
]
|
| 181 |
);
|
| 182 |
}
|
| 183 |
|
| 184 |
fn consume(self) -> Box<AsSVG> {
|
| 185 |
Box::new(self)
|
| 186 |
}
|
| 187 |
}
|
| 188 |
|
| 189 |
|
| 190 |
pub struct Circle {
|
| 191 |
position: (f64, f64),
|
| 192 |
radius: f64,
|
| 193 |
}
|
| 194 |
|
| 195 |
pub fn circle(position: (f64, f64), radius: f64) -> Circle {
|
| 196 |
Circle { position, radius }
|
| 197 |
}
|
| 198 |
|
| 199 |
impl AsSVG for Circle {
|
| 200 |
fn draw_svg(&self, buf: &mut Vec<u8>) {
|
| 201 |
let (x, y) = self.position;
|
| 202 |
let r = self.radius;
|
| 203 |
xml_tag(
|
| 204 |
buf, "circle",
|
| 205 |
&[("cx", &x),
|
| 206 |
("cy", &y),
|
| 207 |
("r", &r),
|
| 208 |
("stroke", &"black"),
|
| 209 |
("fill", &"white"),
|
| 210 |
]
|
| 211 |
);
|
| 212 |
}
|
| 213 |
|
| 214 |
fn consume(self) -> Box<AsSVG> {
|
| 215 |
Box::new(self)
|
| 216 |
}
|
| 217 |
}
|