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 #![expect(clippy::useless_let_if_seq)]
617
618 let from = curve.points[0];
620 let ctrl = curve.points[1];
621 let to = curve.points[2];
622
623 let ddx = 2.0 * ctrl.x - from.x - to.x;
624 let ddy = 2.0 * ctrl.y - from.y - to.y;
625 let cross = (to.x - from.x) * ddy - (to.y - from.y) * ddx;
626 let inv_cross = 1.0 / cross;
627 let parabola_from = ((ctrl.x - from.x) * ddx + (ctrl.y - from.y) * ddy) * inv_cross;
628 let parabola_to = ((to.x - ctrl.x) * ddx + (to.y - ctrl.y) * ddy) * inv_cross;
629 let scale = cross.abs() / (ddx.hypot(ddy) * (parabola_to - parabola_from).abs());
633
634 let integral_from = approx_parabola_integral(parabola_from);
635 let integral_to = approx_parabola_integral(parabola_to);
636 let integral_diff = integral_to - integral_from;
637
638 let inv_integral_from = approx_parabola_inv_integral(integral_from);
639 let inv_integral_to = approx_parabola_inv_integral(integral_to);
640 let div_inv_integral_diff = 1.0 / (inv_integral_to - inv_integral_from);
641
642 let mut count = (0.5 * integral_diff.abs() * (scale / tolerance).sqrt()).ceil();
645 let mut is_point = false;
646 if !count.is_finite() {
648 count = 0.0;
649 is_point = (to.x - from.x).hypot(to.y - from.y) < tolerance * tolerance;
650 }
651
652 let integral_step = integral_diff / count;
653
654 Self {
655 count,
656 integral_from,
657 integral_step,
658 inv_integral_from,
659 div_inv_integral_diff,
660 is_point,
661 }
662 }
663
664 fn t_at_iteration(&self, iteration: f32) -> f32 {
665 let u = approx_parabola_inv_integral(self.integral_from + self.integral_step * iteration);
666 (u - self.inv_integral_from) * self.div_inv_integral_diff
667 }
668}
669
670fn approx_parabola_integral(x: f32) -> f32 {
672 let d: f32 = 0.67;
673 let quarter = 0.25;
674 x / (1.0 - d + (d.powi(4) + quarter * x * x).sqrt().sqrt())
675}
676
677fn approx_parabola_inv_integral(x: f32) -> f32 {
679 let b = 0.39;
680 let quarter = 0.25;
681 x * (1.0 - b + (b * b + quarter * x * x).sqrt())
682}
683
684fn single_curve_approximation(curve: &CubicBezierShape) -> QuadraticBezierShape {
685 let c1_x = (curve.points[1].x * 3.0 - curve.points[0].x) * 0.5;
686 let c1_y = (curve.points[1].y * 3.0 - curve.points[0].y) * 0.5;
687 let c2_x = (curve.points[2].x * 3.0 - curve.points[3].x) * 0.5;
688 let c2_y = (curve.points[2].y * 3.0 - curve.points[3].y) * 0.5;
689 let c = Pos2 {
690 x: (c1_x + c2_x) * 0.5,
691 y: (c1_y + c2_y) * 0.5,
692 };
693 QuadraticBezierShape {
694 points: [curve.points[0], c, curve.points[3]],
695 closed: curve.closed,
696 fill: curve.fill,
697 stroke: curve.stroke.clone(),
698 }
699}
700
701fn quadratic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, cb: &mut F) {
702 let a = p2 - 2.0 * p1 + p0;
708 if a == 0.0 {
711 return;
712 }
713
714 let t = (p0 - p1) / a;
715 if t > 0.0 && t < 1.0 {
716 cb(t);
717 }
718}
719
720fn cubic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, p3: f32, cb: &mut F) {
721 let a = 3.0 * (p3 + 3.0 * (p1 - p2) - p0);
726 let b = 6.0 * (p2 - 2.0 * p1 + p0);
727 let c = 3.0 * (p1 - p0);
728
729 let in_range = |t: f32| t <= 1.0 && t >= 0.0;
730
731 if a == 0.0 {
733 if b != 0.0 {
734 let t = -c / b;
735 if in_range(t) {
736 cb(t);
737 }
738 }
739 return;
740 }
741
742 let discr = b * b - 4.0 * a * c;
743 if discr < 0.0 {
745 return;
746 }
747
748 if discr == 0.0 {
750 let t = -b / (2.0 * a);
751 if in_range(t) {
752 cb(t);
753 }
754 return;
755 }
756
757 let discr = discr.sqrt();
759 let t1 = (-b - discr) / (2.0 * a);
760 let t2 = (-b + discr) / (2.0 * a);
761 if in_range(t1) {
762 cb(t1);
763 }
764 if in_range(t2) {
765 cb(t2);
766 }
767}
768
769#[cfg(test)]
770mod tests {
771 use emath::pos2;
772
773 use super::*;
774
775 #[test]
776 fn test_quadratic_bounding_box() {
777 let curve = QuadraticBezierShape {
778 points: [
779 Pos2 { x: 110.0, y: 170.0 },
780 Pos2 { x: 10.0, y: 10.0 },
781 Pos2 { x: 180.0, y: 30.0 },
782 ],
783 closed: false,
784 fill: Default::default(),
785 stroke: Default::default(),
786 };
787 let bbox = curve.logical_bounding_rect();
788 assert!((bbox.min.x - 72.96).abs() < 0.01);
789 assert!((bbox.min.y - 27.78).abs() < 0.01);
790
791 assert!((bbox.max.x - 180.0).abs() < 0.01);
792 assert!((bbox.max.y - 170.0).abs() < 0.01);
793
794 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
796 result.push(pos);
797 });
798
799 assert_eq!(result.len(), 26);
800
801 let curve = QuadraticBezierShape {
802 points: [
803 Pos2 { x: 110.0, y: 170.0 },
804 Pos2 { x: 180.0, y: 30.0 },
805 Pos2 { x: 10.0, y: 10.0 },
806 ],
807 closed: false,
808 fill: Default::default(),
809 stroke: Default::default(),
810 };
811 let bbox = curve.logical_bounding_rect();
812 assert!((bbox.min.x - 10.0).abs() < 0.01);
813 assert!((bbox.min.y - 10.0).abs() < 0.01);
814
815 assert!((bbox.max.x - 130.42).abs() < 0.01);
816 assert!((bbox.max.y - 170.0).abs() < 0.01);
817
818 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
820 result.push(pos);
821 });
822
823 assert_eq!(result.len(), 25);
824 }
825
826 #[test]
827 fn test_quadratic_different_tolerance() {
828 let curve = QuadraticBezierShape {
829 points: [
830 Pos2 { x: 110.0, y: 170.0 },
831 Pos2 { x: 180.0, y: 30.0 },
832 Pos2 { x: 10.0, y: 10.0 },
833 ],
834 closed: false,
835 fill: Default::default(),
836 stroke: Default::default(),
837 };
838 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
840 result.push(pos);
841 });
842
843 assert_eq!(result.len(), 9);
844
845 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
847 result.push(pos);
848 });
849
850 assert_eq!(result.len(), 25);
851
852 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
854 result.push(pos);
855 });
856
857 assert_eq!(result.len(), 77);
858
859 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
861 result.push(pos);
862 });
863
864 assert_eq!(result.len(), 240);
865 }
866
867 #[test]
868 fn test_cubic_bounding_box() {
869 let curve = CubicBezierShape {
870 points: [
871 pos2(10.0, 10.0),
872 pos2(110.0, 170.0),
873 pos2(180.0, 30.0),
874 pos2(270.0, 210.0),
875 ],
876 closed: false,
877 fill: Default::default(),
878 stroke: Default::default(),
879 };
880
881 let bbox = curve.logical_bounding_rect();
882 assert_eq!(bbox.min.x, 10.0);
883 assert_eq!(bbox.min.y, 10.0);
884 assert_eq!(bbox.max.x, 270.0);
885 assert_eq!(bbox.max.y, 210.0);
886
887 let curve = CubicBezierShape {
888 points: [
889 pos2(10.0, 10.0),
890 pos2(110.0, 170.0),
891 pos2(270.0, 210.0),
892 pos2(180.0, 30.0),
893 ],
894 closed: false,
895 fill: Default::default(),
896 stroke: Default::default(),
897 };
898
899 let bbox = curve.logical_bounding_rect();
900 assert_eq!(bbox.min.x, 10.0);
901 assert_eq!(bbox.min.y, 10.0);
902 assert!((bbox.max.x - 206.50).abs() < 0.01);
903 assert!((bbox.max.y - 148.48).abs() < 0.01);
904
905 let curve = CubicBezierShape {
906 points: [
907 pos2(110.0, 170.0),
908 pos2(10.0, 10.0),
909 pos2(270.0, 210.0),
910 pos2(180.0, 30.0),
911 ],
912 closed: false,
913 fill: Default::default(),
914 stroke: Default::default(),
915 };
916
917 let bbox = curve.logical_bounding_rect();
918 assert!((bbox.min.x - 86.71).abs() < 0.01);
919 assert!((bbox.min.y - 30.0).abs() < 0.01);
920
921 assert!((bbox.max.x - 199.27).abs() < 0.01);
922 assert!((bbox.max.y - 170.0).abs() < 0.01);
923 }
924
925 #[test]
926 fn test_cubic_different_tolerance_flattening() {
927 let curve = CubicBezierShape {
928 points: [
929 pos2(0.0, 0.0),
930 pos2(100.0, 0.0),
931 pos2(100.0, 100.0),
932 pos2(100.0, 200.0),
933 ],
934 closed: false,
935 fill: Default::default(),
936 stroke: Default::default(),
937 };
938
939 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
941 result.push(pos);
942 });
943
944 assert_eq!(result.len(), 10);
945
946 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
948 result.push(pos);
949 });
950
951 assert_eq!(result.len(), 13);
952
953 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
955 result.push(pos);
956 });
957
958 assert_eq!(result.len(), 28);
959
960 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
962 result.push(pos);
963 });
964
965 assert_eq!(result.len(), 83);
966
967 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
969 result.push(pos);
970 });
971
972 assert_eq!(result.len(), 248);
973 }
974
975 #[test]
976 fn test_cubic_different_shape_flattening() {
977 let curve = CubicBezierShape {
978 points: [
979 pos2(90.0, 110.0),
980 pos2(30.0, 170.0),
981 pos2(210.0, 170.0),
982 pos2(170.0, 110.0),
983 ],
984 closed: false,
985 fill: Default::default(),
986 stroke: Default::default(),
987 };
988
989 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
991 result.push(pos);
992 });
993
994 assert_eq!(result.len(), 117);
995
996 let curve = CubicBezierShape {
997 points: [
998 pos2(90.0, 110.0),
999 pos2(90.0, 170.0),
1000 pos2(170.0, 170.0),
1001 pos2(170.0, 110.0),
1002 ],
1003 closed: false,
1004 fill: Default::default(),
1005 stroke: Default::default(),
1006 };
1007
1008 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1010 result.push(pos);
1011 });
1012
1013 assert_eq!(result.len(), 91);
1014
1015 let curve = CubicBezierShape {
1016 points: [
1017 pos2(90.0, 110.0),
1018 pos2(110.0, 170.0),
1019 pos2(150.0, 170.0),
1020 pos2(170.0, 110.0),
1021 ],
1022 closed: false,
1023 fill: Default::default(),
1024 stroke: Default::default(),
1025 };
1026
1027 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1029 result.push(pos);
1030 });
1031
1032 assert_eq!(result.len(), 75);
1033
1034 let curve = CubicBezierShape {
1035 points: [
1036 pos2(90.0, 110.0),
1037 pos2(110.0, 170.0),
1038 pos2(230.0, 110.0),
1039 pos2(170.0, 110.0),
1040 ],
1041 closed: false,
1042 fill: Default::default(),
1043 stroke: Default::default(),
1044 };
1045
1046 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1048 result.push(pos);
1049 });
1050
1051 assert_eq!(result.len(), 100);
1052
1053 let curve = CubicBezierShape {
1054 points: [
1055 pos2(90.0, 110.0),
1056 pos2(110.0, 170.0),
1057 pos2(210.0, 70.0),
1058 pos2(170.0, 110.0),
1059 ],
1060 closed: false,
1061 fill: Default::default(),
1062 stroke: Default::default(),
1063 };
1064
1065 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1067 result.push(pos);
1068 });
1069
1070 assert_eq!(result.len(), 71);
1071
1072 let curve = CubicBezierShape {
1073 points: [
1074 pos2(90.0, 110.0),
1075 pos2(110.0, 170.0),
1076 pos2(150.0, 50.0),
1077 pos2(170.0, 110.0),
1078 ],
1079 closed: false,
1080 fill: Default::default(),
1081 stroke: Default::default(),
1082 };
1083
1084 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1086 result.push(pos);
1087 });
1088
1089 assert_eq!(result.len(), 88);
1090 }
1091
1092 #[test]
1093 fn test_quadratic_flattening() {
1094 let curve = QuadraticBezierShape {
1095 points: [pos2(0.0, 0.0), pos2(80.0, 200.0), pos2(100.0, 30.0)],
1096 closed: false,
1097 fill: Default::default(),
1098 stroke: Default::default(),
1099 };
1100
1101 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(1.0, &mut |pos, _t| {
1103 result.push(pos);
1104 });
1105
1106 assert_eq!(result.len(), 9);
1107
1108 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.5, &mut |pos, _t| {
1110 result.push(pos);
1111 });
1112
1113 assert_eq!(result.len(), 11);
1114
1115 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.1, &mut |pos, _t| {
1117 result.push(pos);
1118 });
1119
1120 assert_eq!(result.len(), 24);
1121
1122 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.01, &mut |pos, _t| {
1124 result.push(pos);
1125 });
1126
1127 assert_eq!(result.len(), 72);
1128 let mut result = vec![curve.points[0]]; curve.for_each_flattened_with_t(0.001, &mut |pos, _t| {
1130 result.push(pos);
1131 });
1132
1133 assert_eq!(result.len(), 223);
1134 }
1135}