diff --git a/frontend/resources/wasm-playground/js/lib.js b/frontend/resources/wasm-playground/js/lib.js
index d653398a19..4aefe6701f 100644
--- a/frontend/resources/wasm-playground/js/lib.js
+++ b/frontend/resources/wasm-playground/js/lib.js
@@ -71,11 +71,11 @@ function ptr8ToPtr32(ptr8) {
return ptr8 >>> 2;
}
-function allocBytes(size) {
+export function allocBytes(size) {
return Module._alloc_bytes(size);
}
-function getHeapU32() {
+export function getHeapU32() {
return Module.HEAPU32;
}
diff --git a/frontend/resources/wasm-playground/plus.html b/frontend/resources/wasm-playground/plus.html
new file mode 100644
index 0000000000..9ed6734bff
--- /dev/null
+++ b/frontend/resources/wasm-playground/plus.html
@@ -0,0 +1,109 @@
+
+
+
+
+ WASM + WebGL2 Canvas
+
+
+
+
+
+
+
diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs
index c505cd8517..7db114109c 100644
--- a/render-wasm/src/shapes.rs
+++ b/render-wasm/src/shapes.rs
@@ -2,6 +2,7 @@ use skia_safe::{self as skia};
use crate::render::BlendMode;
use crate::uuid::Uuid;
+use std::cell::OnceCell;
use std::collections::{HashMap, HashSet};
mod blurs;
@@ -177,6 +178,7 @@ pub struct Shape {
pub svg_attrs: HashMap,
pub shadows: Vec,
pub layout_item: Option,
+ pub extrect: OnceCell,
}
impl Shape {
@@ -202,9 +204,14 @@ impl Shape {
svg_attrs: HashMap::new(),
shadows: vec![],
layout_item: None,
+ extrect: OnceCell::new(),
}
}
+ fn invalidate_extrect(&mut self) {
+ self.extrect = OnceCell::new();
+ }
+
pub fn set_parent(&mut self, id: Uuid) {
self.parent_id = Some(id);
}
@@ -233,6 +240,7 @@ impl Shape {
}
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
+ self.invalidate_extrect();
self.selrect.set_ltrb(left, top, right, bottom);
if let Type::Text(ref mut text) = self.shape_type {
text.set_xywh(left, top, right - left, bottom - top);
@@ -423,6 +431,7 @@ impl Shape {
}
pub fn set_blur(&mut self, blur_type: u8, hidden: bool, value: f32) {
+ self.invalidate_extrect();
self.blur = Blur::new(blur_type, hidden, value);
}
@@ -456,6 +465,7 @@ impl Shape {
}
pub fn add_stroke(&mut self, s: Stroke) {
+ self.invalidate_extrect();
self.strokes.push(s)
}
@@ -466,12 +476,13 @@ impl Shape {
}
pub fn clear_strokes(&mut self) {
+ self.invalidate_extrect();
self.strokes.clear();
}
pub fn set_path_segments(&mut self, segments: Vec) {
+ self.invalidate_extrect();
let path = Path::new(segments);
-
match &mut self.shape_type {
Type::Bool(Bool { bool_type, .. }) => {
self.shape_type = Type::Bool(Bool {
@@ -549,8 +560,8 @@ impl Shape {
}
pub fn visually_insignificant(&self, scale: f32) -> bool {
- self.selrect.width() * scale < MIN_VISIBLE_SIZE
- || self.selrect.height() * scale < MIN_VISIBLE_SIZE
+ let extrect = self.extrect();
+ extrect.width() * scale < MIN_VISIBLE_SIZE || extrect.height() * scale < MIN_VISIBLE_SIZE
}
pub fn should_use_antialias(&self, scale: f32) -> bool {
@@ -585,7 +596,40 @@ impl Shape {
}
pub fn extrect(&self) -> math::Rect {
- let mut rect = self.bounds().to_rect();
+ *self.extrect.get_or_init(|| self.calculate_extrect())
+ }
+
+ pub fn calculate_extrect(&self) -> math::Rect {
+ let mut max_stroke: f32 = 0.;
+ let is_open = if let Type::Path(p) = &self.shape_type {
+ p.is_open()
+ } else {
+ false
+ };
+
+ for stroke in self.strokes.iter() {
+ let width = match stroke.render_kind(is_open) {
+ StrokeKind::Inner => 0.,
+ StrokeKind::Center => stroke.width / 2.,
+ StrokeKind::Outer => stroke.width,
+ };
+ max_stroke = max_stroke.max(width);
+ }
+ let mut rect = if let Some(path) = self.get_skia_path() {
+ path.compute_tight_bounds()
+ .with_outset((max_stroke, max_stroke))
+ } else {
+ let mut bounds_rect = self.bounds().to_rect();
+ let mut stroke_rect = bounds_rect;
+ stroke_rect.left -= max_stroke;
+ stroke_rect.right += max_stroke;
+ stroke_rect.top -= max_stroke;
+ stroke_rect.bottom += max_stroke;
+
+ bounds_rect.join(stroke_rect);
+ bounds_rect
+ };
+
for shadow in self.shadows.iter() {
let (x, y) = shadow.offset;
let mut shadow_rect = rect;
@@ -601,6 +645,7 @@ impl Shape {
rect.join(shadow_rect);
}
+
if self.blur.blur_type != blurs::BlurType::None {
rect.left -= self.blur.value;
rect.top -= self.blur.value;
@@ -666,10 +711,12 @@ impl Shape {
}
pub fn add_shadow(&mut self, shadow: Shadow) {
+ self.invalidate_extrect();
self.shadows.push(shadow);
}
pub fn clear_shadows(&mut self) {
+ self.invalidate_extrect();
self.shadows.clear();
}
@@ -751,6 +798,7 @@ impl Shape {
}
pub fn apply_transform(&mut self, transform: &Matrix) {
+ self.invalidate_extrect();
self.transform_selrect(transform);
if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type {
if let Some(path) = shape_type.path_mut() {