plotters_unsable/drawing/backend_impl/
svg.rs1use 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
28pub 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 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 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}