nvgx 0.3.3

Pure-rust implementation of NanoVG
Documentation
use clamped::Clamp;

use super::ImageId;
use crate::{Color, Extent, Point, Rect, Transform};

#[derive(Debug, Copy, Clone)]
pub struct PaintPattern {
    pub xform: Transform,
    pub extent: Extent,
    pub radius: f32,
    pub feather: f32,
    pub inner_color: Color,
    pub outer_color: Color,
    pub image: Option<ImageId>,
}

#[derive(Debug, Copy, Clone)]
pub enum Gradient {
    Linear {
        start: Point,
        end: Point,
        start_color: Color,
        end_color: Color,
    },
    Radial {
        center: Point,
        in_radius: f32,
        out_radius: f32,
        inner_color: Color,
        outer_color: Color,
    },
    Box {
        rect: Rect,
        radius: f32,
        feather: f32,
        inner_color: Color,
        outer_color: Color,
    },
}

#[derive(Debug, Copy, Clone)]
pub struct ImagePattern {
    pub center: Point,
    pub size: Extent,
    pub angle: f32,
    pub img: ImageId,
    pub alpha: f32,
}

impl From<Gradient> for PaintPattern {
    fn from(grad: Gradient) -> Self {
        match grad {
            Gradient::Linear {
                start,
                end,
                start_color: inner_color,
                end_color: outer_color,
            } => {
                const LARGE: f32 = 1e5;

                let mut dx = end.x - start.x;
                let mut dy = end.y - start.y;
                let d = (dx * dx + dy * dy).sqrt();

                if d > 0.0001 {
                    dx /= d;
                    dy /= d;
                } else {
                    dx = 0.0;
                    dy = 1.0;
                }

                PaintPattern {
                    xform: Transform([dy, -dx, dx, dy, start.x - dx * LARGE, start.y - dy * LARGE]),
                    extent: Extent {
                        width: LARGE,
                        height: LARGE + d * 0.5,
                    },
                    radius: 0.0,
                    feather: d.max(1.0),
                    inner_color,
                    outer_color,
                    image: None,
                }
            }
            Gradient::Radial {
                center,
                in_radius,
                out_radius,
                inner_color,
                outer_color,
            } => {
                let r = (in_radius + out_radius) * 0.5;
                let f = out_radius - in_radius;
                PaintPattern {
                    xform: Transform([1.0, 0.0, 0.0, 1.0, center.x, center.y]),
                    extent: Extent {
                        width: r,
                        height: r,
                    },
                    radius: r,
                    feather: f.max(1.0),
                    inner_color,
                    outer_color,
                    image: None,
                }
            }
            Gradient::Box {
                rect,
                radius,
                feather,
                inner_color,
                outer_color,
            } => {
                let Rect { xy, size } = rect;
                PaintPattern {
                    xform: Transform([
                        1.0,
                        0.0,
                        0.0,
                        1.0,
                        xy.x + size.width * 0.5,
                        xy.y + size.height * 0.5,
                    ]),
                    extent: Extent::new(size.width * 0.5, size.height * 0.5),
                    radius,
                    feather: feather.max(1.0),
                    inner_color,
                    outer_color,
                    image: None,
                }
            }
        }
    }
}

impl From<ImagePattern> for PaintPattern {
    fn from(pat: ImagePattern) -> Self {
        let mut xform = Transform::rotate(pat.angle);
        xform.0[4] = pat.center.x;
        xform.0[5] = pat.center.y;
        PaintPattern {
            xform,
            extent: pat.size,
            radius: 0.0,
            feather: 0.0,
            inner_color: Color::rgba(1.0, 1.0, 1.0, pat.alpha),
            outer_color: Color::rgba(1.0, 1.0, 1.0, pat.alpha),
            image: Some(pat.img),
        }
    }
}

impl<T: Into<Color> + Clone> From<T> for PaintPattern {
    fn from(color: T) -> Self {
        PaintPattern {
            xform: Transform::identity(),
            extent: Default::default(),
            radius: 0.0,
            feather: 1.0,
            inner_color: color.clone().into(),
            outer_color: color.into(),
            image: None,
        }
    }
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum LineJoin {
    Miter,
    Round,
    Bevel,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum LineCap {
    Butt,
    Round,
    Square,
}

#[derive(Debug, Clone)]
pub struct Paint {
    pub antialias: bool,
    pub alpha: f32,
    pub stroke: PaintPattern,
    pub fill: PaintPattern,
    pub stroke_width: f32,
    pub line_join: LineJoin,
    pub line_cap: LineCap,
    pub miter_limit: f32,
}

impl Paint {
    pub fn new() -> Self {
        return Self::default();
    }

    #[inline]
    pub fn get_fill(&self) -> PaintPattern {
        let mut fill_paint = self.fill.clone();
        fill_paint.inner_color.a *= self.alpha;
        fill_paint.outer_color.a *= self.alpha;
        return fill_paint;
    }

    #[inline]
    pub fn get_stroke(
        &self,
        antialias: bool,
        fringe_width: f32,
        average_scale: f32,
        device_pixel_ratio: f32,
    ) -> (PaintPattern, f32) {
        let mut stroke_width =
            (self.stroke_width * device_pixel_ratio * average_scale).clamped(0.0, 200.0);
        let mut stroke_paint = self.stroke.clone();
        if antialias {
            if stroke_width < fringe_width {
                let alpha = (stroke_width / fringe_width).clamped(0.0, 1.0);
                stroke_paint.inner_color.a *= alpha * alpha;
                stroke_paint.outer_color.a *= alpha * alpha;
                stroke_width = fringe_width;
            }
        }

        stroke_paint.inner_color.a *= self.alpha;
        stroke_paint.outer_color.a *= self.alpha;

        return (stroke_paint, stroke_width);
    }
}

impl Default for Paint {
    fn default() -> Self {
        return Self {
            antialias: true,
            alpha: 1.0,
            stroke: Color::rgb(0.0, 0.0, 0.0).into(),
            stroke_width: 1.0,
            line_join: LineJoin::Miter,
            line_cap: LineCap::Butt,
            miter_limit: 10.0,
            fill: Color::rgb(1.0, 1.0, 1.0).into(),
        };
    }
}