1 /*!
2 The SVG image drawing backend
3 */
4 
5 use plotters_backend::{
6     text_anchor::{HPos, VPos},
7     BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind,
8     FontStyle, FontTransform,
9 };
10 
11 use std::fs::File;
12 #[allow(unused_imports)]
13 use std::io::Cursor;
14 use std::io::{BufWriter, Error, Write};
15 use std::path::Path;
16 
make_svg_color(color: BackendColor) -> String17 fn make_svg_color(color: BackendColor) -> String {
18     let (r, g, b) = color.rgb;
19     return format!("#{:02X}{:02X}{:02X}", r, g, b);
20 }
21 
make_svg_opacity(color: BackendColor) -> String22 fn make_svg_opacity(color: BackendColor) -> String {
23     return format!("{}", color.alpha);
24 }
25 
26 enum Target<'a> {
27     File(String, &'a Path),
28     Buffer(&'a mut String),
29     // TODO: At this point we won't make the breaking change
30     // so the u8 buffer is still supported. But in 0.3, we definitely
31     // should get rid of this.
32     #[cfg(feature = "deprecated_items")]
33     U8Buffer(String, &'a mut Vec<u8>),
34 }
35 
36 impl Target<'_> {
get_mut(&mut self) -> &mut String37     fn get_mut(&mut self) -> &mut String {
38         match self {
39             Target::File(ref mut buf, _) => buf,
40             Target::Buffer(buf) => buf,
41             #[cfg(feature = "deprecated_items")]
42             Target::U8Buffer(ref mut buf, _) => buf,
43         }
44     }
45 }
46 
47 enum SVGTag {
48     SVG,
49     Circle,
50     Line,
51     Polygon,
52     Polyline,
53     Rectangle,
54     Text,
55     #[allow(dead_code)]
56     Image,
57 }
58 
59 impl SVGTag {
to_tag_name(&self) -> &'static str60     fn to_tag_name(&self) -> &'static str {
61         match self {
62             SVGTag::SVG => "svg",
63             SVGTag::Circle => "circle",
64             SVGTag::Line => "line",
65             SVGTag::Polyline => "polyline",
66             SVGTag::Rectangle => "rect",
67             SVGTag::Text => "text",
68             SVGTag::Image => "image",
69             SVGTag::Polygon => "polygon",
70         }
71     }
72 }
73 
74 /// The SVG image drawing backend
75 pub struct SVGBackend<'a> {
76     target: Target<'a>,
77     size: (u32, u32),
78     tag_stack: Vec<SVGTag>,
79     saved: bool,
80 }
81 
82 impl<'a> SVGBackend<'a> {
escape_and_push(buf: &mut String, value: &str)83     fn escape_and_push(buf: &mut String, value: &str) {
84         value.chars().for_each(|c| match c {
85             '<' => buf.push_str("&lt;"),
86             '>' => buf.push_str("&gt;"),
87             '&' => buf.push_str("&amp;"),
88             '"' => buf.push_str("&quot;"),
89             '\'' => buf.push_str("&apos;"),
90             other => buf.push(other),
91         });
92     }
open_tag(&mut self, tag: SVGTag, attr: &[(&str, &str)], close: bool)93     fn open_tag(&mut self, tag: SVGTag, attr: &[(&str, &str)], close: bool) {
94         let buf = self.target.get_mut();
95         buf.push_str("<");
96         buf.push_str(tag.to_tag_name());
97         for (key, value) in attr {
98             buf.push_str(" ");
99             buf.push_str(key);
100             buf.push_str("=\"");
101             Self::escape_and_push(buf, value);
102             buf.push_str("\"");
103         }
104         if close {
105             buf.push_str("/>\n");
106         } else {
107             self.tag_stack.push(tag);
108             buf.push_str(">\n");
109         }
110     }
111 
close_tag(&mut self) -> bool112     fn close_tag(&mut self) -> bool {
113         if let Some(tag) = self.tag_stack.pop() {
114             let buf = self.target.get_mut();
115             buf.push_str("</");
116             buf.push_str(tag.to_tag_name());
117             buf.push_str(">\n");
118             return true;
119         }
120         false
121     }
122 
init_svg_file(&mut self, size: (u32, u32))123     fn init_svg_file(&mut self, size: (u32, u32)) {
124         self.open_tag(
125             SVGTag::SVG,
126             &[
127                 ("width", &format!("{}", size.0)),
128                 ("height", &format!("{}", size.1)),
129                 ("viewBox", &format!("0 0 {} {}", size.0, size.1)),
130                 ("xmlns", "http://www.w3.org/2000/svg"),
131             ],
132             false,
133         );
134     }
135 
136     /// Create a new SVG drawing backend
new<T: AsRef<Path> + ?Sized>(path: &'a T, size: (u32, u32)) -> Self137     pub fn new<T: AsRef<Path> + ?Sized>(path: &'a T, size: (u32, u32)) -> Self {
138         let mut ret = Self {
139             target: Target::File(String::default(), path.as_ref()),
140             size,
141             tag_stack: vec![],
142             saved: false,
143         };
144 
145         ret.init_svg_file(size);
146         ret
147     }
148 
149     /// Create a new SVG drawing backend and store the document into a u8 vector
150     #[cfg(feature = "deprecated_items")]
151     #[deprecated(
152         note = "This will be replaced by `with_string`, consider use `with_string` to avoid breaking change in the future"
153     )]
with_buffer(buf: &'a mut Vec<u8>, size: (u32, u32)) -> Self154     pub fn with_buffer(buf: &'a mut Vec<u8>, size: (u32, u32)) -> Self {
155         let mut ret = Self {
156             target: Target::U8Buffer(String::default(), buf),
157             size,
158             tag_stack: vec![],
159             saved: false,
160         };
161 
162         ret.init_svg_file(size);
163 
164         ret
165     }
166 
167     /// Create a new SVG drawing backend and store the document into a String buffer
with_string(buf: &'a mut String, size: (u32, u32)) -> Self168     pub fn with_string(buf: &'a mut String, size: (u32, u32)) -> Self {
169         let mut ret = Self {
170             target: Target::Buffer(buf),
171             size,
172             tag_stack: vec![],
173             saved: false,
174         };
175 
176         ret.init_svg_file(size);
177 
178         ret
179     }
180 }
181 
182 impl<'a> DrawingBackend for SVGBackend<'a> {
183     type ErrorType = Error;
184 
get_size(&self) -> (u32, u32)185     fn get_size(&self) -> (u32, u32) {
186         self.size
187     }
188 
ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Error>>189     fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Error>> {
190         Ok(())
191     }
192 
present(&mut self) -> Result<(), DrawingErrorKind<Error>>193     fn present(&mut self) -> Result<(), DrawingErrorKind<Error>> {
194         if !self.saved {
195             while self.close_tag() {}
196             match self.target {
197                 Target::File(ref buf, path) => {
198                     let outfile = File::create(path).map_err(DrawingErrorKind::DrawingError)?;
199                     let mut outfile = BufWriter::new(outfile);
200                     outfile
201                         .write_all(buf.as_ref())
202                         .map_err(DrawingErrorKind::DrawingError)?;
203                 }
204                 Target::Buffer(_) => {}
205                 #[cfg(feature = "deprecated_items")]
206                 Target::U8Buffer(ref actual, ref mut target) => {
207                     target.clear();
208                     target.extend_from_slice(actual.as_bytes());
209                 }
210             }
211             self.saved = true;
212         }
213         Ok(())
214     }
215 
draw_pixel( &mut self, point: BackendCoord, color: BackendColor, ) -> Result<(), DrawingErrorKind<Error>>216     fn draw_pixel(
217         &mut self,
218         point: BackendCoord,
219         color: BackendColor,
220     ) -> Result<(), DrawingErrorKind<Error>> {
221         if color.alpha == 0.0 {
222             return Ok(());
223         }
224         self.open_tag(
225             SVGTag::Rectangle,
226             &[
227                 ("x", &format!("{}", point.0)),
228                 ("y", &format!("{}", point.1)),
229                 ("width", "1"),
230                 ("height", "1"),
231                 ("stroke", "none"),
232                 ("opacity", &make_svg_opacity(color)),
233                 ("fill", &make_svg_color(color)),
234             ],
235             true,
236         );
237         Ok(())
238     }
239 
draw_line<S: BackendStyle>( &mut self, from: BackendCoord, to: BackendCoord, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>240     fn draw_line<S: BackendStyle>(
241         &mut self,
242         from: BackendCoord,
243         to: BackendCoord,
244         style: &S,
245     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
246         if style.color().alpha == 0.0 {
247             return Ok(());
248         }
249         self.open_tag(
250             SVGTag::Line,
251             &[
252                 ("opacity", &make_svg_opacity(style.color())),
253                 ("stroke", &make_svg_color(style.color())),
254                 ("stroke-width", &format!("{}", style.stroke_width())),
255                 ("x1", &format!("{}", from.0)),
256                 ("y1", &format!("{}", from.1)),
257                 ("x2", &format!("{}", to.0)),
258                 ("y2", &format!("{}", to.1)),
259             ],
260             true,
261         );
262         Ok(())
263     }
264 
draw_rect<S: BackendStyle>( &mut self, upper_left: BackendCoord, bottom_right: BackendCoord, style: &S, fill: bool, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>265     fn draw_rect<S: BackendStyle>(
266         &mut self,
267         upper_left: BackendCoord,
268         bottom_right: BackendCoord,
269         style: &S,
270         fill: bool,
271     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
272         if style.color().alpha == 0.0 {
273             return Ok(());
274         }
275 
276         let (fill, stroke) = if !fill {
277             ("none".to_string(), make_svg_color(style.color()))
278         } else {
279             (make_svg_color(style.color()), "none".to_string())
280         };
281 
282         self.open_tag(
283             SVGTag::Rectangle,
284             &[
285                 ("x", &format!("{}", upper_left.0)),
286                 ("y", &format!("{}", upper_left.1)),
287                 ("width", &format!("{}", bottom_right.0 - upper_left.0)),
288                 ("height", &format!("{}", bottom_right.1 - upper_left.1)),
289                 ("opacity", &make_svg_opacity(style.color())),
290                 ("fill", &fill),
291                 ("stroke", &stroke),
292             ],
293             true,
294         );
295 
296         Ok(())
297     }
298 
draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( &mut self, path: I, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>299     fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
300         &mut self,
301         path: I,
302         style: &S,
303     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
304         if style.color().alpha == 0.0 {
305             return Ok(());
306         }
307         self.open_tag(
308             SVGTag::Polyline,
309             &[
310                 ("fill", "none"),
311                 ("opacity", &make_svg_opacity(style.color())),
312                 ("stroke", &make_svg_color(style.color())),
313                 ("stroke-width", &format!("{}", style.stroke_width())),
314                 (
315                     "points",
316                     &path.into_iter().fold(String::new(), |mut s, (x, y)| {
317                         s.push_str(&format!("{},{} ", x, y));
318                         s
319                     }),
320                 ),
321             ],
322             true,
323         );
324         Ok(())
325     }
326 
fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( &mut self, path: I, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>327     fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
328         &mut self,
329         path: I,
330         style: &S,
331     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
332         if style.color().alpha == 0.0 {
333             return Ok(());
334         }
335         self.open_tag(
336             SVGTag::Polygon,
337             &[
338                 ("opacity", &make_svg_opacity(style.color())),
339                 ("fill", &make_svg_color(style.color())),
340                 (
341                     "points",
342                     &path.into_iter().fold(String::new(), |mut s, (x, y)| {
343                         s.push_str(&format!("{},{} ", x, y));
344                         s
345                     }),
346                 ),
347             ],
348             true,
349         );
350         Ok(())
351     }
352 
draw_circle<S: BackendStyle>( &mut self, center: BackendCoord, radius: u32, style: &S, fill: bool, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>353     fn draw_circle<S: BackendStyle>(
354         &mut self,
355         center: BackendCoord,
356         radius: u32,
357         style: &S,
358         fill: bool,
359     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
360         if style.color().alpha == 0.0 {
361             return Ok(());
362         }
363         let (stroke, fill) = if !fill {
364             (make_svg_color(style.color()), "none".to_string())
365         } else {
366             ("none".to_string(), make_svg_color(style.color()))
367         };
368         self.open_tag(
369             SVGTag::Circle,
370             &[
371                 ("cx", &format!("{}", center.0)),
372                 ("cy", &format!("{}", center.1)),
373                 ("r", &format!("{}", radius)),
374                 ("opacity", &make_svg_opacity(style.color())),
375                 ("fill", &fill),
376                 ("stroke", &stroke),
377                 ("stroke-width", &format!("{}", style.stroke_width())),
378             ],
379             true,
380         );
381         Ok(())
382     }
383 
draw_text<S: BackendTextStyle>( &mut self, text: &str, style: &S, pos: BackendCoord, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>384     fn draw_text<S: BackendTextStyle>(
385         &mut self,
386         text: &str,
387         style: &S,
388         pos: BackendCoord,
389     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
390         let color = style.color();
391         if color.alpha == 0.0 {
392             return Ok(());
393         }
394 
395         let (x0, y0) = pos;
396         let text_anchor = match style.anchor().h_pos {
397             HPos::Left => "start",
398             HPos::Right => "end",
399             HPos::Center => "middle",
400         };
401 
402         let dy = match style.anchor().v_pos {
403             VPos::Top => "0.76em",
404             VPos::Center => "0.5ex",
405             VPos::Bottom => "-0.5ex",
406         };
407 
408         #[cfg(feature = "debug")]
409         {
410             let ((fx0, fy0), (fx1, fy1)) =
411                 font.layout_box(text).map_err(DrawingErrorKind::FontError)?;
412             let x0 = match style.anchor().h_pos {
413                 HPos::Left => x0,
414                 HPos::Center => x0 - fx1 / 2 + fx0 / 2,
415                 HPos::Right => x0 - fx1 + fx0,
416             };
417             let y0 = match style.anchor().v_pos {
418                 VPos::Top => y0,
419                 VPos::Center => y0 - fy1 / 2 + fy0 / 2,
420                 VPos::Bottom => y0 - fy1 + fy0,
421             };
422             self.draw_rect(
423                 (x0, y0),
424                 (x0 + fx1 - fx0, y0 + fy1 - fy0),
425                 &crate::prelude::RED,
426                 false,
427             )
428             .unwrap();
429             self.draw_circle((x0, y0), 2, &crate::prelude::RED, false)
430                 .unwrap();
431         }
432 
433         let mut attrs = vec![
434             ("x", format!("{}", x0)),
435             ("y", format!("{}", y0)),
436             ("dy", dy.to_owned()),
437             ("text-anchor", text_anchor.to_string()),
438             ("font-family", style.family().as_str().to_string()),
439             ("font-size", format!("{}", style.size() / 1.24)),
440             ("opacity", make_svg_opacity(color)),
441             ("fill", make_svg_color(color)),
442         ];
443 
444         match style.style() {
445             FontStyle::Normal => {}
446             FontStyle::Bold => attrs.push(("font-weight", "bold".to_string())),
447             other_style => attrs.push(("font-style", other_style.as_str().to_string())),
448         };
449 
450         let trans = style.transform();
451         match trans {
452             FontTransform::Rotate90 => {
453                 attrs.push(("transform", format!("rotate(90, {}, {})", x0, y0)))
454             }
455             FontTransform::Rotate180 => {
456                 attrs.push(("transform", format!("rotate(180, {}, {})", x0, y0)));
457             }
458             FontTransform::Rotate270 => {
459                 attrs.push(("transform", format!("rotate(270, {}, {})", x0, y0)));
460             }
461             _ => {}
462         }
463 
464         self.open_tag(
465             SVGTag::Text,
466             attrs
467                 .iter()
468                 .map(|(a, b)| (*a, b.as_ref()))
469                 .collect::<Vec<_>>()
470                 .as_ref(),
471             false,
472         );
473 
474         Self::escape_and_push(self.target.get_mut(), text);
475         self.target.get_mut().push_str("\n");
476 
477         self.close_tag();
478 
479         Ok(())
480     }
481 
482     #[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
blit_bitmap<'b>( &mut self, pos: BackendCoord, (w, h): (u32, u32), src: &'b [u8], ) -> Result<(), DrawingErrorKind<Self::ErrorType>>483     fn blit_bitmap<'b>(
484         &mut self,
485         pos: BackendCoord,
486         (w, h): (u32, u32),
487         src: &'b [u8],
488     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
489         use image::png::PNGEncoder;
490 
491         let mut data = vec![0; 0];
492 
493         {
494             let cursor = Cursor::new(&mut data);
495 
496             let encoder = PNGEncoder::new(cursor);
497 
498             let color = image::ColorType::RGB(8);
499 
500             encoder.encode(src, w, h, color).map_err(|e| {
501                 DrawingErrorKind::DrawingError(Error::new(
502                     std::io::ErrorKind::Other,
503                     format!("Image error: {}", e),
504                 ))
505             })?;
506         }
507 
508         let padding = (3 - data.len() % 3) % 3;
509         for _ in 0..padding {
510             data.push(0);
511         }
512 
513         let mut rem_bits = 0;
514         let mut rem_num = 0;
515 
516         fn cvt_base64(from: u8) -> char {
517             (if from < 26 {
518                 b'A' + from
519             } else if from < 52 {
520                 b'a' + from - 26
521             } else if from < 62 {
522                 b'0' + from - 52
523             } else if from == 62 {
524                 b'+'
525             } else {
526                 b'/'
527             })
528             .into()
529         }
530 
531         let mut buf = String::new();
532         buf.push_str("data:png;base64,");
533 
534         for byte in data {
535             let value = (rem_bits << (6 - rem_num)) | (byte >> (rem_num + 2));
536             rem_bits = byte & ((1 << (2 + rem_num)) - 1);
537             rem_num += 2;
538 
539             buf.push(cvt_base64(value));
540             if rem_num == 6 {
541                 buf.push(cvt_base64(rem_bits));
542                 rem_bits = 0;
543                 rem_num = 0;
544             }
545         }
546 
547         for _ in 0..padding {
548             buf.pop();
549             buf.push('=');
550         }
551 
552         self.open_tag(
553             SVGTag::Image,
554             &[
555                 ("x", &format!("{}", pos.0)),
556                 ("y", &format!("{}", pos.1)),
557                 ("width", &format!("{}", w)),
558                 ("height", &format!("{}", h)),
559                 ("href", buf.as_str()),
560             ],
561             true,
562         );
563 
564         Ok(())
565     }
566 }
567 
568 impl Drop for SVGBackend<'_> {
drop(&mut self)569     fn drop(&mut self) {
570         if !self.saved {
571             // drop should not panic, so we ignore a failed present
572             let _ = self.present();
573         }
574     }
575 }
576 
577 #[cfg(test)]
578 mod test {
579     use super::*;
580     use plotters::element::Circle;
581     use plotters::prelude::*;
582     use plotters::style::text_anchor::{HPos, Pos, VPos};
583     use std::fs;
584     use std::path::Path;
585 
586     static DST_DIR: &str = "target/test/svg";
587 
checked_save_file(name: &str, content: &str)588     fn checked_save_file(name: &str, content: &str) {
589         /*
590           Please use the SVG file to manually verify the results.
591         */
592         assert!(!content.is_empty());
593         fs::create_dir_all(DST_DIR).unwrap();
594         let file_name = format!("{}.svg", name);
595         let file_path = Path::new(DST_DIR).join(file_name);
596         println!("{:?} created", file_path);
597         fs::write(file_path, &content).unwrap();
598     }
599 
draw_mesh_with_custom_ticks(tick_size: i32, test_name: &str)600     fn draw_mesh_with_custom_ticks(tick_size: i32, test_name: &str) {
601         let mut content: String = Default::default();
602         {
603             let root = SVGBackend::with_string(&mut content, (500, 500)).into_drawing_area();
604 
605             let mut chart = ChartBuilder::on(&root)
606                 .caption("This is a test", ("sans-serif", 20))
607                 .set_all_label_area_size(40)
608                 .build_cartesian_2d(0..10, 0..10)
609                 .unwrap();
610 
611             chart
612                 .configure_mesh()
613                 .set_all_tick_mark_size(tick_size)
614                 .draw()
615                 .unwrap();
616         }
617 
618         checked_save_file(test_name, &content);
619 
620         assert!(content.contains("This is a test"));
621     }
622 
623     #[test]
test_draw_mesh_no_ticks()624     fn test_draw_mesh_no_ticks() {
625         draw_mesh_with_custom_ticks(0, "test_draw_mesh_no_ticks");
626     }
627 
628     #[test]
test_draw_mesh_negative_ticks()629     fn test_draw_mesh_negative_ticks() {
630         draw_mesh_with_custom_ticks(-10, "test_draw_mesh_negative_ticks");
631     }
632 
633     #[test]
test_text_alignments()634     fn test_text_alignments() {
635         let mut content: String = Default::default();
636         {
637             let mut root = SVGBackend::with_string(&mut content, (500, 500));
638 
639             let style = TextStyle::from(("sans-serif", 20).into_font())
640                 .pos(Pos::new(HPos::Right, VPos::Top));
641             root.draw_text("right-align", &style, (150, 50)).unwrap();
642 
643             let style = style.pos(Pos::new(HPos::Center, VPos::Top));
644             root.draw_text("center-align", &style, (150, 150)).unwrap();
645 
646             let style = style.pos(Pos::new(HPos::Left, VPos::Top));
647             root.draw_text("left-align", &style, (150, 200)).unwrap();
648         }
649 
650         checked_save_file("test_text_alignments", &content);
651 
652         for svg_line in content.split("</text>") {
653             if let Some(anchor_and_rest) = svg_line.split("text-anchor=\"").nth(1) {
654                 if anchor_and_rest.starts_with("end") {
655                     assert!(anchor_and_rest.contains("right-align"))
656                 }
657                 if anchor_and_rest.starts_with("middle") {
658                     assert!(anchor_and_rest.contains("center-align"))
659                 }
660                 if anchor_and_rest.starts_with("start") {
661                     assert!(anchor_and_rest.contains("left-align"))
662                 }
663             }
664         }
665     }
666 
667     #[test]
test_text_draw()668     fn test_text_draw() {
669         let mut content: String = Default::default();
670         {
671             let root = SVGBackend::with_string(&mut content, (1500, 800)).into_drawing_area();
672             let root = root
673                 .titled("Image Title", ("sans-serif", 60).into_font())
674                 .unwrap();
675 
676             let mut chart = ChartBuilder::on(&root)
677                 .caption("All anchor point positions", ("sans-serif", 20))
678                 .set_all_label_area_size(40)
679                 .build_cartesian_2d(0..100, 0..50)
680                 .unwrap();
681 
682             chart
683                 .configure_mesh()
684                 .disable_x_mesh()
685                 .disable_y_mesh()
686                 .x_desc("X Axis")
687                 .y_desc("Y Axis")
688                 .draw()
689                 .unwrap();
690 
691             let ((x1, y1), (x2, y2), (x3, y3)) = ((-30, 30), (0, -30), (30, 30));
692 
693             for (dy, trans) in [
694                 FontTransform::None,
695                 FontTransform::Rotate90,
696                 FontTransform::Rotate180,
697                 FontTransform::Rotate270,
698             ]
699             .iter()
700             .enumerate()
701             {
702                 for (dx1, h_pos) in [HPos::Left, HPos::Right, HPos::Center].iter().enumerate() {
703                     for (dx2, v_pos) in [VPos::Top, VPos::Center, VPos::Bottom].iter().enumerate() {
704                         let x = 150_i32 + (dx1 as i32 * 3 + dx2 as i32) * 150;
705                         let y = 120 + dy as i32 * 150;
706                         let draw = |x, y, text| {
707                             root.draw(&Circle::new((x, y), 3, &BLACK.mix(0.5))).unwrap();
708                             let style = TextStyle::from(("sans-serif", 20).into_font())
709                                 .pos(Pos::new(*h_pos, *v_pos))
710                                 .transform(trans.clone());
711                             root.draw_text(text, &style, (x, y)).unwrap();
712                         };
713                         draw(x + x1, y + y1, "dood");
714                         draw(x + x2, y + y2, "dog");
715                         draw(x + x3, y + y3, "goog");
716                     }
717                 }
718             }
719         }
720 
721         checked_save_file("test_text_draw", &content);
722 
723         assert_eq!(content.matches("dog").count(), 36);
724         assert_eq!(content.matches("dood").count(), 36);
725         assert_eq!(content.matches("goog").count(), 36);
726     }
727 
728     #[test]
test_text_clipping()729     fn test_text_clipping() {
730         let mut content: String = Default::default();
731         {
732             let (width, height) = (500_i32, 500_i32);
733             let root = SVGBackend::with_string(&mut content, (width as u32, height as u32))
734                 .into_drawing_area();
735 
736             let style = TextStyle::from(("sans-serif", 20).into_font())
737                 .pos(Pos::new(HPos::Center, VPos::Center));
738             root.draw_text("TOP LEFT", &style, (0, 0)).unwrap();
739             root.draw_text("TOP CENTER", &style, (width / 2, 0))
740                 .unwrap();
741             root.draw_text("TOP RIGHT", &style, (width, 0)).unwrap();
742 
743             root.draw_text("MIDDLE LEFT", &style, (0, height / 2))
744                 .unwrap();
745             root.draw_text("MIDDLE RIGHT", &style, (width, height / 2))
746                 .unwrap();
747 
748             root.draw_text("BOTTOM LEFT", &style, (0, height)).unwrap();
749             root.draw_text("BOTTOM CENTER", &style, (width / 2, height))
750                 .unwrap();
751             root.draw_text("BOTTOM RIGHT", &style, (width, height))
752                 .unwrap();
753         }
754 
755         checked_save_file("test_text_clipping", &content);
756     }
757 
758     #[test]
test_series_labels()759     fn test_series_labels() {
760         let mut content = String::default();
761         {
762             let (width, height) = (500, 500);
763             let root = SVGBackend::with_string(&mut content, (width, height)).into_drawing_area();
764 
765             let mut chart = ChartBuilder::on(&root)
766                 .caption("All series label positions", ("sans-serif", 20))
767                 .set_all_label_area_size(40)
768                 .build_cartesian_2d(0..50, 0..50)
769                 .unwrap();
770 
771             chart
772                 .configure_mesh()
773                 .disable_x_mesh()
774                 .disable_y_mesh()
775                 .draw()
776                 .unwrap();
777 
778             chart
779                 .draw_series(std::iter::once(Circle::new((5, 15), 5, &RED)))
780                 .expect("Drawing error")
781                 .label("Series 1")
782                 .legend(|(x, y)| Circle::new((x, y), 3, RED.filled()));
783 
784             chart
785                 .draw_series(std::iter::once(Circle::new((5, 15), 10, &BLUE)))
786                 .expect("Drawing error")
787                 .label("Series 2")
788                 .legend(|(x, y)| Circle::new((x, y), 3, BLUE.filled()));
789 
790             for pos in vec![
791                 SeriesLabelPosition::UpperLeft,
792                 SeriesLabelPosition::MiddleLeft,
793                 SeriesLabelPosition::LowerLeft,
794                 SeriesLabelPosition::UpperMiddle,
795                 SeriesLabelPosition::MiddleMiddle,
796                 SeriesLabelPosition::LowerMiddle,
797                 SeriesLabelPosition::UpperRight,
798                 SeriesLabelPosition::MiddleRight,
799                 SeriesLabelPosition::LowerRight,
800                 SeriesLabelPosition::Coordinate(70, 70),
801             ]
802             .into_iter()
803             {
804                 chart
805                     .configure_series_labels()
806                     .border_style(&BLACK.mix(0.5))
807                     .position(pos)
808                     .draw()
809                     .expect("Drawing error");
810             }
811         }
812 
813         checked_save_file("test_series_labels", &content);
814     }
815 
816     #[test]
test_draw_pixel_alphas()817     fn test_draw_pixel_alphas() {
818         let mut content = String::default();
819         {
820             let (width, height) = (100_i32, 100_i32);
821             let root = SVGBackend::with_string(&mut content, (width as u32, height as u32))
822                 .into_drawing_area();
823             root.fill(&WHITE).unwrap();
824 
825             for i in -20..20 {
826                 let alpha = i as f64 * 0.1;
827                 root.draw_pixel((50 + i, 50 + i), &BLACK.mix(alpha))
828                     .unwrap();
829             }
830         }
831 
832         checked_save_file("test_draw_pixel_alphas", &content);
833     }
834 }
835