0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-21 22:36:12 -05:00

🎉 Implement boolean operations (wasm)

This commit is contained in:
Belén Albeza 2025-01-09 12:21:05 +01:00
parent 1514faca55
commit 4e5f67676c
8 changed files with 126 additions and 16 deletions

View file

@ -93,6 +93,9 @@
(= type :path) (= type :path)
(h/call internal-module "_set_shape_kind_path") (h/call internal-module "_set_shape_kind_path")
(= type :bool)
(h/call internal-module "_set_shape_kind_bool")
:else :else
(h/call internal-module "_set_shape_kind_rect"))) (h/call internal-module "_set_shape_kind_rect")))
@ -343,6 +346,24 @@
[hidden] [hidden]
(h/call internal-module "_set_shape_hidden" hidden)) (h/call internal-module "_set_shape_hidden" hidden))
(defn- translate-bool-type
[bool-type]
(case bool-type
:union 0
:difference 1
:intersection 2
:exclusion 3
0))
(defn set-shape-bool-type
[bool-type]
(h/call internal-module "_set_shape_bool_type" (translate-bool-type bool-type)))
(defn set-shape-bool-content
[content]
(set-shape-path-content content))
(def debounce-render-without-cache (fns/debounce render-without-cache 100)) (def debounce-render-without-cache (fns/debounce render-without-cache 100))
(defn set-view (defn set-view
@ -373,7 +394,8 @@
blend-mode (dm/get-prop shape :blend-mode) blend-mode (dm/get-prop shape :blend-mode)
opacity (dm/get-prop shape :opacity) opacity (dm/get-prop shape :opacity)
hidden (dm/get-prop shape :hidden) hidden (dm/get-prop shape :hidden)
content (dm/get-prop shape :content)] content (dm/get-prop shape :content)
bool-content (dm/get-prop shape :bool-content)]
(use-shape id) (use-shape id)
(set-shape-type type) (set-shape-type type)
@ -386,6 +408,7 @@
(set-shape-opacity opacity) (set-shape-opacity opacity)
(set-shape-hidden hidden) (set-shape-hidden hidden)
(when (and (some? content) (= type :path)) (set-shape-path-content content)) (when (and (some? content) (= type :path)) (set-shape-path-content content))
(when (some? bool-content) (set-shape-bool-content bool-content))
(let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))] (let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))]
(recur (inc index) (into pending pending')))) (recur (inc index) (into pending pending'))))
pending))] pending))]

View file

