From e03a852fb2e25bbd591d622a2ce0b5769f0f496d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Torr=C3=B3?= Date: Fri, 26 Jun 2026 15:39:46 +0200 Subject: [PATCH] :bug: Set evenodd when needed on stroke to path (#10446) --- .../data/workspace/path/shapes_to_path.cljs | 22 ++++++++++--------- frontend/src/app/render_wasm/api.cljs | 18 +++++++++------ render-wasm/src/mem.rs | 20 +++++++++++++++++ render-wasm/src/wasm/paths.rs | 4 +++- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs b/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs index 61134948cd..6c55397514 100644 --- a/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs +++ b/frontend/src/app/main/data/workspace/path/shapes_to_path.cljs @@ -104,17 +104,19 @@ (into [] (keep-indexed (fn [idx stroke] - (let [content (wasm.api/stroke-to-path (:id shape) idx)] - (when (some? content) + (let [result (wasm.api/stroke-to-path (:id shape) idx)] + (when (some? result) (cts/setup-shape - {:type :path - :id (uuid/next) - :name (str (:name shape) " (stroke)") - :parent-id parent-id - :frame-id frame-id - :content content - :fills [(stroke->fill stroke)] - :strokes []}))))) + (cond-> {:type :path + :id (uuid/next) + :name (str (:name shape) " (stroke)") + :parent-id parent-id + :frame-id frame-id + :content (:content result) + :fills [(stroke->fill stroke)] + :strokes []} + (:even-odd? result) + (assoc :svg-attrs {:fillRule "evenodd"}))))))) (:strokes shape))) (defn convert-selected-strokes-to-path diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index a1a0a2f6bf..d2e1be72bf 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -2272,21 +2272,25 @@ (defn stroke-to-path "Converts a shape's stroke at the given index into a filled path. - Returns the stroke outline as PathData content." + Returns a map {:content :even-odd? }, or nil when the + stroke produces no geometry. The buffer carries two header words ahead of + the segments: [even-odd flag][length] (the flat segment list can't encode + the fill rule itself)." [id stroke-index] (use-shape id) (try - (let [offset (-> (h/call wasm/internal-module "_convert_stroke_to_path" stroke-index) - (mem/->offset-32)) - heap (mem/get-heap-u32) - length (aget heap offset)] + (let [offset (-> (h/call wasm/internal-module "_convert_stroke_to_path" stroke-index) + (mem/->offset-32)) + heap (mem/get-heap-u32) + even-odd? (not (zero? (aget heap offset))) + length (aget heap (inc offset))] (if (pos? length) (let [data (mem/slice heap - (+ offset 1) + (+ offset 2) (* length path.impl/SEGMENT-U32-SIZE)) content (path/from-bytes data)] (mem/free) - content) + {:content content :even-odd? even-odd?}) (do (mem/free) nil))) (catch :default cause diff --git a/render-wasm/src/mem.rs b/render-wasm/src/mem.rs index 1c8531e7e9..a786f8eb55 100644 --- a/render-wasm/src/mem.rs +++ b/render-wasm/src/mem.rs @@ -95,3 +95,23 @@ pub fn write_vec(result: Vec) -> *mut u8 { write_bytes(result_bytes) } + +/* + Like `write_vec`, but prepends an extra u32 header word before the + length. Layout: [header u32][length u32][items...] +*/ +pub fn write_vec_with_header(header: u32, result: Vec) -> *mut u8 { + let elem_size = size_of::(); + let bytes_len = 8 + result.len() * elem_size; + let mut result_bytes = vec![0; bytes_len]; + + result_bytes[0..4].clone_from_slice(&header.to_le_bytes()); + result_bytes[4..8].clone_from_slice(&result.len().to_le_bytes()); + + for (i, item) in result.iter().enumerate() { + let base = 8 + i * elem_size; + item.clone_to_slice(&mut result_bytes[base..base + elem_size]); + } + + write_bytes(result_bytes) +} diff --git a/render-wasm/src/wasm/paths.rs b/render-wasm/src/wasm/paths.rs index 2350edb393..b035ee4f81 100644 --- a/render-wasm/src/wasm/paths.rs +++ b/render-wasm/src/wasm/paths.rs @@ -243,6 +243,7 @@ pub extern "C" fn current_to_path() -> *mut u8 { #[no_mangle] pub extern "C" fn convert_stroke_to_path(stroke_index: i32) -> *mut u8 { let mut result = Vec::::default(); + let mut even_odd = false; with_current_shape!(state, |shape: &Shape| { let idx = stroke_index as usize; if let Some(stroke) = shape.strokes.get(idx) { @@ -257,6 +258,7 @@ pub extern "C" fn convert_stroke_to_path(stroke_index: i32) -> *mut u8 { shape.svg_attrs.as_ref(), false, ) { + even_odd = path.is_even_odd(); result = path .segments() .iter() @@ -267,7 +269,7 @@ pub extern "C" fn convert_stroke_to_path(stroke_index: i32) -> *mut u8 { } }); - mem::write_vec(result) + mem::write_vec_with_header(even_odd as u32, result) } #[cfg(test)]