plotters_piet/
lib.rs

1/*!
2A [Piet](https://crates.io/crates/piet) backend for [Plotters](https://crates.io/crates/plotters). This lets you draw plots on a Piet render context.
3*/
4
5use piet_common::{kurbo, Color, LineCap, Piet, RenderContext, StrokeStyle};
6use plotters_backend::{BackendColor, BackendCoord, DrawingBackend, DrawingErrorKind};
7
8#[derive(Debug, PartialEq, Eq)]
9pub struct Error {}
10
11impl std::fmt::Display for Error {
12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13        writeln!(f, "plotters-piet error")
14    }
15}
16
17impl std::error::Error for Error {}
18
19/// The piet backend.
20///
21/// Note that the size of the piet context has to be specified here.
22pub struct PietBackend<'a, 'b> {
23    pub size: (u32, u32),
24    pub render_ctx: &'a mut Piet<'b>,
25}
26
27impl<'a, 'b> std::fmt::Debug for PietBackend<'a, 'b> {
28    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
29        fmt.debug_struct("PietBackend")
30            .field("size", &self.size)
31            .field("render_ctx", &"(not printable)")
32            .finish()
33    }
34}
35
36impl<'a, 'b> DrawingBackend for PietBackend<'a, 'b> {
37    type ErrorType = Error;
38
39    fn get_size(&self) -> (u32, u32) {
40        self.size
41    }
42
43    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
44        Ok(())
45    }
46
47    fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
48        self.render_ctx
49            .finish()
50            .map_err(|_| DrawingErrorKind::DrawingError(Error {}))
51    }
52
53    fn draw_pixel(
54        &mut self,
55        point: BackendCoord,
56        color: BackendColor,
57    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
58        let x = point.0 as f64;
59        let y = point.1 as f64;
60        self.render_ctx.fill(
61            kurbo::Rect::new(x, y, x + 1., y + 1.),
62            &plotters_color_to_piet(&color),
63        );
64        Ok(())
65    }
66
67    fn draw_line<S: plotters_backend::BackendStyle>(
68        &mut self,
69        from: BackendCoord,
70        to: BackendCoord,
71        style: &S,
72    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
73        let from = plotters_point_to_kurbo_mid(from);
74        let to = plotters_point_to_kurbo_mid(to);
75
76        self.render_ctx.stroke_styled(
77            kurbo::Line::new(from, to),
78            &plotters_color_to_piet(&style.color()),
79            style.stroke_width() as f64,
80            &STROKE_STYLE_SQUARE_CAP,
81        );
82        Ok(())
83    }
84
85    fn draw_rect<S: plotters_backend::BackendStyle>(
86        &mut self,
87        upper_left: BackendCoord,
88        bottom_right: BackendCoord,
89        style: &S,
90        fill: bool,
91    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
92        let color = plotters_color_to_piet(&style.color());
93
94        if fill {
95            let upper_left = plotters_point_to_kurbo_corner(upper_left);
96            let mut bottom_right = plotters_point_to_kurbo_corner(bottom_right);
97            bottom_right.x += 1.;
98            bottom_right.y += 1.;
99            let rect = kurbo::Rect::new(upper_left.x, upper_left.y, bottom_right.x, bottom_right.y);
100
101            self.render_ctx.fill(rect, &color);
102        } else {
103            let upper_left = plotters_point_to_kurbo_mid(upper_left);
104            let bottom_right = plotters_point_to_kurbo_mid(bottom_right);
105            let rect = kurbo::Rect::new(upper_left.x, upper_left.y, bottom_right.x, bottom_right.y);
106
107            self.render_ctx
108                .stroke(rect, &color, style.stroke_width() as f64);
109        }
110
111        Ok(())
112    }
113
114    fn draw_path<S: plotters_backend::BackendStyle, I: IntoIterator<Item = BackendCoord>>(
115        &mut self,
116        path: I,
117        style: &S,
118    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
119        if style.color().alpha == 0.0 {
120            return Ok(());
121        }
122
123        let path: Vec<kurbo::PathEl> = plotters_path_to_kurbo(path).collect();
124
125        self.render_ctx.stroke_styled(
126            &*path,
127            &plotters_color_to_piet(&style.color()),
128            style.stroke_width() as f64,
129            &STROKE_STYLE_SQUARE_CAP,
130        );
131        Ok(())
132    }
133
134    fn draw_circle<S: plotters_backend::BackendStyle>(
135        &mut self,
136        center: BackendCoord,
137        radius: u32,
138        style: &S,
139        fill: bool,
140    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
141        let center = plotters_point_to_kurbo_mid(center);
142        let color = plotters_color_to_piet(&style.color());
143        let circle = kurbo::Circle::new(center, radius as f64);
144
145        if fill {
146            self.render_ctx.fill(circle, &color);
147        } else {
148            self.render_ctx
149                .stroke(circle, &color, style.stroke_width() as f64);
150        }
151        Ok(())
152    }
153
154    fn fill_polygon<S: plotters_backend::BackendStyle, I: IntoIterator<Item = BackendCoord>>(
155        &mut self,
156        vert: I,
157        style: &S,
158    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
159        if style.color().alpha == 0.0 {
160            return Ok(());
161        }
162
163        let path: Vec<kurbo::PathEl> = plotters_path_to_kurbo(vert)
164            .chain(std::iter::once(kurbo::PathEl::ClosePath))
165            .collect();
166        self.render_ctx
167            .fill(&*path, &plotters_color_to_piet(&style.color()));
168        Ok(())
169    }
170
171    // For now we use the default text drawing provided by plotters. This is definitely slower,
172    // but at least we don't have to worry about matching the font size and offset which turns
173    // out to be trickier than expected.
174    // fn draw_text<TStyle: plotters_backend::BackendTextStyle>(
175    //     &mut self,
176    //     text: &str,
177    //     style: &TStyle,
178    //     pos: BackendCoord,
179    // ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
180    //     let pos = plotters_point_to_kurbo(pos);
181    //     let color = plotters_color_to_piet(&style.color());
182
183    //     let text_api = self.render_ctx.text();
184    //     let font_family = match style.family() {
185    //         plotters_backend::FontFamily::Serif => Ok(FontFamily::SERIF),
186    //         plotters_backend::FontFamily::SansSerif => Ok(FontFamily::SANS_SERIF),
187    //         plotters_backend::FontFamily::Monospace => Ok(FontFamily::MONOSPACE),
188    //         plotters_backend::FontFamily::Name(name) => text_api
189    //             .font_family(name)
190    //             .ok_or(piet_common::Error::MissingFont),
191    //     };
192
193    //     let (font_style, weight) = match style.style() {
194    //         plotters_backend::FontStyle::Normal => (FontStyle::Regular, FontWeight::REGULAR),
195    //         plotters_backend::FontStyle::Oblique => (FontStyle::Italic, FontWeight::REGULAR),
196    //         plotters_backend::FontStyle::Italic => (FontStyle::Italic, FontWeight::REGULAR),
197    //         plotters_backend::FontStyle::Bold => (FontStyle::Regular, FontWeight::BOLD),
198    //     };
199
200    //     let alignment = match style.anchor().h_pos {
201    //         plotters_backend::text_anchor::HPos::Left => TextAlignment::Start,
202    //         plotters_backend::text_anchor::HPos::Right => TextAlignment::End,
203    //         plotters_backend::text_anchor::HPos::Center => TextAlignment::Center,
204    //     };
205
206    //     let layout = text_api
207    //         .new_text_layout(String::from(text))
208    //         .font(font_family.unwrap(), style.size())
209    //         .text_color(color)
210    //         .alignment(alignment)
211    //         .default_attribute(TextAttribute::Style(font_style))
212    //         .default_attribute(TextAttribute::Weight(weight))
213    //         .build()
214    //         .unwrap();
215
216    //     // todo: style.anchor().v_pos
217    //     // todo: style.transform()
218
219    //     self.render_ctx.draw_text(&layout, pos);
220    //     Ok(())
221    // }
222}
223
224fn plotters_color_to_piet(col: &BackendColor) -> piet_common::Color {
225    Color::rgba8(col.rgb.0, col.rgb.1, col.rgb.2, (col.alpha * 256.) as u8)
226}
227
228fn plotters_point_to_kurbo_mid((x, y): BackendCoord) -> kurbo::Point {
229    kurbo::Point {
230        x: x as f64 + 0.5,
231        y: y as f64 + 0.5,
232    }
233}
234
235fn plotters_point_to_kurbo_corner((x, y): BackendCoord) -> kurbo::Point {
236    kurbo::Point {
237        x: x as f64,
238        y: y as f64,
239    }
240}
241
242/// This is basically just an iterator map that applies a different function on
243/// the first item as on the later items.
244/// We need this because the piet direct2d backend doesn't like it if a path
245/// consists entirely of `LineTo` entries, it requires the first entry to be
246/// a `MoveTo` entry.
247struct PlottersPathToKurbo<I> {
248    iter: I,
249    first: bool,
250}
251
252impl<I> PlottersPathToKurbo<I> {
253    fn new(path: I) -> PlottersPathToKurbo<I> {
254        PlottersPathToKurbo {
255            iter: path,
256            first: true,
257        }
258    }
259}
260
261impl<I> Iterator for PlottersPathToKurbo<I>
262where
263    I: Iterator<Item = BackendCoord>,
264{
265    type Item = kurbo::PathEl;
266
267    fn next(&mut self) -> Option<Self::Item> {
268        self.iter.next().map(|point| {
269            let point = plotters_point_to_kurbo_mid(point);
270
271            if self.first {
272                self.first = false;
273                kurbo::PathEl::MoveTo(point)
274            } else {
275                kurbo::PathEl::LineTo(point)
276            }
277        })
278    }
279}
280
281fn plotters_path_to_kurbo(
282    path: impl IntoIterator<Item = BackendCoord>,
283) -> impl Iterator<Item = kurbo::PathEl> {
284    PlottersPathToKurbo::new(path.into_iter())
285}
286
287const STROKE_STYLE_SQUARE_CAP: StrokeStyle = StrokeStyle::new().line_cap(LineCap::Square);
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292    use piet_common::RenderContext;
293    use plotters::prelude::*;
294
295    #[test]
296    fn fill_root_white() {
297        let width = 3;
298        let height = 2;
299
300        let mut device = piet_common::Device::new().unwrap();
301        let mut bitmap = device.bitmap_target(width, height, 1.0).unwrap();
302
303        {
304            let mut render_ctx = bitmap.render_context();
305
306            let piet_backend = PietBackend {
307                size: (width as u32, height as u32),
308                render_ctx: &mut render_ctx,
309            };
310
311            let root = piet_backend.into_drawing_area();
312            root.fill(&WHITE).unwrap();
313
314            render_ctx.finish().unwrap();
315        }
316
317        let mut buf = [0; 6 * 4];
318        bitmap
319            .copy_raw_pixels(piet_common::ImageFormat::RgbaPremul, &mut buf)
320            .unwrap();
321
322        assert_eq!(buf, [255; 6 * 4]);
323    }
324
325    #[test]
326    fn test_plotters_path_to_kurbo() {
327        let path = vec![(1, 2), (3, 4), (5, 6)];
328
329        let kurbo_path: Vec<kurbo::PathEl> = plotters_path_to_kurbo(path).collect();
330
331        assert_eq!(
332            kurbo_path,
333            vec![
334                kurbo::PathEl::MoveTo(kurbo::Point { x: 1.5, y: 2.5 }),
335                kurbo::PathEl::LineTo(kurbo::Point { x: 3.5, y: 4.5 }),
336                kurbo::PathEl::LineTo(kurbo::Point { x: 5.5, y: 6.5 }),
337            ]
338        );
339    }
340}