diff --git a/crates/svg/src/import/convert.rs b/crates/svg/src/import/convert.rs index e640880e7..2a777b342 100644 --- a/crates/svg/src/import/convert.rs +++ b/crates/svg/src/import/convert.rs @@ -156,14 +156,16 @@ pub(super) fn drawn_ellipse(p: &usvg::tiny_skia_path::Path) -> Option let (x0, x1) = ends.iter().fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), q| (lo.min(q.x), hi.max(q.x))); let (y0, y1) = ends.iter().fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), q| (lo.min(q.y), hi.max(q.y))); let (c, rx, ry) = (Point::new((x0 + x1) / 2.0, (y0 + y1) / 2.0), (x1 - x0) / 2.0, (y1 - y0) / 2.0); - // usvg works in f32: points match within a few of its steps at the size of the coordinates. - let tol = 16.0 * f64::from(f32::EPSILON) * [x0, x1, y0, y1].iter().fold(1.0f64, |m, v| m.max(v.abs())); + // usvg works in f32: points match within a few of its steps at the size of the coordinates, and + // a radius of fewer than two steps does not survive the conversion. + let step = f64::from(f32::EPSILON) * [x0, x1, y0, y1].iter().fold(1.0f64, |m, v| m.max(v.abs())); + let tol = 16.0 * step; let axes = [Point::new(x1, c.y), Point::new(c.x, y1), Point::new(x0, c.y), Point::new(c.x, y0)]; let off = 1e-3 + 4.0 * tol / rx.min(ry); let on_ellipse = |q: &Point| (((q.x - c.x) / rx).powi(2) + ((q.y - c.y) / ry).powi(2) - 1.0).abs() <= off; if !closed - || rx <= tol - || ry <= tol + || rx < 2.0 * step + || ry < 2.0 * step || ends.first().is_none_or(|s| s.distance(axes[0]) > tol) || !axes.iter().all(|a| ends.iter().any(|q| q.distance(*a) <= tol)) || !ends.iter().all(on_ellipse) diff --git a/crates/svg/tests/it/basic_shapes.rs b/crates/svg/tests/it/basic_shapes.rs index 8d76739b4..da759b197 100644 --- a/crates/svg/tests/it/basic_shapes.rs +++ b/crates/svg/tests/it/basic_shapes.rs @@ -164,9 +164,10 @@ fn circles_and_ellipses_import_as_the_ellipse_drawn() { - "#; + + "#; let back = paths(&import(svg).unwrap()); - assert_eq!(back.len(), 4); + assert_eq!(back.len(), 5); let circle = &back[0].subpaths[0].anchors; let want: Vec = [(100.0, 70.0), (60.0, 110.0), (20.0, 70.0), (60.0, 30.0)].map(|(x, y)| Point::new(x, y)).into(); assert_eq!(circle.len(), 4); @@ -182,6 +183,11 @@ fn circles_and_ellipses_import_as_the_ellipse_drawn() { // A small circle far from the origin too. let far = &back[3].subpaths[0].anchors; assert!(far.len() == 4 && far[0].p.distance(Point::new(40_000_400.0, 0.0)) < 8.0, "{far:?}"); + // A radius of a few of f32's steps at its coordinates, with handles of the standard length. + let thin = &back[4].subpaths[0].anchors; + let handle = thin.first().map(|a| a.h_out - a.p); + assert!(thin.len() == 4 && thin[0].p.distance(Point::new(1_000_036.0, 1_000_000.0)) < 0.25, "{thin:?}"); + assert!(handle.is_some_and(|h| h.x.abs() < 1e-6 && (h.y - shapes::KAPPA).abs() < 1e-6), "{thin:?}"); } #[test]