From 4943285e6300ba352971ce85657862de3ac57da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Torr=C3=B3?= Date: Fri, 25 Sep 2026 14:12:01 +0200 Subject: [PATCH] :bug: Fix inner and outer rect strokes ignoring corner radius (#11926) --- render-wasm/src/render/strokes.rs | 26 ++++++++++++++++++++++++-- render-wasm/src/shapes/strokes.rs | 4 +++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 527e395c15..48efeada92 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -40,9 +40,12 @@ pub(super) fn draw_stroke_on_rect( return; } + // Radii as Skia fits them to the shape, so the stroke follows the fill. + let shape_radii = corners.map(|radii| *RRect::new_rect_radii(*rect, &radii).radii_ref()); + // By default just draw the rect. Only dotted inner/outer strokes need // clipping to prevent the dotted pattern from appearing in wrong areas. - let draw_stroke = || match corners { + let draw_stroke = || match &shape_radii { Some(radii) => { let radii = stroke.outer_corners(radii); let rrect = RRect::new_rect_radii(stroke_rect, &radii); @@ -73,10 +76,13 @@ pub(super) fn draw_stroke_on_rect( draw_stroke(); canvas.restore(); } else if stroke.kind == StrokeKind::Inner - && (stroke.width >= rect.width() || stroke.width >= rect.height()) + && (stroke.width >= rect.width() + || stroke.width >= rect.height() + || inner_corners_collapse(stroke, rect, &shape_radii)) { // When the inner stroke width exceeds a shape dimension, the inset // rect goes negative and the stroke overflows outside the shape. + // Same when the inset radii can't follow the shape corners. // Fall back to the same approach as the SVG renderer: draw with // doubled width centered on the original shape and clip to it. canvas.save(); @@ -106,6 +112,22 @@ pub(super) fn draw_stroke_on_rect( } } +/// Whether the inset radii clamp to zero or get rescaled by Skia to fit. +fn inner_corners_collapse(stroke: &Stroke, rect: &Rect, radii: &Option) -> bool { + let Some(radii) = radii else { + return false; + }; + if matches!(stroke.style, StrokeStyle::Dotted | StrokeStyle::Dashed) { + return false; + } + let wanted = stroke.outer_corners(radii); + let inset = RRect::new_rect_radii(stroke.outer_rect(rect), &wanted); + radii + .iter() + .zip(wanted.iter().zip(inset.radii_ref())) + .any(|(shape, (want, got))| shape.x > 0.0 && (want.x <= 0.0 || want != got)) +} + /// Draws a rect/frame stroke whose sides have different widths as the area /// between an outer and an inner (rounded) rect, mitered like CSS borders. /// Dashed/dotted styles cut that band per side, each side fitting a whole diff --git a/render-wasm/src/shapes/strokes.rs b/render-wasm/src/shapes/strokes.rs index e5df8fd049..0a479442e4 100644 --- a/render-wasm/src/shapes/strokes.rs +++ b/render-wasm/src/shapes/strokes.rs @@ -358,7 +358,9 @@ impl Stroke { let mut outer = *corners; for corner in outer.iter_mut() { - corner.offset((offset, offset)) + if corner.x > 0.0 { + corner.offset((offset, offset)) + } } outer }