rusty_systems/interpretation/
svg.rs1use std::fmt::Debug;
18use std::fs::File;
19use std::io::prelude::*;
20use std::ops::Deref;
21use std::rc::Rc;
22use crate::error::Error;
23
24use crate::geometry::{Bounds, Path, Point, Vector};
25use crate::prelude::{Interpretation, ProductionString, RunSettings, System};
26use crate::symbols::SymbolStore;
27
28#[derive(Debug, Clone)]
29pub struct SvgPathInterpretation<T>
30 where T: Interpretation<Item=Vec<Path>>
31{
32 initial: T,
33 width: usize,
34 height: usize
35}
36
37impl<T> Default for SvgPathInterpretation<T>
38 where T: Interpretation<Item=Vec<Path>>
39{
40 fn default() -> Self {
41 SvgPathInterpretation {
42 initial: T::default(),
43 width: 500,
44 height: 500,
45 }
46 }
47}
48
49impl<T> SvgPathInterpretation<T>
50 where T: Interpretation<Item=Vec<Path>>
51{
52 pub fn new(width: usize, height: usize) -> Self {
53 SvgPathInterpretation {
54 width,
55 height,
56 ..Default::default()
57 }
58 }
59
60 pub fn new_with(width: usize, height: usize, interpretation: T) -> Self {
61 SvgPathInterpretation {
62 width,
63 height,
64 initial: interpretation
65 }
66 }
67
68
69 #[inline]
70 pub fn width(&self) -> usize {
71 self.width
72 }
73
74 #[inline]
75 pub fn height(&self) -> usize {
76 self.height
77 }
78}
79
80impl<T> Interpretation for SvgPathInterpretation<T>
81 where T: Interpretation<Item=Vec<Path>>
82{
83 type Item = Svg;
84
85 fn system() -> crate::Result<System> {
86 T::system()
87 }
88
89 fn interpret<S: SymbolStore>(&self, tokens: &S, string: &ProductionString) -> crate::Result<Self::Item> {
90 let paths = self.initial.interpret(tokens, string)?;
91 let bounds = paths.bounds();
92
93 let center = bounds.as_ref().unwrap().center();
94 let canvas_centre = Point::new(self.width as f64 / 2.0, self.height as f64 / 2.0);
95
96 let scale = bounds.as_ref()
97 .map(|bounds| {
98 let scale_y = self.height() as f64 / bounds.height();
99 let scale_x = self.width() as f64 / bounds.width();
100 let scale = scale_x.min(scale_y);
101
102 Point::new(scale, -scale)
103 })
104 .unwrap_or(Point::new(1.0, -1.0));
105
106 let elements: Vec<_> = paths.into_iter()
107 .map(SvgPath::from)
108 .map(Rc::new)
109 .map(|rc| rc as Rc<dyn SvgElement>)
110 .collect();
111
112 #[allow(clippy::needless_update)]
113 let group = SvgGroup {
114 elements,
115 decorations: SvgDecorations {
116 stroke: Some(String::from("black")),
117 stroke_width: Some(0.2),
118 fill: Some(String::from("none")),
119 },
120 transforms: vec![
121 Rc::new(SvgTranslate(Vector::from(canvas_centre))),
122 Rc::new(SvgScale(scale)),
123 Rc::new(SvgTranslate(-Vector::from(center))),
124 ],
125 ..SvgGroup::default()
126 };
127
128 Ok(Svg {
129 elements: vec![Rc::new(group)],
130 width: self.width,
131 height: self.height
132 })
133 }
134
135 fn run_settings(&self) -> RunSettings {
136 self.initial.run_settings()
137 }
138}
139
140
141pub trait SvgElement : Debug {
142 fn to_svg(&self) -> String;
143
144}
145
146#[derive(Debug, Clone)]
147pub struct Svg {
148 elements: Vec<Rc<dyn SvgElement>>,
149 width: usize,
150 height: usize
151}
152
153impl Default for Svg {
154 fn default() -> Self {
155 Svg {
156 elements: Vec::new(),
157 width: 500,
158 height: 500
159 }
160 }
161}
162
163impl Svg {
164 pub fn save_file<P: AsRef<std::path::Path>>(&self, name: P) -> std::io::Result<()> {
166 let mut file = File::create(name)?;
167 file.write_all(self.to_svg().as_bytes())
168 }
169}
170
171impl SvgElement for Svg {
172 fn to_svg(&self) -> String {
173 let mut string = String::new();
174 string.push_str(format!("<svg version=\"1.1\" width=\"{}\" height=\"{}\" xmlns=\"http://www.w3.org/2000/svg\">",
175 self.width, self.height).as_str());
176
177 for item in &self.elements {
178 string.push_str(item.to_svg().as_str());
179 }
180
181 string.push_str("</svg>");
182 string
183 }
184}
185
186
187
188#[derive(Debug, Clone)]
189pub struct SvgPath {
190 path: Path,
191 fill: Option<String>,
192 stroke: Option<String>
193}
194
195impl SvgPath {
196 pub fn fill(&self) -> Option<&String> {
197 self.fill.as_ref()
198 }
199
200 pub fn stroke(&self) -> Option<&String> {
201 self.stroke.as_ref()
202 }
203}
204
205impl From<Path> for SvgPath {
206 fn from(path: Path) -> Self {
207 SvgPath { path, fill: None, stroke: None }
208 }
209}
210
211impl SvgElement for SvgPath {
212 fn to_svg(&self) -> String {
213 let mut string = String::new();
214
215 string.push_str("<path");
216
217 if let Some(fill) = self.fill() {
218 string.push_str(format!(" fill=\"{}\"", fill).as_str());
219 }
220 if let Some(stroke) = self.stroke() {
221 string.push_str(format!(" stroke=\"{}\"", stroke).as_str());
222 }
223
224 if !self.is_empty() {
225 string.push_str(" d=\"");
226 let first = self.get(0).unwrap();
227 string.push_str(format!("M {} {}", first.x(), first.y()).as_str());
228 for point in self.iter().skip(1) {
229 string.push_str(format!(" L {} {}", point.x(), point.y()).as_str());
230 }
231 string.push('"');
232 }
233 string.push_str("/>");
234
235 string
236 }
237}
238
239impl Deref for SvgPath {
240 type Target = Path;
241
242 fn deref(&self) -> &Self::Target {
243 &self.path
244 }
245}
246
247#[derive(Debug, Clone, Default)]
248pub struct SvgDecorations {
249 pub fill: Option<String>,
250 pub stroke: Option<String>,
251 pub stroke_width: Option<f32>,
252}
253
254impl SvgDecorations {
255 pub fn to_attr_string(&self) -> String {
256 [
257 self.fill.as_ref().map(|fill| format!("fill=\"{}\"", fill)).unwrap_or_default(),
258 self.stroke.as_ref().map(|stroke| format!("stroke=\"{}\"", stroke)).unwrap_or_default(),
259 self.stroke_width.as_ref().map(|width| format!("stroke-width=\"{}\"", width)).unwrap_or_default()
260 ].join(" ")
261
262 }
263}
264
265#[derive(Debug, Clone, Default)]
266pub struct SvgGroup {
267 elements: Vec<Rc<dyn SvgElement>>,
268 decorations: SvgDecorations,
269
270 transforms: Vec<Rc<dyn SvgTransformEl>>
271}
272
273
274impl SvgElement for SvgGroup {
275 fn to_svg(&self) -> String {
276 let mut string = String::new();
277
278 string.push_str("<g ");
279 string.push_str(self.decorations.to_attr_string().as_str());
280
281 if !self.transforms.is_empty() {
282 string.push_str(" transform=\"");
283 for transform in &self.transforms {
284 string.push_str(transform.to_transform().as_str());
285 string.push(' ');
286 }
287 string.push('\"');
288 }
289
290 string.push('>');
291
292 for element in &self.elements {
293 string.push_str(element.to_svg().as_str());
294 }
295
296 string.push_str("</g>");
297
298 string
299 }
300}
301
302pub trait SvgTransformEl: Debug {
303 fn to_transform(&self) -> String;
304}
305
306#[derive(Debug, Clone, Default)]
307pub struct SvgScale(Point);
308
309impl SvgTransformEl for SvgScale {
310 fn to_transform(&self) -> String {
311 format!("scale({} {})", self.0.x(), self.0.y())
312 }
313}
314
315#[derive(Debug, Clone, Default)]
316pub struct SvgTranslate(Vector);
317
318impl SvgTransformEl for SvgTranslate {
319 fn to_transform(&self) -> String {
320 format!("translate({} {})", self.0.x(), self.0.y())
321 }
322}
323
324#[derive(Debug, Clone)]
325pub struct SvgCircle {
326 pub centre: Point,
327 pub radius: f64,
328 pub decorations: SvgDecorations
329}
330
331impl SvgCircle {
332 pub fn build<P: Into<Point>>(point: P, radius: f64) -> crate::Result<SvgCircle> {
333 if radius < 0.0 {
334 return Err(Error::general("radius should be non-negative"))
335 }
336
337 Ok(SvgCircle { centre: point.into(), radius, ..Default::default()})
338 }
339}
340
341impl Default for SvgCircle {
342 fn default() -> Self {
343 SvgCircle {
344 centre: Point::default(),
345 radius: 5.0,
346 decorations: SvgDecorations { stroke: Some(String::from("black")), ..Default::default() }
347 }
348 }
349}
350
351impl SvgElement for SvgCircle {
352 fn to_svg(&self) -> String {
353 format!("<circle cx=\"{}\" cy=\"{}\" r=\"{}\" {}/>",
354 self.centre.x(), self.centre.y(), self.radius, self.decorations.to_attr_string())
355 }
356}