1 use crate::style::text_anchor::{HPos, VPos};
2 use crate::style::{Color, FontDesc, FontError, RGBAColor, ShapeStyle, TextStyle};
3 use std::error::Error;
4 
5 /// A coordinate in the image
6 pub type BackendCoord = (i32, i32);
7 
8 /// The error produced by a drawing backend
9 #[derive(Debug)]
10 pub enum DrawingErrorKind<E: Error + Send + Sync>
11 where
12     FontError: Send + Sync,
13 {
14     /// A drawing backend error
15     DrawingError(E),
16     /// A font rendering error
17     FontError(FontError),
18 }
19 
20 impl<E: Error + Send + Sync> std::fmt::Display for DrawingErrorKind<E> {
fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error>21     fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
22         match self {
23             DrawingErrorKind::DrawingError(e) => write!(fmt, "Drawing backend error: {}", e),
24             DrawingErrorKind::FontError(e) => write!(fmt, "Font loading error: {}", e),
25         }
26     }
27 }
28 
29 impl<E: Error + Send + Sync> Error for DrawingErrorKind<E> {}
30 
31 /// The style data for the backend drawing API
32 pub trait BackendStyle {
33     /// The underlying type represents the color for this style
34     type ColorType: Color;
35 
36     /// Convert the style into the underlying color
as_color(&self) -> RGBAColor37     fn as_color(&self) -> RGBAColor;
38 
39     // TODO: In the future we should support stroke width, line shape, etc....
stroke_width(&self) -> u3240     fn stroke_width(&self) -> u32 {
41         1
42     }
43 }
44 
45 impl<T: Color> BackendStyle for T {
46     type ColorType = T;
as_color(&self) -> RGBAColor47     fn as_color(&self) -> RGBAColor {
48         self.to_rgba()
49     }
50 }
51 
52 impl BackendStyle for ShapeStyle {
53     type ColorType = RGBAColor;
as_color(&self) -> RGBAColor54     fn as_color(&self) -> RGBAColor {
55         self.color.clone()
56     }
stroke_width(&self) -> u3257     fn stroke_width(&self) -> u32 {
58         self.stroke_width
59     }
60 }
61 
62 ///  The drawing backend trait, which implements the low-level drawing APIs.
63 ///  This trait has a set of default implementation. And the minimal requirement of
64 ///  implementing a drawing backend is implementing the `draw_pixel` function.
65 ///
66 ///  If the drawing backend supports vector graphics, the other drawing APIs should be
67 ///  override by the backend specific implementation. Otherwise, the default implementation
68 ///  will use the pixel-based approach to draw other types of low-level shapes.
69 pub trait DrawingBackend: Sized {
70     /// The error type reported by the backend
71     type ErrorType: Error + Send + Sync;
72 
73     /// Get the dimension of the drawing backend in pixels
get_size(&self) -> (u32, u32)74     fn get_size(&self) -> (u32, u32);
75 
76     /// Ensure the backend is ready to draw
ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>77     fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
78 
79     /// Finalize the drawing step and present all the changes.
80     /// This is used as the real-time rendering support.
81     /// The backend may implement in the following way, when `ensure_prepared` is called
82     /// it checks if it needs a fresh buffer and `present` is called rendering all the
83     /// pending changes on the screen.
present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>84     fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
85 
86     /// Draw a pixel on the drawing backend
87     /// - `point`: The backend pixel-based coordinate to draw
88     /// - `color`: The color of the pixel
draw_pixel( &mut self, point: BackendCoord, color: &RGBAColor, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>89     fn draw_pixel(
90         &mut self,
91         point: BackendCoord,
92         color: &RGBAColor,
93     ) -> Result<(), DrawingErrorKind<Self::ErrorType>>;
94 
95     /// Draw a line on the drawing backend
96     /// - `from`: The start point of the line
97     /// - `to`: The end point of the line
98     /// - `style`: The style of the line
draw_line<S: BackendStyle>( &mut self, from: BackendCoord, to: BackendCoord, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>99     fn draw_line<S: BackendStyle>(
100         &mut self,
101         from: BackendCoord,
102         to: BackendCoord,
103         style: &S,
104     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
105         super::rasterizer::draw_line(self, from, to, style)
106     }
107 
108     /// Draw a rectangle on the drawing backend
109     /// - `upper_left`: The coordinate of the upper-left corner of the rect
110     /// - `bottom_right`: The coordinate of the bottom-right corner of the rect
111     /// - `style`: The style
112     /// - `fill`: If the rectangle should be filled
draw_rect<S: BackendStyle>( &mut self, upper_left: BackendCoord, bottom_right: BackendCoord, style: &S, fill: bool, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>113     fn draw_rect<S: BackendStyle>(
114         &mut self,
115         upper_left: BackendCoord,
116         bottom_right: BackendCoord,
117         style: &S,
118         fill: bool,
119     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
120         super::rasterizer::draw_rect(self, upper_left, bottom_right, style, fill)
121     }
122 
123     /// Draw a path on the drawing backend
124     /// - `path`: The iterator of key points of the path
125     /// - `style`: The style of the path
draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( &mut self, path: I, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>126     fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
127         &mut self,
128         path: I,
129         style: &S,
130     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
131         if style.as_color().alpha() == 0.0 {
132             return Ok(());
133         }
134 
135         if style.stroke_width() == 1 {
136             let mut begin: Option<BackendCoord> = None;
137             for end in path.into_iter() {
138                 if let Some(begin) = begin {
139                     let result = self.draw_line(begin, end, style);
140                     if result.is_err() {
141                         return result;
142                     }
143                 }
144                 begin = Some(end);
145             }
146         } else {
147             let p: Vec<_> = path.into_iter().collect();
148             let v = super::rasterizer::polygonize(&p[..], style.stroke_width());
149             return self.fill_polygon(v, &style.as_color());
150         }
151         Ok(())
152     }
153 
154     /// Draw a circle on the drawing backend
155     /// - `center`: The center coordinate of the circle
156     /// - `radius`: The radius of the circle
157     /// - `style`: The style of the shape
158     /// - `fill`: If the circle should be filled
draw_circle<S: BackendStyle>( &mut self, center: BackendCoord, radius: u32, style: &S, fill: bool, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>159     fn draw_circle<S: BackendStyle>(
160         &mut self,
161         center: BackendCoord,
162         radius: u32,
163         style: &S,
164         fill: bool,
165     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
166         super::rasterizer::draw_circle(self, center, radius, style, fill)
167     }
168 
fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>( &mut self, vert: I, style: &S, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>169     fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
170         &mut self,
171         vert: I,
172         style: &S,
173     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
174         let vert_buf: Vec<_> = vert.into_iter().collect();
175 
176         super::rasterizer::fill_polygon(self, &vert_buf[..], style)
177     }
178 
179     /// Draw a text on the drawing backend
180     /// - `text`: The text to draw
181     /// - `style`: The text style
182     /// - `pos` : The text anchor point
draw_text( &mut self, text: &str, style: &TextStyle, pos: BackendCoord, ) -> Result<(), DrawingErrorKind<Self::ErrorType>>183     fn draw_text(
184         &mut self,
185         text: &str,
186         style: &TextStyle,
187         pos: BackendCoord,
188     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
189         let font = &style.font;
190         let color = &style.color;
191         if color.alpha() == 0.0 {
192             return Ok(());
193         }
194 
195         let layout = font.layout_box(text).map_err(DrawingErrorKind::FontError)?;
196         let ((min_x, min_y), (max_x, max_y)) = layout;
197         let width = (max_x - min_x) as i32;
198         let height = (max_y - min_y) as i32;
199         let dx = match style.pos.h_pos {
200             HPos::Left => 0,
201             HPos::Right => -width,
202             HPos::Center => -width / 2,
203         };
204         let dy = match style.pos.v_pos {
205             VPos::Top => 0,
206             VPos::Center => -height / 2,
207             VPos::Bottom => -height,
208         };
209         let trans = font.get_transform();
210         let (w, h) = self.get_size();
211         match font.draw(text, (0, 0), |x, y, v| {
212             let (x, y) = trans.transform(x + dx - min_x, y + dy - min_y);
213             let (x, y) = (pos.0 + x, pos.1 + y);
214             if x >= 0 && x < w as i32 && y >= 0 && y < h as i32 {
215                 self.draw_pixel((x, y), &color.mix(f64::from(v)))
216             } else {
217                 Ok(())
218             }
219         }) {
220             Ok(drawing_result) => drawing_result,
221             Err(font_error) => Err(DrawingErrorKind::FontError(font_error)),
222         }
223     }
224 
225     /// Estimate the size of the horizontal text if rendered on this backend.
226     /// This is important because some of the backend may not have font ability.
227     /// Thus this allows those backend reports proper value rather than ask the
228     /// font rasterizer for that.
229     ///
230     /// - `text`: The text to estimate
231     /// - `font`: The font to estimate
232     /// - *Returns* The estimated text size
estimate_text_size<'a>( &self, text: &str, font: &FontDesc<'a>, ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>>233     fn estimate_text_size<'a>(
234         &self,
235         text: &str,
236         font: &FontDesc<'a>,
237     ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
238         let layout = font.layout_box(text).map_err(DrawingErrorKind::FontError)?;
239         Ok((
240             ((layout.1).0 - (layout.0).0) as u32,
241             ((layout.1).1 - (layout.0).1) as u32,
242         ))
243     }
244 
245     /// Blit a bitmap on to the backend.
246     ///
247     /// - `text`: pos the left upper conner of the bitmap to blit
248     /// - `src`: The source of the image
249     ///
250     /// TODO: The default implementation of bitmap blitting assumes that the bitmap is RGB, but
251     /// this may not be the case. But for bitmap backend it's actually ok if we use the bitmap
252     /// element that matches the pixel format, but we need to fix this.
blit_bitmap<'a>( &mut self, pos: BackendCoord, (iw, ih): (u32, u32), src: &'a [u8], ) -> Result<(), DrawingErrorKind<Self::ErrorType>>253     fn blit_bitmap<'a>(
254         &mut self,
255         pos: BackendCoord,
256         (iw, ih): (u32, u32),
257         src: &'a [u8],
258     ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
259         let (w, h) = self.get_size();
260 
261         for dx in 0..iw {
262             if pos.0 + dx as i32 >= w as i32 {
263                 break;
264             }
265             for dy in 0..ih {
266                 if pos.1 + dy as i32 >= h as i32 {
267                     break;
268                 }
269                 // FIXME: This assume we have RGB image buffer
270                 let r = src[(dx + dy * w) as usize * 3];
271                 let g = src[(dx + dy * w) as usize * 3 + 1];
272                 let b = src[(dx + dy * w) as usize * 3 + 2];
273                 let color = crate::style::RGBColor(r, g, b);
274                 let result =
275                     self.draw_pixel((pos.0 + dx as i32, pos.1 + dy as i32), &color.to_rgba());
276                 if result.is_err() {
277                     return result;
278                 }
279             }
280         }
281 
282         Ok(())
283     }
284 }
285