raqote 0.6.4

2D graphics library
Documentation
use lyon_geom::math::Angle;
use lyon_geom::Arc;
use lyon_geom::CubicBezierSegment;
use lyon_geom::QuadraticBezierSegment;

use crate::{Point, Vector};

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Winding {
    EvenOdd,
    NonZero,
}

#[derive(Clone, Copy, Debug)]
pub enum PathOp {
    MoveTo(Point),
    LineTo(Point),
    QuadTo(Point, Point),
    CubicTo(Point, Point, Point),
    Close,
}

/// Represents a complete path usable for filling or stroking.
#[derive(Clone, Debug)]
pub struct Path {
    pub ops: Vec<PathOp>,
    pub winding: Winding,
}

impl Path {
    /// Flattens `self` by replacing all QuadTo and CurveTo
    /// commands with an appropriate number of LineTo commands
    /// so that the error is not greater than `tolerance`.
    pub fn flatten(&self, tolerance: f32) -> Path {
        let mut cur_pt = Point::zero();
        let mut flattened = Path { ops: Vec::new(), winding: Winding::NonZero };
        for op in &self.ops {
            match *op {
                PathOp::MoveTo(pt) | PathOp::LineTo(pt) => {
                    cur_pt = pt;
                    flattened.ops.push(op.clone())
                }
                PathOp::Close => flattened.ops.push(op.clone()),
                PathOp::QuadTo(cpt, pt) => {
                    let c = QuadraticBezierSegment {
                        from: cur_pt,
                        ctrl: cpt,
                        to: pt,
                    };
                    for l in c.flattened(tolerance) {
                        flattened.ops.push(PathOp::LineTo(l));
                    }
                    cur_pt = pt;
                }
                PathOp::CubicTo(cpt1, cpt2, pt) => {
                    let c = CubicBezierSegment {
                        from: cur_pt,
                        ctrl1: cpt1,
                        ctrl2: cpt2,
                        to: pt,
                    };
                    for l in c.flattened(tolerance) {
                        flattened.ops.push(PathOp::LineTo(l));
                    }
                    cur_pt = pt;
                }
            }
        }
        flattened
    }

    /// Returns true if the point `x`, `y` is within the filled
    /// area of of `self`. The path will be flattened using `tolerance`
    // this function likely has bugs
    pub fn contains_point(&self, tolerance: f32, x: f32, y: f32) -> bool {
        //XXX Instead of making a new path we should just use flattening callbacks
        let flat_path = self.flatten(tolerance);

        struct WindState {
            count: i32,
            first_point: Option<Point>,
            current_point: Point,
            on_edge: bool,
            x: f32,
            y: f32,
        }

        impl WindState {
            fn close(&mut self) {
                if let Some(first_point) = self.first_point {
                    self.add_edge(
                        self.current_point,
                        first_point,
                    );
                }
                self.first_point = None;
            }

            // to determine containment we just need to count crossing of ray from (x, y) going to infinity
            fn add_edge(&mut self, p1: Point, p2: Point) {
                let (x1, y1) = (p1.x, p1.y);
                let (x2, y2) = (p2.x, p2.y);

                let dir = if y1 < y2 { -1 } else { 1 };

                // entirely to the right
                if x1 > self.x && x2 > self.x {
                    return
                }

                // entirely above
                if y1 > self.y && y2 > self.y {
                    return
                }

                // entirely below
                if y1 < self.y && y2 < self.x {
                    return
                }

                // entirely to the left
                if x1 < self.x && x2 < self.x {
                    if y1 > self.y && y2 < self.y {
                        self.count += 1;
                        return;
                    }
                    if y2 > self.y && y1 < self.y {
                        self.count -= 1;
                        return;
                    }
                }

                let dx = x2 - x1;
                let dy = y2 - y1;

                // cross product/perp dot product lets us know which side of the line we're on
                let cross = dx * (self.y - y1) - dy * (self.x - x1);

                if cross == 0. {
                    self.on_edge = true;
                } else if (cross > 0. && dir > 0) || (cross < 0. && dir < 0) {
                    self.count += dir;
                }
            }
        }

        let mut ws = WindState { count: 0, first_point: None, current_point: Point::new(0., 0.),x , y, on_edge: false};

        for op in &flat_path.ops {
            match *op {
                PathOp::MoveTo(pt) => {
                    ws.close();
                    ws.current_point = pt;
                    ws.first_point = Some(pt);
                },
                PathOp::LineTo(pt) => {
                    ws.add_edge(ws.current_point, pt);
                    ws.current_point = pt;
                },
                PathOp::QuadTo(..) |
                PathOp::CubicTo(..) => panic!(),
                PathOp::Close => ws.close(),
            }
        }
        // make sure the path is closed
        ws.close();

        let inside = match self.winding {
            Winding::EvenOdd => ws.count & 1 != 0,
            Winding::NonZero => ws.count != 0,
        };
        inside || ws.on_edge
    }
}

/// A helper struct used for constructing a `Path`.
pub struct PathBuilder {
    path: Path,
}

impl From<Path> for PathBuilder {
    fn from(path: Path) -> Self {
        PathBuilder {
            path
        }
    }
}

impl PathBuilder {
    pub fn new() -> PathBuilder {
        PathBuilder {
            path: Path {
                ops: Vec::new(),
                winding: Winding::NonZero,
            },
        }
    }

    /// Moves the current point to `x`, `y`
    pub fn move_to(&mut self, x: f32, y: f32) {
        self.path.ops.push(PathOp::MoveTo(Point::new(x, y)))
    }

    /// Adds a line segment from the current point to `x`, `y`
    pub fn line_to(&mut self, x: f32, y: f32) {
        self.path.ops.push(PathOp::LineTo(Point::new(x, y)))
    }

    /// Adds a quadratic bezier from the current point to `x`, `y`,
    /// using a control point of `cx`, `cy`
    pub fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
        self.path
            .ops
            .push(PathOp::QuadTo(Point::new(cx, cy), Point::new(x, y)))
    }

    /// Adds a rect to the path
    pub fn rect(&mut self, x: f32, y: f32, width: f32, height: f32) {
        self.move_to(x, y);
        self.line_to(x + width, y);
        self.line_to(x + width, y + height);
        self.line_to(x, y + height);
        self.close();
    }

    /// Adds a cubic bezier from the current point to `x`, `y`,
    /// using control points `cx1`, `cy1` and `cx2`, `cy2`
    pub fn cubic_to(&mut self, cx1: f32, cy1: f32, cx2: f32, cy2: f32, x: f32, y: f32) {
        self.path.ops.push(PathOp::CubicTo(
            Point::new(cx1, cy1),
            Point::new(cx2, cy2),
            Point::new(x, y),
        ))
    }

    /// Closes the current subpath
    pub fn close(&mut self) {
        self.path.ops.push(PathOp::Close)
    }


    /// Adds an arc approximated by quadratic beziers with center `x`, `y`
    /// and radius `r` from `angle1` and to `angle2`
    pub fn arc(&mut self, x: f32, y: f32, r: f32, angle1: f32, angle2: f32) {
        //XXX: handle the current point being the wrong spot
        let a: Arc<f32> = Arc {
            center: Point::new(x, y),
            radii: Vector::new(r, r),
            start_angle: Angle::radians(angle1),
            sweep_angle: Angle::radians(angle2),
            x_rotation: Angle::zero(),
        };
        a.for_each_quadratic_bezier(&mut |q| {
            self.quad_to(q.ctrl.x, q.ctrl.y, q.to.x, q.to.y);
        });
    }

    /// Completes the current path
    pub fn finish(self) -> Path {
        self.path
    }
}