From 731c21f0827272bb4d7d4ac4106e23cf8dfe4606 Mon Sep 17 00:00:00 2001 From: Elena Torro Date: Mon, 17 Mar 2025 16:55:17 +0100 Subject: [PATCH] :bug: Fix drop shadows viewport clipping --- render-wasm/src/render.rs | 72 +++++++++++++++++++----------- render-wasm/src/render/fills.rs | 45 +++++++------------ render-wasm/src/render/shadows.rs | 47 ++++++++++++++++--- render-wasm/src/render/surfaces.rs | 29 +++++++++++- render-wasm/src/shapes.rs | 15 +++++++ render-wasm/src/shapes/shadows.rs | 29 ++++++++++++ 6 files changed, 174 insertions(+), 63 deletions(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index eabaa1491..de2974610 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -187,6 +187,9 @@ impl RenderState { pub fn reset_canvas(&mut self) { self.surfaces.canvas(SurfaceId::Fills).restore_to_count(1); + self.surfaces + .canvas(SurfaceId::DropShadows) + .restore_to_count(1); self.surfaces.canvas(SurfaceId::Strokes).restore_to_count(1); self.surfaces.canvas(SurfaceId::Current).restore_to_count(1); @@ -195,6 +198,7 @@ impl RenderState { SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::Current, + SurfaceId::DropShadows, SurfaceId::Shadow, SurfaceId::Overlay, ], @@ -220,6 +224,16 @@ impl RenderState { pub fn apply_drawing_to_render_canvas(&mut self, shape: &Shape) { self.surfaces .flush_and_submit(&mut self.gpu_state, SurfaceId::Fills); + + self.surfaces + .flush_and_submit(&mut self.gpu_state, SurfaceId::DropShadows); + + self.surfaces.draw_into( + SurfaceId::DropShadows, + SurfaceId::Current, + Some(&skia::Paint::default()), + ); + self.surfaces.draw_into( SurfaceId::Fills, SurfaceId::Current, @@ -263,6 +277,7 @@ impl RenderState { self.surfaces.apply_mut( &[ SurfaceId::Shadow, + SurfaceId::DropShadows, SurfaceId::Overlay, SurfaceId::Fills, SurfaceId::Strokes, @@ -285,17 +300,19 @@ impl RenderState { modifiers: Option<&Matrix>, clip_bounds: Option<(Rect, Option, Matrix)>, ) { - let surface_ids = &[SurfaceId::Fills, SurfaceId::Strokes]; + let surface_ids = &[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows]; self.surfaces.apply_mut(surface_ids, |s| { s.canvas().save(); }); // set clipping if let Some((bounds, corners, transform)) = clip_bounds { - self.surfaces - .apply_mut(&[SurfaceId::Fills, SurfaceId::Strokes], |s| { + self.surfaces.apply_mut( + &[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows], + |s| { s.canvas().concat(&transform); - }); + }, + ); if let Some(corners) = corners { let rrect = RRect::new_rect_radii(bounds, &corners); @@ -362,10 +379,12 @@ impl RenderState { text::render(self, text_content); } _ => { - self.surfaces - .apply_mut(&[SurfaceId::Fills, SurfaceId::Strokes], |s| { + self.surfaces.apply_mut( + &[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows], + |s| { s.canvas().concat(&matrix); - }); + }, + ); for fill in shape.fills().rev() { fills::render(self, &shape, fill); @@ -384,21 +403,17 @@ impl RenderState { ); } - for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) { - shadows::render_drop_shadow( - self, - shadow, - self.viewbox.zoom * self.options.dpr(), - ); - } + shadows::render_drop_shadows(self, &shape); } }; self.apply_drawing_to_render_canvas(&shape); - self.surfaces - .apply_mut(&[SurfaceId::Fills, SurfaceId::Strokes], |s| { + self.surfaces.apply_mut( + &[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows], + |s| { s.canvas().restore(); - }); + }, + ); } pub fn start_render_loop( @@ -407,22 +422,23 @@ impl RenderState { modifiers: &HashMap, timestamp: i32, ) -> Result<(), String> { - let surface_ids = &[SurfaceId::Fills, SurfaceId::Strokes]; - if self.render_in_progress { if let Some(frame_id) = self.render_request_id { self.cancel_animation_frame(frame_id); } } self.reset_canvas(); - self.surfaces.apply_mut(surface_ids, |s| { - s.canvas().scale(( - self.viewbox.zoom * self.options.dpr(), - self.viewbox.zoom * self.options.dpr(), - )); - s.canvas() - .translate((self.viewbox.pan_x, self.viewbox.pan_y)); - }); + self.surfaces.apply_mut( + &[SurfaceId::Fills, SurfaceId::Strokes, SurfaceId::DropShadows], + |s| { + s.canvas().scale(( + self.viewbox.zoom * self.options.dpr(), + self.viewbox.zoom * self.options.dpr(), + )); + s.canvas() + .translate((self.viewbox.pan_x, self.viewbox.pan_y)); + }, + ); self.pending_nodes = vec![NodeRenderState { id: Uuid::nil(), @@ -514,6 +530,7 @@ impl RenderState { self.surfaces.canvas(SurfaceId::Target).save(); self.surfaces.canvas(SurfaceId::Fills).save(); self.surfaces.canvas(SurfaceId::Strokes).save(); + self.surfaces.canvas(SurfaceId::DropShadows).save(); let navigate_zoom = self.viewbox.zoom / cached.viewbox.zoom; let navigate_x = cached.viewbox.zoom * (self.viewbox.pan_x - cached.viewbox.pan_x); @@ -536,6 +553,7 @@ impl RenderState { self.surfaces.canvas(SurfaceId::Target).restore(); self.surfaces.canvas(SurfaceId::Fills).restore(); self.surfaces.canvas(SurfaceId::Strokes).restore(); + self.surfaces.canvas(SurfaceId::DropShadows).restore(); self.flush(); diff --git a/render-wasm/src/render/fills.rs b/render-wasm/src/render/fills.rs index 6c842ff5b..1efdc8fd8 100644 --- a/render-wasm/src/render/fills.rs +++ b/render-wasm/src/render/fills.rs @@ -1,14 +1,14 @@ -use skia_safe::{self as skia, RRect}; +use skia_safe::{self as skia, Paint, RRect}; use super::{RenderState, SurfaceId}; use crate::math::Rect as MathRect; use crate::shapes::{Fill, Frame, ImageFill, Rect, Shape, Type}; -fn draw_image_fill_in_container( +fn draw_image_fill( render_state: &mut RenderState, shape: &Shape, - fill: &Fill, image_fill: &ImageFill, + paint: &Paint, ) { let image = render_state.images.get(&image_fill.id()); if image.is_none() { @@ -19,7 +19,6 @@ fn draw_image_fill_in_container( let canvas = render_state.surfaces.canvas(SurfaceId::Fills); let container = &shape.selrect; let path_transform = shape.to_path_transform(); - let paint = fill.to_paint(container); let width = size.0 as f32; let height = size.1 as f32; @@ -110,39 +109,29 @@ fn draw_image_fill_in_container( * This SHOULD be the only public function in this module. */ pub fn render(render_state: &mut RenderState, shape: &Shape, fill: &Fill) { - let canvas = render_state.surfaces.canvas(SurfaceId::Fills); - let selrect = shape.selrect; - let path_transform = shape.to_path_transform(); + let paint = &fill.to_paint(&shape.selrect); match (fill, &shape.shape_type) { (Fill::Image(image_fill), _) => { - draw_image_fill_in_container(render_state, shape, fill, image_fill); + draw_image_fill(render_state, shape, image_fill, paint); } (_, Type::Rect(_) | Type::Frame(_)) => { - if let Some(corners) = shape.shape_type.corners() { - let rrect = RRect::new_rect_radii(selrect, &corners); - canvas.draw_rrect(rrect, &fill.to_paint(&selrect)); - } else { - canvas.draw_rect(selrect, &fill.to_paint(&selrect)); - } + render_state + .surfaces + .draw_rect_to(SurfaceId::Fills, shape, paint); } (_, Type::Circle) => { - canvas.draw_oval(selrect, &fill.to_paint(&selrect)); + render_state + .surfaces + .draw_circle_to(SurfaceId::Fills, shape, paint); } (_, Type::Path(_)) | (_, Type::Bool(_)) => { - if let Some(path) = &shape.shape_type.path() { - let svg_attrs = &shape.svg_attrs; - let mut skia_path = &mut path.to_skia_path(); - - if let Some(path_transform) = path_transform { - skia_path = skia_path.transform(&path_transform); - if let Some("evenodd") = svg_attrs.get("fill-rule").map(String::as_str) { - skia_path.set_fill_type(skia::PathFillType::EvenOdd); - } - canvas.draw_path(&skia_path, &fill.to_paint(&selrect)); - } - } + render_state + .surfaces + .draw_path_to(SurfaceId::Fills, shape, paint); + } + (_, _) => { + unreachable!("This shape should not have fills") } - (_, _) => unreachable!("This shape should not have fills"), } } diff --git a/render-wasm/src/render/shadows.rs b/render-wasm/src/render/shadows.rs index b7d5f7eb2..17fca69d2 100644 --- a/render-wasm/src/render/shadows.rs +++ b/render-wasm/src/render/shadows.rs @@ -1,17 +1,50 @@ use skia_safe::{self as skia}; use super::{RenderState, SurfaceId}; -use crate::shapes::Shadow; +use crate::shapes::{Shadow, Shape, Type}; -pub fn render_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) { - let shadow_paint = shadow.to_paint(scale); - render_state - .surfaces - .draw_into(SurfaceId::Fills, SurfaceId::Shadow, Some(&shadow_paint)); +pub fn render_drop_shadows(render_state: &mut RenderState, shape: &Shape) { + if shape.fills().len() > 0 { + for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) { + render_fill_drop_shadow(render_state, &shape, &shadow); + } + } else { + let scale = render_state.viewbox.zoom * render_state.options.dpr(); + for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) { + render_stroke_drop_shadow(render_state, &shadow, scale); + } + } +} + +fn render_fill_drop_shadow(render_state: &mut RenderState, shape: &Shape, shadow: &Shadow) { + let paint = &shadow.get_drop_shadow_paint(); + + match &shape.shape_type { + Type::Rect(_) | Type::Frame(_) => { + render_state + .surfaces + .draw_rect_to(SurfaceId::DropShadows, shape, paint); + } + Type::Circle => { + render_state + .surfaces + .draw_circle_to(SurfaceId::DropShadows, shape, paint); + } + Type::Path(_) | Type::Bool(_) => { + render_state + .surfaces + .draw_path_to(SurfaceId::DropShadows, shape, paint); + } + _ => {} + } +} + +fn render_stroke_drop_shadow(render_state: &mut RenderState, shadow: &Shadow, scale: f32) { + let shadow_paint = &shadow.to_paint(scale); render_state .surfaces - .draw_into(SurfaceId::Strokes, SurfaceId::Shadow, Some(&shadow_paint)); + .draw_into(SurfaceId::Strokes, SurfaceId::Shadow, Some(shadow_paint)); render_state.surfaces.draw_into( SurfaceId::Shadow, diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 1e7c9e0e2..8ff0db278 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -1,5 +1,6 @@ use super::gpu_state::GpuState; -use skia_safe as skia; +use crate::shapes::Shape; +use skia_safe::{self as skia, Paint, RRect}; #[derive(Debug, PartialEq, Clone, Copy)] pub enum SurfaceId { @@ -8,6 +9,7 @@ pub enum SurfaceId { Fills, Strokes, Shadow, + DropShadows, Overlay, Debug, } @@ -23,6 +25,8 @@ pub struct Surfaces { shape_strokes: skia::Surface, // used for rendering shadows shadow: skia::Surface, + // used for new shadow rendering + drop_shadows: skia::Surface, // for drawing the things that are over shadows. overlay: skia::Surface, // for drawing debug info. @@ -40,6 +44,7 @@ impl Surfaces { let mut target = gpu_state.create_target_surface(width, height); let current = target.new_surface_with_dimensions((width, height)).unwrap(); let shadow = target.new_surface_with_dimensions((width, height)).unwrap(); + let drop_shadows = target.new_surface_with_dimensions((width, height)).unwrap(); let overlay = target.new_surface_with_dimensions((width, height)).unwrap(); let shape_fills = target.new_surface_with_dimensions((width, height)).unwrap(); let shape_strokes = target.new_surface_with_dimensions((width, height)).unwrap(); @@ -49,6 +54,7 @@ impl Surfaces { target, current, shadow, + drop_shadows, overlay, shape_fills, shape_strokes, @@ -94,6 +100,7 @@ impl Surfaces { SurfaceId::Target => &mut self.target, SurfaceId::Current => &mut self.current, SurfaceId::Shadow => &mut self.shadow, + SurfaceId::DropShadows => &mut self.drop_shadows, SurfaceId::Overlay => &mut self.overlay, SurfaceId::Fills => &mut self.shape_fills, SurfaceId::Strokes => &mut self.shape_strokes, @@ -107,7 +114,27 @@ impl Surfaces { self.current = self.target.new_surface_with_dimensions(dim).unwrap(); self.overlay = self.target.new_surface_with_dimensions(dim).unwrap(); self.shadow = self.target.new_surface_with_dimensions(dim).unwrap(); + self.drop_shadows = self.target.new_surface_with_dimensions(dim).unwrap(); self.shape_fills = self.target.new_surface_with_dimensions(dim).unwrap(); self.debug = self.target.new_surface_with_dimensions(dim).unwrap(); } + + pub fn draw_rect_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) { + if let Some(corners) = shape.shape_type.corners() { + let rrect = RRect::new_rect_radii(shape.selrect, &corners); + self.canvas(id).draw_rrect(rrect, paint); + } else { + self.canvas(id).draw_rect(shape.selrect, paint); + } + } + + pub fn draw_circle_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) { + self.canvas(id).draw_oval(shape.selrect, paint); + } + + pub fn draw_path_to(&mut self, id: SurfaceId, shape: &Shape, paint: &Paint) { + if let Some(path) = shape.get_skia_path() { + self.canvas(id).draw_path(&path, paint); + } + } } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index cc7494500..ec58b4f0e 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -646,6 +646,21 @@ impl Shape { } } + pub fn get_skia_path(&self) -> Option { + if let Some(path) = self.shape_type.path() { + let mut skia_path = path.to_skia_path(); + if let Some(path_transform) = self.to_path_transform() { + skia_path.transform(&path_transform); + } + if let Some("evenodd") = self.svg_attrs.get("fill-rule").map(String::as_str) { + skia_path.set_fill_type(skia::PathFillType::EvenOdd); + } + Some(skia_path) + } else { + None + } + } + fn transform_selrect(&mut self, transform: &Matrix) { let mut center = self.selrect.center(); center = transform.map_point(center); diff --git a/render-wasm/src/shapes/shadows.rs b/render-wasm/src/shapes/shadows.rs index acf51fcd4..755d8e063 100644 --- a/render-wasm/src/shapes/shadows.rs +++ b/render-wasm/src/shapes/shadows.rs @@ -123,4 +123,33 @@ impl Shadow { filter } + + // New methods for DropShadow + pub fn get_drop_shadow_paint(&self) -> skia::Paint { + let mut paint = skia::Paint::default(); + + let image_filter = self.get_drop_shadow_filter(); + + paint.set_image_filter(image_filter); + paint.set_anti_alias(true); + + paint + } + + fn get_drop_shadow_filter(&self) -> Option { + let mut filter = image_filters::drop_shadow_only( + (self.offset.0, self.offset.1), + (self.blur, self.blur), + self.color, + None, + None, + None, + ); + + if self.spread > 0. { + filter = image_filters::dilate((self.spread, self.spread), filter, None); + } + + filter + } }