diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-18 02:49:42 +0000 |
commit | 837b550238aa671a591ccf282dddeab29cadb206 (patch) | |
tree | 914b6b8862bace72bd3245ca184d374b08d8a672 /vendor/plotters-backend/src | |
parent | Adding debian version 1.70.0+dfsg2-1. (diff) | |
download | rustc-837b550238aa671a591ccf282dddeab29cadb206.tar.xz rustc-837b550238aa671a591ccf282dddeab29cadb206.zip |
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/plotters-backend/src')
-rw-r--r-- | vendor/plotters-backend/src/lib.rs | 328 | ||||
-rw-r--r-- | vendor/plotters-backend/src/rasterizer/circle.rs | 342 | ||||
-rw-r--r-- | vendor/plotters-backend/src/rasterizer/line.rs | 123 | ||||
-rw-r--r-- | vendor/plotters-backend/src/rasterizer/mod.rs | 41 | ||||
-rw-r--r-- | vendor/plotters-backend/src/rasterizer/path.rs | 151 | ||||
-rw-r--r-- | vendor/plotters-backend/src/rasterizer/polygon.rs | 242 | ||||
-rw-r--r-- | vendor/plotters-backend/src/rasterizer/rect.rs | 57 | ||||
-rw-r--r-- | vendor/plotters-backend/src/style.rs | 33 | ||||
-rw-r--r-- | vendor/plotters-backend/src/text.rs | 245 |
9 files changed, 1562 insertions, 0 deletions
diff --git a/vendor/plotters-backend/src/lib.rs b/vendor/plotters-backend/src/lib.rs new file mode 100644 index 000000000..d4fd904c9 --- /dev/null +++ b/vendor/plotters-backend/src/lib.rs @@ -0,0 +1,328 @@ +/*! + The Plotters backend API crate. This is a part of Plotters, the Rust drawing and plotting library, for more details regarding the entire + Plotters project, please check the [main crate](https://crates.io/crates/plotters). + + This is the crate that used as the connector between Plotters and different backend crates. Since Plotters 0.3, all the backends has been + hosted as seperate crates for the usability and maintainability reasons. + + At the same time, Plotters is now supporting third-party backends and all the backends are now supports "plug-and-play": + To use a external backend, just depends on both the Plotters main crate and the third-party backend crate. + + # Notes for implementing Backend for Plotters + + To create a new Plotters backend, this crate should be imported to the crate and the trait [DrawingBackend](trait.DrawingBackend.html) should + be implemented. It's highly recommended that the third-party backend uses `plotters-backend` by version specification `^x.y.*`. + For more details, see the [compatibility note](#compatibility-note). + + If the backend only implements [DrawingBackend::draw_pixel](trait.DrawingBackend.html#tymethod.draw_pixel), the default CPU rasterizer will be + used to give your backend ability of drawing different shapes. For those backend that supports advanced drawing instructions, such as, GPU + acelerated shape drawing, all the provided trait method can be overriden from the specific backend code. + + If your backend have text rendering ability, you may want to override the [DrawingBackend::estimate_text_size](trait.DrawingBackend.html#tymethod.estimate_text_size) + to avoid wrong spacing, since the Plotters default text handling code may behaves differently from the backend in terms of text rendering. + + ## Animated or Realtime Rendering + Backend might render the image realtimely/animated, for example, a GTK backend for realtime display or a GIF image rendering. To support these + features, you need to play with `ensure_prepared` and `present` method. The following figure illustrates how Plotters operates a drawing backend. + + - `ensure_prepared` - Called before each time when plotters want to draw. This function should initialize the backend for current frame, if the backend is already prepared + for a frame, this function should simply do nothing. + - `present` - Called when plotters want to finish current frame drawing + + + ```text + .ensure_prepared() && + +-------------+ +-------------+ .draw_pixels() +--------------+ drop + |Start drwaing|--->|Ready to draw| ------------------------+---->|Finish 1 frame| ---------> + +-------------+ +-------------+ | +--------------+ + ^ ^ | | + | +------------------------------- + | + | continue drawing | + +----------------------------------------------------------------+ + start render the next frame + .present() + ``` + - For both animated and static drawing, `DrawingBackend::present` indicates current frame should be flushed. + - For both animated and static drawing, `DrawingBackend::ensure_prepared` is called every time when plotters need to draw. + - For static drawing, the `DrawingBackend::present` is only called once manually, or from the Drop impl for the backend. + - For dynamic drawing, frames are defined by invocation of `DrawingBackend::present`, everything prior the invocation should belongs to previous frame + + # Compatibility Note + Since Plotters v0.3, plotters use the "plug-and-play" schema to import backends, this requires both Plotters and the backend crates depdens on a + same version of `plotters-backend` crate. This crate (`plotters-backend`) will enforce that any revision (means the last number in a version number) + won't contains breaking change - both on the Plotters side and backend side. + + Plotters main crate is always importing the backend crate with version specification `plotters-backend = "^<major>.<minor>*"`. + It's highly recommended that all the external crates follows the same rule to import `plotters-backend` depdendency, to avoid protential breaking + caused by `plotters-backend` crates gets a revision update. + + We also impose a versioning rule with `plotters` and some backends: + The compatible main crate (`plotters`) and this crate (`plotters-backend`) are always use the same major and minor version number. + All the plotters main crate and second-party backends with version "x.y.*" should be compatible, and they should depens on the latest version of `plotters-backend x.y.*` + +*/ +use std::error::Error; + +pub mod rasterizer; +mod style; +mod text; + +pub use style::{BackendColor, BackendStyle}; +pub use text::{text_anchor, BackendTextStyle, FontFamily, FontStyle, FontTransform}; + +use text_anchor::{HPos, VPos}; + +/// A coordinate in the pixel-based backend. The coordinate follows the framebuffer's convention, +/// which defines the top-left point as (0, 0). +pub type BackendCoord = (i32, i32); + +/// The error produced by a drawing backend. +#[derive(Debug)] +pub enum DrawingErrorKind<E: Error + Send + Sync> { + /// A drawing backend error + DrawingError(E), + /// A font rendering error + FontError(Box<dyn Error + Send + Sync + 'static>), +} + +impl<E: Error + Send + Sync> std::fmt::Display for DrawingErrorKind<E> { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + DrawingErrorKind::DrawingError(e) => write!(fmt, "Drawing backend error: {}", e), + DrawingErrorKind::FontError(e) => write!(fmt, "Font loading error: {}", e), + } + } +} + +impl<E: Error + Send + Sync> Error for DrawingErrorKind<E> {} + +/// The drawing backend trait, which implements the low-level drawing APIs. +/// This trait has a set of default implementation. And the minimal requirement of +/// implementing a drawing backend is implementing the `draw_pixel` function. +/// +/// If the drawing backend supports vector graphics, the other drawing APIs should be +/// override by the backend specific implementation. Otherwise, the default implementation +/// will use the pixel-based approach to draw other types of low-level shapes. +pub trait DrawingBackend: Sized { + /// The error type reported by the backend + type ErrorType: Error + Send + Sync; + + /// Get the dimension of the drawing backend in pixels + fn get_size(&self) -> (u32, u32); + + /// Ensure the backend is ready to draw + fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>; + + /// Finalize the drawing step and present all the changes. + /// This is used as the real-time rendering support. + /// The backend may implement in the following way, when `ensure_prepared` is called + /// it checks if it needs a fresh buffer and `present` is called rendering all the + /// pending changes on the screen. + fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>; + + /// Draw a pixel on the drawing backend + /// - `point`: The backend pixel-based coordinate to draw + /// - `color`: The color of the pixel + fn draw_pixel( + &mut self, + point: BackendCoord, + color: BackendColor, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>>; + + /// Draw a line on the drawing backend + /// - `from`: The start point of the line + /// - `to`: The end point of the line + /// - `style`: The style of the line + fn draw_line<S: BackendStyle>( + &mut self, + from: BackendCoord, + to: BackendCoord, + style: &S, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + rasterizer::draw_line(self, from, to, style) + } + + /// Draw a rectangle on the drawing backend + /// - `upper_left`: The coordinate of the upper-left corner of the rect + /// - `bottom_right`: The coordinate of the bottom-right corner of the rect + /// - `style`: The style + /// - `fill`: If the rectangle should be filled + fn draw_rect<S: BackendStyle>( + &mut self, + upper_left: BackendCoord, + bottom_right: BackendCoord, + style: &S, + fill: bool, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + rasterizer::draw_rect(self, upper_left, bottom_right, style, fill) + } + + /// Draw a path on the drawing backend + /// - `path`: The iterator of key points of the path + /// - `style`: The style of the path + 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(()); + } + + if style.stroke_width() == 1 { + let mut begin: Option<BackendCoord> = None; + for end in path.into_iter() { + if let Some(begin) = begin { + let result = self.draw_line(begin, end, style); + #[allow(clippy::question_mark)] + if result.is_err() { + return result; + } + } + begin = Some(end); + } + } else { + let p: Vec<_> = path.into_iter().collect(); + let v = rasterizer::polygonize(&p[..], style.stroke_width()); + return self.fill_polygon(v, &style.color()); + } + Ok(()) + } + + /// Draw a circle on the drawing backend + /// - `center`: The center coordinate of the circle + /// - `radius`: The radius of the circle + /// - `style`: The style of the shape + /// - `fill`: If the circle should be filled + fn draw_circle<S: BackendStyle>( + &mut self, + center: BackendCoord, + radius: u32, + style: &S, + fill: bool, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + rasterizer::draw_circle(self, center, radius, style, fill) + } + + fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( + &mut self, + vert: I, + style: &S, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + let vert_buf: Vec<_> = vert.into_iter().collect(); + + rasterizer::fill_polygon(self, &vert_buf[..], style) + } + + /// Draw a text on the drawing backend + /// - `text`: The text to draw + /// - `style`: The text style + /// - `pos` : The text anchor point + fn draw_text<TStyle: BackendTextStyle>( + &mut self, + text: &str, + style: &TStyle, + pos: BackendCoord, + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + let color = style.color(); + if color.alpha == 0.0 { + return Ok(()); + } + + let layout = style + .layout_box(text) + .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?; + let ((min_x, min_y), (max_x, max_y)) = layout; + let width = (max_x - min_x) as i32; + let height = (max_y - min_y) as i32; + let dx = match style.anchor().h_pos { + HPos::Left => 0, + HPos::Right => -width, + HPos::Center => -width / 2, + }; + let dy = match style.anchor().v_pos { + VPos::Top => 0, + VPos::Center => -height / 2, + VPos::Bottom => -height, + }; + let trans = style.transform(); + let (w, h) = self.get_size(); + match style.draw(text, (0, 0), |x, y, color| { + let (x, y) = trans.transform(x + dx - min_x, y + dy - min_y); + let (x, y) = (pos.0 + x, pos.1 + y); + if x >= 0 && x < w as i32 && y >= 0 && y < h as i32 { + self.draw_pixel((x, y), color) + } else { + Ok(()) + } + }) { + Ok(drawing_result) => drawing_result, + Err(font_error) => Err(DrawingErrorKind::FontError(Box::new(font_error))), + } + } + + /// Estimate the size of the horizontal text if rendered on this backend. + /// This is important because some of the backend may not have font ability. + /// Thus this allows those backend reports proper value rather than ask the + /// font rasterizer for that. + /// + /// - `text`: The text to estimate + /// - `font`: The font to estimate + /// - *Returns* The estimated text size + fn estimate_text_size<TStyle: BackendTextStyle>( + &self, + text: &str, + style: &TStyle, + ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> { + let layout = style + .layout_box(text) + .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?; + Ok(( + ((layout.1).0 - (layout.0).0) as u32, + ((layout.1).1 - (layout.0).1) as u32, + )) + } + + /// Blit a bitmap on to the backend. + /// + /// - `text`: pos the left upper conner of the bitmap to blit + /// - `src`: The source of the image + /// + /// TODO: The default implementation of bitmap blitting assumes that the bitmap is RGB, but + /// this may not be the case. But for bitmap backend it's actually ok if we use the bitmap + /// element that matches the pixel format, but we need to fix this. + fn blit_bitmap( + &mut self, + pos: BackendCoord, + (iw, ih): (u32, u32), + src: &[u8], + ) -> Result<(), DrawingErrorKind<Self::ErrorType>> { + let (w, h) = self.get_size(); + + for dx in 0..iw { + if pos.0 + dx as i32 >= w as i32 { + break; + } + for dy in 0..ih { + if pos.1 + dy as i32 >= h as i32 { + break; + } + // FIXME: This assume we have RGB image buffer + let r = src[(dx + dy * w) as usize * 3]; + let g = src[(dx + dy * w) as usize * 3 + 1]; + let b = src[(dx + dy * w) as usize * 3 + 2]; + let color = BackendColor { + alpha: 1.0, + rgb: (r, g, b), + }; + let result = self.draw_pixel((pos.0 + dx as i32, pos.1 + dy as i32), color); + #[allow(clippy::question_mark)] + if result.is_err() { + return result; + } + } + } + + Ok(()) + } +} diff --git a/vendor/plotters-backend/src/rasterizer/circle.rs b/vendor/plotters-backend/src/rasterizer/circle.rs new file mode 100644 index 000000000..0584167a1 --- /dev/null +++ b/vendor/plotters-backend/src/rasterizer/circle.rs @@ -0,0 +1,342 @@ +use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind}; + +fn draw_part_a< + B: DrawingBackend, + Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>, +>( + height: f64, + radius: u32, + mut draw: Draw, +) -> Result<(), DrawingErrorKind<B::ErrorType>> { + let half_width = (radius as f64 * radius as f64 + - (radius as f64 - height) * (radius as f64 - height)) + .sqrt(); + + let x0 = (-half_width).ceil() as i32; + let x1 = half_width.floor() as i32; + + let y0 = (radius as f64 - height).ceil(); + + for x in x0..=x1 { + let y1 = (radius as f64 * radius as f64 - x as f64 * x as f64).sqrt(); + check_result!(draw(x, (y0, y1))); + } + + Ok(()) +} + +fn draw_part_b< + B: DrawingBackend, + Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>, +>( + from: f64, + size: f64, + mut draw: Draw, +) -> Result<(), DrawingErrorKind<B::ErrorType>> { + let from = from.floor(); + for x in (from - size).floor() as i32..=from as i32 { + check_result!(draw(x, (-x as f64, x as f64))); + } + Ok(()) +} + +fn draw_part_c< + B: DrawingBackend, + Draw: FnMut(i32, (f64, f64)) -> Result<(), DrawingErrorKind<B::ErrorType>>, +>( + r: i32, + r_limit: i32, + mut draw: Draw, +) -> Result<(), DrawingErrorKind<B::ErrorType>> { + let half_size = r as f64 / (2f64).sqrt(); + + let (x0, x1) = ((-half_size).ceil() as i32, half_size.floor() as i32); + + for x in x0..x1 { + let outter_y0 = ((r_limit as f64) * (r_limit as f64) - x as f64 * x as f64).sqrt(); + let inner_y0 = r as f64 - 1.0; + let mut y1 = outter_y0.min(inner_y0); + let y0 = ((r as f64) * (r as f64) - x as f64 * x as f64).sqrt(); + + if y0 > y1 { + y1 = y0.ceil(); + if y1 >= r as f64 { + continue; + } + } + + check_result!(draw(x, (y0, y1))); + } + + for x in x1 + 1..r { + let outter_y0 = ((r_limit as f64) * (r_limit as f64) - x as f64 * x as f64).sqrt(); + let inner_y0 = r as f64 - 1.0; + let y0 = outter_y0.min(inner_y0); + let y1 = x as f64; + + if y1 < y0 { + check_result!(draw(x, (y0, y1 + 1.0))); + check_result!(draw(-x, (y0, y1 + 1.0))); + } + } + + Ok(()) +} + +fn draw_sweep_line<B: DrawingBackend, S: BackendStyle>( + b: &mut B, + style: &S, + (x0, y0): BackendCoord, + (dx, dy): (i32, i32), + p0: i32, + (s, e): (f64, f64), +) -> Result<(), DrawingErrorKind<B::ErrorType>> { + let mut s = if dx < 0 || dy < 0 { -s } else { s }; + let mut e = if dx < 0 || dy < 0 { -e } else { e }; + if s > e { + std::mem::swap(&mut s, &mut e); + } + + let vs = s.ceil() - s; + let ve = e - e.floor(); + + if dx == 0 { + check_result!(b.draw_line( + (p0 + x0, s.ceil() as i32 + y0), + (p0 + x0, e.floor() as i32 + y0), + &style.color() + )); + check_result!(b.draw_pixel((p0 + x0, s.ceil() as i32 + y0 - 1), style.color().mix(vs))); + check_result!(b.draw_pixel((p0 + x0, e.floor() as i32 + y0 + 1), style.color().mix(ve))); + } else { + check_result!(b.draw_line( + (s.ceil() as i32 + x0, p0 + y0), + (e.floor() as i32 + x0, p0 + y0), + &style.color() + )); + check_result!(b.draw_pixel((s.ceil() as i32 + x0 - 1, p0 + y0), style.color().mix(vs))); + check_result!(b.draw_pixel((e.floor() as i32 + x0 + 1, p0 + y0), style.color().mix(ve))); + } + + Ok(()) +} + +fn draw_annulus<B: DrawingBackend, S: BackendStyle>( + b: &mut B, + center: BackendCoord, + radius: (u32, u32), + style: &S, +) -> Result<(), DrawingErrorKind<B::ErrorType>> { + let a0 = ((radius.0 - radius.1) as f64).min(radius.0 as f64 * (1.0 - 1.0 / (2f64).sqrt())); + let a1 = (radius.0 as f64 - a0 - radius.1 as f64).max(0.0); + + check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line( + b, + style, + center, + (0, 1), + p, + r + ))); + check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line( + b, + style, + center, + (0, -1), + p, + r + ))); + check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line( + b, + style, + center, + (1, 0), + p, + r + ))); + check_result!(draw_part_a::<B, _>(a0, radius.0, |p, r| draw_sweep_line( + b, + style, + center, + (-1, 0), + p, + r + ))); + + if a1 > 0.0 { + check_result!(draw_part_b::<B, _>( + radius.0 as f64 - a0, + a1.floor(), + |h, (f, t)| { + let h = h as i32; + let f = f as i32; + let t = t as i32; + check_result!(b.draw_line( + (center.0 + h, center.1 + f), + (center.0 + h, center.1 + t), + &style.color() + )); + check_result!(b.draw_line( + (center.0 - h, center.1 + f), + (center.0 - h, center.1 + t), + &style.color() + )); + + check_result!(b.draw_line( + (center.0 + f + 1, center.1 + h), + (center.0 + t - 1, center.1 + h), + &style.color() + )); + check_result!(b.draw_line( + (center.0 + f + 1, center.1 - h), + (center.0 + t - 1, center.1 - h), + &style.color() + )); + + Ok(()) + } + )); + } + + check_result!(draw_part_c::<B, _>( + radius.1 as i32, + radius.0 as i32, + |p, r| draw_sweep_line(b, style, center, (0, 1), p, r) + )); + check_result!(draw_part_c::<B, _>( + radius.1 as i32, + radius.0 as i32, + |p, r| draw_sweep_line(b, style, center, (0, -1), p, r) + )); + check_result!(draw_part_c::<B, _>( + radius.1 as i32, + radius.0 as i32, + |p, r| draw_sweep_line(b, style, center, (1, 0), p, r) + )); + check_result!(draw_part_c::<B, _>( + radius.1 as i32, + radius.0 as i32, + |p, r| draw_sweep_line(b, style, center, (-1, 0), p, r) + )); + + let d_inner = ((radius.1 as f64) / (2f64).sqrt()) as i32; + let d_outter = (((radius.0 as f64) / (2f64).sqrt()) as i32).min(radius.1 as i32 - 1); + let d_outter_actually = (radius.1 as i32).min( + (radius.0 as f64 * radius.0 as f64 - radius.1 as f64 * radius.1 as f64 / 2.0) + .sqrt() + .ceil() as i32, + ); + + check_result!(b.draw_line( + (center.0 - d_inner, center.1 - d_inner), + (center.0 - d_outter, center.1 - d_outter), + &style.color() + )); + check_result!(b.draw_line( + (center.0 + d_inner, center.1 - d_inner), + (center.0 + d_outter, center.1 - d_outter), + &style.color() + )); + check_result!(b.draw_line( + (center.0 - d_inner, center.1 + d_inner), + (center.0 - d_outter, center.1 + d_outter), + &style.color() + )); + check_result!(b.draw_line( + (center.0 + d_inner, center.1 + d_inner), + (center.0 + d_outter, center.1 + d_outter), + &style.color() + )); + + check_result!(b.draw_line( + (center.0 - d_inner, center.1 + d_inner), + (center.0 - d_outter_actually, center.1 + d_inner), + &style.color() + )); + check_result!(b.draw_line( + (center.0 + d_inner, center.1 - d_inner), + (center.0 + d_inner, center.1 - d_outter_actually), + &style.color() + )); + check_result!(b.draw_line( + (center.0 + d_inner, center.1 + d_inner), + (center.0 + d_inner, center.1 + d_outter_actually), + &style.color() + )); + check_result!(b.draw_line( + (center.0 + d_inner, center.1 + d_inner), + (center.0 + d_outter_actually, center.1 + d_inner), + &style.color() + )); + + Ok(()) +} + +pub fn draw_circle<B: DrawingBackend, S: BackendStyle>( + b: &mut B, + center: BackendCoord, + mut radius: u32, + style: &S, + mut fill: bool, +) -> Result<(), DrawingErrorKind<B::ErrorType>> { + if style.color().alpha == 0.0 { + return Ok(()); + } + + if !fill && style.stroke_width() != 1 { + let inner_radius = radius - (style.stroke_width() / 2).min(radius); + radius += style.stroke_width() / 2; + if inner_radius > 0 { + return draw_annulus(b, center, (radius, inner_radius), style); + } else { + fill = true; + } + } + + let min = (f64::from(radius) * (1.0 - (2f64).sqrt() / 2.0)).ceil() as i32; + let max = (f64::from(radius) * (1.0 + (2f64).sqrt() / 2.0)).floor() as i32; + + let range = min..=max; + + let (up, down) = ( + range.start() + center.1 - radius as i32, + range.end() + center.1 - radius as i32, + ); + + for dy in range { + let dy = dy - radius as i32; + let y = center.1 + dy; + + let lx = (f64::from(radius) * f64::from(radius) + - (f64::from(dy) * f64::from(dy)).max(1e-5)) + .sqrt(); + + let left = center.0 - lx.floor() as i32; + let right = center.0 + lx.floor() as i32; + + let v = lx - lx.floor(); + + let x = center.0 + dy; + let top = center.1 - lx.floor() as i32; + let bottom = center.1 + lx.floor() as i32; + + if fill { + check_result!(b.draw_line((left, y), (right, y), &style.color())); + check_result!(b.draw_line((x, top), (x, up - 1), &style.color())); + check_result!(b.draw_line((x, down + 1), (x, bottom), &style.color())); + } else { + check_result!(b.draw_pixel((left, y), style.color().mix(1.0 - v))); + check_result!(b.draw_pixel((right, y), style.color().mix(1.0 - v))); + + check_result!(b.draw_pixel((x, top), style.color().mix(1.0 - v))); + check_result!(b.draw_pixel((x, bottom), style.color().mix(1.0 - v))); + } + + check_result!(b.draw_pixel((left - 1, y), style.color().mix(v))); + check_result!(b.draw_pixel((right + 1, y), style.color().mix(v))); + check_result!(b.draw_pixel((x, top - 1), style.color().mix(v))); + check_result!(b.draw_pixel((x, bottom + 1), style.color().mix(v))); + } + + Ok(()) +} diff --git a/vendor/plotters-backend/src/rasterizer/line.rs b/vendor/plotters-backend/src/rasterizer/line.rs new file mode 100644 index 000000000..17836d9d2 --- /dev/null +++ b/vendor/plotters-backend/src/rasterizer/line.rs @@ -0,0 +1,123 @@ +use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind}; + +pub fn draw_line<DB: DrawingBackend, S: BackendStyle>( + back: &mut DB, + mut from: BackendCoord, + mut to: BackendCoord, + style: &S, +) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if style.color().alpha == 0.0 || style.stroke_width() == 0 { + return Ok(()); + } + + if style.stroke_width() != 1 { + // If the line is wider than 1px, then we need to make it a polygon + let v = (i64::from(to.0 - from.0), i64::from(to.1 - from.1)); + let l = ((v.0 * v.0 + v.1 * v.1) as f64).sqrt(); + + if l < 1e-5 { + return Ok(()); + } + + let v = (v.0 as f64 / l, v.1 as f64 / l); + + let r = f64::from(style.stroke_width()) / 2.0; + let mut trans = [(v.1 * r, -v.0 * r), (-v.1 * r, v.0 * r)]; + let mut vertices = vec![]; + + for point in [from, to].iter() { + for t in trans.iter() { + vertices.push(( + (f64::from(point.0) + t.0) as i32, + (f64::from(point.1) + t.1) as i32, + )) + } + + trans.swap(0, 1); + } + + return back.fill_polygon(vertices, style); + } + + if from.0 == to.0 { + if from.1 > to.1 { + std::mem::swap(&mut from, &mut to); + } + for y in from.1..=to.1 { + check_result!(back.draw_pixel((from.0, y), style.color())); + } + return Ok(()); + } + + if from.1 == to.1 { + if from.0 > to.0 { + std::mem::swap(&mut from, &mut to); + } + for x in from.0..=to.0 { + check_result!(back.draw_pixel((x, from.1), style.color())); + } + return Ok(()); + } + + let steep = (from.0 - to.0).abs() < (from.1 - to.1).abs(); + + if steep { + from = (from.1, from.0); + to = (to.1, to.0); + } + + let (from, to) = if from.0 > to.0 { + (to, from) + } else { + (from, to) + }; + + let mut size_limit = back.get_size(); + + if steep { + size_limit = (size_limit.1, size_limit.0); + } + + let grad = f64::from(to.1 - from.1) / f64::from(to.0 - from.0); + + let mut put_pixel = |(x, y): BackendCoord, b: f64| { + if steep { + back.draw_pixel((y, x), style.color().mix(b)) + } else { + back.draw_pixel((x, y), style.color().mix(b)) + } + }; + + let y_step_limit = + (f64::from(to.1.min(size_limit.1 as i32 - 1).max(0) - from.1) / grad).floor() as i32; + + let batch_start = (f64::from(from.1.min(size_limit.1 as i32 - 2).max(0) - from.1) / grad) + .abs() + .ceil() as i32 + + from.0; + + let batch_limit = + to.0.min(size_limit.0 as i32 - 2) + .min(from.0 + y_step_limit - 1); + + let mut y = f64::from(from.1) + f64::from(batch_start - from.0) * grad; + + for x in batch_start..=batch_limit { + check_result!(put_pixel((x, y as i32), 1.0 + y.floor() - y)); + check_result!(put_pixel((x, y as i32 + 1), y - y.floor())); + + y += grad; + } + + if to.0 > batch_limit && y < f64::from(to.1) { + let x = batch_limit as i32 + 1; + if 1.0 + y.floor() - y > 1e-5 { + check_result!(put_pixel((x, y as i32), 1.0 + y.floor() - y)); + } + if y - y.floor() > 1e-5 && y + 1.0 < f64::from(to.1) { + check_result!(put_pixel((x, y as i32 + 1), y - y.floor())); + } + } + + Ok(()) +} diff --git a/vendor/plotters-backend/src/rasterizer/mod.rs b/vendor/plotters-backend/src/rasterizer/mod.rs new file mode 100644 index 000000000..d322391e3 --- /dev/null +++ b/vendor/plotters-backend/src/rasterizer/mod.rs @@ -0,0 +1,41 @@ +/*! # The built-in rasterizers. + + Plotters make a minimal backend ability assumption - which is drawing a pixel on + backend. And this is the rasterizer that utilize this minimal ability to build a + fully functioning backend. + +*/ + +// TODO: We need to revisit this. It has been a long time since last time we figured out +// the question mark operator has a huge performance impact due to LLVM unable to handle it. +// So the question is if this trick is still useful, or LLVM is smart enough to handle it since +// then. +// +// -- +// Original comment: +// +// ? operator is very slow. See issue #58 for details +macro_rules! check_result { + ($e:expr) => { + let result = $e; + #[allow(clippy::question_mark)] + if result.is_err() { + return result; + } + }; +} + +mod line; +pub use line::draw_line; + +mod rect; +pub use rect::draw_rect; + +mod circle; +pub use circle::draw_circle; + +mod polygon; +pub use polygon::fill_polygon; + +mod path; +pub use path::polygonize; diff --git a/vendor/plotters-backend/src/rasterizer/path.rs b/vendor/plotters-backend/src/rasterizer/path.rs new file mode 100644 index 000000000..35ae6049b --- /dev/null +++ b/vendor/plotters-backend/src/rasterizer/path.rs @@ -0,0 +1,151 @@ +use crate::BackendCoord; + +// Compute the tanginal and normal vectors of the given straight line. +fn get_dir_vector(from: BackendCoord, to: BackendCoord, flag: bool) -> ((f64, f64), (f64, f64)) { + let v = (i64::from(to.0 - from.0), i64::from(to.1 - from.1)); + let l = ((v.0 * v.0 + v.1 * v.1) as f64).sqrt(); + + let v = (v.0 as f64 / l, v.1 as f64 / l); + + if flag { + (v, (v.1, -v.0)) + } else { + (v, (-v.1, v.0)) + } +} + +// Compute the polygonized vertex of the given angle +// d is the distance between the polygon edge and the actual line. +// d can be negative, this will emit a vertex on the other side of the line. +fn compute_polygon_vertex(triple: &[BackendCoord; 3], d: f64, buf: &mut Vec<BackendCoord>) { + buf.clear(); + + // Compute the tanginal and normal vectors of the given straight line. + let (a_t, a_n) = get_dir_vector(triple[0], triple[1], false); + let (b_t, b_n) = get_dir_vector(triple[2], triple[1], true); + + // Compute a point that is d away from the line for line a and line b. + let a_p = ( + f64::from(triple[1].0) + d * a_n.0, + f64::from(triple[1].1) + d * a_n.1, + ); + let b_p = ( + f64::from(triple[1].0) + d * b_n.0, + f64::from(triple[1].1) + d * b_n.1, + ); + + // If they are actually the same point, then the 3 points are colinear, so just emit the point. + if a_p.0 as i32 == b_p.0 as i32 && a_p.1 as i32 == b_p.1 as i32 { + buf.push((a_p.0 as i32, a_p.1 as i32)); + return; + } + + // So we are actually computing the intersection of two lines: + // a_p + u * a_t and b_p + v * b_t. + // We can solve the following vector equation: + // u * a_t + a_p = v * b_t + b_p + // + // which is actually a equation system: + // u * a_t.0 - v * b_t.0 = b_p.0 - a_p.0 + // u * a_t.1 - v * b_t.1 = b_p.1 - a_p.1 + + // The following vars are coefficients of the linear equation system. + // a0*u + b0*v = c0 + // a1*u + b1*v = c1 + // in which x and y are the coordinates that two polygon edges intersect. + + let a0 = a_t.0; + let b0 = -b_t.0; + let c0 = b_p.0 - a_p.0; + let a1 = a_t.1; + let b1 = -b_t.1; + let c1 = b_p.1 - a_p.1; + + let mut x = f64::INFINITY; + let mut y = f64::INFINITY; + + // Well if the determinant is not 0, then we can actuall get a intersection point. + if (a0 * b1 - a1 * b0).abs() > f64::EPSILON { + let u = (c0 * b1 - c1 * b0) / (a0 * b1 - a1 * b0); + + x = a_p.0 + u * a_t.0; + y = a_p.1 + u * a_t.1; + } + + let cross_product = a_t.0 * b_t.1 - a_t.1 * b_t.0; + if (cross_product < 0.0 && d < 0.0) || (cross_product > 0.0 && d > 0.0) { + // Then we are at the outter side of the angle, so we need to consider a cap. + let dist_square = (x - triple[1].0 as f64).powi(2) + (y - triple[1].1 as f64).powi(2); + // If the point is too far away from the line, we need to cap it. + if dist_square > d * d * 16.0 { + buf.push((a_p.0.round() as i32, a_p.1.round() as i32)); + buf.push((b_p.0.round() as i32, b_p.1.round() as i32)); + return; + } + } + + buf.push((x.round() as i32, y.round() as i32)); +} + +fn traverse_vertices<'a>( + mut vertices: impl Iterator<Item = &'a BackendCoord>, + width: u32, + mut op: impl FnMut(BackendCoord), +) { + let mut a = vertices.next().unwrap(); + let mut b = vertices.next().unwrap(); + + while a == b { + a = b; + if let Some(new_b) = vertices.next() { + b = new_b; + } else { + return; + } + } + + let (_, n) = get_dir_vector(*a, *b, false); + + op(( + (f64::from(a.0) + n.0 * f64::from(width) / 2.0).round() as i32, + (f64::from(a.1) + n.1 * f64::from(width) / 2.0).round() as i32, + )); + + let mut recent = [(0, 0), *a, *b]; + let mut vertex_buf = Vec::with_capacity(3); + + for p in vertices { + if *p == recent[2] { + continue; + } + recent.swap(0, 1); + recent.swap(1, 2); + recent[2] = *p; + compute_polygon_vertex(&recent, f64::from(width) / 2.0, &mut vertex_buf); + vertex_buf.iter().cloned().for_each(&mut op); + } + + let b = recent[1]; + let a = recent[2]; + + let (_, n) = get_dir_vector(a, b, true); + + op(( + (f64::from(a.0) + n.0 * f64::from(width) / 2.0).round() as i32, + (f64::from(a.1) + n.1 * f64::from(width) / 2.0).round() as i32, + )); +} + +/// Covert a path with >1px stroke width into polygon. +pub fn polygonize(vertices: &[BackendCoord], stroke_width: u32) -> Vec<BackendCoord> { + if vertices.len() < 2 { + return vec![]; + } + + let mut ret = vec![]; + + traverse_vertices(vertices.iter(), stroke_width, |v| ret.push(v)); + traverse_vertices(vertices.iter().rev(), stroke_width, |v| ret.push(v)); + + ret +} diff --git a/vendor/plotters-backend/src/rasterizer/polygon.rs b/vendor/plotters-backend/src/rasterizer/polygon.rs new file mode 100644 index 000000000..ce33c5cfd --- /dev/null +++ b/vendor/plotters-backend/src/rasterizer/polygon.rs @@ -0,0 +1,242 @@ +use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind}; + +use std::cmp::{Ord, Ordering, PartialOrd}; + +#[derive(Clone, Debug)] +struct Edge { + epoch: u32, + total_epoch: u32, + slave_begin: i32, + slave_end: i32, +} + +impl Edge { + fn horizontal_sweep(mut from: BackendCoord, mut to: BackendCoord) -> Option<Edge> { + if from.0 == to.0 { + return None; + } + + if from.0 > to.0 { + std::mem::swap(&mut from, &mut to); + } + + Some(Edge { + epoch: 0, + total_epoch: (to.0 - from.0) as u32, + slave_begin: from.1, + slave_end: to.1, + }) + } + + fn vertical_sweep(from: BackendCoord, to: BackendCoord) -> Option<Edge> { + Edge::horizontal_sweep((from.1, from.0), (to.1, to.0)) + } + + fn get_master_pos(&self) -> i32 { + (self.total_epoch - self.epoch) as i32 + } + + fn inc_epoch(&mut self) { + self.epoch += 1; + } + + fn get_slave_pos(&self) -> f64 { + f64::from(self.slave_begin) + + (i64::from(self.slave_end - self.slave_begin) * i64::from(self.epoch)) as f64 + / f64::from(self.total_epoch) + } +} + +impl PartialOrd for Edge { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + self.get_slave_pos().partial_cmp(&other.get_slave_pos()) + } +} + +impl PartialEq for Edge { + fn eq(&self, other: &Self) -> bool { + self.get_slave_pos() == other.get_slave_pos() + } +} + +impl Eq for Edge {} + +impl Ord for Edge { + fn cmp(&self, other: &Self) -> Ordering { + self.get_slave_pos() + .partial_cmp(&other.get_slave_pos()) + .unwrap() + } +} + +pub fn fill_polygon<DB: DrawingBackend, S: BackendStyle>( + back: &mut DB, + vertices: &[BackendCoord], + style: &S, +) -> Result<(), DrawingErrorKind<DB::ErrorType>> { + if let Some((x_span, y_span)) = + vertices + .iter() + .fold(None, |res: Option<((i32, i32), (i32, i32))>, (x, y)| { + Some( + res.map(|((min_x, max_x), (min_y, max_y))| { + ( + (min_x.min(*x), max_x.max(*x)), + (min_y.min(*y), max_y.max(*y)), + ) + }) + .unwrap_or(((*x, *x), (*y, *y))), + ) + }) + { + // First of all, let's handle the case that all the points is in a same vertical or + // horizontal line + if x_span.0 == x_span.1 || y_span.0 == y_span.1 { + return back.draw_line((x_span.0, y_span.0), (x_span.1, y_span.1), style); + } + + let horizontal_sweep = x_span.1 - x_span.0 > y_span.1 - y_span.0; + + let mut edges: Vec<_> = vertices + .iter() + .zip(vertices.iter().skip(1)) + .map(|(a, b)| (*a, *b)) + .collect(); + edges.push((vertices[vertices.len() - 1], vertices[0])); + edges.sort_by_key(|((x1, y1), (x2, y2))| { + if horizontal_sweep { + *x1.min(x2) + } else { + *y1.min(y2) + } + }); + + for edge in &mut edges.iter_mut() { + if horizontal_sweep { + if (edge.0).0 > (edge.1).0 { + std::mem::swap(&mut edge.0, &mut edge.1); + } + } else if (edge.0).1 > (edge.1).1 { + std::mem::swap(&mut edge.0, &mut edge.1); + } + } + + let (low, high) = if horizontal_sweep { x_span } else { y_span }; + + let mut idx = 0; + + let mut active_edge: Vec<Edge> = vec![]; + + for sweep_line in low..=high { + let mut new_vec = vec![]; + + for mut e in active_edge { + if e.get_master_pos() > 0 { + e.inc_epoch(); + new_vec.push(e); + } + } + + active_edge = new_vec; + + loop { + if idx >= edges.len() { + break; + } + let line = if horizontal_sweep { + (edges[idx].0).0 + } else { + (edges[idx].0).1 + }; + if line > sweep_line { + break; + } + + let edge_obj = if horizontal_sweep { + Edge::horizontal_sweep(edges[idx].0, edges[idx].1) + } else { + Edge::vertical_sweep(edges[idx].0, edges[idx].1) + }; + + if let Some(edge_obj) = edge_obj { + active_edge.push(edge_obj); + } + + idx += 1; + } + + active_edge.sort(); + + let mut first = None; + let mut second = None; + + for edge in active_edge.iter() { + if first.is_none() { + first = Some(edge.clone()) + } else if second.is_none() { + second = Some(edge.clone()) + } + + if let Some(a) = first.clone() { + if let Some(b) = second.clone() { + if a.get_master_pos() == 0 && b.get_master_pos() != 0 { + first = Some(b); + second = None; + continue; + } + + if a.get_master_pos() != 0 && b.get_master_pos() == 0 { + first = Some(a); + second = None; + continue; + } + + let from = a.get_slave_pos(); + let to = b.get_slave_pos(); + + if a.get_master_pos() == 0 && b.get_master_pos() == 0 && to - from > 1.0 { + first = None; + second = None; + continue; + } + + if horizontal_sweep { + check_result!(back.draw_line( + (sweep_line, from.ceil() as i32), + (sweep_line, to.floor() as i32), + &style.color(), + )); + check_result!(back.draw_pixel( + (sweep_line, from.floor() as i32), + style.color().mix(from.ceil() - from), + )); + check_result!(back.draw_pixel( + (sweep_line, to.ceil() as i32), + style.color().mix(to - to.floor()), + )); + } else { + check_result!(back.draw_line( + (from.ceil() as i32, sweep_line), + (to.floor() as i32, sweep_line), + &style.color(), + )); + check_result!(back.draw_pixel( + (from.floor() as i32, sweep_line), + style.color().mix(from.ceil() - from), + )); + check_result!(back.draw_pixel( + (to.ceil() as i32, sweep_line), + style.color().mix(to.floor() - to), + )); + } + + first = None; + second = None; + } + } + } + } + } + + Ok(()) +} diff --git a/vendor/plotters-backend/src/rasterizer/rect.rs b/vendor/plotters-backend/src/rasterizer/rect.rs new file mode 100644 index 000000000..cd6c77400 --- /dev/null +++ b/vendor/plotters-backend/src/rasterizer/rect.rs @@ -0,0 +1,57 @@ +use crate::{BackendCoord, BackendStyle, DrawingBackend, DrawingErrorKind}; + +pub fn draw_rect<B: DrawingBackend, S: BackendStyle>( + b: &mut B, + upper_left: BackendCoord, + bottom_right: BackendCoord, + style: &S, + fill: bool, +) -> Result<(), DrawingErrorKind<B::ErrorType>> { + if style.color().alpha == 0.0 { + return Ok(()); + } + let (upper_left, bottom_right) = ( + ( + upper_left.0.min(bottom_right.0), + upper_left.1.min(bottom_right.1), + ), + ( + upper_left.0.max(bottom_right.0), + upper_left.1.max(bottom_right.1), + ), + ); + + if fill { + if bottom_right.0 - upper_left.0 < bottom_right.1 - upper_left.1 { + for x in upper_left.0..=bottom_right.0 { + check_result!(b.draw_line((x, upper_left.1), (x, bottom_right.1), style)); + } + } else { + for y in upper_left.1..=bottom_right.1 { + check_result!(b.draw_line((upper_left.0, y), (bottom_right.0, y), style)); + } + } + } else { + b.draw_line( + (upper_left.0, upper_left.1), + (upper_left.0, bottom_right.1), + style, + )?; + b.draw_line( + (upper_left.0, upper_left.1), + (bottom_right.0, upper_left.1), + style, + )?; + b.draw_line( + (bottom_right.0, bottom_right.1), + (upper_left.0, bottom_right.1), + style, + )?; + b.draw_line( + (bottom_right.0, bottom_right.1), + (bottom_right.0, upper_left.1), + style, + )?; + } + Ok(()) +} diff --git a/vendor/plotters-backend/src/style.rs b/vendor/plotters-backend/src/style.rs new file mode 100644 index 000000000..028a06bcb --- /dev/null +++ b/vendor/plotters-backend/src/style.rs @@ -0,0 +1,33 @@ +/// The color type that is used by all the backend +#[derive(Clone, Copy)] +pub struct BackendColor { + pub alpha: f64, + pub rgb: (u8, u8, u8), +} + +impl BackendColor { + #[inline(always)] + pub fn mix(&self, alpha: f64) -> Self { + Self { + alpha: self.alpha * alpha, + rgb: self.rgb, + } + } +} + +/// The style data for the backend drawing API +pub trait BackendStyle { + /// Get the color of current style + fn color(&self) -> BackendColor; + + /// Get the stroke width of current style + fn stroke_width(&self) -> u32 { + 1 + } +} + +impl BackendStyle for BackendColor { + fn color(&self) -> BackendColor { + *self + } +} diff --git a/vendor/plotters-backend/src/text.rs b/vendor/plotters-backend/src/text.rs new file mode 100644 index 000000000..16e2c6652 --- /dev/null +++ b/vendor/plotters-backend/src/text.rs @@ -0,0 +1,245 @@ +use super::{BackendColor, BackendCoord}; +use std::error::Error; + +/// Describes font family. +/// This can be either a specific font family name, such as "arial", +/// or a general font family class, such as "serif" and "sans-serif" +#[derive(Clone, Copy)] +pub enum FontFamily<'a> { + /// The system default serif font family + Serif, + /// The system default sans-serif font family + SansSerif, + /// The system default monospace font + Monospace, + /// A specific font family name + Name(&'a str), +} + +impl<'a> FontFamily<'a> { + /// Make a CSS compatible string for the font family name. + /// This can be used as the value of `font-family` attribute in SVG. + pub fn as_str(&self) -> &str { + match self { + FontFamily::Serif => "serif", + FontFamily::SansSerif => "sans-serif", + FontFamily::Monospace => "monospace", + FontFamily::Name(face) => face, + } + } +} + +impl<'a> From<&'a str> for FontFamily<'a> { + fn from(from: &'a str) -> FontFamily<'a> { + match from.to_lowercase().as_str() { + "serif" => FontFamily::Serif, + "sans-serif" => FontFamily::SansSerif, + "monospace" => FontFamily::Monospace, + _ => FontFamily::Name(from), + } + } +} + +/// Text anchor attributes are used to properly position the text. +/// +/// # Examples +/// +/// In the example below, the text anchor (X) position is `Pos::new(HPos::Right, VPos::Center)`. +/// ```text +/// ***** X +/// ``` +/// The position is always relative to the text regardless of its rotation. +/// In the example below, the text has style +/// `style.transform(FontTransform::Rotate90).pos(Pos::new(HPos::Center, VPos::Top))`. +/// ```text +/// * +/// * +/// * X +/// * +/// * +/// ``` +pub mod text_anchor { + /// The horizontal position of the anchor point relative to the text. + #[derive(Clone, Copy)] + pub enum HPos { + /// Anchor point is on the left side of the text + Left, + /// Anchor point is on the right side of the text + Right, + /// Anchor point is in the horizontal center of the text + Center, + } + + /// The vertical position of the anchor point relative to the text. + #[derive(Clone, Copy)] + pub enum VPos { + /// Anchor point is on the top of the text + Top, + /// Anchor point is in the vertical center of the text + Center, + /// Anchor point is on the bottom of the text + Bottom, + } + + /// The text anchor position. + #[derive(Clone, Copy)] + pub struct Pos { + /// The horizontal position of the anchor point + pub h_pos: HPos, + /// The vertical position of the anchor point + pub v_pos: VPos, + } + + impl Pos { + /// Create a new text anchor position. + /// + /// - `h_pos`: The horizontal position of the anchor point + /// - `v_pos`: The vertical position of the anchor point + /// - **returns** The newly created text anchor position + /// + /// ```rust + /// use plotters_backend::text_anchor::{Pos, HPos, VPos}; + /// + /// let pos = Pos::new(HPos::Left, VPos::Top); + /// ``` + pub fn new(h_pos: HPos, v_pos: VPos) -> Self { + Pos { h_pos, v_pos } + } + + /// Create a default text anchor position (top left). + /// + /// - **returns** The default text anchor position + /// + /// ```rust + /// use plotters_backend::text_anchor::{Pos, HPos, VPos}; + /// + /// let pos = Pos::default(); + /// ``` + pub fn default() -> Self { + Pos { + h_pos: HPos::Left, + v_pos: VPos::Top, + } + } + } +} + +/// Specifying text transformations +#[derive(Clone)] +pub enum FontTransform { + /// Nothing to transform + None, + /// Rotating the text 90 degree clockwise + Rotate90, + /// Rotating the text 180 degree clockwise + Rotate180, + /// Rotating the text 270 degree clockwise + Rotate270, +} + +impl FontTransform { + /// Transform the coordinate to perform the rotation + /// + /// - `x`: The x coordinate in pixels before transform + /// - `y`: The y coordinate in pixels before transform + /// - **returns**: The coordinate after transform + pub fn transform(&self, x: i32, y: i32) -> (i32, i32) { + match self { + FontTransform::None => (x, y), + FontTransform::Rotate90 => (-y, x), + FontTransform::Rotate180 => (-x, -y), + FontTransform::Rotate270 => (y, -x), + } + } +} + +/// Describes the font style. Such as Italic, Oblique, etc. +#[derive(Clone, Copy)] +pub enum FontStyle { + /// The normal style + Normal, + /// The oblique style + Oblique, + /// The italic style + Italic, + /// The bold style + Bold, +} + +impl FontStyle { + /// Convert the font style into a CSS compatible string which can be used in `font-style` attribute. + pub fn as_str(&self) -> &str { + match self { + FontStyle::Normal => "normal", + FontStyle::Italic => "italic", + FontStyle::Oblique => "oblique", + FontStyle::Bold => "bold", + } + } +} + +impl<'a> From<&'a str> for FontStyle { + fn from(from: &'a str) -> FontStyle { + match from.to_lowercase().as_str() { + "normal" => FontStyle::Normal, + "italic" => FontStyle::Italic, + "oblique" => FontStyle::Oblique, + "bold" => FontStyle::Bold, + _ => FontStyle::Normal, + } + } +} + +/// The trait that abstracts a style of a text. +/// +/// This is used because the the backend crate have no knowledge about how +/// the text handling is implemented in plotters. +/// +/// But the backend still wants to know some information about the font, for +/// the backend doesn't handles text drawing, may want to call the `draw` method which +/// is implemented by the plotters main crate. While for the backend that handles the +/// text drawing, those font information provides instructions about how the text should be +/// rendered: color, size, slant, anchor, font, etc. +/// +/// This trait decouples the detailed implementaiton about the font and the backend code which +/// wants to perfome some operation on the font. +/// +pub trait BackendTextStyle { + /// The error type of this text style implementation + type FontError: Error + Sync + Send + 'static; + + fn color(&self) -> BackendColor { + BackendColor { + alpha: 1.0, + rgb: (0, 0, 0), + } + } + + fn size(&self) -> f64 { + 1.0 + } + + fn transform(&self) -> FontTransform { + FontTransform::None + } + + fn style(&self) -> FontStyle { + FontStyle::Normal + } + + fn anchor(&self) -> text_anchor::Pos { + text_anchor::Pos::default() + } + + fn family(&self) -> FontFamily; + + #[allow(clippy::type_complexity)] + fn layout_box(&self, text: &str) -> Result<((i32, i32), (i32, i32)), Self::FontError>; + + fn draw<E, DrawFunc: FnMut(i32, i32, BackendColor) -> Result<(), E>>( + &self, + text: &str, + pos: BackendCoord, + draw: DrawFunc, + ) -> Result<Result<(), E>, Self::FontError>; +} |