skia_safe/core/
path_builder.rs

1use std::fmt;
2
3use crate::{
4    path, prelude::*, scalar, Matrix, Path, PathDirection, PathFillType, PathVerb, Point, RRect,
5    Rect, Vector,
6};
7use skia_bindings::{self as sb, SkPathBuilder, SkPath_AddPathMode};
8
9pub use sb::SkPathBuilder_ArcSize as ArcSize;
10variant_name!(ArcSize::Large);
11
12// PathBuilder can't be a Handle<>, because SkPathBuilder contains several STArrays with interior
13// pointers.
14//
15// See <https://github.com/rust-skia/rust-skia/pull/1195>.
16pub type PathBuilder = RefHandle<SkPathBuilder>;
17unsafe_send_sync!(PathBuilder);
18
19impl NativeDrop for SkPathBuilder {
20    fn drop(&mut self) {
21        unsafe { sb::C_SkPathBuilder_delete(self) }
22    }
23}
24
25impl NativePartialEq for SkPathBuilder {
26    fn eq(&self, rhs: &Self) -> bool {
27        unsafe { sb::C_SkPathBuilder_equals(self, rhs) }
28    }
29}
30
31impl Default for PathBuilder {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl Clone for PathBuilder {
38    fn clone(&self) -> Self {
39        Self::from_ptr(unsafe { sb::C_SkPathBuilder_clone(self.native()) }).unwrap()
40    }
41}
42
43impl fmt::Debug for PathBuilder {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.debug_struct("PathBuilder")
46            .field("fill_type", &self.fill_type())
47            .finish()
48    }
49}
50
51impl From<PathBuilder> for Path {
52    fn from(mut value: PathBuilder) -> Self {
53        value.detach()
54    }
55}
56
57impl PathBuilder {
58    /// Constructs an empty [`PathBuilder`]. By default, [`PathBuilder`] has no verbs, no [`Point`], and
59    /// no weights. [`PathFillType`] is set to [`PathFillType::Winding`].
60    ///
61    /// # Returns
62    /// empty [`PathBuilder`]
63    pub fn new() -> Self {
64        Self::from_ptr(unsafe { sb::C_SkPathBuilder_new() }).unwrap()
65    }
66
67    /// Constructs an empty [`PathBuilder`] with the given [`PathFillType`]. By default, [`PathBuilder`] has no
68    /// verbs, no [`Point`], and no weights.
69    ///
70    /// - `fill_type`: [`PathFillType`] to set on the [`PathBuilder`].
71    ///
72    /// # Returns
73    /// empty [`PathBuilder`]
74    pub fn new_with_fill_type(fill_type: PathFillType) -> Self {
75        Self::from_ptr(unsafe { sb::C_SkPathBuilder_newWithFillType(fill_type) }).unwrap()
76    }
77
78    /// Constructs a [`PathBuilder`] that is a copy of an existing [`Path`].
79    /// Copies the [`PathFillType`] and replays all of the verbs from the [`Path`] into the [`PathBuilder`].
80    ///
81    /// - `path`: [`Path`] to copy
82    ///
83    /// # Returns
84    /// [`PathBuilder`]
85    pub fn new_path(path: &Path) -> Self {
86        Self::from_ptr(unsafe { sb::C_SkPathBuilder_newFromPath(path.native()) }).unwrap()
87    }
88
89    /// Returns [`PathFillType`], the rule used to fill [`Path`].
90    ///
91    /// # Returns
92    /// current [`PathFillType`] setting
93    pub fn fill_type(&self) -> PathFillType {
94        self.native().fFillType
95    }
96
97    /// Returns minimum and maximum axes values of [`Point`] array.
98    /// Returns `None` if [`PathBuilder`] contains no points.
99    ///
100    /// [`Rect`] returned includes all [`Point`] added to [`PathBuilder`], including [`Point`] associated
101    /// with [`PathVerb::Move`] that define empty contours.
102    ///
103    /// If any of the points are non-finite, returns `None`.
104    ///
105    /// # Returns
106    /// Bounds of all [`Point`] in [`Point`] array, or `None`.
107    pub fn compute_finite_bounds(&self) -> Option<Rect> {
108        let mut rect = Rect::default();
109        unsafe { sb::C_SkPathBuilder_computeFiniteBounds(self.native(), rect.native_mut()) }
110            .then_some(rect)
111    }
112
113    /// Like [`Self::compute_finite_bounds()`] but returns a 'tight' bounds, meaning when there are curve
114    /// segments, this computes the X/Y limits of the curve itself, not the curve's control
115    /// point(s). For a polygon, this returns the same as [`Self::compute_finite_bounds()`].
116    pub fn compute_tight_bounds(&self) -> Option<Rect> {
117        let mut rect = Rect::default();
118        unsafe { sb::C_SkPathBuilder_computeTightBounds(self.native(), rect.native_mut()) }
119            .then_some(rect)
120    }
121
122    /// Returns minimum and maximum axes values of [`Point`] array.
123    ///
124    /// # Returns
125    /// Bounds of all [`Point`] in [`Point`] array, or an empty [`Rect`] if the bounds are non-finite.
126    ///
127    /// # Deprecated
128    /// Use [`Self::compute_finite_bounds()`] instead, which returns `None` when the bounds are non-finite.
129    #[deprecated(since = "0.91.0", note = "Use compute_finite_bounds() instead")]
130    pub fn compute_bounds(&self) -> Rect {
131        self.compute_finite_bounds().unwrap_or_else(Rect::new_empty)
132    }
133
134    /// Returns a [`Path`] representing the current state of the [`PathBuilder`]. The builder is
135    /// unchanged after returning the path.
136    ///
137    /// # Returns
138    /// [`Path`] representing the current state of the builder.
139    pub fn snapshot(&self) -> Path {
140        self.snapshot_and_transform(None)
141    }
142
143    /// Returns a [`Path`] representing the current state of the [`PathBuilder`]. The builder is
144    /// unchanged after returning the path.
145    ///
146    /// - `mx`: if present, applied to the points after they are copied into the resulting path.
147    ///
148    /// # Returns
149    /// [`Path`] representing the current state of the builder.
150    pub fn snapshot_and_transform<'m>(&self, mx: impl Into<Option<&'m Matrix>>) -> Path {
151        let mut p = Path::default();
152        unsafe {
153            sb::C_SkPathBuilder_snapshot(
154                self.native(),
155                mx.into().native_ptr_or_null(),
156                p.native_mut(),
157            )
158        };
159        p
160    }
161
162    /// Returns a [`Path`] representing the current state of the [`PathBuilder`]. The builder is
163    /// reset to empty after returning the path.
164    ///
165    /// # Returns
166    /// [`Path`] representing the current state of the builder.
167    pub fn detach(&mut self) -> Path {
168        self.detach_and_transform(None)
169    }
170
171    /// Returns a [`Path`] representing the current state of the [`PathBuilder`]. The builder is
172    /// reset to empty after returning the path.
173    ///
174    /// - `mx`: if present, applied to the points after they are copied into the resulting path.
175    ///
176    /// # Returns
177    /// [`Path`] representing the current state of the builder.
178    pub fn detach_and_transform<'m>(&mut self, mx: impl Into<Option<&'m Matrix>>) -> Path {
179        let mut p = Path::default();
180        unsafe {
181            sb::C_SkPathBuilder_detach(
182                self.native_mut(),
183                mx.into().native_ptr_or_null(),
184                p.native_mut(),
185            )
186        };
187        p
188    }
189
190    /// Sets [`PathFillType`], the rule used to fill [`Path`]. While there is no
191    /// check that `ft` is legal, values outside of [`PathFillType`] are not supported.
192    ///
193    /// - `ft`: [`PathFillType`] to be used by [`Path`]s generated from this builder.
194    ///
195    /// # Returns
196    /// reference to [`PathBuilder`]
197    pub fn set_fill_type(&mut self, ft: PathFillType) -> &mut Self {
198        self.native_mut().fFillType = ft;
199        self
200    }
201
202    /// Specifies whether [`Path`] is volatile; whether it will be altered or discarded
203    /// by the caller after it is drawn. [`Path`] by default have volatile set false, allowing
204    /// Skia to attach a cache of data which speeds repeated drawing.
205    ///
206    /// Mark temporary paths, discarded or modified after use, as volatile
207    /// to inform Skia that the path need not be cached.
208    ///
209    /// Mark animating [`Path`] volatile to improve performance.
210    /// Mark unchanging [`Path`] non-volatile to improve repeated rendering.
211    ///
212    /// raster surface [`Path`] draws are affected by volatile for some shadows.
213    /// GPU surface [`Path`] draws are affected by volatile for some shadows and concave geometries.
214    ///
215    /// - `is_volatile`: true if caller will alter [`Path`] after drawing
216    ///
217    /// # Returns
218    /// reference to [`PathBuilder`]
219    pub fn set_is_volatile(&mut self, is_volatile: bool) -> &mut Self {
220        self.native_mut().fIsVolatile = is_volatile;
221        self
222    }
223
224    /// Sets [`PathBuilder`] to its initial state.
225    /// Removes verb array, [`Point`] array, and weights, and sets [`PathFillType`] to [`PathFillType::Winding`].
226    /// Internal storage associated with [`PathBuilder`] is preserved.
227    ///
228    /// # Returns
229    /// reference to [`PathBuilder`]
230    pub fn reset(&mut self) -> &mut Self {
231        unsafe {
232            self.native_mut().reset();
233        }
234        self
235    }
236
237    /// Specifies the beginning of contour. If the previous verb was a "move" verb,
238    /// then this just replaces the point value of that move, otherwise it appends a new
239    /// "move" verb to the builder using the point.
240    ///
241    /// Thus, each contour can only have 1 move verb in it (the last one specified).
242    pub fn move_to(&mut self, pt: impl Into<Point>) -> &mut Self {
243        unsafe {
244            self.native_mut().moveTo(pt.into().into_native());
245        }
246        self
247    }
248
249    /// Adds line from last point to [`Point`] p. If [`PathBuilder`] is empty, or last [`PathVerb`] is
250    /// [`PathVerb::Close`], last point is set to (0, 0) before adding line.
251    ///
252    /// `line_to()` first appends [`PathVerb::Move`] to verb array and (0, 0) to [`Point`] array, if needed.
253    /// `line_to()` then appends [`PathVerb::Line`] to verb array and [`Point`] p to [`Point`] array.
254    ///
255    /// - `pt`: end [`Point`] of added line
256    ///
257    /// # Returns
258    /// reference to [`PathBuilder`]
259    pub fn line_to(&mut self, pt: impl Into<Point>) -> &mut Self {
260        unsafe {
261            self.native_mut().lineTo(pt.into().into_native());
262        }
263        self
264    }
265
266    /// Adds quad from last point towards [`Point`] p1, to [`Point`] p2.
267    /// If [`PathBuilder`] is empty, or last [`PathVerb`] is [`PathVerb::Close`], last point is set to (0, 0)
268    /// before adding quad.
269    ///
270    /// Appends [`PathVerb::Move`] to verb array and (0, 0) to [`Point`] array, if needed;
271    /// then appends [`PathVerb::Quad`] to verb array; and [`Point`] p1, p2
272    /// to [`Point`] array.
273    ///
274    /// - `p1`: control [`Point`] of added quad
275    /// - `p2`: end [`Point`] of added quad
276    ///
277    /// # Returns
278    /// reference to [`PathBuilder`]
279    pub fn quad_to(&mut self, p1: impl Into<Point>, p2: impl Into<Point>) -> &mut Self {
280        unsafe {
281            self.native_mut()
282                .quadTo(p1.into().into_native(), p2.into().into_native());
283        }
284        self
285    }
286
287    /// Adds conic from last point towards pt1, to pt2, weighted by w.
288    /// If [`PathBuilder`] is empty, or last [`PathVerb`] is [`PathVerb::Close`], last point is set to (0, 0)
289    /// before adding conic.
290    ///
291    /// Appends [`PathVerb::Move`] to verb array and (0, 0) to [`Point`] array, if needed.
292    ///
293    /// If w is finite and not one, appends [`PathVerb::Conic`] to verb array;
294    /// and pt1, pt2 to [`Point`] array; and w to conic weights.
295    ///
296    /// If w is one, appends [`PathVerb::Quad`] to verb array, and
297    /// pt1, pt2 to [`Point`] array.
298    ///
299    /// If w is not finite, appends [`PathVerb::Line`] twice to verb array, and
300    /// pt1, pt2 to [`Point`] array.
301    ///
302    /// - `pt1`: control [`Point`] of conic
303    /// - `pt2`: end [`Point`] of conic
304    /// - `w`: weight of added conic
305    ///
306    /// # Returns
307    /// reference to [`PathBuilder`]
308    pub fn conic_to(&mut self, p1: impl Into<Point>, p2: impl Into<Point>, w: scalar) -> &mut Self {
309        unsafe {
310            self.native_mut()
311                .conicTo(p1.into().into_native(), p2.into().into_native(), w);
312        }
313        self
314    }
315
316    /// Adds cubic from last point towards [`Point`] p1, then towards [`Point`] p2, ending at
317    /// [`Point`] p3. If [`PathBuilder`] is empty, or last [`PathVerb`] is [`PathVerb::Close`], last point is
318    /// set to (0, 0) before adding cubic.
319    ///
320    /// Appends [`PathVerb::Move`] to verb array and (0, 0) to [`Point`] array, if needed;
321    /// then appends [`PathVerb::Cubic`] to verb array; and [`Point`] p1, p2, p3
322    /// to [`Point`] array.
323    ///
324    /// - `p1`: first control [`Point`] of cubic
325    /// - `p2`: second control [`Point`] of cubic
326    /// - `p3`: end [`Point`] of cubic
327    ///
328    /// # Returns
329    /// reference to [`PathBuilder`]
330    pub fn cubic_to(
331        &mut self,
332        p1: impl Into<Point>,
333        p2: impl Into<Point>,
334        p3: impl Into<Point>,
335    ) -> &mut Self {
336        unsafe {
337            self.native_mut().cubicTo(
338                p1.into().into_native(),
339                p2.into().into_native(),
340                p3.into().into_native(),
341            )
342        };
343        self
344    }
345
346    /// Appends [`PathVerb::Close`] to [`PathBuilder`]. A closed contour connects the first and last [`Point`]
347    /// with line, forming a continuous loop. Open and closed contour draw the same
348    /// with [`crate::PaintStyle::Fill`]. With [`crate::PaintStyle::Stroke`], open contour draws
349    /// [`crate::PaintCap`] at contour start and end; closed contour draws
350    /// [`crate::PaintJoin`] at contour start and end.
351    ///
352    /// `close()` has no effect if [`PathBuilder`] is empty or last [`PathVerb`] is [`PathVerb::Close`].
353    ///
354    /// # Returns
355    /// reference to [`PathBuilder`]
356    pub fn close(&mut self) -> &mut Self {
357        unsafe {
358            self.native_mut().close();
359        }
360        self
361    }
362
363    /// Append a series of `line_to(...)`
364    ///
365    /// - `points`: array of [`Point`]
366    ///
367    /// # Returns
368    /// reference to [`PathBuilder`]
369    pub fn polyline_to(&mut self, points: &[Point]) -> &mut Self {
370        unsafe {
371            sb::C_SkPathBuilder_polylineTo(
372                self.native_mut(),
373                points.native().as_ptr(),
374                points.len(),
375            );
376        }
377        self
378    }
379
380    /// Adds beginning of contour relative to last point.
381    /// If [`PathBuilder`] is empty, starts contour at (dx, dy).
382    /// Otherwise, start contour at last point offset by (dx, dy).
383    /// Function name stands for "relative move to".
384    ///
385    /// - `pt`: vector offset from last point to contour start
386    ///
387    /// # Returns
388    /// reference to [`PathBuilder`]
389    pub fn r_move_to(&mut self, pt: impl Into<Point>) -> &mut Self {
390        unsafe {
391            self.native_mut().rMoveTo(pt.into().into_native());
392        }
393        self
394    }
395
396    /// Adds line from last point to vector given by pt. If [`PathBuilder`] is empty, or last
397    /// [`PathVerb`] is [`PathVerb::Close`], last point is set to (0, 0) before adding line.
398    ///
399    /// Appends [`PathVerb::Move`] to verb array and (0, 0) to [`Point`] array, if needed;
400    /// then appends [`PathVerb::Line`] to verb array and line end to [`Point`] array.
401    /// Line end is last point plus vector given by pt.
402    /// Function name stands for "relative line to".
403    ///
404    /// - `pt`: vector offset from last point to line end
405    ///
406    /// # Returns
407    /// reference to [`PathBuilder`]
408    pub fn r_line_to(&mut self, pt: impl Into<Point>) -> &mut Self {
409        unsafe {
410            self.native_mut().rLineTo(pt.into().into_native());
411        }
412        self
413    }
414
415    /// Adds quad from last point towards vector pt1, to vector pt2.
416    /// If [`PathBuilder`] is empty, or last [`PathVerb`]
417    /// is [`PathVerb::Close`], last point is set to (0, 0) before adding quad.
418    ///
419    /// Appends [`PathVerb::Move`] to verb array and (0, 0) to [`Point`] array,
420    /// if needed; then appends [`PathVerb::Quad`] to verb array; and appends quad
421    /// control and quad end to [`Point`] array.
422    /// Quad control is last point plus vector pt1.
423    /// Quad end is last point plus vector pt2.
424    /// Function name stands for "relative quad to".
425    ///
426    /// - `pt1`: offset vector from last point to quad control
427    /// - `pt2`: offset vector from last point to quad end
428    ///
429    /// # Returns
430    /// reference to [`PathBuilder`]
431    pub fn r_quad_to(&mut self, pt1: impl Into<Point>, pt2: impl Into<Point>) -> &mut Self {
432        unsafe {
433            self.native_mut()
434                .rQuadTo(pt1.into().into_native(), pt2.into().into_native());
435        }
436        self
437    }
438
439    /// Adds conic from last point towards vector p1, to vector p2,
440    /// weighted by w. If [`PathBuilder`] is empty, or last [`PathVerb`]
441    /// is [`PathVerb::Close`], last point is set to (0, 0) before adding conic.
442    ///
443    /// Appends [`PathVerb::Move`] to verb array and (0, 0) to [`Point`] array,
444    /// if needed.
445    ///
446    /// If w is finite and not one, next appends [`PathVerb::Conic`] to verb array,
447    /// and w is recorded as conic weight; otherwise, if w is one, appends
448    /// [`PathVerb::Quad`] to verb array; or if w is not finite, appends [`PathVerb::Line`]
449    /// twice to verb array.
450    ///
451    /// In all cases appends [`Point`] control and end to [`Point`] array.
452    /// control is last point plus vector p1.
453    /// end is last point plus vector p2.
454    ///
455    /// Function name stands for "relative conic to".
456    ///
457    /// - `pt1`: offset vector from last point to conic control
458    /// - `pt2`: offset vector from last point to conic end
459    /// - `w`: weight of added conic
460    ///
461    /// # Returns
462    /// reference to [`PathBuilder`]
463    pub fn r_conic_to(
464        &mut self,
465        pt1: impl Into<Point>,
466        pt2: impl Into<Point>,
467        w: scalar,
468    ) -> &mut Self {
469        unsafe {
470            self.native_mut()
471                .rConicTo(pt1.into().into_native(), pt2.into().into_native(), w);
472        }
473        self
474    }
475
476    /// Adds cubic from last point towards vector pt1, then towards
477    /// vector pt2, to vector pt3.
478    /// If [`PathBuilder`] is empty, or last [`PathVerb`]
479    /// is [`PathVerb::Close`], last point is set to (0, 0) before adding cubic.
480    ///
481    /// Appends [`PathVerb::Move`] to verb array and (0, 0) to [`Point`] array,
482    /// if needed; then appends [`PathVerb::Cubic`] to verb array; and appends cubic
483    /// control and cubic end to [`Point`] array.
484    /// Cubic control is last point plus vector (dx1, dy1).
485    /// Cubic end is last point plus vector (dx2, dy2).
486    /// Function name stands for "relative cubic to".
487    ///
488    /// - `pt1`: offset vector from last point to first cubic control
489    /// - `pt2`: offset vector from last point to second cubic control
490    /// - `pt3`: offset vector from last point to cubic end
491    ///
492    /// # Returns
493    /// reference to [`PathBuilder`]
494    pub fn r_cubic_to(
495        &mut self,
496        pt1: impl Into<Point>,
497        pt2: impl Into<Point>,
498        pt3: impl Into<Point>,
499    ) -> &mut Self {
500        unsafe {
501            self.native_mut().rCubicTo(
502                pt1.into().into_native(),
503                pt2.into().into_native(),
504                pt3.into().into_native(),
505            );
506        }
507        self
508    }
509
510    /// Appends arc to [`PathBuilder`], relative to last [`Path`] [`Point`]. Arc is implemented by one or
511    /// more conic, weighted to describe part of oval with radii (rx, ry) rotated by
512    /// `x_axis_rotate` degrees. Arc curves from last [`PathBuilder`] [`Point`] to relative end [`Point`]:
513    /// (dx, dy), choosing one of four possible routes: clockwise or
514    /// counterclockwise, and smaller or larger. If [`PathBuilder`] is empty, the start arc [`Point`]
515    /// is (0, 0).
516    ///
517    /// Arc sweep is always less than 360 degrees. `arc_to()` appends line to end [`Point`]
518    /// if either radii are zero, or if last [`Path`] [`Point`] equals end [`Point`].
519    /// `arc_to()` scales radii (rx, ry) to fit last [`Path`] [`Point`] and end [`Point`] if both are
520    /// greater than zero but too small to describe an arc.
521    ///
522    /// `arc_to()` appends up to four conic curves.
523    /// `arc_to()` implements the functionality of svg arc, although SVG "sweep-flag" value is
524    /// opposite the integer value of sweep; SVG "sweep-flag" uses 1 for clockwise, while
525    /// [`PathDirection::CW`] cast to int is zero.
526    ///
527    /// - `r`: radii on axes before x-axis rotation
528    /// - `x_axis_rotate`: x-axis rotation in degrees; positive values are clockwise
529    /// - `large_arc`: chooses smaller or larger arc
530    /// - `sweep`: chooses clockwise or counterclockwise arc
531    /// - `dxdy`: offset end of arc from last [`Path`] point
532    ///
533    /// # Returns
534    /// reference to [`PathBuilder`]
535    pub fn r_arc_to(
536        &mut self,
537        r: impl Into<Vector>,
538        x_axis_rotate: scalar,
539        large_arc: ArcSize,
540        sweep: PathDirection,
541        dxdy: impl Into<Vector>,
542    ) -> &mut Self {
543        let r = r.into();
544        let d = dxdy.into();
545        unsafe {
546            self.native_mut().rArcTo(
547                r.into_native(),
548                x_axis_rotate,
549                large_arc,
550                sweep,
551                d.into_native(),
552            );
553        }
554        self
555    }
556
557    /// Appends arc to the builder. Arc added is part of ellipse
558    /// bounded by oval, from `start_angle` through `sweep_angle`. Both `start_angle` and
559    /// `sweep_angle` are measured in degrees, where zero degrees is aligned with the
560    /// positive x-axis, and positive sweeps extends arc clockwise.
561    ///
562    /// `arc_to()` adds line connecting the builder's last point to initial arc point if `force_move_to`
563    /// is false and the builder is not empty. Otherwise, added contour begins with first point
564    /// of arc. Angles greater than -360 and less than 360 are treated modulo 360.
565    ///
566    /// - `oval`: bounds of ellipse containing arc
567    /// - `start_angle_deg`: starting angle of arc in degrees
568    /// - `sweep_angle_deg`: sweep, in degrees. Positive is clockwise; treated modulo 360
569    /// - `force_move_to`: true to start a new contour with arc
570    ///
571    /// # Returns
572    /// reference to the builder
573    pub fn arc_to(
574        &mut self,
575        oval: impl AsRef<Rect>,
576        start_angle_deg: scalar,
577        sweep_angle_deg: scalar,
578        force_move_to: bool,
579    ) -> &mut Self {
580        unsafe {
581            self.native_mut().arcTo(
582                oval.as_ref().native(),
583                start_angle_deg,
584                sweep_angle_deg,
585                force_move_to,
586            );
587        }
588        self
589    }
590
591    /// Appends arc to [`Path`], after appending line if needed. Arc is implemented by conic
592    /// weighted to describe part of circle. Arc is contained by tangent from
593    /// last [`Path`] point to p1, and tangent from p1 to p2. Arc
594    /// is part of circle sized to radius, positioned so it touches both tangent lines.
595    ///
596    /// If last [`Path`] [`Point`] does not start arc, `arc_to()` appends connecting line to [`Path`].
597    /// The length of vector from p1 to p2 does not affect arc.
598    ///
599    /// Arc sweep is always less than 180 degrees. If radius is zero, or if
600    /// tangents are nearly parallel, `arc_to()` appends line from last [`Path`] [`Point`] to p1.
601    ///
602    /// `arc_to()` appends at most one line and one conic.
603    /// `arc_to()` implements the functionality of PostScript arct and HTML Canvas `arcTo`.
604    ///
605    /// - `p1`: [`Point`] common to pair of tangents
606    /// - `p2`: end of second tangent
607    /// - `radius`: distance from arc to circle center
608    ///
609    /// # Returns
610    /// reference to [`PathBuilder`]
611    pub fn arc_to_tangent(
612        &mut self,
613        p1: impl Into<Point>,
614        p2: impl Into<Point>,
615        radius: scalar,
616    ) -> &mut Self {
617        unsafe {
618            self.native_mut()
619                .arcTo1(p1.into().into_native(), p2.into().into_native(), radius);
620        }
621        self
622    }
623
624    /// Appends arc to [`Path`]. Arc is implemented by one or more conic weighted to describe
625    /// part of oval with radii (r.fX, r.fY) rotated by `x_axis_rotate` degrees. Arc curves
626    /// from last [`Path`] [`Point`] to (xy.fX, xy.fY), choosing one of four possible routes:
627    /// clockwise or counterclockwise,
628    /// and smaller or larger.
629    ///
630    /// Arc sweep is always less than 360 degrees. `arc_to_radius()` appends line to xy if either
631    /// radii are zero, or if last [`Path`] [`Point`] equals (xy.fX, xy.fY). `arc_to_radius()` scales radii r to
632    /// fit last [`Path`] [`Point`] and xy if both are greater than zero but too small to describe
633    /// an arc.
634    ///
635    /// `arc_to_radius()` appends up to four conic curves.
636    /// `arc_to_radius()` implements the functionality of SVG arc, although SVG sweep-flag value is
637    /// opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, while
638    /// [`PathDirection::CW`] cast to int is zero.
639    ///
640    /// - `r`: radii on axes before x-axis rotation
641    /// - `x_axis_rotate`: x-axis rotation in degrees; positive values are clockwise
642    /// - `large_arc`: chooses smaller or larger arc
643    /// - `sweep`: chooses clockwise or counterclockwise arc
644    /// - `xy`: end of arc
645    ///
646    /// # Returns
647    /// reference to [`PathBuilder`]
648    pub fn arc_to_radius(
649        &mut self,
650        r: impl Into<Point>,
651        x_axis_rotate: scalar,
652        large_arc: ArcSize,
653        sweep: PathDirection,
654        xy: impl Into<Point>,
655    ) -> &mut Self {
656        unsafe {
657            self.native_mut().arcTo2(
658                r.into().into_native(),
659                x_axis_rotate,
660                large_arc,
661                sweep,
662                xy.into().into_native(),
663            );
664        }
665        self
666    }
667
668    /// Appends arc to the builder, as the start of new contour. Arc added is part of ellipse
669    /// bounded by oval, from `start_angle` through `sweep_angle`. Both `start_angle` and
670    /// `sweep_angle` are measured in degrees, where zero degrees is aligned with the
671    /// positive x-axis, and positive sweeps extends arc clockwise.
672    ///
673    /// If `sweep_angle` <= -360, or `sweep_angle` >= 360; and `start_angle` modulo 90 is nearly
674    /// zero, append oval instead of arc. Otherwise, `sweep_angle` values are treated
675    /// modulo 360, and arc may or may not draw depending on numeric rounding.
676    ///
677    /// - `oval`: bounds of ellipse containing arc
678    /// - `start_angle_deg`: starting angle of arc in degrees
679    /// - `sweep_angle_deg`: sweep, in degrees. Positive is clockwise; treated modulo 360
680    ///
681    /// # Returns
682    /// reference to this builder
683    pub fn add_arc(
684        &mut self,
685        oval: impl AsRef<Rect>,
686        start_angle_deg: scalar,
687        sweep_angle_deg: scalar,
688    ) -> &mut Self {
689        unsafe {
690            self.native_mut()
691                .addArc(oval.as_ref().native(), start_angle_deg, sweep_angle_deg);
692        }
693        self
694    }
695
696    pub fn add_line(&mut self, a: impl Into<Point>, b: impl Into<Point>) -> &mut Self {
697        self.move_to(a).line_to(b)
698    }
699
700    /// Adds a new contour to the [`PathBuilder`], defined by the rect, and wound in the
701    /// specified direction. The verbs added to the path will be:
702    ///
703    /// [`PathVerb::Move`], [`PathVerb::Line`], [`PathVerb::Line`], [`PathVerb::Line`], [`PathVerb::Close`]
704    ///
705    /// start specifies which corner to begin the contour:
706    ///     0: upper-left  corner
707    ///     1: upper-right corner
708    ///     2: lower-right corner
709    ///     3: lower-left  corner
710    ///
711    /// This start point also acts as the implied beginning of the subsequent,
712    /// contour, if it does not have an explicit `move_to()`. e.g.
713    ///
714    ///     path.add_rect(...)
715    ///     // if we don't say move_to() here, we will use the rect's start point
716    ///     path.line_to(...)
717    ///
718    /// - `rect`: [`Rect`] to add as a closed contour
719    /// - `dir`: [`PathDirection`] to orient the new contour
720    /// - `start_index`: initial corner of [`Rect`] to add
721    ///
722    /// # Returns
723    /// reference to [`PathBuilder`]
724    pub fn add_rect(
725        &mut self,
726        rect: impl AsRef<Rect>,
727        dir: impl Into<Option<PathDirection>>,
728        start_index: impl Into<Option<usize>>,
729    ) -> &mut Self {
730        let dir = dir.into().unwrap_or_default();
731        let start_index = start_index.into().unwrap_or(0);
732        unsafe {
733            self.native_mut()
734                .addRect(rect.as_ref().native(), dir, start_index.try_into().unwrap());
735        }
736        self
737    }
738
739    /// Adds oval to [`PathBuilder`], appending [`PathVerb::Move`], four [`PathVerb::Conic`], and [`PathVerb::Close`].
740    /// Oval is upright ellipse bounded by [`Rect`] oval with radii equal to half oval width
741    /// and half oval height. Oval begins at (oval.right, oval.center_y()) and continues
742    /// clockwise if dir is [`PathDirection::CW`], counterclockwise if dir is [`PathDirection::CCW`].
743    ///
744    /// - `rect`: bounds of ellipse added
745    /// - `dir`: [`PathDirection`] to wind ellipse
746    /// - `start_index`: index of initial point of ellipse
747    ///
748    /// # Returns
749    /// reference to [`PathBuilder`]
750    pub fn add_oval(
751        &mut self,
752        rect: impl AsRef<Rect>,
753        dir: impl Into<Option<PathDirection>>,
754        start_index: impl Into<Option<usize>>,
755    ) -> &mut Self {
756        let dir = dir.into().unwrap_or_default();
757        // m86: default start index changed from 0 to 1
758        let start_index = start_index.into().unwrap_or(1);
759        unsafe {
760            self.native_mut()
761                .addOval(rect.as_ref().native(), dir, start_index.try_into().unwrap());
762        }
763        self
764    }
765
766    /// Appends [`RRect`] to [`PathBuilder`], creating a new closed contour. If dir is [`PathDirection::CW`],
767    /// [`RRect`] winds clockwise. If dir is [`PathDirection::CCW`], [`RRect`] winds counterclockwise.
768    ///
769    /// After appending, [`PathBuilder`] may be empty, or may contain: [`Rect`], oval, or [`RRect`].
770    ///
771    /// - `rect`: [`RRect`] to add
772    /// - `dir`: [`PathDirection`] to wind [`RRect`]
773    /// - `start_index`: index of initial point of [`RRect`]
774    ///
775    /// # Returns
776    /// reference to [`PathBuilder`]
777    pub fn add_rrect(
778        &mut self,
779        rect: impl AsRef<RRect>,
780        dir: impl Into<Option<PathDirection>>,
781        start_index: impl Into<Option<usize>>,
782    ) -> &mut Self {
783        let dir = dir.into().unwrap_or_default();
784        // m86: default start index changed from 0 to 6 or 7 depending on the path's direction.
785        let start_index =
786            start_index
787                .into()
788                .unwrap_or(if dir == PathDirection::CW { 6 } else { 7 });
789        unsafe {
790            self.native_mut().addRRect(
791                rect.as_ref().native(),
792                dir,
793                start_index.try_into().unwrap(),
794            );
795        }
796        self
797    }
798
799    /// Adds circle centered at (x, y) of size radius to [`PathBuilder`], appending [`PathVerb::Move`],
800    /// four [`PathVerb::Conic`], and [`PathVerb::Close`]. Circle begins at: (x + radius, y), continuing
801    /// clockwise if dir is [`PathDirection::CW`], and counterclockwise if dir is [`PathDirection::CCW`].
802    ///
803    /// Has no effect if radius is zero or negative.
804    ///
805    /// - `center`: center of circle
806    /// - `radius`: distance from center to edge
807    /// - `dir`: [`PathDirection`] to wind circle
808    ///
809    /// # Returns
810    /// reference to [`PathBuilder`]
811    pub fn add_circle(
812        &mut self,
813        center: impl Into<Point>,
814        radius: scalar,
815        dir: impl Into<Option<PathDirection>>,
816    ) -> &mut Self {
817        let center = center.into();
818        let dir = dir.into().unwrap_or_default();
819        unsafe {
820            self.native_mut().addCircle(center.x, center.y, radius, dir);
821        }
822        self
823    }
824
825    /// Adds contour created from line array, adding (pts.len() - 1) line segments.
826    /// Contour added starts at `pts[0]`, then adds a line for every additional [`Point`]
827    /// in pts array. If close is true, appends [`PathVerb::Close`] to [`Path`], connecting
828    /// `pts[count - 1]` and `pts[0]`.
829    ///
830    /// - `pts`: array of line sharing end and start [`Point`]
831    /// - `close`: true to add line connecting contour end and start
832    ///
833    /// # Returns
834    /// reference to [`PathBuilder`]
835    pub fn add_polygon(&mut self, pts: &[Point], close: bool) -> &mut Self {
836        unsafe {
837            sb::C_SkPathBuilder_addPolygon(
838                self.native_mut(),
839                pts.native().as_ptr(),
840                pts.len(),
841                close,
842            );
843        }
844        self
845    }
846
847    /// Appends src to [`PathBuilder`], offset by (dx, dy).
848    ///
849    /// If mode is [`path::AddPathMode::Append`], src verb array, [`Point`] array, and conic weights are
850    /// added unaltered. If mode is [`path::AddPathMode::Extend`], add line before appending
851    /// verbs, [`Point`], and conic weights.
852    ///
853    /// - `path`: [`Path`] verbs, [`Point`], and conic weights to add
854    ///
855    /// # Returns
856    /// reference to [`PathBuilder`]
857    pub fn add_path(&mut self, path: &Path) -> &mut Self {
858        unsafe {
859            self.native_mut()
860                .addPath(path.native(), 0., 0., SkPath_AddPathMode::Append)
861        };
862        self
863    }
864
865    /// Appends src to [`PathBuilder`], transformed by matrix. Transformed curves may have different
866    /// verbs, [`Point`], and conic weights.
867    ///
868    /// If mode is [`path::AddPathMode::Append`], src verb array, [`Point`] array, and conic weights are
869    /// added unaltered. If mode is [`path::AddPathMode::Extend`], add line before appending
870    /// verbs, [`Point`], and conic weights.
871    ///
872    /// - `src`: [`Path`] verbs, [`Point`], and conic weights to add
873    /// - `matrix`: transform applied to src
874    /// - `mode`: [`path::AddPathMode::Append`] or [`path::AddPathMode::Extend`]
875    pub fn add_path_with_transform(
876        &mut self,
877        src: &Path,
878        matrix: &Matrix,
879        mode: impl Into<Option<path::AddPathMode>>,
880    ) {
881        unsafe {
882            self.native_mut().addPath1(
883                src.native(),
884                matrix.native(),
885                mode.into().unwrap_or(path::AddPathMode::Append),
886            )
887        };
888    }
889
890    /// Grows [`PathBuilder`] verb array and [`Point`] array to contain additional space.
891    /// May improve performance and use less memory by
892    /// reducing the number and size of allocations when creating [`PathBuilder`].
893    ///
894    /// - `extra_pt_count`: number of additional [`Point`] to allocate
895    /// - `extra_verb_count`: number of additional verbs
896    /// - `extra_conic_count`: number of additional conic weights
897    pub fn inc_reserve(
898        &mut self,
899        extra_pt_count: usize,
900        extra_verb_count: usize,
901        extra_conic_count: usize,
902    ) {
903        unsafe {
904            self.native_mut().incReserve(
905                extra_pt_count.try_into().unwrap(),
906                extra_verb_count.try_into().unwrap(),
907                extra_conic_count.try_into().unwrap(),
908            )
909        }
910    }
911
912    /// Offsets [`Point`] array by (dx, dy).
913    ///
914    /// - `d`: offset added to [`Point`] array coordinates
915    ///
916    /// # Returns
917    /// reference to [`PathBuilder`]
918    pub fn offset(&mut self, d: impl Into<Vector>) -> &mut Self {
919        let d = d.into();
920        unsafe {
921            self.native_mut().offset(d.x, d.y);
922        }
923        self
924    }
925
926    /// Transforms verb array, [`Point`] array, and weight by matrix.
927    /// transform may change verbs and increase their number.
928    ///
929    /// - `matrix`: [`Matrix`] to apply to [`Path`]
930    ///
931    /// # Returns
932    /// reference to [`PathBuilder`]
933    pub fn transform(&mut self, matrix: &Matrix) -> &mut Self {
934        unsafe {
935            self.native_mut().transform(matrix.native());
936        }
937        self
938    }
939
940    /// Returns true if the builder is empty, or all of its points are finite.
941    pub fn is_finite(&self) -> bool {
942        unsafe { self.native().isFinite() }
943    }
944
945    /// Replaces [`PathFillType`] with its inverse. The inverse of [`PathFillType`] describes the area
946    /// unmodified by the original [`PathFillType`].
947    ///
948    /// # Returns
949    /// reference to [`PathBuilder`]
950    pub fn toggle_inverse_fill_type(&mut self) -> &mut Self {
951        let n = self.native_mut();
952        n.fFillType = n.fFillType.toggle_inverse();
953        self
954    }
955
956    /// Returns if [`Path`] is empty.
957    /// Empty [`PathBuilder`] may have [`PathFillType`] but has no [`Point`], [`PathVerb`], or conic weight.
958    /// [`PathBuilder::new()`] constructs empty [`PathBuilder`]; `reset()` and `rewind()` make [`Path`] empty.
959    ///
960    /// # Returns
961    /// true if the path contains no [`PathVerb`] array
962    pub fn is_empty(&self) -> bool {
963        unsafe { sb::C_SkPathBuilder_isEmpty(self.native()) }
964    }
965
966    /// Returns last point on [`PathBuilder`]. Returns `None` if [`Point`] array is empty.
967    ///
968    /// # Returns
969    /// last [`Point`] if [`Point`] array contains one or more [`Point`], otherwise `None`
970    pub fn get_last_pt(&self) -> Option<Point> {
971        let mut p = Point::default();
972        unsafe { sb::C_SkPathBuilder_getLastPt(self.native(), p.native_mut()) }.then_some(p)
973    }
974
975    /// Change the point at the specified index (see `count_points()`).
976    /// If index is out of range, the call does nothing.
977    ///
978    /// - `index`: which point to replace
979    /// - `p`: the new point value
980    pub fn set_point(&mut self, index: usize, p: impl Into<Point>) {
981        let p = p.into();
982        unsafe { sb::C_SkPathBuilder_setPoint(self.native_mut(), index, *p.native()) }
983    }
984
985    /// Sets the last point on the path. If [`Point`] array is empty, append [`PathVerb::Move`] to
986    /// verb array and append p to [`Point`] array.
987    ///
988    /// - `p`: last point
989    pub fn set_last_pt(&mut self, p: impl Into<Point>) {
990        let p = p.into();
991        unsafe { self.native_mut().setLastPt(p.x, p.y) };
992    }
993
994    /// Returns the number of points in [`PathBuilder`].
995    /// [`Point`] count is initially zero.
996    ///
997    /// # Returns
998    /// [`PathBuilder`] [`Point`] array length
999    pub fn count_points(&self) -> usize {
1000        unsafe { sb::C_SkPathBuilder_countPoints(self.native()) }
1001    }
1002
1003    /// Returns if [`PathFillType`] describes area outside [`Path`] geometry. The inverse fill area
1004    /// extends indefinitely.
1005    ///
1006    /// # Returns
1007    /// true if [`PathFillType`] is [`PathFillType::InverseWinding`] or [`PathFillType::InverseEvenOdd`]
1008    pub fn is_inverse_fill_type(&self) -> bool {
1009        self.fill_type().is_inverse()
1010    }
1011
1012    pub fn points(&self) -> &[Point] {
1013        unsafe {
1014            let mut len = 0;
1015            let points = sb::C_SkPathBuilder_points(self.native(), &mut len);
1016            safer::from_raw_parts(Point::from_native_ptr(points), len)
1017        }
1018    }
1019
1020    pub fn verbs(&self) -> &[PathVerb] {
1021        unsafe {
1022            let mut len = 0;
1023            let verbs = sb::C_SkPathBuilder_verbs(self.native(), &mut len);
1024            safer::from_raw_parts(verbs, len)
1025        }
1026    }
1027
1028    pub fn conic_weights(&self) -> &[scalar] {
1029        unsafe {
1030            let mut len = 0;
1031            let weights = sb::C_SkPathBuilder_conicWeights(self.native(), &mut len);
1032            safer::from_raw_parts(weights, len)
1033        }
1034    }
1035}
1036
1037pub use skia_bindings::SkPathBuilder_DumpFormat as DumpFormat;
1038variant_name!(DumpFormat::Hex);
1039
1040impl PathBuilder {
1041    /// Dumps the path to a string using the specified format.
1042    ///
1043    /// # Arguments
1044    /// * `format` - The format to use for dumping (Decimal or Hex)
1045    ///
1046    /// # Returns
1047    /// A string representation of the path
1048    pub fn dump_to_string(&self, format: DumpFormat) -> String {
1049        let mut str = crate::interop::String::default();
1050        unsafe {
1051            sb::C_SkPathBuilder_dumpToString(self.native(), format, str.native_mut());
1052        }
1053        str.as_str().to_owned()
1054    }
1055
1056    /// Dumps the path to stdout using the specified format.
1057    ///
1058    /// # Arguments
1059    /// * `format` - The format to use for dumping (Decimal or Hex)
1060    pub fn dump(&self, format: DumpFormat) {
1061        unsafe {
1062            sb::C_SkPathBuilder_dump(self.native(), format);
1063        }
1064    }
1065}
1066
1067impl PathBuilder {
1068    pub fn contains(&self, point: impl Into<Point>) -> bool {
1069        unsafe { self.native().contains(point.into().into_native()) }
1070    }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075    use crate::{paint, surfaces, Paint};
1076
1077    use super::*;
1078
1079    #[test]
1080    fn test_creation_snapshot_and_detach() {
1081        let mut builder = PathBuilder::new();
1082        let _path = builder.snapshot();
1083        let _path = builder.detach();
1084    }
1085
1086    #[test]
1087    fn issue_1195() {
1088        let mut surface = surfaces::raster_n32_premul((1000, 1000)).unwrap();
1089        let canvas = surface.canvas();
1090        let mut paint = Paint::default();
1091        paint.set_style(paint::Style::Stroke);
1092        let mut path = PathBuilder::new();
1093        path.move_to((250., 250.));
1094        path.cubic_to((300., 300.), (700., 700.), (750., 750.));
1095        let path_sh = path.snapshot();
1096        canvas.draw_path(&path_sh, &paint);
1097    }
1098
1099    #[test]
1100    fn test_equality() {
1101        let mut a = PathBuilder::new();
1102        let mut b = PathBuilder::new();
1103
1104        // Empty builders should be equal
1105        assert_eq!(a, b);
1106
1107        // Different paths should not be equal
1108        a.move_to((0., 0.));
1109        assert_ne!(a, b);
1110
1111        // Same paths should be equal
1112        b.move_to((0., 0.));
1113        assert_eq!(a, b);
1114
1115        // Different fill types should not be equal
1116        b.set_fill_type(PathFillType::EvenOdd);
1117        assert_ne!(a, b);
1118
1119        a.set_fill_type(PathFillType::EvenOdd);
1120        assert_eq!(a, b);
1121    }
1122
1123    #[test]
1124    fn test_compute_bounds() {
1125        let mut builder = PathBuilder::new();
1126
1127        // Empty builder should return empty rect (0, 0, 0, 0), not None
1128        let empty_bounds = builder.compute_finite_bounds().unwrap();
1129        assert!(empty_bounds.is_empty());
1130        assert_eq!(empty_bounds, Rect::new(0., 0., 0., 0.));
1131
1132        let empty_tight = builder.compute_tight_bounds().unwrap();
1133        assert!(empty_tight.is_empty());
1134
1135        // Deprecated method should also return empty rect
1136        #[allow(deprecated)]
1137        let bounds = builder.compute_bounds();
1138        assert!(bounds.is_empty());
1139
1140        // Add a simple rectangle
1141        builder.move_to((10., 20.));
1142        builder.line_to((100., 20.));
1143        builder.line_to((100., 80.));
1144        builder.line_to((10., 80.));
1145        builder.close();
1146
1147        let finite_bounds = builder.compute_finite_bounds().unwrap();
1148        assert_eq!(finite_bounds.left, 10.);
1149        assert_eq!(finite_bounds.top, 20.);
1150        assert_eq!(finite_bounds.right, 100.);
1151        assert_eq!(finite_bounds.bottom, 80.);
1152
1153        // For a polygon, tight bounds should equal finite bounds
1154        let tight_bounds = builder.compute_tight_bounds().unwrap();
1155        assert_eq!(finite_bounds, tight_bounds);
1156
1157        // Test with curves - finite bounds includes control points
1158        let mut curve_builder = PathBuilder::new();
1159        curve_builder.move_to((0., 0.));
1160        curve_builder.cubic_to((50., 100.), (150., 100.), (200., 0.));
1161
1162        let finite = curve_builder.compute_finite_bounds().unwrap();
1163        let tight = curve_builder.compute_tight_bounds().unwrap();
1164
1165        // Finite bounds should include control points
1166        assert_eq!(finite.left, 0.);
1167        assert_eq!(finite.right, 200.);
1168        assert_eq!(finite.top, 0.);
1169        assert_eq!(finite.bottom, 100.);
1170
1171        // Tight bounds should be smaller (not including full extent of control points)
1172        assert!(tight.bottom < finite.bottom);
1173    }
1174
1175    #[test]
1176    fn test_dump_to_string() {
1177        let mut builder = PathBuilder::new();
1178        builder.move_to((10.5, 20.25));
1179        builder.line_to((100.0, 50.0));
1180        builder.cubic_to((30.0, 40.0), (70.0, 80.0), (90.0, 100.0));
1181        builder.close();
1182
1183        // Test decimal format
1184        let decimal = builder.dump_to_string(DumpFormat::Decimal);
1185        println!("Decimal format:\n{}", decimal);
1186        assert!(decimal.contains("10.5"));
1187        assert!(decimal.contains("20.25"));
1188
1189        // Test hex format
1190        let hex = builder.dump_to_string(DumpFormat::Hex);
1191        println!("Hex format:\n{}", hex);
1192        assert!(hex.contains("0x"));
1193
1194        // Both should contain verb information
1195        assert!(decimal.contains("path") || decimal.contains("move") || decimal.contains("line"));
1196    }
1197
1198    #[test]
1199    fn test_contains() {
1200        let mut builder = PathBuilder::new();
1201        builder.add_rect(Rect::new(10., 10., 100., 100.), None, None);
1202
1203        assert!(builder.contains((50., 50.)));
1204        assert!(builder.contains((10., 10.)));
1205        assert!(!builder.contains((5., 5.)));
1206        assert!(!builder.contains((150., 150.)));
1207    }
1208}