summaryrefslogtreecommitdiffstats
path: root/vendor/plotters-svg/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
commit9835e2ae736235810b4ea1c162ca5e65c547e770 (patch)
tree3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/plotters-svg/src
parentReleasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff)
downloadrustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz
rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/plotters-svg/src')
-rw-r--r--vendor/plotters-svg/src/lib.rs10
-rw-r--r--vendor/plotters-svg/src/svg.rs839
2 files changed, 849 insertions, 0 deletions
diff --git a/vendor/plotters-svg/src/lib.rs b/vendor/plotters-svg/src/lib.rs
new file mode 100644
index 000000000..d4bdbe7d3
--- /dev/null
+++ b/vendor/plotters-svg/src/lib.rs
@@ -0,0 +1,10 @@
+/*!
+ The Plotters SVG backend.
+
+ The plotters bitmap backend allows you to render images by Plotters into SVG vector graphs.
+
+ See the documentation for [SVGBackend](struct.SVGBackend.html) for more details.
+*/
+mod svg;
+
+pub use svg::SVGBackend;
diff --git a/vendor/plotters-svg/src/svg.rs b/vendor/plotters-svg/src/svg.rs
new file mode 100644
index 000000000..43bf36a24
--- /dev/null
+++ b/vendor/plotters-svg/src/svg.rs
@@ -0,0 +1,839 @@
+/*!
+The SVG image drawing backend
+*/
+
+use plotters_backend::{
+ text_anchor::{HPos, VPos},
+ BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind,
+ FontStyle, FontTransform,
+};
+
+use std::fs::File;
+#[allow(unused_imports)]
+use std::io::Cursor;
+use std::io::{BufWriter, Error, Write};
+use std::path::Path;
+use std::fmt::Write as _;
+
+fn make_svg_color(color: BackendColor) -> String {
+ let (r, g, b) = color.rgb;
+ return format!("#{:02X}{:02X}{:02X}", r, g, b);
+}
+
+fn make_svg_opacity(color: BackendColor) -> String {
+ return format!("{}", color.alpha);
+}
+
+enum Target<'a> {
+ File(String, &'a Path),
+ Buffer(&'a mut String),
+ // TODO: At this point we won't make the breaking change
+ // so the u8 buffer is still supported. But in 0.3, we definitely
+ // should get rid of this.
+ #[cfg(feature = "deprecated_items")]
+ U8Buffer(String, &'a mut Vec<u8>),
+}
+
+impl Target<'_> {
+ fn get_mut(&mut self) -> &mut String {
+ match self {
+ Target::File(ref mut buf, _) => buf,
+ Target::Buffer(buf) => buf,
+ #[cfg(feature = "deprecated_items")]
+ Target::U8Buffer(ref mut buf, _) => buf,
+ }
+ }
+}
+
+enum SVGTag {
+ Svg,
+ Circle,
+ Line,
+ Polygon,
+ Polyline,
+ Rectangle,
+ Text,
+ #[allow(dead_code)]
+ Image,
+}
+
+impl SVGTag {
+ fn to_tag_name(&self) -> &'static str {
+ match self {
+ SVGTag::Svg => "svg",
+ SVGTag::Circle => "circle",
+ SVGTag::Line => "line",
+ SVGTag::Polyline => "polyline",
+ SVGTag::Rectangle => "rect",
+ SVGTag::Text => "text",
+ SVGTag::Image => "image",
+ SVGTag::Polygon => "polygon",
+ }
+ }
+}
+
+/// The SVG image drawing backend
+pub struct SVGBackend<'a> {
+ target: Target<'a>,
+ size: (u32, u32),
+ tag_stack: Vec<SVGTag>,
+ saved: bool,
+}
+
+impl<'a> SVGBackend<'a> {
+ fn escape_and_push(buf: &mut String, value: &str) {
+ value.chars().for_each(|c| match c {
+ '<' => buf.push_str("&lt;"),
+ '>' => buf.push_str("&gt;"),
+ '&' => buf.push_str("&amp;"),
+ '"' => buf.push_str("&quot;"),
+ '\'' => buf.push_str("&apos;"),
+ other => buf.push(other),
+ });
+ }
+ fn open_tag(&mut self, tag: SVGTag, attr: &[(&str, &str)], close: bool) {
+ let buf = self.target.get_mut();
+ buf.push('<');
+ buf.push_str(tag.to_tag_name());
+ for (key, value) in attr {
+ buf.push(' ');
+ buf.push_str(key);
+ buf.push_str("=\"");
+ Self::escape_and_push(buf, value);
+ buf.push('\"');
+ }
+ if close {
+ buf.push_str("/>\n");
+ } else {
+ self.tag_stack.push(tag);
+ buf.push_str(">\n");
+ }
+ }
+
+ fn close_tag(&mut self) -> bool {
+ if let Some(tag) = self.tag_stack.pop() {
+ let buf = self.target.get_mut();
+ buf.push_str("</");
+ buf.push_str(tag.to_tag_name());
+ buf.push_str(">\n");
+ return true;
+ }
+ false
+ }
+
+ fn init_svg_file(&mut self, size: (u32, u32)) {
+ self.open_tag(
+ SVGTag::Svg,
+ &[
+ ("width", &format!("{}", size.0)),
+ ("height", &format!("{}", size.1)),
+ ("viewBox", &format!("0 0 {} {}", size.0, size.1)),
+ ("xmlns", "http://www.w3.org/2000/svg"),
+ ],
+ false,
+ );
+ }
+
+ /// Create a new SVG drawing backend
+ pub fn new<T: AsRef<Path> + ?Sized>(path: &'a T, size: (u32, u32)) -> Self {
+ let mut ret = Self {
+ target: Target::File(String::default(), path.as_ref()),
+ size,
+ tag_stack: vec![],
+ saved: false,
+ };
+
+ ret.init_svg_file(size);
+ ret
+ }
+
+ /// Create a new SVG drawing backend and store the document into a u8 vector
+ #[cfg(feature = "deprecated_items")]
+ #[deprecated(
+ note = "This will be replaced by `with_string`, consider use `with_string` to avoid breaking change in the future"
+ )]
+ pub fn with_buffer(buf: &'a mut Vec<u8>, size: (u32, u32)) -> Self {
+ let mut ret = Self {
+ target: Target::U8Buffer(String::default(), buf),
+ size,
+ tag_stack: vec![],
+ saved: false,
+ };
+
+ ret.init_svg_file(size);
+
+ ret
+ }
+
+ /// Create a new SVG drawing backend and store the document into a String buffer
+ pub fn with_string(buf: &'a mut String, size: (u32, u32)) -> Self {
+ let mut ret = Self {
+ target: Target::Buffer(buf),
+ size,
+ tag_stack: vec![],
+ saved: false,
+ };
+
+ ret.init_svg_file(size);
+
+ ret
+ }
+}
+
+impl<'a> DrawingBackend for SVGBackend<'a> {
+ type ErrorType = Error;
+
+ fn get_size(&self) -> (u32, u32) {
+ self.size
+ }
+
+ fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Error>> {
+ Ok(())
+ }
+
+ fn present(&mut self) -> Result<(), DrawingErrorKind<Error>> {
+ if !self.saved {
+ while self.close_tag() {}
+ match self.target {
+ Target::File(ref buf, path) => {
+ let outfile = File::create(path).map_err(DrawingErrorKind::DrawingError)?;
+ let mut outfile = BufWriter::new(outfile);
+ outfile
+ .write_all(buf.as_ref())
+ .map_err(DrawingErrorKind::DrawingError)?;
+ }
+ Target::Buffer(_) => {}
+ #[cfg(feature = "deprecated_items")]
+ Target::U8Buffer(ref actual, ref mut target) => {
+ target.clear();
+ target.extend_from_slice(actual.as_bytes());
+ }
+ }
+ self.saved = true;
+ }
+ Ok(())
+ }
+
+ fn draw_pixel(
+ &mut self,
+ point: BackendCoord,
+ color: BackendColor,
+ ) -> Result<(), DrawingErrorKind<Error>> {
+ if color.alpha == 0.0 {
+ return Ok(());
+ }
+ self.open_tag(
+ SVGTag::Rectangle,
+ &[
+ ("x", &format!("{}", point.0)),
+ ("y", &format!("{}", point.1)),
+ ("width", "1"),
+ ("height", "1"),
+ ("stroke", "none"),
+ ("opacity", &make_svg_opacity(color)),
+ ("fill", &make_svg_color(color)),
+ ],
+ true,
+ );
+ Ok(())
+ }
+
+ fn draw_line<S: BackendStyle>(
+ &mut self,
+ from: BackendCoord,
+ to: BackendCoord,
+ style: &S,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+ self.open_tag(
+ SVGTag::Line,
+ &[
+ ("opacity", &make_svg_opacity(style.color())),
+ ("stroke", &make_svg_color(style.color())),
+ ("stroke-width", &format!("{}", style.stroke_width())),
+ ("x1", &format!("{}", from.0)),
+ ("y1", &format!("{}", from.1)),
+ ("x2", &format!("{}", to.0)),
+ ("y2", &format!("{}", to.1)),
+ ],
+ true,
+ );
+ Ok(())
+ }
+
+ fn draw_rect<S: BackendStyle>(
+ &mut self,
+ upper_left: BackendCoord,
+ bottom_right: BackendCoord,
+ style: &S,
+ fill: bool,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+
+ let (fill, stroke) = if !fill {
+ ("none".to_string(), make_svg_color(style.color()))
+ } else {
+ (make_svg_color(style.color()), "none".to_string())
+ };
+
+ self.open_tag(
+ SVGTag::Rectangle,
+ &[
+ ("x", &format!("{}", upper_left.0)),
+ ("y", &format!("{}", upper_left.1)),
+ ("width", &format!("{}", bottom_right.0 - upper_left.0)),
+ ("height", &format!("{}", bottom_right.1 - upper_left.1)),
+ ("opacity", &make_svg_opacity(style.color())),
+ ("fill", &fill),
+ ("stroke", &stroke),
+ ],
+ true,
+ );
+
+ Ok(())
+ }
+
+ fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
+ &mut self,
+ path: I,
+ style: &S,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+ self.open_tag(
+ SVGTag::Polyline,
+ &[
+ ("fill", "none"),
+ ("opacity", &make_svg_opacity(style.color())),
+ ("stroke", &make_svg_color(style.color())),
+ ("stroke-width", &format!("{}", style.stroke_width())),
+ (
+ "points",
+ &path.into_iter().fold(String::new(), |mut s, (x, y)| {
+ write!(s, "{},{} ", x, y).ok();
+ s
+ }),
+ ),
+ ],
+ true,
+ );
+ Ok(())
+ }
+
+ fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
+ &mut self,
+ path: I,
+ style: &S,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+ self.open_tag(
+ SVGTag::Polygon,
+ &[
+ ("opacity", &make_svg_opacity(style.color())),
+ ("fill", &make_svg_color(style.color())),
+ (
+ "points",
+ &path.into_iter().fold(String::new(), |mut s, (x, y)| {
+ write!(s, "{},{} ", x, y).ok();
+ s
+ }),
+ ),
+ ],
+ true,
+ );
+ Ok(())
+ }
+
+ fn draw_circle<S: BackendStyle>(
+ &mut self,
+ center: BackendCoord,
+ radius: u32,
+ style: &S,
+ fill: bool,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ if style.color().alpha == 0.0 {
+ return Ok(());
+ }
+ let (stroke, fill) = if !fill {
+ (make_svg_color(style.color()), "none".to_string())
+ } else {
+ ("none".to_string(), make_svg_color(style.color()))
+ };
+ self.open_tag(
+ SVGTag::Circle,
+ &[
+ ("cx", &format!("{}", center.0)),
+ ("cy", &format!("{}", center.1)),
+ ("r", &format!("{}", radius)),
+ ("opacity", &make_svg_opacity(style.color())),
+ ("fill", &fill),
+ ("stroke", &stroke),
+ ("stroke-width", &format!("{}", style.stroke_width())),
+ ],
+ true,
+ );
+ Ok(())
+ }
+
+ fn draw_text<S: BackendTextStyle>(
+ &mut self,
+ text: &str,
+ style: &S,
+ pos: BackendCoord,
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ let color = style.color();
+ if color.alpha == 0.0 {
+ return Ok(());
+ }
+
+ let (x0, y0) = pos;
+ let text_anchor = match style.anchor().h_pos {
+ HPos::Left => "start",
+ HPos::Right => "end",
+ HPos::Center => "middle",
+ };
+
+ let dy = match style.anchor().v_pos {
+ VPos::Top => "0.76em",
+ VPos::Center => "0.5ex",
+ VPos::Bottom => "-0.5ex",
+ };
+
+ #[cfg(feature = "debug")]
+ {
+ let ((fx0, fy0), (fx1, fy1)) =
+ font.layout_box(text).map_err(DrawingErrorKind::FontError)?;
+ let x0 = match style.anchor().h_pos {
+ HPos::Left => x0,
+ HPos::Center => x0 - fx1 / 2 + fx0 / 2,
+ HPos::Right => x0 - fx1 + fx0,
+ };
+ let y0 = match style.anchor().v_pos {
+ VPos::Top => y0,
+ VPos::Center => y0 - fy1 / 2 + fy0 / 2,
+ VPos::Bottom => y0 - fy1 + fy0,
+ };
+ self.draw_rect(
+ (x0, y0),
+ (x0 + fx1 - fx0, y0 + fy1 - fy0),
+ &crate::prelude::RED,
+ false,
+ )
+ .unwrap();
+ self.draw_circle((x0, y0), 2, &crate::prelude::RED, false)
+ .unwrap();
+ }
+
+ let mut attrs = vec![
+ ("x", format!("{}", x0)),
+ ("y", format!("{}", y0)),
+ ("dy", dy.to_owned()),
+ ("text-anchor", text_anchor.to_string()),
+ ("font-family", style.family().as_str().to_string()),
+ ("font-size", format!("{}", style.size() / 1.24)),
+ ("opacity", make_svg_opacity(color)),
+ ("fill", make_svg_color(color)),
+ ];
+
+ match style.style() {
+ FontStyle::Normal => {}
+ FontStyle::Bold => attrs.push(("font-weight", "bold".to_string())),
+ other_style => attrs.push(("font-style", other_style.as_str().to_string())),
+ };
+
+ let trans = style.transform();
+ match trans {
+ FontTransform::Rotate90 => {
+ attrs.push(("transform", format!("rotate(90, {}, {})", x0, y0)))
+ }
+ FontTransform::Rotate180 => {
+ attrs.push(("transform", format!("rotate(180, {}, {})", x0, y0)));
+ }
+ FontTransform::Rotate270 => {
+ attrs.push(("transform", format!("rotate(270, {}, {})", x0, y0)));
+ }
+ _ => {}
+ }
+
+ self.open_tag(
+ SVGTag::Text,
+ attrs
+ .iter()
+ .map(|(a, b)| (*a, b.as_ref()))
+ .collect::<Vec<_>>()
+ .as_ref(),
+ false,
+ );
+
+ Self::escape_and_push(self.target.get_mut(), text);
+ self.target.get_mut().push('\n');
+
+ self.close_tag();
+
+ Ok(())
+ }
+
+ #[cfg(all(not(target_arch = "wasm32"), feature = "image"))]
+ fn blit_bitmap<'b>(
+ &mut self,
+ pos: BackendCoord,
+ (w, h): (u32, u32),
+ src: &'b [u8],
+ ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
+ use image::codecs::png::PngEncoder;
+ use image::ImageEncoder;
+
+ let mut data = vec![0; 0];
+
+ {
+ let cursor = Cursor::new(&mut data);
+
+ let encoder = PngEncoder::new(cursor);
+
+ let color = image::ColorType::Rgb8;
+
+ encoder.write_image(src, w, h, color).map_err(|e| {
+ DrawingErrorKind::DrawingError(Error::new(
+ std::io::ErrorKind::Other,
+ format!("Image error: {}", e),
+ ))
+ })?;
+ }
+
+ let padding = (3 - data.len() % 3) % 3;
+ for _ in 0..padding {
+ data.push(0);
+ }
+
+ let mut rem_bits = 0;
+ let mut rem_num = 0;
+
+ fn cvt_base64(from: u8) -> char {
+ (if from < 26 {
+ b'A' + from
+ } else if from < 52 {
+ b'a' + from - 26
+ } else if from < 62 {
+ b'0' + from - 52
+ } else if from == 62 {
+ b'+'
+ } else {
+ b'/'
+ })
+ .into()
+ }
+
+ let mut buf = String::new();
+ buf.push_str("data:png;base64,");
+
+ for byte in data {
+ let value = (rem_bits << (6 - rem_num)) | (byte >> (rem_num + 2));
+ rem_bits = byte & ((1 << (2 + rem_num)) - 1);
+ rem_num += 2;
+
+ buf.push(cvt_base64(value));
+ if rem_num == 6 {
+ buf.push(cvt_base64(rem_bits));
+ rem_bits = 0;
+ rem_num = 0;
+ }
+ }
+
+ for _ in 0..padding {
+ buf.pop();
+ buf.push('=');
+ }
+
+ self.open_tag(
+ SVGTag::Image,
+ &[
+ ("x", &format!("{}", pos.0)),
+ ("y", &format!("{}", pos.1)),
+ ("width", &format!("{}", w)),
+ ("height", &format!("{}", h)),
+ ("href", buf.as_str()),
+ ],
+ true,
+ );
+
+ Ok(())
+ }
+}
+
+impl Drop for SVGBackend<'_> {
+ fn drop(&mut self) {
+ if !self.saved {
+ // drop should not panic, so we ignore a failed present
+ let _ = self.present();
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use plotters::element::Circle;
+ use plotters::prelude::{
+ ChartBuilder, Color, IntoDrawingArea, IntoFont, SeriesLabelPosition, TextStyle, BLACK,
+ BLUE, RED, WHITE,
+ };
+ use plotters::style::text_anchor::{HPos, Pos, VPos};
+ use std::fs;
+ use std::path::Path;
+
+ static DST_DIR: &str = "target/test/svg";
+
+ fn checked_save_file(name: &str, content: &str) {
+ /*
+ Please use the SVG file to manually verify the results.
+ */
+ assert!(!content.is_empty());
+ fs::create_dir_all(DST_DIR).unwrap();
+ let file_name = format!("{}.svg", name);
+ let file_path = Path::new(DST_DIR).join(file_name);
+ println!("{:?} created", file_path);
+ fs::write(file_path, &content).unwrap();
+ }
+
+ fn draw_mesh_with_custom_ticks(tick_size: i32, test_name: &str) {
+ let mut content: String = Default::default();
+ {
+ let root = SVGBackend::with_string(&mut content, (500, 500)).into_drawing_area();
+
+ let mut chart = ChartBuilder::on(&root)
+ .caption("This is a test", ("sans-serif", 20u32))
+ .set_all_label_area_size(40u32)
+ .build_cartesian_2d(0..10, 0..10)
+ .unwrap();
+
+ chart
+ .configure_mesh()
+ .set_all_tick_mark_size(tick_size)
+ .draw()
+ .unwrap();
+ }
+
+ checked_save_file(test_name, &content);
+
+ assert!(content.contains("This is a test"));
+ }
+
+ #[test]
+ fn test_draw_mesh_no_ticks() {
+ draw_mesh_with_custom_ticks(0, "test_draw_mesh_no_ticks");
+ }
+
+ #[test]
+ fn test_draw_mesh_negative_ticks() {
+ draw_mesh_with_custom_ticks(-10, "test_draw_mesh_negative_ticks");
+ }
+
+ #[test]
+ fn test_text_alignments() {
+ let mut content: String = Default::default();
+ {
+ let mut root = SVGBackend::with_string(&mut content, (500, 500));
+
+ let style = TextStyle::from(("sans-serif", 20).into_font())
+ .pos(Pos::new(HPos::Right, VPos::Top));
+ root.draw_text("right-align", &style, (150, 50)).unwrap();
+
+ let style = style.pos(Pos::new(HPos::Center, VPos::Top));
+ root.draw_text("center-align", &style, (150, 150)).unwrap();
+
+ let style = style.pos(Pos::new(HPos::Left, VPos::Top));
+ root.draw_text("left-align", &style, (150, 200)).unwrap();
+ }
+
+ checked_save_file("test_text_alignments", &content);
+
+ for svg_line in content.split("</text>") {
+ if let Some(anchor_and_rest) = svg_line.split("text-anchor=\"").nth(1) {
+ if anchor_and_rest.starts_with("end") {
+ assert!(anchor_and_rest.contains("right-align"))
+ }
+ if anchor_and_rest.starts_with("middle") {
+ assert!(anchor_and_rest.contains("center-align"))
+ }
+ if anchor_and_rest.starts_with("start") {
+ assert!(anchor_and_rest.contains("left-align"))
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn test_text_draw() {
+ let mut content: String = Default::default();
+ {
+ let root = SVGBackend::with_string(&mut content, (1500, 800)).into_drawing_area();
+ let root = root
+ .titled("Image Title", ("sans-serif", 60).into_font())
+ .unwrap();
+
+ let mut chart = ChartBuilder::on(&root)
+ .caption("All anchor point positions", ("sans-serif", 20u32))
+ .set_all_label_area_size(40u32)
+ .build_cartesian_2d(0..100i32, 0..50i32)
+ .unwrap();
+
+ chart
+ .configure_mesh()
+ .disable_x_mesh()
+ .disable_y_mesh()
+ .x_desc("X Axis")
+ .y_desc("Y Axis")
+ .draw()
+ .unwrap();
+
+ let ((x1, y1), (x2, y2), (x3, y3)) = ((-30, 30), (0, -30), (30, 30));
+
+ for (dy, trans) in [
+ FontTransform::None,
+ FontTransform::Rotate90,
+ FontTransform::Rotate180,
+ FontTransform::Rotate270,
+ ]
+ .iter()
+ .enumerate()
+ {
+ for (dx1, h_pos) in [HPos::Left, HPos::Right, HPos::Center].iter().enumerate() {
+ for (dx2, v_pos) in [VPos::Top, VPos::Center, VPos::Bottom].iter().enumerate() {
+ let x = 150_i32 + (dx1 as i32 * 3 + dx2 as i32) * 150;
+ let y = 120 + dy as i32 * 150;
+ let draw = |x, y, text| {
+ root.draw(&Circle::new((x, y), 3, &BLACK.mix(0.5))).unwrap();
+ let style = TextStyle::from(("sans-serif", 20).into_font())
+ .pos(Pos::new(*h_pos, *v_pos))
+ .transform(trans.clone());
+ root.draw_text(text, &style, (x, y)).unwrap();
+ };
+ draw(x + x1, y + y1, "dood");
+ draw(x + x2, y + y2, "dog");
+ draw(x + x3, y + y3, "goog");
+ }
+ }
+ }
+ }
+
+ checked_save_file("test_text_draw", &content);
+
+ assert_eq!(content.matches("dog").count(), 36);
+ assert_eq!(content.matches("dood").count(), 36);
+ assert_eq!(content.matches("goog").count(), 36);
+ }
+
+ #[test]
+ fn test_text_clipping() {
+ let mut content: String = Default::default();
+ {
+ let (width, height) = (500_i32, 500_i32);
+ let root = SVGBackend::with_string(&mut content, (width as u32, height as u32))
+ .into_drawing_area();
+
+ let style = TextStyle::from(("sans-serif", 20).into_font())
+ .pos(Pos::new(HPos::Center, VPos::Center));
+ root.draw_text("TOP LEFT", &style, (0, 0)).unwrap();
+ root.draw_text("TOP CENTER", &style, (width / 2, 0))
+ .unwrap();
+ root.draw_text("TOP RIGHT", &style, (width, 0)).unwrap();
+
+ root.draw_text("MIDDLE LEFT", &style, (0, height / 2))
+ .unwrap();
+ root.draw_text("MIDDLE RIGHT", &style, (width, height / 2))
+ .unwrap();
+
+ root.draw_text("BOTTOM LEFT", &style, (0, height)).unwrap();
+ root.draw_text("BOTTOM CENTER", &style, (width / 2, height))
+ .unwrap();
+ root.draw_text("BOTTOM RIGHT", &style, (width, height))
+ .unwrap();
+ }
+
+ checked_save_file("test_text_clipping", &content);
+ }
+
+ #[test]
+ fn test_series_labels() {
+ let mut content = String::default();
+ {
+ let (width, height) = (500, 500);
+ let root = SVGBackend::with_string(&mut content, (width, height)).into_drawing_area();
+
+ let mut chart = ChartBuilder::on(&root)
+ .caption("All series label positions", ("sans-serif", 20u32))
+ .set_all_label_area_size(40u32)
+ .build_cartesian_2d(0..50i32, 0..50i32)
+ .unwrap();
+
+ chart
+ .configure_mesh()
+ .disable_x_mesh()
+ .disable_y_mesh()
+ .draw()
+ .unwrap();
+
+ chart
+ .draw_series(std::iter::once(Circle::new((5, 15), 5u32, &RED)))
+ .expect("Drawing error")
+ .label("Series 1")
+ .legend(|(x, y)| Circle::new((x, y), 3u32, RED.filled()));
+
+ chart
+ .draw_series(std::iter::once(Circle::new((5, 15), 10u32, &BLUE)))
+ .expect("Drawing error")
+ .label("Series 2")
+ .legend(|(x, y)| Circle::new((x, y), 3u32, BLUE.filled()));
+
+ for pos in vec![
+ SeriesLabelPosition::UpperLeft,
+ SeriesLabelPosition::MiddleLeft,
+ SeriesLabelPosition::LowerLeft,
+ SeriesLabelPosition::UpperMiddle,
+ SeriesLabelPosition::MiddleMiddle,
+ SeriesLabelPosition::LowerMiddle,
+ SeriesLabelPosition::UpperRight,
+ SeriesLabelPosition::MiddleRight,
+ SeriesLabelPosition::LowerRight,
+ SeriesLabelPosition::Coordinate(70, 70),
+ ]
+ .into_iter()
+ {
+ chart
+ .configure_series_labels()
+ .border_style(&BLACK.mix(0.5))
+ .position(pos)
+ .draw()
+ .expect("Drawing error");
+ }
+ }
+
+ checked_save_file("test_series_labels", &content);
+ }
+
+ #[test]
+ fn test_draw_pixel_alphas() {
+ let mut content = String::default();
+ {
+ let (width, height) = (100_i32, 100_i32);
+ let root = SVGBackend::with_string(&mut content, (width as u32, height as u32))
+ .into_drawing_area();
+ root.fill(&WHITE).unwrap();
+
+ for i in -20..20 {
+ let alpha = i as f64 * 0.1;
+ root.draw_pixel((50 + i, 50 + i), &BLACK.mix(alpha))
+ .unwrap();
+ }
+ }
+
+ checked_save_file("test_draw_pixel_alphas", &content);
+ }
+}