freya_plotters_backend/
lib.rs

1//! Plotters backend for Freya using Skia.
2//!
3//! This crate implements a `DrawingBackend` for the `plotters` library which
4//! renders into Freya's Skia-based canvas.
5//!
6//! Requires the `plot` feature in `freya`.
7//!
8//! # Example
9//!
10//! See `examples/feature_plot_3d.rs`.
11
12use std::error::Error;
13
14use freya_engine::prelude::*;
15use plotters_backend::{
16    BackendCoord,
17    BackendStyle,
18    BackendTextStyle,
19    DrawingBackend,
20    DrawingErrorKind,
21    rasterizer,
22    text_anchor::{
23        HPos,
24        VPos,
25    },
26};
27
28#[derive(Debug)]
29pub struct PlotSkiaBackendError;
30
31impl std::fmt::Display for PlotSkiaBackendError {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.write_str("Skia backend error.")
34    }
35}
36
37impl Error for PlotSkiaBackendError {}
38
39pub struct PlotSkiaBackend<'a> {
40    size: (i32, i32),
41    canvas: &'a Canvas,
42    font_collection: &'a mut FontCollection,
43}
44
45impl<'a> PlotSkiaBackend<'a> {
46    pub fn new(
47        canvas: &'a Canvas,
48        font_collection: &'a mut FontCollection,
49        size: (i32, i32),
50    ) -> Self {
51        Self {
52            canvas,
53            font_collection,
54            size,
55        }
56    }
57}
58
59impl DrawingBackend for PlotSkiaBackend<'_> {
60    type ErrorType = PlotSkiaBackendError;
61
62    fn draw_line<S: BackendStyle>(
63        &mut self,
64        from: BackendCoord,
65        to: BackendCoord,
66        style: &S,
67    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
68        let mut paint = Paint::default();
69        let color = style.color();
70        paint.set_color(Color::from_argb(
71            (255. * color.alpha) as u8,
72            color.rgb.0,
73            color.rgb.1,
74            color.rgb.2,
75        ));
76        paint.set_stroke_width(style.stroke_width() as f32);
77        self.canvas.draw_line(from, to, &paint);
78        Ok(())
79    }
80
81    fn draw_rect<S: BackendStyle>(
82        &mut self,
83        upper_left: BackendCoord,
84        bottom_right: BackendCoord,
85        style: &S,
86        fill: bool,
87    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
88        let mut paint = Paint::default();
89        let color = style.color();
90        paint.set_color(Color::from_argb(
91            (255. * color.alpha) as u8,
92            color.rgb.0,
93            color.rgb.1,
94            color.rgb.2,
95        ));
96        paint.set_style(if fill {
97            PaintStyle::Fill
98        } else {
99            PaintStyle::Stroke
100        });
101        paint.set_stroke_width(style.stroke_width() as f32);
102        let rect = Rect::new(
103            upper_left.0 as f32,
104            upper_left.1 as f32,
105            bottom_right.0 as f32,
106            bottom_right.1 as f32,
107        );
108        self.canvas.draw_rect(rect, &paint);
109        Ok(())
110    }
111
112    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
113        &mut self,
114        path: I,
115        style: &S,
116    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
117        if style.color().alpha == 0.0 {
118            return Ok(());
119        }
120
121        // Code based on the SVG backend implementation
122        if style.stroke_width() == 1 {
123            let mut begin: Option<BackendCoord> = None;
124            for end in path.into_iter() {
125                if let Some(begin) = begin {
126                    let result = self.draw_line(begin, end, style);
127                    #[allow(clippy::question_mark)]
128                    if result.is_err() {
129                        return result;
130                    }
131                }
132                begin = Some(end);
133            }
134        } else {
135            let p: Vec<_> = path.into_iter().collect();
136            let v = rasterizer::polygonize(&p[..], style.stroke_width());
137            return self.fill_polygon(v, &style.color());
138        }
139        Ok(())
140    }
141
142    fn draw_circle<S: BackendStyle>(
143        &mut self,
144        center: BackendCoord,
145        radius: u32,
146        style: &S,
147        fill: bool,
148    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
149        let radius = radius as f32;
150
151        let mut paint = Paint::default();
152        let color = style.color();
153        paint.set_anti_alias(true);
154        paint.set_style(if fill {
155            PaintStyle::Fill
156        } else {
157            PaintStyle::Stroke
158        });
159        paint.set_color(Color::from_argb(
160            (255.0 * color.alpha) as u8,
161            color.rgb.0,
162            color.rgb.1,
163            color.rgb.2,
164        ));
165
166        if !fill {
167            paint.set_stroke_width(1.0);
168        }
169
170        self.canvas.draw_circle(center, radius, &paint);
171
172        Ok(())
173    }
174
175    fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
176        &mut self,
177        vert: I,
178        style: &S,
179    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
180        let vert_buf: Vec<_> = vert.into_iter().collect();
181
182        let mut paint = Paint::default();
183        let color = style.color();
184        paint.set_color(Color::from_argb(
185            (255. * color.alpha) as u8,
186            color.rgb.0,
187            color.rgb.1,
188            color.rgb.2,
189        ));
190        let mut path = PathBuilder::new();
191        let first = vert_buf[0];
192        path.move_to(first);
193
194        for pos in &vert_buf[1..] {
195            path.line_to(*pos);
196        }
197        let path = path.detach();
198        self.canvas.draw_path(&path, &paint);
199
200        Ok(())
201    }
202
203    fn draw_text<TStyle: BackendTextStyle>(
204        &mut self,
205        text: &str,
206        style: &TStyle,
207        pos: BackendCoord,
208    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
209        let mut builder =
210            ParagraphBuilder::new(&ParagraphStyle::default(), self.font_collection.clone());
211        let mut text_style = TextStyle::new();
212        let color = style.color();
213        text_style.set_color(Color::from_argb(
214            (255. * color.alpha) as u8,
215            color.rgb.0,
216            color.rgb.1,
217            color.rgb.2,
218        ));
219        text_style.set_font_families(&[style.family().as_str()]);
220        text_style.set_font_size(style.size() as f32);
221        builder.push_style(&text_style);
222        builder.add_text(text);
223        let mut paragraph = builder.build();
224        paragraph.layout(f32::MAX);
225
226        let mut pos = (pos.0 as f32, pos.1 as f32);
227        match style.anchor().h_pos {
228            HPos::Left => {}
229            HPos::Center => {
230                pos.0 -= paragraph.max_intrinsic_width() / 2.0;
231            }
232            HPos::Right => {
233                pos.0 -= paragraph.max_intrinsic_width();
234            }
235        }
236        match style.anchor().v_pos {
237            VPos::Top => {}
238            VPos::Center => {
239                pos.1 -= paragraph.height() / 2.0;
240            }
241            VPos::Bottom => {
242                pos.1 -= paragraph.height();
243            }
244        }
245
246        paragraph.paint(self.canvas, pos);
247        Ok(())
248    }
249
250    fn estimate_text_size<TStyle: BackendTextStyle>(
251        &self,
252        text: &str,
253        style: &TStyle,
254    ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
255        let mut builder =
256            ParagraphBuilder::new(&ParagraphStyle::default(), self.font_collection.clone());
257        let mut text_style = TextStyle::new();
258        let color = style.color();
259        text_style.set_color(Color::from_argb(
260            (255. * color.alpha) as u8,
261            color.rgb.0,
262            color.rgb.1,
263            color.rgb.2,
264        ));
265        text_style.set_font_families(&[style.family().as_str()]);
266        text_style.set_font_size(style.size() as f32);
267        builder.push_style(&text_style);
268        builder.add_text(text);
269        let mut paragraph = builder.build();
270        paragraph.layout(f32::MAX);
271        Ok((
272            paragraph.max_intrinsic_width() as u32,
273            paragraph.height() as u32,
274        ))
275    }
276
277    fn draw_pixel(
278        &mut self,
279        _point: plotters_backend::BackendCoord,
280        _color: plotters_backend::BackendColor,
281    ) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
282        todo!()
283    }
284
285    fn get_size(&self) -> (u32, u32) {
286        (self.size.0 as u32, self.size.1 as u32)
287    }
288
289    fn ensure_prepared(
290        &mut self,
291    ) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
292        Ok(())
293    }
294
295    fn present(&mut self) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
296        Ok(())
297    }
298}