SVG: a thin ellipse far from the origin imports with standard handles

An ellipse whose radius is smaller than the point tolerance at its coordinates, but at least two
f32 steps, now imports as the four-anchor ellipse instead of usvg's curves with rounded handles.
This commit is contained in:
dotCooCoo
2026-11-02 03:09:08 -08:00
parent b4d3ae41bf
commit 7c7a127ec8
2 changed files with 14 additions and 6 deletions
+6 -4
View File
@@ -156,14 +156,16 @@ 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: 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)
+8 -2
View File
@@ -164,9 +164,10 @@ fn circles_and_ellipses_import_as_the_ellipse_drawn() {
<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"/>
<circle cx="40000000" cy="0" r="400"/></svg>"#;
<circle cx="40000000" cy="0" r="400"/>
<ellipse cx="1000000" cy="1000000" rx="36" ry="1"/></svg>"#;
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<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);
@@ -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]