Keep rounded corners round in non-uniformly scaled groups

Fixes #1083

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-20 22:50:30 +02:00
co-authored by Claude Haiku 5.5
parent a31b990d32
commit 98b6d04413
2 changed files with 53 additions and 1 deletions
+22 -1
View File
@@ -851,6 +851,27 @@ impl Renderer {
(tw, th, m * Affine::scale_non_uniform(1.0 / sx, 1.0 / sy))
}
/// A shape's geometry in its own `w` × `h` space, built at its size on the slide: inside a
/// group whose child coordinates scale the shape unevenly, the preset's rounded corners and
/// other size-derived features stay as they would be on a plain shape of that size (#1083).
fn scaled_geometry(&self, s: &Shape, m: Affine, depth: usize, w: f64, h: f64) -> Geometry {
let (tw, th, _) = self.text_space(m, depth, w, h);
if (tw, th) == (w, h) {
return shape_geometry(s, w, h);
}
let mut geo = shape_geometry(s, tw, th);
let k = |a: f64, b: f64| if b.abs() > 1e-12 { a / b } else { 1.0 };
let back = Affine::scale_non_uniform(k(w, tw), k(h, th));
for p in &mut geo.paths {
p.path.apply_affine(back);
}
geo.text_rect = back.transform_rect_bbox(geo.text_rect);
for p in &mut geo.sites {
*p = back * *p;
}
geo
}
fn shape_at(&mut self, ctx: &mut RenderContext, f: &Frame, rctx: &Ctx, s: &Shape, parent: Affine, depth: usize) {
if depth > 32 || (s.hidden && !f.opts.show_hidden) || f.opts.skip.contains(&s.id) {
return;
@@ -1007,7 +1028,7 @@ impl Renderer {
) {
let (geo, fades) = match self.paths.iter().find(|p| p.id == s.id) {
Some(pm) => self.morphs.geometry(pm, s, w, h),
None => (shape_geometry(s, w, h), Vec::new()),
None => (self.scaled_geometry(s, m, depth, w, h), Vec::new()),
};
let fade = |i: usize| fades.get(i).copied().unwrap_or(deckcraft_geom::morph::Fade { fill: 1.0, stroke: 1.0 });
let (fill, fill_ph) = resolve::fill(rctx, s);
+31
View File
@@ -1971,6 +1971,37 @@ fn text_in_a_scaled_group_keeps_its_point_size() {
}
}
#[test]
fn rounded_corners_in_a_stretched_group_stay_round() {
// #1083: a group that stretches its child space 4x across makes a rounded rectangle wider on
// the slide, but its corners stay circular, like a plain rounded rectangle of that size.
let round = |id: u32, w: f64, h: f64| Shape {
id: ShapeId(id),
xfrm: Some(Xfrm::new(0.0, 0.0, w, h)),
geom: Geom::Preset { name: "roundRect".into(), adj: vec![16667.0] },
fill: Some(Fill::solid(ColorRef::rgb(deckcraft_color::Rgba::BLACK))),
line: Some(deckcraft_model::style::Line { fill: Some(Fill::None), ..Default::default() }),
..Default::default()
};
let render = |sh: Shape| {
let mut p = Presentation::default();
std::sync::Arc::make_mut(&mut p.slides[0]).shapes = vec![sh];
render_slide(&p, 0, &RenderOpts { scale: 1.0, ..Default::default() })
};
let mut plain = round(700, 400.0, 100.0);
plain.xfrm = Some(Xfrm::new(100.0, 100.0, 400.0, 100.0));
let plain = render(plain);
let grouped = render(Shape {
id: ShapeId(701),
xfrm: Some(Xfrm::new(100.0, 100.0, 400.0, 100.0)),
kind: ShapeKind::Group { children: vec![round(702, 100.0, 100.0)], child: Xfrm::new(0.0, 0.0, 100.0, 100.0) },
..Default::default()
});
for (x, y) in [(120, 102), (102, 120), (480, 102), (498, 180), (120, 198)] {
assert_eq!(plain.pixel(x, y), grouped.pixel(x, y), "pixel ({x},{y}) of a roundRect in a stretched group");
}
}
#[test]
fn a_fill_overlay_blends_over_the_fill() {
// `a:fillOverlay blend="mult"`: a 50% grey multiplied over a light fill darkens it; "screen"