1use core::mem::swap;
4use core::ops::Range;
5
6use num_traits::NumCast;
7
8use crate::scalar::{cast, Float, Scalar};
9use crate::segment::Segment;
10use crate::{point, vector, Angle, Box2D, Point, Rotation, Transform, Vector};
11use crate::{CubicBezierSegment, LineSegment, QuadraticBezierSegment};
12
13#[derive(Copy, Clone, Debug, PartialEq)]
15#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
16pub struct Arc<S> {
17 pub center: Point<S>,
18 pub radii: Vector<S>,
19 pub start_angle: Angle<S>,
20 pub sweep_angle: Angle<S>,
21 pub x_rotation: Angle<S>,
22}
23
24#[derive(Copy, Clone, Debug, PartialEq)]
26#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
27pub struct SvgArc<S> {
28 pub from: Point<S>,
29 pub to: Point<S>,
30 pub radii: Vector<S>,
31 pub x_rotation: Angle<S>,
32 pub flags: ArcFlags,
33}
34
35impl<S: Scalar> Arc<S> {
36 pub fn cast<NewS: NumCast>(self) -> Arc<NewS> {
37 Arc {
38 center: self.center.cast(),
39 radii: self.radii.cast(),
40 start_angle: self.start_angle.cast(),
41 sweep_angle: self.sweep_angle.cast(),
42 x_rotation: self.x_rotation.cast(),
43 }
44 }
45
46 pub fn circle(center: Point<S>, radius: S) -> Self {
48 Arc {
49 center,
50 radii: vector(radius, radius),
51 start_angle: Angle::zero(),
52 sweep_angle: Angle::two_pi(),
53 x_rotation: Angle::zero(),
54 }
55 }
56
57 pub fn from_svg_arc(arc: &SvgArc<S>) -> Arc<S> {
59 debug_assert!(!arc.from.x.is_nan());
60 debug_assert!(!arc.from.y.is_nan());
61 debug_assert!(!arc.to.x.is_nan());
62 debug_assert!(!arc.to.y.is_nan());
63 debug_assert!(!arc.radii.x.is_nan());
64 debug_assert!(!arc.radii.y.is_nan());
65 debug_assert!(!arc.x_rotation.get().is_nan());
66 assert!(!arc.is_straight_line());
70
71 let mut rx = S::abs(arc.radii.x);
72 let mut ry = S::abs(arc.radii.y);
73
74 let xr = arc.x_rotation.get() % (S::TWO * S::PI());
75 let cos_phi = Float::cos(xr);
76 let sin_phi = Float::sin(xr);
77 let hd_x = (arc.from.x - arc.to.x) / S::TWO;
78 let hd_y = (arc.from.y - arc.to.y) / S::TWO;
79 let hs_x = (arc.from.x + arc.to.x) / S::TWO;
80 let hs_y = (arc.from.y + arc.to.y) / S::TWO;
81
82 let p = Point::new(
84 cos_phi * hd_x + sin_phi * hd_y,
85 -sin_phi * hd_x + cos_phi * hd_y,
86 );
87
88 let rf = p.x * p.x / (rx * rx) + p.y * p.y / (ry * ry);
95 if rf > S::ONE {
96 let scale = S::sqrt(rf);
97 rx *= scale;
98 ry *= scale;
99 }
100
101 let rxry = rx * ry;
102 let rxpy = rx * p.y;
103 let rypx = ry * p.x;
104 let sum_of_sq = rxpy * rxpy + rypx * rypx;
105
106 debug_assert_ne!(sum_of_sq, S::ZERO);
107
108 let sign_coe = if arc.flags.large_arc == arc.flags.sweep {
110 -S::ONE
111 } else {
112 S::ONE
113 };
114 let coe = sign_coe * S::sqrt(S::abs((rxry * rxry - sum_of_sq) / sum_of_sq));
115 let transformed_cx = coe * rxpy / ry;
116 let transformed_cy = -coe * rypx / rx;
117
118 let center = point(
120 cos_phi * transformed_cx - sin_phi * transformed_cy + hs_x,
121 sin_phi * transformed_cx + cos_phi * transformed_cy + hs_y,
122 );
123
124 let start_v: Vector<S> = vector((p.x - transformed_cx) / rx, (p.y - transformed_cy) / ry);
125 let end_v: Vector<S> = vector((-p.x - transformed_cx) / rx, (-p.y - transformed_cy) / ry);
126
127 let two_pi = S::TWO * S::PI();
128
129 let start_angle = start_v.angle_from_x_axis();
130
131 let mut sweep_angle = (end_v.angle_from_x_axis() - start_angle).radians % two_pi;
132
133 if arc.flags.sweep && sweep_angle < S::ZERO {
134 sweep_angle += two_pi;
135 } else if !arc.flags.sweep && sweep_angle > S::ZERO {
136 sweep_angle -= two_pi;
137 }
138
139 Arc {
140 center,
141 radii: vector(rx, ry),
142 start_angle,
143 sweep_angle: Angle::radians(sweep_angle),
144 x_rotation: arc.x_rotation,
145 }
146 }
147
148 pub fn to_svg_arc(&self) -> SvgArc<S> {
150 let from = self.sample(S::ZERO);
151 let to = self.sample(S::ONE);
152 let flags = ArcFlags {
153 sweep: self.sweep_angle.get() >= S::ZERO,
154 large_arc: S::abs(self.sweep_angle.get()) >= S::PI(),
155 };
156 SvgArc {
157 from,
158 to,
159 radii: self.radii,
160 x_rotation: self.x_rotation,
161 flags,
162 }
163 }
164
165 #[inline]
167 pub fn for_each_quadratic_bezier<F>(&self, cb: &mut F)
168 where
169 F: FnMut(&QuadraticBezierSegment<S>),
170 {
171 arc_to_quadratic_beziers_with_t(self, &mut |curve, _| cb(curve));
172 }
173
174 #[inline]
176 pub fn for_each_quadratic_bezier_with_t<F>(&self, cb: &mut F)
177 where
178 F: FnMut(&QuadraticBezierSegment<S>, Range<S>),
179 {
180 arc_to_quadratic_beziers_with_t(self, cb);
181 }
182
183 #[inline]
185 pub fn for_each_cubic_bezier<F>(&self, cb: &mut F)
186 where
187 F: FnMut(&CubicBezierSegment<S>),
188 {
189 arc_to_cubic_beziers(self, cb);
190 }
191
192 #[inline]
194 pub fn sample(&self, t: S) -> Point<S> {
195 let angle = self.get_angle(t);
196 self.center + sample_ellipse(self.radii, self.x_rotation, angle).to_vector()
197 }
198
199 #[inline]
200 pub fn x(&self, t: S) -> S {
201 self.sample(t).x
202 }
203
204 #[inline]
205 pub fn y(&self, t: S) -> S {
206 self.sample(t).y
207 }
208
209 #[inline]
211 pub fn sample_tangent(&self, t: S) -> Vector<S> {
212 self.tangent_at_angle(self.get_angle(t))
213 }
214
215 #[inline]
217 pub fn get_angle(&self, t: S) -> Angle<S> {
218 self.start_angle + Angle::radians(self.sweep_angle.get() * t)
219 }
220
221 #[inline]
222 pub fn end_angle(&self) -> Angle<S> {
223 self.start_angle + self.sweep_angle
224 }
225
226 #[inline]
227 pub fn from(&self) -> Point<S> {
228 self.sample(S::ZERO)
229 }
230
231 #[inline]
232 pub fn to(&self) -> Point<S> {
233 self.sample(S::ONE)
234 }
235
236 pub fn split_range(&self, t_range: Range<S>) -> Self {
240 let angle_1 = Angle::radians(self.sweep_angle.get() * t_range.start);
241 let angle_2 = Angle::radians(self.sweep_angle.get() * t_range.end);
242
243 Arc {
244 center: self.center,
245 radii: self.radii,
246 start_angle: self.start_angle + angle_1,
247 sweep_angle: angle_2 - angle_1,
248 x_rotation: self.x_rotation,
249 }
250 }
251
252 pub fn split(&self, t: S) -> (Arc<S>, Arc<S>) {
254 let split_angle = Angle::radians(self.sweep_angle.get() * t);
255 (
256 Arc {
257 center: self.center,
258 radii: self.radii,
259 start_angle: self.start_angle,
260 sweep_angle: split_angle,
261 x_rotation: self.x_rotation,
262 },
263 Arc {
264 center: self.center,
265 radii: self.radii,
266 start_angle: self.start_angle + split_angle,
267 sweep_angle: self.sweep_angle - split_angle,
268 x_rotation: self.x_rotation,
269 },
270 )
271 }
272
273 pub fn before_split(&self, t: S) -> Arc<S> {
275 let split_angle = Angle::radians(self.sweep_angle.get() * t);
276 Arc {
277 center: self.center,
278 radii: self.radii,
279 start_angle: self.start_angle,
280 sweep_angle: split_angle,
281 x_rotation: self.x_rotation,
282 }
283 }
284
285 pub fn after_split(&self, t: S) -> Arc<S> {
287 let split_angle = Angle::radians(self.sweep_angle.get() * t);
288 Arc {
289 center: self.center,
290 radii: self.radii,
291 start_angle: self.start_angle + split_angle,
292 sweep_angle: self.sweep_angle - split_angle,
293 x_rotation: self.x_rotation,
294 }
295 }
296
297 pub fn flip(&self) -> Self {
299 let mut arc = *self;
300 arc.start_angle += self.sweep_angle;
301 arc.sweep_angle = -self.sweep_angle;
302
303 arc
304 }
305
306 pub fn for_each_flattened<F>(&self, tolerance: S, callback: &mut F)
311 where
312 F: FnMut(&LineSegment<S>),
313 {
314 let mut from = self.from();
315 let mut iter = *self;
316 loop {
317 let t = iter.flattening_step(tolerance);
318 if t >= S::ONE {
319 break;
320 }
321 iter = iter.after_split(t);
322 let to = iter.from();
323 callback(&LineSegment { from, to });
324 from = to;
325 }
326
327 callback(&LineSegment {
328 from,
329 to: self.to(),
330 });
331 }
332
333 pub fn for_each_flattened_with_t<F>(&self, tolerance: S, callback: &mut F)
340 where
341 F: FnMut(&LineSegment<S>, Range<S>),
342 {
343 let mut iter = *self;
344 let mut t0 = S::ZERO;
345 let mut from = self.from();
346 loop {
347 let step = iter.flattening_step(tolerance);
348
349 if step >= S::ONE {
350 break;
351 }
352
353 iter = iter.after_split(step);
354 let t1 = t0 + step * (S::ONE - t0);
355 let to = iter.from();
356 callback(&LineSegment { from, to }, t0..t1);
357 from = to;
358 t0 = t1;
359 }
360
361 callback(
362 &LineSegment {
363 from,
364 to: self.to(),
365 },
366 t0..S::ONE,
367 );
368 }
369
370 fn flattening_step(&self, tolerance: S) -> S {
373 let r = (self.from() - self.center).length();
380 let a = S::TWO * S::acos((r - tolerance) / r);
381 let result = S::min(a / self.sweep_angle.radians.abs(), S::ONE);
382
383 if result < S::EPSILON {
384 return S::ONE;
385 }
386
387 result
388 }
389
390 pub fn flattened(&self, tolerance: S) -> Flattened<S> {
393 Flattened::new(*self, tolerance)
394 }
395
396 pub fn fast_bounding_box(&self) -> Box2D<S> {
398 Transform::rotation(self.x_rotation).outer_transformed_box(&Box2D {
399 min: self.center - self.radii,
400 max: self.center + self.radii,
401 })
402 }
403
404 pub fn bounding_box(&self) -> Box2D<S> {
406 let from = self.from();
407 let to = self.to();
408 let mut min = Point::min(from, to);
409 let mut max = Point::max(from, to);
410 self.for_each_local_x_extremum_t(&mut |t| {
411 let p = self.sample(t);
412 min.x = S::min(min.x, p.x);
413 max.x = S::max(max.x, p.x);
414 });
415 self.for_each_local_y_extremum_t(&mut |t| {
416 let p = self.sample(t);
417 min.y = S::min(min.y, p.y);
418 max.y = S::max(max.y, p.y);
419 });
420
421 Box2D { min, max }
422 }
423
424 pub fn for_each_local_x_extremum_t<F>(&self, cb: &mut F)
425 where
426 F: FnMut(S),
427 {
428 let rx = self.radii.x;
429 let ry = self.radii.y;
430 let a1 = Angle::radians(-S::atan(ry * Float::tan(self.x_rotation.radians) / rx));
431 let a2 = Angle::pi() + a1;
432
433 self.for_each_extremum_inner(a1, a2, cb);
434 }
435
436 pub fn for_each_local_y_extremum_t<F>(&self, cb: &mut F)
437 where
438 F: FnMut(S),
439 {
440 let rx = self.radii.x;
441 let ry = self.radii.y;
442 let a1 = Angle::radians(S::atan(ry / (Float::tan(self.x_rotation.radians) * rx)));
443 let a2 = Angle::pi() + a1;
444
445 self.for_each_extremum_inner(a1, a2, cb);
446 }
447
448 fn for_each_extremum_inner<F>(&self, a1: Angle<S>, a2: Angle<S>, cb: &mut F)
449 where
450 F: FnMut(S),
451 {
452 let sweep = self.sweep_angle.radians;
453 let abs_sweep = S::abs(sweep);
454 let sign = S::signum(sweep);
455
456 let mut a1 = (a1 - self.start_angle).positive().radians;
457 let mut a2 = (a2 - self.start_angle).positive().radians;
458 if a1 * sign > a2 * sign {
459 swap(&mut a1, &mut a2);
460 }
461
462 let two_pi = S::TWO * S::PI();
463 if sweep >= S::ZERO {
464 if a1 < abs_sweep {
465 cb(a1 / abs_sweep);
466 }
467 if a2 < abs_sweep {
468 cb(a2 / abs_sweep);
469 }
470 } else {
471 if a1 > two_pi - abs_sweep {
472 cb(a1 / abs_sweep);
473 }
474 if a2 > two_pi - abs_sweep {
475 cb(a2 / abs_sweep);
476 }
477 }
478 }
479
480 pub fn bounding_range_x(&self) -> (S, S) {
481 let r = self.bounding_box();
482 (r.min.x, r.max.x)
483 }
484
485 pub fn bounding_range_y(&self) -> (S, S) {
486 let r = self.bounding_box();
487 (r.min.y, r.max.y)
488 }
489
490 pub fn fast_bounding_range_x(&self) -> (S, S) {
491 let r = self.fast_bounding_box();
492 (r.min.x, r.max.x)
493 }
494
495 pub fn fast_bounding_range_y(&self) -> (S, S) {
496 let r = self.fast_bounding_box();
497 (r.min.y, r.max.y)
498 }
499
500 pub fn approximate_length(&self, tolerance: S) -> S {
501 let mut len = S::ZERO;
502 self.for_each_flattened(tolerance, &mut |segment| {
503 len += segment.length();
504 });
505
506 len
507 }
508
509 #[inline]
510 fn tangent_at_angle(&self, angle: Angle<S>) -> Vector<S> {
511 let a = angle.get();
512 Rotation::new(self.x_rotation).transform_vector(vector(
513 -self.radii.x * Float::sin(a),
514 self.radii.y * Float::cos(a),
515 ))
516 }
517}
518
519impl<S: Scalar> From<SvgArc<S>> for Arc<S> {
520 fn from(svg: SvgArc<S>) -> Self {
521 svg.to_arc()
522 }
523}
524
525impl<S: Scalar> SvgArc<S> {
526 pub fn to_arc(&self) -> Arc<S> {
528 Arc::from_svg_arc(self)
529 }
530
531 pub fn is_straight_line(&self) -> bool {
535 S::abs(self.radii.x) <= S::EPSILON
536 || S::abs(self.radii.y) <= S::EPSILON
537 || self.from == self.to
538 }
539
540 pub fn for_each_quadratic_bezier<F>(&self, cb: &mut F)
542 where
543 F: FnMut(&QuadraticBezierSegment<S>),
544 {
545 if self.is_straight_line() {
546 cb(&QuadraticBezierSegment {
547 from: self.from,
548 ctrl: self.from,
549 to: self.to,
550 });
551 return;
552 }
553
554 Arc::from_svg_arc(self).for_each_quadratic_bezier(cb);
555 }
556
557 pub fn for_each_quadratic_bezier_with_t<F>(&self, cb: &mut F)
559 where
560 F: FnMut(&QuadraticBezierSegment<S>, Range<S>),
561 {
562 if self.is_straight_line() {
563 cb(
564 &QuadraticBezierSegment {
565 from: self.from,
566 ctrl: self.from,
567 to: self.to,
568 },
569 S::ZERO..S::ONE,
570 );
571 return;
572 }
573
574 Arc::from_svg_arc(self).for_each_quadratic_bezier_with_t(cb);
575 }
576
577 pub fn for_each_cubic_bezier<F>(&self, cb: &mut F)
579 where
580 F: FnMut(&CubicBezierSegment<S>),
581 {
582 if self.is_straight_line() {
583 cb(&CubicBezierSegment {
584 from: self.from,
585 ctrl1: self.from,
586 ctrl2: self.to,
587 to: self.to,
588 });
589 return;
590 }
591
592 Arc::from_svg_arc(self).for_each_cubic_bezier(cb);
593 }
594
595 pub fn for_each_flattened<F: FnMut(&LineSegment<S>)>(&self, tolerance: S, cb: &mut F) {
600 if self.is_straight_line() {
601 cb(&LineSegment {
602 from: self.from,
603 to: self.to,
604 });
605 return;
606 }
607
608 Arc::from_svg_arc(self).for_each_flattened(tolerance, cb);
609 }
610
611 pub fn for_each_flattened_with_t<F: FnMut(&LineSegment<S>, Range<S>)>(
618 &self,
619 tolerance: S,
620 cb: &mut F,
621 ) {
622 if self.is_straight_line() {
623 cb(
624 &LineSegment {
625 from: self.from,
626 to: self.to,
627 },
628 S::ZERO..S::ONE,
629 );
630 return;
631 }
632
633 Arc::from_svg_arc(self).for_each_flattened_with_t(tolerance, cb);
634 }
635}
636
637#[derive(Copy, Clone, Debug, PartialEq, Default)]
646#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
647pub struct ArcFlags {
648 pub large_arc: bool,
654 pub sweep: bool,
662}
663
664fn arc_to_quadratic_beziers_with_t<S, F>(arc: &Arc<S>, callback: &mut F)
669where
670 S: Scalar,
671 F: FnMut(&QuadraticBezierSegment<S>, Range<S>),
672{
673 let sign = arc.sweep_angle.get().signum();
674 let sweep_angle = S::abs(arc.sweep_angle.get()).min(S::PI() * S::TWO);
675
676 let n_steps = S::ceil(sweep_angle / S::FRAC_PI_4());
677 let step = Angle::radians(sweep_angle / n_steps * sign);
678
679 let mut t0 = S::ZERO;
680 let dt = S::ONE / n_steps;
681
682 let alpha = Float::tan(step.radians * S::HALF);
683
684 let n = cast::<S, i32>(n_steps).unwrap();
685 for i in 0..n {
686 let a1 = arc.start_angle + step * cast(i).unwrap();
687 let a2 = arc.start_angle + step * cast(i + 1).unwrap();
688
689 let v1 = sample_ellipse(arc.radii, arc.x_rotation, a1).to_vector();
690 let v2 = sample_ellipse(arc.radii, arc.x_rotation, a2).to_vector();
691 let from = arc.center + v1;
692 let to = arc.center + v2;
693
694 let ctrl = from + arc.tangent_at_angle(a1) * alpha;
695
696 let t1 = if i + 1 == n { S::ONE } else { t0 + dt };
697
698 callback(&QuadraticBezierSegment { from, ctrl, to }, t0..t1);
699 t0 = t1;
700 }
701}
702
703fn arc_to_cubic_beziers<S, F>(arc: &Arc<S>, callback: &mut F)
704where
705 S: Scalar,
706 F: FnMut(&CubicBezierSegment<S>),
707{
708 let sign = arc.sweep_angle.get().signum();
709 let sweep_angle = S::abs(arc.sweep_angle.get()).min(S::PI() * S::TWO);
710
711 let n_steps = S::ceil(sweep_angle / S::FRAC_PI_2());
712 let step = Angle::radians(sweep_angle / n_steps * sign);
713
714 for i in 0..cast::<S, i32>(n_steps).unwrap() {
715 let a1 = arc.start_angle + step * cast(i).unwrap();
716 let a2 = arc.start_angle + step * cast(i + 1).unwrap();
717
718 let v1 = sample_ellipse(arc.radii, arc.x_rotation, a1).to_vector();
719 let v2 = sample_ellipse(arc.radii, arc.x_rotation, a2).to_vector();
720 let from = arc.center + v1;
721 let to = arc.center + v2;
722
723 let delta_a = a2 - a1;
726 let tan_da = Float::tan(delta_a.get() * S::HALF);
727 let alpha_sqrt = S::sqrt(S::FOUR + S::THREE * tan_da * tan_da);
728 let alpha = Float::sin(delta_a.get()) * (alpha_sqrt - S::ONE) / S::THREE;
729 let ctrl1 = from + arc.tangent_at_angle(a1) * alpha;
730 let ctrl2 = to - arc.tangent_at_angle(a2) * alpha;
731
732 callback(&CubicBezierSegment {
733 from,
734 ctrl1,
735 ctrl2,
736 to,
737 });
738 }
739}
740
741fn sample_ellipse<S: Scalar>(radii: Vector<S>, x_rotation: Angle<S>, angle: Angle<S>) -> Point<S> {
742 Rotation::new(x_rotation).transform_point(point(
743 radii.x * Float::cos(angle.get()),
744 radii.y * Float::sin(angle.get()),
745 ))
746}
747
748impl<S: Scalar> Segment for Arc<S> {
749 type Scalar = S;
750 fn from(&self) -> Point<S> {
751 self.from()
752 }
753 fn to(&self) -> Point<S> {
754 self.to()
755 }
756 fn sample(&self, t: S) -> Point<S> {
757 self.sample(t)
758 }
759 fn x(&self, t: S) -> S {
760 self.x(t)
761 }
762 fn y(&self, t: S) -> S {
763 self.y(t)
764 }
765 fn derivative(&self, t: S) -> Vector<S> {
766 self.sample_tangent(t)
767 }
768 fn split(&self, t: S) -> (Self, Self) {
769 self.split(t)
770 }
771 fn before_split(&self, t: S) -> Self {
772 self.before_split(t)
773 }
774 fn after_split(&self, t: S) -> Self {
775 self.after_split(t)
776 }
777 fn split_range(&self, t_range: Range<S>) -> Self {
778 self.split_range(t_range)
779 }
780 fn flip(&self) -> Self {
781 self.flip()
782 }
783 fn approximate_length(&self, tolerance: S) -> S {
784 self.approximate_length(tolerance)
785 }
786
787 fn for_each_flattened_with_t(
788 &self,
789 tolerance: Self::Scalar,
790 callback: &mut dyn FnMut(&LineSegment<S>, Range<S>),
791 ) {
792 self.for_each_flattened_with_t(tolerance, &mut |s, t| callback(s, t));
793 }
794}
795
796pub struct Flattened<S> {
801 arc: Arc<S>,
802 tolerance: S,
803 done: bool,
804}
805
806impl<S: Scalar> Flattened<S> {
807 pub(crate) fn new(arc: Arc<S>, tolerance: S) -> Self {
808 assert!(tolerance > S::ZERO);
809 Flattened {
810 arc,
811 tolerance,
812 done: false,
813 }
814 }
815}
816impl<S: Scalar> Iterator for Flattened<S> {
817 type Item = Point<S>;
818 fn next(&mut self) -> Option<Point<S>> {
819 if self.done {
820 return None;
821 }
822
823 let t = self.arc.flattening_step(self.tolerance);
824 if t >= S::ONE {
825 self.done = true;
826 return Some(self.arc.to());
827 }
828 self.arc = self.arc.after_split(t);
829
830 Some(self.arc.from())
831 }
832}
833
834#[test]
835fn test_from_svg_arc() {
836 use crate::vector;
837 use euclid::approxeq::ApproxEq;
838
839 let flags = ArcFlags {
840 large_arc: false,
841 sweep: false,
842 };
843
844 test_endpoints(&SvgArc {
845 from: point(0.0, -10.0),
846 to: point(10.0, 0.0),
847 radii: vector(10.0, 10.0),
848 x_rotation: Angle::radians(0.0),
849 flags,
850 });
851
852 test_endpoints(&SvgArc {
853 from: point(0.0, -10.0),
854 to: point(10.0, 0.0),
855 radii: vector(100.0, 10.0),
856 x_rotation: Angle::radians(0.0),
857 flags,
858 });
859
860 test_endpoints(&SvgArc {
861 from: point(0.0, -10.0),
862 to: point(10.0, 0.0),
863 radii: vector(10.0, 30.0),
864 x_rotation: Angle::radians(1.0),
865 flags,
866 });
867
868 test_endpoints(&SvgArc {
869 from: point(5.0, -10.0),
870 to: point(5.0, 5.0),
871 radii: vector(10.0, 30.0),
872 x_rotation: Angle::radians(-2.0),
873 flags,
874 });
875
876 test_endpoints(&SvgArc {
879 from: point(0.0, 0.0),
880 to: point(80.0, 60.0),
881 radii: vector(40.0, 40.0),
882 x_rotation: Angle::radians(0.0),
883 flags,
884 });
885
886 fn test_endpoints(svg_arc: &SvgArc<f64>) {
887 do_test_endpoints(&SvgArc {
888 flags: ArcFlags {
889 large_arc: false,
890 sweep: false,
891 },
892 ..svg_arc.clone()
893 });
894
895 do_test_endpoints(&SvgArc {
896 flags: ArcFlags {
897 large_arc: true,
898 sweep: false,
899 },
900 ..svg_arc.clone()
901 });
902
903 do_test_endpoints(&SvgArc {
904 flags: ArcFlags {
905 large_arc: false,
906 sweep: true,
907 },
908 ..svg_arc.clone()
909 });
910
911 do_test_endpoints(&SvgArc {
912 flags: ArcFlags {
913 large_arc: true,
914 sweep: true,
915 },
916 ..svg_arc.clone()
917 });
918 }
919
920 fn do_test_endpoints(svg_arc: &SvgArc<f64>) {
921 let eps = point(0.01, 0.01);
922 let arc = svg_arc.to_arc();
923 assert!(
924 arc.from().approx_eq_eps(&svg_arc.from, &eps),
925 "unexpected arc.from: {:?} == {:?}, flags: {:?}",
926 arc.from(),
927 svg_arc.from,
928 svg_arc.flags,
929 );
930 assert!(
931 arc.to().approx_eq_eps(&svg_arc.to, &eps),
932 "unexpected arc.from: {:?} == {:?}, flags: {:?}",
933 arc.to(),
934 svg_arc.to,
935 svg_arc.flags,
936 );
937 }
938}
939
940#[test]
941fn test_to_quadratics_and_cubics() {
942 use euclid::approxeq::ApproxEq;
943
944 fn do_test(arc: &Arc<f32>, expected_quadratic_count: u32, expected_cubic_count: u32) {
945 let last = arc.to();
946 {
947 let mut prev = arc.from();
948 let mut count = 0;
949 arc.for_each_quadratic_bezier(&mut |c| {
950 assert!(c.from.approx_eq(&prev));
951 prev = c.to;
952 count += 1;
953 });
954 assert!(prev.approx_eq(&last));
955 assert_eq!(count, expected_quadratic_count);
956 }
957 {
958 let mut prev = arc.from();
959 let mut count = 0;
960 arc.for_each_cubic_bezier(&mut |c| {
961 assert!(c.from.approx_eq(&prev));
962 prev = c.to;
963 count += 1;
964 });
965 assert!(prev.approx_eq(&last));
966 assert_eq!(count, expected_cubic_count);
967 }
968 }
969
970 do_test(
971 &Arc {
972 center: point(2.0, 3.0),
973 radii: vector(10.0, 3.0),
974 start_angle: Angle::radians(0.1),
975 sweep_angle: Angle::radians(3.0),
976 x_rotation: Angle::radians(0.5),
977 },
978 4,
979 2,
980 );
981
982 do_test(
983 &Arc {
984 center: point(4.0, 5.0),
985 radii: vector(3.0, 5.0),
986 start_angle: Angle::radians(2.0),
987 sweep_angle: Angle::radians(-3.0),
988 x_rotation: Angle::radians(1.3),
989 },
990 4,
991 2,
992 );
993
994 do_test(
995 &Arc {
996 center: point(0.0, 0.0),
997 radii: vector(100.0, 0.01),
998 start_angle: Angle::radians(-1.0),
999 sweep_angle: Angle::radians(0.1),
1000 x_rotation: Angle::radians(0.3),
1001 },
1002 1,
1003 1,
1004 );
1005
1006 do_test(
1007 &Arc {
1008 center: point(0.0, 0.0),
1009 radii: vector(1.0, 1.0),
1010 start_angle: Angle::radians(3.0),
1011 sweep_angle: Angle::radians(-0.1),
1012 x_rotation: Angle::radians(-0.3),
1013 },
1014 1,
1015 1,
1016 );
1017}
1018
1019#[test]
1020fn test_bounding_box() {
1021 use euclid::approxeq::ApproxEq;
1022
1023 fn approx_eq(r1: Box2D<f32>, r2: Box2D<f32>) -> bool {
1024 if !r1.min.x.approx_eq(&r2.min.x)
1025 || !r1.max.x.approx_eq(&r2.max.x)
1026 || !r1.min.y.approx_eq(&r2.min.y)
1027 || !r1.max.y.approx_eq(&r2.max.y)
1028 {
1029 std::println!("\n left: {r1:?}\n right: {r2:?}");
1030 return false;
1031 }
1032
1033 true
1034 }
1035
1036 let r = Arc {
1037 center: point(0.0, 0.0),
1038 radii: vector(1.0, 1.0),
1039 start_angle: Angle::radians(0.0),
1040 sweep_angle: Angle::pi(),
1041 x_rotation: Angle::zero(),
1042 }
1043 .bounding_box();
1044 assert!(approx_eq(
1045 r,
1046 Box2D {
1047 min: point(-1.0, 0.0),
1048 max: point(1.0, 1.0)
1049 }
1050 ));
1051
1052 let r = Arc {
1053 center: point(0.0, 0.0),
1054 radii: vector(1.0, 1.0),
1055 start_angle: Angle::radians(0.0),
1056 sweep_angle: Angle::pi(),
1057 x_rotation: Angle::pi(),
1058 }
1059 .bounding_box();
1060 assert!(approx_eq(
1061 r,
1062 Box2D {
1063 min: point(-1.0, -1.0),
1064 max: point(1.0, 0.0)
1065 }
1066 ));
1067
1068 let r = Arc {
1069 center: point(0.0, 0.0),
1070 radii: vector(2.0, 1.0),
1071 start_angle: Angle::radians(0.0),
1072 sweep_angle: Angle::pi(),
1073 x_rotation: Angle::pi() * 0.5,
1074 }
1075 .bounding_box();
1076 assert!(approx_eq(
1077 r,
1078 Box2D {
1079 min: point(-1.0, -2.0),
1080 max: point(0.0, 2.0)
1081 }
1082 ));
1083
1084 let r = Arc {
1085 center: point(1.0, 1.0),
1086 radii: vector(1.0, 1.0),
1087 start_angle: Angle::pi(),
1088 sweep_angle: Angle::pi(),
1089 x_rotation: -Angle::pi() * 0.25,
1090 }
1091 .bounding_box();
1092 assert!(approx_eq(
1093 r,
1094 Box2D {
1095 min: point(0.0, 0.0),
1096 max: point(1.707107, 1.707107)
1097 }
1098 ));
1099
1100 let mut angle = Angle::zero();
1101 for _ in 0..10 {
1102 std::println!("angle: {angle:?}");
1103 let r = Arc {
1104 center: point(0.0, 0.0),
1105 radii: vector(4.0, 4.0),
1106 start_angle: angle,
1107 sweep_angle: Angle::pi() * 2.0,
1108 x_rotation: Angle::pi() * 0.25,
1109 }
1110 .bounding_box();
1111 assert!(approx_eq(
1112 r,
1113 Box2D {
1114 min: point(-4.0, -4.0),
1115 max: point(4.0, 4.0)
1116 }
1117 ));
1118 angle += Angle::pi() * 2.0 / 10.0;
1119 }
1120
1121 let mut angle = Angle::zero();
1122 for _ in 0..10 {
1123 std::println!("angle: {angle:?}");
1124 let r = Arc {
1125 center: point(0.0, 0.0),
1126 radii: vector(4.0, 4.0),
1127 start_angle: Angle::zero(),
1128 sweep_angle: Angle::pi() * 2.0,
1129 x_rotation: angle,
1130 }
1131 .bounding_box();
1132 assert!(approx_eq(
1133 r,
1134 Box2D {
1135 min: point(-4.0, -4.0),
1136 max: point(4.0, 4.0)
1137 }
1138 ));
1139 angle += Angle::pi() * 2.0 / 10.0;
1140 }
1141}
1142
1143#[test]
1144fn negative_flattening_step() {
1145 let arc = Arc {
1149 center: point(-100.0, -150.0),
1150 radii: vector(50.0, 50.0),
1151 start_angle: Angle::radians(0.982944787),
1152 sweep_angle: Angle::radians(-898.0),
1153 x_rotation: Angle::zero(),
1154 };
1155
1156 arc.for_each_flattened(0.100000001, &mut |_| {});
1157
1158 let arc = Arc {
1162 center: point(0.0, 0.0),
1163 radii: vector(100.0, 10.0),
1164 start_angle: Angle::radians(0.2),
1165 sweep_angle: Angle::radians(-2.0),
1166 x_rotation: Angle::zero(),
1167 };
1168
1169 let flattened: std::vec::Vec<_> = arc.flattened(0.1).collect();
1170
1171 assert!(flattened.len() > 1);
1172}