freya-plotters-backend 0.4.0

Freya's skia-safe backend for plotters
Documentation
//! Plotters backend for Freya using Skia.
//!
//! This crate implements a `DrawingBackend` for the `plotters` library which
//! renders into Freya's Skia-based canvas.
//!
//! Requires the `plot` feature in `freya`.
//!
//! # Example
//!
//! See `examples/feature_plot_3d.rs`.

use std::error::Error;

use freya_engine::prelude::*;
use plotters_backend::{
    BackendCoord,
    BackendStyle,
    BackendTextStyle,
    DrawingBackend,
    DrawingErrorKind,
    rasterizer,
    text_anchor::{
        HPos,
        VPos,
    },
};

#[derive(Debug)]
pub struct PlotSkiaBackendError;

impl std::fmt::Display for PlotSkiaBackendError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Skia backend error.")
    }
}

impl Error for PlotSkiaBackendError {}

pub struct PlotSkiaBackend<'a> {
    size: (i32, i32),
    canvas: &'a Canvas,
    font_collection: &'a mut FontCollection,
}

impl<'a> PlotSkiaBackend<'a> {
    pub fn new(
        canvas: &'a Canvas,
        font_collection: &'a mut FontCollection,
        size: (i32, i32),
    ) -> Self {
        Self {
            canvas,
            font_collection,
            size,
        }
    }
}

impl DrawingBackend for PlotSkiaBackend<'_> {
    type ErrorType = PlotSkiaBackendError;

    fn draw_line<S: BackendStyle>(
        &mut self,
        from: BackendCoord,
        to: BackendCoord,
        style: &S,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        let mut paint = Paint::default();
        let color = style.color();
        paint.set_color(Color::from_argb(
            (255. * color.alpha) as u8,
            color.rgb.0,
            color.rgb.1,
            color.rgb.2,
        ));
        paint.set_stroke_width(style.stroke_width() as f32);
        self.canvas.draw_line(from, to, &paint);
        Ok(())
    }

    fn draw_rect<S: BackendStyle>(
        &mut self,
        upper_left: BackendCoord,
        bottom_right: BackendCoord,
        style: &S,
        fill: bool,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        let mut paint = Paint::default();
        let color = style.color();
        paint.set_color(Color::from_argb(
            (255. * color.alpha) as u8,
            color.rgb.0,
            color.rgb.1,
            color.rgb.2,
        ));
        paint.set_style(if fill {
            PaintStyle::Fill
        } else {
            PaintStyle::Stroke
        });
        paint.set_stroke_width(style.stroke_width() as f32);
        let rect = Rect::new(
            upper_left.0 as f32,
            upper_left.1 as f32,
            bottom_right.0 as f32,
            bottom_right.1 as f32,
        );
        self.canvas.draw_rect(rect, &paint);
        Ok(())
    }

    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
        &mut self,
        path: I,
        style: &S,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        if style.color().alpha == 0.0 {
            return Ok(());
        }

        // Code based on the SVG backend implementation
        if style.stroke_width() == 1 {
            let mut begin: Option<BackendCoord> = None;
            for end in path.into_iter() {
                if let Some(begin) = begin {
                    let result = self.draw_line(begin, end, style);
                    #[allow(clippy::question_mark)]
                    if result.is_err() {
                        return result;
                    }
                }
                begin = Some(end);
            }
        } else {
            let p: Vec<_> = path.into_iter().collect();
            let v = rasterizer::polygonize(&p[..], style.stroke_width());
            return self.fill_polygon(v, &style.color());
        }
        Ok(())
    }

    fn draw_circle<S: BackendStyle>(
        &mut self,
        center: BackendCoord,
        radius: u32,
        style: &S,
        fill: bool,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        let radius = radius as f32;

        let mut paint = Paint::default();
        let color = style.color();
        paint.set_anti_alias(true);
        paint.set_style(if fill {
            PaintStyle::Fill
        } else {
            PaintStyle::Stroke
        });
        paint.set_color(Color::from_argb(
            (255.0 * color.alpha) as u8,
            color.rgb.0,
            color.rgb.1,
            color.rgb.2,
        ));

        if !fill {
            paint.set_stroke_width(1.0);
        }

        self.canvas.draw_circle(center, radius, &paint);

        Ok(())
    }

    fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
        &mut self,
        vert: I,
        style: &S,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        let mut vert = vert.into_iter();
        let Some(first) = vert.next() else {
            return Ok(());
        };

        let mut paint = Paint::default();
        let color = style.color();
        paint.set_color(Color::from_argb(
            (255. * color.alpha) as u8,
            color.rgb.0,
            color.rgb.1,
            color.rgb.2,
        ));

        let mut path = PathBuilder::new();
        path.move_to(first);
        for pos in vert {
            path.line_to(pos);
        }
        self.canvas.draw_path(&path.detach(), &paint);

        Ok(())
    }

    fn draw_text<TStyle: BackendTextStyle>(
        &mut self,
        text: &str,
        style: &TStyle,
        pos: BackendCoord,
    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
        let mut builder =
            ParagraphBuilder::new(&ParagraphStyle::default(), self.font_collection.clone());
        let mut text_style = TextStyle::new();
        let color = style.color();
        text_style.set_color(Color::from_argb(
            (255. * color.alpha) as u8,
            color.rgb.0,
            color.rgb.1,
            color.rgb.2,
        ));
        text_style.set_font_families(&[style.family().as_str()]);
        text_style.set_font_size(style.size() as f32);
        builder.push_style(&text_style);
        builder.add_text(text);
        let mut paragraph = builder.build();
        paragraph.layout(f32::MAX);

        let mut pos = (pos.0 as f32, pos.1 as f32);
        match style.anchor().h_pos {
            HPos::Left => {}
            HPos::Center => {
                pos.0 -= paragraph.max_intrinsic_width() / 2.0;
            }
            HPos::Right => {
                pos.0 -= paragraph.max_intrinsic_width();
            }
        }
        match style.anchor().v_pos {
            VPos::Top => {}
            VPos::Center => {
                pos.1 -= paragraph.height() / 2.0;
            }
            VPos::Bottom => {
                pos.1 -= paragraph.height();
            }
        }

        paragraph.paint(self.canvas, pos);
        Ok(())
    }

    fn estimate_text_size<TStyle: BackendTextStyle>(
        &self,
        text: &str,
        style: &TStyle,
    ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
        let mut builder =
            ParagraphBuilder::new(&ParagraphStyle::default(), self.font_collection.clone());
        let mut text_style = TextStyle::new();
        let color = style.color();
        text_style.set_color(Color::from_argb(
            (255. * color.alpha) as u8,
            color.rgb.0,
            color.rgb.1,
            color.rgb.2,
        ));
        text_style.set_font_families(&[style.family().as_str()]);
        text_style.set_font_size(style.size() as f32);
        builder.push_style(&text_style);
        builder.add_text(text);
        let mut paragraph = builder.build();
        paragraph.layout(f32::MAX);
        Ok((
            paragraph.max_intrinsic_width() as u32,
            paragraph.height() as u32,
        ))
    }

    fn draw_pixel(
        &mut self,
        _point: plotters_backend::BackendCoord,
        _color: plotters_backend::BackendColor,
    ) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
        todo!()
    }

    fn get_size(&self) -> (u32, u32) {
        (self.size.0 as u32, self.size.1 as u32)
    }

    fn ensure_prepared(
        &mut self,
    ) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
        Ok(())
    }

    fn present(&mut self) -> Result<(), plotters_backend::DrawingErrorKind<Self::ErrorType>> {
        Ok(())
    }
}