Skip to main content

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