Skip to main content

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 mut vert = vert.into_iter();
181        let Some(first) = vert.next() else {
182            return Ok(());
183        };
184
185        let mut paint = Paint::default();
186        let color = style.color();
187        paint.set_color(Color::from_argb(
188            (255. * color.alpha) as u8,
189            color.rgb.0,
190            color.rgb.1,
191            color.rgb.2,
192        ));
193
194        let mut path = PathBuilder::new();
195        path.move_to(first);
196        for pos in vert {
197            path.line_to(pos);
198        }
199        self.canvas.draw_path(&path.detach(), &paint);
200
201        Ok(())
202    }
203
204    fn draw_text<TStyle: BackendTextStyle>(
205        &mut self,
206        text: &str,
207        style: &TStyle,
208        pos: BackendCoord,
209    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
210        let mut builder =
211            ParagraphBuilder::new(&ParagraphStyle::default(), self.font_collection.clone());
212        let mut text_style = TextStyle::new();
213        let color = style.color();
214        text_style.set_color(Color::from_argb(
215            (255. * color.alpha) as u8,
216            color.rgb.0,
217            color.rgb.1,
218            color.rgb.2,
219        ));
220        text_style.set_font_families(&[style.family().as_str()]);
221        text_style.set_font_size(style.size() as f32);
222        builder.push_style(&text_style);
223        builder.add_text(text);
224        let mut paragraph = builder.build();
225        paragraph.layout(f32::MAX);
226
227        let mut pos = (pos.0 as f32, pos.1 as f32);
228        match style.anchor().h_pos {
229            HPos::Left => {}
230            HPos::Center => {
231                pos.0 -= paragraph.max_intrinsic_width() / 2.0;
232            }
233            HPos::Right => {
234                pos.0 -= paragraph.max_intrinsic_width();
235            }
236        }
237        match style.anchor().v_pos {
238            VPos::Top => {}
239            VPos::Center => {
240                pos.1 -= paragraph.height() / 2.0;
241            }
242            VPos::Bottom => {
243                pos.1 -= paragraph.height();
244            }
245        }
246
247        paragraph.paint(self.canvas, pos);
248        Ok(())
249    }
250
251    fn estimate_text_size<TStyle: BackendTextStyle>(
252        &self,
253        text: &str,
254        style: &TStyle,
255    ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
256        let mut builder =
257            ParagraphBuilder::new(&ParagraphStyle::default(), self.font_collection.clone());
258        let mut text_style = TextStyle::new();
259        let color = style.color();
260        text_style.set_color(Color::from_argb(
261            (255. * color.alpha) as u8,
262            color.rgb.0,
263            color.rgb.1,
264            color.rgb.2,
265        ));
266        text_style.set_font_families(&[style.family().as_str()]);
267        text_style.set_font_size(style.size() as f32);
268        builder.push_style(&text_style);
269        builder.add_text(text);
270        let mut paragraph = builder.build();
271        paragraph.layout(f32::MAX);
272        Ok((
273            paragraph.max_intrinsic_width() as u32,
274            paragraph.height() as u32,
275        ))
276    }
277
278    fn draw_pixel(
279        &mut self,
280        _point: plotters_backend::BackendCoord,
281        _color: plotters_backend::BackendColor,
282    ) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
283        todo!()
284    }
285
286    fn get_size(&self) -> (u32, u32) {
287        (self.size.0 as u32, self.size.1 as u32)
288    }
289
290    fn ensure_prepared(
291        &mut self,
292    ) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
293        Ok(())
294    }
295
296    fn present(&mut self) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
297        Ok(())
298    }
299}