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 | |