@ -112,6 +112,8 @@
(api/use-shape (:id self)) (api/use-shape (:id self))
(case k (case k
:type (api/set-shape-type v) :type (api/set-shape-type v)
:bool-type (api/set-shape-bool-type v)
:bool-content (api/set-shape-bool-content v)
:selrect (api/set-shape-selrect v) :selrect (api/set-shape-selrect v)
:show-content (api/set-shape-clip-content (not v)) :show-content (api/set-shape-clip-content (not v))
:rotation (api/set-shape-rotation v) :rotation (api/set-shape-rotation v)

View file

@ -40,7 +40,7 @@ Gradient stops are serialized in a `Uint8Array`, each stop taking **5 bytes**.
**Stop offset** is the offset, being integer values ranging from `0` to `100` (both inclusive). **Stop offset** is the offset, being integer values ranging from `0` to `100` (both inclusive).
## StrokeCap ## Stroke Caps
Stroke caps are serialized as `u8`: Stroke caps are serialized as `u8`:
@ -53,9 +53,9 @@ Stroke caps are serialized as `u8`:
| 5 | Diamond | | 5 | Diamond |
| 6 | Round | | 6 | Round |
| 7 | Square | | 7 | Square |
| _ | None | | \_ | None |
## StrokeStyle ## Stroke Sytles
Stroke styles are serialized as `u8`: Stroke styles are serialized as `u8`:
@ -64,4 +64,16 @@ Stroke styles are serialized as `u8`:
| 1 | Dotted | | 1 | Dotted |
| 2 | Dashed | | 2 | Dashed |
| 3 | Mixed | | 3 | Mixed |
| _ | Solid | | \_ | Solid |
## Bool Operations
Bool operations (`bool-type`) are serialized as `u8`:
| Value | Field |
| ----- | ------------ |
| 0 | Union |
| 1 | Difference |
| 2 | Intersection |
| 3 | Exclusion |
| \_ | Union |

View file

@ -7,8 +7,7 @@ mod state;
mod utils; mod utils;
mod view; mod view;
use crate::shapes::Kind; use crate::shapes::{BoolType, Kind, Path};
use crate::shapes::Path;
use skia_safe as skia; use skia_safe as skia;
use crate::state::State; use crate::state::State;
@ -134,8 +133,26 @@ pub unsafe extern "C" fn set_shape_kind_rect() {
pub unsafe extern "C" fn set_shape_kind_path() { pub unsafe extern "C" fn set_shape_kind_path() {
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
if let Some(shape) = state.current_shape() { if let Some(shape) = state.current_shape() {
let p = Path::try_from(Vec::new()).unwrap(); shape.set_kind(Kind::Path(Path::default()));
shape.set_kind(Kind::Path(p)); }
}
#[no_mangle]
pub unsafe extern "C" fn set_shape_kind_bool() {
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
if let Some(shape) = state.current_shape() {
match shape.kind() {
Kind::Bool(_, _) => {}
_ => shape.set_kind(Kind::Bool(BoolType::default(), Path::default())),
}
}
}
#[no_mangle]
pub unsafe extern "C" fn set_shape_bool_type(raw_bool_type: u8) {
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
if let Some(shape) = state.current_shape() {
shape.set_bool_type(BoolType::from(raw_bool_type))
} }
} }

View file

@ -4,6 +4,7 @@ use uuid::Uuid;
use crate::render::{BlendMode, Renderable}; use crate::render::{BlendMode, Renderable};
mod bools;
mod fills; mod fills;
mod images; mod images;
mod matrix; mod matrix;
@ -11,6 +12,7 @@ mod paths;
mod renderable; mod renderable;
mod strokes; mod strokes;
pub use bools::*;
pub use fills::*; pub use fills::*;
pub use images::*; pub use images::*;
use matrix::*; use matrix::*;
@ -22,6 +24,7 @@ pub enum Kind {
Rect(math::Rect), Rect(math::Rect),
Circle(math::Rect), Circle(math::Rect),
Path(Path), Path(Path),
Bool(BoolType, Path),
} }
pub type Color = skia::Color; pub type Color = skia::Color;
@ -61,6 +64,10 @@ impl Shape {
} }
} }
pub fn kind(&self) -> Kind {
self.kind.clone()
}
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.selrect.set_ltrb(left, top, right, bottom); self.selrect.set_ltrb(left, top, right, bottom);
match self.kind { match self.kind {
@ -169,7 +176,12 @@ impl Shape {
pub fn set_path_segments(&mut self, buffer: Vec<RawPathData>) -> Result<(), String> { pub fn set_path_segments(&mut self, buffer: Vec<RawPathData>) -> Result<(), String> {
let p = Path::try_from(buffer)?; let p = Path::try_from(buffer)?;
self.kind = Kind::Path(p); let kind = match &self.kind {
Kind::Bool(bool_type, _) => Kind::Bool(*bool_type, p),
_ => Kind::Path(p),
};
self.kind = kind;
Ok(()) Ok(())
} }
@ -177,9 +189,18 @@ impl Shape {
self.blend_mode = mode; self.blend_mode = mode;
} }
pub fn set_bool_type(&mut self, bool_type: BoolType) {
let kind = match &self.kind {
Kind::Bool(_, path) => Kind::Bool(bool_type, path.clone()),
_ => Kind::Bool(bool_type, Path::default()),
};
self.kind = kind;
}
fn to_path_transform(&self) -> Option<skia::Matrix> { fn to_path_transform(&self) -> Option<skia::Matrix> {
match self.kind { match self.kind {
Kind::Path(_) => { Kind::Path(_) | Kind::Bool(_, _) => {
let center = self.bounds().center(); let center = self.bounds().center();
let mut matrix = skia::Matrix::new_identity(); let mut matrix = skia::Matrix::new_identity();
matrix.pre_translate(center); matrix.pre_translate(center);

View file

@ -0,0 +1,25 @@
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BoolType {
Union,
Difference,
Intersection,
Exclusion,
}
impl From<u8> for BoolType {
fn from(value: u8) -> Self {
match value {
0 => Self::Union,
1 => Self::Difference,
2 => Self::Intersection,
3 => Self::Exclusion,
_ => Self::default(),
}
}
}
impl Default for BoolType {
fn default() -> Self {
Self::Union
}
}

View file

@ -88,6 +88,12 @@ fn starts_and_ends_at_same_point(path: &skia::Path) -> bool {
start_point == end_point start_point == end_point
} }
impl Default for Path {
fn default() -> Self {
Path::try_from(Vec::new()).unwrap()
}
}
impl TryFrom<Vec<RawPathData>> for Path { impl TryFrom<Vec<RawPathData>> for Path {
type Error = String; type Error = String;

View file

@ -64,7 +64,11 @@ impl Renderable for Shape {
} }
fn children_ids(&self) -> Vec<Uuid> { fn children_ids(&self) -> Vec<Uuid> {
self.children.clone() if let Kind::Bool(_, _) = self.kind {
vec![]
} else {
self.children.clone()
}
} }
} }
@ -97,7 +101,7 @@ fn render_fill(
(_, Kind::Circle(rect)) => { (_, Kind::Circle(rect)) => {
surface.canvas().draw_oval(rect, &fill.to_paint(&selrect)); surface.canvas().draw_oval(rect, &fill.to_paint(&selrect));
} }
(_, Kind::Path(path)) => { (_, Kind::Path(path)) | (_, Kind::Bool(_, path)) => {
surface.canvas().draw_path( surface.canvas().draw_path(
&path.to_skia_path().transform(path_transform.unwrap()), &path.to_skia_path().transform(path_transform.unwrap()),
&fill.to_paint(&selrect), &fill.to_paint(&selrect),
@ -130,7 +134,7 @@ fn render_stroke(
match kind { match kind {
Kind::Rect(rect) => draw_stroke_on_rect(surface.canvas(), stroke, rect, &selrect), Kind::Rect(rect) => draw_stroke_on_rect(surface.canvas(), stroke, rect, &selrect),
Kind::Circle(rect) => draw_stroke_on_circle(surface.canvas(), stroke, rect, &selrect), Kind::Circle(rect) => draw_stroke_on_circle(surface.canvas(), stroke, rect, &selrect),
Kind::Path(path) => { Kind::Path(path) | Kind::Bool(_, path) => {
draw_stroke_on_path(surface.canvas(), stroke, path, &selrect, path_transform); draw_stroke_on_path(surface.canvas(), stroke, path, &selrect, path_transform);
} }
} }
@ -439,7 +443,7 @@ pub fn draw_image_fill_in_container(
oval_path.add_oval(container, None); oval_path.add_oval(container, None);
canvas.clip_path(&oval_path, skia::ClipOp::Intersect, true); canvas.clip_path(&oval_path, skia::ClipOp::Intersect, true);
} }
Kind::Path(p) => { Kind::Path(p) | Kind::Bool(_, p) => {
canvas.clip_path( canvas.clip_path(
&p.to_skia_path().transform(path_transform.unwrap()), &p.to_skia_path().transform(path_transform.unwrap()),
skia::ClipOp::Intersect, skia::ClipOp::Intersect,
@ -476,7 +480,7 @@ pub fn draw_image_stroke_in_container(
match kind { match kind {
Kind::Rect(rect) => draw_stroke_on_rect(canvas, stroke, rect, &outer_rect), Kind::Rect(rect) => draw_stroke_on_rect(canvas, stroke, rect, &outer_rect),
Kind::Circle(rect) => draw_stroke_on_circle(canvas, stroke, rect, &outer_rect), Kind::Circle(rect) => draw_stroke_on_circle(canvas, stroke, rect, &outer_rect),
Kind::Path(p) => { Kind::Path(p) | Kind::Bool(_, p) => {
let mut path = p.to_skia_path(); let mut path = p.to_skia_path();
path.transform(path_transform.unwrap()); path.transform(path_transform.unwrap());
let stroke_kind = stroke.render_kind(p.is_open()); let stroke_kind = stroke.render_kind(p.is_open());