Skip to main content

euv_chart/geom/base/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`Transform2D`].
4impl Transform2D {
5    /// Returns the identity transform.
6    pub fn identity() -> Self {
7        Self {
8            a: 1.0,
9            b: 0.0,
10            c: 0.0,
11            d: 1.0,
12            e: 0.0,
13            f: 0.0,
14        }
15    }
16
17    /// Returns a pure translation.
18    ///
19    /// # Arguments
20    ///
21    /// - `f64` - A 64-bit float (`f64`).
22    pub fn translate(tx: f64, ty: f64) -> Self {
23        Self {
24            a: 1.0,
25            b: 0.0,
26            c: 0.0,
27            d: 1.0,
28            e: tx,
29            f: ty,
30        }
31    }
32
33    /// Returns a pure scale.
34    ///
35    /// # Arguments
36    ///
37    /// - `f64` - A 64-bit float (`f64`).
38    pub fn scale(sx: f64, sy: f64) -> Self {
39        Self {
40            a: sx,
41            b: 0.0,
42            c: 0.0,
43            d: sy,
44            e: 0.0,
45            f: 0.0,
46        }
47    }
48
49    /// Returns a pure rotation, in radians, around the origin.
50    ///
51    /// # Arguments
52    ///
53    /// - `f64` - A 64-bit float (`f64`).
54    pub fn rotate(angle: f64) -> Self {
55        let (sin, cos) = angle.sin_cos();
56        Self {
57            a: cos,
58            b: sin,
59            c: -sin,
60            d: cos,
61            e: 0.0,
62            f: 0.0,
63        }
64    }
65
66    /// Returns the transform that maps the `from` rectangle onto the `to`
67    /// rectangle, scaling uniformly on both axes and centering the result.
68    ///
69    /// # Arguments
70    ///
71    /// - `BBox` - A `BBox` parameter.
72    pub fn from_rect_to_rect(from: BBox, to: BBox) -> Self {
73        let from_width: f64 = from.max_x - from.min_x;
74        let from_height: f64 = from.max_y - from.min_y;
75        if from_width <= 0.0 || from_height <= 0.0 {
76            return Self::identity();
77        }
78        let scale_x: f64 = (to.max_x - to.min_x) / from_width;
79        let scale_y: f64 = (to.max_y - to.min_y) / from_height;
80        Self {
81            a: scale_x,
82            b: 0.0,
83            c: 0.0,
84            d: scale_y,
85            e: to.min_x - from.min_x * scale_x,
86            f: to.min_y - from.min_y * scale_y,
87        }
88    }
89
90    /// Returns the composition of this transform with `other`, where
91    /// `other` is applied first.
92    ///
93    /// # Arguments
94    ///
95    /// - `&Self` - Shared reference to a `Self`.
96    pub fn then(&self, other: &Self) -> Self {
97        Self {
98            a: self.get_a() * other.get_a() + self.get_c() * other.get_b(),
99            b: self.get_b() * other.get_a() + self.get_d() * other.get_b(),
100            c: self.get_a() * other.get_c() + self.get_c() * other.get_d(),
101            d: self.get_b() * other.get_c() + self.get_d() * other.get_d(),
102            e: self.get_a() * other.get_e() + self.get_c() * other.get_f() + self.get_e(),
103            f: self.get_b() * other.get_e() + self.get_d() * other.get_f() + self.get_f(),
104        }
105    }
106
107    /// Applies the transform to a point.
108    ///
109    /// # Arguments
110    ///
111    /// - `f64` - A 64-bit float (`f64`).
112    ///
113    /// # Returns
114    ///
115    /// - `(f64, f64)` - A `(f64, f64)` value.
116    pub fn apply(&self, x: f64, y: f64) -> (f64, f64) {
117        (
118            self.get_a() * x + self.get_c() * y + self.get_e(),
119            self.get_b() * x + self.get_d() * y + self.get_f(),
120        )
121    }
122
123    /// Applies the transform to a point, keeping 32-bit precision.
124    ///
125    /// # Arguments
126    ///
127    /// - `f32` - A 32-bit float (`f32`).
128    ///
129    /// # Returns
130    ///
131    /// - `(f32, f32)` - A `(f32, f32)` value.
132    pub fn apply_f32(&self, x: f32, y: f32) -> (f32, f32) {
133        let (x, y) = self.apply(x as f64, y as f64);
134        (x as f32, y as f32)
135    }
136
137    /// Returns the transform's determinant.
138    ///
139    /// A determinant of zero means the transform collapses the plane onto a
140    /// line or a point; such a transform has no inverse.
141    ///
142    /// # Returns
143    ///
144    /// - `f64` - A 64-bit float.
145    pub fn determinant(&self) -> f64 {
146        self.get_a() * self.get_d() - self.get_b() * self.get_c()
147    }
148
149    /// Returns the inverse transform, or `None` when the transform is not
150    /// invertible.
151    ///
152    /// # Returns
153    ///
154    /// - `Option<Self>` - `Some(...)` on success, `None` otherwise.
155    pub fn invert(&self) -> Option<Self> {
156        let determinant: f64 = self.determinant();
157        if determinant.abs() < f64::EPSILON {
158            return None;
159        }
160        let inverse: f64 = 1.0 / determinant;
161        Some(Self {
162            a: self.get_d() * inverse,
163            b: -self.get_b() * inverse,
164            c: -self.get_c() * inverse,
165            d: self.get_a() * inverse,
166            e: (self.get_c() * self.get_f() - self.get_d() * self.get_e()) * inverse,
167            f: (self.get_b() * self.get_e() - self.get_a() * self.get_f()) * inverse,
168        })
169    }
170}
171
172/// Default construction for [`BBox`].
173impl Default for BBox {
174    /// Constructs a default [`BBox`] value.
175    fn default() -> Self {
176        Self::empty()
177    }
178}
179
180/// Inherent implementation of [`BBox`].
181impl BBox {
182    /// Returns an empty bounding box.
183    ///
184    /// The empty box has `min` greater than `max` on both axes, which
185    /// makes [`BBox::extend_point`] work without a special case for the
186    /// first vertex.
187    pub fn empty() -> Self {
188        Self {
189            min_x: f64::INFINITY,
190            min_y: f64::INFINITY,
191            max_x: f64::NEG_INFINITY,
192            max_y: f64::NEG_INFINITY,
193        }
194    }
195
196    /// Returns a bounding box from its minimum corner and its extent.
197    ///
198    /// # Arguments
199    ///
200    /// - `f64` - A 64-bit float (`f64`).
201    pub fn from_min_size(min_x: f64, min_y: f64, width: f64, height: f64) -> Self {
202        Self {
203            min_x,
204            min_y,
205            max_x: min_x + width,
206            max_y: min_y + height,
207        }
208    }
209
210    /// Returns a bounding box from two opposite corners.
211    ///
212    /// # Arguments
213    ///
214    /// - `f64` - A 64-bit float (`f64`).
215    pub fn from_corners(x0: f64, y0: f64, x1: f64, y1: f64) -> Self {
216        Self {
217            min_x: x0.min(x1),
218            min_y: y0.min(y1),
219            max_x: x0.max(x1),
220            max_y: y0.max(y1),
221        }
222    }
223
224    /// Returns `true` when the box contains no geometry.
225    ///
226    /// # Returns
227    ///
228    /// - `bool` - A boolean.
229    pub fn is_empty(&self) -> bool {
230        self.get_min_x() > self.get_max_x() || self.get_min_y() > self.get_max_y()
231    }
232
233    /// Returns the width of the box, or zero when it is empty.
234    ///
235    /// # Returns
236    ///
237    /// - `f64` - A 64-bit float.
238    pub fn width(&self) -> f64 {
239        if self.is_empty() {
240            0.0
241        } else {
242            self.get_max_x() - self.get_min_x()
243        }
244    }
245
246    /// Returns the height of the box, or zero when it is empty.
247    ///
248    /// # Returns
249    ///
250    /// - `f64` - A 64-bit float.
251    pub fn height(&self) -> f64 {
252        if self.is_empty() {
253            0.0
254        } else {
255            self.get_max_y() - self.get_min_y()
256        }
257    }
258
259    /// Returns the horizontal center of the box.
260    ///
261    /// # Returns
262    ///
263    /// - `f64` - A 64-bit float.
264    pub fn center_x(&self) -> f64 {
265        (self.get_min_x() + self.get_max_x()) * 0.5
266    }
267
268    /// Returns the vertical center of the box.
269    ///
270    /// # Returns
271    ///
272    /// - `f64` - A 64-bit float.
273    pub fn center_y(&self) -> f64 {
274        (self.get_min_y() + self.get_max_y()) * 0.5
275    }
276
277    /// Grows the box so that it contains the given point.
278    ///
279    /// # Arguments
280    ///
281    /// - `f64` - A 64-bit float (`f64`).
282    pub fn extend_point(&mut self, x: f64, y: f64) {
283        self.set_min_x(self.get_min_x().min(x));
284        self.set_min_y(self.get_min_y().min(y));
285        self.set_max_x(self.get_max_x().max(x));
286        self.set_max_y(self.get_max_y().max(y));
287    }
288
289    /// Grows the box so that it contains the given box.
290    ///
291    /// # Arguments
292    ///
293    /// - `&BBox` - Shared reference to a `BBox`.
294    pub fn extend_box(&mut self, other: &BBox) {
295        if other.is_empty() {
296            return;
297        }
298        self.set_min_x(self.get_min_x().min(other.min_x));
299        self.set_min_y(self.get_min_y().min(other.min_y));
300        self.set_max_x(self.get_max_x().max(other.max_x));
301        self.set_max_y(self.get_max_y().max(other.max_y));
302    }
303
304    /// Returns `true` when the box fully contains the given point.
305    ///
306    /// # Arguments
307    ///
308    /// - `f64` - A 64-bit float (`f64`).
309    ///
310    /// # Returns
311    ///
312    /// - `bool` - A boolean.
313    pub fn contains(&self, x: f64, y: f64) -> bool {
314        !self.is_empty()
315            && x >= self.get_min_x()
316            && x <= self.get_max_x()
317            && y >= self.get_min_y()
318            && y <= self.get_max_y()
319    }
320
321    /// Returns `true` when the two boxes overlap.
322    ///
323    /// # Arguments
324    ///
325    /// - `&BBox` - Shared reference to a `BBox`.
326    ///
327    /// # Returns
328    ///
329    /// - `bool` - A boolean.
330    pub fn intersects(&self, other: &BBox) -> bool {
331        !self.is_empty()
332            && !other.is_empty()
333            && self.get_min_x() <= other.max_x
334            && self.get_max_x() >= other.min_x
335            && self.get_min_y() <= other.max_y
336            && self.get_max_y() >= other.min_y
337    }
338
339    /// Returns the overlap of the two boxes, or `None` when they do not
340    /// overlap.
341    ///
342    /// # Arguments
343    ///
344    /// - `&BBox` - Shared reference to a `BBox`.
345    ///
346    /// # Returns
347    ///
348    /// - `Option<BBox>` - `Some(...)` on success, `None` otherwise.
349    pub fn intersection(&self, other: &BBox) -> Option<BBox> {
350        if !self.intersects(other) {
351            return None;
352        }
353        Some(BBox {
354            min_x: self.get_min_x().max(other.min_x),
355            min_y: self.get_min_y().max(other.min_y),
356            max_x: self.get_max_x().min(other.max_x),
357            max_y: self.get_max_y().min(other.max_y),
358        })
359    }
360
361    /// Returns the union of the two boxes.
362    ///
363    /// # Arguments
364    ///
365    /// - `&BBox` - Shared reference to a `BBox`.
366    ///
367    /// # Returns
368    ///
369    /// - `BBox` - A `BBox` value.
370    pub fn union(&self, other: &BBox) -> BBox {
371        let mut result: BBox = *self;
372        result.extend_box(other);
373        result
374    }
375}
376
377/// Inherent implementation of [`Point`].
378impl Point {
379    /// Builds a point from its coordinates.
380    ///
381    /// # Arguments
382    ///
383    /// - `f32` - A 32-bit float (`f32`).
384    pub fn new(x: f32, y: f32) -> Self {
385        Self { x, y }
386    }
387
388    /// Returns the distance between two points.
389    ///
390    /// # Arguments
391    ///
392    /// - `&Point` - Shared reference to a `Point`.
393    ///
394    /// # Returns
395    ///
396    /// - `f32` - A 32-bit float.
397    pub fn distance_to(&self, other: &Point) -> f32 {
398        let dx: f32 = self.get_x() - other.x;
399        let dy: f32 = self.get_y() - other.y;
400        (dx * dx + dy * dy).sqrt()
401    }
402
403    /// Returns the squared distance between two points.
404    ///
405    /// # Arguments
406    ///
407    /// - `&Point` - Shared reference to a `Point`.
408    ///
409    /// # Returns
410    ///
411    /// - `f32` - A 32-bit float.
412    pub fn distance_squared_to(&self, other: &Point) -> f32 {
413        let dx: f32 = self.get_x() - other.x;
414        let dy: f32 = self.get_y() - other.y;
415        dx * dx + dy * dy
416    }
417}
418
419/// Inherent implementation of [`Polygon`].
420impl Polygon {
421    /// Builds a polygon from a flat list of coordinates.
422    ///
423    /// # Arguments
424    ///
425    /// - `&[f32]` - Shared reference to a `[f32]`.
426    pub fn from_coordinates(coordinates: &[f32]) -> Self {
427        let mut points: Vec<Point> = Vec::with_capacity(coordinates.len() / 2);
428        for pair in coordinates.as_chunks::<2>().0 {
429            points.push(Point::new(pair[0], pair[1]));
430        }
431        Self { points }
432    }
433
434    /// Returns the polygon's area, using the shoelace formula.
435    ///
436    /// The sign of the result encodes the winding direction: a positive
437    /// area means the vertices run clockwise in a y-down coordinate system.
438    ///
439    /// # Returns
440    ///
441    /// - `f64` - A 64-bit float.
442    pub fn signed_area(&self) -> f64 {
443        let count: usize = self.get_points().len();
444        if count < 3 {
445            return 0.0;
446        }
447        let mut sum: f64 = 0.0;
448        for index in 0..count {
449            let current: Point = self.get_points()[index];
450            let next: Point = self.get_points()[(index + 1) % count];
451            sum += current.x as f64 * next.y as f64 - next.x as f64 * current.y as f64;
452        }
453        sum * 0.5
454    }
455
456    /// Returns `true` when the polygon's vertices wind clockwise.
457    ///
458    /// # Returns
459    ///
460    /// - `bool` - A boolean.
461    pub fn is_clockwise(&self) -> bool {
462        self.signed_area() > 0.0
463    }
464
465    /// Reverses the winding order of the polygon's vertices.
466    pub fn reverse(&mut self) {
467        self.get_mut_points().reverse();
468    }
469}
470
471/// Inherent implementation of [`Path`].
472impl Path {
473    /// Returns an empty path.
474    pub fn new() -> Self {
475        Self {
476            commands: Vec::new(),
477            fill_rule: FillRule::default(),
478        }
479    }
480
481    /// Returns a path builder.
482    ///
483    /// # Returns
484    ///
485    /// - `PathBuilder` - A `PathBuilder` value.
486    pub fn builder() -> PathBuilder {
487        PathBuilder::new()
488    }
489
490    /// Returns the path's command buffer.
491    ///
492    /// # Returns
493    ///
494    /// - `&[PathCommand]` - A `[PathCommand]` value.
495    pub fn commands(&self) -> &[PathCommand] {
496        self.get_commands()
497    }
498
499    /// Returns the rule used to decide which parts of the path are inside.
500    ///
501    /// # Returns
502    ///
503    /// - `FillRule` - A `FillRule` value.
504    pub fn fill_rule(&self) -> FillRule {
505        self.get_fill_rule()
506    }
507
508    /// Returns `true` when the path holds no commands.
509    ///
510    /// # Returns
511    ///
512    /// - `bool` - A boolean.
513    pub fn is_empty(&self) -> bool {
514        self.get_commands().is_empty()
515    }
516
517    /// Returns the number of commands in the path.
518    ///
519    /// # Returns
520    ///
521    /// - `usize` - A non-negative integer.
522    pub fn len(&self) -> usize {
523        self.get_commands().len()
524    }
525
526    /// Returns the tightest axis-aligned box containing the path.
527    ///
528    /// The bounds are computed from the control points of every curve,
529    /// which is a conservative over-estimate for Béziers — the true
530    /// extremum of a curve lies strictly inside the control polygon.
531    /// Over-estimating is the safe direction for culling: a shape is never
532    /// culled when it should be drawn.
533    ///
534    /// # Returns
535    ///
536    /// - `BBox` - A `BBox` value.
537    pub fn bounds(&self) -> BBox {
538        let mut bounds: BBox = BBox::empty();
539        for command in self.get_commands() {
540            match *command {
541                PathCommand::MoveTo(x, y) | PathCommand::LineTo(x, y) => {
542                    bounds.extend_point(x as f64, y as f64);
543                }
544                PathCommand::QuadTo(cx, cy, x, y) => {
545                    bounds.extend_point(cx as f64, cy as f64);
546                    bounds.extend_point(x as f64, y as f64);
547                }
548                PathCommand::CubicTo(c1x, c1y, c2x, c2y, x, y) => {
549                    bounds.extend_point(c1x as f64, c1y as f64);
550                    bounds.extend_point(c2x as f64, c2y as f64);
551                    bounds.extend_point(x as f64, y as f64);
552                }
553                PathCommand::Close => {}
554            }
555        }
556        bounds
557    }
558
559    /// Returns a copy of the path with the given transform applied.
560    ///
561    /// The transform is baked into the coordinates rather than kept
562    /// alongside the path, so the tessellator never has to apply it
563    /// per-vertex.
564    ///
565    /// # Arguments
566    ///
567    /// - `&Transform2D` - Shared reference to a `Transform2D`.
568    ///
569    /// # Returns
570    ///
571    /// - `Path` - A `Path` value.
572    pub fn transformed(&self, transform: &Transform2D) -> Path {
573        let mut commands: Vec<PathCommand> = Vec::with_capacity(self.get_commands().len());
574        for command in self.get_commands() {
575            commands.push(match *command {
576                PathCommand::MoveTo(x, y) => {
577                    let (x, y) = transform.apply_f32(x, y);
578                    PathCommand::MoveTo(x, y)
579                }
580                PathCommand::LineTo(x, y) => {
581                    let (x, y) = transform.apply_f32(x, y);
582                    PathCommand::LineTo(x, y)
583                }
584                PathCommand::QuadTo(cx, cy, x, y) => {
585                    let (cx, cy) = transform.apply_f32(cx, cy);
586                    let (x, y) = transform.apply_f32(x, y);
587                    PathCommand::QuadTo(cx, cy, x, y)
588                }
589                PathCommand::CubicTo(c1x, c1y, c2x, c2y, x, y) => {
590                    let (c1x, c1y) = transform.apply_f32(c1x, c1y);
591                    let (c2x, c2y) = transform.apply_f32(c2x, c2y);
592                    let (x, y) = transform.apply_f32(x, y);
593                    PathCommand::CubicTo(c1x, c1y, c2x, c2y, x, y)
594                }
595                PathCommand::Close => PathCommand::Close,
596            });
597        }
598        Path {
599            commands,
600            fill_rule: self.get_fill_rule(),
601        }
602    }
603}
604
605/// Inherent implementation of [`PathBuilder`].
606impl PathBuilder {
607    /// Returns an empty builder.
608    pub fn new() -> Self {
609        Self {
610            // The room for a handful of commands is reserved right
611            // away: a chart over a million bars builds a million paths,
612            // and a path that has to grow its buffer twice costs two
613            // allocations instead of one.
614            commands: Vec::with_capacity(PATH_COMMAND_CAPACITY),
615            fill_rule: FillRule::default(),
616            current: Point::default(),
617            start: Point::default(),
618        }
619    }
620
621    /// Sets the fill rule used when the path is built.
622    ///
623    /// # Arguments
624    ///
625    /// - `FillRule` - A `FillRule` parameter.
626    pub fn fill_rule(mut self, rule: FillRule) -> Self {
627        self.set_fill_rule(rule);
628        self
629    }
630
631    /// Starts a new sub-path at the given point.
632    ///
633    /// # Arguments
634    ///
635    /// - `f32` - A 32-bit float (`f32`).
636    pub fn move_to(mut self, x: f32, y: f32) -> Self {
637        self.set_current(Point::new(x, y));
638        self.set_start(self.get_current());
639        self.get_mut_commands().push(PathCommand::MoveTo(x, y));
640        self
641    }
642
643    /// Appends a straight line segment.
644    ///
645    /// # Arguments
646    ///
647    /// - `f32` - A 32-bit float (`f32`).
648    pub fn line_to(mut self, x: f32, y: f32) -> Self {
649        self.set_current(Point::new(x, y));
650        self.get_mut_commands().push(PathCommand::LineTo(x, y));
651        self
652    }
653
654    /// Appends a quadratic Bézier segment.
655    ///
656    /// # Arguments
657    ///
658    /// - `f32` - A 32-bit float (`f32`).
659    pub fn quad_to(mut self, control_x: f32, control_y: f32, x: f32, y: f32) -> Self {
660        self.set_current(Point::new(x, y));
661        self.get_mut_commands()
662            .push(PathCommand::QuadTo(control_x, control_y, x, y));
663        self
664    }
665
666    /// Appends a cubic Bézier segment.
667    ///
668    /// # Arguments
669    ///
670    /// - `f32` - A 32-bit float (`f32`).
671    pub fn cubic_to(
672        mut self,
673        control1_x: f32,
674        control1_y: f32,
675        control2_x: f32,
676        control2_y: f32,
677        x: f32,
678        y: f32,
679    ) -> Self {
680        self.set_current(Point::new(x, y));
681        self.get_mut_commands().push(PathCommand::CubicTo(
682            control1_x, control1_y, control2_x, control2_y, x, y,
683        ));
684        self
685    }
686
687    /// Closes the current sub-path.
688    pub fn close(mut self) -> Self {
689        self.set_current(self.get_start());
690        self.get_mut_commands().push(PathCommand::Close);
691        self
692    }
693
694    /// Appends an elliptical arc centered at the given point.
695    ///
696    /// `start_angle` and `end_angle` are measured in radians, clockwise
697    /// from the positive x-axis (the y-axis points down, matching the
698    /// Canvas2D and SVG conventions). The arc is approximated with cubic
699    /// Bézier segments.
700    ///
701    /// # Arguments
702    ///
703    /// - `f32` - A 32-bit float (`f32`).
704    /// - `f64` - A 64-bit float (`f64`).
705    pub fn arc(
706        mut self,
707        center_x: f32,
708        center_y: f32,
709        radius_x: f32,
710        radius_y: f32,
711        start_angle: f64,
712        end_angle: f64,
713    ) -> Self {
714        let sweep: f64 = end_angle - start_angle;
715        if sweep == 0.0 {
716            return self;
717        }
718        let segments: usize = arc_segment_count(sweep, radius_x.max(radius_y) as f64, 0.1);
719        let (sin, cos) = start_angle.sin_cos();
720        let start_x: f64 = center_x as f64 + radius_x as f64 * cos;
721        let start_y: f64 = center_y as f64 + radius_y as f64 * sin;
722        if self.get_commands().is_empty() {
723            self.get_mut_commands()
724                .push(PathCommand::MoveTo(start_x as f32, start_y as f32));
725        } else {
726            self.get_mut_commands()
727                .push(PathCommand::LineTo(start_x as f32, start_y as f32));
728        }
729        self.set_current(Point::new(start_x as f32, start_y as f32));
730        self.set_start(self.get_current());
731
732        arc_to_cubic(
733            Ellipse {
734                center_x: center_x as f64,
735                center_y: center_y as f64,
736                radius_x: radius_x as f64,
737                radius_y: radius_y as f64,
738            },
739            start_angle,
740            sweep,
741            segments,
742            |c1x: f64, c1y: f64, c2x: f64, c2y: f64, x: f64, y: f64| {
743                self.get_mut_commands().push(PathCommand::CubicTo(
744                    c1x as f32, c1y as f32, c2x as f32, c2y as f32, x as f32, y as f32,
745                ));
746            },
747        );
748        self
749    }
750
751    /// Appends a full ellipse centered at the given point.
752    ///
753    /// # Arguments
754    ///
755    /// - `f32` - A 32-bit float (`f32`).
756    pub fn ellipse(mut self, center_x: f32, center_y: f32, radius_x: f32, radius_y: f32) -> Self {
757        let start: f64 = core::f64::consts::FRAC_PI_2;
758        self = self.move_to(center_x, center_y - radius_y);
759        self = self.arc(
760            center_x,
761            center_y,
762            radius_x,
763            radius_y,
764            start,
765            start + core::f64::consts::TAU,
766        );
767        self.close()
768    }
769
770    /// Appends an axis-aligned rectangle.
771    ///
772    /// # Arguments
773    ///
774    /// - `f32` - A 32-bit float (`f32`).
775    pub fn rect(mut self, x: f32, y: f32, width: f32, height: f32) -> Self {
776        self.get_mut_commands().push(PathCommand::MoveTo(x, y));
777        self.get_mut_commands()
778            .push(PathCommand::LineTo(x + width, y));
779        self.get_mut_commands()
780            .push(PathCommand::LineTo(x + width, y + height));
781        self.get_mut_commands()
782            .push(PathCommand::LineTo(x, y + height));
783        self.get_mut_commands().push(PathCommand::Close);
784        self
785    }
786
787    /// Appends a rectangle with rounded corners.
788    ///
789    /// # Arguments
790    ///
791    /// - `f32` - A 32-bit float (`f32`).
792    pub fn round_rect(mut self, x: f32, y: f32, width: f32, height: f32, radius: f32) -> Self {
793        let radius: f32 = radius.min(width * 0.5).min(height * 0.5);
794        if radius <= 0.0 {
795            return self.rect(x, y, width, height);
796        }
797        let right: f32 = x + width;
798        let bottom: f32 = y + height;
799        let kappa: f64 = radius as f64 * ARC_BEZIER_KAPPA;
800        self.get_mut_commands()
801            .push(PathCommand::MoveTo(x + radius, y));
802        self.get_mut_commands()
803            .push(PathCommand::LineTo(right - radius, y));
804        self.get_mut_commands().push(PathCommand::CubicTo(
805            right - radius + kappa as f32,
806            y,
807            right,
808            y + radius - kappa as f32,
809            right,
810            y + radius,
811        ));
812        self.get_mut_commands()
813            .push(PathCommand::LineTo(right, bottom - radius));
814        self.get_mut_commands().push(PathCommand::CubicTo(
815            right,
816            bottom - radius + kappa as f32,
817            right - radius + kappa as f32,
818            bottom,
819            right - radius,
820            bottom,
821        ));
822        self.get_mut_commands()
823            .push(PathCommand::LineTo(x + radius, bottom));
824        self.get_mut_commands().push(PathCommand::CubicTo(
825            x + radius - kappa as f32,
826            bottom,
827            x,
828            bottom - radius + kappa as f32,
829            x,
830            bottom - radius,
831        ));
832        self.get_mut_commands()
833            .push(PathCommand::LineTo(x, y + radius));
834        self.get_mut_commands().push(PathCommand::CubicTo(
835            x,
836            y + radius - kappa as f32,
837            x + radius - kappa as f32,
838            y,
839            x + radius,
840            y,
841        ));
842        self.get_mut_commands().push(PathCommand::Close);
843        self
844    }
845
846    /// Appends a closed polygon through the given points.
847    ///
848    /// # Arguments
849    ///
850    /// - `&[Point]` - Shared reference to a `[Point]`.
851    pub fn polygon(mut self, points: &[Point]) -> Self {
852        if let Some(first) = points.first() {
853            self.get_mut_commands()
854                .push(PathCommand::MoveTo(first.x, first.y));
855            for point in &points[1..] {
856                self.get_mut_commands()
857                    .push(PathCommand::LineTo(point.x, point.y));
858            }
859            self.get_mut_commands().push(PathCommand::Close);
860        }
861        self
862    }
863
864    /// Returns the finished path.
865    ///
866    /// # Returns
867    ///
868    /// - `Path` - A `Path` value.
869    pub fn build(self) -> Path {
870        let Self {
871            commands,
872            fill_rule,
873            ..
874        } = self;
875        Path {
876            commands,
877            fill_rule,
878        }
879    }
880}
881
882/// Default construction for [`PathBuilder`].
883impl Default for PathBuilder {
884    /// Constructs a default [`PathBuilder`] value.
885    fn default() -> Self {
886        Self::new()
887    }
888}
889
890/// Inherent implementation of [`DashPattern`].
891impl DashPattern {
892    /// Builds a solid (undashed) pattern.
893    pub fn solid() -> Self {
894        Self {
895            pattern: Vec::new(),
896            offset: 0.0,
897        }
898    }
899
900    /// Builds a dash pattern from alternating on/off lengths.
901    ///
902    /// # Arguments
903    ///
904    /// - `Vec<f32>` - A `Vec<f32>` parameter.
905    /// - `f32` - A 32-bit float (`f32`).
906    pub fn new(pattern: Vec<f32>, offset: f32) -> Self {
907        Self { pattern, offset }
908    }
909
910    /// Returns `true` when the pattern draws an unbroken line.
911    ///
912    /// # Returns
913    ///
914    /// - `bool` - A boolean.
915    pub fn is_solid(&self) -> bool {
916        self.get_pattern().is_empty()
917    }
918}