diff --git a/render-wasm/src/shapes/paths.rs b/render-wasm/src/shapes/paths.rs index 33666bb0de..94f7521c3e 100644 --- a/render-wasm/src/shapes/paths.rs +++ b/render-wasm/src/shapes/paths.rs @@ -156,6 +156,7 @@ impl Path { let mut current_point = 0; let mut current_conic = 0; let mut last_point = skia::Point::new(0.0, 0.0); + let mut subpath_start = skia::Point::new(0.0, 0.0); for verb in verbs { match verb { @@ -163,12 +164,15 @@ impl Path { let p = points[current_point]; segments.push(Segment::MoveTo((p.x, p.y))); last_point = p; + subpath_start = p; current_point += 1; } skia::PathVerb::Line => { let p = points[current_point]; - segments.push(Segment::LineTo((p.x, p.y))); - last_point = p; + if p != last_point { + segments.push(Segment::LineTo((p.x, p.y))); + last_point = p; + } current_point += 1; } skia::PathVerb::Quad => { @@ -239,6 +243,13 @@ impl Path { current_point += 3; } skia::PathVerb::Close => { + if let Some(Segment::LineTo(p)) = segments.last() { + if (p.0 - subpath_start.x).abs() < 1e-5 + && (p.1 - subpath_start.y).abs() < 1e-5 + { + segments.pop(); + } + } segments.push(Segment::Close); } } diff --git a/render-wasm/src/shapes/stroke_paths.rs b/render-wasm/src/shapes/stroke_paths.rs index 358be86141..cbf06874db 100644 --- a/render-wasm/src/shapes/stroke_paths.rs +++ b/render-wasm/src/shapes/stroke_paths.rs @@ -58,27 +58,24 @@ pub fn stroke_to_path( // For inner/outer strokes, use boolean ops to clip // the 2×-width stroke outline to the correct region. // Set EvenOdd to preserve the annular ring's inner hole, - // then as_winding() on the result fixes contour winding - // for Penpot's NonZero fill rule. + // then switch to Winding for Penpot's NonZero fill rule. + // Use set_fill_type instead of as_winding() because as_winding() + // decomposes self-intersecting geometry, which removes points + // at intersections of straight lines in closed paths. + // Center strokes skip the conversion: fill_path_with_paint + // already produces correctly-wound contours. let final_path = match render_kind { - StrokeKind::Inner => { - stroke_outline.set_fill_type(skia::PathFillType::EvenOdd); - let inner = stroke_outline - .op(&transformed_shape_path, skia::PathOp::Intersect) - .unwrap_or(stroke_outline); - inner.as_winding().unwrap_or(inner) - } - StrokeKind::Outer => { - stroke_outline.set_fill_type(skia::PathFillType::EvenOdd); - let outer = stroke_outline - .op(&transformed_shape_path, skia::PathOp::Difference) - .unwrap_or(stroke_outline); - outer.as_winding().unwrap_or(outer) - } - StrokeKind::Center => { - stroke_outline.set_fill_type(skia::PathFillType::EvenOdd); - stroke_outline.as_winding().unwrap_or(stroke_outline) - } + StrokeKind::Inner => stroke_outline + .simplify() + .unwrap() + .op(&transformed_shape_path, skia::PathOp::Intersect) + .unwrap_or(stroke_outline), + StrokeKind::Outer => stroke_outline + .simplify() + .unwrap() + .op(&transformed_shape_path, skia::PathOp::Difference) + .unwrap_or(stroke_outline), + StrokeKind::Center => stroke_outline.simplify().unwrap_or(stroke_outline), }; // If there was a path_transform, invert it back to local coords