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:
parent
1514faca55
commit
4e5f67676c
8 changed files with 126 additions and 16 deletions
|
@ -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))]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
25
render-wasm/src/shapes/bools.rs
Normal file
25
render-wasm/src/shapes/bools.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Add table
Reference in a new issue