1use crate::combined_geometry_impl::CombinedGeometryImpl;
2use crate::helpers::path_measure::PathMeasure;
3use crate::helpers::{path_helper, pen_helper};
4use crate::simple_geometry_impl::SimpleGeometryImpl;
5use crate::transformed_geometry_impl::TransformedGeometryImpl;
6use crate::vello_extensions::{closed_path, to_kurbo_point, to_point, to_rect};
7use ferroui_base::media::{FillRule, GeometryCombineMode, IPen, IntersectionResult};
8use ferroui_base::platform::{IGeometryImpl, ITransformedGeometryImpl};
9use ferroui_base::{Matrix, Point, Rect};
10use kurbo::{BezPath, Shape};
11use std::sync::{Arc, Mutex, Weak};
12
13#[derive(Clone, Debug)]
19pub struct VelloPath {
20 elements: Arc<BezPath>,
21 fill_rule: FillRule,
22}
23
24impl VelloPath {
25 pub fn new(path: BezPath, fill_rule: FillRule) -> Self {
27 Self { elements: Arc::new(path), fill_rule }
28 }
29
30 pub fn empty() -> Self {
32 Self::new(BezPath::new(), FillRule::EvenOdd)
33 }
34
35 pub fn path(&self) -> &BezPath {
37 &self.elements
38 }
39
40 pub fn fill_rule(&self) -> FillRule {
42 self.fill_rule
43 }
44
45 pub fn with_fill_rule(&self, fill_rule: FillRule) -> Self {
47 Self { elements: self.elements.clone(), fill_rule }
48 }
49
50 pub fn transformed(&self, transform: kurbo::Affine) -> Self {
52 let mut path = (*self.elements).clone();
53 path.apply_affine(transform);
54 Self::new(path, self.fill_rule)
55 }
56
57 pub fn tight_bounds(&self) -> Rect {
67 let mut bounds: Option<kurbo::Rect> = None;
68 let mut add = |rect: kurbo::Rect| bounds = Some(bounds.map_or(rect, |bounds| bounds.union(rect)));
69
70 for element in self.elements.elements() {
71 if let kurbo::PathEl::MoveTo(point) = element {
72 add(kurbo::Rect::from_points(*point, *point));
73 }
74 }
75 for segment in self.elements.segments() {
76 add(kurbo::Shape::bounding_box(&segment));
77 }
78
79 bounds.map(to_rect).unwrap_or_default()
80 }
81
82 pub fn is_same(&self, other: &VelloPath) -> bool {
84 Arc::ptr_eq(&self.elements, &other.elements) && self.fill_rule == other.fill_rule
85 }
86
87 pub fn contains(&self, point: Point) -> bool {
90 let winding = closed_path(&self.elements).winding(to_kurbo_point(point));
91 if self.fill_rule == FillRule::EvenOdd {
92 winding & 1 != 0
93 } else {
94 winding != 0
95 }
96 }
97}
98
99#[derive(Clone, Debug, Default)]
105pub enum FillPath {
106 #[default]
108 None,
109 SameAsStroke,
111 Separate(VelloPath),
113}
114
115pub trait GeometryImpl: IGeometryImpl {
121 fn base(&self) -> &GeometryImplBase;
123
124 fn stroke_path(&self) -> Option<VelloPath>;
126
127 fn fill(&self) -> FillPath;
129
130 fn fill_path(&self) -> Option<VelloPath> {
132 match self.fill() {
133 FillPath::None => None,
134 FillPath::SameAsStroke => self.stroke_path(),
135 FillPath::Separate(path) => Some(path),
136 }
137 }
138
139 fn is_fill_same_as_stroke(&self) -> bool {
141 matches!(self.fill(), FillPath::SameAsStroke) && self.stroke_path().is_some()
142 }
143}
144
145#[derive(Default)]
148pub struct GeometryImplBase {
149 path_cache: Mutex<PathCache>,
150 cached_path_measure: Mutex<Option<PathMeasure>>,
151 weak_self: Mutex<Option<Weak<dyn GeometryImpl>>>,
152}
153
154#[derive(Default)]
157pub struct Shared<T>(Mutex<T>);
158
159impl<T: Clone> Shared<T> {
160 pub fn new(value: T) -> Self {
162 Self(Mutex::new(value))
163 }
164
165 pub fn get(&self) -> T {
167 self.0.lock().unwrap().clone()
168 }
169
170 pub fn set(&self, value: T) {
172 *self.0.lock().unwrap() = value;
173 }
174}
175
176impl GeometryImplBase {
177 pub fn new() -> Self {
179 Self::default()
180 }
181
182 pub fn invalidate_caches(&self) {
184 *self.path_cache.lock().unwrap() = PathCache::default();
185 *self.cached_path_measure.lock().unwrap() = None;
186 }
187
188 fn to_arc(&self) -> Arc<dyn GeometryImpl> {
190 self.weak_self
191 .lock()
192 .unwrap()
193 .as_ref()
194 .and_then(Weak::upgrade)
195 .expect("geometries of the backend are created behind a handle")
196 }
197}
198
199pub(crate) fn register<T: GeometryImpl>(geometry: T) -> Arc<T> {
202 let geometry = Arc::new(geometry);
203 let weak: Weak<dyn GeometryImpl> = Arc::downgrade(&(geometry.clone() as Arc<dyn GeometryImpl>));
204 *geometry.base().weak_self.lock().unwrap() = Some(weak);
205 geometry
206}
207
208pub fn try_get_geometry_impl(geometry: &dyn IGeometryImpl) -> Option<&dyn GeometryImpl> {
212 use crate::ellipse_geometry_impl::EllipseGeometryImpl;
213 use crate::geometry_group_impl::GeometryGroupImpl;
214 use crate::line_geometry_impl::LineGeometryImpl;
215 use crate::rectangle_geometry_impl::RectangleGeometryImpl;
216
217 let any = geometry.as_any();
218
219 if let Some(geometry) = any.downcast_ref::<SimpleGeometryImpl>() {
220 return Some(geometry);
221 }
222 if let Some(geometry) = any.downcast_ref::<TransformedGeometryImpl>() {
223 return Some(geometry);
224 }
225 if let Some(geometry) = any.downcast_ref::<RectangleGeometryImpl>() {
226 return Some(geometry);
227 }
228 if let Some(geometry) = any.downcast_ref::<EllipseGeometryImpl>() {
229 return Some(geometry);
230 }
231 if let Some(geometry) = any.downcast_ref::<LineGeometryImpl>() {
232 return Some(geometry);
233 }
234 if let Some(geometry) = any.downcast_ref::<GeometryGroupImpl>() {
235 return Some(geometry);
236 }
237 if let Some(geometry) = any.downcast_ref::<CombinedGeometryImpl>() {
238 return Some(geometry);
239 }
240
241 None
242}
243
244fn with_path_measure<R>(geometry: &dyn GeometryImpl, f: impl FnOnce(&PathMeasure) -> R) -> Option<R> {
245 let stroke_path = geometry.stroke_path()?;
246 let mut measure = geometry.base().cached_path_measure.lock().unwrap();
247 let measure = measure.get_or_insert_with(|| PathMeasure::new(stroke_path.path()));
248 Some(f(measure))
249}
250
251pub(crate) fn contour_length(geometry: &dyn GeometryImpl) -> f64 {
252 with_path_measure(geometry, |measure| measure.length()).unwrap_or(0.0)
253}
254
255pub(crate) fn fill_contains(geometry: &dyn GeometryImpl, point: Point) -> bool {
256 geometry.fill_path().is_some_and(|path| path.contains(point))
257}
258
259pub(crate) fn stroke_contains(geometry: &dyn GeometryImpl, pen: Option<&dyn IPen>, point: Point) -> bool {
260 let mut cache = geometry.base().path_cache.lock().unwrap();
261 cache.update_if_needed(geometry.stroke_path().as_ref(), pen);
262 cache.expanded_path().is_some_and(|path| path.contains(point))
263}
264
265pub(crate) fn intersect(geometry: &dyn GeometryImpl, other: &dyn IGeometryImpl) -> Option<Arc<dyn IGeometryImpl>> {
266 let other = try_get_geometry_impl(other)?;
267 CombinedGeometryImpl::try_create(GeometryCombineMode::Intersect, geometry, other)
268 .map(|geometry| geometry as Arc<dyn IGeometryImpl>)
269}
270
271pub(crate) fn get_render_bounds(geometry: &dyn GeometryImpl, pen: Option<&dyn IPen>) -> Rect {
272 let mut cache = geometry.base().path_cache.lock().unwrap();
273 cache.update_if_needed(geometry.stroke_path().as_ref(), pen);
274 let mut bounds = cache.render_bounds();
275
276 if let FillPath::Separate(fill_path) = geometry.fill() {
277 bounds = bounds.union(fill_path.tight_bounds());
278 }
279
280 bounds
281}
282
283pub(crate) fn get_widened_geometry(geometry: &dyn GeometryImpl, pen: &dyn IPen) -> Arc<dyn IGeometryImpl> {
284 if let Some(stroke_path) = geometry.stroke_path() {
285 if let Some(path) = path_helper::create_stroked_path(stroke_path.path(), pen) {
286 return SimpleGeometryImpl::new(
289 VelloPath::new(path, FillRule::NonZero),
290 FillPath::SameAsStroke,
291 None,
292 );
293 }
294 }
295
296 SimpleGeometryImpl::new(VelloPath::empty(), FillPath::None, None)
297}
298
299pub(crate) fn with_transform(geometry: &dyn GeometryImpl, transform: Matrix) -> Arc<dyn ITransformedGeometryImpl> {
300 TransformedGeometryImpl::new(geometry.base().to_arc(), transform)
301}
302
303pub(crate) fn try_get_point_at_distance(geometry: &dyn GeometryImpl, distance: f64) -> Option<Point> {
304 try_get_point_and_tangent_at_distance(geometry, distance).map(|(point, _)| point)
305}
306
307pub(crate) fn try_get_point_and_tangent_at_distance(
308 geometry: &dyn GeometryImpl,
309 distance: f64,
310) -> Option<(Point, Point)> {
311 let (point, tangent) = with_path_measure(geometry, |measure| measure.pos_tan(distance))??;
312 Some((to_point(point), Point::new(tangent.x, tangent.y)))
313}
314
315pub(crate) fn try_get_segment(
316 geometry: &dyn GeometryImpl,
317 start_distance: f64,
318 stop_distance: f64,
319 start_on_begin_figure: bool,
320) -> Option<Arc<dyn IGeometryImpl>> {
321 let segment = with_path_measure(geometry, |measure| {
322 measure.get_segment(start_distance, stop_distance, start_on_begin_figure)
323 })??;
324
325 Some(SimpleGeometryImpl::new(VelloPath::new(segment, FillRule::EvenOdd), FillPath::None, None))
326}
327
328pub(crate) fn get_fill_intersection_result(
329 geometry: &dyn GeometryImpl,
330 other: &dyn IGeometryImpl,
331) -> IntersectionResult {
332 let Some(other) = try_get_geometry_impl(other) else {
333 return IntersectionResult::Empty;
334 };
335
336 hit_test_path(geometry.fill_path().as_ref(), other.fill_path().as_ref())
337}
338
339fn hit_test_path(path1: Option<&VelloPath>, path2: Option<&VelloPath>) -> IntersectionResult {
346 use crate::combined_geometry_impl::{combine_paths, path_area};
347
348 let (Some(path1), Some(path2)) = (path1, path2) else {
349 return IntersectionResult::Empty;
350 };
351
352 const MINIMUM_AREA: f64 = 1e-6;
354
355 let has_area = |mode| {
356 combine_paths(mode, path1, path2).is_some_and(|path| path_area(path.path()) > MINIMUM_AREA)
357 };
358
359 if has_area(GeometryCombineMode::Intersect) {
360 let other_outside = combine_paths(GeometryCombineMode::Exclude, path2, path1)
361 .is_some_and(|path| path_area(path.path()) > MINIMUM_AREA);
362 if !other_outside {
363 return IntersectionResult::FullyInside;
364 }
365
366 if !has_area(GeometryCombineMode::Exclude) {
367 return IntersectionResult::FullyContains;
368 }
369
370 return IntersectionResult::Intersects;
371 }
372
373 IntersectionResult::Empty
374}
375
376#[derive(Default)]
379struct PathCache {
380 pen_hash: u64,
381 path: Option<VelloPath>,
382 cached_for: Option<VelloPath>,
384 render_bounds: Option<Rect>,
385}
386
387impl PathCache {
388 fn render_bounds(&mut self) -> Rect {
389 if let Some(bounds) = self.render_bounds {
390 return bounds;
391 }
392
393 let bounds = match (&self.path, &self.cached_for) {
394 (Some(path), _) => path.tight_bounds(),
395 (None, Some(cached_for)) => cached_for.tight_bounds(),
396 (None, None) => Rect::default(),
397 };
398 self.render_bounds = Some(bounds);
399 bounds
400 }
401
402 fn expanded_path(&self) -> Option<&VelloPath> {
403 self.path.as_ref()
404 }
405
406 fn update_if_needed(&mut self, stroke_path: Option<&VelloPath>, pen: Option<&dyn IPen>) {
407 let pen_hash = pen_helper::get_hash_code(pen, false);
408
409 let same_path = match (stroke_path, &self.cached_for) {
410 (Some(stroke_path), Some(cached_for)) => stroke_path.is_same(cached_for),
411 (None, None) => true,
412 _ => false,
413 };
414
415 if pen_hash == self.pen_hash && same_path {
416 return;
418 }
419
420 self.render_bounds = None;
421 self.cached_for = stroke_path.cloned();
422 self.pen_hash = pen_hash;
423
424 self.path = match (stroke_path, pen) {
425 (Some(stroke_path), Some(pen)) => path_helper::create_stroked_path(stroke_path.path(), pen)
426 .map(|path| VelloPath::new(path, FillRule::NonZero)),
427 _ => None,
428 };
429 }
430}
431
432macro_rules! impl_geometry_impl {
438 ($ty:ty) => {
439 $crate::geometry_impl::impl_geometry_impl!($ty, {});
440 };
441 ($ty:ty, { $($extra:tt)* }) => {
442 impl ferroui_base::platform::IGeometryImpl for $ty {
443 fn bounds(&self) -> ferroui_base::Rect {
444 self.geometry_bounds()
445 }
446
447 fn as_any(&self) -> &dyn std::any::Any {
448 self
449 }
450
451 fn contour_length(&self) -> f64 {
452 $crate::geometry_impl::contour_length(self)
453 }
454
455 fn get_render_bounds(&self, pen: Option<&dyn ferroui_base::media::IPen>) -> ferroui_base::Rect {
456 $crate::geometry_impl::get_render_bounds(self, pen)
457 }
458
459 fn get_widened_geometry(
460 &self,
461 pen: &dyn ferroui_base::media::IPen,
462 ) -> std::sync::Arc<dyn ferroui_base::platform::IGeometryImpl> {
463 $crate::geometry_impl::get_widened_geometry(self, pen)
464 }
465
466 fn fill_contains(&self, point: ferroui_base::Point) -> bool {
467 $crate::geometry_impl::fill_contains(self, point)
468 }
469
470 fn get_fill_intersection_result(
471 &self,
472 geometry: &dyn ferroui_base::platform::IGeometryImpl,
473 ) -> ferroui_base::media::IntersectionResult {
474 $crate::geometry_impl::get_fill_intersection_result(self, geometry)
475 }
476
477 fn intersect(
478 &self,
479 geometry: &dyn ferroui_base::platform::IGeometryImpl,
480 ) -> Option<std::sync::Arc<dyn ferroui_base::platform::IGeometryImpl>> {
481 $crate::geometry_impl::intersect(self, geometry)
482 }
483
484 fn stroke_contains(
485 &self,
486 pen: Option<&dyn ferroui_base::media::IPen>,
487 point: ferroui_base::Point,
488 ) -> bool {
489 $crate::geometry_impl::stroke_contains(self, pen, point)
490 }
491
492 fn with_transform(
493 &self,
494 transform: ferroui_base::Matrix,
495 ) -> std::sync::Arc<dyn ferroui_base::platform::ITransformedGeometryImpl> {
496 $crate::geometry_impl::with_transform(self, transform)
497 }
498
499 fn try_get_point_at_distance(&self, distance: f64) -> Option<ferroui_base::Point> {
500 $crate::geometry_impl::try_get_point_at_distance(self, distance)
501 }
502
503 fn try_get_point_and_tangent_at_distance(
504 &self,
505 distance: f64,
506 ) -> Option<(ferroui_base::Point, ferroui_base::Point)> {
507 $crate::geometry_impl::try_get_point_and_tangent_at_distance(self, distance)
508 }
509
510 fn try_get_segment(
511 &self,
512 start_distance: f64,
513 stop_distance: f64,
514 start_on_begin_figure: bool,
515 ) -> Option<std::sync::Arc<dyn ferroui_base::platform::IGeometryImpl>> {
516 $crate::geometry_impl::try_get_segment(self, start_distance, stop_distance, start_on_begin_figure)
517 }
518
519 $($extra)*
520 }
521 };
522}
523
524pub(crate) use impl_geometry_impl;