use piet_common::{kurbo, Color, LineCap, Piet, RenderContext, StrokeDash, StrokeStyle}; use plotters_backend::{BackendColor, BackendCoord, DrawingBackend, DrawingErrorKind}; #[derive(Debug, PartialEq, Eq)] pub struct Error {} impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "plotters-piet error") } } impl std::error::Error for Error {} /// The piet backend. /// /// Note that the size of the piet context has to be specified here. pub struct PietBackend<'a, 'b> { pub size: (u32, u32), pub render_ctx: &'a mut Piet<'b>, } impl<'a, 'b> std::fmt::Debug for PietBackend<'a, 'b> { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { fmt.debug_struct("PietBackend") .field("size", &self.size) .field("render_ctx", &"(not printable)") .finish() } } impl<'a, 'b> DrawingBackend for PietBackend<'a, 'b> { type ErrorType = Error; fn get_size(&self) -> (u32, u32) { self.size } fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind> { Ok(()) } fn present(&mut self) -> Result<(), DrawingErrorKind> { self.render_ctx .finish() .map_err(|_| DrawingErrorKind::DrawingError(Error {})) } fn draw_pixel( &mut self, point: BackendCoord, color: BackendColor, ) -> Result<(), DrawingErrorKind> { let x = point.0 as f64; let y = point.1 as f64; self.render_ctx.fill( kurbo::Rect::new(x, y, x + 1., y + 1.), &plotters_color_to_piet(&color), ); Ok(()) } fn draw_line( &mut self, from: BackendCoord, to: BackendCoord, style: &S, ) -> Result<(), DrawingErrorKind> { let from = plotters_point_to_kurbo_mid(from); let to = plotters_point_to_kurbo_mid(to); self.render_ctx.stroke_styled( kurbo::Line::new(from, to), &plotters_color_to_piet(&style.color()), style.stroke_width() as f64, &STROKE_STYLE_SQUARE_CAP, ); Ok(()) } fn draw_rect( &mut self, upper_left: BackendCoord, bottom_right: BackendCoord, style: &S, fill: bool, ) -> Result<(), DrawingErrorKind> { let color = plotters_color_to_piet(&style.color()); if fill { let upper_left = plotters_point_to_kurbo_corner(upper_left); let mut bottom_right = plotters_point_to_kurbo_corner(bottom_right); bottom_right.x += 1.; bottom_right.y += 1.; let rect = kurbo::Rect::new(upper_left.x, upper_left.y, bottom_right.x, bottom_right.y); self.render_ctx.fill(rect, &color); } else { let upper_left = plotters_point_to_kurbo_mid(upper_left); let bottom_right = plotters_point_to_kurbo_mid(bottom_right); let rect = kurbo::Rect::new(upper_left.x, upper_left.y, bottom_right.x, bottom_right.y); self.render_ctx .stroke(rect, &color, style.stroke_width() as f64); } Ok(()) } fn draw_path>( &mut self, path: I, style: &S, ) -> Result<(), DrawingErrorKind> { if style.color().alpha == 0.0 { return Ok(()); } let path: Vec = plotters_path_to_kurbo(path).collect(); self.render_ctx.stroke_styled( &*path, &plotters_color_to_piet(&style.color()), style.stroke_width() as f64, &STROKE_STYLE_SQUARE_CAP, ); Ok(()) } fn draw_circle( &mut self, center: BackendCoord, radius: u32, style: &S, fill: bool, ) -> Result<(), DrawingErrorKind> { let center = plotters_point_to_kurbo_mid(center); let color = plotters_color_to_piet(&style.color()); let circle = kurbo::Circle::new(center, radius as f64); if fill { self.render_ctx.fill(circle, &color); } else { self.render_ctx .stroke(circle, &color, style.stroke_width() as f64); } Ok(()) } fn fill_polygon>( &mut self, vert: I, style: &S, ) -> Result<(), DrawingErrorKind> { if style.color().alpha == 0.0 { return Ok(()); } let path: Vec = plotters_path_to_kurbo(vert) .chain(std::iter::once(kurbo::PathEl::ClosePath)) .collect(); self.render_ctx .fill(&*path, &plotters_color_to_piet(&style.color())); Ok(()) } // For now we use the default text drawing provided by plotters. This is definitely slower, // but at least we don't have to worry about matching the font size and offset which turns // out to be trickier than expected. // fn draw_text( // &mut self, // text: &str, // style: &TStyle, // pos: BackendCoord, // ) -> Result<(), DrawingErrorKind> { // let pos = plotters_point_to_kurbo(pos); // let color = plotters_color_to_piet(&style.color()); // let text_api = self.render_ctx.text(); // let font_family = match style.family() { // plotters_backend::FontFamily::Serif => Ok(FontFamily::SERIF), // plotters_backend::FontFamily::SansSerif => Ok(FontFamily::SANS_SERIF), // plotters_backend::FontFamily::Monospace => Ok(FontFamily::MONOSPACE), // plotters_backend::FontFamily::Name(name) => text_api // .font_family(name) // .ok_or(piet_common::Error::MissingFont), // }; // let (font_style, weight) = match style.style() { // plotters_backend::FontStyle::Normal => (FontStyle::Regular, FontWeight::REGULAR), // plotters_backend::FontStyle::Oblique => (FontStyle::Italic, FontWeight::REGULAR), // plotters_backend::FontStyle::Italic => (FontStyle::Italic, FontWeight::REGULAR), // plotters_backend::FontStyle::Bold => (FontStyle::Regular, FontWeight::BOLD), // }; // let alignment = match style.anchor().h_pos { // plotters_backend::text_anchor::HPos::Left => TextAlignment::Start, // plotters_backend::text_anchor::HPos::Right => TextAlignment::End, // plotters_backend::text_anchor::HPos::Center => TextAlignment::Center, // }; // let layout = text_api // .new_text_layout(String::from(text)) // .font(font_family.unwrap(), style.size()) // .text_color(color) // .alignment(alignment) // .default_attribute(TextAttribute::Style(font_style)) // .default_attribute(TextAttribute::Weight(weight)) // .build() // .unwrap(); // // todo: style.anchor().v_pos // // todo: style.transform() // self.render_ctx.draw_text(&layout, pos); // Ok(()) // } } fn plotters_color_to_piet(col: &BackendColor) -> piet_common::Color { Color::rgba8(col.rgb.0, col.rgb.1, col.rgb.2, (col.alpha * 256.) as u8) } fn plotters_point_to_kurbo_mid((x, y): BackendCoord) -> kurbo::Point { kurbo::Point { x: x as f64 + 0.5, y: y as f64 + 0.5, } } fn plotters_point_to_kurbo_corner((x, y): BackendCoord) -> kurbo::Point { kurbo::Point { x: x as f64, y: y as f64, } } /// This is basically just an iterator map that applies a different function on /// the first item as on the later items. /// We need this because the piet direct2d backend doesn't like it if a path /// consists entirely of `LineTo` entries, it requires the first entry to be /// a `MoveTo` entry. struct PlottersPathToKurbo { iter: I, first: bool, } impl PlottersPathToKurbo { fn new(path: I) -> PlottersPathToKurbo { PlottersPathToKurbo { iter: path, first: true, } } } impl Iterator for PlottersPathToKurbo where I: Iterator, { type Item = kurbo::PathEl; fn next(&mut self) -> Option { self.iter.next().map(|point| { let point = plotters_point_to_kurbo_mid(point); if self.first { self.first = false; kurbo::PathEl::MoveTo(point) } else { kurbo::PathEl::LineTo(point) } }) } } fn plotters_path_to_kurbo( path: impl IntoIterator, ) -> impl Iterator { PlottersPathToKurbo::new(path.into_iter()) } const STROKE_STYLE_SQUARE_CAP: StrokeStyle = StrokeStyle::new().line_cap(LineCap::Square); #[cfg(test)] mod tests { use super::*; use piet_common::RenderContext; use plotters::prelude::*; #[test] fn fill_root_white() { let width = 3; let height = 2; let mut device = piet_common::Device::new().unwrap(); let mut bitmap = device.bitmap_target(width, height, 1.0).unwrap(); { let mut render_ctx = bitmap.render_context(); let piet_backend = PietBackend { size: (width as u32, height as u32), render_ctx: &mut render_ctx, }; let root = piet_backend.into_drawing_area(); root.fill(&WHITE).unwrap(); render_ctx.finish().unwrap(); } let mut buf = [0; 6 * 4]; bitmap .copy_raw_pixels(piet_common::ImageFormat::RgbaPremul, &mut buf) .unwrap(); assert_eq!(buf, [255; 6 * 4]); } #[test] fn test_plotters_path_to_kurbo() { let path = vec![(1, 2), (3, 4), (5, 6)]; let kurbo_path: Vec = plotters_path_to_kurbo(path).collect(); assert_eq!( kurbo_path, vec![ kurbo::PathEl::MoveTo(kurbo::Point { x: 1.5, y: 2.5 }), kurbo::PathEl::LineTo(kurbo::Point { x: 3.5, y: 4.5 }), kurbo::PathEl::LineTo(kurbo::Point { x: 5.5, y: 6.5 }), ] ); } }