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}