plotters_unsable/drawing/backend_impl/
svg.rs

1/*!
2The SVG image drawing backend
3*/
4
5use svg::node::element::{Circle, Line, Polyline, Rectangle, Text};
6use svg::Document;
7
8use crate::drawing::backend::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind};
9use crate::style::{Color, FontDesc, FontTransform};
10
11use std::io::{Cursor, Error};
12use std::path::Path;
13
14fn make_svg_color<C: Color>(color: &C) -> String {
15    let (r, g, b) = color.rgb();
16    return format!("#{:02X}{:02X}{:02X}", r, g, b);
17}
18
19fn make_svg_opacity<C: Color>(color: &C) -> String {
20    return format!("{}", color.alpha());
21}
22
23enum Target<'a> {
24    File(&'a Path),
25    Buffer(Cursor<&'a mut Vec<u8>>),
26}
27
28/// The SVG image drawing backend
29pub struct SVGBackend<'a> {
30    target: Target<'a>,
31    size: (u32, u32),
32    document: Option<Document>,
33    saved: bool,
34}
35
36impl<'a> SVGBackend<'a> {
37    fn update_document<F: FnOnce(Document) -> Document>(&mut self, op: F) {
38        let mut temp = None;
39        std::mem::swap(&mut temp, &mut self.document);
40        self.document = Some(op(temp.unwrap()));
41    }
42
43    /// Create a new SVG drawing backend
44    pub fn new<T: AsRef<Path> + ?Sized>(path: &'a T, size: (u32, u32)) -> Self {
45        Self {
46            target: Target::File(path.as_ref()),
47            size,
48            document: Some(Document::new().set("viewBox", (0, 0, size.0, size.1))),
49            saved: false,
50        }
51    }
52
53    /// Create a new SVG drawing backend and store the document into a u8 buffer
54    pub fn with_buffer(buf: &'a mut Vec<u8>, size: (u32, u32)) -> Self {
55        Self {
56            target: Target::Buffer(Cursor::new(buf)),
57            size,
58            document: Some(Document::new().set("viewBox", (0, 0, size.0, size.1))),
59            saved: false,
60        }
61    }
62}
63
64impl<'a> DrawingBackend for SVGBackend<'a> {
65    type ErrorType = Error;
66
67    fn get_size(&self) -> (u32, u32) {
68        self.size
69    }
70
71    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Error>> {
72        Ok(())
73    }
74
75    fn present(&mut self) -> Result<(), DrawingErrorKind<Error>> {
76        if !self.saved {
77            match self.target {
78                Target::File(path) => svg::save(path, self.document.as_ref().unwrap())
79                    .map_err(DrawingErrorKind::DrawingError)?,
80                Target::Buffer(ref mut w) => svg::write(w, self.document.as_ref().unwrap())
81                    .map_err(DrawingErrorKind::DrawingError)?,
82            }
83            self.saved = true;
84        }
85        Ok(())
86    }
87
88    fn draw_pixel<C: Color>(
89        &mut self,
90        point: BackendCoord,
91        color: &C,
92    ) -> Result<(), DrawingErrorKind<Error>> {
93        if color.alpha() == 0.0 {
94            return Ok(());
95        }
96        let node = Rectangle::new()
97            .set("x", point.0)
98            .set("y", point.1)
99            .set("width", 1)
100            .set("height", 1)
101            .set("stroke", "none")
102            .set("opacity", make_svg_opacity(color))
103            .set("fill", make_svg_color(color));
104        self.update_document(|d| d.add(node));
105        Ok(())
106    }
107
108    fn draw_line<S: BackendStyle>(
109        &mut self,
110        from: BackendCoord,
111        to: BackendCoord,
112        style: &S,
113    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
114        if style.as_color().alpha() == 0.0 {
115            return Ok(());
116        }
117        let node = Line::new()
118            .set("x1", from.0)
119            .set("y1", from.1)
120            .set("x2", to.0)
121            .set("y2", to.1)
122            .set("opacity", make_svg_opacity(style.as_color()))
123            .set("stroke", make_svg_color(style.as_color()));
124        self.update_document(|d| d.add(node));
125        Ok(())
126    }
127
128    fn draw_rect<S: BackendStyle>(
129        &mut self,
130        upper_left: BackendCoord,
131        bottom_right: BackendCoord,
132        style: &S,
133        fill: bool,
134    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
135        if style.as_color().alpha() == 0.0 {
136            return Ok(());
137        }
138        let mut node = Rectangle::new()
139            .set("x", upper_left.0)
140            .set("y", upper_left.1)
141            .set("width", bottom_right.0 - upper_left.0)
142            .set("height", bottom_right.1 - upper_left.1);
143
144        if !fill {
145            node = node
146                .set("opacity", make_svg_opacity(style.as_color()))
147                .set("stroke", make_svg_color(style.as_color()))
148                .set("fill", "none");
149        } else {
150            node = node
151                .set("opacity", make_svg_opacity(style.as_color()))
152                .set("fill", make_svg_color(style.as_color()))
153                .set("stroke", "none");
154        }
155
156        self.update_document(|d| d.add(node));
157        Ok(())
158    }
159
160    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
161        &mut self,
162        path: I,
163        style: &S,
164    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
165        if style.as_color().alpha() == 0.0 {
166            return Ok(());
167        }
168        let node = Polyline::new()
169            .set("fill", "none")
170            .set("opacity", make_svg_opacity(style.as_color()))
171            .set("stroke", make_svg_color(style.as_color()))
172            .set(
173                "points",
174                path.into_iter().fold(String::new(), |mut s, (x, y)| {
175                    s.push_str(&format!("{},{} ", x, y));
176                    s
177                }),
178            );
179        self.update_document(|d| d.add(node));
180        Ok(())
181    }
182
183    fn draw_circle<S: BackendStyle>(
184        &mut self,
185        center: BackendCoord,
186        radius: u32,
187        style: &S,
188        fill: bool,
189    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
190        if style.as_color().alpha() == 0.0 {
191            return Ok(());
192        }
193        let mut node = Circle::new()
194            .set("cx", center.0)
195            .set("cy", center.1)
196            .set("r", radius);
197
198        if !fill {
199            node = node
200                .set("opacity", make_svg_opacity(style.as_color()))
201                .set("stroke", make_svg_color(style.as_color()))
202                .set("fill", "none");
203        } else {
204            node = node
205                .set("opacity", make_svg_opacity(style.as_color()))
206                .set("fill", make_svg_color(style.as_color()))
207                .set("stroke", "none");
208        }
209
210        self.update_document(|d| d.add(node));
211        Ok(())
212    }
213    fn draw_text<'b, C: Color>(
214        &mut self,
215        text: &str,
216        font: &FontDesc<'b>,
217        pos: BackendCoord,
218        color: &C,
219    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
220        if color.alpha() == 0.0 {
221            return Ok(());
222        }
223        let context = svg::node::Text::new(text);
224        let layout = font.layout_box(text).map_err(DrawingErrorKind::FontError)?;
225
226        let trans = font.get_transform();
227        let offset = trans.offset(layout);
228        let x0 = pos.0 + offset.0;
229        let y0 = pos.1 + offset.1;
230
231        let node = Text::new()
232            .set("x", x0)
233            .set("y", y0 - (layout.0).1)
234            .set("font-famliy", font.get_name())
235            .set("font-size", font.get_size())
236            .set("opacity", make_svg_opacity(color))
237            .set("fill", make_svg_color(color));
238
239        let node = match trans {
240            FontTransform::Rotate90 => node.set("transform", format!("rotate(90, {}, {})", x0, y0)),
241            FontTransform::Rotate180 => {
242                node.set("transform", format!("rotate(180, {}, {})", x0, y0))
243            }
244            FontTransform::Rotate270 => {
245                node.set("transform", format!("rotate(270, {}, {})", x0, y0))
246            }
247            _ => node,
248        }
249        .add(context);
250
251        self.update_document(|d| d.add(node));
252
253        Ok(())
254    }
255}
256
257impl Drop for SVGBackend<'_> {
258    fn drop(&mut self) {
259        if !self.saved {
260            self.present().expect("Unable to save the SVG image");
261        }
262    }
263}