From c0dcfc1db5a264298d397f9f4e985ddad07baf3c Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 13 Oct 2026 07:22:32 -0400 Subject: [PATCH] Geom: stars stretched by the standard's hf/vf factors Five-, six-, seven- and ten-pointed stars used their own normalisation and came out narrower or shifted against PowerPoint's. ECMA-376 places a star's points on an ellipse around (hc, vc * vf) with radii half the box times hf / vf (star5 1.05146 / 1.10557, star6 1.15470, star7 1.02572 / 1.05210, star10 1.05146), so the points touch the box's edges; now done for all stars (checked exactly against PowerPoint's outlines at three sizes and inner-radius adjusts). Wild synthetic set: star galleries 4.08 -> 3.76, 3.77 -> 3.45, 4.62 -> 4.31 and the adjust galleries slightly. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/geom/src/preset.rs | 40 ++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/crates/geom/src/preset.rs b/crates/geom/src/preset.rs index 33575bf..b93f94f 100644 --- a/crates/geom/src/preset.rs +++ b/crates/geom/src/preset.rs @@ -520,14 +520,25 @@ fn normalize(pts: &[(f64, f64)], w: f64, h: f64) -> Vec<(f64, f64)> { pts.iter().map(|(x, y)| ((x - x0) * sx, (y - y0) * sy)).collect() } -fn star(n: usize, w: f64, h: f64, inner: f64, normalized: bool) -> Vec<(f64, f64)> { +/// An n-pointed star as ECMA-376 draws it: points on an ellipse around (hc, vc ยท vf) with radii +/// half the box stretched by the preset's `hf` / `vf` (so a five-pointed star fills its box and +/// looks regular in a square), the inner corners `inner` of the way out. +fn star(n: usize, w: f64, h: f64, inner: f64) -> Vec<(f64, f64)> { + let (hf, vf) = match n { + 5 => (1.05146, 1.10557), + 6 => (1.15470, 1.0), + 7 => (1.02572, 1.05210), + 10 => (1.05146, 1.0), + _ => (1.0, 1.0), + }; + let (rx, ry, cy) = (w / 2.0 * hf, h / 2.0 * vf, h / 2.0 * vf); let mut pts = Vec::with_capacity(n * 2); for i in 0..n * 2 { let a = (-90.0 + 180.0 * i as f64 / n as f64).to_radians(); let r = if i % 2 == 0 { 1.0 } else { inner }; - pts.push((r * a.cos(), r * a.sin())); + pts.push((w / 2.0 + r * rx * a.cos(), cy + r * ry * a.sin())); } - if normalized { normalize(&pts, w, h) } else { pts.iter().map(|(x, y)| (w / 2.0 + x * w / 2.0, h / 2.0 + y * h / 2.0)).collect() } + pts } #[derive(Clone, Copy)] @@ -1576,8 +1587,7 @@ pub fn build(name: &str, w: f64, h: f64, adj: &[f64]) -> Option { let n: usize = name.trim_start_matches("star").parse().unwrap_or(5); let v = a(0).clamp(0.0, 50000.0); let inner = v / 50000.0; - let normalized = matches!(n, 5 | 7); - let mut g = simple(b.poly(&star(n, w, h, inner, normalized)).take(), full.inset(-ss * (0.5 - inner * 0.5) * 0.5), w, h); + let mut g = simple(b.poly(&star(n, w, h, inner)).take(), full.inset(-ss * (0.5 - inner * 0.5) * 0.5), w, h); g.handles.push(Handle::new(0, Point::new(w / 2.0, h / 2.0), Vec2::new(0.0, -h / 2.0 / 50000.0), v, 0.0, 50000.0)); g } @@ -2268,6 +2278,26 @@ mod tests { } } + #[test] + fn stars_are_stretched_like_the_standards() { + // star5 in a 216 pt square: top point at the top middle, side points at 18 degrees on an + // ellipse 5.146% wider and 10.557% taller, centred 10.557% below the middle; its bottom + // points reach the box's bottom edge. + let k = 216.0; + let g = build("star5", k, k, &[]).unwrap(); + let (rx, ry, cy) = (k / 2.0 * 1.05146, k / 2.0 * 1.10557, k / 2.0 * 1.10557); + let a = 18f64.to_radians(); + assert!(on_path(&g, Point::new(k / 2.0, 0.0), 1e-6)); + assert!(on_path(&g, Point::new(k / 2.0 + rx * a.cos(), cy - ry * a.sin()), 1e-6)); + use kurbo::Shape as _; + let bb = g.paths[0].path.bounding_box(); + assert!((bb.x0).abs() < 0.5 && (bb.x1 - k).abs() < 0.5 && (bb.y1 - k).abs() < 0.5, "{bb:?}"); + // star6 is 15.47% wider than its box's half-width allows (its points touch the sides). + let g = build("star6", k, k, &[]).unwrap(); + let bb = g.paths[0].path.bounding_box(); + assert!((bb.x0).abs() < 0.5 && (bb.x1 - k).abs() < 0.5, "{bb:?}"); + } + #[test] fn the_cloud_fills_its_box_at_any_aspect_and_has_creases() { use kurbo::Shape as _;