diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 638fdaca3f..a54c7c0dc6 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -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) diff --git a/frontend/src/app/render_wasm/serializers.cljs b/frontend/src/app/render_wasm/serializers.cljs index cd5feb080f..e6f1729c8a 100644 --- a/frontend/src/app/render_wasm/serializers.cljs +++ b/frontend/src/app/render_wasm/serializers.cljs @@ -4,10 +4,12 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.render-wasm.serializers - (:require - [app.common.uuid :as uuid] - [cuerdas.core :as str])) + (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 [value] @@ -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] - (case font-style - "normal" 0 - "regular" 0 - "italic" 1 - 0)) + (let [values (unchecked-get wasm/serializers "font-style") + default (unchecked-get values "normal")] + (case font-style + ;; 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))) diff --git a/frontend/src/app/render_wasm/wasm.cljs b/frontend/src/app/render_wasm/wasm.cljs index 542d4c49a1..7d7511d9ab 100644 --- a/frontend/src/app/render_wasm/wasm.cljs +++ b/frontend/src/app/render_wasm/wasm.cljs @@ -1,4 +1,5 @@ (ns app.render-wasm.wasm) (defonce internal-frame-id nil) -(defonce internal-module #js {}) \ No newline at end of file +(defonce internal-module #js {}) +(defonce serializers #js {}) \ No newline at end of file diff --git a/render-wasm/Cargo.lock b/render-wasm/Cargo.lock index ed1ebde4b1..9a9d050849 100644 --- a/render-wasm/Cargo.lock +++ b/render-wasm/Cargo.lock @@ -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", diff --git a/render-wasm/Cargo.toml b/render-wasm/Cargo.toml index 1a327e507c..82cde41199 100644 --- a/render-wasm/Cargo.toml +++ b/render-wasm/Cargo.toml @@ -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"] } diff --git a/render-wasm/build b/render-wasm/build index e518367005..f2f03c3a8b 100755 --- a/render-wasm/build +++ b/render-wasm/build @@ -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; diff --git a/render-wasm/build.rs b/render-wasm/build.rs new file mode 100644 index 0000000000..8f5ce28f5f --- /dev/null +++ b/render-wasm/build.rs @@ -0,0 +1,3 @@ +// We need this empty script so OUT_DIR is automatically set and we can build +// the macros crate +fn main() {} diff --git a/render-wasm/docs/serialization.md b/render-wasm/docs/serialization.md index 36cbc5833c..0b41bdb561 100644 --- a/render-wasm/docs/serialization.md +++ b/render-wasm/docs/serialization.md @@ -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: @@ -126,16 +137,17 @@ 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 | -| 6 | Round | -| 7 | Square | -| \_ | None | +| Value | Field | +| ----- | ------------- | +| 0 | None | +| 1 | LineArrow | +| 2 | TriangleArrow | +| 3 | SquareMarker | +| 4 | CircleMarker | +| 5 | DiamondMarker | +| 6 | Round | +| 7 | Square | +| \_ | None | ## Stroke Sytles diff --git a/render-wasm/macros/Cargo.lock b/render-wasm/macros/Cargo.lock new file mode 100644 index 0000000000..8b4fd748ae --- /dev/null +++ b/render-wasm/macros/Cargo.lock @@ -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" diff --git a/render-wasm/macros/Cargo.toml b/render-wasm/macros/Cargo.toml new file mode 100644 index 0000000000..6c2abd7509 --- /dev/null +++ b/render-wasm/macros/Cargo.toml @@ -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" diff --git a/render-wasm/macros/src/lib.rs b/render-wasm/macros/src/lib.rs new file mode 100644 index 0000000000..a0eec23ca6 --- /dev/null +++ b/render-wasm/macros/src/lib.rs @@ -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 = std::result::Result; + +#[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::>(); + + 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::>()); + + 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> { + 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 { + 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::>() + .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(()) +} diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 076b6c0dde..35feb470a0 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -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(); diff --git a/render-wasm/src/render/blend.rs b/render-wasm/src/render/blend.rs index b54283a012..b232867e6d 100644 --- a/render-wasm/src/render/blend.rs +++ b/render-wasm/src/render/blend.rs @@ -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); diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 62046d9ead..784705bffb 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -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 => { diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index a92dcda94b..b785c4c786 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -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 { 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 { 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), diff --git a/render-wasm/src/shapes/blurs.rs b/render-wasm/src/shapes/blurs.rs index 36d3b588f2..101e125d5f 100644 --- a/render-wasm/src/shapes/blurs.rs +++ b/render-wasm/src/shapes/blurs.rs @@ -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 for BlurType { + // TODO: use transmute fn from(value: u8) -> Self { match value { - 1 => BlurType::Layer, + 1 => BlurType::LayerBlur, _ => BlurType::None, } } diff --git a/render-wasm/src/shapes/bools.rs b/render-wasm/src/shapes/bools.rs index 1bb77b699c..381eba3e65 100644 --- a/render-wasm/src/shapes/bools.rs +++ b/render-wasm/src/shapes/bools.rs @@ -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 for BoolType { + // TODO: use transmute fn from(value: u8) -> Self { match value { 0 => Self::Union, diff --git a/render-wasm/src/shapes/fonts.rs b/render-wasm/src/shapes/fonts.rs index 7a4524cad5..296e08570e 100644 --- a/render-wasm/src/shapes/fonts.rs +++ b/render-wasm/src/shapes/fonts.rs @@ -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 for FontStyle { + // TODO: use transmute fn from(value: u8) -> Self { match value { 0 => Self::Normal, diff --git a/render-wasm/src/shapes/layouts.rs b/render-wasm/src/shapes/layouts.rs index edb2e80548..9fefa58224 100644 --- a/render-wasm/src/shapes/layouts.rs +++ b/render-wasm/src/shapes/layouts.rs @@ -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 { 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 { 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)] diff --git a/render-wasm/src/shapes/modifiers/constraints.rs b/render-wasm/src/shapes/modifiers/constraints.rs index 1a838c0a9a..1dd051480e 100644 --- a/render-wasm/src/shapes/modifiers/constraints.rs +++ b/render-wasm/src/shapes/modifiers/constraints.rs @@ -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 diff --git a/render-wasm/src/shapes/shadows.rs b/render-wasm/src/shapes/shadows.rs index 2eec866f86..65d0a51f31 100644 --- a/render-wasm/src/shapes/shadows.rs +++ b/render-wasm/src/shapes/shadows.rs @@ -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 for ShadowStyle { + // TODO: use transmute fn from(value: u8) -> Self { match value { 0 => Self::Drop, diff --git a/render-wasm/src/shapes/strokes.rs b/render-wasm/src/shapes/strokes.rs index c69de1ffcb..b5a1a84a9c 100644 --- a/render-wasm/src/shapes/strokes.rs +++ b/render-wasm/src/shapes/strokes.rs @@ -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 for StrokeStyle { + // TODO: use transmute fn from(value: u8) -> Self { match value { 1 => StrokeStyle::Dotted, @@ -23,26 +28,29 @@ impl From 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 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, diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index 00584712e2..2309096c1f 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -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 { diff --git a/render-wasm/src/wasm/fills.rs b/render-wasm/src/wasm/fills.rs index 29c0d573d6..dd57170555 100644 --- a/render-wasm/src/wasm/fills.rs +++ b/render-wasm/src/wasm/fills.rs @@ -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::(); #[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, diff --git a/render-wasm/src/wasm/paths.rs b/render-wasm/src/wasm/paths.rs index a211eaaff5..c31160a3c4 100644 --- a/render-wasm/src/wasm/paths.rs +++ b/render-wasm/src/wasm/paths.rs @@ -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::(); #[repr(C, u16, align(4))] -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Clone, Copy, ToJs)] #[allow(dead_code)] enum RawSegmentData { MoveTo(RawMoveCommand) = 0x01, diff --git a/render-wasm/watch b/render-wasm/watch index 874dfba92f..74a2b1974a 100755 --- a/render-wasm/watch +++ b/render-wasm/watch @@ -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'";