1#![allow(clippy::many_single_char_names)]
2#![allow(clippy::wrong_self_convention)] use std::ops::Range;
5
6use crate::{shape::Shape, Color32, PathShape, PathStroke};
7use emath::*;
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> {
230 let p0 = self.points[0];
231 let p1 = self.points[1];
232 let p2 = self.points[2];
233 let p3 = self.points[3];
234
235 let a = (p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x) * (p3.y - p0.y)
236 - (p3.y - 3.0 * p2.y + 3.0 * p1.y - p0.y) * (p3.x - p0.x);
237 let b = (3.0 * p2.x - 6.0 * p1.x + 3.0 * p0.x) * (p3.y - p0.y)
238 - (3.0 * p2.y - 6.0 * p1.y + 3.0 * p0.y) * (p3.x - p0.x);
239 let c =
240 (3.0 * p1.x - 3.0 * p0.x) * (p3.y - p0.y) - (3.0 * p1.y - 3.0 * p0.y) * (p3.x - p0.x);
241 let d = p0.x * (p3.y - p0.y) - p0.y * (p3.x - p0.x)
242 + p0.x * (p0.y - p3.y)
243 + p0.y * (p3.x - p0.x);
244
245 let h = -b / (3.0 * a);
246 let p = (3.0 * a * c - b * b) / (3.0 * a * a);
247 let q = (2.0 * b * b * b - 9.0 * a * b * c + 27.0 * a * a * d) / (27.0 * a * a * a);
248
249 if p > 0.0 {
250 return None;
251 }
252 let r = (-1.0 * (p / 3.0).powi(3)).sqrt();
253 let theta = (-1.0 * q / (2.0 * r)).acos() / 3.0;
254
255 let t1 = 2.0 * r.cbrt() * theta.cos() + h;
256 let t2 = 2.0 * r.cbrt() * (theta + 120.0 * std::f32::consts::PI / 180.0).cos() + h;
257 let t3 = 2.0 * r.cbrt() * (theta + 240.0 * std::f32::consts::PI / 180.0).cos() + h;
258
259 if t1 > epsilon && t1 < 1.0 - epsilon {
260 return Some(t1);
261 }
262 if t2 > epsilon && t2 < 1.0 - epsilon {
263 return Some(t2);
264 }
265 if t3 > epsilon && t3 < 1.0 - epsilon {
266 return Some(t3);
267 }
268 None
269 }
270
271 pub fn sample(&self, t: f32) -> Pos2 {
276 debug_assert!(
277 t >= 0.0 && t <= 1.0,
278 "the sample value should be in [0.0,1.0]"
279 );
280
281 let h = 1.0 - t;
282 let a = t * t * t;
283 let b = 3.0 * t * t * h;
284 let c = 3.0 * t * h * h;
285 let d = h * h * h;
286 let result = self.points[3].to_vec2() * a
287 + self.points[2].to_vec2() * b
288 + self.points[1].to_vec2() * c
289 + self.points[0].to_vec2() * d;
290 result.to_pos2()
291 }
292
293 pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
297 let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[3].x).abs() * 0.001);
298 let mut result = vec![self.points[0]];
299 self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
300 result.push(p);
301 });
302 result
303 }
304
305 pub fn flatten_closed(&self, tolerance: Option<f32>, epsilon: Option<f32>) -> Vec<Vec<Pos2>> {
312 let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[3].x).abs() * 0.001);
313 let epsilon = epsilon.unwrap_or(1.0e-5);
314 let mut result = Vec::new();
315 let mut first_half = Vec::new();
316 let mut second_half = Vec::new();
317 let mut flipped = false;
318 first_half.push(self.points[0]);
319
320 let cross = self.find_cross_t(epsilon);
321 match cross {
322 Some(cross) => {
323 if self.closed {
324 self.for_each_flattened_with_t(tolerance, &mut |p, t| {
325 if t < cross {
326 first_half.push(p);
327 } else {
328 if !flipped {
329 flipped = true;
333 let cross_point = self.sample(cross);
334 first_half.push(cross_point);
335 second_half.push(cross_point);
336 }
337 second_half.push(p);
338 }
339 });
340 } else {
341 self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
342 first_half.push(p);
343 });
344 }
345 }
346 None => {
347 self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
348 first_half.push(p);
349 });
350 }
351 }
352
353 result.push(first_half);
354 if !second_half.is_empty() {
355 result.push(second_half);
356 }
357 result
358 }
359 pub fn for_each_flattened_with_t<F: FnMut(Pos2, f32)>(&self, tolerance: f32, callback: &mut F) {
362 flatten_cubic_bezier_with_t(self, tolerance, callback);
363 }
364}
365
366impl From<CubicBezierShape> for Shape {
367 #[inline(always)]
368 fn from(shape: CubicBezierShape) -> Self {
369 Self::CubicBezier(shape)
370 }
371}
372
373#[derive(Clone, Debug, PartialEq)]
379#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
380pub struct QuadraticBezierShape {
381 pub points: [Pos2; 3],
384 pub closed: bool,
385
386 pub fill: Color32,
387 pub stroke: PathStroke,
388}
389
390impl QuadraticBezierShape {
391 pub fn from_points_stroke(
397 points: [Pos2; 3],
398 closed: bool,
399 fill: Color32,
400 stroke: impl Into<PathStroke>,
401 ) -> Self {
402 Self {
403 points,
404 closed,
405 fill,
406 stroke: stroke.into(),
407 }
408 }
409
410 pub fn transform(&self, transform: &RectTransform) -> Self {
412 let mut points = [Pos2::default(); 3];
413 for (i, origin_point) in self.points.iter().enumerate() {
414 points[i] = transform * *origin_point;
415 }
416 Self {
417 points,
418 closed: self.closed,
419 fill: self.fill,
420 stroke: self.stroke.clone(),
421 }
422 }
423
424 pub fn to_path_shape(&self, tolerance: Option<f32>) -> PathShape {
427 let points = self.flatten(tolerance);
428 PathShape {
429 points,
430 closed: self.closed,
431 fill: self.fill,
432 stroke: self.stroke.clone(),
433 }
434 }
435
436 pub fn visual_bounding_rect(&self) -> Rect {
438 if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() {
439 Rect::NOTHING
440 } else {
441 self.logical_bounding_rect().expand(self.stroke.width / 2.0)
442 }
443 }
444
445 pub fn logical_bounding_rect(&self) -> Rect {
447 let (mut min_x, mut max_x) = if self.points[0].x < self.points[2].x {
448 (self.points[0].x, self.points[2].x)
449 } else {
450 (self.points[2].x, self.points[0].x)
451 };
452 let (mut min_y, mut max_y) = if self.points[0].y < self.points[2].y {
453 (self.points[0].y, self.points[2].y)
454 } else {
455 (self.points[2].y, self.points[0].y)
456 };
457
458 quadratic_for_each_local_extremum(
459 self.points[0].x,
460 self.points[1].x,
461 self.points[2].x,
462 &mut |t| {
463 let x = self.sample(t).x;
464 if x < min_x {
465 min_x = x;
466 }
467 if x > max_x {
468 max_x = x;
469 }
470 },
471 );
472
473 quadratic_for_each_local_extremum(
474 self.points[0].y,
475 self.points[1].y,
476 self.points[2].y,
477 &mut |t| {
478 let y = self.sample(t).y;
479 if y < min_y {
480 min_y = y;
481 }
482 if y > max_y {
483 max_y = y;
484 }
485 },
486 );
487
488 Rect {
489 min: Pos2 { x: min_x, y: min_y },
490 max: Pos2 { x: max_x, y: max_y },
491 }
492 }
493
494 pub fn sample(&self, t: f32) -> Pos2 {
499 debug_assert!(
500 t >= 0.0 && t <= 1.0,
501 "the sample value should be in [0.0,1.0]"
502 );
503
504 let h = 1.0 - t;
505 let a = t * t;
506 let b = 2.0 * t * h;
507 let c = h * h;
508 let result = self.points[2].to_vec2() * a
509 + self.points[1].to_vec2() * b
510 + self.points[0].to_vec2() * c;
511 result.to_pos2()
512 }
513
514 pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
518 let tolerance = tolerance.unwrap_or((self.points[0].x - self.points[2].x).abs() * 0.001);
519 let mut result = vec![self.points[0]];
520 self.for_each_flattened_with_t(tolerance, &mut |p, _t| {
521 result.push(p);
522 });
523 result
524 }
525
526 pub fn for_each_flattened_with_t<F>(&self, tolerance: f32, callback: &mut F)
535 where
536 F: FnMut(Pos2, f32),
537 {
538 let params = FlatteningParameters::from_curve(self, tolerance);
539 if params.is_point {
540 return;
541 }
542
543 let count = params.count as u32;
544 for index in 1..count {
545 let t = params.t_at_iteration(index as f32);
546
547 callback(self.sample(t), t);
548 }
549
550 callback(self.sample(1.0), 1.0);
551 }
552}
553
554impl From<QuadraticBezierShape> for Shape {
555 #[inline(always)]
556 fn from(shape: QuadraticBezierShape) -> Self {
557 Self::QuadraticBezier(shape)
558 }
559}
560
561fn flatten_cubic_bezier_with_t<F: FnMut(Pos2, f32)>(
566 curve: &CubicBezierShape,
567 tolerance: f32,
568 callback: &mut F,
569) {
570 let quadratics_tolerance = tolerance * 0.2;
572 let flattening_tolerance = tolerance * 0.8;
573
574 let num_quadratics = curve.num_quadratics(quadratics_tolerance);
575 let step = 1.0 / num_quadratics as f32;
576 let n = num_quadratics;
577 let mut t0 = 0.0;
578 for _ in 0..(n - 1) {
579 let t1 = t0 + step;
580
581 let quadratic = single_curve_approximation(&curve.split_range(t0..t1));
582 quadratic.for_each_flattened_with_t(flattening_tolerance, &mut |point, t_sub| {
583 let t = t0 + step * t_sub;
584 callback(point, t);
585 });
586
587 t0 = t1;
588 }
589
590 let quadratic = single_curve_approximation(&curve.split_range(t0..1.0));
592 quadratic.for_each_flattened_with_t(flattening_tolerance, &mut |point, t_sub| {
593 let t = t0 + step * t_sub;
594 callback(point, t);
595 });
596}
597
598struct FlatteningParameters {
601 count: f32,
602 integral_from: f32,
603 integral_step: f32,
604 inv_integral_from: f32,
605 div_inv_integral_diff: f32,
606 is_point: bool,
607}
608
609impl FlatteningParameters {
610 pub fn from_curve(curve: &QuadraticBezierShape, tolerance: f32) -> Self {
612 let from = curve.points[0];
614 let ctrl = curve.points[1];
615 let to = curve.points[2];
616
617 let ddx = 2.0 * ctrl.x - from.x - to.x;
618 let ddy = 2.0 * ctrl.y - from.y - to.y;
619 let cross = (to.x - from.x) * ddy - (to.y - from.y) * ddx;
620 let inv_cross = 1.0 / cross;
621 let parabola_from = ((ctrl.x - from.x) * ddx + (ctrl.y - from.y) * ddy) * inv_cross;
622 let parabola_to = ((to.x - ctrl.x) * ddx + (to.y - ctrl.y) * ddy) * inv_cross;
623 let scale = cross.abs() / (ddx.hypot(ddy) * (parabola_to - parabola_from).abs());
627
628 let integral_from = approx_parabola_integral(parabola_from);
629 let integral_to = approx_parabola_integral(parabola_to);
630 let integral_diff = integral_to - integral_from;
631
632 let inv_integral_from = approx_parabola_inv_integral(integral_from);
633 let inv_integral_to = approx_parabola_inv_integral(integral_to);
634 let div_inv_integral_diff = 1.0 / (inv_integral_to - inv_integral_from);
635
636 let mut count = (0.5 * integral_diff.abs() * (scale / tolerance).sqrt()).ceil();
639 let mut is_point = false;
640 if !count.is_finite() {
642 count = 0.0;
643 is_point = (to.x - from.x).hypot(to.y - from.y) < tolerance * tolerance;
644 }
645
646 let integral_step = integral_diff / count;
647
648 Self {
649 count,
650 integral_from,
651 integral_step,
652 inv_integral_from,
653 div_inv_integral_diff,
654 is_point,
655 }
656 }
657
658 fn t_at_iteration(&self, iteration: f32) -> f32 {
659 let u = approx_parabola_inv_integral(self.integral_from + self.integral_step * iteration);
660 (u - self.inv_integral_from) * self.div_inv_integral_diff
661 }
662}
663
664fn approx_parabola_integral(x: f32) -> f32 {
666 let d: f32 = 0.67;
667 let quarter = 0.25;
668 x / (1.0 - d + (d.powi(4) + quarter * x * x).sqrt().sqrt())
669}
670
671fn approx_parabola_inv_integral(x: f32) -> f32 {
673 let b = 0.39;
674 let quarter = 0.25;
675 x * (1.0 - b + (b * b + quarter * x * x).sqrt())
676}
677
678fn single_curve_approximation(curve: &CubicBezierShape) -> QuadraticBezierShape {
679 let c1_x = (curve.points[1].x * 3.0 - curve.points[0].x) * 0.5;
680 let c1_y = (curve.points[1].y * 3.0 - curve.points[0].y) * 0.5;
681 let c2_x = (curve.points[2].x * 3.0 - curve.points[3].x) * 0.5;
682 let c2_y = (curve.points[2].y * 3.0 - curve.points[3].y) * 0.5;
683 let c = Pos2 {
684 x: (c1_x + c2_x) * 0.5,
685 y: (c1_y + c2_y) * 0.5,
686 };
687 QuadraticBezierShape {
688 points: [curve.points[0], c, curve.points[3]],
689 closed: curve.closed,
690 fill: curve.fill,
691 stroke: curve.stroke.clone(),
692 }
693}
694
695fn quadratic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, cb: &mut F) {
696 let a = p2 - 2.0 * p1 + p0;
702 if a == 0.0 {
705 return;
706 }
707
708 let t = (p0 - p1) / a;
709 if t > 0.0 && t < 1.0 {
710 cb(t);
711 }
712}
713
714fn cubic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, p3: f32, cb: &mut F) {
715 let a = 3.0 * (p3 + 3.0 * (p1 - p2) - p0);
720 let b = 6.0 * (p2 - 2.0 * p1 + p0);
721 let c = 3.0 * (p1 - p0);
722
723 let in_range = |t: f32| t <= 1.0 && t >= 0.0;
724
725 if a == 0.0 {
727 if b != 0.0 {
728 let t = -c / b;
729 if in_range(t) {
730 cb(t);
731 }
732 }
733 return;
734 }
735
736 let discr = b * b - 4.0 * a * c;
737 if discr < 0.0 {
739 return;
740 }
741
742 if discr == 0.0 {
744 let t = -b / (2.0 * a);
745 if in_range(t) {
746 cb(t);
747 }
748 return;
749 }
750
751 let discr = discr.sqrt();
753 let t1 = (-b - discr) / (2.0 * a);
754 let t2 = (-b + discr) / (2.0 * a);
755 if in_range(t1) {
756 cb(t1);
757 }
758 if in_range(t2) {
759 cb(t2);
760 }
761}
762
763#[cfg(test)]
764mod tests {
765 use super::*;
766
767 #[test]
768 fn test_quadratic_bounding_box() {
769 let curve = QuadraticBezierShape {
770 points: [
771 Pos2 { x: 110.0, y: 170.0 },
772 Pos2 { x: 10.0, y: 10.0 },
773 Pos2 { x: 180.0, y: 30.0 },
774 ],
775 closed: false,
776 fill: Default::default(),
777 stroke: Default::default(),
778 };
779 let bbox = curve.logical_bounding_rect();
780 assert!((bbox.min.x - 72.96).abs() < 0.01);
781 assert!((bbox.min.y - 27.78).abs() < 0.01);
782
783 assert!((bbox.max.x - 180.0).abs() < 0.01);
784 assert!((bbox.max.y - 170.0).abs() < 0.01);
785
786 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
788 result.push(pos);
789 });
790
791 assert_eq!(result.len(), 26);
792
793 let curve = QuadraticBezierShape {
794 points: [
795 Pos2 { x: 110.0, y: 170.0 },
796 Pos2 { x: 180.0, y: 30.0 },
797 Pos2 { x: 10.0, y: 10.0 },
798 ],
799 closed: false,
800 fill: Default::default(),
801 stroke: Default::default(),
802 };
803 let bbox = curve.logical_bounding_rect();
804 assert!((bbox.min.x - 10.0).abs() < 0.01);
805 assert!((bbox.min.y - 10.0).abs() < 0.01);
806
807 assert!((bbox.max.x - 130.42).abs() < 0.01);
808 assert!((bbox.max.y - 170.0).abs() < 0.01);
809
810 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
812 result.push(pos);
813 });
814
815 assert_eq!(result.len(), 25);
816 }
817
818 #[test]
819 fn test_quadratic_different_tolerance() {
820 let curve = QuadraticBezierShape {
821 points: [
822 Pos2 { x: 110.0, y: 170.0 },
823 Pos2 { x: 180.0, y: 30.0 },
824 Pos2 { x: 10.0, y: 10.0 },
825 ],
826 closed: false,
827 fill: Default::default(),
828 stroke: Default::default(),
829 };
830 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
832 result.push(pos);
833 });
834
835 assert_eq!(result.len(), 9);
836
837 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
839 result.push(pos);
840 });
841
842 assert_eq!(result.len(), 25);
843
844 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
846 result.push(pos);
847 });
848
849 assert_eq!(result.len(), 77);
850
851 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
853 result.push(pos);
854 });
855
856 assert_eq!(result.len(), 240);
857 }
858
859 #[test]
860 fn test_cubic_bounding_box() {
861 let curve = CubicBezierShape {
862 points: [
863 pos2(10.0, 10.0),
864 pos2(110.0, 170.0),
865 pos2(180.0, 30.0),
866 pos2(270.0, 210.0),
867 ],
868 closed: false,
869 fill: Default::default(),
870 stroke: Default::default(),
871 };
872
873 let bbox = curve.logical_bounding_rect();
874 assert_eq!(bbox.min.x, 10.0);
875 assert_eq!(bbox.min.y, 10.0);
876 assert_eq!(bbox.max.x, 270.0);
877 assert_eq!(bbox.max.y, 210.0);
878
879 let curve = CubicBezierShape {
880 points: [
881 pos2(10.0, 10.0),
882 pos2(110.0, 170.0),
883 pos2(270.0, 210.0),
884 pos2(180.0, 30.0),
885 ],
886 closed: false,
887 fill: Default::default(),
888 stroke: Default::default(),
889 };
890
891 let bbox = curve.logical_bounding_rect();
892 assert_eq!(bbox.min.x, 10.0);
893 assert_eq!(bbox.min.y, 10.0);
894 assert!((bbox.max.x - 206.50).abs() < 0.01);
895 assert!((bbox.max.y - 148.48).abs() < 0.01);
896
897 let curve = CubicBezierShape {
898 points: [
899 pos2(110.0, 170.0),
900 pos2(10.0, 10.0),
901 pos2(270.0, 210.0),
902 pos2(180.0, 30.0),
903 ],
904 closed: false,
905 fill: Default::default(),
906 stroke: Default::default(),
907 };
908
909 let bbox = curve.logical_bounding_rect();
910 assert!((bbox.min.x - 86.71).abs() < 0.01);
911 assert!((bbox.min.y - 30.0).abs() < 0.01);
912
913 assert!((bbox.max.x - 199.27).abs() < 0.01);
914 assert!((bbox.max.y - 170.0).abs() < 0.01);
915 }
916
917 #[test]
918 fn test_cubic_different_tolerance_flattening() {
919 let curve = CubicBezierShape {
920 points: [
921 pos2(0.0, 0.0),
922 pos2(100.0, 0.0),
923 pos2(100.0, 100.0),
924 pos2(100.0, 200.0),
925 ],
926 closed: false,
927 fill: Default::default(),
928 stroke: Default::default(),
929 };
930
931 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
933 result.push(pos);
934 });
935
936 assert_eq!(result.len(), 10);
937
938 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
940 result.push(pos);
941 });
942
943 assert_eq!(result.len(), 13);
944
945 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
947 result.push(pos);
948 });
949
950 assert_eq!(result.len(), 28);
951
952 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
954 result.push(pos);
955 });
956
957 assert_eq!(result.len(), 83);
958
959 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
961 result.push(pos);
962 });
963
964 assert_eq!(result.len(), 248);
965 }
966
967 #[test]
968 fn test_cubic_different_shape_flattening() {
969 let curve = CubicBezierShape {
970 points: [
971 pos2(90.0, 110.0),
972 pos2(30.0, 170.0),
973 pos2(210.0, 170.0),
974 pos2(170.0, 110.0),
975 ],
976 closed: false,
977 fill: Default::default(),
978 stroke: Default::default(),
979 };
980
981 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
983 result.push(pos);
984 });
985
986 assert_eq!(result.len(), 117);
987
988 let curve = CubicBezierShape {
989 points: [
990 pos2(90.0, 110.0),
991 pos2(90.0, 170.0),
992 pos2(170.0, 170.0),
993 pos2(170.0, 110.0),
994 ],
995 closed: false,
996 fill: Default::default(),
997 stroke: Default::default(),
998 };
999
1000 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1002 result.push(pos);
1003 });
1004
1005 assert_eq!(result.len(), 91);
1006
1007 let curve = CubicBezierShape {
1008 points: [
1009 pos2(90.0, 110.0),
1010 pos2(110.0, 170.0),
1011 pos2(150.0, 170.0),
1012 pos2(170.0, 110.0),
1013 ],
1014 closed: false,
1015 fill: Default::default(),
1016 stroke: Default::default(),
1017 };
1018
1019 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1021 result.push(pos);
1022 });
1023
1024 assert_eq!(result.len(), 75);
1025
1026 let curve = CubicBezierShape {
1027 points: [
1028 pos2(90.0, 110.0),
1029 pos2(110.0, 170.0),
1030 pos2(230.0, 110.0),
1031 pos2(170.0, 110.0),
1032 ],
1033 closed: false,
1034 fill: Default::default(),
1035 stroke: Default::default(),
1036 };
1037
1038 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1040 result.push(pos);
1041 });
1042
1043 assert_eq!(result.len(), 100);
1044
1045 let curve = CubicBezierShape {
1046 points: [
1047 pos2(90.0, 110.0),
1048 pos2(110.0, 170.0),
1049 pos2(210.0, 70.0),
1050 pos2(170.0, 110.0),
1051 ],
1052 closed: false,
1053 fill: Default::default(),
1054 stroke: Default::default(),
1055 };
1056
1057 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1059 result.push(pos);
1060 });
1061
1062 assert_eq!(result.len(), 71);
1063
1064 let curve = CubicBezierShape {
1065 points: [
1066 pos2(90.0, 110.0),
1067 pos2(110.0, 170.0),
1068 pos2(150.0, 50.0),
1069 pos2(170.0, 110.0),
1070 ],
1071 closed: false,
1072 fill: Default::default(),
1073 stroke: Default::default(),
1074 };
1075
1076 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1078 result.push(pos);
1079 });
1080
1081 assert_eq!(result.len(), 88);
1082 }
1083
1084 #[test]
1085 fn test_quadratic_flattening() {
1086 let curve = QuadraticBezierShape {
1087 points: [pos2(0.0, 0.0), pos2(80.0, 200.0), pos2(100.0, 30.0)],
1088 closed: false,
1089 fill: Default::default(),
1090 stroke: Default::default(),
1091 };
1092
1093 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
1095 result.push(pos);
1096 });
1097
1098 assert_eq!(result.len(), 9);
1099
1100 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
1102 result.push(pos);
1103 });
1104
1105 assert_eq!(result.len(), 11);
1106
1107 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
1109 result.push(pos);
1110 });
1111
1112 assert_eq!(result.len(), 24);
1113
1114 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1116 result.push(pos);
1117 });
1118
1119 assert_eq!(result.len(), 72);
1120 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
1122 result.push(pos);
1123 });
1124
1125 assert_eq!(result.len(), 223);
1126 }
1127}