plotters_backend/
lib.rs

1/*!
2  The Plotters backend API crate. This is a part of Plotters, the Rust drawing and plotting library, for more details regarding the entire
3  Plotters project, please check the [main crate](https://crates.io/crates/plotters).
4
5  This is the crate that used as the connector between Plotters and different backend crates. Since Plotters 0.3, all the backends has been
6  hosted as seperate crates for the usability and maintainability reasons.
7
8  At the same time, Plotters is now supporting third-party backends and all the backends are now supports "plug-and-play":
9  To use a external backend, just depends on both the Plotters main crate and the third-party backend crate.
10
11  # Notes for implementing Backend for Plotters
12
13  To create a new Plotters backend, this crate should be imported to the crate and the trait [DrawingBackend](trait.DrawingBackend.html) should
14  be implemented. It's highly recommended that the third-party backend uses `plotters-backend` by version specification `^x.y.*`.
15  For more details, see the [compatibility note](#compatibility-note).
16
17  If the backend only implements [DrawingBackend::draw_pixel](trait.DrawingBackend.html#tymethod.draw_pixel), the default CPU rasterizer will be
18  used to give your backend ability of drawing different shapes. For those backend that supports advanced drawing instructions, such as, GPU
19  acelerated shape drawing, all the provided trait method can be overriden from the specific backend code.
20
21  If your backend have text rendering ability, you may want to override the [DrawingBackend::estimate_text_size](trait.DrawingBackend.html#tymethod.estimate_text_size)
22  to avoid wrong spacing, since the Plotters default text handling code may behaves differently from the backend in terms of text rendering.
23
24  ## Animated or Realtime Rendering
25  Backend might render the image realtimely/animated, for example, a GTK backend for realtime display or a GIF image rendering. To support these
26  features, you need to play with `ensure_prepared` and `present` method. The following figure illustrates how Plotters operates a drawing backend.
27
28  - `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
29     for a frame, this function should simply do nothing.
30  - `present` - Called when plotters want to finish current frame drawing
31
32
33  ```text
34                                        .ensure_prepared() &&
35    +-------------+    +-------------+    .draw_pixels()             +--------------+   drop
36    |Start drwaing|--->|Ready to draw| ------------------------+---->|Finish 1 frame| --------->
37    +-------------+    +-------------+                         |     +--------------+
38           ^                  ^                                |            |
39           |                  +------------------------------- +            |
40           |                            continue drawing                    |
41           +----------------------------------------------------------------+
42                                start render the next frame
43                                        .present()
44  ```
45  - For both animated and static drawing, `DrawingBackend::present` indicates current frame should be flushed.
46  - For both animated and static drawing, `DrawingBackend::ensure_prepared` is called every time when plotters need to draw.
47  - For static drawing, the `DrawingBackend::present` is only called once manually, or from the Drop impl for the backend.
48  - For dynamic drawing, frames are defined by invocation of `DrawingBackend::present`, everything prior the invocation should belongs to previous frame
49
50  # Compatibility Note
51  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
52  same version of `plotters-backend` crate. This crate (`plotters-backend`) will enforce that any revision (means the last number in a version number)
53  won't contains breaking change - both on the Plotters side and backend side.
54
55  Plotters main crate is always importing the backend crate with version specification `plotters-backend = "^<major>.<minor>*"`.
56  It's highly recommended that all the external crates follows the same rule to import `plotters-backend` depdendency, to avoid protential breaking
57  caused by `plotters-backend` crates gets a revision update.
58
59  We also impose a versioning rule with `plotters` and some backends:
60  The compatible main crate (`plotters`) and this crate (`plotters-backend`) are always use the same major and minor version number.
61  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.*`
62
63*/
64use std::error::Error;
65
66pub mod rasterizer;
67mod style;
68mod text;
69
70pub use style::{BackendColor, BackendStyle};
71pub use text::{text_anchor, BackendTextStyle, FontFamily, FontStyle, FontTransform};
72
73use text_anchor::{HPos, VPos};
74
75/// A coordinate in the pixel-based backend. The coordinate follows the framebuffer's convention,
76/// which defines the top-left point as (0, 0).
77pub type BackendCoord = (i32, i32);
78
79/// The error produced by a drawing backend.
80#[derive(Debug)]
81pub enum DrawingErrorKind<E: Error + Send + Sync> {
82    /// A drawing backend error
83    DrawingError(E),
84    /// A font rendering error
85    FontError(Box<dyn Error + Send + Sync + 'static>),
86}
87
88impl<E: Error + Send + Sync> std::fmt::Display for DrawingErrorKind<E> {
89    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
90        match self {
91            DrawingErrorKind::DrawingError(e) => write!(fmt, "Drawing backend error: {}", e),
92            DrawingErrorKind::FontError(e) => write!(fmt, "Font loading error: {}", e),
93        }
94    }
95}
96
97impl<E: Error + Send + Sync> Error for DrawingErrorKind<E> {}
98
99///  The drawing backend trait, which implements the low-level drawing APIs.
100///  This trait has a set of default implementation. And the minimal requirement of
101///  implementing a drawing backend is implementing the `draw_pixel` function.
102///
103///  If the drawing backend supports vector graphics, the other drawing APIs should be
104///  override by the backend specific implementation. Otherwise, the default implementation
105///  will use the pixel-based approach to draw other types of low-level shapes.
106pub trait DrawingBackend: Sized {
107    /// The error type reported by the backend
108    type ErrorType: Error + Send + Sync;
109
110    /// Get the dimension of the drawing backend in pixels
111    fn get_size(&self) -> (u32, u32);
112
113    /// Ensure the backend is ready to draw
114    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
115
116    /// Finalize the drawing step and present all the changes.
117    /// This is used as the real-time rendering support.
118    /// The backend may implement in the following way, when `ensure_prepared` is called
119    /// it checks if it needs a fresh buffer and `present` is called rendering all the
120    /// pending changes on the screen.
121    fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
122
123    /// Draw a pixel on the drawing backend
124    /// - `point`: The backend pixel-based coordinate to draw
125    /// - `color`: The color of the pixel
126    fn draw_pixel(
127        &mut self,
128        point: BackendCoord,
129        color: BackendColor,
130    ) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
131
132    /// Draw a line on the drawing backend
133    /// - `from`: The start point of the line
134    /// - `to`: The end point of the line
135    /// - `style`: The style of the line
136    fn draw_line<S: BackendStyle>(
137        &mut self,
138        from: BackendCoord,
139        to: BackendCoord,
140        style: &S,
141    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
142        rasterizer::draw_line(self, from, to, style)
143    }
144
145    /// Draw a rectangle on the drawing backend
146    /// - `upper_left`: The coordinate of the upper-left corner of the rect
147    /// - `bottom_right`: The coordinate of the bottom-right corner of the rect
148    /// - `style`: The style
149    /// - `fill`: If the rectangle should be filled
150    fn draw_rect<S: BackendStyle>(
151        &mut self,
152        upper_left: BackendCoord,
153        bottom_right: BackendCoord,
154        style: &S,
155        fill: bool,
156    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
157        rasterizer::draw_rect(self, upper_left, bottom_right, style, fill)
158    }
159
160    /// Draw a path on the drawing backend
161    /// - `path`: The iterator of key points of the path
162    /// - `style`: The style of the path
163    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
164        &mut self,
165        path: I,
166        style: &S,
167    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
168        if style.color().alpha == 0.0 {
169            return Ok(());
170        }
171
172        if style.stroke_width() == 1 {
173            let mut begin: Option<BackendCoord> = None;
174            for end in path.into_iter() {
175                if let Some(begin) = begin {
176                    let result = self.draw_line(begin, end, style);
177                    if result.is_err() {
178                        return result;
179                    }
180                }
181                begin = Some(end);
182            }
183        } else {
184            let p: Vec<_> = path.into_iter().collect();
185            let v = rasterizer::polygonize(&p[..], style.stroke_width());
186            return self.fill_polygon(v, &style.color());
187        }
188        Ok(())
189    }
190
191    /// Draw a circle on the drawing backend
192    /// - `center`: The center coordinate of the circle
193    /// - `radius`: The radius of the circle
194    /// - `style`: The style of the shape
195    /// - `fill`: If the circle should be filled
196    fn draw_circle<S: BackendStyle>(
197        &mut self,
198        center: BackendCoord,
199        radius: u32,
200        style: &S,
201        fill: bool,
202    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
203        rasterizer::draw_circle(self, center, radius, style, fill)
204    }
205
206    fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
207        &mut self,
208        vert: I,
209        style: &S,
210    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
211        let vert_buf: Vec<_> = vert.into_iter().collect();
212
213        rasterizer::fill_polygon(self, &vert_buf[..], style)
214    }
215
216    /// Draw a text on the drawing backend
217    /// - `text`: The text to draw
218    /// - `style`: The text style
219    /// - `pos` : The text anchor point
220    fn draw_text<TStyle: BackendTextStyle>(
221        &mut self,
222        text: &str,
223        style: &TStyle,
224        pos: BackendCoord,
225    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
226        let color = style.color();
227        if color.alpha == 0.0 {
228            return Ok(());
229        }
230
231        let layout = style
232            .layout_box(text)
233            .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?;
234        let ((min_x, min_y), (max_x, max_y)) = layout;
235        let width = (max_x - min_x) as i32;
236        let height = (max_y - min_y) as i32;
237        let dx = match style.anchor().h_pos {
238            HPos::Left => 0,
239            HPos::Right => -width,
240            HPos::Center => -width / 2,
241        };
242        let dy = match style.anchor().v_pos {
243            VPos::Top => 0,
244            VPos::Center => -height / 2,
245            VPos::Bottom => -height,
246        };
247        let trans = style.transform();
248        let (w, h) = self.get_size();
249        match style.draw(text, (0, 0), |x, y, color| {
250            let (x, y) = trans.transform(x + dx - min_x, y + dy - min_y);
251            let (x, y) = (pos.0 + x, pos.1 + y);
252            if x >= 0 && x < w as i32 && y >= 0 && y < h as i32 {
253                self.draw_pixel((x, y), color)
254            } else {
255                Ok(())
256            }
257        }) {
258            Ok(drawing_result) => drawing_result,
259            Err(font_error) => Err(DrawingErrorKind::FontError(Box::new(font_error))),
260        }
261    }
262
263    /// Estimate the size of the horizontal text if rendered on this backend.
264    /// This is important because some of the backend may not have font ability.
265    /// Thus this allows those backend reports proper value rather than ask the
266    /// font rasterizer for that.
267    ///
268    /// - `text`: The text to estimate
269    /// - `font`: The font to estimate
270    /// - *Returns* The estimated text size
271    fn estimate_text_size<TStyle: BackendTextStyle>(
272        &self,
273        text: &str,
274        style: &TStyle,
275    ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
276        let layout = style
277            .layout_box(text)
278            .map_err(|e| DrawingErrorKind::FontError(Box::new(e)))?;
279        Ok((
280            ((layout.1).0 - (layout.0).0) as u32,
281            ((layout.1).1 - (layout.0).1) as u32,
282        ))
283    }
284
285    /// Blit a bitmap on to the backend.
286    ///
287    /// - `text`: pos the left upper conner of the bitmap to blit
288    /// - `src`: The source of the image
289    ///
290    /// TODO: The default implementation of bitmap blitting assumes that the bitmap is RGB, but
291    /// this may not be the case. But for bitmap backend it's actually ok if we use the bitmap
292    /// element that matches the pixel format, but we need to fix this.
293    fn blit_bitmap<'a>(
294        &mut self,
295        pos: BackendCoord,
296        (iw, ih): (u32, u32),
297        src: &'a [u8],
298    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
299        let (w, h) = self.get_size();
300
301        for dx in 0..iw {
302            if pos.0 + dx as i32 >= w as i32 {
303                break;
304            }
305            for dy in 0..ih {
306                if pos.1 + dy as i32 >= h as i32 {
307                    break;
308                }
309                // FIXME: This assume we have RGB image buffer
310                let r = src[(dx + dy * w) as usize * 3];
311                let g = src[(dx + dy * w) as usize * 3 + 1];
312                let b = src[(dx + dy * w) as usize * 3 + 2];
313                let color = BackendColor {
314                    alpha: 1.0,
315                    rgb: (r, g, b),
316                };
317                let result = self.draw_pixel((pos.0 + dx as i32, pos.1 + dy as i32), color);
318                if result.is_err() {
319                    return result;
320                }
321            }
322        }
323
324        Ok(())
325    }
326}