diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 2ecf5b7bb1..4b7e2dcc61 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -409,6 +409,8 @@ pub(super) fn handle_stroke_caps( if let [first_point, .., last_point] = points.as_slice() { let mut paint_stroke = paint.clone(); + paint_stroke.set_path_effect(None); + if let Some(filter) = blur { paint_stroke.set_image_filter(filter.clone()); } diff --git a/render-wasm/src/shapes/stroke_paths.rs b/render-wasm/src/shapes/stroke_paths.rs index a38ba96fb1..52dc58e762 100644 --- a/render-wasm/src/shapes/stroke_paths.rs +++ b/render-wasm/src/shapes/stroke_paths.rs @@ -423,4 +423,43 @@ mod tests { assert!(bounds.left < -1., "bounds: {bounds:?}"); assert!(bounds.right > 104., "bounds: {bounds:?}"); } + + #[test] + fn dashed_stroke_keeps_solid_caps() { + let stroke = Stroke::new_center_stroke( + 4., + StrokeStyle::Dashed, + Some(StrokeCap::Round), + Some(StrokeCap::Round), + Some(7.), + Some(7.), + ); + let selrect = Rect::from_xywh(0., 0., 100., 0.); + let path = stroke_to_path(&stroke, &horizontal_line(), None, &selrect, None, false) + .expect("stroke outline") + .to_skia_path(None); + + assert!(path.contains((-1.5, 0.)), "start cap missing"); + assert!(path.contains((101.5, 0.)), "end cap missing"); + } + + #[test] + fn dashed_stroke_marker_caps_are_not_dashed() { + let stroke = Stroke::new_center_stroke( + 4., + StrokeStyle::Dashed, + None, + Some(StrokeCap::SquareMarker), + Some(3.), + Some(3.), + ); + let selrect = Rect::from_xywh(0., 0., 100., 0.); + let path = stroke_to_path(&stroke, &horizontal_line(), None, &selrect, None, false) + .expect("stroke outline") + .to_skia_path(None); + + for x in [100., 102., 104., 106., 107.5] { + assert!(path.contains((x, 0.)), "marker has a gap at x = {x}"); + } + } } diff --git a/render-wasm/src/shapes/strokes.rs b/render-wasm/src/shapes/strokes.rs index 2e80abafe0..2a30b57e77 100644 --- a/render-wasm/src/shapes/strokes.rs +++ b/render-wasm/src/shapes/strokes.rs @@ -463,6 +463,9 @@ impl Stroke { /// so this is a no-op on closed paths and avoids the extra fill draw the /// manual caps would otherwise require on open paths. pub fn to_skia_linecap(&self) -> Option { + if self.style != StrokeStyle::Solid { + return None; + } match (self.cap_start, self.cap_end) { (Some(StrokeCap::Round), Some(StrokeCap::Round)) => Some(skia::paint::Cap::Round), (Some(StrokeCap::Square), Some(StrokeCap::Square)) => Some(skia::paint::Cap::Square), @@ -587,6 +590,32 @@ mod tests { assert_eq!(dashed.style_at_scale(0.05), StrokeStyle::Dashed); } + #[test] + fn native_linecap_is_used_for_solid_strokes() { + let solid = Stroke::new_center_stroke( + 4.0, + StrokeStyle::Solid, + Some(StrokeCap::Round), + Some(StrokeCap::Round), + None, + None, + ); + assert_eq!(solid.to_skia_linecap(), Some(skia::paint::Cap::Round)); + } + + #[test] + fn native_linecap_is_skipped_for_dashed_strokes() { + let dashed = Stroke::new_center_stroke( + 4.0, + StrokeStyle::Dashed, + Some(StrokeCap::Round), + Some(StrokeCap::Round), + None, + None, + ); + assert_eq!(dashed.to_skia_linecap(), None); + } + #[test] fn path_lod_never_drops_thin_stroke() { // Hairline strokes must still paint (stroke-only icons).