x-graphics 0.2.1

Graphics framework for X
Documentation
use web_sys::{CanvasWindingRule, Path2d};

use super::device::WebDeviceContext;
use crate::{
    error::GraphicsError,
    geometry::FRect,
    path::{FillType, PathBackend},
    Float,
};

#[derive(Clone, Debug)]
pub struct WebPath {
    path: Path2d,
    fill_type: FillType,
    points: Vec<(Float, Float)>,
}

impl PathBackend for WebPath {
    type DeviceContextType = WebDeviceContext;

    fn new(_context: Option<&Self::DeviceContextType>) -> Result<Self, GraphicsError> {
        Path2d::new()
            .map(|path| Self {
                path,
                fill_type: FillType::Winding,
                points: Vec::new(),
            })
            .map_err(|err| GraphicsError::CreationFailed(err.as_string().unwrap_or(stringify!(Path2d).to_string())))
    }

    fn get_fill_type(&self) -> FillType {
        self.fill_type
    }

    fn set_fill_type(&mut self, fill_type: FillType) {
        self.fill_type = fill_type;
    }

    fn begin(&mut self) {
        let path = Path2d::new();
        if let Ok(path) = path {
            self.path = path;
            self.points.clear();
        }
    }

    fn close(&mut self) {
        self.path.close_path();
    }

    fn move_to(&mut self, x: Float, y: Float) {
        self.path.move_to(x, y);
        self.points.push((x, y));
    }

    fn line_to(&mut self, x: Float, y: Float) {
        self.path.line_to(x, y);
        self.points.push((x, y));
    }

    fn arc_to(&mut self, x1: Float, y1: Float, x2: Float, y2: Float, radius: Float) {
        self.path.arc_to(x1, y1, x2, y2, radius).ok();
        // The correct codes for acquiring boundary points have not been implemented.
        self.points.push((x1, y1));
        self.points.push((x2, y2));
    }

    fn bezier_curve_to(&mut self, cpx1: Float, cpy1: Float, cpx2: Float, cpy2: Float, x: Float, y: Float) {
        self.path.bezier_curve_to(cpx1, cpy1, cpx2, cpy2, x, y);
        // The correct codes for acquiring boundary points have not been implemented.
        self.points.push((cpx1, cpy1));
        self.points.push((cpx2, cpy2));
        self.points.push((x, y));
    }

    fn quad_curve_to(&mut self, cpx: Float, cpy: Float, x: Float, y: Float) {
        self.path.quadratic_curve_to(cpx, cpy, x, y);
        // The correct codes for acquiring boundary points have not been implemented.
        self.points.push((cpx, cpy));
        self.points.push((x, y));
    }

    fn add_arc(&mut self, x: Float, y: Float, radius: Float, start_angle: Float, end_angle: Float, clockwise: bool) {
        self.path.arc_with_anticlockwise(x, y, radius, start_angle, end_angle, clockwise).ok();
        // The correct codes for acquiring boundary points have not been implemented.
        self.points.push((x - radius, y - radius));
        self.points.push((x + radius, y + radius));
    }

    fn add_rect(&mut self, x: Float, y: Float, width: Float, height: Float) {
        self.path.rect(x, y, width, height);
        self.points.push((x, y));
        self.points.push((x + width, y + height));
    }

    fn add_circle(&mut self, x: Float, y: Float, radius: Float) {
        self.path.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI).ok();
        self.points.push((x - radius, y - radius));
        self.points.push((x + radius, y + radius));
    }

    fn add_ellipse(&mut self, x: Float, y: Float, width: Float, height: Float) {
        let radius_x = width / 2.0;
        let radius_y = height / 2.0;
        let (x, y) = (x + radius_x, y + radius_y);
        self.path.ellipse(x, y, radius_x, radius_y, 0.0, 0.0, 2.0 * std::f64::consts::PI).ok();
        self.points.push((x - radius_x, y - radius_y));
        self.points.push((x + radius_x, y + radius_y));
    }

    fn add_rounded_rect(&mut self, x: Float, y: Float, width: Float, height: Float, radius: Float) {
        self.path.round_rect_with_f64(x, y, width, height, radius).ok();
        self.points.push((x, y));
        self.points.push((x + width, y + height));
    }

    // The calculation of bounds is inaccurate when using
    // arc_to/bezier_curve_to/quad_curve_to/add_arc
    fn bounds(&self) -> FRect {
        let mut min_x = Float::INFINITY;
        let mut min_y = Float::INFINITY;
        let mut max_x = Float::NEG_INFINITY;
        let mut max_y = Float::NEG_INFINITY;

        for (x, y) in &self.points {
            min_x = min_x.min(*x);
            min_y = min_y.min(*y);
            max_x = max_x.max(*x);
            max_y = max_y.max(*y);
        }

        FRect::new(min_x, min_y, max_x - min_x, max_y - min_y)
    }

    fn is_empty(&self) -> bool {
        false
    }
}

impl WebPath {
    pub(super) fn path(&self) -> &Path2d {
        &self.path
    }
}

impl From<FillType> for CanvasWindingRule {
    fn from(fill_type: FillType) -> Self {
        match fill_type {
            FillType::Winding => CanvasWindingRule::Nonzero,
            FillType::EvenOdd => CanvasWindingRule::Evenodd,
        }
    }
}