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