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 = (-1.0 * (p / 3.0).powi(3)).sqrt();
257 let theta = (-1.0 * 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}