aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs326
1 files changed, 326 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..f4a92b0
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,326 @@
+/*!
+ 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);
+ 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<'a>(
+ &mut self,
+ pos: BackendCoord,
+ (iw, ih): (u32, u32),
+ src: &'a [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);
+ if result.is_err() {
+ return result;
+ }
+ }
+ }
+
+ Ok(())
+ }
+}