1 use lyon_geom::math::Angle;
2 use lyon_geom::Arc;
3 use lyon_geom::CubicBezierSegment;
4 use lyon_geom::QuadraticBezierSegment;
5 
6 use crate::{Point, Transform, Vector};
7 
8 #[derive(Clone, Copy, PartialEq, Debug)]
9 pub enum Winding {
10     EvenOdd,
11     NonZero,
12 }
13 
14 #[derive(Clone, Copy, Debug)]
15 pub enum PathOp {
16     MoveTo(Point),
17     LineTo(Point),
18     QuadTo(Point, Point),
19     CubicTo(Point, Point, Point),
20     Close,
21 }
22 
23 impl PathOp {
transform(self, xform: &Transform) -> PathOp24     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)]
44 pub struct Path {
45     pub ops: Vec<PathOp>,
46     pub winding: Winding,
47 }
48 
49 impl 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`.
flatten(&self, tolerance: f32) -> Path53     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
contains_point(&self, tolerance: f32, x: f32, y: f32) -> bool100     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 
transform(self, transform: &Transform) -> Path204     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`.
212 pub struct PathBuilder {
213     path: Path,
214 }
215 
216 impl From<Path> for PathBuilder {
from(path: Path) -> Self217     fn from(path: Path) -> Self {
218         PathBuilder {
219             path
220         }
221     }
222 }
223 
224 impl PathBuilder {
new() -> PathBuilder225     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`
move_to(&mut self, x: f32, y: f32)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`
line_to(&mut self, x: f32, y: f32)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`
quad_to(&mut self, cx: f32, cy: f32, x: f32, y: f32)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
rect(&mut self, x: f32, y: f32, width: f32, height: f32)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`
cubic_to(&mut self, cx1: f32, cy1: f32, cx2: f32, cy2: f32, x: f32, y: f32)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
close(&mut self)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.
arc(&mut self, x: f32, y: f32, r: f32, start_angle: f32, sweep_angle: f32)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
finish(self) -> Path298     pub fn finish(self) -> Path {
299         self.path
300     }
301 }
302