1#![allow(clippy::many_single_char_names)]
2#![allow(clippy::wrong_self_convention)] use std::ops::Range;
5
6use crate::{Color32, PathShape, PathStroke, Shape};
7use emath::{Pos2, Rect, RectTransform};
8
9#[derive(Clone, Debug, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16pub struct CubicBezierShape {
17    pub points: [Pos2; 4],
20    pub closed: bool,
21
22    pub fill: Color32,
23    pub stroke: PathStroke,
24}
25
26impl CubicBezierShape {
27    pub fn from_points_stroke(
32        points: [Pos2; 4],
33        closed: bool,
34        fill: Color32,
35        stroke: impl Into<PathStroke>,
36    ) -> Self {
37        Self {
38            points,
39            closed,
40            fill,
41            stroke: stroke.into(),
42        }
43    }
44
45    pub fn transform(&self, transform: &RectTransform) -> Self {
47        let mut points = [Pos2::default(); 4];
48        for (i, origin_point) in self.points.iter().enumerate() {
49            points[i] = transform * *origin_point;
50        }
51        Self {
52            points,
53            closed: self.closed,
54            fill: self.fill,
55            stroke: self.stroke.clone(),
56        }
57    }
58
59    pub fn to_path_shapes(&self, tolerance: Option<f32>, epsilon: Option<f32>) -> Vec<PathShape> {
65        let mut pathshapes = Vec::new();
66        let mut points_vec = self.flatten_closed(tolerance, epsilon);
67        for points in points_vec.drain(..) {
68            let pathshape = PathShape {
69                points,
70                closed: self.closed,
71                fill: self.fill,
72                stroke: self.stroke.clone(),
73            };
74            pathshapes.push(pathshape);
75        }
76        pathshapes
77    }
78
79    pub fn visual_bounding_rect(&self) -> Rect {
81        if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
82            Rect::NOTHING
83        } else {
84            self.logical_bounding_rect().expand(self.stroke.width / 2.0)
85        }
86    }
87
88    pub fn logical_bounding_rect(&self) -> Rect {
90        let (mut min_x, mut max_x) = if self.points[0].x < self.points[3].x {
92            (self.points[0].x, self.points[3].x)
93        } else {
94            (self.points[3].x, self.points[0].x)
95        };
96        let (mut min_y, mut max_y) = if self.points[0].y < self.points[3].y {
97            (self.points[0].y, self.points[3].y)
98        } else {
99            (self.points[3].y, self.points[0].y)
100        };
101
102        cubic_for_each_local_extremum(
104            self.points[0].x,
105            self.points[1].x,
106            self.points[2].x,
107            self.points[3].x,
108            &mut |t| {
109                let x = self.sample(t).x;
110                if x < min_x {
111                    min_x = x;
112                }
113                if x > max_x {
114                    max_x = x;
115                }
116            },
117        );
118
119        cubic_for_each_local_extremum(
121            self.points[0].y,
122            self.points[1].y,
123            self.points[2].y,
124            self.points[3].y,
125            &mut |t| {
126                let y = self.sample(t).y;
127                if y < min_y {
128                    min_y = y;
129                }
130                if y > max_y {
131                    max_y = y;
132                }
133            },
134        );
135
136        Rect {
137            min: Pos2 { x: min_x, y: min_y },
138            max: Pos2 { x: max_x, y: max_y },
139        }
140    }
141
142    pub fn split_range(&self, t_range: Range<f32>) -> Self {
144        debug_assert!(
145            0.0 <= t_range.start && t_range.end <= 1.0 && t_range.start <= t_range.end,
146            "range should be in [0.0,1.0]"
147        );
148
149        let from = self.sample(t_range.start);
150        let to = self.sample(t_range.end);
151
152        let d_from = self.points[1] - self.points[0].to_vec2();
153        let d_ctrl = self.points[2] - self.points[1].to_vec2();
154        let d_to = self.points[3] - self.points[2].to_vec2();
155        let q = QuadraticBezierShape {
156            points: [d_from, d_ctrl, d_to],
157            closed: self.closed,
158            fill: self.fill,
159            stroke: self.stroke.clone(),
160        };
161        let delta_t = t_range.end - t_range.start;
162        let q_start = q.sample(t_range.start);
163        let q_end = q.sample(t_range.end);
164        let ctrl1 = from + q_start.to_vec2() * delta_t;
165        let ctrl2 = to - q_end.to_vec2() * delta_t;
166
167        Self {
168            points: [from, ctrl1, ctrl2, to],
169            closed: self.closed,
170            fill: self.fill,
171            stroke: self.stroke.clone(),
172        }
173    }
174
175    pub fn num_quadratics(&self, tolerance: f32) -> u32 {
181        debug_assert!(tolerance > 0.0, "the tolerance should be positive");
182
183        let x =
184            self.points[0].x - 3.0 * self.points[1].x + 3.0 * self.points[2].x - self.points[3].x;
185        let y =
186            self.points[0].y - 3.0 * self.points[1].y + 3.0 * self.points[2].y - self.points[3].y;
187        let err = x * x + y * y;
188
189        (err / (432.0 * tolerance * tolerance))
190            .powf(1.0 / 6.0)
191            .ceil()
192            .max(1.0) as u32
193    }
194
195    pub fn find_cross_t(&self, epsilon: f32) -> Option<f32> {
234        let p0 = self.points[0];
235        let p1 = self.points[1];
236        let p2 = self.points[2];
237        let p3 = self.points[3];
238
239        let a = (p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x) * (p3.y - p0.y)
240            - (p3.y - 3.0 * p2.y + 3.0 * p1.y - p0.y) * (p3.x - p0.x);
241        let b = (3.0 * p2.x - 6.0 * p1.x + 3.0 * p0.x) * (p3.y - p0.y)
242            - (3.0 * p2.y - 6.0 * p1.y + 3.0 * p0.y) * (p3.x - p0.x);
243        let c =
244            (3.0 * p1.x - 3.0 * p0.x) * (p3.y - p0.y) - (3.0 * p1.y - 3.0 * p0.y) * (p3.x - p0.x);
245        let d = p0.x * (p3.y - p0.y) - p0.y * (p3.x - p0.x)
246            + p0.x * (p0.y - p3.y)
247            + p0.y * (p3.x - p0.x);
248
249        let h = -b / (3.0 * a);
250        let p = (3.0 * a * c - b * b) / (3.0 * a * a);
251        let q = (2.0 * b * b * b - 9.0 * a * b * c + 27.0 * a * a * d) / (27.0 * a * a * a);
252
253        if p > 0.0 {
254            return None;
255        }
256        let r = (-(p / 3.0).powi(3)).sqrt();
257        let theta = (-q / (2.0 * r)).acos() / 3.0;
258
259        let t1 = 2.0 * r.cbrt() * theta.cos() + h;
260        let t2 = 2.0 * r.cbrt() * (theta + 120.0 * std::f32::consts::PI / 180.0).cos() + h;
261        let t3 = 2.0 * r.cbrt() * (theta + 240.0 * std::f32::consts::PI / 180.0).cos() + h;
262
263        if t1 > epsilon && t1 < 1.0 - epsilon {
264            return Some(t1);
265        }
266        if t2 > epsilon && t2 < 1.0 - epsilon {
267            return Some(t2);
268        }
269        if t3 > epsilon && t3 < 1.0 - epsilon {
270            return Some(t3);
271        }
272        None
273    }
274
275    pub fn sample(&self, t: f32) -> Pos2 {
280        debug_assert!(
281            t >= 0.0 && t <= 1.0,
282            "the sample value should be in [0.0,1.0]"
283        );
284
285        let h = 1.0 - t;
286        let a = t * t * t;
287        let b = 3.0 * t * t * h;
288        let c = 3.0 * t * h * h;
289        let d = h * h * h;
290        let result = self.points[3].to_vec2() * a
291            + self.points[2].to_vec2() * b
292            + self.points[1].to_vec2() * c
293            + self.points[0].to_vec2() * d;
294        result.to_pos2()
295    }
296
297    pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
301        let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[3].x).abs() * 0.001);
302        let mut result = vec![self.points[0]];
303        self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
304            result.push(p);
305        });
306        result
307    }
308
309    pub fn flatten_closed(&self, tolerance: Option<f32>, epsilon: Option<f32>) -> Vec<Vec<Pos2>> {
316        let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[3].x).abs() * 0.001);
317        let epsilon = epsilon.unwrap_or(1.0e-5);
318        let mut result = Vec::new();
319        let mut first_half = Vec::new();
320        let mut second_half = Vec::new();
321        let mut flipped = false;
322        first_half.push(self.points[0]);
323
324        let cross = self.find_cross_t(epsilon);
325        match cross {
326            Some(cross) => {
327                if self.closed {
328                    self.for_each_flattened_with_t(tolerance, &mut |p, t| {
329                        if t < cross {
330                            first_half.push(p);
331                        } else {
332                            if !flipped {
333                                flipped = true;
337                                let cross_point = self.sample(cross);
338                                first_half.push(cross_point);
339                                second_half.push(cross_point);
340                            }
341                            second_half.push(p);
342                        }
343                    });
344                } else {
345                    self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
346                        first_half.push(p);
347                    });
348                }
349            }
350            None => {
351                self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
352                    first_half.push(p);
353                });
354            }
355        }
356
357        result.push(first_half);
358        if !second_half.is_empty() {
359            result.push(second_half);
360        }
361        result
362    }
363    pub fn for_each_flattened_with_t<F: FnMut(Pos2, f32)>(&self, tolerance: f32, callback: &mut F) {
366        flatten_cubic_bezier_with_t(self, tolerance, callback);
367    }
368}
369
370impl From<CubicBezierShape> for Shape {
371    #[inline(always)]
372    fn from(shape: CubicBezierShape) -> Self {
373        Self::CubicBezier(shape)
374    }
375}
376
377#[derive(Clone, Debug, PartialEq)]
383#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
384pub struct QuadraticBezierShape {
385    pub points: [Pos2; 3],
388    pub closed: bool,
389
390    pub fill: Color32,
391    pub stroke: PathStroke,
392}
393
394impl QuadraticBezierShape {
395    pub fn from_points_stroke(
401        points: [Pos2; 3],
402        closed: bool,
403        fill: Color32,
404        stroke: impl Into<PathStroke>,
405    ) -> Self {
406        Self {
407            points,
408            closed,
409            fill,
410            stroke: stroke.into(),
411        }
412    }
413
414    pub fn transform(&self, transform: &RectTransform) -> Self {
416        let mut points = [Pos2::default(); 3];
417        for (i, origin_point) in self.points.iter().enumerate() {
418            points[i] = transform * *origin_point;
419        }
420        Self {
421            points,
422            closed: self.closed,
423            fill: self.fill,
424            stroke: self.stroke.clone(),
425        }
426    }
427
428    pub fn to_path_shape(&self, tolerance: Option<f32>) -> PathShape {
431        let points = self.flatten(tolerance);
432        PathShape {
433            points,
434            closed: self.closed,
435            fill: self.fill,
436            stroke: self.stroke.clone(),
437        }
438    }
439
440    pub fn visual_bounding_rect(&self) -> Rect {
442        if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
443            Rect::NOTHING
444        } else {
445            self.logical_bounding_rect().expand(self.stroke.width / 2.0)
446        }
447    }
448
449    pub fn logical_bounding_rect(&self) -> Rect {
451        let (mut min_x, mut max_x) = if self.points[0].x < self.points[2].x {
452            (self.points[0].x, self.points[2].x)
453        } else {
454            (self.points[2].x, self.points[0].x)
455        };
456        let (mut min_y, mut max_y) = if self.points[0].y < self.points[2].y {
457            (self.points[0].y, self.points[2].y)
458        } else {
459            (self.points[2].y, self.points[0].y)
460        };
461
462        quadratic_for_each_local_extremum(
463            self.points[0].x,
464            self.points[1].x,
465            self.points[2].x,
466            &mut |t| {
467                let x = self.sample(t).x;
468                if x < min_x {
469                    min_x = x;
470                }
471                if x > max_x {
472                    max_x = x;
473                }
474            },
475        );
476
477        quadratic_for_each_local_extremum(
478            self.points[0].y,
479            self.points[1].y,
480            self.points[2].y,
481            &mut |t| {
482                let y = self.sample(t).y;
483                if y < min_y {
484                    min_y = y;
485                }
486                if y > max_y {
487                    max_y = y;
488                }
489            },
490        );
491
492        Rect {
493            min: Pos2 { x: min_x, y: min_y },
494            max: Pos2 { x: max_x, y: max_y },
495        }
496    }
497
498    pub fn sample(&self, t: f32) -> Pos2 {
503        debug_assert!(
504            t >= 0.0 && t <= 1.0,
505            "the sample value should be in [0.0,1.0]"
506        );
507
508        let h = 1.0 - t;
509        let a = t * t;
510        let b = 2.0 * t * h;
511        let c = h * h;
512        let result = self.points[2].to_vec2() * a
513            + self.points[1].to_vec2() * b
514            + self.points[0].to_vec2() * c;
515        result.to_pos2()
516    }
517
518    pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
522        let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[2].x).abs() * 0.001);
523        let mut result = vec![self.points[0]];
524        self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
525            result.push(p);
526        });
527        result
528    }
529
530    pub fn for_each_flattened_with_t<F>(&self, tolerance: f32, callback: &mut F)
539    where
540        F: FnMut(Pos2, f32),
541    {
542        let params = FlatteningParameters::from_curve(self, tolerance);
543        if params.is_point {
544            return;
545        }
546
547        let count = params.count as u32;
548        for index in 1..count {
549            let t = params.t_at_iteration(index as f32);
550
551            callback(self.sample(t), t);
552        }
553
554        callback(self.sample(1.0), 1.0);
555    }
556}
557
558impl From<QuadraticBezierShape> for Shape {
559    #[inline(always)]
560    fn from(shape: QuadraticBezierShape) -> Self {
561        Self::QuadraticBezier(shape)
562    }
563}
564
565fn flatten_cubic_bezier_with_t<F: FnMut(Pos2, f32)>(
570    curve: &CubicBezierShape,
571    tolerance: f32,
572    callback: &mut F,
573) {
574    let quadratics_tolerance = tolerance * 0.2;
576    let flattening_tolerance = tolerance * 0.8;
577
578    let num_quadratics = curve.num_quadratics(quadratics_tolerance);
579    let step = 1.0 / num_quadratics as f32;
580    let n = num_quadratics;
581    let mut t0 = 0.0;
582    for _ in 0..(n - 1) {
583        let t1 = t0 + step;
584
585        let quadratic = single_curve_approximation(&curve.split_range(t0..t1));
586        quadratic.for_each_flattened_with_t(flattening_tolerance, &mut |point, t_sub| {
587            let t = t0 + step * t_sub;
588            callback(point, t);
589        });
590
591        t0 = t1;
592    }
593
594    let quadratic = single_curve_approximation(&curve.split_range(t0..1.0));
596    quadratic.for_each_flattened_with_t(flattening_tolerance, &mut |point, t_sub| {
597        let t = t0 + step * t_sub;
598        callback(point, t);
599    });
600}
601
602struct FlatteningParameters {
605    count: f32,
606    integral_from: f32,
607    integral_step: f32,
608    inv_integral_from: f32,
609    div_inv_integral_diff: f32,
610    is_point: bool,
611}
612
613impl FlatteningParameters {
614    pub fn from_curve(curve: &QuadraticBezierShape, tolerance: f32) -> Self {
616        let from = curve.points[0];
618        let ctrl = curve.points[1];
619        let to = curve.points[2];
620
621        let ddx = 2.0 * ctrl.x - from.x - to.x;
622        let ddy = 2.0 * ctrl.y - from.y - to.y;
623        let cross = (to.x - from.x) * ddy - (to.y - from.y) * ddx;
624        let inv_cross = 1.0 / cross;
625        let parabola_from = ((ctrl.x - from.x) * ddx + (ctrl.y - from.y) * ddy) * inv_cross;
626        let parabola_to = ((to.x - ctrl.x) * ddx + (to.y - ctrl.y) * ddy) * inv_cross;
627        let scale = cross.abs() / (ddx.hypot(ddy) * (parabola_to - parabola_from).abs());
631
632        let integral_from = approx_parabola_integral(parabola_from);
633        let integral_to = approx_parabola_integral(parabola_to);
634        let integral_diff = integral_to - integral_from;
635
636        let inv_integral_from = approx_parabola_inv_integral(integral_from);
637        let inv_integral_to = approx_parabola_inv_integral(integral_to);
638        let div_inv_integral_diff = 1.0 / (inv_integral_to - inv_integral_from);
639
640        let mut count = (0.5 * integral_diff.abs() * (scale / tolerance).sqrt()).ceil();
643        let mut is_point = false;
644        if !count.is_finite() {
646            count = 0.0;
647            is_point = (to.x - from.x).hypot(to.y - from.y) < tolerance * tolerance;
648        }
649
650        let integral_step = integral_diff / count;
651
652        Self {
653            count,
654            integral_from,
655            integral_step,
656            inv_integral_from,
657            div_inv_integral_diff,
658            is_point,
659        }
660    }
661
662    fn t_at_iteration(&self, iteration: f32) -> f32 {
663        let u = approx_parabola_inv_integral(self.integral_from + self.integral_step * iteration);
664        (u - self.inv_integral_from) * self.div_inv_integral_diff
665    }
666}
667
668fn approx_parabola_integral(x: f32) -> f32 {
670    let d: f32 = 0.67;
671    let quarter = 0.25;
672    x / (1.0 - d + (d.powi(4) + quarter * x * x).sqrt().sqrt())
673}
674
675fn approx_parabola_inv_integral(x: f32) -> f32 {
677    let b = 0.39;
678    let quarter = 0.25;
679    x * (1.0 - b + (b * b + quarter * x * x).sqrt())
680}
681
682fn single_curve_approximation(curve: &CubicBezierShape) -> QuadraticBezierShape {
683    let c1_x = (curve.points[1].x * 3.0 - curve.points[0].x) * 0.5;
684    let c1_y = (curve.points[1].y * 3.0 - curve.points[0].y) * 0.5;
685    let c2_x = (curve.points[2].x * 3.0 - curve.points[3].x) * 0.5;
686    let c2_y = (curve.points[2].y * 3.0 - curve.points[3].y) * 0.5;
687    let c = Pos2 {
688        x: (c1_x + c2_x) * 0.5,
689        y: (c1_y + c2_y) * 0.5,
690    };
691    QuadraticBezierShape {
692        points: [curve.points[0], c, curve.points[3]],
693        closed: curve.closed,
694        fill: curve.fill,
695        stroke: curve.stroke.clone(),
696    }
697}
698
699fn quadratic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, cb: &mut F) {
700    let a = p2 - 2.0 * p1 + p0;
706    if a == 0.0 {
709        return;
710    }
711
712    let t = (p0 - p1) / a;
713    if t > 0.0 && t < 1.0 {
714        cb(t);
715    }
716}
717
718fn cubic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, p3: f32, cb: &mut F) {
719    let a = 3.0 * (p3 + 3.0 * (p1 - p2) - p0);
724    let b = 6.0 * (p2 - 2.0 * p1 + p0);
725    let c = 3.0 * (p1 - p0);
726
727    let in_range = |t: f32| t <= 1.0 && t >= 0.0;
728
729    if a == 0.0 {
731        if b != 0.0 {
732            let t = -c / b;
733            if in_range(t) {
734                cb(t);
735            }
736        }
737        return;
738    }
739
740    let discr = b * b - 4.0 * a * c;
741    if discr < 0.0 {
743        return;
744    }
745
746    if discr == 0.0 {
748        let t = -b / (2.0 * a);
749        if in_range(t) {
750            cb(t);
751        }
752        return;
753    }
754
755    let discr = discr.sqrt();
757    let t1 = (-b - discr) / (2.0 * a);
758    let t2 = (-b + discr) / (2.0 * a);
759    if in_range(t1) {
760        cb(t1);
761    }
762    if in_range(t2) {
763        cb(t2);
764    }
765}
766
767#[cfg(test)]
768mod tests {
769    use emath::pos2;
770
771    use super::*;
772
773    #[test]
774    fn test_quadratic_bounding_box() {
775        let curve = QuadraticBezierShape {
776            points: [
777                Pos2 { x: 110.0, y: 170.0 },
778                Pos2 { x: 10.0, y: 10.0 },
779                Pos2 { x: 180.0, y: 30.0 },
780            ],
781            closed: false,
782            fill: Default::default(),
783            stroke: Default::default(),
784        };
785        let bbox = curve.logical_bounding_rect();
786        assert!((bbox.min.x - 72.96).abs() < 0.01);
787        assert!((bbox.min.y - 27.78).abs() < 0.01);
788
789        assert!((bbox.max.x - 180.0).abs() < 0.01);
790        assert!((bbox.max.y - 170.0).abs() < 0.01);
791
792        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
794            result.push(pos);
795        });
796
797        assert_eq!(result.len(), 26);
798
799        let curve = QuadraticBezierShape {
800            points: [
801                Pos2 { x: 110.0, y: 170.0 },
802                Pos2 { x: 180.0, y: 30.0 },
803                Pos2 { x: 10.0, y: 10.0 },
804            ],
805            closed: false,
806            fill: Default::default(),
807            stroke: Default::default(),
808        };
809        let bbox = curve.logical_bounding_rect();
810        assert!((bbox.min.x - 10.0).abs() < 0.01);
811        assert!((bbox.min.y - 10.0).abs() < 0.01);
812
813        assert!((bbox.max.x - 130.42).abs() < 0.01);
814        assert!((bbox.max.y - 170.0).abs() < 0.01);
815
816        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
818            result.push(pos);
819        });
820
821        assert_eq!(result.len(), 25);
822    }
823
824    #[test]
825    fn test_quadratic_different_tolerance() {
826        let curve = QuadraticBezierShape {
827            points: [
828                Pos2 { x: 110.0, y: 170.0 },
829                Pos2 { x: 180.0, y: 30.0 },
830                Pos2 { x: 10.0, y: 10.0 },
831            ],
832            closed: false,
833            fill: Default::default(),
834            stroke: Default::default(),
835        };
836        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
838            result.push(pos);
839        });
840
841        assert_eq!(result.len(), 9);
842
843        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
845            result.push(pos);
846        });
847
848        assert_eq!(result.len(), 25);
849
850        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
852            result.push(pos);
853        });
854
855        assert_eq!(result.len(), 77);
856
857        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
859            result.push(pos);
860        });
861
862        assert_eq!(result.len(), 240);
863    }
864
865    #[test]
866    fn test_cubic_bounding_box() {
867        let curve = CubicBezierShape {
868            points: [
869                pos2(10.0, 10.0),
870                pos2(110.0, 170.0),
871                pos2(180.0, 30.0),
872                pos2(270.0, 210.0),
873            ],
874            closed: false,
875            fill: Default::default(),
876            stroke: Default::default(),
877        };
878
879        let bbox = curve.logical_bounding_rect();
880        assert_eq!(bbox.min.x, 10.0);
881        assert_eq!(bbox.min.y, 10.0);
882        assert_eq!(bbox.max.x, 270.0);
883        assert_eq!(bbox.max.y, 210.0);
884
885        let curve = CubicBezierShape {
886            points: [
887                pos2(10.0, 10.0),
888                pos2(110.0, 170.0),
889                pos2(270.0, 210.0),
890                pos2(180.0, 30.0),
891            ],
892            closed: false,
893            fill: Default::default(),
894            stroke: Default::default(),
895        };
896
897        let bbox = curve.logical_bounding_rect();
898        assert_eq!(bbox.min.x, 10.0);
899        assert_eq!(bbox.min.y, 10.0);
900        assert!((bbox.max.x - 206.50).abs() < 0.01);
901        assert!((bbox.max.y - 148.48).abs() < 0.01);
902
903        let curve = CubicBezierShape {
904            points: [
905                pos2(110.0, 170.0),
906                pos2(10.0, 10.0),
907                pos2(270.0, 210.0),
908                pos2(180.0, 30.0),
909            ],
910            closed: false,
911            fill: Default::default(),
912            stroke: Default::default(),
913        };
914
915        let bbox = curve.logical_bounding_rect();
916        assert!((bbox.min.x - 86.71).abs() < 0.01);
917        assert!((bbox.min.y - 30.0).abs() < 0.01);
918
919        assert!((bbox.max.x - 199.27).abs() < 0.01);
920        assert!((bbox.max.y - 170.0).abs() < 0.01);
921    }
922
923    #[test]
924    fn test_cubic_different_tolerance_flattening() {
925        let curve = CubicBezierShape {
926            points: [
927                pos2(0.0, 0.0),
928                pos2(100.0, 0.0),
929                pos2(100.0, 100.0),
930                pos2(100.0, 200.0),
931            ],
932            closed: false,
933            fill: Default::default(),
934            stroke: Default::default(),
935        };
936
937        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
939            result.push(pos);
940        });
941
942        assert_eq!(result.len(), 10);
943
944        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
946            result.push(pos);
947        });
948
949        assert_eq!(result.len(), 13);
950
951        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
953            result.push(pos);
954        });
955
956        assert_eq!(result.len(), 28);
957
958        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
960            result.push(pos);
961        });
962
963        assert_eq!(result.len(), 83);
964
965        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
967            result.push(pos);
968        });
969
970        assert_eq!(result.len(), 248);
971    }
972
973    #[test]
974    fn test_cubic_different_shape_flattening() {
975        let curve = CubicBezierShape {
976            points: [
977                pos2(90.0, 110.0),
978                pos2(30.0, 170.0),
979                pos2(210.0, 170.0),
980                pos2(170.0, 110.0),
981            ],
982            closed: false,
983            fill: Default::default(),
984            stroke: Default::default(),
985        };
986
987        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
989            result.push(pos);
990        });
991
992        assert_eq!(result.len(), 117);
993
994        let curve = CubicBezierShape {
995            points: [
996                pos2(90.0, 110.0),
997                pos2(90.0, 170.0),
998                pos2(170.0, 170.0),
999                pos2(170.0, 110.0),
1000            ],
1001            closed: false,
1002            fill: Default::default(),
1003            stroke: Default::default(),
1004        };
1005
1006        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1008            result.push(pos);
1009        });
1010
1011        assert_eq!(result.len(), 91);
1012
1013        let curve = CubicBezierShape {
1014            points: [
1015                pos2(90.0, 110.0),
1016                pos2(110.0, 170.0),
1017                pos2(150.0, 170.0),
1018                pos2(170.0, 110.0),
1019            ],
1020            closed: false,
1021            fill: Default::default(),
1022            stroke: Default::default(),
1023        };
1024
1025        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1027            result.push(pos);
1028        });
1029
1030        assert_eq!(result.len(), 75);
1031
1032        let curve = CubicBezierShape {
1033            points: [
1034                pos2(90.0, 110.0),
1035                pos2(110.0, 170.0),
1036                pos2(230.0, 110.0),
1037                pos2(170.0, 110.0),
1038            ],
1039            closed: false,
1040            fill: Default::default(),
1041            stroke: Default::default(),
1042        };
1043
1044        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1046            result.push(pos);
1047        });
1048
1049        assert_eq!(result.len(), 100);
1050
1051        let curve = CubicBezierShape {
1052            points: [
1053                pos2(90.0, 110.0),
1054                pos2(110.0, 170.0),
1055                pos2(210.0, 70.0),
1056                pos2(170.0, 110.0),
1057            ],
1058            closed: false,
1059            fill: Default::default(),
1060            stroke: Default::default(),
1061        };
1062
1063        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1065            result.push(pos);
1066        });
1067
1068        assert_eq!(result.len(), 71);
1069
1070        let curve = CubicBezierShape {
1071            points: [
1072                pos2(90.0, 110.0),
1073                pos2(110.0, 170.0),
1074                pos2(150.0, 50.0),
1075                pos2(170.0, 110.0),
1076            ],
1077            closed: false,
1078            fill: Default::default(),
1079            stroke: Default::default(),
1080        };
1081
1082        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1084            result.push(pos);
1085        });
1086
1087        assert_eq!(result.len(), 88);
1088    }
1089
1090    #[test]
1091    fn test_quadratic_flattening() {
1092        let curve = QuadraticBezierShape {
1093            points: [pos2(0.0, 0.0), pos2(80.0, 200.0), pos2(100.0, 30.0)],
1094            closed: false,
1095            fill: Default::default(),
1096            stroke: Default::default(),
1097        };
1098
1099        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
1101            result.push(pos);
1102        });
1103
1104        assert_eq!(result.len(), 9);
1105
1106        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
1108            result.push(pos);
1109        });
1110
1111        assert_eq!(result.len(), 11);
1112
1113        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
1115            result.push(pos);
1116        });
1117
1118        assert_eq!(result.len(), 24);
1119
1120        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1122            result.push(pos);
1123        });
1124
1125        assert_eq!(result.len(), 72);
1126        let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
1128            result.push(pos);
1129        });
1130
1131        assert_eq!(result.len(), 223);
1132    }
1133}