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}