🔧 Autogenerate serialization values for wasm enums (#7296)

* 🔧 Autogenerate serialization values for wasm enums

* 🔧 Add serializer values to the wasm api

*  Avoid converting to a clojure map the serializer js object

* 🔧 Update watch script for autoserialized enums

* 🐛 Fix missing serializer values
This commit is contained in:
Belén Albeza
2025-09-16 12:29:14 +02:00
committed by GitHub
parent 01e963ae35
commit e5e11b6383
26 changed files with 592 additions and 304 deletions

View File

@@ -1072,10 +1072,35 @@
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
(->> (js/dynamicImport (str uri))
(p/mcat (fn [module]
(let [default (unchecked-get module "default")]
(let [default (unchecked-get module "default")
serializers #js{:blur-type (unchecked-get module "BlurType")
:bool-type (unchecked-get module "BoolType")
:font-style (unchecked-get module "FontStyle")
:flex-direction (unchecked-get module "FlexDirection")
:grid-direction (unchecked-get module "GridDirection")
:grow-type (unchecked-get module "GrowType")
:align-items (unchecked-get module "AlignItems")
:align-self (unchecked-get module "AlignSelf")
:align-content (unchecked-get module "AlignContent")
:justify-items (unchecked-get module "JustifyItems")
:justify-content (unchecked-get module "JustifyContent")
:justify-self (unchecked-get module "JustifySelf")
:wrap-type (unchecked-get module "WrapType")
:grid-track-type (unchecked-get module "GridTrackType")
:shadow-style (unchecked-get module "ShadowStyle")
:stroke-style (unchecked-get module "StrokeStyle")
:stroke-cap (unchecked-get module "StrokeCap")
:shape-type (unchecked-get module "Type")
:constraint-h (unchecked-get module "ConstraintH")
:constraint-v (unchecked-get module "ConstraintV")
:sizing (unchecked-get module "Sizing")
:vertical-align (unchecked-get module "VerticalAlign")
:fill-data (unchecked-get module "RawFillData")
:segment-data (unchecked-get module "RawSegmentData")}]
(set! wasm/serializers serializers)
(default))))
(p/fmap (fn [module]
(set! wasm/internal-module module)
(p/fmap (fn [default]
(set! wasm/internal-module default)
true))
(p/merr (fn [cause]
(js/console.error cause)

View File

@@ -4,9 +4,11 @@
;;
;; Copyright (c) KALEIDOS INC
(ns app.render-wasm.serializers
(ns app.render-wasm.serializers
(:require
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.render-wasm.wasm :as wasm]
[cuerdas.core :as str]))
(defn u8
@@ -56,37 +58,21 @@
(defn translate-shape-type
[type]
(case type
:frame 0
:group 1
:bool 2
:rect 3
:path 4
:text 5
:circle 6
:svg-raw 7
:image 8))
(let [values (unchecked-get wasm/serializers "shape-type")
default (unchecked-get values "rect")]
(d/nilv (unchecked-get values (d/name type)) default)))
(defn translate-stroke-style
[stroke-style]
(case stroke-style
:dotted 1
:dashed 2
:mixed 3
0))
(let [values (unchecked-get wasm/serializers "stroke-style")
default (unchecked-get values "solid")]
(d/nilv (unchecked-get values (d/name stroke-style)) default)))
(defn translate-stroke-cap
[stroke-cap]
(case stroke-cap
:line-arrow 1
:triangle-arrow 2
:square-marker 3
:circle-marker 4
:diamond-marker 5
:round 6
:square 7
0))
(let [values (unchecked-get wasm/serializers "stroke-cap")
default (unchecked-get values "none")]
(d/nilv (unchecked-get values (d/name stroke-cap)) default)))
(defn serialize-path-attrs
[svg-attrs]
@@ -120,143 +106,99 @@
(defn translate-constraint-h
[type]
(case type
:left 0
:right 1
:leftright 2
:center 3
:scale 4))
(let [values (unchecked-get wasm/serializers "constraint-h")
default 5] ;; TODO: fix code in rust so we have a proper None variant
(d/nilv (unchecked-get values (d/name type)) default)))
(defn translate-constraint-v
[type]
(case type
:top 0
:bottom 1
:topbottom 2
:center 3
:scale 4))
(let [values (unchecked-get wasm/serializers "constraint-v")
default 5] ;; TODO: fix code in rust so we have a proper None variant
(d/nilv (unchecked-get values (d/name type)) default)))
(defn translate-bool-type
[bool-type]
(case bool-type
:union 0
:difference 1
:intersection 2
:exclude 3
0))
(let [values (unchecked-get wasm/serializers "bool-type")
default (unchecked-get values "union")]
(d/nilv (unchecked-get values (d/name bool-type)) default)))
(defn translate-blur-type
[blur-type]
(case blur-type
:layer-blur 1
0))
(let [values (unchecked-get wasm/serializers "blur-type")
default (unchecked-get values "none")]
(d/nilv (unchecked-get values (d/name blur-type)) default)))
(defn translate-layout-flex-dir
[flex-dir]
(case flex-dir
:row 0
:row-reverse 1
:column 2
:column-reverse 3))
(let [values (unchecked-get wasm/serializers "flex-direction")]
(unchecked-get values (d/name flex-dir))))
(defn translate-layout-grid-dir
[flex-dir]
(case flex-dir
:row 0
:column 1))
[grid-dir]
(let [values (unchecked-get wasm/serializers "grid-direction")]
(unchecked-get values (d/name grid-dir))))
(defn translate-layout-align-items
[align-items]
(case align-items
:start 0
:end 1
:center 2
:stretch 3
0))
(let [values (unchecked-get wasm/serializers "align-items")
default (unchecked-get values "start")]
(d/nilv (unchecked-get values (d/name align-items)) default)))
(defn translate-layout-align-content
[align-content]
(case align-content
:start 0
:end 1
:center 2
:space-between 3
:space-around 4
:space-evenly 5
:stretch 6
6))
(let [values (unchecked-get wasm/serializers "align-content")
default (unchecked-get values "stretch")]
(d/nilv (unchecked-get values (d/name align-content)) default)))
(defn translate-layout-justify-items
[justify-items]
(case justify-items
:start 0
:end 1
:center 2
:stretch 3
0))
(let [values (unchecked-get wasm/serializers "justify-items")
default (unchecked-get values "start")]
(d/nilv (unchecked-get values (d/name justify-items)) default)))
(defn translate-layout-justify-content
[justify-content]
(case justify-content
:start 0
:end 1
:center 2
:space-between 3
:space-around 4
:space-evenly 5
:stretch 6
6))
(let [values (unchecked-get wasm/serializers "justify-content")
default (unchecked-get values "stretch")]
(d/nilv (unchecked-get values (d/name justify-content)) default)))
(defn translate-layout-wrap-type
[wrap-type]
(case wrap-type
:wrap 0
:nowrap 1
1))
(let [values (unchecked-get wasm/serializers "wrap-type")
default (unchecked-get values "nowrap")]
(d/nilv (unchecked-get values (d/name wrap-type)) default)))
(defn translate-grid-track-type
[type]
(case type
:percent 0
:flex 1
:auto 2
:fixed 3))
(let [values (unchecked-get wasm/serializers "grid-track-type")]
(unchecked-get values (d/name type))))
(defn translate-layout-sizing
[value]
(case value
:fill 0
:fix 1
:auto 2
1))
[sizing]
(let [values (unchecked-get wasm/serializers "sizing")
default (unchecked-get values "fix")]
(d/nilv (unchecked-get values (d/name sizing)) default)))
(defn translate-align-self
[value]
(when value
(case value
:auto 0
:start 1
:end 2
:center 3
:stretch 4)))
[align-self]
(let [values (unchecked-get wasm/serializers "align-self")]
(unchecked-get values (d/name align-self))))
(defn translate-justify-self
[value]
(when value
(case value
:auto 0
:start 1
:end 2
:center 3
:stretch 4)))
[justify-self]
(let [values (unchecked-get wasm/serializers "justify-self")]
(unchecked-get values (d/name justify-self))))
(defn translate-shadow-style
[style]
(case style
:drop-shadow 0
:inner-shadow 1
0))
(let [values (unchecked-get wasm/serializers "shadow-style")
default (unchecked-get values "drop-shadow")]
(d/nilv (unchecked-get values (d/name style)) default)))
;; TODO: Find/Create a Rust enum for this
(defn translate-structure-modifier-type
[type]
(case type
@@ -266,19 +208,17 @@
(defn translate-grow-type
[grow-type]
(case grow-type
:auto-width 1
:auto-height 2
0))
(let [values (unchecked-get wasm/serializers "grow-type")
default (unchecked-get values "fixed")]
(d/nilv (unchecked-get values (d/name grow-type)) default)))
(defn translate-vertical-align
[vertical-align]
(case vertical-align
"top" 0
"center" 1
"bottom" 2
0))
(let [values (unchecked-get wasm/serializers "vertical-align")
default (unchecked-get values "top")]
(d/nilv (unchecked-get values (d/name vertical-align)) default)))
;; TODO: Find/Create a Rust enum for this
(defn translate-text-align
[text-align]
(case text-align
@@ -288,6 +228,7 @@
"justify" 3
0))
;; TODO: Find/Create a Rust enum for this
(defn translate-text-transform
[text-transform]
(case text-transform
@@ -298,6 +239,7 @@
nil 0
0))
;; TODO: Find/Create a Rust enum for this
(defn translate-text-decoration
[text-decoration]
(case text-decoration
@@ -308,6 +250,7 @@
nil 0
0))
;; TODO: Find/Create a Rust enum for this
(defn translate-text-direction
[text-direction]
(case text-direction
@@ -318,8 +261,12 @@
(defn translate-font-style
[font-style]
(let [values (unchecked-get wasm/serializers "font-style")
default (unchecked-get values "normal")]
(case font-style
"normal" 0
"regular" 0
"italic" 1
0))
;; NOTE: normal == regular!
;; is it OK to keep those two values in our cljs model?
"normal" (unchecked-get values "normal")
"regular" (unchecked-get values "normal")
"italic" (unchecked-get values "italic")
default)))

