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", note = "use points()")]
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 the min/max of the path's 'trimmed' points. The trimmed points are all of the
711    /// points in the path, with the exception of the path having more than one contour, and the
712    /// final contour containing only a [`Verb::Move`]. In that case the trailing [`Verb::Move`] point
713    /// is ignored when computing the bounds.
714    ///
715    /// If the path has no verbs, or the path contains non-finite values,
716    /// then `{0, 0, 0, 0}` is returned. (see `is_finite`())
717    ///
718    /// Returns: bounds of the path's points
719    pub fn bounds(&self) -> &Rect {
720        Rect::from_native_ref(unsafe { &*sb::C_SkPath_getBounds(self.native()) })
721    }
722
723    /// Updates internal bounds so that subsequent calls to `bounds()` are instantaneous.
724    /// Unaltered copies of [`Path`] may also access cached bounds through `bounds()`.
725    ///
726    /// For now, identical to calling `bounds()` and ignoring the returned value.
727    ///
728    /// Call to prepare [`Path`] subsequently drawn from multiple threads,
729    /// to avoid a race condition where each draw separately computes the bounds.
730    pub fn update_bounds_cache(&mut self) -> &mut Self {
731        self.bounds();
732        self
733    }
734
735    /// Returns minimum and maximum axes values of the lines and curves in [`Path`].
736    /// Returns (0, 0, 0, 0) if [`Path`] contains no points.
737    /// Returned bounds width and height may be larger or smaller than area affected
738    /// when [`Path`] is drawn.
739    ///
740    /// Includes [`Point`] associated with [`Verb::Move`] that define empty
741    /// contours.
742    ///
743    /// Behaves identically to `bounds()` when [`Path`] contains
744    /// only lines. If [`Path`] contains curves, computed bounds includes
745    /// the maximum extent of the quad, conic, or cubic; is slower than `bounds()`;
746    /// and unlike `bounds()`, does not cache the result.
747    ///
748    /// Returns: tight bounds of curves in [`Path`]
749    ///
750    /// example: <https://fiddle.skia.org/c/@Path_computeTightBounds>
751    pub fn compute_tight_bounds(&self) -> Rect {
752        Rect::construct(|r| unsafe { sb::C_SkPath_computeTightBounds(self.native(), r) })
753    }
754
755    /// Returns `true` if rect is contained by [`Path`].
756    /// May return `false` when rect is contained by [`Path`].
757    ///
758    /// For now, only returns `true` if [`Path`] has one contour and is convex.
759    /// rect may share points and edges with [`Path`] and be contained.
760    /// Returns `true` if rect is empty, that is, it has zero width or height; and
761    /// the [`Point`] or line described by rect is contained by [`Path`].
762    ///
763    /// * `rect` - [`Rect`], line, or [`Point`] checked for containment
764    ///
765    /// Returns: `true` if rect is contained
766    ///
767    /// example: <https://fiddle.skia.org/c/@Path_conservativelyContainsRect>
768    pub fn conservatively_contains_rect(&self, rect: impl AsRef<Rect>) -> bool {
769        unsafe {
770            self.native()
771                .conservativelyContainsRect(rect.as_ref().native())
772        }
773    }
774}
775
776impl Path {
777    /// Approximates conic with quad array. Conic is constructed from start [`Point`] p0,
778    /// control [`Point`] p1, end [`Point`] p2, and weight w.
779    /// Quad array is stored in pts; this storage is supplied by caller.
780    /// Maximum quad count is 2 to the pow2.
781    /// Every third point in array shares last [`Point`] of previous quad and first [`Point`] of
782    /// next quad. Maximum pts storage size is given by:
783    /// (1 + 2 * (1 << pow2)) * sizeof([`Point`]).
784    ///
785    /// Returns quad count used the approximation, which may be smaller
786    /// than the number requested.
787    ///
788    /// conic weight determines the amount of influence conic control point has on the curve.
789    /// w less than one represents an elliptical section. w greater than one represents
790    /// a hyperbolic section. w equal to one represents a parabolic section.
791    ///
792    /// Two quad curves are sufficient to approximate an elliptical conic with a sweep
793    /// of up to 90 degrees; in this case, set pow2 to one.
794    ///
795    /// * `p0` - conic start [`Point`]
796    /// * `p1` - conic control [`Point`]
797    /// * `p2` - conic end [`Point`]
798    /// * `w` - conic weight
799    /// * `pts` - storage for quad array
800    /// * `pow2` - quad count, as power of two, normally 0 to 5 (1 to 32 quad curves)
801    ///
802    /// Returns: number of quad curves written to pts
803    pub fn convert_conic_to_quads(
804        p0: impl Into<Point>,
805        p1: impl Into<Point>,
806        p2: impl Into<Point>,
807        w: scalar,
808        pts: &mut [Point],
809        pow2: usize,
810    ) -> Option<usize> {
811        let (p0, p1, p2) = (p0.into(), p1.into(), p2.into());
812        let max_pts_count = 1 + 2 * (1 << pow2);
813        if pts.len() >= max_pts_count {
814            Some(unsafe {
815                SkPath::ConvertConicToQuads(
816                    p0.native(),
817                    p1.native(),
818                    p2.native(),
819                    w,
820                    pts.native_mut().as_mut_ptr(),
821                    pow2.try_into().unwrap(),
822                )
823                .try_into()
824                .unwrap()
825            })
826        } else {
827            None
828        }
829    }
830
831    // TODO: return type is probably worth a struct.
832
833    /// Returns `Some(Rect, bool, PathDirection)` if [`Path`] is equivalent to [`Rect`] when filled.
834    /// If `false`: rect, `is_closed`, and direction are unchanged.
835    /// If `true`: rect, `is_closed`, and direction are written to.
836    ///
837    /// rect may be smaller than the [`Path`] bounds. [`Path`] bounds may include [`Verb::Move`] points
838    /// that do not alter the area drawn by the returned rect.
839    ///
840    /// Returns: `Some(rect, is_closed, direction)` if [`Path`] contains [`Rect`]
841    /// * `rect` - bounds of [`Rect`]
842    /// * `is_closed` - set to `true` if [`Path`] is closed
843    /// * `direction` - to [`Rect`] direction
844    ///
845    /// example: <https://fiddle.skia.org/c/@Path_isRect>
846    pub fn is_rect(&self) -> Option<(Rect, bool, PathDirection)> {
847        let mut rect = Rect::default();
848        let mut is_closed = Default::default();
849        let mut direction = PathDirection::default();
850        unsafe {
851            self.native()
852                .isRect(rect.native_mut(), &mut is_closed, &mut direction)
853        }
854        .then_some((rect, is_closed, direction))
855    }
856}
857
858/// AddPathMode chooses how `add_path()` appends. Adding one [`Path`] to another can extend
859/// the last contour or start a new contour.
860pub use sb::SkPath_AddPathMode as AddPathMode;
861variant_name!(AddPathMode::Append);
862
863impl Path {
864    /// Return a copy of [`Path`] with verb array, [`Point`] array, and weight transformed
865    /// by matrix. `try_make_transform` may change verbs and increase their number.
866    ///
867    /// If the resulting path has any non-finite values, returns `None`.
868    ///
869    /// * `matrix` - [`Matrix`] to apply to [`Path`]
870    ///
871    /// Returns: [`Path`] if finite, or `None`
872    pub fn try_make_transform(&self, matrix: &Matrix) -> Option<Path> {
873        Path::try_construct(|path| unsafe {
874            sb::C_SkPath_tryMakeTransform(self.native(), matrix.native(), path)
875        })
876    }
877
878    pub fn try_make_offset(&self, d: impl Into<Vector>) -> Option<Path> {
879        let d = d.into();
880        Path::try_construct(|path| unsafe {
881            sb::C_SkPath_tryMakeOffset(self.native(), d.x, d.y, path)
882        })
883    }
884
885    pub fn try_make_scale(&self, (sx, sy): (scalar, scalar)) -> Option<Path> {
886        Path::try_construct(|path| unsafe {
887            sb::C_SkPath_tryMakeScale(self.native(), sx, sy, path)
888        })
889    }
890
891    // TODO: I think we should keep only the make_ variants.
892
893    /// Return a copy of [`Path`] with verb array, [`Point`] array, and weight transformed
894    /// by matrix. `with_transform` may change verbs and increase their number.
895    ///
896    /// If the resulting path has any non-finite values, this will still return a path
897    /// but that path will return `true` for `is_finite()`.
898    ///
899    /// The newer pattern is to call [`try_make_transform`](Self::try_make_transform) which will only return a
900    /// path if the result is finite.
901    ///
902    /// * `matrix` - [`Matrix`] to apply to [`Path`]
903    ///
904    /// Returns: [`Path`]
905    ///
906    /// example: <https://fiddle.skia.org/c/@Path_transform>
907    #[must_use]
908    pub fn with_transform(&self, matrix: &Matrix) -> Path {
909        Path::construct(|path| unsafe {
910            sb::C_SkPath_makeTransform(self.native(), matrix.native(), path)
911        })
912    }
913
914    #[must_use]
915    pub fn make_transform(&self, m: &Matrix) -> Path {
916        self.with_transform(m)
917    }
918
919    /// Returns [`Path`] with [`Point`] array offset by `(d.x, d.y)`.
920    ///
921    /// * `d` - offset added to [`Point`] array coordinates
922    ///
923    /// Returns: [`Path`]
924    ///
925    /// example: <https://fiddle.skia.org/c/@Path_offset>
926    #[must_use]
927    pub fn with_offset(&self, d: impl Into<Vector>) -> Path {
928        let d = d.into();
929        Path::construct(|path| unsafe { sb::C_SkPath_makeOffset(self.native(), d.x, d.y, path) })
930    }
931
932    #[must_use]
933    pub fn make_offset(&self, d: impl Into<Vector>) -> Path {
934        self.with_offset(d)
935    }
936
937    #[must_use]
938    pub fn make_scale(&self, (sx, sy): (scalar, scalar)) -> Path {
939        self.make_transform(&Matrix::scale((sx, sy)))
940    }
941}
942
943/// SegmentMask constants correspond to each drawing Verb type in [`crate::Path`]; for instance, if
944/// [`crate::Path`] only contains lines, only the [`crate::path::SegmentMask::LINE`] bit is set.
945pub type SegmentMask = path_types::PathSegmentMask;
946
947impl Path {
948    /// Returns a mask, where each set bit corresponds to a [`SegmentMask`] constant
949    /// if [`Path`] contains one or more verbs of that type.
950    /// Returns zero if [`Path`] contains no lines, or curves: quads, conics, or cubics.
951    ///
952    /// `segment_masks()` returns a cached result; it is very fast.
953    ///
954    /// Returns: [`SegmentMask`] bits or zero
955    pub fn segment_masks(&self) -> SegmentMask {
956        SegmentMask::from_bits_truncate(unsafe { self.native().getSegmentMasks() })
957    }
958}
959
960/// Verb instructs [`Path`] how to interpret one or more [`Point`] and optional conic weight;
961/// manage contour, and terminate [`Path`].
962pub type Verb = sb::SkPath_Verb;
963variant_name!(Verb::Line);
964
965// SK_HIDE_PATH_EDIT_METHODS
966
967impl Path {
968    /// Specifies whether [`Path`] is volatile; whether it will be altered or discarded
969    /// by the caller after it is drawn. [`Path`] by default have volatile set `false`, allowing
970    /// `Device` to attach a cache of data which speeds repeated drawing.
971    ///
972    /// Mark temporary paths, discarded or modified after use, as volatile
973    /// to inform `Device` that the path need not be cached.
974    ///
975    /// Mark animating [`Path`] volatile to improve performance.
976    /// Mark unchanging [`Path`] non-volatile to improve repeated rendering.
977    ///
978    /// raster surface [`Path`] draws are affected by volatile for some shadows.
979    /// GPU surface [`Path`] draws are affected by volatile for some shadows and concave geometries.
980    ///
981    /// * `is_volatile` - `true` if caller will alter [`Path`] after drawing
982    ///
983    /// Returns: reference to [`Path`]
984    pub fn set_is_volatile(&mut self, is_volatile: bool) -> &mut Self {
985        self.native_mut().fIsVolatile = is_volatile;
986        self
987    }
988
989    /// Exchanges the verb array, [`Point`] array, weights, and [`PathFillType`] with other.
990    /// Cached state is also exchanged. `swap()` internally exchanges pointers, so
991    /// it is lightweight and does not allocate memory.
992    ///
993    /// `swap()` usage has largely been replaced by PartialEq.
994    /// [`Path`] do not copy their content on assignment until they are written to,
995    /// making assignment as efficient as swap().
996    ///
997    /// * `other` - [`Path`] exchanged by value
998    ///
999    /// example: <https://fiddle.skia.org/c/@Path_swap>
1000    pub fn swap(&mut self, other: &mut Path) -> &mut Self {
1001        unsafe { self.native_mut().swap(other.native_mut()) }
1002        self
1003    }
1004
1005    /// Sets `FillType`, the rule used to fill [`Path`]. While there is no check
1006    /// that `ft` is legal, values outside of `FillType` are not supported.
1007    pub fn set_fill_type(&mut self, ft: PathFillType) -> &mut Self {
1008        self.native_mut().fFillType = ft;
1009        self
1010    }
1011
1012    /// Replaces FillType with its inverse. The inverse of FillType describes the area
1013    /// unmodified by the original FillType.
1014    pub fn toggle_inverse_fill_type(&mut self) -> &mut Self {
1015        let n = self.native_mut();
1016        n.fFillType = n.fFillType.toggle_inverse();
1017        self
1018    }
1019
1020    /// Sets [`Path`] to its initial state.
1021    /// Removes verb array, [`Point`] array, and weights, and sets FillType to `Winding`.
1022    /// Internal storage associated with [`Path`] is released.
1023    ///
1024    /// Returns: reference to [`Path`]
1025    ///
1026    /// example: <https://fiddle.skia.org/c/@Path_reset>
1027    pub fn reset(&mut self) -> &mut Self {
1028        unsafe { self.native_mut().reset() };
1029        self
1030    }
1031}
1032
1033impl Path {
1034    /// Returns a copy of this path in the current state, and resets the path to empty.
1035    pub fn detach(&mut self) -> Self {
1036        let result = self.clone();
1037        self.reset();
1038        result
1039    }
1040
1041    pub fn iter(&self) -> PathIter {
1042        PathIter::from_native_c(construct(|iter| unsafe {
1043            sb::C_SkPath_iter(self.native(), iter)
1044        }))
1045    }
1046}
1047
1048/// Iterates through verb array, and associated [`Point`] array and conic weight.
1049/// Provides options to treat open contours as closed, and to ignore
1050/// degenerate data.
1051#[repr(transparent)]
1052pub struct Iter<'a>(SkPath_Iter, PhantomData<&'a Handle<SkPath>>);
1053
1054impl NativeAccess for Iter<'_> {
1055    type Native = SkPath_Iter;
1056
1057    fn native(&self) -> &SkPath_Iter {
1058        &self.0
1059    }
1060    fn native_mut(&mut self) -> &mut SkPath_Iter {
1061        &mut self.0
1062    }
1063}
1064
1065impl Drop for Iter<'_> {
1066    fn drop(&mut self) {
1067        unsafe { sb::C_SkPath_Iter_destruct(&mut self.0) }
1068    }
1069}
1070
1071impl Default for Iter<'_> {
1072    /// Initializes [`Iter`] with an empty [`Path`]. `next()` on [`Iter`] returns
1073    /// [`Verb::Done`].
1074    /// Call `set_path` to initialize [`Iter`] at a later time.
1075    ///
1076    /// Returns: [`Iter`] of empty [`Path`]
1077    ///
1078    /// example: <https://fiddle.skia.org/c/@Path_Iter_Iter>
1079    fn default() -> Self {
1080        Iter(unsafe { SkPath_Iter::new() }, PhantomData)
1081    }
1082}
1083
1084impl fmt::Debug for Iter<'_> {
1085    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1086        f.debug_struct("Iter")
1087            .field("conic_weight", &self.conic_weight())
1088            .field("is_close_line", &self.is_close_line())
1089            .field("is_closed_contour", &self.is_closed_contour())
1090            .finish()
1091    }
1092}
1093
1094impl Iter<'_> {
1095    /// Sets [`Iter`] to return elements of verb array, [`Point`] array, and conic weight in
1096    /// path. If `force_close` is `true`, [`Iter`] will add [`Verb::Line`] and [`Verb::Close`] after each
1097    /// open contour. path is not altered.
1098    ///
1099    /// * `path` - [`Path`] to iterate
1100    /// * `force_close` - `true` if open contours generate [`Verb::Close`]
1101    ///
1102    /// Returns: [`Iter`] of path
1103    ///
1104    /// example: <https://fiddle.skia.org/c/@Path_Iter_const_SkPath>
1105    pub fn new(path: &Path, force_close: bool) -> Self {
1106        Self(
1107            unsafe { SkPath_Iter::new1(path.native(), force_close) },
1108            PhantomData,
1109        )
1110    }
1111
1112    /// Sets [`Iter`] to return elements of verb array, [`Point`] array, and conic weight in
1113    /// path. If `force_close` is `true`, [`Iter`] will add [`Verb::Line`] and [`Verb::Close`] after each
1114    /// open contour. path is not altered.
1115    ///
1116    /// * `path` - [`Path`] to iterate
1117    /// * `force_close` - `true` if open contours generate [`Verb::Close`]
1118    ///
1119    /// example: <https://fiddle.skia.org/c/@Path_Iter_setPath>
1120    pub fn set_path(&mut self, path: &Path, force_close: bool) {
1121        unsafe {
1122            self.0.setPath(path.native(), force_close);
1123        }
1124    }
1125
1126    /// Returns conic weight if `next()` returned [`Verb::Conic`].
1127    ///
1128    /// If `next()` has not been called, or `next()` did not return [`Verb::Conic`],
1129    /// result is `None`.
1130    ///
1131    /// Returns: conic weight for conic [`Point`] returned by `next()`
1132    pub fn conic_weight(&self) -> Option<scalar> {
1133        #[allow(clippy::map_clone)]
1134        self.native()
1135            .fConicWeights
1136            .into_non_null()
1137            .map(|p| unsafe { *p.as_ref() })
1138    }
1139
1140    /// Returns `true` if last [`Verb::Line`] returned by `next()` was generated
1141    /// by [`Verb::Close`]. When `true`, the end point returned by `next()` is
1142    /// also the start point of contour.
1143    ///
1144    /// If `next()` has not been called, or `next()` did not return [`Verb::Line`],
1145    /// result is undefined.
1146    ///
1147    /// Returns: `true` if last [`Verb::Line`] was generated by [`Verb::Close`]
1148    pub fn is_close_line(&self) -> bool {
1149        unsafe { sb::C_SkPath_Iter_isCloseLine(self.native()) }
1150    }
1151
1152    /// Returns `true` if subsequent calls to `next()` return [`Verb::Close`] before returning
1153    /// [`Verb::Move`]. if `true`, contour [`Iter`] is processing may end with [`Verb::Close`], or
1154    /// [`Iter`] may have been initialized with force close set to `true`.
1155    ///
1156    /// Returns: `true` if contour is closed
1157    ///
1158    /// example: <https://fiddle.skia.org/c/@Path_Iter_isClosedContour>
1159    pub fn is_closed_contour(&self) -> bool {
1160        unsafe { self.native().isClosedContour() }
1161    }
1162}
1163
1164impl Iterator for Iter<'_> {
1165    type Item = (Verb, Vec<Point>);
1166
1167    /// Returns next [`Verb`] in verb array, and advances [`Iter`].
1168    /// When verb array is exhausted, returns [`Verb::Done`].
1169    ///
1170    /// Zero to four [`Point`] are stored in pts, depending on the returned [`Verb`].
1171    ///
1172    /// * `pts` - storage for [`Point`] data describing returned [`Verb`]
1173    ///
1174    /// Returns: next [`Verb`] from verb array
1175    ///
1176    /// example: <https://fiddle.skia.org/c/@Path_RawIter_next>
1177    fn next(&mut self) -> Option<Self::Item> {
1178        let mut points = [Point::default(); Verb::MAX_POINTS];
1179        let verb = unsafe { self.native_mut().next(points.native_mut().as_mut_ptr()) };
1180        if verb != Verb::Done {
1181            Some((verb, points[0..verb.points()].into()))
1182        } else {
1183            None
1184        }
1185    }
1186}
1187
1188#[repr(transparent)]
1189#[deprecated(
1190    since = "0.30.0",
1191    note = "User Iter instead, RawIter will soon be removed."
1192)]
1193pub struct RawIter<'a>(SkPath_RawIter, PhantomData<&'a Handle<SkPath>>);
1194
1195#[allow(deprecated)]
1196impl NativeAccess for RawIter<'_> {
1197    type Native = SkPath_RawIter;
1198
1199    fn native(&self) -> &SkPath_RawIter {
1200        &self.0
1201    }
1202    fn native_mut(&mut self) -> &mut SkPath_RawIter {
1203        &mut self.0
1204    }
1205}
1206
1207#[allow(deprecated)]
1208impl Drop for RawIter<'_> {
1209    fn drop(&mut self) {
1210        unsafe { sb::C_SkPath_RawIter_destruct(&mut self.0) }
1211    }
1212}
1213
1214#[allow(deprecated)]
1215impl Default for RawIter<'_> {
1216    fn default() -> Self {
1217        RawIter(
1218            construct(|ri| unsafe { sb::C_SkPath_RawIter_Construct(ri) }),
1219            PhantomData,
1220        )
1221    }
1222}
1223
1224#[allow(deprecated)]
1225impl RawIter<'_> {
1226    pub fn new(path: &Path) -> RawIter {
1227        RawIter::default().set_path(path)
1228    }
1229
1230    pub fn set_path(mut self, path: &Path) -> RawIter {
1231        unsafe { self.native_mut().setPath(path.native()) }
1232        let r = RawIter(self.0, PhantomData);
1233        forget(self);
1234        r
1235    }
1236
1237    pub fn peek(&self) -> Verb {
1238        unsafe { sb::C_SkPath_RawIter_peek(self.native()) }
1239    }
1240
1241    pub fn conic_weight(&self) -> scalar {
1242        self.native().fConicWeight
1243    }
1244}
1245
1246#[allow(deprecated)]
1247impl Iterator for RawIter<'_> {
1248    type Item = (Verb, Vec<Point>);
1249
1250    fn next(&mut self) -> Option<Self::Item> {
1251        let mut points = [Point::default(); Verb::MAX_POINTS];
1252
1253        let verb = unsafe { self.native_mut().next(points.native_mut().as_mut_ptr()) };
1254        (verb != Verb::Done).then(|| (verb, points[0..verb.points()].into()))
1255    }
1256}
1257
1258impl Path {
1259    /// Returns `true` if the point is contained by [`Path`], taking into
1260    /// account [`PathFillType`].
1261    ///
1262    /// * `point` - the point to test
1263    ///
1264    /// Returns: `true` if [`Point`] is in [`Path`]
1265    ///
1266    /// example: <https://fiddle.skia.org/c/@Path_contains>
1267    pub fn contains(&self, point: impl Into<Point>) -> bool {
1268        let point = point.into();
1269        unsafe { self.native().contains(point.into_native()) }
1270    }
1271
1272    /// Writes text representation of [`Path`] to [`Data`].
1273    /// Set `dump_as_hex` `true` to generate exact binary representations
1274    /// of floating point numbers used in [`Point`] array and conic weights.
1275    ///
1276    /// * `dump_as_hex` - `true` if scalar values are written as hexadecimal
1277    ///
1278    /// example: <https://fiddle.skia.org/c/@Path_dump>
1279    pub fn dump_as_data(&self, dump_as_hex: bool) -> Data {
1280        let mut stream = DynamicMemoryWStream::new();
1281        unsafe {
1282            self.native()
1283                .dump(stream.native_mut().base_mut(), dump_as_hex);
1284        }
1285        stream.detach_as_data()
1286    }
1287
1288    /// See [`Path::dump_as_data()`]
1289    pub fn dump(&self) {
1290        unsafe { self.native().dump(ptr::null_mut(), false) }
1291    }
1292
1293    /// See [`Path::dump_as_data()`]
1294    pub fn dump_hex(&self) {
1295        unsafe { self.native().dump(ptr::null_mut(), true) }
1296    }
1297
1298    // TODO: writeToMemory()?
1299
1300    /// Writes [`Path`] to buffer, returning the buffer written to, wrapped in [`Data`].
1301    ///
1302    /// `serialize()` writes [`PathFillType`], verb array, [`Point`] array, conic weight, and
1303    /// additionally writes computed information like convexity and bounds.
1304    ///
1305    /// `serialize()` should only be used in concert with `read_from_memory`().
1306    /// The format used for [`Path`] in memory is not guaranteed.
1307    ///
1308    /// Returns: [`Path`] data wrapped in [`Data`] buffer
1309    ///
1310    /// example: <https://fiddle.skia.org/c/@Path_serialize>
1311    pub fn serialize(&self) -> Data {
1312        Data::from_ptr(unsafe { sb::C_SkPath_serialize(self.native()) }).unwrap()
1313    }
1314
1315    // TODO: ReadFromMemory
1316
1317    pub fn deserialize(data: &Data) -> Option<Path> {
1318        let mut path = Path::default();
1319        let bytes = data.as_bytes();
1320        unsafe { sb::C_SkPath_ReadFromMemory(path.native_mut(), bytes.as_ptr() as _, bytes.len()) }
1321            .then_some(path)
1322    }
1323
1324    /// (See skbug.com/40032862)
1325    /// Returns a non-zero, globally unique value. A different value is returned
1326    /// if verb array, [`Point`] array, or conic weight changes.
1327    ///
1328    /// Setting [`PathFillType`] does not change generation identifier.
1329    ///
1330    /// Each time the path is modified, a different generation identifier will be returned.
1331    /// [`PathFillType`] does affect generation identifier on Android framework.
1332    ///
1333    /// Returns: non-zero, globally unique value
1334    ///
1335    /// example: <https://fiddle.skia.org/c/@Path_getGenerationID>
1336    pub fn generation_id(&self) -> u32 {
1337        unsafe { self.native().getGenerationID() }
1338    }
1339
1340    /// Returns if [`Path`] data is consistent. Corrupt [`Path`] data is detected if
1341    /// internal values are out of range or internal storage does not match
1342    /// array dimensions.
1343    ///
1344    /// Returns: `true` if [`Path`] data is consistent
1345    pub fn is_valid(&self) -> bool {
1346        unsafe { self.native().isValid() }
1347    }
1348}
1349
1350#[cfg(test)]
1351mod tests {
1352    use super::*;
1353
1354    #[test]
1355    fn test_count_points() {
1356        let p = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1357        let points_count = p.count_points();
1358        assert_eq!(points_count, 4);
1359    }
1360
1361    #[test]
1362    fn test_fill_type() {
1363        let mut p = Path::default();
1364        assert_eq!(p.fill_type(), PathFillType::Winding);
1365        p.set_fill_type(PathFillType::EvenOdd);
1366        assert_eq!(p.fill_type(), PathFillType::EvenOdd);
1367        assert!(!p.is_inverse_fill_type());
1368        p.toggle_inverse_fill_type();
1369        assert_eq!(p.fill_type(), PathFillType::InverseEvenOdd);
1370        assert!(p.is_inverse_fill_type());
1371    }
1372
1373    #[test]
1374    fn test_is_volatile() {
1375        let mut p = Path::default();
1376        assert!(!p.is_volatile());
1377        p.set_is_volatile(true);
1378        assert!(p.is_volatile());
1379    }
1380
1381    #[test]
1382    fn test_path_rect() {
1383        let r = Rect::new(0.0, 0.0, 100.0, 100.0);
1384        let path = Path::rect(r, None);
1385        assert_eq!(*path.bounds(), r);
1386    }
1387
1388    #[test]
1389    fn test_points_verbs_conic_weights() {
1390        let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1391
1392        // Test points()
1393        let points = path.points();
1394        assert_eq!(points.len(), 4);
1395
1396        // Test verbs()
1397        let verbs = path.verbs();
1398        assert_eq!(verbs.len(), 5); // Move + 4 Lines + Close
1399
1400        // Test conic_weights()
1401        let weights = path.conic_weights();
1402        assert_eq!(weights.len(), 0); // Rectangle has no conics
1403    }
1404
1405    #[test]
1406    fn test_with_offset() {
1407        let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1408        let offset_path = path.with_offset((5.0, 5.0));
1409
1410        assert_eq!(*offset_path.bounds(), Rect::new(5.0, 5.0, 15.0, 15.0));
1411    }
1412
1413    #[test]
1414    fn test_with_transform() {
1415        let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1416        let matrix = Matrix::scale((2.0, 2.0));
1417        let transformed = path.with_transform(&matrix);
1418
1419        assert_eq!(*transformed.bounds(), Rect::new(0.0, 0.0, 20.0, 20.0));
1420    }
1421
1422    #[test]
1423    fn test_try_make_transform() {
1424        let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1425
1426        // Test with finite transform
1427        let matrix = Matrix::scale((2.0, 2.0));
1428        let result = path.try_make_transform(&matrix);
1429        assert!(result.is_some());
1430        let transformed = result.unwrap();
1431        assert_eq!(*transformed.bounds(), Rect::new(0.0, 0.0, 20.0, 20.0));
1432
1433        // Test with extreme scale that might produce non-finite values
1434        let extreme_matrix = Matrix::scale((f32::MAX, f32::MAX));
1435        let result = path.try_make_transform(&extreme_matrix);
1436        // The result depends on whether the transform produces finite values
1437        // This test documents the behavior
1438        if let Some(transformed) = result {
1439            assert!(transformed.is_finite());
1440        }
1441    }
1442
1443    #[test]
1444    fn test_try_make_offset() {
1445        let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1446
1447        // Test with finite offset
1448        let result = path.try_make_offset((5.0, 5.0));
1449        assert!(result.is_some());
1450        let offset_path = result.unwrap();
1451        assert_eq!(*offset_path.bounds(), Rect::new(5.0, 5.0, 15.0, 15.0));
1452    }
1453
1454    #[test]
1455    fn test_try_make_scale() {
1456        let path = Path::rect(Rect::new(0.0, 0.0, 10.0, 10.0), None);
1457
1458        // Test with finite scale
1459        let result = path.try_make_scale((3.0, 3.0));
1460        assert!(result.is_some());
1461        let scaled = result.unwrap();
1462        assert_eq!(*scaled.bounds(), Rect::new(0.0, 0.0, 30.0, 30.0));
1463    }
1464
1465    #[test]
1466    fn test_serialize_deserialize() {
1467        let path = Path::rect(Rect::new(10.0, 10.0, 20.0, 20.0), None);
1468
1469        let data = path.serialize();
1470        let deserialized = Path::deserialize(&data).unwrap();
1471
1472        assert_eq!(path, deserialized);
1473    }
1474}