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