View File

@@ -2,3 +2,4 @@
(defonce internal-frame-id nil)
(defonce internal-module #js {})
(defonce serializers #js {})

17
render-wasm/Cargo.lock generated
View File

@@ -298,6 +298,14 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "macros"
version = "0.1.0"
dependencies = [
"heck",
"syn",
]
[[package]]
name = "memchr"
version = "2.7.4"
@@ -359,9 +367,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.88"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
@@ -422,6 +430,7 @@ dependencies = [
"gl",
"glam",
"indexmap",
"macros",
"skia-safe",
"uuid",
]
@@ -548,9 +557,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.82"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,6 +6,8 @@ repository = "https://github.com/penpot/penpot"
license-file = "../LICENSE"
description = "Wasm-based canvas renderer for Penpot"
build = "build.rs"
[features]
default = []
profile = ["profile-macros", "profile-raf"]
@@ -22,12 +24,13 @@ bezier-rs = "0.4.0"
gl = "0.14.0"
glam = "0.24.2"
indexmap = "2.7.1"
macros = { path = "macros" }
skia-safe = { version = "0.87.0", default-features = false, features = [
"gl",
"svg",
"textlayout",
"binary-cache",
"webp"
"webp",
] }
uuid = { version = "1.11.0", features = ["v4", "js"] }

View File

@@ -13,7 +13,9 @@ export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-bi
cargo build $_CARGO_PARAMS
cp target/wasm32-unknown-emscripten/$_BUILD_MODE/render_wasm.js ../frontend/resources/public/js/$_BUILD_NAME.js
_SHARED_FILE=$(find target/wasm32-unknown-emscripten -name render_wasm_shared.js | head -n 1);
cat target/wasm32-unknown-emscripten/$_BUILD_MODE/render_wasm.js "$_SHARED_FILE" > ../frontend/resources/public/js/$_BUILD_NAME.js
cp target/wasm32-unknown-emscripten/$_BUILD_MODE/render_wasm.wasm ../frontend/resources/public/js/$_BUILD_NAME.wasm
sed -i "s/render_wasm.wasm/$_BUILD_NAME.wasm?version=develop/g" ../frontend/resources/public/js/$_BUILD_NAME.js;

3
render-wasm/build.rs Normal file
View File

@@ -0,0 +1,3 @@
// We need this empty script so OUT_DIR is automatically set and we can build
// the macros crate
fn main() {}

View File

@@ -25,7 +25,7 @@ Horizontal constraints are serialized as `u8`:
| ----- | --------- |
| 0 | Left |
| 1 | Right |
| 2 | LeftRight |
| 2 | Leftright |
| 3 | Center |
| 4 | Scale |
| \_ | None |
@@ -38,11 +38,22 @@ Vertical constraints are serialized as `u8`:
| ----- | --------- |
| 0 | Top |
| 1 | Bottom |
| 2 | TopBottom |
| 2 | Topbottom |
| 3 | Center |
| 4 | Scale |
| \_ | None |
## Vertical Alignment
Vertical alignment is serialized as `u8`:
| Value | Field |
| ----- | ------ |
| 0 | Top |
| 1 | Center |
| 2 | Bottom |
| \_ | Top |
## Paths
Paths are made of segments of **28 bytes** each. The layout (assuming positions in a `Uint8Array`) is the following:
@@ -127,12 +138,13 @@ Gradient stops are serialized as a sequence of `16` chunks with the following la
Stroke caps are serialized as `u8`:
| Value | Field |
| ----- | --------- |
| 1 | Line |
| 2 | Triangle |
| 3 | Rectangle |
| 4 | Circle |
| 5 | Diamond |
| ----- | ------------- |
| 0 | None |
| 1 | LineArrow |
| 2 | TriangleArrow |
| 3 | SquareMarker |
| 4 | CircleMarker |
| 5 | DiamondMarker |
| 6 | Round |
| 7 | Square |
| \_ | None |

52
render-wasm/macros/Cargo.lock generated Normal file
View File

@@ -0,0 +1,52 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "macros"
version = "0.1.0"
dependencies = [
"heck",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"

View File

@@ -0,0 +1,11 @@
[package]
name = "macros"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
heck = "0.5.0"
syn = "2.0.106"

View File

@@ -0,0 +1,103 @@
use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::sync;
use heck::{ToKebabCase, ToPascalCase};
use proc_macro::TokenStream;
type Result<T> = std::result::Result<T, String>;
#[proc_macro_derive(ToJs)]
pub fn derive_to_cljs(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
let enum_id = input.ident.to_string();
let data_enum = match input.data {
syn::Data::Enum(data_enum) => data_enum,
_ => panic!("ToCljs can only be derived for enums"),
};
let raw_variants = data_enum
.variants
.to_owned()
.into_iter()
.collect::<Vec<_>>();
let variants = parse_variants(&raw_variants).expect("Failed to parse variants");
let js_code = generate_js_for_enum(&enum_id, &mut variants.into_iter().collect::<Vec<_>>());
if let Err(e) = write_enum_to_temp_file(&js_code) {
eprintln!("Error writing enum {} to file: {}", enum_id, e);
}
TokenStream::new() // we don't need to return any generated code
}
fn parse_variants(variants: &[syn::Variant]) -> Result<HashMap<String, u32>> {
let mut res = HashMap::new();
for variant in variants {
let value_expr = variant
.discriminant
.clone()
.ok_or(format!(
"No discriminant found for variant {}",
variant.ident
))?
.1;
let discriminant = parse_discriminant_value(value_expr)?;
res.insert(variant.ident.to_string(), discriminant);
}
Ok(res)
}
fn parse_discriminant_value(value: syn::Expr) -> Result<u32> {
match value {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(int),
..
}) => Ok(int.base10_digits().parse().unwrap()),
_ => Err(format!("Invalid discriminant value")),
}
}
fn generate_js_for_enum(id: &str, variants: &mut [(String, u32)]) -> String {
variants.sort_by_key(|(_, discriminant)| *discriminant);
let output_variants: String = variants
.into_iter()
.map(|(variant, discriminant)| {
format!(r#" "{}": {},"#, variant.to_kebab_case(), discriminant)
})
.collect::<Vec<String>>()
.join("\n");
format!(
"export const {} = {{\n{}\n}};",
id.to_pascal_case(),
output_variants
)
}
static INIT: sync::Once = sync::Once::new();
fn write_enum_to_temp_file(js_code: &str) -> std::io::Result<()> {
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR environment variable is not set");
let out_path = Path::new(&out_dir).join("render_wasm_shared.js");
// clean the file the first time this function is called
INIT.call_once(|| {
fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&out_path)
.expect("Failed to open output file");
});
let mut file = fs::OpenOptions::new().append(true).open(&out_path)?;
writeln!(file, "{}\n", js_code)?;
Ok(())
}

View File

@@ -563,19 +563,19 @@ impl RenderState {
let mut nested_blur_value = 0.;
for nested_blur in self.nested_blurs.iter().flatten() {
if !nested_blur.hidden && nested_blur.blur_type == BlurType::Layer {
if !nested_blur.hidden && nested_blur.blur_type == BlurType::LayerBlur {
nested_blur_value += nested_blur.value.powf(2.);
}
}
if !shape.blur.hidden && shape.blur.blur_type == BlurType::Layer {
if !shape.blur.hidden && shape.blur.blur_type == BlurType::LayerBlur {
nested_blur_value += shape.blur.value.powf(2.);
}
if nested_blur_value > 0. {
shape
.to_mut()
.set_blur(BlurType::Layer as u8, false, nested_blur_value.sqrt());
.set_blur(BlurType::LayerBlur as u8, false, nested_blur_value.sqrt());
}
let center = shape.center();

View File

@@ -1,5 +1,7 @@
use skia_safe as skia;
// TODO: maybe move this to the wasm module?
// TODO: find a way to use the ToJS derive macro for this
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct BlendMode(skia::BlendMode);

View File

@@ -183,22 +183,22 @@ fn handle_stroke_cap(
paint.set_style(skia::PaintStyle::Fill);
match cap {
StrokeCap::None => {}
StrokeCap::Line => {
StrokeCap::LineArrow => {
// We also draw this square cap to fill the gap between the path and the arrow
draw_square_cap(canvas, paint, p1, p2, width, 0.);
paint.set_style(skia::PaintStyle::Stroke);
draw_arrow_cap(canvas, paint, p1, p2, width * 4.);
}
StrokeCap::Triangle => {
StrokeCap::TriangleArrow => {
draw_triangle_cap(canvas, paint, p1, p2, width * 4.);
}
StrokeCap::Rectangle => {
StrokeCap::SquareMarker => {
draw_square_cap(canvas, paint, p1, p2, width * 4., 0.);
}
StrokeCap::Circle => {
StrokeCap::CircleMarker => {
canvas.draw_circle((p1.x, p1.y), width * 2., paint);
}
StrokeCap::Diamond => {
StrokeCap::DiamondMarker => {
draw_square_cap(canvas, paint, p1, p2, width * 4., 45.);
}
StrokeCap::Round => {

View File

@@ -1,3 +1,4 @@
use macros::ToJs;
use skia_safe::{self as skia};
use crate::render::BlendMode;
@@ -54,19 +55,22 @@ const MIN_VISIBLE_SIZE: f32 = 2.0;
const ANTIALIAS_THRESHOLD: f32 = 15.0;
const MIN_STROKE_WIDTH: f32 = 0.001;
#[derive(Debug, Clone, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, ToJs)]
#[repr(u8)]
pub enum Type {
Frame(Frame),
Group(Group),
Bool(Bool),
Rect(Rect),
Path(Path),
Circle,
SVGRaw(SVGRaw),
Text(TextContent),
Frame(Frame) = 0,
Group(Group) = 1,
Bool(Bool) = 2,
Rect(Rect) = 3,
Path(Path) = 4,
Text(TextContent) = 5,
Circle = 6,
SVGRaw(SVGRaw) = 7,
}
impl Type {
// TODO: move this to the wasm module, use transmute
pub fn from(value: u8) -> Self {
match value {
0 => Type::Frame(Frame::default()),
@@ -141,21 +145,24 @@ impl Type {
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
#[derive(Debug, Clone, PartialEq, Copy, ToJs)]
#[repr(u8)]
pub enum ConstraintH {
Left,
Right,
LeftRight,
Center,
Scale,
Left = 0,
Right = 1,
Leftright = 2,
Center = 3,
Scale = 4,
}
impl ConstraintH {
// TODO: we should implement a proper From trait for this
// TODO: use transmute
pub fn from(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Left),
1 => Some(Self::Right),
2 => Some(Self::LeftRight),
2 => Some(Self::Leftright),
3 => Some(Self::Center),
4 => Some(Self::Scale),
_ => None,
@@ -163,14 +170,18 @@ impl ConstraintH {
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, Copy, ToJs)]
#[repr(u8)]
pub enum VerticalAlign {
Top,
Center,
Bottom,
Top = 0,
Center = 1,
Bottom = 2,
}
impl VerticalAlign {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from(value: u8) -> Self {
match value {
0 => Self::Top,
@@ -181,21 +192,25 @@ impl VerticalAlign {
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, Copy, ToJs)]
#[repr(u8)]
pub enum ConstraintV {
Top,
Bottom,
TopBottom,
Center,
Scale,
Top = 0,
Bottom = 1,
Topbottom = 2,
Center = 3,
Scale = 4,
}
impl ConstraintV {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Top),
1 => Some(Self::Bottom),
2 => Some(Self::TopBottom),
2 => Some(Self::Topbottom),
3 => Some(Self::Center),
4 => Some(Self::Scale),
_ => None,
@@ -973,7 +988,7 @@ impl Shape {
if !self.blur.hidden {
match self.blur.blur_type {
BlurType::None => None,
BlurType::Layer => skia::image_filters::blur(
BlurType::LayerBlur => skia::image_filters::blur(
(self.blur.value * scale, self.blur.value * scale),
None,
None,
@@ -990,7 +1005,7 @@ impl Shape {
if !self.blur.hidden {
match self.blur.blur_type {
BlurType::None => None,
BlurType::Layer => skia::MaskFilter::blur(
BlurType::LayerBlur => skia::MaskFilter::blur(
skia::BlurStyle::Normal,
self.blur.value * scale,
Some(true),

View File

@@ -1,7 +1,10 @@
#[derive(Debug, Clone, Copy, PartialEq)]
use macros::ToJs;
#[derive(Debug, Clone, Copy, PartialEq, ToJs)]
#[repr(u8)]
pub enum BlurType {
None,
Layer,
None = 0,
LayerBlur = 1,
}
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -11,10 +14,12 @@ pub struct Blur {
pub value: f32,
}
// TODO: maybe move this to the wasm module?
impl From<u8> for BlurType {
// TODO: use transmute
fn from(value: u8) -> Self {
match value {
1 => BlurType::Layer,
1 => BlurType::LayerBlur,
_ => BlurType::None,
}
}

View File

@@ -1,3 +1,5 @@
use macros::ToJs;
use super::Path;
#[derive(Debug, Clone, PartialEq)]
@@ -6,12 +8,14 @@ pub struct Bool {
pub path: Path,
}
#[derive(Debug, Clone, Copy, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, Copy, PartialEq, ToJs)]
#[repr(u8)]
pub enum BoolType {
Union,
Difference,
Intersection,
Exclusion,
Union = 0,
Difference = 1,
Intersection = 2,
Exclusion = 3,
}
impl Default for Bool {
@@ -23,7 +27,9 @@ impl Default for Bool {
}
}
// TODO: maybe move this to the wasm module?
impl From<u8> for BoolType {
// TODO: use transmute
fn from(value: u8) -> Self {
match value {
0 => Self::Union,

View File

@@ -1,14 +1,19 @@
use std::fmt;
use crate::uuid::Uuid;
use macros::ToJs;
#[derive(Debug, PartialEq, Clone, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[repr(u8)]
pub enum FontStyle {
Normal,
Italic,
Normal = 0,
Italic = 1,
}
// TODO: maybe move this to the wasm module?
impl From<u8> for FontStyle {
// TODO: use transmute
fn from(value: u8) -> Self {
match value {
0 => Self::Normal,

View File

@@ -1,3 +1,5 @@
use macros::ToJs;
use crate::utils::uuid_from_u32_quartet;
use crate::uuid::Uuid;
@@ -22,15 +24,20 @@ impl Layout {
}
}
#[derive(Debug, Clone, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, ToJs)]
#[repr(u8)]
pub enum FlexDirection {
Row,
RowReverse,
Column,
ColumnReverse,
Row = 0,
RowReverse = 1,
Column = 2,
ColumnReverse = 3,
}
// TODO: maybe move this to the wasm module?
impl FlexDirection {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Row,
@@ -42,13 +49,18 @@ impl FlexDirection {
}
}
#[derive(Debug, Clone, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, ToJs)]
#[repr(u8)]
pub enum GridDirection {
Row,
Column,
Row = 0,
Column = 1,
}
// TODO: maybe move this to the wasm module?
impl GridDirection {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Row,
@@ -58,15 +70,20 @@ impl GridDirection {
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, Copy, ToJs)]
#[repr(u8)]
pub enum AlignItems {
Start,
End,
Center,
Stretch,
Start = 0,
End = 1,
Center = 2,
Stretch = 3,
}
// TODO: maybe move this to the wasm module?
impl AlignItems {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Start,
@@ -78,18 +95,23 @@ impl AlignItems {
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, Copy, ToJs)]
#[repr(u8)]
pub enum AlignContent {
Start,
End,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
Stretch,
Start = 0,
End = 1,
Center = 2,
SpaceBetween = 3,
SpaceAround = 4,
SpaceEvenly = 5,
Stretch = 6,
}
// TODO: maybe move this to the wasm module?
impl AlignContent {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Start,
@@ -104,15 +126,20 @@ impl AlignContent {
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, Copy, ToJs)]
#[repr(u8)]
pub enum JustifyItems {
Start,
End,
Center,
Stretch,
Start = 0,
End = 1,
Center = 2,
Stretch = 3,
}
// TODO: maybe move this to the wasm module?
impl JustifyItems {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Start,
@@ -124,18 +151,23 @@ impl JustifyItems {
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, Copy, ToJs)]
#[repr(u8)]
pub enum JustifyContent {
Start,
End,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
Stretch,
Start = 0,
End = 1,
Center = 2,
SpaceBetween = 3,
SpaceAround = 4,
SpaceEvenly = 5,
Stretch = 6,
}
// TODO: maybe move this to the wasm module?
impl JustifyContent {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Start,
@@ -150,30 +182,41 @@ impl JustifyContent {
}
}
#[derive(Debug, Clone, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, ToJs)]
#[repr(u8)]
pub enum WrapType {
Wrap,
NoWrap,
Wrap = 0,
Nowrap = 1,
}
// TODO: maybe move this to the wasm module?
impl WrapType {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Wrap,
1 => Self::NoWrap,
1 => Self::Nowrap,
_ => unreachable!(),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Copy, Clone, PartialEq, ToJs)]
#[repr(u8)]
pub enum GridTrackType {
Percent,
Flex,
Auto,
Fixed,
Percent = 0,
Flex = 1,
Auto = 2,
Fixed = 3,
}
// TODO: maybe move this to the wasm module?
impl GridTrackType {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Percent,
@@ -248,14 +291,19 @@ impl GridCell {
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, Copy, ToJs)]
#[repr(u8)]
pub enum Sizing {
Fill,
Fix,
Auto,
Fill = 0,
Fix = 1,
Auto = 2,
}
// TODO: maybe move this to the wasm module?
impl Sizing {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Self {
match value {
0 => Self::Fill,
@@ -291,16 +339,21 @@ impl LayoutData {
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Copy, Clone, PartialEq, ToJs)]
#[repr(u8)]
pub enum AlignSelf {
Auto,
Start,
End,
Center,
Stretch,
Auto = 0,
Start = 1,
End = 2,
Center = 3,
Stretch = 4,
}
// TODO: maybe move this to the wasm module?
impl AlignSelf {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Option<AlignSelf> {
match value {
0 => Some(Self::Auto),
@@ -313,16 +366,21 @@ impl AlignSelf {
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Copy, Clone, PartialEq, ToJs)]
#[repr(u8)]
pub enum JustifySelf {
Auto,
Start,
End,
Center,
Stretch,
Auto = 0,
Start = 1,
End = 2,
Center = 3,
Stretch = 4,
}
// TODO: maybe move this to the wasm module?
impl JustifySelf {
// TODO: implement a proper From trait for this
// TODO: use transmute
pub fn from_u8(value: u8) -> Option<JustifySelf> {
match value {
0 => Some(Self::Auto),
@@ -385,6 +443,7 @@ impl GridData {
}
}
// TODO: move this to the wasm module
// FIXME: use transmute
#[derive(Debug)]
#[repr(C)]

View File

@@ -13,7 +13,7 @@ pub fn calculate_resize(
ConstraintH::Left | ConstraintH::Right | ConstraintH::Center => {
parent_before.width() / f32::max(0.01, parent_after.width())
}
ConstraintH::LeftRight => {
ConstraintH::Leftright => {
let left = parent_before.left(child_before.nw);
let right = parent_before.right(child_before.ne);
let target_width = parent_after.width() - left - right;
@@ -26,7 +26,7 @@ pub fn calculate_resize(
ConstraintV::Top | ConstraintV::Bottom | ConstraintV::Center => {
parent_before.height() / f32::max(0.01, parent_after.height())
}
ConstraintV::TopBottom => {
ConstraintV::Topbottom => {
let top = parent_before.top(child_before.nw);
let bottom = parent_before.bottom(child_before.sw);
let target_height = parent_after.height() - top - bottom;
@@ -51,7 +51,7 @@ pub fn calculate_displacement(
child_after: &Bounds,
) -> Option<(f32, f32)> {
let delta_x = match constraint_h {
ConstraintH::Left | ConstraintH::LeftRight => {
ConstraintH::Left | ConstraintH::Leftright => {
let target_left = parent_before.left(child_before.nw);
let current_left = parent_after.left(child_after.nw);
target_left - current_left
@@ -71,7 +71,7 @@ pub fn calculate_displacement(
};
let delta_y = match constraint_v {
ConstraintV::Top | ConstraintV::TopBottom => {
ConstraintV::Top | ConstraintV::Topbottom => {
let target_top = parent_before.top(child_before.nw);
let current_top = parent_after.top(child_after.nw);
target_top - current_top

View File

@@ -1,15 +1,20 @@
use macros::ToJs;
use skia_safe::{self as skia, image_filters, ImageFilter, Paint};
use super::Color;
use crate::render::filters::compose_filters;
#[derive(Debug, Clone, Copy, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, Copy, PartialEq, ToJs)]
#[repr(u8)]
pub enum ShadowStyle {
Drop,
Inner,
Drop = 0,
Inner = 1,
}
// TODO: maybe move this to the wasm module?
impl From<u8> for ShadowStyle {
// TODO: use transmute
fn from(value: u8) -> Self {
match value {
0 => Self::Drop,

View File

@@ -1,18 +1,23 @@
use crate::shapes::fills::{Fill, SolidColor};
use macros::ToJs;
use skia_safe::{self as skia, Rect};
use std::collections::HashMap;
use super::Corners;
#[derive(Debug, Clone, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, PartialEq, ToJs)]
#[repr(u8)]
pub enum StrokeStyle {
Solid,
Dotted,
Dashed,
Mixed,
Solid = 0,
Dotted = 1,
Dashed = 2,
Mixed = 3,
}
// TODO: maybe move this to the wasm module?
impl From<u8> for StrokeStyle {
// TODO: use transmute
fn from(value: u8) -> Self {
match value {
1 => StrokeStyle::Dotted,
@@ -23,26 +28,29 @@ impl From<u8> for StrokeStyle {
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, Clone, Copy, PartialEq, ToJs)]
pub enum StrokeCap {
None,
Line,
Triangle,
Rectangle,
Circle,
Diamond,
Round,
Square,
None = 0,
LineArrow = 1,
TriangleArrow = 2,
SquareMarker = 3,
CircleMarker = 4,
DiamondMarker = 5,
Round = 6,
Square = 7,
}
// TODO: maybe move this to the wasm module?
impl From<u8> for StrokeCap {
// TODO: use transmute
fn from(value: u8) -> Self {
match value {
1 => StrokeCap::Line,
2 => StrokeCap::Triangle,
3 => StrokeCap::Rectangle,
4 => StrokeCap::Circle,
5 => StrokeCap::Diamond,
1 => StrokeCap::LineArrow,
2 => StrokeCap::TriangleArrow,
3 => StrokeCap::SquareMarker,
4 => StrokeCap::CircleMarker,
5 => StrokeCap::DiamondMarker,
6 => StrokeCap::Round,
7 => StrokeCap::Square,
_ => StrokeCap::None,

View File

@@ -4,6 +4,7 @@ use crate::{
textlayout::paragraph_builder_group_from_text,
};
use macros::ToJs;
use skia_safe::{
self as skia,
paint::{self, Paint},
@@ -18,14 +19,18 @@ use crate::utils::uuid_from_u32;
use crate::wasm::fills::parse_fills_from_bytes;
use crate::Uuid;
#[derive(Debug, PartialEq, Clone, Copy)]
// TODO: maybe move this to the wasm module?
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[repr(u8)]
pub enum GrowType {
Fixed,
AutoWidth,
AutoHeight,
Fixed = 0,
AutoWidth = 1,
AutoHeight = 2,
}
// TODO: maybe move this to the wasm module?
impl GrowType {
// TODO: use transmute
pub fn from(grow_type: u8) -> Self {
match grow_type {
0 => Self::Fixed,
@@ -134,6 +139,8 @@ impl Default for TextContent {
}
}
// FIXME: Rethink this type. We'll probably need to move the serialization to the
// wasm moduel and store here meaningful model values (and/or skia type aliases)
#[derive(Debug, PartialEq, Clone)]
pub struct Paragraph {
num_leaves: u32,
@@ -207,6 +214,7 @@ impl Paragraph {
self.children.push(leaf);
}
// FIXME: move serialization to wasm module
pub fn paragraph_to_style(&self) -> ParagraphStyle {
let mut style = ParagraphStyle::default();
style.set_text_align(match self.text_align {

View File

@@ -1,16 +1,18 @@
mod gradient;
mod image;
mod solid;
use macros::ToJs;
use crate::mem;
use crate::shapes;
use crate::with_current_shape_mut;
use crate::STATE;
mod gradient;
mod image;
mod solid;
const RAW_FILL_DATA_SIZE: usize = std::mem::size_of::<RawFillData>();
#[repr(C, u8, align(4))]
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[allow(dead_code)]
pub enum RawFillData {
Solid(solid::RawSolidData) = 0x00,

View File

@@ -1,5 +1,6 @@
#![allow(unused_mut, unused_variables)]
use indexmap::IndexSet;
use macros::ToJs;
use mem::SerializableResult;
use uuid::Uuid;
@@ -11,7 +12,7 @@ use crate::{mem, with_current_shape, with_current_shape_mut, with_state, STATE};
const RAW_SEGMENT_DATA_SIZE: usize = size_of::<RawSegmentData>();
#[repr(C, u16, align(4))]
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
#[allow(dead_code)]
enum RawSegmentData {
MoveTo(RawMoveCommand) = 0x01,

View File

@@ -9,11 +9,15 @@ pushd $_SCRIPT_DIR;
export CARGO_BUILD_TARGET=${CARGO_BUILD_TARGET:-"wasm32-unknown-emscripten"};
export SKIA_BINARIES_URL=${SKIA_BINARIES_URL:-"https://github.com/penpot/skia-binaries/releases/download/0.87.0/skia-binaries-e551f334ad5cbdf43abf-wasm32-unknown-emscripten-gl-svg-textlayout-binary-cache-webp.tar.gz"}
_SHARED_FILE=$(find target/wasm32-unknown-emscripten -name render_wasm_shared.js | head -n 1);
cat target/wasm32-unknown-emscripten/$_BUILD_MODE/render_wasm.js "$_SHARED_FILE" > ../frontend/resources/public/js/$_BUILD_NAME.js
cp target/wasm32-unknown-emscripten/$_BUILD_MODE/render_wasm.wasm ../frontend/resources/public/js/$_BUILD_NAME.wasm
pushd $_SCRIPT_DIR;
cargo watch \
-x "build $_CARGO_PARAMS" \
-s "cp target/wasm32-unknown-emscripten/$_BUILD_MODE/render_wasm.js ../frontend/resources/public/js/" \
-s "cat target/wasm32-unknown-emscripten/$_BUILD_MODE/render_wasm.js \"$_SHARED_FILE\" > ../frontend/resources/public/js/$_BUILD_NAME.js" \
-s "cp target/wasm32-unknown-emscripten/$_BUILD_MODE/render_wasm.wasm ../frontend/resources/public/js/" \
-s "sed -i 's/render_wasm.wasm/render_wasm.wasm?version=develop/g' ../frontend/resources/public/js/render_wasm.js" \
-s "echo 'DONE\n'";