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