raqote/
path_builder.rs

1use lyon_geom::Angle;
2use lyon_geom::Arc;
3use lyon_geom::CubicBezierSegment;
4use lyon_geom::QuadraticBezierSegment;
5
6use crate::{Point, Transform, Vector};
7
8#[derive(Clone, Copy, PartialEq, Debug)]
9pub enum Winding {
10    EvenOdd,
11    NonZero,
12}
13
14#[derive(Clone, Copy, Debug)]
15pub enum PathOp {
16    MoveTo(Point),
17    LineTo(Point),
18    QuadTo(Point, Point),
19    CubicTo(Point, Point, Point),
20    Close,
21}
22
23impl PathOp {
24    fn transform(self, xform: &Transform) -> PathOp {
25        match self {
26            PathOp::MoveTo(p) => PathOp::MoveTo(xform.transform_point(p)),
27            PathOp::LineTo(p) => PathOp::LineTo(xform.transform_point(p)),
28            PathOp::QuadTo(p1, p2) => PathOp::QuadTo(
29                xform.transform_point(p1),
30                xform.transform_point(p2)
31            ),
32            PathOp::CubicTo(p1, p2, p3) => PathOp::CubicTo(
33                xform.transform_point(p1),
34                xform.transform_point(p2),
35                xform.transform_point(p3),
36            ),
37            PathOp::Close => PathOp::Close,
38        }
39    }
40}
41
42/// Represents a complete path usable for filling or stroking.
43#[derive(Clone, Debug)]
44pub struct Path {
45    pub ops: Vec<PathOp>,
46    pub winding: Winding,
47}
48
49impl Path {
50    /// Flattens `self` by replacing all QuadTo and CurveTo
51    /// commands with an appropriate number of LineTo commands
52    /// so that the error is not greater than `tolerance`.
53    pub fn flatten(&self, tolerance: f32) -> Path {
54        let mut cur_pt = None;
55        let mut flattened = Path { ops: Vec::new(), winding: Winding::NonZero };
56        for op in &self.ops {
57            match *op {
58                PathOp::MoveTo(pt) | PathOp::LineTo(pt) => {
59                    cur_pt = Some(pt);
60                    flattened.ops.push(op.clone())
61                }
62                PathOp::Close => {
63                    cur_pt = None;
64                    flattened.ops.push(op.clone())
65                }
66                PathOp::QuadTo(cpt, pt) => {
67                    let start = cur_pt.unwrap_or(cpt);
68                    let c = QuadraticBezierSegment {
69                        from: start,
70                        ctrl: cpt,
71                        to: pt,
72                    };
73                    for l in c.flattened(tolerance) {
74                        flattened.ops.push(PathOp::LineTo(l));
75                    }
76                    cur_pt = Some(pt);
77                }
78                PathOp::CubicTo(cpt1, cpt2, pt) => {
79                    let start = cur_pt.unwrap_or(cpt1);
80                    let c = CubicBezierSegment {
81                        from: start,
82                        ctrl1: cpt1,
83                        ctrl2: cpt2,
84                        to: pt,
85                    };
86                    for l in c.flattened(tolerance) {
87                        flattened.ops.push(PathOp::LineTo(l));
88                    }
89                    cur_pt = Some(pt);
90                }
91            }
92        }
93        flattened
94    }
95
96    /// Returns true if the point `x`, `y` is within the filled
97    /// area of of `self`. The path will be flattened using `tolerance`.
98    /// The point is considered contained if it's on the path.
99    // this function likely has bugs
100    pub fn contains_point(&self, tolerance: f32, x: f32, y: f32) -> bool {
101        //XXX Instead of making a new path we should just use flattening callbacks
102        let flat_path = self.flatten(tolerance);
103        struct WindState {
104            first_point: Option<Point>,
105            current_point: Option<Point>,
106            count: i32,
107            on_edge: bool,
108
109            x: f32,
110            y: f32,
111        }
112
113        impl WindState {
114            fn close(&mut self) {
115                if let (Some(first_point), Some(current_point)) = (self.first_point, self.current_point) {
116                    self.add_edge(
117                        current_point,
118                        first_point,
119                    );
120                }
121                self.first_point = None;
122            }
123
124            // to determine containment we just need to count crossing of ray from (x, y) going to infinity
125            fn add_edge(&mut self, p1: Point, p2: Point) {
126                let (x1, y1) = (p1.x, p1.y);
127                let (x2, y2) = (p2.x, p2.y);
128
129                let dir = if y1 < y2 { -1 } else { 1 };
130
131                // entirely to the right
132                if x1 > self.x && x2 > self.x {
133                    return
134                }
135
136                // entirely above
137                if y1 > self.y && y2 > self.y {
138                    return
139                }
140
141                // entirely below
142                if y1 < self.y && y2 < self.y {
143                    return
144                }
145
146                // entirely to the left
147                if x1 < self.x && x2 < self.x {
148                    if y1 > self.y && y2 < self.y {
149                        self.count += 1;
150                        return;
151                    }
152                    if y2 > self.y && y1 < self.y {
153                        self.count -= 1;
154                        return;
155                    }
156                }
157
158                let dx = x2 - x1;
159                let dy = y2 - y1;
160
161                // cross product/perp dot product lets us know which side of the line we're on
162                let cross = dx * (self.y - y1) - dy * (self.x - x1);
163
164                if cross == 0. {
165                    self.on_edge = true;
166                } else if (cross > 0. && dir > 0) || (cross < 0. && dir < 0) {
167                    self.count += dir;
168                }
169            }
170        }
171
172        let mut ws = WindState { count: 0, first_point: None, current_point: None, x, y, on_edge: false};
173
174        for op in &flat_path.ops {
175            match *op {
176                PathOp::MoveTo(pt) => {
177                    ws.close();
178                    ws.current_point = Some(pt);
179                    ws.first_point = Some(pt);
180                },
181                PathOp::LineTo(pt) => {
182                    if let Some(current_point) = ws.current_point {
183                        ws.add_edge(current_point, pt);
184                    } else {
185                        ws.first_point = Some(pt);
186                    }
187                    ws.current_point = Some(pt);
188                },
189                PathOp::QuadTo(..) |
190                PathOp::CubicTo(..) => panic!(),
191                PathOp::Close => ws.close(),
192            }
193        }
194        // make sure the path is closed
195        ws.close();
196
197        let inside = match self.winding {
198            Winding::EvenOdd => ws.count & 1 != 0,
199            Winding::NonZero => ws.count != 0,
200        };
201        inside || ws.on_edge
202    }
203
204    pub fn transform(self, transform: &Transform) -> Path {
205        let Path { ops, winding } = self;
206        let ops = ops.into_iter().map(|op| op.transform(transform)).collect();
207        Path { ops, winding }
208    }
209}
210
211/// A helper struct used for constructing a `Path`.
212pub struct PathBuilder {
213    path: Path,
214}
215
216impl From<Path> for PathBuilder {
217    fn from(path: Path) -> Self {
218        PathBuilder {
219            path
220        }
221    }
222}
223
224impl PathBuilder {
225    pub fn new() -> PathBuilder {
226        PathBuilder {
227            path: Path {
228                ops: Vec::new(),
229                winding: Winding::NonZero,
230            },
231        }
232    }
233
234    /// Moves the current point to `x`, `y`
235    pub fn move_to(&mut self, x: f32, y: f32) {
236        self.path.ops.push(PathOp::MoveTo(Point::new(x, y)))
237    }
238
239    /// Adds a line segment from the current point to `x`, `y`
240    pub fn line_to(&mut self, x: f32, y: f32) {
241        self.path.ops.push(PathOp::LineTo(Point::new(x, y)))
242    }
243
244    /// Adds a quadratic bezier from the current point to `x`, `y`,
245    /// using a control point of `cx`, `cy`
246    pub fn quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32) {
247        self.path
248            .ops
249            .push(PathOp::QuadTo(Point::new(cx, cy), Point::new(x, y)))
250    }
251
252    /// Adds a rect to the path
253    pub fn rect(&mut self, x: f32, y: f32, width: f32, height: f32) {
254        self.move_to(x, y);
255        self.line_to(x + width, y);
256        self.line_to(x + width, y + height);
257        self.line_to(x, y + height);
258        self.close();
259    }
260
261    /// Adds a cubic bezier from the current point to `x`, `y`,
262    /// using control points `cx1`, `cy1` and `cx2`, `cy2`
263    pub fn cubic_to(&mut self, cx1: f32, cy1: f32, cx2: f32, cy2: f32, x: f32, y: f32) {
264        self.path.ops.push(PathOp::CubicTo(
265            Point::new(cx1, cy1),
266            Point::new(cx2, cy2),
267            Point::new(x, y),
268        ))
269    }
270
271    /// Closes the current subpath
272    pub fn close(&mut self) {
273        self.path.ops.push(PathOp::Close)
274    }
275
276
277    /// Adds an arc approximated by quadratic beziers with center `x`, `y`
278    /// and radius `r` starting at `start_angle` and sweeping by `sweep_angle`.
279    /// For a positive `sweep_angle` the sweep is done clockwise, for a negative
280    /// `sweep_angle` the sweep is done counterclockwise.
281    pub fn arc(&mut self, x: f32, y: f32, r: f32, start_angle: f32, sweep_angle: f32) {
282        //XXX: handle the current point being the wrong spot
283        let a: Arc<f32> = Arc {
284            center: Point::new(x, y),
285            radii: Vector::new(r, r),
286            start_angle: Angle::radians(start_angle),
287            sweep_angle: Angle::radians(sweep_angle),
288            x_rotation: Angle::zero(),
289        };
290        let start = a.from();
291        self.line_to(start.x, start.y);
292        a.for_each_quadratic_bezier(&mut |q| {
293            self.quad_to(q.ctrl.x, q.ctrl.y, q.to.x, q.to.y);
294        });
295    }
296
297    /// Completes the current path
298    pub fn finish(self) -> Path {
299        self.path
300    }
301}