1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! [`ShapeHelpers`] — shape convenience helpers with default impls over [`Painter`].
use super::painter::Painter;
/// Shape convenience helpers — all have default impls over [`Painter`].
///
/// Backends may override (e.g. vello-gpu overrides [`rounded_rect_corners`]
/// with native kurbo rounded-rect for better precision).
pub trait ShapeHelpers: Painter {
/// Stroke a rectangle.
fn stroke_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
/// Fill a rectangle.
fn fill_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
/// Add a rounded rectangle to the current path using arc-based geometry.
///
/// No stroke or fill is performed — call [`fill`](Painter::fill) or
/// [`stroke`](Painter::stroke) afterwards.
fn rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
let r = r.min(w / 2.0).min(h / 2.0);
self.move_to(x + r, y);
self.line_to(x + w - r, y);
self.arc(x + w - r, y + r, r, -std::f64::consts::FRAC_PI_2, 0.0);
self.line_to(x + w, y + h - r);
self.arc(
x + w - r,
y + h - r,
r,
0.0,
std::f64::consts::FRAC_PI_2,
);
self.line_to(x + r, y + h);
self.arc(
x + r,
y + h - r,
r,
std::f64::consts::FRAC_PI_2,
std::f64::consts::PI,
);
self.line_to(x, y + r);
self.arc(
x + r,
y + r,
r,
std::f64::consts::PI,
std::f64::consts::PI * 1.5,
);
self.close_path();
}
/// Add a rounded rectangle with per-corner radii to the current path.
///
/// Corner order: `tl` (top-left), `tr` (top-right), `br` (bottom-right),
/// `bl` (bottom-left). Each radius is clamped to `min(w, h) / 2`.
///
/// Default falls back to a uniform-radius [`rounded_rect`](Self::rounded_rect)
/// using `min(tl, tr, br, bl)` so we under-round rather than over-round on
/// backends lacking native per-corner support.
#[allow(clippy::too_many_arguments)]
fn rounded_rect_corners(
&mut self,
x: f64,
y: f64,
w: f64,
h: f64,
tl: f64,
tr: f64,
br: f64,
bl: f64,
) {
let max_r = (w / 2.0).min(h / 2.0).max(0.0);
let tl = tl.clamp(0.0, max_r);
let tr = tr.clamp(0.0, max_r);
let br = br.clamp(0.0, max_r);
let bl = bl.clamp(0.0, max_r);
let r = tl.min(tr).min(br).min(bl);
self.rounded_rect(x, y, w, h, r);
}
/// Fill a rounded rectangle.
///
/// Default: `begin_path` + [`rounded_rect`](Self::rounded_rect) + `fill`.
fn fill_rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, radius: f64) {
self.begin_path();
self.rounded_rect(x, y, w, h, radius);
self.fill();
}
/// Stroke a rounded rectangle.
///
/// Default: `begin_path` + [`rounded_rect`](Self::rounded_rect) + `stroke`.
fn stroke_rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, radius: f64) {
self.begin_path();
self.rounded_rect(x, y, w, h, radius);
self.stroke();
}
/// Fill a rounded rectangle with per-corner radii.
///
/// Default: `begin_path` + [`rounded_rect_corners`](Self::rounded_rect_corners) + `fill`.
#[allow(clippy::too_many_arguments)]
fn fill_rounded_rect_corners(
&mut self,
x: f64,
y: f64,
w: f64,
h: f64,
tl: f64,
tr: f64,
br: f64,
bl: f64,
) {
self.begin_path();
self.rounded_rect_corners(x, y, w, h, tl, tr, br, bl);
self.fill();
}
/// Stroke a rounded rectangle with per-corner radii.
///
/// Default: `begin_path` + [`rounded_rect_corners`](Self::rounded_rect_corners) + `stroke`.
#[allow(clippy::too_many_arguments)]
fn stroke_rounded_rect_corners(
&mut self,
x: f64,
y: f64,
w: f64,
h: f64,
tl: f64,
tr: f64,
br: f64,
bl: f64,
) {
self.begin_path();
self.rounded_rect_corners(x, y, w, h, tl, tr, br, bl);
self.stroke();
}
}