| /*! |
| 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(()) |
| } |
| } |