SVG: a small circle far from the origin imports as a four-anchor ellipse

The tolerance that compares the points usvg makes of a <circle> or
<ellipse> 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.
This commit is contained in:
dotCooCoo
2026-11-02 02:53:34 -08:00
parent 60e1600caa
commit 820a750310
2 changed files with 10 additions and 5 deletions
+4 -3
View File
@@ -156,10 +156,11 @@ pub(super) fn drawn_ellipse(p: &usvg::tiny_skia_path::Path) -> Option<PathData>
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
+6 -2
View File
@@ -163,9 +163,10 @@ fn circles_and_ellipses_import_as_the_ellipse_drawn() {
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<circle cx="50" cy="50" r="40" transform="translate(10 20)"/>
<ellipse cx="100" cy="100" rx="60" ry="20" transform="rotate(30 100 100)"/>
<ellipse cx="36.255" cy="154.118" rx="36.255" ry="5.421"/></svg>"#;
<ellipse cx="36.255" cy="154.118" rx="36.255" ry="5.421"/>
<circle cx="40000000" cy="0" r="400"/></svg>"#;
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<Point> = [(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:?}");
}