| 1 | // Copyright 2006 The Android Open Source Project |
| 2 | // Copyright 2020 Yevhenii Reizner |
| 3 | // |
| 4 | // Use of this source code is governed by a BSD-style license that can be |
| 5 | // found in the LICENSE file. |
| 6 | |
| 7 | // NOTE: this is not SkPathBuilder, but rather a reimplementation of SkPath. |
| 8 | |
| 9 | use alloc::vec; |
| 10 | use alloc::vec::Vec; |
| 11 | |
| 12 | use crate::{Path, Point, Rect}; |
| 13 | |
| 14 | use crate::path::PathVerb; |
| 15 | use crate::path_geometry; |
| 16 | use crate::scalar::{Scalar, SCALAR_ROOT_2_OVER_2}; |
| 17 | |
| 18 | #[derive (Copy, Clone, PartialEq, Debug)] |
| 19 | pub(crate) enum PathDirection { |
| 20 | /// Clockwise direction for adding closed contours. |
| 21 | CW, |
| 22 | /// Counter-clockwise direction for adding closed contours. |
| 23 | CCW, |
| 24 | } |
| 25 | |
| 26 | /// A path builder. |
| 27 | #[derive (Clone, Default, Debug)] |
| 28 | pub struct PathBuilder { |
| 29 | pub(crate) verbs: Vec<PathVerb>, |
| 30 | pub(crate) points: Vec<Point>, |
| 31 | pub(crate) last_move_to_index: usize, |
| 32 | pub(crate) move_to_required: bool, |
| 33 | } |
| 34 | |
| 35 | impl PathBuilder { |
| 36 | /// Creates a new builder. |
| 37 | pub fn new() -> Self { |
| 38 | PathBuilder { |
| 39 | verbs: Vec::new(), |
| 40 | points: Vec::new(), |
| 41 | last_move_to_index: 0, |
| 42 | move_to_required: true, |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | /// Creates a new builder with a specified capacity. |
| 47 | /// |
| 48 | /// Number of points depends on a verb type: |
| 49 | /// |
| 50 | /// - Move - 1 |
| 51 | /// - Line - 1 |
| 52 | /// - Quad - 2 |
| 53 | /// - Cubic - 3 |
| 54 | /// - Close - 0 |
| 55 | pub fn with_capacity(verbs_capacity: usize, points_capacity: usize) -> Self { |
| 56 | PathBuilder { |
| 57 | verbs: Vec::with_capacity(verbs_capacity), |
| 58 | points: Vec::with_capacity(points_capacity), |
| 59 | last_move_to_index: 0, |
| 60 | move_to_required: true, |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | /// Creates a new `Path` from `Rect`. |
| 65 | /// |
| 66 | /// Never fails since `Rect` is always valid. |
| 67 | /// |
| 68 | /// Segments are created clockwise: TopLeft -> TopRight -> BottomRight -> BottomLeft |
| 69 | /// |
| 70 | /// The contour is closed. |
| 71 | pub fn from_rect(rect: Rect) -> Path { |
| 72 | let verbs = vec![ |
| 73 | PathVerb::Move, |
| 74 | PathVerb::Line, |
| 75 | PathVerb::Line, |
| 76 | PathVerb::Line, |
| 77 | PathVerb::Close, |
| 78 | ]; |
| 79 | |
| 80 | let points = vec![ |
| 81 | Point::from_xy(rect.left(), rect.top()), |
| 82 | Point::from_xy(rect.right(), rect.top()), |
| 83 | Point::from_xy(rect.right(), rect.bottom()), |
| 84 | Point::from_xy(rect.left(), rect.bottom()), |
| 85 | ]; |
| 86 | |
| 87 | Path { |
| 88 | bounds: rect, |
| 89 | verbs, |
| 90 | points, |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | /// Creates a new `Path` from a circle. |
| 95 | /// |
| 96 | /// See [`PathBuilder::push_circle`] for details. |
| 97 | pub fn from_circle(cx: f32, cy: f32, radius: f32) -> Option<Path> { |
| 98 | let mut b = PathBuilder::new(); |
| 99 | b.push_circle(cx, cy, radius); |
| 100 | b.finish() |
| 101 | } |
| 102 | |
| 103 | /// Creates a new `Path` from an oval. |
| 104 | /// |
| 105 | /// See [`PathBuilder::push_oval`] for details. |
| 106 | pub fn from_oval(oval: Rect) -> Option<Path> { |
| 107 | let mut b = PathBuilder::new(); |
| 108 | b.push_oval(oval); |
| 109 | b.finish() |
| 110 | } |
| 111 | |
| 112 | pub(crate) fn reserve(&mut self, additional_verbs: usize, additional_points: usize) { |
| 113 | self.verbs.reserve(additional_verbs); |
| 114 | self.points.reserve(additional_points); |
| 115 | } |
| 116 | |
| 117 | /// Returns the current number of segments in the builder. |
| 118 | pub fn len(&self) -> usize { |
| 119 | self.verbs.len() |
| 120 | } |
| 121 | |
| 122 | /// Checks if the builder has any segments added. |
| 123 | pub fn is_empty(&self) -> bool { |
| 124 | self.verbs.is_empty() |
| 125 | } |
| 126 | |
| 127 | /// Adds beginning of a contour. |
| 128 | /// |
| 129 | /// Multiple continuous MoveTo segments are not allowed. |
| 130 | /// If the previous segment was also MoveTo, it will be overwritten with the current one. |
| 131 | pub fn move_to(&mut self, x: f32, y: f32) { |
| 132 | if let Some(PathVerb::Move) = self.verbs.last() { |
| 133 | let last_idx = self.points.len() - 1; |
| 134 | self.points[last_idx] = Point::from_xy(x, y); |
| 135 | } else { |
| 136 | self.last_move_to_index = self.points.len(); |
| 137 | self.move_to_required = false; |
| 138 | |
| 139 | self.verbs.push(PathVerb::Move); |
| 140 | self.points.push(Point::from_xy(x, y)); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | fn inject_move_to_if_needed(&mut self) { |
| 145 | if self.move_to_required { |
| 146 | match self.points.get(self.last_move_to_index).cloned() { |
| 147 | Some(p) => self.move_to(p.x, p.y), |
| 148 | None => self.move_to(0.0, 0.0), |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | /// Adds a line from the last point. |
| 154 | /// |
| 155 | /// - If `Path` is empty - adds Move(0, 0) first. |
| 156 | /// - If `Path` ends with Close - adds Move(last_x, last_y) first. |
| 157 | pub fn line_to(&mut self, x: f32, y: f32) { |
| 158 | self.inject_move_to_if_needed(); |
| 159 | |
| 160 | self.verbs.push(PathVerb::Line); |
| 161 | self.points.push(Point::from_xy(x, y)); |
| 162 | } |
| 163 | |
| 164 | /// Adds a quad curve from the last point to `x`, `y`. |
| 165 | /// |
| 166 | /// - If `Path` is empty - adds Move(0, 0) first. |
| 167 | /// - If `Path` ends with Close - adds Move(last_x, last_y) first. |
| 168 | pub fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { |
| 169 | self.inject_move_to_if_needed(); |
| 170 | |
| 171 | self.verbs.push(PathVerb::Quad); |
| 172 | self.points.push(Point::from_xy(x1, y1)); |
| 173 | self.points.push(Point::from_xy(x, y)); |
| 174 | } |
| 175 | |
| 176 | pub(crate) fn quad_to_pt(&mut self, p1: Point, p: Point) { |
| 177 | self.quad_to(p1.x, p1.y, p.x, p.y); |
| 178 | } |
| 179 | |
| 180 | // We do not support conic segments, but Skia still relies on them from time to time. |
| 181 | // This method will simply convert the input data into quad segments. |
| 182 | pub(crate) fn conic_to(&mut self, x1: f32, y1: f32, x: f32, y: f32, weight: f32) { |
| 183 | // check for <= 0 or NaN with this test |
| 184 | if !(weight > 0.0) { |
| 185 | self.line_to(x, y); |
| 186 | } else if !weight.is_finite() { |
| 187 | self.line_to(x1, y1); |
| 188 | self.line_to(x, y); |
| 189 | } else if weight == 1.0 { |
| 190 | self.quad_to(x1, y1, x, y); |
| 191 | } else { |
| 192 | self.inject_move_to_if_needed(); |
| 193 | |
| 194 | let last = self.last_point().unwrap(); |
| 195 | let quadder = path_geometry::AutoConicToQuads::compute( |
| 196 | last, |
| 197 | Point::from_xy(x1, y1), |
| 198 | Point::from_xy(x, y), |
| 199 | weight, |
| 200 | ); |
| 201 | if let Some(quadder) = quadder { |
| 202 | // Points are ordered as: 0 - 1 2 - 3 4 - 5 6 - .. |
| 203 | // `count` is a number of pairs +1 |
| 204 | let mut offset = 1; |
| 205 | for _ in 0..quadder.len { |
| 206 | let pt1 = quadder.points[offset + 0]; |
| 207 | let pt2 = quadder.points[offset + 1]; |
| 208 | self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y); |
| 209 | offset += 2; |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | pub(crate) fn conic_points_to(&mut self, pt1: Point, pt2: Point, weight: f32) { |
| 216 | self.conic_to(pt1.x, pt1.y, pt2.x, pt2.y, weight); |
| 217 | } |
| 218 | |
| 219 | /// Adds a cubic curve from the last point to `x`, `y`. |
| 220 | /// |
| 221 | /// - If `Path` is empty - adds Move(0, 0) first. |
| 222 | /// - If `Path` ends with Close - adds Move(last_x, last_y) first. |
| 223 | pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { |
| 224 | self.inject_move_to_if_needed(); |
| 225 | |
| 226 | self.verbs.push(PathVerb::Cubic); |
| 227 | self.points.push(Point::from_xy(x1, y1)); |
| 228 | self.points.push(Point::from_xy(x2, y2)); |
| 229 | self.points.push(Point::from_xy(x, y)); |
| 230 | } |
| 231 | |
| 232 | pub(crate) fn cubic_to_pt(&mut self, p1: Point, p2: Point, p: Point) { |
| 233 | self.cubic_to(p1.x, p1.y, p2.x, p2.y, p.x, p.y); |
| 234 | } |
| 235 | |
| 236 | /// Closes the current contour. |
| 237 | /// |
| 238 | /// A closed contour connects the first and the last Point |
| 239 | /// with a line, forming a continuous loop. |
| 240 | /// |
| 241 | /// Does nothing when `Path` is empty or already closed. |
| 242 | /// |
| 243 | /// Open and closed contour will be filled the same way. |
| 244 | /// Stroking an open contour will add LineCap at contour's start and end. |
| 245 | /// Stroking an closed contour will add LineJoin at contour's start and end. |
| 246 | pub fn close(&mut self) { |
| 247 | // don't add a close if it's the first verb or a repeat |
| 248 | if !self.verbs.is_empty() { |
| 249 | if self.verbs.last().cloned() != Some(PathVerb::Close) { |
| 250 | self.verbs.push(PathVerb::Close); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | self.move_to_required = true; |
| 255 | } |
| 256 | |
| 257 | /// Returns the last point if any. |
| 258 | pub fn last_point(&self) -> Option<Point> { |
| 259 | self.points.last().cloned() |
| 260 | } |
| 261 | |
| 262 | pub(crate) fn set_last_point(&mut self, pt: Point) { |
| 263 | match self.points.last_mut() { |
| 264 | Some(last) => *last = pt, |
| 265 | None => self.move_to(pt.x, pt.y), |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | pub(crate) fn is_zero_length_since_point(&self, start_pt_index: usize) -> bool { |
| 270 | let count = self.points.len() - start_pt_index; |
| 271 | if count < 2 { |
| 272 | return true; |
| 273 | } |
| 274 | |
| 275 | let first = self.points[start_pt_index]; |
| 276 | for i in 1..count { |
| 277 | if first != self.points[start_pt_index + i] { |
| 278 | return false; |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | true |
| 283 | } |
| 284 | |
| 285 | /// Adds a rectangle contour. |
| 286 | /// |
| 287 | /// The contour is closed and has a clock-wise direction. |
| 288 | pub fn push_rect(&mut self, rect: Rect) { |
| 289 | self.move_to(rect.left(), rect.top()); |
| 290 | self.line_to(rect.right(), rect.top()); |
| 291 | self.line_to(rect.right(), rect.bottom()); |
| 292 | self.line_to(rect.left(), rect.bottom()); |
| 293 | self.close(); |
| 294 | } |
| 295 | |
| 296 | /// Adds an oval contour bounded by the provided rectangle. |
| 297 | /// |
| 298 | /// The contour is closed and has a clock-wise direction. |
| 299 | pub fn push_oval(&mut self, oval: Rect) { |
| 300 | let cx = oval.left().half() + oval.right().half(); |
| 301 | let cy = oval.top().half() + oval.bottom().half(); |
| 302 | |
| 303 | let oval_points = [ |
| 304 | Point::from_xy(cx, oval.bottom()), |
| 305 | Point::from_xy(oval.left(), cy), |
| 306 | Point::from_xy(cx, oval.top()), |
| 307 | Point::from_xy(oval.right(), cy), |
| 308 | ]; |
| 309 | |
| 310 | let rect_points = [ |
| 311 | Point::from_xy(oval.right(), oval.bottom()), |
| 312 | Point::from_xy(oval.left(), oval.bottom()), |
| 313 | Point::from_xy(oval.left(), oval.top()), |
| 314 | Point::from_xy(oval.right(), oval.top()), |
| 315 | ]; |
| 316 | |
| 317 | let weight = SCALAR_ROOT_2_OVER_2; |
| 318 | self.move_to(oval_points[3].x, oval_points[3].y); |
| 319 | for (p1, p2) in rect_points.iter().zip(oval_points.iter()) { |
| 320 | self.conic_points_to(*p1, *p2, weight); |
| 321 | } |
| 322 | self.close(); |
| 323 | } |
| 324 | |
| 325 | /// Adds a circle contour. |
| 326 | /// |
| 327 | /// The contour is closed and has a clock-wise direction. |
| 328 | /// |
| 329 | /// Does nothing when: |
| 330 | /// - `radius` <= 0 |
| 331 | /// - any value is not finite or really large |
| 332 | pub fn push_circle(&mut self, x: f32, y: f32, r: f32) { |
| 333 | if let Some(r) = Rect::from_xywh(x - r, y - r, r + r, r + r) { |
| 334 | self.push_oval(r); |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | /// Adds a path. |
| 339 | pub fn push_path(&mut self, other: &Path) { |
| 340 | self.last_move_to_index = self.points.len(); |
| 341 | |
| 342 | self.verbs.extend_from_slice(&other.verbs); |
| 343 | self.points.extend_from_slice(&other.points); |
| 344 | } |
| 345 | |
| 346 | pub(crate) fn push_path_builder(&mut self, other: &PathBuilder) { |
| 347 | if other.is_empty() { |
| 348 | return; |
| 349 | } |
| 350 | |
| 351 | if self.last_move_to_index != 0 { |
| 352 | self.last_move_to_index = self.points.len() + other.last_move_to_index; |
| 353 | } |
| 354 | |
| 355 | self.verbs.extend_from_slice(&other.verbs); |
| 356 | self.points.extend_from_slice(&other.points); |
| 357 | } |
| 358 | |
| 359 | /// Appends, in a reverse order, the first contour of path ignoring path's last point. |
| 360 | pub(crate) fn reverse_path_to(&mut self, other: &PathBuilder) { |
| 361 | if other.is_empty() { |
| 362 | return; |
| 363 | } |
| 364 | |
| 365 | debug_assert_eq!(other.verbs[0], PathVerb::Move); |
| 366 | |
| 367 | let mut points_offset = other.points.len() - 1; |
| 368 | for verb in other.verbs.iter().rev() { |
| 369 | match verb { |
| 370 | PathVerb::Move => { |
| 371 | // if the path has multiple contours, stop after reversing the last |
| 372 | break; |
| 373 | } |
| 374 | PathVerb::Line => { |
| 375 | // We're moving one point back manually, to prevent points_offset overflow. |
| 376 | let pt = other.points[points_offset - 1]; |
| 377 | points_offset -= 1; |
| 378 | self.line_to(pt.x, pt.y); |
| 379 | } |
| 380 | PathVerb::Quad => { |
| 381 | let pt1 = other.points[points_offset - 1]; |
| 382 | let pt2 = other.points[points_offset - 2]; |
| 383 | points_offset -= 2; |
| 384 | self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y); |
| 385 | } |
| 386 | PathVerb::Cubic => { |
| 387 | let pt1 = other.points[points_offset - 1]; |
| 388 | let pt2 = other.points[points_offset - 2]; |
| 389 | let pt3 = other.points[points_offset - 3]; |
| 390 | points_offset -= 3; |
| 391 | self.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y); |
| 392 | } |
| 393 | PathVerb::Close => {} |
| 394 | } |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | /// Reset the builder. |
| 399 | /// |
| 400 | /// Memory is not deallocated. |
| 401 | pub fn clear(&mut self) { |
| 402 | self.verbs.clear(); |
| 403 | self.points.clear(); |
| 404 | self.last_move_to_index = 0; |
| 405 | self.move_to_required = true; |
| 406 | } |
| 407 | |
| 408 | /// Finishes the builder and returns a `Path`. |
| 409 | /// |
| 410 | /// Returns `None` when `Path` is empty or has invalid bounds. |
| 411 | pub fn finish(self) -> Option<Path> { |
| 412 | if self.is_empty() { |
| 413 | return None; |
| 414 | } |
| 415 | |
| 416 | // Just a move to? Bail. |
| 417 | if self.verbs.len() == 1 { |
| 418 | return None; |
| 419 | } |
| 420 | |
| 421 | let bounds = Rect::from_points(&self.points)?; |
| 422 | |
| 423 | Some(Path { |
| 424 | bounds, |
| 425 | verbs: self.verbs, |
| 426 | points: self.points, |
| 427 | }) |
| 428 | } |
| 429 | } |
| 430 | |