From 820a750310d359c6ff91acf1ecf856202c562a21 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 2 Nov 2026 02:53:34 -0800 Subject: [PATCH] SVG: a small circle far from the origin imports as a four-anchor ellipse The tolerance that compares the points usvg makes of a or grew with the center's distance from the origin, so a circle of radius 400 at x 40,000,000 fell back to eight anchors. The tolerance is now a few f32 steps at the size of the coordinates, and the on-ellipse check allows the same steps relative to the smaller radius. --- crates/svg/src/import/convert.rs | 7 ++++--- crates/svg/tests/it/basic_shapes.rs | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/svg/src/import/convert.rs b/crates/svg/src/import/convert.rs index a154a233f..e640880e7 100644 --- a/crates/svg/src/import/convert.rs +++ b/crates/svg/src/import/convert.rs @@ -156,10 +156,11 @@ 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: the tolerance follows the size of the numbers. - let tol = 1e-5 * (c.x.abs() + c.y.abs() + rx + ry).max(1.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())); let axes = [Point::new(x1, c.y), Point::new(c.x, y1), Point::new(x0, c.y), Point::new(c.x, y0)]; - let on_ellipse = |q: &Point| (((q.x - c.x) / rx).powi(2) + ((q.y - c.y) / ry).powi(2) - 1.0).abs() <= 1e-3; + 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 diff --git a/crates/svg/tests/it/basic_shapes.rs b/crates/svg/tests/it/basic_shapes.rs index e6aa0c12b..24ef6fd8d 100644 --- a/crates/svg/tests/it/basic_shapes.rs +++ b/crates/svg/tests/it/basic_shapes.rs @@ -163,9 +163,10 @@ fn circles_and_ellipses_import_as_the_ellipse_drawn() { let svg = r#" - "#; + + "#; let back = paths(&import(svg).unwrap()); - assert_eq!(back.len(), 3); + assert_eq!(back.len(), 4); 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); @@ -178,4 +179,7 @@ fn circles_and_ellipses_import_as_the_ellipse_drawn() { // However many curves the arcs of a flat ellipse make. let flat = &back[2].subpaths[0].anchors; assert!(flat.len() == 4 && flat[0].p.distance(Point::new(72.51, 154.118)) < 1e-3, "{flat:?}"); + // 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:?}"); }