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