gdritter repos gunpowder_treason / ebcf35e
Initial slightly wonky API for gunpowder_treason Getty Ritter 5 years ago
3 changed file(s) with 226 addition(s) and 0 deletion(s). Collapse all Expand all
1 /target
2 **/*.rs.bk
3 Cargo.lock
1 [package]
2 name = "gunpowder_treason"
3 version = "0.1.0"
4 authors = ["Getty Ritter <gettylefou@gmail.com>"]
5
6 [dependencies]
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 }