skia_safe/core/path.rs
1use std::{fmt, marker::PhantomData, mem::forget, ptr};
2
3use skia_bindings::{self as sb, SkPath, SkPath_Iter, SkPath_RawIter};
4
5use crate::PathIter;
6use crate::{
7 interop::DynamicMemoryWStream, path_types, prelude::*, scalar, Data, Matrix, PathDirection,
8 PathFillType, PathVerb, Point, RRect, Rect, Vector,
9};
10
11/// [`Path`] contain geometry. [`Path`] may be empty, or contain one or more verbs that
12/// outline a figure. [`Path`] always starts with a move verb to a Cartesian coordinate,
13/// and may be followed by additional verbs that add lines or curves.
14/// Adding a close verb makes the geometry into a continuous loop, a closed contour.
15/// [`Path`] may contain any number of contours, each beginning with a move verb.
16///
17/// [`Path`] contours may contain only a move verb, or may also contain lines,
18/// quadratic beziers, conics, and cubic beziers. [`Path`] contours may be open or
19/// closed.
20///
21/// When used to draw a filled area, [`Path`] describes whether the fill is inside or
22/// outside the geometry. [`Path`] also describes the winding rule used to fill
23/// overlapping contours.
24///
25/// Internally, [`Path`] lazily computes metrics likes bounds and convexity. Call
26/// [`Path::update_bounds_cache`] to make [`Path`] thread safe.
27pub type Path = Handle<SkPath>;
28unsafe impl Send for Path {}
29
30impl NativeDrop for SkPath {
31 /// Releases ownership of any shared data and deletes data if [`Path`] is sole owner.
32 ///
33 /// example: <https://fiddle.skia.org/c/@Path_destructor>
34 fn drop(&mut self) {
35 unsafe { sb::C_SkPath_destruct(self) }
36 }
37}
38
39impl NativeClone for SkPath {
40 /// Constructs a copy of an existing path.
41 /// Copy constructor makes two paths identical by value. Internally, path and
42 /// the returned result share pointer values. The underlying verb array, [`Point`] array
43 /// and weights are copied when modified.
44 ///
45 /// Creating a [`Path`] copy is very efficient and never allocates memory.
46 /// [`Path`] are always copied by value from the interface; the underlying shared
47 /// pointers are not exposed.
48 ///
49 /// * `path` - [`Path`] to copy by value
50 ///
51 /// Returns: copy of [`Path`]
52 ///
53 /// example: <https://fiddle.skia.org/c/@Path_copy_const_SkPath>
54 fn clone(&self) -> Self {
55 unsafe { SkPath::new1(self) }
56 }
57}
58
59impl NativePartialEq for SkPath {
60 /// Compares a and b; returns `true` if [`path::FillType`], verb array, [`Point`] array, and weights
61 /// are equivalent.
62 ///
63 /// * `a` - [`Path`] to compare
64 /// * `b` - [`Path`] to compare
65 ///
66 /// Returns: `true` if [`Path`] pair are equivalent
67 fn eq(&self, rhs: &Self) -> bool {
68 unsafe { sb::C_SkPath_Equals(self, rhs) }
69 }
70}
71
72impl Default for Handle<SkPath> {
73 /// See [`Self::new()`]
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl fmt::Debug for Path {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.debug_struct("Path")
82 .field("fill_type", &self.fill_type())
83 .field("is_convex", &self.is_convex())
84 .field("is_oval", &self.is_oval())
85 .field("is_rrect", &self.is_rrect())
86 .field("is_empty", &self.is_empty())
87 .field("is_last_contour_closed", &self.is_last_contour_closed())
88 .field("is_finite", &self.is_finite())
89 .field("is_volatile", &self.is_volatile())
90 .field("is_line", &self.is_line())
91 .field("count_points", &self.count_points())
92 .field("count_verbs", &self.count_verbs())
93 .field("approximate_bytes_used", &self.approximate_bytes_used())
94 .field("bounds", &self.bounds())
95 .field("is_rect", &self.is_rect())
96 .field("segment_masks", &self.segment_masks())
97 .field("generation_id", &self.generation_id())
98 .field("is_valid", &self.is_valid())
99 .finish()
100 }
101}
102
103/// [`Path`] contain geometry. [`Path`] may be empty, or contain one or more verbs that
104/// outline a figure. [`Path`] always starts with a move verb to a Cartesian coordinate,
105/// and may be followed by additional verbs that add lines or curves.
106/// Adding a close verb makes the geometry into a continuous loop, a closed contour.
107/// [`Path`] may contain any number of contours, each beginning with a move verb.
108///
109/// [`Path`] contours may contain only a move verb, or may also contain lines,
110/// quadratic beziers, conics, and cubic beziers. [`Path`] contours may be open or
111/// closed.
112///
113/// When used to draw a filled area, [`Path`] describes whether the fill is inside or
114/// outside the geometry. [`Path`] also describes the winding rule used to fill
115/// overlapping contours.
116///
117/// Internally, [`Path`] lazily computes metrics likes bounds and convexity. Call
118/// [`Path::update_bounds_cache`] to make [`Path`] thread safe.
119impl Path {
120 /// Create a new path with the specified spans.
121 ///
122 /// The points and weights arrays are read in order, based on the sequence of verbs.
123 ///
124 /// Move 1 point
125 /// Line 1 point
126 /// Quad 2 points
127 /// Conic 2 points and 1 weight
128 /// Cubic 3 points
129 /// Close 0 points
130 ///
131 /// If an illegal sequence of verbs is encountered, or the specified number of points
132 /// or weights is not sufficient given the verbs, an empty Path is returned.
133 ///
134 /// A legal sequence of verbs consists of any number of Contours. A contour always begins
135 /// with a Move verb, followed by 0 or more segments: Line, Quad, Conic, Cubic, followed
136 /// by an optional Close.
137 pub fn raw(
138 points: &[Point],
139 verbs: &[PathVerb],
140 conic_weights: &[scalar],
141 fill_type: PathFillType,
142 is_volatile: impl Into<Option<bool>>,
143 ) -> Self {
144 Self::construct(|path| unsafe {
145 sb::C_SkPath_Raw(
146 path,
147 points.native().as_ptr(),
148 points.len(),
149 verbs.as_ptr(),
150 verbs.len(),
151 conic_weights.as_ptr(),
152 conic_weights.len(),
153 fill_type,
154 is_volatile.into().unwrap_or(false),
155 )
156 })
157 }
158
159 /// Create a new path with the specified spans.
160 ///
161 /// The points and weights arrays are read in order, based on the sequence of verbs.
162 ///
163 /// Move 1 point
164 /// Line 1 point
165 /// Quad 2 points
166 /// Conic 2 points and 1 weight
167 /// Cubic 3 points
168 /// Close 0 points
169 ///
170 /// If an illegal sequence of verbs is encountered, or the specified number of points
171 /// or weights is not sufficient given the verbs, an empty Path is returned.
172 ///
173 /// A legal sequence of verbs consists of any number of Contours. A contour always begins
174 /// with a Move verb, followed by 0 or more segments: Line, Quad, Conic, Cubic, followed
175 /// by an optional Close.
176 #[deprecated(since = "0.88.0", note = "use raw()")]
177 pub fn new_from(
178 points: &[Point],
179 verbs: &[u8],
180 conic_weights: &[scalar],
181 fill_type: PathFillType,
182 is_volatile: impl Into<Option<bool>>,
183 ) -> Self {
184 Self::construct(|path| unsafe {
185 sb::C_SkPath_Make(
186 path,
187 points.native().as_ptr(),
188 points.len(),
189 verbs.as_ptr(),
190 verbs.len(),
191 conic_weights.as_ptr(),
192 conic_weights.len(),
193 fill_type,
194 is_volatile.into().unwrap_or(false),
195 )
196 })
197 }
198
199 pub fn rect_with_fill_type(
200 rect: impl AsRef<Rect>,
201 fill_type: PathFillType,
202 dir: impl Into<Option<PathDirection>>,
203 ) -> Self {
204 Self::construct(|path| unsafe {
205 sb::C_SkPath_Rect(
206 path,
207 rect.as_ref().native(),
208 fill_type,
209 dir.into().unwrap_or_default(),
210 )
211 })
212 }
213
214 pub fn rect(rect: impl AsRef<Rect>, dir: impl Into<Option<PathDirection>>) -> Self {
215 Self::rect_with_fill_type(rect, PathFillType::default(), dir)
216 }
217
218 pub fn oval(oval: impl AsRef<Rect>, dir: impl Into<Option<PathDirection>>) -> Self {
219 Self::construct(|path| unsafe {
220 sb::C_SkPath_Oval(path, oval.as_ref().native(), dir.into().unwrap_or_default())
221 })
222 }
223
224 pub fn oval_with_start_index(
225 oval: impl AsRef<Rect>,
226 dir: PathDirection,
227 start_index: usize,
228 ) -> Self {
229 Self::construct(|path| unsafe {
230 sb::C_SkPath_OvalWithStartIndex(
231 path,
232 oval.as_ref().native(),
233 dir,
234 start_index.try_into().unwrap(),
235 )
236 })
237 }
238
239 pub fn circle(
240 center: impl Into<Point>,
241 radius: scalar,
242 dir: impl Into<Option<PathDirection>>,
243 ) -> Self {
244 let center = center.into();
245 Self::construct(|path| unsafe {
246 sb::C_SkPath_Circle(
247 path,
248 center.x,
249 center.y,
250 radius,
251 dir.into().unwrap_or(PathDirection::CW),
252 )
253 })
254 }
255
256 pub fn rrect(rect: impl AsRef<RRect>, dir: impl Into<Option<PathDirection>>) -> Self {
257 Self::construct(|path| unsafe {
258 sb::C_SkPath_RRect(path, rect.as_ref().native(), dir.into().unwrap_or_default())
259 })
260 }
261
262 pub fn rrect_with_start_index(
263 rect: impl AsRef<RRect>,
264 dir: PathDirection,
265 start_index: usize,
266 ) -> Self {
267 Self::construct(|path| unsafe {
268 sb::C_SkPath_RRectWithStartIndex(
269 path,
270 rect.as_ref().native(),
271 dir,
272 start_index.try_into().unwrap(),
273 )
274 })
275 }
276
277 pub fn polygon(
278 pts: &[Point],
279 is_closed: bool,
280 fill_type: impl Into<Option<PathFillType>>,
281 is_volatile: impl Into<Option<bool>>,
282 ) -> Self {
283 Self::construct(|path| unsafe {
284 sb::C_SkPath_Polygon(
285 path,
286 pts.native().as_ptr(),
287 pts.len(),
288 is_closed,
289 fill_type.into().unwrap_or_default(),
290 is_volatile.into().unwrap_or(false),
291 )
292 })
293 }
294
295 pub fn line(a: impl Into<Point>, b: impl Into<Point>) -> Self {
296 Self::polygon(&[a.into(), b.into()], false, None, None)
297 }
298
299 /// Constructs an empty [`Path`]. By default, [`Path`] has no verbs, no [`Point`], and no weights.
300 ///
301 /// Returns: empty [`Path`]
302 ///
303 /// example: <https://fiddle.skia.org/c/@Path_empty_constructor>
304 pub fn new_with_fill_type(fill_type: PathFillType) -> Self {
305 Self::construct(|path| unsafe { sb::C_SkPath_Construct(path, fill_type) })
306 }
307
308 pub fn new() -> Self {
309 Self::new_with_fill_type(PathFillType::default())
310 }
311
312 /// Returns a copy of this path in the current state.
313 pub fn snapshot(&self) -> Self {
314 self.clone()
315 }
316
317 /// Returns `true` if [`Path`] contain equal verbs and equal weights.
318 /// If [`Path`] contain one or more conics, the weights must match.
319 ///
320 /// `conic_to()` may add different verbs depending on conic weight, so it is not
321 /// trivial to interpolate a pair of [`Path`] containing conics with different
322 /// conic weight values.
323 ///
324 /// * `compare` - [`Path`] to compare
325 ///
326 /// Returns: `true` if [`Path`] verb array and weights are equivalent
327 ///
328 /// example: <https://fiddle.skia.org/c/@Path_isInterpolatable>
329 pub fn is_interpolatable(&self, compare: &Path) -> bool {
330 unsafe { self.native().isInterpolatable(compare.native()) }
331 }
332
333 /// Interpolates between [`Path`] with [`Point`] array of equal size.
334 /// Copy verb array and weights to out, and set out [`Point`] array to a weighted
335 /// average of this [`Point`] array and ending [`Point`] array, using the formula:
336 /// (Path Point * weight) + ending Point * (1 - weight).
337 ///
338 /// weight is most useful when between zero (ending [`Point`] array) and
339 /// one (this Point_Array); will work with values outside of this
340 /// range.
341 ///
342 /// `interpolate()` returns an empty [`Path`] if [`Point`] array is not the same size
343 /// as ending [`Point`] array. Call `is_interpolatable()` to check [`Path`] compatibility
344 /// prior to calling `make_interpolate`().
345 ///
346 /// * `ending` - [`Point`] array averaged with this [`Point`] array
347 /// * `weight` - contribution of this [`Point`] array, and
348 /// one minus contribution of ending [`Point`] array
349 ///
350 /// Returns: [`Path`] replaced by interpolated averages
351 ///
352 /// example: <https://fiddle.skia.org/c/@Path_interpolate>
353 pub fn interpolate(&self, ending: &Path, weight: scalar) -> Option<Self> {
354 let mut out = Path::default();
355 self.interpolate_inplace(ending, weight, &mut out)
356 .then_some(out)
357 }
358
359 /// Interpolates between [`Path`] with [`Point`] array of equal size.
360 /// Copy verb array and weights to out, and set out [`Point`] array to a weighted
361 /// average of this [`Point`] array and ending [`Point`] array, using the formula:
362 /// `(Path Point * weight) + ending Point * (1 - weight)`.
363 ///
364 /// `weight` is most useful when between zero (ending [`Point`] array) and
365 /// one (this Point_Array); will work with values outside of this
366 /// range.
367 ///
368 /// `interpolate_inplace()` returns `false` and leaves out unchanged if [`Point`] array is not
369 /// the same size as ending [`Point`] array. Call `is_interpolatable()` to check [`Path`]
370 /// compatibility prior to calling `interpolate_inplace()`.
371 ///
372 /// * `ending` - [`Point`] array averaged with this [`Point`] array
373 /// * `weight` - contribution of this [`Point`] array, and
374 /// one minus contribution of ending [`Point`] array
375 /// * `out` - [`Path`] replaced by interpolated averages
376 ///
377 /// Returns: `true` if [`Path`] contain same number of [`Point`]
378 ///
379 /// example: <https://fiddle.skia.org/c/@Path_interpolate>
380 pub fn interpolate_inplace(&self, ending: &Path, weight: scalar, out: &mut Path) -> bool {
381 unsafe {
382 self.native()
383 .interpolate(ending.native(), weight, out.native_mut())
384 }
385 }
386
387 /// Returns [`PathFillType`], the rule used to fill [`Path`].
388 ///
389 /// Returns: current [`PathFillType`] setting
390 pub fn fill_type(&self) -> PathFillType {
391 unsafe { sb::C_SkPath_getFillType(self.native()) }
392 }
393
394 pub fn with_fill_type(&self, new_fill_type: PathFillType) -> Path {
395 Self::construct(|p| unsafe { sb::C_SkPath_makeFillType(self.native(), new_fill_type, p) })
396 }
397
398 /// Returns if FillType describes area outside [`Path`] geometry. The inverse fill area
399 /// extends indefinitely.
400 ///
401 /// Returns: `true` if FillType is `InverseWinding` or `InverseEvenOdd`
402 pub fn is_inverse_fill_type(&self) -> bool {
403 self.fill_type().is_inverse()
404 }
405
406 /// Creates an [`Path`] with the same properties and data, and with [`PathFillType`] replaced
407 /// with its inverse. The inverse of [`PathFillType`] describes the area unmodified by the
408 /// original FillType.
409 pub fn with_toggle_inverse_fill_type(&self) -> Self {
410 Self::construct(|p| unsafe {
411 sb::C_SkPath_makeToggleInverseFillType(self.native(), p);
412 })
413 }
414
415 /// Returns `true` if the path is convex. If necessary, it will first compute the convexity.
416 pub fn is_convex(&self) -> bool {
417 unsafe { self.native().isConvex() }
418 }
419
420 /// Returns `true` if this path is recognized as an oval or circle.
421 ///
422 /// bounds receives bounds of oval.
423 ///
424 /// bounds is unmodified if oval is not found.
425 ///
426 /// * `bounds` - storage for bounding [`Rect`] of oval; may be `None`
427 ///
428 /// Returns: `true` if [`Path`] is recognized as an oval or circle
429 ///
430 /// example: <https://fiddle.skia.org/c/@Path_isOval>
431 pub fn is_oval(&self) -> Option<Rect> {
432 let mut bounds = Rect::default();
433 unsafe { self.native().isOval(bounds.native_mut()) }.then_some(bounds)
434 }
435
436 /// Returns [`RRect`] if path is representable as [`RRect`].
437 /// Returns `None` if path is representable as oval, circle, or [`Rect`].
438 ///
439 /// Returns: [`RRect`] if [`Path`] contains only [`RRect`]
440 ///
441 /// example: <https://fiddle.skia.org/c/@Path_isRRect>
442 pub fn is_rrect(&self) -> Option<RRect> {
443 let mut rrect = RRect::default();
444 unsafe { self.native().isRRect(rrect.native_mut()) }.then_some(rrect)
445 }
446
447 /// Returns if [`Path`] is empty.
448 /// Empty [`Path`] may have FillType but has no [`Point`], [`Verb`], or conic weight.
449 /// [`Path::default()`] constructs empty [`Path`]; `reset()` and `rewind()` make [`Path`] empty.
450 ///
451 /// Returns: `true` if the path contains no [`Verb`] array
452 pub fn is_empty(&self) -> bool {
453 unsafe { self.native().isEmpty() }
454 }
455
456 /// Returns if contour is closed.
457 /// Contour is closed if [`Path`] [`Verb`] array was last modified by `close()`. When stroked,
458 /// closed contour draws [`crate::paint::Join`] instead of [`crate::paint::Cap`] at first and last [`Point`].
459 ///
460 /// Returns: `true` if the last contour ends with a [`Verb::Close`]
461 ///
462 /// example: <https://fiddle.skia.org/c/@Path_isLastContourClosed>
463 pub fn is_last_contour_closed(&self) -> bool {
464 unsafe { self.native().isLastContourClosed() }
465 }
466
467 /// Returns `true` for finite [`Point`] array values between negative SK_ScalarMax and
468 /// positive SK_ScalarMax. Returns `false` for any [`Point`] array value of
469 /// SK_ScalarInfinity, SK_ScalarNegativeInfinity, or SK_ScalarNaN.
470 ///
471 /// Returns: `true` if all [`Point`] values are finite
472 pub fn is_finite(&self) -> bool {
473 unsafe { self.native().isFinite() }
474 }
475
476 /// Returns `true` if the path is volatile; it will not be altered or discarded
477 /// by the caller after it is drawn. [`Path`] by default have volatile set `false`, allowing
478 /// [`crate::Surface`] to attach a cache of data which speeds repeated drawing. If `true`, [`crate::Surface`]
479 /// may not speed repeated drawing.
480 ///
481 /// Returns: `true` if caller will alter [`Path`] after drawing
482 pub fn is_volatile(&self) -> bool {
483 self.native().fIsVolatile
484 }
485
486 /// Return a copy of [`Path`] with `is_volatile` indicating whether it will be altered
487 /// or discarded by the caller after it is drawn. [`Path`] by default have volatile
488 /// set `false`, allowing Skia to attach a cache of data which speeds repeated drawing.
489 ///
490 /// Mark temporary paths, discarded or modified after use, as volatile
491 /// to inform Skia that the path need not be cached.
492 ///
493 /// Mark animating [`Path`] volatile to improve performance.
494 /// Mark unchanging [`Path`] non-volatile to improve repeated rendering.
495 ///
496 /// raster surface [`Path`] draws are affected by volatile for some shadows.
497 /// GPU surface [`Path`] draws are affected by volatile for some shadows and concave geometries.
498 ///
499 /// * `is_volatile` - `true` if caller will alter [`Path`] after drawing
500 ///
501 /// Returns: [`Path`]
502 pub fn with_is_volatile(&self, is_volatile: bool) -> Self {
503 Self::construct(|p| unsafe { sb::C_SkPath_makeIsVolatile(self.native(), is_volatile, p) })
504 }
505
506 /// Tests if line between [`Point`] pair is degenerate.
507 /// Line with no length or that moves a very short distance is degenerate; it is
508 /// treated as a point.
509 ///
510 /// exact changes the equality test. If `true`, returns `true` only if p1 equals p2.
511 /// If `false`, returns `true` if p1 equals or nearly equals p2.
512 ///
513 /// * `p1` - line start point
514 /// * `p2` - line end point
515 /// * `exact` - if `false`, allow nearly equals
516 ///
517 /// Returns: `true` if line is degenerate; its length is effectively zero
518 ///
519 /// example: <https://fiddle.skia.org/c/@Path_IsLineDegenerate>
520 pub fn is_line_degenerate(p1: impl Into<Point>, p2: impl Into<Point>, exact: bool) -> bool {
521 unsafe { SkPath::IsLineDegenerate(p1.into().native(), p2.into().native(), exact) }
522 }
523
524 /// Tests if quad is degenerate.
525 /// Quad with no length or that moves a very short distance is degenerate; it is
526 /// treated as a point.
527 ///
528 /// * `p1` - quad start point
529 /// * `p2` - quad control point
530 /// * `p3` - quad end point
531 /// * `exact` - if `true`, returns `true` only if p1, p2, and p3 are equal;
532 /// if `false`, returns `true` if p1, p2, and p3 are equal or nearly equal
533 ///
534 /// Returns: `true` if quad is degenerate; its length is effectively zero
535 pub fn is_quad_degenerate(
536 p1: impl Into<Point>,
537 p2: impl Into<Point>,
538 p3: impl Into<Point>,
539 exact: bool,
540 ) -> bool {
541 unsafe {
542 SkPath::IsQuadDegenerate(
543 p1.into().native(),
544 p2.into().native(),
545 p3.into().native(),
546 exact,
547 )
548 }
549 }
550
551 /// Tests if cubic is degenerate.
552 /// Cubic with no length or that moves a very short distance is degenerate; it is
553 /// treated as a point.
554 ///
555 /// * `p1` - cubic start point
556 /// * `p2` - cubic control point 1
557 /// * `p3` - cubic control point 2
558 /// * `p4` - cubic end point
559 /// * `exact` - if `true`, returns `true` only if p1, p2, p3, and p4 are equal;
560 /// if `false`, returns `true` if p1, p2, p3, and p4 are equal or nearly equal
561 ///
562 /// Returns: `true` if cubic is degenerate; its length is effectively zero
563 pub fn is_cubic_degenerate(
564 p1: impl Into<Point>,
565 p2: impl Into<Point>,
566 p3: impl Into<Point>,
567 p4: impl Into<Point>,
568 exact: bool,
569 ) -> bool {
570 unsafe {
571 SkPath::IsCubicDegenerate(
572 p1.into().native(),
573 p2.into().native(),
574 p3.into().native(),
575 p4.into().native(),
576 exact,
577 )
578 }
579 }
580
581 /// Returns `true` if [`Path`] contains only one line;
582 /// [`Verb`] array has two entries: [`Verb::Move`], [`Verb::Line`].
583 /// If [`Path`] contains one line and line is not `None`, line is set to
584 /// line start point and line end point.
585 /// Returns `false` if [`Path`] is not one line; line is unaltered.
586 ///
587 /// * `line` - storage for line. May be `None`
588 ///
589 /// Returns: `true` if [`Path`] contains exactly one line
590 ///
591 /// example: <https://fiddle.skia.org/c/@Path_isLine>
592 pub fn is_line(&self) -> Option<(Point, Point)> {
593 let mut line = [Point::default(); 2];
594 #[allow(clippy::tuple_array_conversions)]
595 unsafe { self.native().isLine(line.native_mut().as_mut_ptr()) }
596 .then_some((line[0], line[1]))
597 }
598
599 /// Return a read-only view into the path's points.
600 pub fn points(&self) -> &[Point] {
601 unsafe {
602 let mut len = 0;
603 let points = sb::C_SkPath_points(self.native(), &mut len);
604 safer::from_raw_parts(Point::from_native_ptr(points), len)
605 }
606 }
607
608 /// Return a read-only view into the path's verbs.
609 pub fn verbs(&self) -> &[PathVerb] {
610 unsafe {
611 let mut len = 0;
612 let verbs = sb::C_SkPath_verbs(self.native(), &mut len);
613 safer::from_raw_parts(verbs, len)
614 }
615 }
616
617 /// Return a read-only view into the path's conic-weights.
618 pub fn conic_weights(&self) -> &[scalar] {
619 unsafe {
620 let mut len = 0;
621 let weights = sb::C_SkPath_conicWeights(self.native(), &mut len);
622 safer::from_raw_parts(weights, len)
623 }
624 }
625
626 pub fn count_points(&self) -> usize {
627 self.points().len()
628 }
629
630 pub fn count_verbs(&self) -> usize {
631 self.verbs().len()
632 }
633
634 /// Return the last point, or `None`
635 ///
636 /// Returns: The last if the path contains one or more [`Point`], else returns `None`
637 ///
638 /// example: <https://fiddle.skia.org/c/@Path_getLastPt>
639 pub fn last_pt(&self) -> Option<Point> {
640 let mut p = Point::default();
641 unsafe { sb::C_SkPath_getLastPt(self.native(), p.native_mut()) }.then_some(p)
642 }
643}
644
645impl Path {
646 /// Returns [`Point`] at index in [`Point`] array. Valid range for index is
647 /// 0 to `count_points()` - 1.
648 /// Returns `None` if index is out of range.
649 /// DEPRECATED
650 ///
651 /// * `index` - [`Point`] array element selector
652 ///
653 /// Returns: [`Point`] array value
654 ///
655 /// example: <https://fiddle.skia.org/c/@Path_getPoint>
656 #[deprecated(since = "0.91.0", note = "use points()")]
657 pub fn get_point(&self, index: usize) -> Option<Point> {
658 let p = Point::from_native_c(unsafe { self.native().getPoint(index.try_into().ok()?) });
659 // Assuming that count_points() is somewhat slow, we check the index when a Point(0,0) is
660 // returned.
661 if p != Point::default() || index < self.count_points() {
662 Some(p)
663 } else {
664 None
665 }
666 }
667
668 /// Returns number of points in [`Path`].
669 /// Copies N points from the path into the span, where N = min(#points, span capacity)
670 /// DEPRECATED
671 /// * `points` - span to receive the points. may be empty
672 ///
673 /// Returns: the number of points in the path
674 ///
675 /// example: <https://fiddle.skia.org/c/@Path_getPoints>
676 #[deprecated(since = "0.91.0")]
677 pub fn get_points(&self, points: &mut [Point]) -> usize {
678 unsafe {
679 sb::C_SkPath_getPoints(
680 self.native(),
681 points.native_mut().as_mut_ptr(),
682 points.len(),
683 )
684 }
685 }
686
687 /// Returns number of points in [`Path`].
688 /// Copies N points from the path into the span, where N = min(#points, span capacity)
689 /// DEPRECATED
690 ///
691 /// * `verbs` - span to store the verbs. may be empty.
692 ///
693 /// Returns: the number of verbs in the path
694 ///
695 /// example: <https://fiddle.skia.org/c/@Path_getVerbs>
696 #[deprecated(since = "0.91.0")]
697 pub fn get_verbs(&self, verbs: &mut [u8]) -> usize {
698 unsafe { sb::C_SkPath_getVerbs(self.native(), verbs.as_mut_ptr(), verbs.len()) }
699 }
700}
701
702impl Path {
703 /// Returns the approximate byte size of the [`Path`] in memory.
704 ///
705 /// Returns: approximate size
706 pub fn approximate_bytes_used(&self) -> usize {
707 unsafe { self.native().approximateBytesUsed() }
708 }
709
710 /// Returns minimum and maximum axes values of [`Point`] array.
711 /// Returns (0, 0, 0, 0) if [`Path`] contains no points. Returned bounds width and height may
712 /// be larger or smaller than area affected when [`Path`] is drawn.
713 ///
714 /// [`Rect`] returned includes all [`Point`] added to [`Path`], including [`Point`] associated with
715 /// [`Verb::Move`] that define empty contours.
716 ///
717 /// Returns: bounds of all [`Point`] in [`Point`] array
718 pub fn bounds(&self) -> &Rect {
719 Rect::from_native_ref(unsafe { &*sb::C_SkPath_getBounds(self.native()) })
720 }
721
722 /// Updates internal bounds so that subsequent calls to `bounds()` are instantaneous.
723 /// Unaltered copies of [`Path`] may also access cached bounds through `bounds()`.
724 ///
725 /// For now, identical to calling `bounds()` and ignoring the returned value.
726 ///
727 /// Call to prepare [`Path`] subsequently drawn from multiple threads,
728 /// to avoid a race condition where each draw separately computes the bounds.
729 pub fn update_bounds_cache(&mut self) -> &mut Self {
730 self.bounds();
731 self
732 }
733
734 /// Returns minimum and maximum axes values of the lines and curves in [`Path`].
735 /// Returns (0, 0, 0, 0) if [`Path`] contains no points.
736 /// Returned bounds width and height may be larger or smaller than area affected
737 /// when [`Path`] is drawn.
738 ///
739 /// Includes [`Point`] associated with [`Verb::Move`] that define empty
740 /// contours.
741 ///
742 /// Behaves identically to `bounds()` when [`Path`] contains
743 /// only lines. If [`Path`] contains curves, computed bounds includes
744 /// the maximum extent of the quad, conic, or cubic; is slower than `bounds()`;
745 /// and unlike `bounds()`, does not cache the result.
746 ///
747 /// Returns: tight bounds of curves in [`Path`]
748 ///
749 /// example: <https://fiddle.skia.org/c/@Path_computeTightBounds>
750 pub fn compute_tight_bounds(&self) -> Rect {
751 Rect::construct(|r| unsafe { sb::C_SkPath_computeTightBounds(self.native(), r) })
752 }
753
754 /// Returns `true` if rect is contained by [`Path`].
755 /// May return `false` when rect is contained by [`Path`].
756 ///
757 /// For now, only returns `true` if [`Path`] has one contour and is convex.
758 /// rect may share points and edges with [`Path`] and be contained.
759 /// Returns `true` if rect is empty, that is, it has zero width or height; and
760 /// the [`Point`] or line described by rect is contained by [`Path`].
761 ///
762 /// * `rect` - [`Rect`], line, or [`Point`] checked for containment
763 ///
764 /// Returns: `true` if rect is contained
765 ///
766 /// example: <https://fiddle.skia.org/c/@Path_conservativelyContainsRect>
767 pub fn conservatively_contains_rect(&self, rect: impl AsRef<Rect>) -> bool {
768 unsafe {
769 self.native()
770 .conservativelyContainsRect(rect.as_ref().native())
771 }
772 }
773}
774
775/// Four oval parts with radii (rx, ry) start at last [`Path`] [`Point`] and ends at (x, y).
776/// ArcSize and Direction select one of the four oval parts.
777pub use sb::SkPath_ArcSize as ArcSize;
778variant_name!(ArcSize::Small);
779
780impl Path {
781 /// Approximates conic with quad array. Conic is constructed from start [`Point`] p0,
782 /// control [`Point`] p1, end [`Point`] p2, and weight w.
783 /// Quad array is stored in pts; this storage is supplied by caller.
784 /// Maximum quad count is 2 to the pow2.
785 /// Every third point in array shares last [`Point`] of previous quad and first [`Point`] of
786 /// next quad. Maximum pts storage size is given by:
787 /// (1 + 2 * (1 << pow2)) * sizeof([`Point`]).
788 ///
789 /// Returns quad count used the approximation, which may be smaller
790 /// than the number requested.
791 ///
792 /// conic weight determines the amount of influence conic control point has on the curve.
793 /// w less than one represents an elliptical section. w greater than one represents
794 /// a hyperbolic section. w equal to one represents a parabolic section.
795 ///
796 /// Two quad curves are sufficient to approximate an elliptical conic with a sweep
797 /// of up to 90 degrees; in this case, set pow2 to one.
798 ///
799 /// * `p0` - conic start [`Point`]
800 /// * `p1` - conic control [`Point`]
801 /// * `p2` - conic end [`Point`]
802 /// * `w` - conic weight
803 /// * `pts` - storage for quad array
804 /// * `pow2` - quad count, as power of two, normally 0 to 5 (1 to 32 quad curves)
805 ///
806 /// Returns: number of quad curves written to pts
807 pub fn convert_conic_to_quads(
808 p0: impl Into<Point>,
809 p1: impl Into<Point>,
810 p2: impl Into<Point>,
811 w: scalar,
812 pts: &mut [Point],
813 pow2: usize,
814 ) -> Option<usize> {
815 let (p0, p1, p2) = (p0.into(), p1.into(), p2.into());
816 let max_pts_count = 1 + 2 * (1 << pow2);
817 if pts.len() >= max_pts_count {
818 Some(unsafe {
819 SkPath::ConvertConicToQuads(
820 p0.native(),
821 p1.native(),
822 p2.native(),
823 w,
824 pts.native_mut().as_mut_ptr(),
825 pow2.try_into().unwrap(),
826 )
827 .try_into()
828 .unwrap()
829 })
830 } else {
831 None
832 }
833 }
834
835 // TODO: return type is probably worth a struct.
836
837 /// Returns `Some(Rect, bool, PathDirection)` if [`Path`] is equivalent to [`Rect`] when filled.
838 /// If `false`: rect, `is_closed`, and direction are unchanged.
839 /// If `true`: rect, `is_closed`, and direction are written to.
840 ///
841 /// rect may be smaller than the [`Path`] bounds. [`Path`] bounds may include [`Verb::Move`] points
842 /// that do not alter the area drawn by the returned rect.
843 ///
844 /// Returns: `Some(rect, is_closed, direction)` if [`Path`] contains [`Rect`]
845 /// * `rect` - bounds of [`Rect`]
846 /// * `is_closed` - set to `true` if [`Path`] is closed
847 /// * `direction` - to [`Rect`] direction
848 ///
849 /// example: <https://fiddle.skia.org/c/@Path_isRect>
850 pub fn is_rect(&self) -> Option<(Rect, bool, PathDirection)> {
851 let mut rect = Rect::default();
852 let mut is_closed = Default::default();
853 let mut direction = PathDirection::default();
854 unsafe {
855 self.native()
856 .isRect(rect.native_mut(), &mut is_closed, &mut direction)
857 }
858 .then_some((rect, is_closed, direction))
859 }
860}
861
862/// AddPathMode chooses how `add_path()` appends. Adding one [`Path`] to another can extend
863/// the last contour or start a new contour.
864pub use sb::SkPath_AddPathMode as AddPathMode;
865variant_name!(AddPathMode::Append);
866
867impl Path {
868 /// Return a copy of [`Path`] with verb array, [`Point`] array, and weight transformed
869 /// by matrix. `try_make_transform` may change verbs and increase their number.
870 ///
871 /// If the resulting path has any non-finite values, returns `None`.
872 ///
873 /// * `matrix` - [`Matrix`] to apply to [`Path`]
874 ///
875 /// Returns: [`Path`] if finite, or `None`
876 pub fn try_make_transform(&self, matrix: &Matrix) -> Option<Path> {
877 Path::try_construct(|path| unsafe {
878 sb::C_SkPath_tryMakeTransform(self.native(), matrix.native(), path)
879 })
880 }
881
882 pub fn try_make_offset(&self, d: impl Into<Vector>) -> Option<Path> {
883 let d = d.into();
884 Path::try_construct(|path| unsafe {
885 sb::C_SkPath_tryMakeOffset(self.native(), d.x, d.y, path)
886 })
887 }
888
889 pub fn try_make_scale(&self, (sx, sy): (scalar, scalar)) -> Option<Path> {
890 Path::try_construct(|path| unsafe {
891 sb::C_SkPath_tryMakeScale(self.native(), sx, sy, path)
892 })
893 }
894
895 // TODO: I think we should keep only the make_ variants.
896
897 /// Return a copy of [`Path`] with verb array, [`Point`] array, and weight transformed
898 /// by matrix. `with_transform` may change verbs and increase their number.
899 ///
900 /// If the resulting path has any non-finite values, this will still return a path
901 /// but that path will return `true` for `is_finite()`.
902 ///
903 /// The newer pattern is to call [`try_make_transform`](Self::try_make_transform) which will only return a
904 /// path if the result is finite.
905 ///
906 /// * `matrix` - [`Matrix`] to apply to [`Path`]
907 ///
908 /// Returns: [`Path`]
909 ///
910 /// example: <https://fiddle.skia.org/c/@Path_transform>
911 #[must_use]
912 pub fn with_transform(&self, matrix: &Matrix) -> Path {
913 Path::construct(|path| unsafe {
914 sb::C_SkPath_makeTransform(self.native(), matrix.native(), path)
915 })
916 }
917
918 #[must_use]
919 pub fn make_transform(&self, m: &Matrix) -> Path {
920 self.with_transform(m)
921 }
922
923 /// Returns [`Path`] with [`Point`] array offset by `(d.x, d.y)`.
924 ///
925 /// * `d` - offset added to [`Point`] array coordinates
926 ///
927 /// Returns: [`Path`]
928 ///
929 /// example: <https://fiddle.skia.org/c/@Path_offset>
930 #[must_use]
931 pub fn with_offset(&self, d: impl Into<Vector>) -> Path {
932 let d = d.into();
933 Path::construct(|path| unsafe { sb::C_SkPath_makeOffset(self.native(), d.x, d.y, path) })
934 }
935
936 #[must_use]
937 pub fn make_offset(&self, d: impl Into<Vector>) -> Path {
938 self.with_offset(d)
939 }
940
941 #[must_use]
942 pub fn make_scale(&self, (sx, sy): (scalar, scalar)) -> Path {
943 self.make_transform(&Matrix::scale((sx, sy)))
944 }
945}
946
947/// SegmentMask constants correspond to each drawing Verb type in [`crate::Path`]; for instance, if
948/// [`crate::Path`] only contains lines, only the [`crate::path::SegmentMask::LINE`] bit is set.
949pub type SegmentMask = path_types::PathSegmentMask;
950
951impl Path {
952 /// Returns a mask, where each set bit corresponds to a [`SegmentMask`] constant
953 /// if [`Path`] contains one or more verbs of that type.
954 /// Returns zero if [`Path`] contains no lines, or curves: quads, conics, or cubics.
955 ///
956 /// `segment_masks()` returns a cached result; it is very fast.
957 ///
958 /// Returns: [`SegmentMask`] bits or zero
959 pub fn segment_masks(&self) -> SegmentMask {
960 SegmentMask::from_bits_truncate(unsafe { self.native().getSegmentMasks() })
961 }
962}
963
964/// Verb instructs [`Path`] how to interpret one or more [`Point`] and optional conic weight;
965/// manage contour, and terminate [`Path`].
966pub type Verb = sb::SkPath_Verb;
967variant_name!(Verb::Line);
968
969// SK_HIDE_PATH_EDIT_METHODS
970
971impl Path {
972 /// Specifies whether [`Path`] is volatile; whether it will be altered or discarded
973 /// by the caller after it is drawn. [`Path`] by default have volatile set `false`, allowing
974 /// `Device` to attach a cache of data which speeds repeated drawing.
975 ///
976 /// Mark temporary paths, discarded or modified after use, as volatile
977 /// to inform `Device` that the path need not be cached.
978 ///
979 /// Mark animating [`Path`] volatile to improve performance.
980 /// Mark unchanging [`Path`] non-volatile to improve repeated rendering.
981 ///
982 /// raster surface [`Path`] draws are affected by volatile for some shadows.
983 /// GPU surface [`Path`] draws are affected by volatile for some shadows and concave geometries.
984 ///
985 /// * `is_volatile` - `true` if caller will alter [`Path`] after drawing
986 ///
987 /// Returns: reference to [`Path`]
988 pub fn set_is_volatile(&mut self, is_volatile: bool) -> &mut Self {
989 self.native_mut().fIsVolatile = is_volatile;
990 self
991 }
992
993 /// Exchanges the verb array, [`Point`] array, weights, and [`PathFillType`] with other.
994 /// Cached state is also exchanged. `swap()` internally exchanges pointers, so
995 /// it is lightweight and does not allocate memory.
996 ///
997 /// `swap()` usage has largely been replaced by PartialEq.
998 /// [`Path`] do not copy their content on assignment until they are written to,
999 /// making assignment as efficient as swap().
1000 ///
1001 /// * `other` - [`Path`] exchanged by value
1002 ///
1003 /// example: <https://fiddle.skia.org/c/@Path_swap>
1004 pub fn swap(&mut self, other: &mut Path) -> &mut Self {
1005 unsafe { self.native_mut().swap(other.native_mut()) }
1006 self
1007 }
1008
1009 /// Sets `FillType`, the rule used to fill [`Path`]. While there is no check
1010 /// that `ft` is legal, values outside of `FillType` are not supported.
1011 pub fn set_fill_type(&mut self, ft: PathFillType) -> &mut Self {
1012 self.native_mut().fFillType = ft;
1013 self
1014 }
1015
1016 /// Replaces FillType with its inverse. The inverse of FillType describes the area
1017 /// unmodified by the original FillType.
1018 pub fn toggle_inverse_fill_type(&mut self) -> &mut Self {
1019 let n = self.native_mut();
1020 n.fFillType = n.fFillType.toggle_inverse();
1021 self
1022 }
1023
1024 /// Sets [`Path`] to its initial state.
1025 /// Removes verb array, [`Point`] array, and weights, and sets FillType to `Winding`.
1026 /// Internal storage associated with [`Path`] is released.
1027 ///
1028 /// Returns: reference to [`Path`]
1029 ///
1030 /// example: <https://fiddle.skia.org/c/@Path_reset>
1031 pub fn reset(&mut self) -> &mut Self {
1032 unsafe { self.native_mut().reset() };
1033 self
1034 }
1035}
1036
1037impl Path {
1038 /// Returns a copy of this path in the current state, and resets the path to empty.
1039 pub fn detach(&mut self) -> Self {
1040 let result = self.clone();
1041 self.reset();
1042 result
1043 }
1044
1045 pub fn iter(&self) -> PathIter {
1046 PathIter::from_native_c(construct(|iter| unsafe {
1047 sb::C_SkPath_iter(self.native(), iter)
1048 }))
1049 }
1050}
1051
1052/// Iterates through verb array, and associated [`Point`] array and conic weight.
1053/// Provides options to treat open contours as closed, and to ignore
1054/// degenerate data.
1055#[repr(transparent)]
1056pub struct Iter<'a>(SkPath_Iter, PhantomData<&'a Handle<SkPath>>);
1057
1058impl NativeAccess for Iter<'_> {
1059 type Native = SkPath_Iter;
1060
1061 fn native(&self) -> &SkPath_Iter {
1062 &self.0
1063 }
1064 fn native_mut(&mut self) -> &mut SkPath_Iter {
1065 &mut self.0
1066 }
1067}
1068
1069impl Drop for Iter<'_> {
1070 fn drop(&mut self) {
1071 unsafe { sb::C_SkPath_Iter_destruct(&mut self.0) }
1072 }
1073}
1074
1075impl Default for Iter<'_> {
1076 /// Initializes [`Iter`] with an empty [`Path`]. `next()` on [`Iter`] returns
1077 /// [`Verb::Done`].
1078 /// Call `set_path` to initialize [`Iter`] at a later time.
1079 ///
1080 /// Returns: [`Iter`] of empty [`Path`]
1081 ///
1082 /// example: <https://fiddle.skia.org/c/@Path_Iter_Iter>
1083 fn default() -> Self {
1084 Iter(unsafe { SkPath_Iter::new() }, PhantomData)
1085 }
1086}
1087
1088impl fmt::Debug for Iter<'_> {
1089 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1090 f.debug_struct("Iter")
1091 .field("conic_weight", &self.conic_weight())
1092 .field("is_close_line", &self.is_close_line())
1093 .field("is_closed_contour", &self.is_closed_contour())
1094 .finish()
1095 }
1096}
1097
1098impl Iter<'_> {
1099 /// Sets [`Iter`] to return elements of verb array, [`Point`] array, and conic weight in
1100 /// path. If `force_close` is `true`, [`Iter`] will add [`Verb::Line`] and [`Verb::Close`] after each
1101 /// open contour. path is not altered.
1102 ///
1103 /// * `path` - [`Path`] to iterate
1104 /// * `force_close` - `true` if open contours generate [`Verb::Close`]
1105 ///
1106 /// Returns: [`Iter`] of path
1107 ///
1108 /// example: <https://fiddle.skia.org/c/@Path_Iter_const_SkPath>
1109 pub fn new(path: &Path, force_close: bool) -> Self {
1110 Self(
1111 unsafe { SkPath_Iter::new1(path.native(), force_close) },
1112 PhantomData,
1113 )
1114 }
1115
1116 /// Sets [`Iter`] to return elements of verb array, [`Point`] array, and conic weight in
1117 /// path. If `force_close` is `true`, [`Iter`] will add [`Verb::Line`] and [`Verb::Close`] after each
1118 /// open contour. path is not altered.
1119 ///
1120 /// * `path` - [`Path`] to iterate
1121 /// * `force_close` - `true` if open contours generate [`Verb::Close`]
1122 ///
1123 /// example: <https://fiddle.skia.org/c/@Path_Iter_setPath>
1124 pub fn set_path(&mut self, path: &Path, force_close: bool) {
1125 unsafe {
1126 self.0.setPath(path.native(), force_close);
1127 }
1128 }
1129
1130 /// Returns conic weight if `next()` returned [`Verb::Conic`].
1131 ///
1132 /// If `next()` has not been called, or `next()` did not return [`Verb::Conic`],
1133 /// result is `None`.
1134 ///
1135 /// Returns: conic weight for conic [`Point`] returned by `next()`
1136 pub fn conic_weight(&self) -> Option<scalar> {
1137 #[allow(clippy::map_clone)]
1138 self.native()
1139 .fConicWeights
1140 .into_non_null()
1141 .map(|p| unsafe { *p.as_ref() })
1142 }
1143
1144 /// Returns `true` if last [`Verb::Line`] returned by `next()` was generated
1145 /// by [`Verb::Close`]. When `true`, the end point returned by `next()` is
1146 /// also the start point of contour.
1147 ///
1148 /// If `next()` has not been called, or `next()` did not return [`Verb::Line`],
1149 /// result is undefined.
1150 ///
1151 /// Returns: `true` if last [`Verb::Line`] was generated by [`Verb::Close`]
1152 pub fn is_close_line(&self) -> bool {
1153 unsafe { sb::C_SkPath_Iter_isCloseLine(self.native()) }
1154 }
1155
1156 /// Returns `true` if subsequent calls to `next()` return [`Verb::Close`] before returning
1157 /// [`Verb::Move`]. if `true`, contour [`Iter`] is processing may end with [`Verb::Close`], or
1158 /// [`Iter`] may have been initialized with force close set to `true`.
1159 ///
1160 /// Returns: `true` if contour is closed
1161 ///
1162 /// example: <https://fiddle.skia.org/c/@Path_Iter_isClosedContour>
1163 pub fn is_closed_contour(&self) -> bool {
1164 unsafe { self.native().isClosedContour() }
1165 }
1166}
1167
1168impl Iterator for Iter<'_> {
1169 type Item = (Verb, Vec<Point>);
1170
1171 /// Returns next [`Verb`] in verb array, and advances [`Iter`].
1172 /// When verb array is exhausted, returns [`Verb::Done`].
1173 ///
1174 /// Zero to four [`Point`] are stored in pts, depending on the returned [`Verb`].
1175 ///
1176 /// * `pts` - storage for [`Point`] data describing returned [`Verb`]
1177 ///
1178 /// Returns: next [`Verb`] from verb array
1179 ///
1180 /// example: <https://fiddle.skia.org/c/@Path_RawIter_next>
1181 fn next(&mut self) -> Option<Self::Item> {
1182 let mut points = [Point::default(); Verb::MAX_POINTS];
1183 let verb = unsafe { self.native_mut().next(points.native_mut().as_mut_ptr()) };
1184 if verb != Verb::Done {
1185 Some((verb, points[0..verb.points()].into()))
1186 } else {
1187 None
1188 }
1189 }
1190}
1191
1192#[repr(transparent)]
1193#[deprecated(
1194 since = "0.30.0",
1195 note = "User Iter instead, RawIter will soon be removed."
1196)]
1197pub struct RawIter<'a>(SkPath_RawIter, PhantomData<&'a Handle<SkPath>>);
1198
1199#[allow(deprecated)]
1200impl NativeAccess for RawIter<'_> {
1201 type Native = SkPath_RawIter;
1202
1203 fn native(&self) -> &SkPath_RawIter {
1204 &self.0
1205 }
1206 fn native_mut(&mut self) -> &mut SkPath_RawIter {
1207 &mut self.0
1208 }
1209}
1210
1211#[allow(deprecated)]
1212impl Drop for RawIter<'_> {
1213 fn drop(&mut self) {
1214 unsafe { sb::C_SkPath_RawIter_destruct(&mut self.0) }
1215 }
1216}
1217
1218#[allow(deprecated)]
1219impl Default for RawIter<'_> {
1220 fn default() -> Self {
1221 RawIter(
1222 construct(|ri| unsafe { sb::C_SkPath_RawIter_Construct(ri) }),
1223 PhantomData,
1224 )
1225 }
1226}
1227
1228#[allow(deprecated)]
1229impl RawIter<'_> {
1230 pub fn new(path: &Path) -> RawIter {
1231 RawIter::default().set_path(path)
1232 }
1233
1234 pub fn set_path(mut self, path: &Path) -> RawIter {
1235 unsafe { self.native_mut().setPath(path.native()) }
1236 let r = RawIter(self.0, PhantomData);
1237 forget(self);
1238 r
1239 }
1240
1241 pub fn peek(&self) -> Verb {
1242 unsafe { sb::C_SkPath_RawIter_peek(self.native()) }
1243 }
1244
1245 pub fn conic_weight(&self) -> scalar {
1246 self.native().fConicWeight
1247 }
1248}
1249
1250#[allow(deprecated)]
1251impl Iterator for RawIter<'_> {
1252 type Item = (Verb, Vec<Point>);
1253
1254 fn next(&mut self) -> Option<Self::Item> {
1255 let mut points = [Point::default(); Verb::MAX_POINTS];
1256
1257 let verb = unsafe { self.native_mut().next(points.native_mut().as_mut_ptr()) };
1258 (verb != Verb::Done).then(|| (verb, points[0..verb.points()].into()))
1259 }
1260}
1261
1262impl Path {
1263 /// Returns `true` if the point is contained by [`Path`], taking into
1264 /// account [`PathFillType`].
1265 ///
1266 /// * `point` - the point to test
1267 ///
1268 /// Returns: `true` if [`Point`] is in [`Path`]
1269 ///
1270 /// example: <https://fiddle.skia.org/c/@Path_contains>
1271 pub fn contains(&self, point: impl Into<Point>) -> bool {
1272 let point = point.into();
1273 unsafe { self.native().contains(point.into_native()) }
1274 }
1275
1276 /// Writes text representation of [`Path`] to [`Data`].
1277 /// Set `dump_as_hex` `true` to generate exact binary representations
1278 /// of floating point numbers used in [`Point`] array and conic weights.
1279 ///
1280 /// * `dump_as_hex` - `true` if scalar values are written as hexadecimal
1281 ///
1282 /// example: <https://fiddle.skia.org/c/@Path_dump>
1283 pub fn dump_as_data(&self, dump_as_hex: bool) -> Data {
1284 let mut stream = DynamicMemoryWStream::new();
1285 unsafe {
1286 self.native()
1287 .dump(stream.native_mut().base_mut(), dump_as_hex);
1288 }
1289 stream.detach_as_data()
1290 }
1291
1292 /// See [`Path::dump_as_data()`]
1293 pub fn dump(&self) {
1294 unsafe { self.native().dump(ptr::null_mut(), false) }
1295 }
1296
1297 /// See [`Path::dump_as_data()`]
1298 pub fn dump_hex(&self) {
1299 unsafe { self.native().dump(ptr::null_mut(), true) }
1300 }
1301
1302 // TODO: writeToMemory()?
1303
1304 /// Writes [`Path`] to buffer, returning the buffer written to, wrapped in [`Data`].
1305 ///
1306 /// `serialize()` writes [`PathFillType`], verb array, [`Point`] array, conic weight, and
1307 /// additionally writes computed information like convexity and bounds.
1308 ///
1309 /// `serialize()` should only be used in concert with `read_from_memory`().
1310 /// The format used for [`Path`] in memory is not guaranteed.
1311 ///
1312 /// Returns: [`Path`] data wrapped in [`Data`] buffer
1313 ///
1314 /// example: <https://fiddle.skia.org/c/@Path_serialize>
1315 pub fn serialize(&self) -> Data {
1316 Data::from_ptr(unsafe { sb::C_SkPath_serialize(self.native()) }).unwrap()
1317 }
1318
1319 // TODO: ReadFromMemory
1320
1321 pub fn deserialize(data: &Data) -> Option<Path> {
1322 let mut path = Path::default();
1323 let bytes = data.as_bytes();
1324 unsafe { sb::C_SkPath_ReadFromMemory(path.native_mut(), bytes.as_ptr() as _, bytes.len()) }
1325 .then_some(path)
1326 }
1327
1328 /// (See skbug.com/40032862)
1329 /// Returns a non-zero, globally unique value. A different value is returned
1330 /// if verb array, [`Point`] array, or conic weight changes.
1331 ///
1332 /// Setting [`PathFillType`] does not change generation identifier.
1333 ///
1334 /// Each time the path is modified, a different generation identifier will be returned.
1335 /// [`PathFillType`] does affect generation identifier on Android framework.
1336 ///
1337 /// Returns: non-zero, globally unique value
1338 ///
1339 /// example: <https://fiddle.skia.org/c/@Path_getGenerationID>
1340 pub fn generation_id(&self) -> u32 {
1341 unsafe { self.native().getGenerationID() }
1342 }
1343
1344 /// Returns if [`Path`] data is consistent. Corrupt [`Path`] data is detected if
1345 /// internal values are out of range or internal storage does not match
1346 /// array dimensions.
1347 ///
1348 /// Returns: `true` if [`Path`] data is consistent
1349 pub fn is_valid(&self) -> bool {
1350 unsafe { self.native().isValid() }
1351 }
1352}
1353
1354#[cfg(test)]
1355mod tests {
1356 use super::*;
1357
1358 #[test]
1359 fn test_count_points() {
1360 let p = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1361 let points_count = p.count_points();
1362 assert_eq!(points_count, 4);
1363 }
1364
1365 #[test]
1366 fn test_fill_type() {
1367 let mut p = Path::default();
1368 assert_eq!(p.fill_type(), PathFillType::Winding);
1369 p.set_fill_type(PathFillType::EvenOdd);
1370 assert_eq!(p.fill_type(), PathFillType::EvenOdd);
1371 assert!(!p.is_inverse_fill_type());
1372 p.toggle_inverse_fill_type();
1373 assert_eq!(p.fill_type(), PathFillType::InverseEvenOdd);
1374 assert!(p.is_inverse_fill_type());
1375 }
1376
1377 #[test]
1378 fn test_is_volatile() {
1379 let mut p = Path::default();
1380 assert!(!p.is_volatile());
1381 p.set_is_volatile(true);
1382 assert!(p.is_volatile());
1383 }
1384
1385 #[test]
1386 fn test_path_rect() {
1387 let r = Rect::new(0.0, 0.0, 100.0, 100.0);
1388 let path = Path::rect(r, None);
1389 assert_eq!(*path.bounds(), r);
1390 }
1391
1392 #[test]
1393 fn test_points_verbs_conic_weights() {
1394 let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1395
1396 // Test points()
1397 let points = path.points();
1398 assert_eq!(points.len(), 4);
1399
1400 // Test verbs()
1401 let verbs = path.verbs();
1402 assert_eq!(verbs.len(), 5); // Move + 4 Lines + Close
1403
1404 // Test conic_weights()
1405 let weights = path.conic_weights();
1406 assert_eq!(weights.len(), 0); // Rectangle has no conics
1407 }
1408
1409 #[test]
1410 fn test_with_offset() {
1411 let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1412 let offset_path = path.with_offset((5.0, 5.0));
1413
1414 assert_eq!(*offset_path.bounds(), Rect::new(5.0, 5.0, 15.0, 15.0));
1415 }
1416
1417 #[test]
1418 fn test_with_transform() {
1419 let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1420 let matrix = Matrix::scale((2.0, 2.0));
1421 let transformed = path.with_transform(&matrix);
1422
1423 assert_eq!(*transformed.bounds(), Rect::new(0.0, 0.0, 20.0, 20.0));
1424 }
1425
1426 #[test]
1427 fn test_try_make_transform() {
1428 let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1429
1430 // Test with finite transform
1431 let matrix = Matrix::scale((2.0, 2.0));
1432 let result = path.try_make_transform(&matrix);
1433 assert!(result.is_some());
1434 let transformed = result.unwrap();
1435 assert_eq!(*transformed.bounds(), Rect::new(0.0, 0.0, 20.0, 20.0));
1436
1437 // Test with extreme scale that might produce non-finite values
1438 let extreme_matrix = Matrix::scale((f32::MAX, f32::MAX));
1439 let result = path.try_make_transform(&extreme_matrix);
1440 // The result depends on whether the transform produces finite values
1441 // This test documents the behavior
1442 if let Some(transformed) = result {
1443 assert!(transformed.is_finite());
1444 }
1445 }
1446
1447 #[test]
1448 fn test_try_make_offset() {
1449 let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1450
1451 // Test with finite offset
1452 let result = path.try_make_offset((5.0, 5.0));
1453 assert!(result.is_some());
1454 let offset_path = result.unwrap();
1455 assert_eq!(*offset_path.bounds(), Rect::new(5.0, 5.0, 15.0, 15.0));
1456 }
1457
1458 #[test]
1459 fn test_try_make_scale() {
1460 let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1461
1462 // Test with finite scale
1463 let result = path.try_make_scale((3.0, 3.0));
1464 assert!(result.is_some());
1465 let scaled = result.unwrap();
1466 assert_eq!(*scaled.bounds(), Rect::new(0.0, 0.0, 30.0, 30.0));
1467 }
1468
1469 #[test]
1470 fn test_serialize_deserialize() {
1471 let path = Path::rect(Rect::new(10.0, 10.0, 20.0, 20.0), None);
1472
1473 let data = path.serialize();
1474 let deserialized = Path::deserialize(&data).unwrap();
1475
1476 assert_eq!(path, deserialized);
1477 }
1478}