1use tiny_skia_path::{NormalizedF32, NormalizedF32Exclusive, Point};
8
9#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
10use tiny_skia_path::NoStdFloat;
11
12pub use tiny_skia_path::path_geometry::{
13 chop_cubic_at2, chop_quad_at, find_cubic_max_curvature, find_unit_quad_roots, new_t_values,
14 CubicCoeff, QuadCoeff,
15};
16
17use tiny_skia_path::path_geometry::valid_unit_divide;
18
19pub fn chop_quad_at_x_extrema(src: &[Point; 3], dst: &mut [Point; 5]) -> usize {
24 let a = src[0].x;
25 let mut b = src[1].x;
26 let c = src[2].x;
27
28 if is_not_monotonic(a, b, c) {
29 if let Some(t_value) = valid_unit_divide(a - b, a - b - b + c) {
30 chop_quad_at(src, t_value, dst);
31
32 dst[1].x = dst[2].x;
34 dst[3].x = dst[2].x;
35
36 return 1;
37 }
38
39 b = if (a - b).abs() < (b - c).abs() { a } else { c };
42 }
43
44 dst[0] = Point::from_xy(a, src[0].y);
45 dst[1] = Point::from_xy(b, src[1].y);
46 dst[2] = Point::from_xy(c, src[2].y);
47 0
48}
49
50pub fn chop_quad_at_y_extrema(src: &[Point; 3], dst: &mut [Point; 5]) -> usize {
54 let a = src[0].y;
55 let mut b = src[1].y;
56 let c = src[2].y;
57
58 if is_not_monotonic(a, b, c) {
59 if let Some(t_value) = valid_unit_divide(a - b, a - b - b + c) {
60 chop_quad_at(src, t_value, dst);
61
62 dst[1].y = dst[2].y;
64 dst[3].y = dst[2].y;
65
66 return 1;
67 }
68
69 b = if (a - b).abs() < (b - c).abs() { a } else { c };
72 }
73
74 dst[0] = Point::from_xy(src[0].x, a);
75 dst[1] = Point::from_xy(src[1].x, b);
76 dst[2] = Point::from_xy(src[2].x, c);
77 0
78}
79
80fn is_not_monotonic(a: f32, b: f32, c: f32) -> bool {
81 let ab = a - b;
82 let mut bc = b - c;
83 if ab < 0.0 {
84 bc = -bc;
85 }
86
87 ab == 0.0 || bc < 0.0
88}
89
90pub fn chop_cubic_at_x_extrema(src: &[Point; 4], dst: &mut [Point; 10]) -> usize {
91 let mut t_values = new_t_values();
92 let t_values = find_cubic_extrema(src[0].x, src[1].x, src[2].x, src[3].x, &mut t_values);
93
94 chop_cubic_at(src, t_values, dst);
95 if !t_values.is_empty() {
96 dst[2].x = dst[3].x;
98 dst[4].x = dst[3].x;
99 if t_values.len() == 2 {
100 dst[5].x = dst[6].x;
101 dst[7].x = dst[6].x;
102 }
103 }
104
105 t_values.len()
106}
107
108pub fn chop_cubic_at_y_extrema(src: &[Point; 4], dst: &mut [Point; 10]) -> usize {
119 let mut t_values = new_t_values();
120 let t_values = find_cubic_extrema(src[0].y, src[1].y, src[2].y, src[3].y, &mut t_values);
121
122 chop_cubic_at(src, t_values, dst);
123 if !t_values.is_empty() {
124 dst[2].y = dst[3].y;
126 dst[4].y = dst[3].y;
127 if t_values.len() == 2 {
128 dst[5].y = dst[6].y;
129 dst[7].y = dst[6].y;
130 }
131 }
132
133 t_values.len()
134}
135
136fn find_cubic_extrema(
142 a: f32,
143 b: f32,
144 c: f32,
145 d: f32,
146 t_values: &mut [NormalizedF32Exclusive; 3],
147) -> &[NormalizedF32Exclusive] {
148 let na = d - a + 3.0 * (b - c);
150 let nb = 2.0 * (a - b - b + c);
151 let nc = b - a;
152
153 let roots = find_unit_quad_roots(na, nb, nc, t_values);
154 &t_values[0..roots]
155}
156
157pub fn chop_cubic_at(src: &[Point; 4], t_values: &[NormalizedF32Exclusive], dst: &mut [Point]) {
167 if t_values.is_empty() {
168 dst[0] = src[0];
170 dst[1] = src[1];
171 dst[2] = src[2];
172 dst[3] = src[3];
173 } else {
174 let mut t = t_values[0];
175 let mut tmp = [Point::zero(); 4];
176
177 let mut src = src;
179
180 let mut dst_offset = 0;
181 for i in 0..t_values.len() {
182 chop_cubic_at2(src, t, &mut dst[dst_offset..]);
183 if i == t_values.len() - 1 {
184 break;
185 }
186
187 dst_offset += 3;
188 tmp[0] = dst[dst_offset + 0];
190 tmp[1] = dst[dst_offset + 1];
191 tmp[2] = dst[dst_offset + 2];
192 tmp[3] = dst[dst_offset + 3];
193 src = &tmp;
194
195 let n = valid_unit_divide(
197 t_values[i + 1].get() - t_values[i].get(),
198 1.0 - t_values[i].get(),
199 );
200
201 match n {
202 Some(n) => t = n,
203 None => {
204 dst[dst_offset + 4] = src[3];
206 dst[dst_offset + 5] = src[3];
207 dst[dst_offset + 6] = src[3];
208 break;
209 }
210 }
211 }
212 }
213}
214
215pub fn chop_cubic_at_max_curvature(
216 src: &[Point; 4],
217 t_values: &mut [NormalizedF32Exclusive; 3],
218 dst: &mut [Point],
219) -> usize {
220 let mut roots = [NormalizedF32::ZERO; 3];
221 let roots = find_cubic_max_curvature(src, &mut roots);
222
223 let mut count = 0;
225 for root in roots {
226 if 0.0 < root.get() && root.get() < 1.0 {
227 t_values[count] = NormalizedF32Exclusive::new_bounded(root.get());
228 count += 1;
229 }
230 }
231
232 if count == 0 {
233 dst[0..4].copy_from_slice(src);
234 } else {
235 chop_cubic_at(src, &t_values[0..count], dst);
236 }
237
238 count + 1
239}
240
241pub fn chop_mono_cubic_at_x(src: &[Point; 4], x: f32, dst: &mut [Point; 7]) -> bool {
242 cubic_dchop_at_intercept(src, x, true, dst)
243}
244
245pub fn chop_mono_cubic_at_y(src: &[Point; 4], y: f32, dst: &mut [Point; 7]) -> bool {
246 cubic_dchop_at_intercept(src, y, false, dst)
247}
248
249fn cubic_dchop_at_intercept(
250 src: &[Point; 4],
251 intercept: f32,
252 is_vertical: bool,
253 dst: &mut [Point; 7],
254) -> bool {
255 use crate::path64::{cubic64::Cubic64, line_cubic_intersections, point64::Point64};
256
257 let src = [
258 Point64::from_point(src[0]),
259 Point64::from_point(src[1]),
260 Point64::from_point(src[2]),
261 Point64::from_point(src[3]),
262 ];
263
264 let cubic = Cubic64::new(src);
265 let mut roots = [0.0; 3];
266 let count = if is_vertical {
267 line_cubic_intersections::vertical_intersect(&cubic, f64::from(intercept), &mut roots)
268 } else {
269 line_cubic_intersections::horizontal_intersect(&cubic, f64::from(intercept), &mut roots)
270 };
271
272 if count > 0 {
273 let pair = cubic.chop_at(roots[0]);
274 for i in 0..7 {
275 dst[i] = pair.points[i].to_point();
276 }
277
278 true
279 } else {
280 false
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn chop_cubic_at_y_extrema_1() {
290 let src = [
291 Point::from_xy(10.0, 20.0),
292 Point::from_xy(67.0, 437.0),
293 Point::from_xy(298.0, 213.0),
294 Point::from_xy(401.0, 214.0),
295 ];
296
297 let mut dst = [Point::zero(); 10];
298 let n = chop_cubic_at_y_extrema(&src, &mut dst);
299 assert_eq!(n, 2);
300 assert_eq!(dst[0], Point::from_xy(10.0, 20.0));
301 assert_eq!(dst[1], Point::from_xy(37.508274, 221.24475));
302 assert_eq!(dst[2], Point::from_xy(105.541855, 273.19803));
303 assert_eq!(dst[3], Point::from_xy(180.15599, 273.19803));
304 assert_eq!(dst[4], Point::from_xy(259.80502, 273.19803));
305 assert_eq!(dst[5], Point::from_xy(346.9527, 213.99666));
306 assert_eq!(dst[6], Point::from_xy(400.30844, 213.99666));
307 assert_eq!(dst[7], Point::from_xy(400.53958, 213.99666));
308 assert_eq!(dst[8], Point::from_xy(400.7701, 213.99777));
309 assert_eq!(dst[9], Point::from_xy(401.0, 214.0));
310 }
311}