rusty_systems/interpretation/
svg.rs

1//! Provides support for producing SVGs as well as interpreting [`ProductionString`] instances
2//! as instructions for creating SVGs. 
3//! 
4//! TODO: more info on SVG support
5//! See <https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths>
6//! 
7//! <div class="warning">
8//! 
9//! Note that this isn't meant to be a full featured SVG composition library. If you want 
10//! more SVG features, crates such as [svg][svg] might be of use.
11//! 
12//! </div>
13//! 
14//! [svg]: https://crates.io/crates/svg
15//!
16
17use 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    /// Writes the SVG to file.
165    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}