From b3a62eeb4b19e3e0b5b6a4c04e380cb87cd9bcff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Torr=C3=B3?= Date: Thu, 8 Oct 2026 09:07:33 +0200 Subject: [PATCH] :bug: Paint layout children in canvas z-order in vector exports (#12171) --- render-wasm/src/render.rs | 7 ++++ render-wasm/src/render/svg/frames.rs | 4 +- render-wasm/src/render/svg/tests.rs | 62 +++++++++++++++++++++++++++- render-wasm/src/render/vector.rs | 5 ++- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index cfc3ed1a0f..322319796b 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -349,6 +349,13 @@ fn sort_z_index(tree: ShapesPoolRef, element: &Shape, children_ids: Vec) - } } +/// Children bottom to top, in the same order the GPU renderer paints them. +pub(crate) fn children_paint_order(tree: ShapesPoolRef, element: &Shape) -> Vec { + let mut ids = sort_z_index(tree, element, element.children_ids(false)); + ids.reverse(); + ids +} + /// Whether this `render_shape_exit` pass closes the shape's focus scope. A /// masked group keeps it open across the mask pass. fn exit_closes_focus_scope(element: &Shape, visited_mask: bool) -> bool { diff --git a/render-wasm/src/render/svg/frames.rs b/render-wasm/src/render/svg/frames.rs index 99fede43b3..fa3bb808f6 100644 --- a/render-wasm/src/render/svg/frames.rs +++ b/render-wasm/src/render/svg/frames.rs @@ -9,7 +9,7 @@ use super::document::{ }; use super::images::{emit_fills, emit_strokes}; use super::render_tree; -use crate::render::RenderResources; +use crate::render::{children_paint_order, RenderResources}; pub(super) fn render_frame( builder: &mut SvgLayerCanvas, @@ -104,7 +104,7 @@ fn render_frame_body( )?; } - let children: Vec<_> = element.children_ids_iter_forward(false).copied().collect(); + let children = children_paint_order(tree, element); for child_id in &children { render_tree(builder, shared, child_id, tree, scale)?; } diff --git a/render-wasm/src/render/svg/tests.rs b/render-wasm/src/render/svg/tests.rs index 2f5e5ff8e6..7557eebb7c 100644 --- a/render-wasm/src/render/svg/tests.rs +++ b/render-wasm/src/render/svg/tests.rs @@ -1,8 +1,9 @@ use super::fixtures::*; use crate::shapes::{ - radius_to_sigma, BlendMode, Blur, BlurType, Fill, ImageFill, ImageFillTransform, Shadow, - ShadowStyle, SolidColor, StrokeCap, StrokeKind, + radius_to_sigma, AlignContent, AlignItems, BlendMode, Blur, BlurType, Fill, FlexDirection, + ImageFill, ImageFillTransform, JustifyContent, JustifyItems, Shadow, ShadowStyle, SolidColor, + StrokeCap, StrokeKind, WrapType, }; use crate::state::ShapesPool; use crate::uuid::Uuid; @@ -222,6 +223,63 @@ fn exports_frame_background_blur_before_children() { insta::assert_snapshot!(svg); } +#[test] +fn exports_flex_children_with_first_child_on_top() { + let mut pool = ShapesPool::new(); + let frame_id = uid(1); + let first = uid(2); + let second = uid(3); + add_frame( + &mut pool, + frame_id, + Uuid::nil(), + (0.0, 0.0, 200.0, 120.0), + skia::Color::from_rgb(255, 255, 255), + false, + ); + pool.get_mut(&frame_id).unwrap().set_flex_layout_data( + FlexDirection::Row, + 0.0, + 0.0, + AlignItems::Start, + AlignContent::Start, + JustifyItems::Start, + JustifyContent::Start, + WrapType::NoWrap, + 0.0, + 0.0, + 0.0, + 0.0, + ); + add_solid_rect( + &mut pool, + first, + frame_id, + (20.0, 20.0, 80.0, 80.0), + skia::Color::from_rgb(255, 0, 0), + ); + add_solid_rect( + &mut pool, + second, + frame_id, + (40.0, 40.0, 100.0, 100.0), + skia::Color::from_rgb(0, 0, 255), + ); + { + let frame = pool.get_mut(&frame_id).unwrap(); + frame.add_child(first); + frame.add_child(second); + } + + let svg = render(&pool, frame_id); + let first_pos = svg.find("fill=\"red\"").expect("first child"); + let second_pos = svg.find("fill=\"blue\"").expect("second child"); + assert!( + second_pos < first_pos, + "flex paints its first child on top: {svg}" + ); +} + #[test] fn exports_group_background_blur_selrect_clip() { let mut pool = ShapesPool::new(); diff --git a/render-wasm/src/render/vector.rs b/render-wasm/src/render/vector.rs index c04be04683..15a8dc0c32 100644 --- a/render-wasm/src/render/vector.rs +++ b/render-wasm/src/render/vector.rs @@ -8,6 +8,7 @@ use crate::shapes::{ use crate::state::ShapesPoolRef; use crate::uuid::Uuid; +use super::children_paint_order; use super::shape_renderer::ShapeRenderer; use super::text; use super::RenderResources; @@ -934,7 +935,7 @@ fn render_frame( } // Children (absolute coords, no frame transform). - let children: Vec = element.children_ids_iter_forward(false).copied().collect(); + let children = children_paint_order(tree, element); let child_opts = opts.for_children_of(element); for child_id in &children { render_tree_inner(shared, canvas, child_id, tree, scale, &child_opts)?; @@ -988,7 +989,7 @@ fn render_container_drop_shadows( renderer.draw_fills(element, &element.fills)?; } - let children: Vec = element.children_ids_iter_forward(false).copied().collect(); + let children = children_paint_order(tree, element); let child_opts = opts.for_children_of(element); for child_id in &children { render_tree_inner(shared, canvas, child_id, tree, scale, &child_opts)?;