mirror of
https://github.com/penpot/penpot.git
synced 2025-06-18 07:11:39 +02:00
🐛 Fix render of paths with empty selrects
This commit is contained in:
parent
179a5654e7
commit
327db5a1a3
3 changed files with 163 additions and 6 deletions
|
@ -71,11 +71,11 @@ function ptr8ToPtr32(ptr8) {
|
||||||
return ptr8 >>> 2;
|
return ptr8 >>> 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function allocBytes(size) {
|
export function allocBytes(size) {
|
||||||
return Module._alloc_bytes(size);
|
return Module._alloc_bytes(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHeapU32() {
|
export function getHeapU32() {
|
||||||
return Module.HEAPU32;
|
return Module.HEAPU32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
109
frontend/resources/wasm-playground/plus.html
Normal file
109
frontend/resources/wasm-playground/plus.html
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>WASM + WebGL2 Canvas</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: #111;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
<script type="module">
|
||||||
|
import initWasmModule from '/js/render_wasm.js';
|
||||||
|
import {
|
||||||
|
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
|
||||||
|
getRandomFloat, useShape, setShapeChildren, setupInteraction, set_parent, allocBytes,
|
||||||
|
addShapeSolidStroleFill, getHeapU32
|
||||||
|
} from './js/lib.js';
|
||||||
|
|
||||||
|
const canvas = document.getElementById("canvas");
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
|
||||||
|
const params = new URLSearchParams(document.location.search);
|
||||||
|
const shapes = params.get("shapes") || 1000;
|
||||||
|
|
||||||
|
function draw_line(Module, x1, y1, x2, y2) {
|
||||||
|
const len = 2;
|
||||||
|
const ptr = allocBytes(len * 28);
|
||||||
|
const heap = getHeapU32();
|
||||||
|
const dv = new DataView(heap.buffer);
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
// MOVE to first point
|
||||||
|
dv.setUint16(ptr + offset + 0, 1, true); // MOVE
|
||||||
|
dv.setFloat32(ptr + offset + 20, x1, true);
|
||||||
|
dv.setFloat32(ptr + offset + 24, y1, true);
|
||||||
|
offset += 28;
|
||||||
|
|
||||||
|
// LINE
|
||||||
|
dv.setUint16(ptr + offset + 0, 2, true); // LINE
|
||||||
|
dv.setFloat32(ptr + offset + 20, x2, true);
|
||||||
|
dv.setFloat32(ptr + offset + 24, y2, true);
|
||||||
|
offset += 28;
|
||||||
|
|
||||||
|
Module._set_shape_path_content();
|
||||||
|
Module._set_shape_selrect(x1, y1, x2, y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
initWasmModule().then(Module => {
|
||||||
|
init(Module);
|
||||||
|
assignCanvas(canvas);
|
||||||
|
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
|
||||||
|
Module._set_view(1, 0, 0);
|
||||||
|
Module._init_shapes_pool(shapes + 1);
|
||||||
|
setupInteraction(canvas);
|
||||||
|
|
||||||
|
const children = [];
|
||||||
|
for (let i = 0; i < shapes; i++) {
|
||||||
|
const uuid = crypto.randomUUID();
|
||||||
|
children.push(uuid);
|
||||||
|
|
||||||
|
useShape(uuid);
|
||||||
|
Module._set_parent(0, 0, 0, 0);
|
||||||
|
Module._set_shape_type(4);
|
||||||
|
const x1 = getRandomInt(0, canvas.width);
|
||||||
|
const y1 = getRandomInt(0, canvas.height);
|
||||||
|
const width = getRandomInt(20, 200);
|
||||||
|
if (Math.random() < 0.5) {
|
||||||
|
draw_line(Module, x1, y1, x1 + width, y1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
draw_line(Module, x1, y1, x1, y1 + width);
|
||||||
|
}
|
||||||
|
|
||||||
|
const color = getRandomColor();
|
||||||
|
Module._add_shape_center_stroke(10, 0, 0, 0);
|
||||||
|
const argb2 = hexToU32ARGB(color, getRandomFloat(0.1, 1.0));
|
||||||
|
addShapeSolidStroleFill(argb2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
useShape("00000000-0000-0000-0000-000000000000");
|
||||||
|
setShapeChildren(children);
|
||||||
|
|
||||||
|
performance.mark('render:begin');
|
||||||
|
Module._render(Date.now());
|
||||||
|
performance.mark('render:end');
|
||||||
|
const { duration } = performance.measure('render', 'render:begin', 'render:end');
|
||||||
|
// alert(`render time: ${duration.toFixed(2)}ms`);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -2,6 +2,7 @@ use skia_safe::{self as skia};
|
||||||
|
|
||||||
use crate::render::BlendMode;
|
use crate::render::BlendMode;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
use std::cell::OnceCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
mod blurs;
|
mod blurs;
|
||||||
|
@ -177,6 +178,7 @@ pub struct Shape {
|
||||||
pub svg_attrs: HashMap<String, String>,
|
pub svg_attrs: HashMap<String, String>,
|
||||||
pub shadows: Vec<Shadow>,
|
pub shadows: Vec<Shadow>,
|
||||||
pub layout_item: Option<LayoutItem>,
|
pub layout_item: Option<LayoutItem>,
|
||||||
|
pub extrect: OnceCell<math::Rect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Shape {
|
impl Shape {
|
||||||
|
@ -202,9 +204,14 @@ impl Shape {
|
||||||
svg_attrs: HashMap::new(),
|
svg_attrs: HashMap::new(),
|
||||||
shadows: vec![],
|
shadows: vec![],
|
||||||
layout_item: None,
|
layout_item: None,
|
||||||
|
extrect: OnceCell::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn invalidate_extrect(&mut self) {
|
||||||
|
self.extrect = OnceCell::new();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_parent(&mut self, id: Uuid) {
|
pub fn set_parent(&mut self, id: Uuid) {
|
||||||
self.parent_id = Some(id);
|
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) {
|
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);
|
self.selrect.set_ltrb(left, top, right, bottom);
|
||||||
if let Type::Text(ref mut text) = self.shape_type {
|
if let Type::Text(ref mut text) = self.shape_type {
|
||||||
text.set_xywh(left, top, right - left, bottom - top);
|
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) {
|
pub fn set_blur(&mut self, blur_type: u8, hidden: bool, value: f32) {
|
||||||
|
self.invalidate_extrect();
|
||||||
self.blur = Blur::new(blur_type, hidden, value);
|
self.blur = Blur::new(blur_type, hidden, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,6 +465,7 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_stroke(&mut self, s: Stroke) {
|
pub fn add_stroke(&mut self, s: Stroke) {
|
||||||
|
self.invalidate_extrect();
|
||||||
self.strokes.push(s)
|
self.strokes.push(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,12 +476,13 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_strokes(&mut self) {
|
pub fn clear_strokes(&mut self) {
|
||||||
|
self.invalidate_extrect();
|
||||||
self.strokes.clear();
|
self.strokes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_path_segments(&mut self, segments: Vec<Segment>) {
|
pub fn set_path_segments(&mut self, segments: Vec<Segment>) {
|
||||||
|
self.invalidate_extrect();
|
||||||
let path = Path::new(segments);
|
let path = Path::new(segments);
|
||||||
|
|
||||||
match &mut self.shape_type {
|
match &mut self.shape_type {
|
||||||
Type::Bool(Bool { bool_type, .. }) => {
|
Type::Bool(Bool { bool_type, .. }) => {
|
||||||
self.shape_type = Type::Bool(Bool {
|
self.shape_type = Type::Bool(Bool {
|
||||||
|
@ -549,8 +560,8 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visually_insignificant(&self, scale: f32) -> bool {
|
pub fn visually_insignificant(&self, scale: f32) -> bool {
|
||||||
self.selrect.width() * scale < MIN_VISIBLE_SIZE
|
let extrect = self.extrect();
|
||||||
|| self.selrect.height() * scale < MIN_VISIBLE_SIZE
|
extrect.width() * scale < MIN_VISIBLE_SIZE || extrect.height() * scale < MIN_VISIBLE_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_use_antialias(&self, scale: f32) -> bool {
|
pub fn should_use_antialias(&self, scale: f32) -> bool {
|
||||||
|
@ -585,7 +596,40 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extrect(&self) -> math::Rect {
|
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() {
|
for shadow in self.shadows.iter() {
|
||||||
let (x, y) = shadow.offset;
|
let (x, y) = shadow.offset;
|
||||||
let mut shadow_rect = rect;
|
let mut shadow_rect = rect;
|
||||||
|
@ -601,6 +645,7 @@ impl Shape {
|
||||||
|
|
||||||
rect.join(shadow_rect);
|
rect.join(shadow_rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.blur.blur_type != blurs::BlurType::None {
|
if self.blur.blur_type != blurs::BlurType::None {
|
||||||
rect.left -= self.blur.value;
|
rect.left -= self.blur.value;
|
||||||
rect.top -= self.blur.value;
|
rect.top -= self.blur.value;
|
||||||
|
@ -666,10 +711,12 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_shadow(&mut self, shadow: Shadow) {
|
pub fn add_shadow(&mut self, shadow: Shadow) {
|
||||||
|
self.invalidate_extrect();
|
||||||
self.shadows.push(shadow);
|
self.shadows.push(shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_shadows(&mut self) {
|
pub fn clear_shadows(&mut self) {
|
||||||
|
self.invalidate_extrect();
|
||||||
self.shadows.clear();
|
self.shadows.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -751,6 +798,7 @@ impl Shape {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_transform(&mut self, transform: &Matrix) {
|
pub fn apply_transform(&mut self, transform: &Matrix) {
|
||||||
|
self.invalidate_extrect();
|
||||||
self.transform_selrect(transform);
|
self.transform_selrect(transform);
|
||||||
if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type {
|
if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type {
|
||||||
if let Some(path) = shape_type.path_mut() {
|
if let Some(path) = shape_type.path_mut() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue