1use crate::geometry::CornerRadii;
2use crate::paint::FillRule;
3use incular_core::{Offset, Rect, Size, Transform};
4use kurbo::{BezPath, Ellipse, Point, RoundedRect, RoundedRectRadii, Shape, Vec2};
5use std::sync::{
6 Arc,
7 atomic::{AtomicU64, Ordering},
8};
9
10static NEXT_PATH_ID: AtomicU64 = AtomicU64::new(1);
11
12fn next_path_id() -> PathId {
13 PathId(
14 NEXT_PATH_ID
15 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
16 .expect("path identity space exhausted"),
17 )
18}
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
23pub struct PathId(u64);
24#[derive(Clone, Debug, PartialEq)]
25pub struct Path {
26 id: PathId,
27 path: Arc<BezPath>,
28 bounds: Option<Rect>,
29}
30impl Path {
31 #[must_use]
32 pub fn builder() -> PathBuilder {
33 PathBuilder::default()
34 }
35 #[must_use]
36 pub fn bez_path(&self) -> &BezPath {
37 &self.path
38 }
39 #[must_use]
40 pub fn bounds(&self) -> Option<Rect> {
41 self.bounds
42 }
43 #[must_use]
44 pub const fn id(&self) -> PathId {
45 self.id
46 }
47 #[must_use]
51 pub fn contains(&self, point: Offset, rule: FillRule) -> bool {
52 let winding = self
53 .path
54 .winding(Point::new(f64::from(point.x), f64::from(point.y)));
55 match rule {
56 FillRule::NonZero => winding != 0,
57 FillRule::EvenOdd => winding.unsigned_abs() % 2 == 1,
58 }
59 }
60
61 pub(crate) fn from_bez_path(path: BezPath) -> Self {
65 let bounds = (!path.is_empty() && path.is_finite()).then(|| {
66 let bounds = path.bounding_box();
67 Rect::from_origin_size(
68 Offset::new(bounds.x0 as f32, bounds.y0 as f32),
69 incular_core::Size::new(
70 (bounds.x1 - bounds.x0) as f32,
71 (bounds.y1 - bounds.y0) as f32,
72 ),
73 )
74 });
75 Self {
76 id: next_path_id(),
77 path: Arc::new(path),
78 bounds,
79 }
80 }
81
82 #[must_use]
86 pub fn rect_excluding(outer: Rect, inner: &Self) -> Self {
87 let left = f64::from(outer.origin.x);
88 let top = f64::from(outer.origin.y);
89 let right = left + f64::from(outer.size.width);
90 let bottom = top + f64::from(outer.size.height);
91 let mut path = BezPath::new();
92 path.move_to((left, top));
93 path.line_to((right, top));
94 path.line_to((right, bottom));
95 path.line_to((left, bottom));
96 path.close_path();
97 let mut hole = (*inner.path).clone();
98 if hole.area().signum() == path.area().signum() {
99 hole = hole.reverse_subpaths();
100 }
101 path.extend(hole);
102 Self::from_bez_path(path)
103 }
104
105 #[must_use]
111 pub fn transformed(&self, transform: Transform) -> Self {
112 let mut path = (*self.path).clone();
113 path.apply_affine(transform.to_kurbo());
114 let bounds = (!path.is_empty() && path.is_finite()).then(|| {
115 let bounds = path.bounding_box();
116 Rect::from_origin_size(
117 Offset::new(bounds.x0 as f32, bounds.y0 as f32),
118 Size::new(
119 (bounds.x1 - bounds.x0) as f32,
120 (bounds.y1 - bounds.y0) as f32,
121 ),
122 )
123 });
124 Self {
125 id: next_path_id(),
126 path: Arc::new(path),
127 bounds,
128 }
129 }
130}
131impl Default for Path {
132 fn default() -> Self {
133 PathBuilder::default().build()
134 }
135}
136#[derive(Default)]
137pub struct PathBuilder {
138 path: BezPath,
139}
140impl PathBuilder {
141 pub fn move_to(&mut self, p: Offset) -> &mut Self {
142 self.path.move_to(point(p));
143 self
144 }
145 pub fn line_to(&mut self, p: Offset) -> &mut Self {
146 self.path.line_to(point(p));
147 self
148 }
149 pub fn quadratic_to(&mut self, c: Offset, p: Offset) -> &mut Self {
150 self.path.quad_to(point(c), point(p));
151 self
152 }
153 pub fn cubic_to(&mut self, a: Offset, b: Offset, p: Offset) -> &mut Self {
154 self.path.curve_to(point(a), point(b), point(p));
155 self
156 }
157 pub fn conic_to(&mut self, control: Offset, end: Offset, weight: f32) -> &mut Self {
164 let Some(start) = self
165 .path
166 .elements()
167 .last()
168 .and_then(|element| element.end_point())
169 else {
170 return self.move_to(end);
171 };
172 let weight = f64::from(weight);
173 if !(weight.is_finite() && weight > 0.0) {
174 return self.line_to(end);
175 }
176 let mut pieces = vec![(start, point(control), point(end), weight)];
177 for _ in 0..2 {
178 pieces = pieces
179 .into_iter()
180 .flat_map(|(p0, p1, p2, w)| {
181 let scale = 1.0 / (1.0 + w);
182 let mid = ((p0.to_vec2() + p1.to_vec2() * (2.0 * w) + p2.to_vec2())
183 * (0.5 * scale))
184 .to_point();
185 let half = ((1.0 + w) / 2.0).sqrt();
186 [
187 (
188 p0,
189 ((p0.to_vec2() + p1.to_vec2() * w) * scale).to_point(),
190 mid,
191 half,
192 ),
193 (
194 mid,
195 ((p1.to_vec2() * w + p2.to_vec2()) * scale).to_point(),
196 p2,
197 half,
198 ),
199 ]
200 })
201 .collect();
202 }
203 for (p0, p1, p2, w) in pieces {
204 let k = 4.0 * w / (3.0 * (1.0 + w));
205 self.path.curve_to(p0.lerp(p1, k), p2.lerp(p1, k), p2);
206 }
207 self
208 }
209
210 pub fn rounded_rect(&mut self, rect: Rect, radii: CornerRadii) -> &mut Self {
213 let radii = radii.normalized(rect.size);
214 let shape = RoundedRect::new(
215 f64::from(rect.origin.x),
216 f64::from(rect.origin.y),
217 f64::from(rect.origin.x + rect.size.width),
218 f64::from(rect.origin.y + rect.size.height),
219 RoundedRectRadii::new(
220 f64::from(radii.top_left),
221 f64::from(radii.top_right),
222 f64::from(radii.bottom_right),
223 f64::from(radii.bottom_left),
224 ),
225 );
226 self.path.extend(shape.path_elements(0.01));
227 self
228 }
229
230 pub fn rounded_superellipse(&mut self, rect: Rect, radii: CornerRadii) -> &mut Self {
233 crate::rsuperellipse::rounded_superellipse(self, rect, radii);
234 self
235 }
236
237 pub fn append(&mut self, path: &Path) -> &mut Self {
240 self.path.extend(path.bez_path().elements().iter().copied());
241 self
242 }
243
244 pub fn close(&mut self) -> &mut Self {
245 if !self.path.elements().is_empty() {
246 self.path.close_path();
247 }
248 self
249 }
250 #[must_use]
251 pub fn build(self) -> Path {
252 Path::from_bez_path(self.path)
253 }
254}
255
256fn point(offset: Offset) -> Point {
257 Point::new(f64::from(offset.x), f64::from(offset.y))
258}
259
260const CLIP_PATH_WORLD_TOLERANCE: f64 = 0.1;
263
264pub(crate) fn fallback_tolerance(world: Transform) -> f64 {
273 (CLIP_PATH_WORLD_TOLERANCE / f64::from(world.magnification_bound())).clamp(1e-3, 1.0)
274}
275
276pub(crate) fn rect_as_path(rect: Rect) -> Path {
279 let mut builder = PathBuilder::default();
280 builder
281 .move_to(rect.origin)
282 .line_to(Offset::new(rect.origin.x + rect.size.width, rect.origin.y))
283 .line_to(Offset::new(
284 rect.origin.x + rect.size.width,
285 rect.origin.y + rect.size.height,
286 ))
287 .line_to(Offset::new(rect.origin.x, rect.origin.y + rect.size.height))
288 .close();
289 builder.build()
290}
291
292pub(crate) fn rrect_as_path(rect: Rect, radii: CornerRadii, tolerance: f64) -> Path {
297 let shape = RoundedRect::new(
298 f64::from(rect.origin.x),
299 f64::from(rect.origin.y),
300 f64::from(rect.origin.x + rect.size.width),
301 f64::from(rect.origin.y + rect.size.height),
302 RoundedRectRadii::new(
303 f64::from(radii.top_left),
304 f64::from(radii.top_right),
305 f64::from(radii.bottom_right),
306 f64::from(radii.bottom_left),
307 ),
308 );
309 Path::from_bez_path(shape.to_path(tolerance))
310}
311
312pub(crate) fn ellipse_as_path(rect: Rect, tolerance: f64) -> Path {
316 let shape = Ellipse::new(
317 Point::new(
318 f64::from(rect.origin.x) + f64::from(rect.size.width) * 0.5,
319 f64::from(rect.origin.y) + f64::from(rect.size.height) * 0.5,
320 ),
321 Vec2::new(
322 f64::from(rect.size.width) * 0.5,
323 f64::from(rect.size.height) * 0.5,
324 ),
325 0.0,
326 );
327 Path::from_bez_path(shape.to_path(tolerance))
328}