1use kurbo::{BezPath, PathEl, Rect};
4use siphasher::sip128::{Hasher128, SipHasher13};
5use std::hash::Hash;
6use std::ops::Sub;
7
8pub(crate) trait OptionLog {
9 fn warn_none(self, f: &str) -> Self;
10}
11
12impl<T> OptionLog for Option<T> {
13 #[inline]
14 fn warn_none(self, _f: &str) -> Self {
15 self.or_else(|| {
16 warn!("{_f}");
17
18 None
19 })
20 }
21}
22
23const SCALAR_NEARLY_ZERO: f32 = 1.0 / (1 << 8) as f32;
24
25pub trait Float32Ext: Sized + Sub<f32, Output = f32> + Copy + PartialOrd<f32> {
27 fn is_nearly_zero(&self) -> bool {
29 self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO)
30 }
31
32 fn is_nearly_equal(&self, other: f32) -> bool {
34 (*self - other).is_nearly_zero()
35 }
36
37 fn is_nearly_less_or_equal(&self, other: f32) -> bool {
39 (*self - other).is_nearly_zero() || *self < other
40 }
41
42 fn is_nearly_greater_or_equal(&self, other: f32) -> bool {
44 (*self - other).is_nearly_zero() || *self > other
45 }
46
47 fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool;
49}
50
51impl Float32Ext for f32 {
52 fn is_nearly_zero_within_tolerance(&self, tolerance: f32) -> bool {
53 debug_assert!(tolerance >= 0.0, "tolerance must be non-negative");
54
55 self.abs() <= tolerance
56 }
57}
58
59pub trait Float64Ext: Sized + Sub<f64, Output = f64> + Copy + PartialOrd<f64> {
61 fn is_nearly_zero(&self) -> bool {
63 self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO as f64)
64 }
65
66 fn is_nearly_equal(&self, other: f64) -> bool {
68 (*self - other).is_nearly_zero()
69 }
70
71 fn is_nearly_less_or_equal(&self, other: f64) -> bool {
73 (*self - other).is_nearly_zero() || *self < other
74 }
75
76 fn is_nearly_greater_or_equal(&self, other: f64) -> bool {
78 (*self - other).is_nearly_zero() || *self > other
79 }
80
81 fn is_nearly_zero_within_tolerance(&self, tolerance: f64) -> bool;
83}
84
85impl Float64Ext for f64 {
86 fn is_nearly_zero_within_tolerance(&self, tolerance: f64) -> bool {
87 debug_assert!(tolerance >= 0.0, "tolerance must be non-negative");
88
89 self.abs() <= tolerance
90 }
91}
92
93pub(crate) trait PointExt: Sized {
94 fn x(&self) -> f32;
95 fn y(&self) -> f32;
96
97 fn nearly_same(&self, other: Self) -> bool {
98 self.x().is_nearly_equal(other.x()) && self.y().is_nearly_equal(other.y())
99 }
100}
101
102impl PointExt for kurbo::Point {
103 fn x(&self) -> f32 {
104 self.x as f32
105 }
106
107 fn y(&self) -> f32 {
108 self.y as f32
109 }
110}
111
112pub(crate) fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
114 let mut state = SipHasher13::new();
115 value.hash(&mut state);
116 state.finish128().as_u128()
117}
118
119pub(crate) trait BezPathExt {
120 fn fast_bounding_box(&self) -> Rect;
121}
122
123impl BezPathExt for BezPath {
124 fn fast_bounding_box(&self) -> Rect {
125 let mut min_x = f64::INFINITY;
126 let mut min_y = f64::INFINITY;
127 let mut max_x = f64::NEG_INFINITY;
128 let mut max_y = f64::NEG_INFINITY;
129
130 let mut include = |x: f64, y: f64| {
131 min_x = min_x.min(x);
132 min_y = min_y.min(y);
133 max_x = max_x.max(x);
134 max_y = max_y.max(y);
135 };
136
137 for el in self.elements() {
138 match *el {
139 PathEl::MoveTo(p) | PathEl::LineTo(p) => include(p.x, p.y),
140 PathEl::QuadTo(p1, p2) => {
141 include(p1.x, p1.y);
142 include(p2.x, p2.y);
143 }
144 PathEl::CurveTo(p1, p2, p3) => {
145 include(p1.x, p1.y);
146 include(p2.x, p2.y);
147 include(p3.x, p3.y);
148 }
149 PathEl::ClosePath => {}
150 }
151 }
152
153 if min_x > max_x {
154 Rect::ZERO
155 } else {
156 Rect::new(min_x, min_y, max_x, max_y)
157 }
158 }
159}
160
161pub trait TransformExt {
163 fn to_kurbo(&self) -> kurbo::Affine;
165}
166
167impl TransformExt for hayro_syntax::transform::Transform {
168 fn to_kurbo(&self) -> kurbo::Affine {
169 kurbo::Affine::new(self.as_coeffs())
170 }
171}
172
173pub trait RectExt {
175 fn to_kurbo(&self) -> Rect;
177}
178
179impl RectExt for hayro_syntax::object::Rect {
180 fn to_kurbo(&self) -> Rect {
181 Rect::new(self.x0, self.y0, self.x1, self.y1)
182 }
183}