Merge pull request #7106 from penpot/niwinz-develop-modifiers-enhacements

 Several enhancements on how shape data is written on memory
This commit is contained in:
Aitor Moreno
2025-08-19 17:05:25 +02:00
committed by GitHub
10 changed files with 425 additions and 292 deletions

View File

@@ -5,7 +5,8 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.common.buffer (ns app.common.buffer
"A collection of helpers and macros for work with byte buffers" "A collection of helpers and macros for work with byte
buffer (ByteBuffer on JVM and DataView on JS)."
(:refer-clojure :exclude [clone]) (:refer-clojure :exclude [clone])
(:require (:require
[app.common.uuid :as uuid]) [app.common.uuid :as uuid])
@@ -81,6 +82,13 @@
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})] (let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target ~offset (unchecked-byte ~value))))) `(.put ~target ~offset (unchecked-byte ~value)))))
(defmacro write-u8
[target offset value]
(if (:ns &env)
`(.setUint8 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target ~offset (unchecked-byte ~value)))))
(defmacro write-bool (defmacro write-bool
[target offset value] [target offset value]
(if (:ns &env) (if (:ns &env)
@@ -102,6 +110,18 @@
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})] (let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putInt ~target ~offset (unchecked-int ~value))))) `(.putInt ~target ~offset (unchecked-int ~value)))))
(defmacro write-u32
[target offset value]
(if (:ns &env)
`(.setUint32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putInt ~target ~offset (unchecked-int ~value)))))
(defmacro write-i32
"Idiomatic alias for `write-int`"
[target offset value]
`(write-int ~target ~offset ~value))
(defmacro write-float (defmacro write-float
[target offset value] [target offset value]
(if (:ns &env) (if (:ns &env)
@@ -109,6 +129,11 @@
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})] (let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putFloat ~target ~offset (unchecked-float ~value))))) `(.putFloat ~target ~offset (unchecked-float ~value)))))
(defmacro write-f32
"Idiomatic alias for `write-float`."
[target offset value]
`(write-float ~target ~offset ~value))
(defmacro write-uuid (defmacro write-uuid
[target offset value] [target offset value]
(if (:ns &env) (if (:ns &env)

View File

@@ -58,13 +58,13 @@
([{:keys [id points selrect] :as shape} content] ([{:keys [id points selrect] :as shape} content]
(wasm.api/use-shape id) (wasm.api/use-shape id)
(wasm.api/set-shape-text id content) (wasm.api/set-shape-text id content)
(let [dimension (wasm.api/text-dimensions) (let [dimension (wasm.api/get-text-dimensions)
resize-v resize-v (gpt/point
(gpt/point (/ (:width dimension) (-> selrect :width))
(/ (:width dimension) (-> selrect :width)) (/ (:height dimension) (-> selrect :height)))
(/ (:height dimension) (-> selrect :height)))
origin (first points)]
origin (first points)]
{id {id
{:modifiers {:modifiers
(ctm/resize-modifiers (ctm/resize-modifiers

View File

@@ -19,7 +19,7 @@
(if (features/active-feature? @st/state "render-wasm/v1") (if (features/active-feature? @st/state "render-wasm/v1")
(let [transform (gsh/transform-str shape) (let [transform (gsh/transform-str shape)
{:keys [id x y grow-type]} shape {:keys [id x y grow-type]} shape
{:keys [width height]} (if (= :fixed grow-type) shape (wasm.api/text-dimensions id))] {:keys [width height]} (if (= :fixed grow-type) shape (wasm.api/get-text-dimensions id))]
[:rect.main.viewport-selrect [:rect.main.viewport-selrect
{:x x {:x x
:y y :y y

View File

@@ -310,7 +310,7 @@
[x y width height] [x y width height]
(if (features/active-feature? @st/state "render-wasm/v1") (if (features/active-feature? @st/state "render-wasm/v1")
(let [{:keys [max-width height]} (wasm.api/text-dimensions shape-id) (let [{:keys [max-width height]} (wasm.api/get-text-dimensions shape-id)
{:keys [x y]} (:selrect shape)] {:keys [x y]} (:selrect shape)]
[x y max-width height]) [x y max-width height])

View File

@@ -309,7 +309,7 @@
(ted/export-content))] (ted/export-content))]
(wasm.api/use-shape edition) (wasm.api/use-shape edition)
(wasm.api/set-shape-text-content edition content) (wasm.api/set-shape-text-content edition content)
(let [dimension (wasm.api/text-dimensions)] (let [dimension (wasm.api/get-text-dimensions)]
(st/emit! (dwt/resize-text-editor edition dimension)) (st/emit! (dwt/resize-text-editor edition dimension))
(wasm.api/clear-drawing-cache) (wasm.api/clear-drawing-cache)
(wasm.api/request-render "content")))))) (wasm.api/request-render "content"))))))

View File

@@ -208,7 +208,7 @@
(defn- process-fill-image (defn- process-fill-image
[shape-id fill] [shape-id fill]
(when-let [image (:fill-image fill)] (when-let [image (:fill-image fill)]
(let [id (dm/get-prop image :id) (let [id (get image :id)
buffer (uuid/get-u32 id) buffer (uuid/get-u32 id)
cached-image? (h/call wasm/internal-module "_is_image_cached" cached-image? (h/call wasm/internal-module "_is_image_cached"
(aget buffer 0) (aget buffer 0)
@@ -283,7 +283,7 @@
(h/call wasm/internal-module "_add_shape_stroke_fill")) (h/call wasm/internal-module "_add_shape_stroke_fill"))
(some? image) (some? image)
(let [image-id (dm/get-prop image :id) (let [image-id (get image :id)
buffer (uuid/get-u32 image-id) buffer (uuid/get-u32 image-id)
cached-image? (h/call wasm/internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))] cached-image? (h/call wasm/internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))]
(types.fills.impl/write-image-fill offset dview opacity image) (types.fills.impl/write-image-fill offset dview opacity image)
@@ -334,7 +334,7 @@
(defn set-shape-vertical-align (defn set-shape-vertical-align
[vertical-align] [vertical-align]
(h/call wasm/internal-module "_set_shape_vertical_align" (sr/serialize-vertical-align vertical-align))) (h/call wasm/internal-module "_set_shape_vertical_align" (sr/translate-vertical-align vertical-align)))
(defn set-shape-opacity (defn set-shape-opacity
[opacity] [opacity]
@@ -378,8 +378,11 @@
(h/call wasm/internal-module "_set_shape_blur" type hidden value))) (h/call wasm/internal-module "_set_shape_blur" type hidden value)))
(defn set-shape-corners (defn set-shape-corners
[corners] [shape]
(let [[r1 r2 r3 r4] corners] (let [r1 (get shape :r1)
r2 (get shape :r2)
r3 (get shape :r3)
r4 (get shape :r4)]
(h/call wasm/internal-module "_set_shape_corners" (h/call wasm/internal-module "_set_shape_corners"
(d/nilv r1 0) (d/nilv r1 0)
(d/nilv r2 0) (d/nilv r2 0)
@@ -466,9 +469,11 @@
;; alligned writes, so for heteregeneus writes we use ;; alligned writes, so for heteregeneus writes we use
;; the buffer abstraction (DataView) for perform ;; the buffer abstraction (DataView) for perform
;; surgical writes. ;; surgical writes.
(mem/write-u8 dview (+ offset 0) (sr/translate-grid-track-type type)) (-> offset
(mem/write-f32 dview (+ offset 1) value) (mem/write-u8 dview (sr/translate-grid-track-type type))
(+ offset GRID-LAYOUT-ROW-U8-SIZE)) (mem/write-f32 dview value)
(mem/assert-written offset GRID-LAYOUT-ROW-U8-SIZE)))
offset offset
entries) entries)
@@ -486,9 +491,12 @@
;; alligned writes, so for heteregeneus writes we use ;; alligned writes, so for heteregeneus writes we use
;; the buffer abstraction (DataView) for perform ;; the buffer abstraction (DataView) for perform
;; surgical writes. ;; surgical writes.
(mem/write-u8 dview (+ offset 0) (sr/translate-grid-track-type type)) (-> offset
(mem/write-f32 dview (+ offset 1) value) (mem/write-u8 dview (sr/translate-grid-track-type type))
(+ offset GRID-LAYOUT-COLUMN-U8-SIZE)) (mem/write-f32 dview value)
(mem/assert-written offset GRID-LAYOUT-COLUMN-U8-SIZE)))
offset offset
entries) entries)
@@ -496,83 +504,82 @@
(defn set-grid-layout-cells (defn set-grid-layout-cells
[cells] [cells]
(let [entries (vals cells) (let [size (mem/get-alloc-size cells GRID-LAYOUT-CELL-U8-SIZE)
size (mem/get-alloc-size cells GRID-LAYOUT-CELL-U8-SIZE)
offset (mem/alloc size) offset (mem/alloc size)
heap (-> (mem/get-heap-u8) dview (mem/get-data-view)]
(mem/view offset size))]
(loop [entries (seq entries) (reduce-kv (fn [offset _ cell]
current-offset 0] (let [shape-id (-> (get cell :shapes) first)]
(when-not (empty? entries) (-> offset
(let [cell (first entries)] ;; row: [u8; 4],
(mem/write-i32 dview (get cell :row))
;; row: [u8; 4], ;; row_span: [u8; 4],
(.set heap (sr/i32->u8 (:row cell)) (+ current-offset 0)) (mem/write-i32 dview (get cell :row-span))
;; row_span: [u8; 4], ;; column: [u8; 4],
(.set heap (sr/i32->u8 (:row-span cell)) (+ current-offset 4)) (mem/write-i32 dview (get cell :column))
;; column: [u8; 4], ;; column_span: [u8; 4],
(.set heap (sr/i32->u8 (:column cell)) (+ current-offset 8)) (mem/write-i32 dview (get cell :column-span))
;; column_span: [u8; 4], ;; has_align_self: u8,
(.set heap (sr/i32->u8 (:column-span cell)) (+ current-offset 12)) (mem/write-bool dview (some? (get cell :align-self)))
;; has_align_self: u8, ;; align_self: u8,
(.set heap (sr/bool->u8 (some? (:align-self cell))) (+ current-offset 16)) (mem/write-u8 dview (get cell :align-self))
;; align_self: u8, ;; has_justify_self: u8,
(.set heap (sr/u8 (sr/translate-align-self (:align-self cell))) (+ current-offset 17)) (mem/write-bool dview (get cell :justify-self))
;; has_justify_self: u8, ;; justify_self: u8,
(.set heap (sr/bool->u8 (some? (:justify-self cell))) (+ current-offset 18)) (mem/write-u8 dview (sr/translate-justify-self (get cell :justify-self)))
;; justify_self: u8, ;; has_shape_id: u8,
(.set heap (sr/u8 (sr/translate-justify-self (:justify-self cell))) (+ current-offset 19)) ;; (.set heap (sr/bool->u8 (d/not-empty? (:shapes cell))) (+ current-offset 20))
(mem/write-u8 dview (some? shape-id))
;; has_shape_id: u8, ;; shape_id_a: [u8; 4],
(.set heap (sr/bool->u8 (d/not-empty? (:shapes cell))) (+ current-offset 20)) ;; shape_id_b: [u8; 4],
;; shape_id_c: [u8; 4],
;; shape_id_d: [u8; 4],
(mem/write-uuid dview (d/nilv shape-id uuid/zero))
(mem/assert-written offset GRID-LAYOUT-CELL-U8-SIZE))))
;; shape_id_a: [u8; 4], offset
;; shape_id_b: [u8; 4], cells)
;; shape_id_c: [u8; 4],
;; shape_id_d: [u8; 4],
(.set heap (sr/uuid->u8 (or (-> cell :shapes first) uuid/zero)) (+ current-offset 21))
(recur (rest entries) (+ current-offset GRID-LAYOUT-CELL-U8-SIZE)))))
(h/call wasm/internal-module "_set_grid_cells"))) (h/call wasm/internal-module "_set_grid_cells")))
(defn set-grid-layout (defn set-grid-layout
[shape] [shape]
(set-grid-layout-data shape) (set-grid-layout-data shape)
(set-grid-layout-rows (:layout-grid-rows shape)) (set-grid-layout-rows (get shape :layout-grid-rows))
(set-grid-layout-columns (:layout-grid-columns shape)) (set-grid-layout-columns (get shape :layout-grid-columns))
(set-grid-layout-cells (:layout-grid-cells shape))) (set-grid-layout-cells (get shape :layout-grid-cells)))
(defn set-layout-child (defn set-layout-child
[shape] [shape]
(let [margins (dm/get-prop shape :layout-item-margin) (let [margins (get shape :layout-item-margin)
margin-top (or (dm/get-prop margins :m1) 0) margin-top (get margins :m1 0)
margin-right (or (dm/get-prop margins :m2) 0) margin-right (get margins :m2 0)
margin-bottom (or (dm/get-prop margins :m3) 0) margin-bottom (get margins :m3 0)
margin-left (or (dm/get-prop margins :m4) 0) margin-left (get margins :m4 0)
h-sizing (-> (dm/get-prop shape :layout-item-h-sizing) (or :fix) sr/translate-layout-sizing) h-sizing (-> (get shape :layout-item-h-sizing) sr/translate-layout-sizing)
v-sizing (-> (dm/get-prop shape :layout-item-v-sizing) (or :fix) sr/translate-layout-sizing) v-sizing (-> (get shape :layout-item-v-sizing) sr/translate-layout-sizing)
align-self (-> (dm/get-prop shape :layout-item-align-self) sr/translate-align-self) align-self (-> (get shape :layout-item-align-self) sr/translate-align-self)
max-h (dm/get-prop shape :layout-item-max-h) max-h (get shape :layout-item-max-h)
has-max-h (some? max-h) has-max-h (some? max-h)
min-h (dm/get-prop shape :layout-item-min-h) min-h (get shape :layout-item-min-h)
has-min-h (some? min-h) has-min-h (some? min-h)
max-w (dm/get-prop shape :layout-item-max-w) max-w (get shape :layout-item-max-w)
has-max-w (some? max-w) has-max-w (some? max-w)
min-w (dm/get-prop shape :layout-item-min-w) min-w (get shape :layout-item-min-w)
has-min-w (some? min-w) has-min-w (some? min-w)
is-absolute (boolean (dm/get-prop shape :layout-item-absolute)) is-absolute (boolean (get shape :layout-item-absolute))
z-index (-> (dm/get-prop shape :layout-item-z-index) (or 0))] z-index (get shape :layout-item-z-index)]
(h/call wasm/internal-module (h/call wasm/internal-module
"_set_layout_child_data" "_set_layout_child_data"
margin-top margin-top
@@ -582,17 +589,17 @@
h-sizing h-sizing
v-sizing v-sizing
has-max-h has-max-h
(or max-h 0) (d/nilv max-h 0)
has-min-h has-min-h
(or min-h 0) (d/nilv min-h 0)
has-max-w has-max-w
(or max-w 0) (d/nilv max-w 0)
has-min-w has-min-w
(or min-w 0) (d/nilv min-w 0)
(some? align-self) (some? align-self)
(or align-self 0) (d/nilv align-self 0)
is-absolute is-absolute
z-index))) (d/nilv z-index))))
(defn clear-layout (defn clear-layout
[] []
@@ -615,50 +622,63 @@
(defn set-shape-shadows (defn set-shape-shadows
[shadows] [shadows]
(h/call wasm/internal-module "_clear_shape_shadows") (h/call wasm/internal-module "_clear_shape_shadows")
(let [total-shadows (count shadows)]
(loop [index 0]
(when (< index total-shadows)
(let [shadow (nth shadows index)
color (dm/get-prop shadow :color)
blur (dm/get-prop shadow :blur)
rgba (sr-clr/hex->u32argb (dm/get-prop color :color) (dm/get-prop color :opacity))
hidden (dm/get-prop shadow :hidden)
x (dm/get-prop shadow :offset-x)
y (dm/get-prop shadow :offset-y)
spread (dm/get-prop shadow :spread)
style (dm/get-prop shadow :style)]
(h/call wasm/internal-module "_add_shape_shadow" rgba blur spread x y (sr/translate-shadow-style style) hidden)
(recur (inc index)))))))
;; (declare propagate-apply) (run! (fn [shadow]
(let [color (get shadow :color)
blur (get shadow :blur)
rgba (sr-clr/hex->u32argb (get color :color)
(get color :opacity))
hidden (get shadow :hidden)
x (get shadow :offset-x)
y (get shadow :offset-y)
spread (get shadow :spread)
style (get shadow :style)]
(h/call wasm/internal-module "_add_shape_shadow"
rgba
blur
spread
x
y
(sr/translate-shadow-style style)
hidden)))
shadows))
(defn set-shape-text-content (defn set-shape-text-content
[shape-id content] [shape-id content]
(h/call wasm/internal-module "_clear_shape_text") (h/call wasm/internal-module "_clear_shape_text")
(set-shape-vertical-align (dm/get-prop content :vertical-align)) (set-shape-vertical-align (get content :vertical-align))
(let [paragraph-set (first (dm/get-prop content :children)) (let [paragraph-set (first (get content :children))
paragraphs (dm/get-prop paragraph-set :children) paragraphs (get paragraph-set :children)
fonts (fonts/get-content-fonts content) fonts (fonts/get-content-fonts content)
emoji? (atom false) total (count paragraphs)]
languages (atom #{})]
(loop [index 0] (loop [index 0
(when (< index (count paragraphs)) emoji? false
langs #{}]
(if (< index total)
(let [paragraph (nth paragraphs index) (let [paragraph (nth paragraphs index)
leaves (dm/get-prop paragraph :children)] leaves (get paragraph :children)]
(when (seq leaves) (if (empty? (seq leaves))
(let [text (apply str (map :text leaves))] (recur (inc index)
(when (and (not @emoji?) (t/contains-emoji? text)) emoji?
(reset! emoji? true)) langs)
(swap! languages into (t/get-languages text))
(t/write-shape-text leaves paragraph text))
(recur (inc index))))))
(let [updated-fonts (let [text (apply str (map :text leaves))
(-> fonts emoji? (if emoji? emoji? (t/contains-emoji? text))
(cond-> @emoji? (f/add-emoji-font)) langs (t/collect-used-languages langs text)]
(f/add-noto-fonts @languages))]
(f/store-fonts shape-id updated-fonts)))) (t/write-shape-text leaves paragraph text)
(recur (inc index)
emoji?
langs))))
(let [updated-fonts
(-> fonts
(cond-> ^boolean emoji? (f/add-emoji-font))
(f/add-noto-fonts langs))]
(f/store-fonts shape-id updated-fonts))))))
(defn set-shape-text (defn set-shape-text
[shape-id content] [shape-id content]
@@ -670,16 +690,17 @@
[grow-type] [grow-type]
(h/call wasm/internal-module "_set_shape_grow_type" (sr/translate-grow-type grow-type))) (h/call wasm/internal-module "_set_shape_grow_type" (sr/translate-grow-type grow-type)))
(defn text-dimensions (defn get-text-dimensions
([id] ([id]
(use-shape id) (use-shape id)
(text-dimensions)) (get-text-dimensions))
([] ([]
(let [offset (h/call wasm/internal-module "_get_text_dimensions") (let [offset (-> (h/call wasm/internal-module "_get_text_dimensions")
heapf32 (mem/get-heap-f32) (mem/->offset-32))
width (aget heapf32 (mem/->offset-32 offset)) heapf32 (mem/get-heap-f32)
height (aget heapf32 (mem/->offset-32 (+ offset 4))) width (aget heapf32 (+ offset 0))
max-width (aget heapf32 (mem/->offset-32 (+ offset 8)))] height (aget heapf32 (+ offset 1))
max-width (aget heapf32 (+ offset 2))]
(mem/free) (mem/free)
{:width width :height height :max-width max-width}))) {:width width :height height :max-width max-width})))
@@ -699,37 +720,34 @@
[objects shape] [objects shape]
(perf/begin-measure "set-object") (perf/begin-measure "set-object")
(let [id (dm/get-prop shape :id) (let [id (dm/get-prop shape :id)
parent-id (dm/get-prop shape :parent-id)
type (dm/get-prop shape :type) type (dm/get-prop shape :type)
masked (dm/get-prop shape :masked-group)
selrect (dm/get-prop shape :selrect) parent-id (get shape :parent-id)
constraint-h (dm/get-prop shape :constraints-h) masked (get shape :masked-group)
constraint-v (dm/get-prop shape :constraints-v) selrect (get shape :selrect)
constraint-h (get shape :constraints-h)
constraint-v (get shape :constraints-v)
clip-content (if (= type :frame) clip-content (if (= type :frame)
(not (dm/get-prop shape :show-content)) (not (get shape :show-content))
false) false)
rotation (dm/get-prop shape :rotation) rotation (get shape :rotation)
transform (dm/get-prop shape :transform) transform (get shape :transform)
;; Groups from imported SVG's can have their own fills ;; Groups from imported SVG's can have their own fills
fills (dm/get-prop shape :fills) fills (get shape :fills)
strokes (if (= type :group) strokes (if (= type :group)
[] (dm/get-prop shape :strokes)) [] (get shape :strokes))
children (dm/get-prop shape :shapes) children (get shape :shapes)
blend-mode (dm/get-prop shape :blend-mode) blend-mode (get shape :blend-mode)
opacity (dm/get-prop shape :opacity) opacity (get shape :opacity)
hidden (dm/get-prop shape :hidden) hidden (get shape :hidden)
content (dm/get-prop shape :content) content (get shape :content)
bool-type (dm/get-prop shape :bool-type) bool-type (get shape :bool-type)
grow-type (dm/get-prop shape :grow-type) grow-type (get shape :grow-type)
blur (dm/get-prop shape :blur) blur (get shape :blur)
svg-attrs (dm/get-prop shape :svg-attrs) svg-attrs (get shape :svg-attrs)
shadows (dm/get-prop shape :shadow) shadows (get shape :shadow)]
r1 (dm/get-prop shape :r1)
r2 (dm/get-prop shape :r2)
r3 (dm/get-prop shape :r3)
r4 (dm/get-prop shape :r4)]
(use-shape id) (use-shape id)
(set-parent-id parent-id) (set-parent-id parent-id)
@@ -743,7 +761,7 @@
(set-shape-opacity opacity) (set-shape-opacity opacity)
(set-shape-hidden hidden) (set-shape-hidden hidden)
(set-shape-children children) (set-shape-children children)
(set-shape-corners [r1 r2 r3 r4]) (set-shape-corners shape)
(when (and (= type :group) masked) (when (and (= type :group) masked)
(set-masked masked)) (set-masked masked))
(when (some? blur) (when (some? blur)
@@ -773,7 +791,6 @@
(perf/end-measure "set-object") (perf/end-measure "set-object")
pending))) pending)))
(defn process-pending (defn process-pending
[pending] [pending]
(let [event (js/CustomEvent. "wasm:set-objects-finished") (let [event (js/CustomEvent. "wasm:set-objects-finished")

View File

@@ -151,21 +151,17 @@
"italic" 1 "italic" 1
0)) 0))
(defn serialize-font-id (defn normalize-font-id
[font-id] [font-id]
(try (try
(if (nil? font-id) (if ^boolean (str/starts-with? font-id "gfont-")
(do (google-font-id->uuid font-id)
[uuid/zero]) (let [no-prefix (subs font-id (inc (str/index-of font-id "-")))]
(let [google-font? (str/starts-with? font-id "gfont-")] (if (or (nil? no-prefix) (not (string? no-prefix)) (str/blank? no-prefix))
(if google-font? uuid/zero
(uuid/get-u32 (google-font-id->uuid font-id)) (uuid/parse no-prefix))))
(let [no-prefix (subs font-id (inc (str/index-of font-id "-")))]
(if (or (nil? no-prefix) (not (string? no-prefix)) (str/blank? no-prefix))
[uuid/zero]
(uuid/get-u32 (uuid/uuid no-prefix)))))))
(catch :default _e (catch :default _e
[uuid/zero]))) uuid/zero)))
(defn serialize-font-weight (defn serialize-font-weight
[font-weight] [font-weight]

View File

@@ -6,24 +6,31 @@
(ns app.render-wasm.api.texts (ns app.render-wasm.api.texts
(:require (:require
[app.common.data :as d]
[app.common.types.fills.impl :as types.fills.impl] [app.common.types.fills.impl :as types.fills.impl]
[app.common.uuid :as uuid]
[app.render-wasm.api.fonts :as f] [app.render-wasm.api.fonts :as f]
[app.render-wasm.helpers :as h] [app.render-wasm.helpers :as h]
[app.render-wasm.mem :as mem] [app.render-wasm.mem :as mem]
[app.render-wasm.serializers :as sr] [app.render-wasm.serializers :as sr]
[app.render-wasm.wasm :as wasm])) [app.render-wasm.wasm :as wasm]))
(defn utf8->buffer [text] (def ^:const PARAGRAPH-ATTR-U8-SIZE 44)
(def ^:const LEAF-ATTR-U8-SIZE 56)
(defn- encode-text
"Into an UTF8 buffer. Returns an ArrayBuffer instance"
[text]
(let [encoder (js/TextEncoder.)] (let [encoder (js/TextEncoder.)]
(.encode encoder text))) (.encode encoder text)))
(defn set-text-leaf-fills (defn- write-leaf-fills
[fills current-offset dview] [offset dview fills]
(reduce (fn [offset fill] (reduce (fn [offset fill]
(let [opacity (or (:fill-opacity fill) 1.0) (let [opacity (get fill :fill-opacity 1.0)
color (:fill-color fill) color (get fill :fill-color)
gradient (:fill-color-gradient fill) gradient (get fill :fill-color-gradient)
image (:fill-image fill)] image (get fill :fill-image)]
(cond (cond
(some? color) (some? color)
@@ -33,115 +40,119 @@
(types.fills.impl/write-gradient-fill offset dview opacity gradient) (types.fills.impl/write-gradient-fill offset dview opacity gradient)
(some? image) (some? image)
(types.fills.impl/write-image-fill offset dview opacity image)) (types.fills.impl/write-image-fill offset dview opacity image))))
(+ offset types.fills.impl/FILL-U8-SIZE))) offset
current-offset
fills)) fills))
(defn total-fills-count (defn- get-total-fills
[leaves] [leaves]
(reduce #(+ %1 (count (:fills %2))) 0 leaves)) (reduce #(+ %1 (count (:fills %2))) 0 leaves))
(defn- write-paragraph
[offset dview paragraph]
(let [text-align (sr/translate-text-align (get paragraph :text-align))
text-direction (sr/translate-text-direction (get paragraph :text-direction))
text-decoration (sr/translate-text-decoration (get paragraph :text-decoration))
text-transform (sr/translate-text-transform (get paragraph :text-transform))
line-height (get paragraph :line-height)
letter-spacing (get paragraph :letter-spacing)
typography-ref-file (get paragraph :typography-ref-file)
typography-ref-id (get paragraph :typography-ref-id)]
(-> offset
(mem/write-u8 dview text-align)
(mem/write-u8 dview text-direction)
(mem/write-u8 dview text-decoration)
(mem/write-u8 dview text-transform)
(mem/write-f32 dview line-height)
(mem/write-f32 dview letter-spacing)
(mem/write-uuid dview (d/nilv typography-ref-file uuid/zero))
(mem/write-uuid dview (d/nilv typography-ref-id uuid/zero))
(mem/assert-written offset PARAGRAPH-ATTR-U8-SIZE))))
(defn- write-leaves
[offset dview leaves paragraph]
(reduce (fn [offset leaf]
(let [font-style (sr/translate-font-style (get leaf :font-style))
font-size (get leaf :font-size)
font-weight (get leaf :font-weight)
font-id (f/normalize-font-id (get leaf :font-id))
font-family (hash (get leaf :font-family))
text-buffer (encode-text (get leaf :text))
text-length (mem/size text-buffer)
fills (get leaf :fills)
total-fills (count fills)
font-variant-id
(get leaf :font-variant-id)
font-variant-id
(if (uuid? font-variant-id)
font-variant-id
uuid/zero)
text-decoration
(or (sr/translate-text-decoration (:text-decoration leaf))
(sr/translate-text-decoration (:text-decoration paragraph))
(sr/translate-text-decoration "none"))
text-transform
(or (sr/translate-text-transform (:text-transform leaf))
(sr/translate-text-transform (:text-transform paragraph))
(sr/translate-text-transform "none"))]
(-> offset
(mem/write-u8 dview font-style)
(mem/write-u8 dview text-decoration)
(mem/write-u8 dview text-transform)
(+ 1) ;;padding
(mem/write-f32 dview font-size)
(mem/write-u32 dview font-weight)
(mem/write-uuid dview font-id)
(mem/write-i32 dview font-family)
(mem/write-uuid dview (d/nilv font-variant-id uuid/zero))
(mem/write-i32 dview text-length)
(mem/write-i32 dview total-fills)
(mem/assert-written offset LEAF-ATTR-U8-SIZE)
(write-leaf-fills dview fills))))
offset
leaves))
(defn write-shape-text (defn write-shape-text
;; buffer has the following format: ;; buffer has the following format:
;; [<num-leaves> <paragraph_attributes> <leaves_attributes> <text>] ;; [<num-leaves> <paragraph_attributes> <leaves_attributes> <text>]
[leaves paragraph text] [leaves paragraph text]
(let [le? true (let [num-leaves (count leaves)
num-leaves (count leaves) fills-size (* types.fills.impl/FILL-U8-SIZE
paragraph-attr-size 48 (get-total-fills leaves))
total-fills (total-fills-count leaves) metadata-size (+ PARAGRAPH-ATTR-U8-SIZE
total-fills-size (* types.fills.impl/FILL-U8-SIZE total-fills) (* num-leaves LEAF-ATTR-U8-SIZE)
leaf-attr-size 56 fills-size)
metadata-size (+ paragraph-attr-size (* num-leaves leaf-attr-size) total-fills-size)
text-buffer (utf8->buffer text)
text-size (.-byteLength text-buffer)
buffer (js/ArrayBuffer. (+ metadata-size text-size))
dview (js/DataView. buffer)]
(.setUint32 dview 0 num-leaves le?) text-buffer (encode-text text)
text-size (mem/size text-buffer)
;; Serialize paragraph attributes total-size (+ 4 metadata-size text-size)
(let [text-align (sr/serialize-text-align (:text-align paragraph)) heapu8 (mem/get-heap-u8)
text-direction (sr/serialize-text-direction (:text-direction paragraph)) dview (mem/get-data-view)
text-decoration (sr/serialize-text-decoration (:text-decoration paragraph)) offset (mem/alloc total-size)]
text-transform (sr/serialize-text-transform (:text-transform paragraph))
line-height (:line-height paragraph)
letter-spacing (:letter-spacing paragraph)
typography-ref-file (sr/serialize-uuid (:typography-ref-file paragraph))
typography-ref-id (sr/serialize-uuid (:typography-ref-id paragraph))]
(.setUint8 dview 4 text-align le?) (-> offset
(.setUint8 dview 5 text-direction le?) (mem/write-u32 dview num-leaves)
(.setUint8 dview 6 text-decoration le?) (write-paragraph dview paragraph)
(.setUint8 dview 7 text-transform le?) (write-leaves dview leaves paragraph)
(mem/write-buffer heapu8 text-buffer))
(.setFloat32 dview 8 line-height le?) (h/call wasm/internal-module "_set_shape_text_content")))
(.setFloat32 dview 12 letter-spacing le?)
(.setUint32 dview 16 (aget typography-ref-file 0) le?)
(.setUint32 dview 20 (aget typography-ref-file 1) le?)
(.setUint32 dview 24 (aget typography-ref-file 2) le?)
(.setInt32 dview 28 (aget typography-ref-file 3) le?)
(.setUint32 dview 32 (aget typography-ref-id 0) le?)
(.setUint32 dview 36 (aget typography-ref-id 1) le?)
(.setUint32 dview 40 (aget typography-ref-id 2) le?)
(.setInt32 dview 44 (aget typography-ref-id 3) le?))
;; Serialize leaves attributes
(loop [index 0 offset paragraph-attr-size]
(when (< index num-leaves)
(let [leaf (nth leaves index)
font-style (f/serialize-font-style (:font-style leaf))
font-size (:font-size leaf)
font-weight (:font-weight leaf)
font-id (f/serialize-font-id (:font-id leaf))
font-family (hash (:font-family leaf))
font-variant-id (sr/serialize-uuid (:font-variant-id leaf))
leaf-text-decoration (or (sr/serialize-text-decoration (:text-decoration leaf)) (sr/serialize-text-decoration (:text-decoration paragraph)))
leaf-text-transform (or (sr/serialize-text-transform (:text-transform leaf)) (sr/serialize-text-transform (:text-transform paragraph)))
text-buffer (utf8->buffer (:text leaf))
text-length (.-byteLength text-buffer)
fills (:fills leaf)
total-fills (count fills)]
(.setUint8 dview offset font-style le?)
(.setUint8 dview (+ offset 1) leaf-text-decoration le?)
(.setUint8 dview (+ offset 2) leaf-text-transform le?)
(.setFloat32 dview (+ offset 4) font-size le?)
(.setUint32 dview (+ offset 8) font-weight le?)
(.setUint32 dview (+ offset 12) (aget font-id 0) le?)
(.setUint32 dview (+ offset 16) (aget font-id 1) le?)
(.setUint32 dview (+ offset 20) (aget font-id 2) le?)
(.setInt32 dview (+ offset 24) (aget font-id 3) le?)
(.setInt32 dview (+ offset 28) font-family le?)
(.setUint32 dview (+ offset 32) (aget font-variant-id 0) le?)
(.setUint32 dview (+ offset 36) (aget font-variant-id 1) le?)
(.setUint32 dview (+ offset 40) (aget font-variant-id 2) le?)
(.setInt32 dview (+ offset 44) (aget font-variant-id 3) le?)
(.setInt32 dview (+ offset 48) text-length le?)
(.setInt32 dview (+ offset 52) total-fills le?)
(let [new-offset (set-text-leaf-fills fills (+ offset leaf-attr-size) dview)]
(recur (inc index) new-offset)))))
;; Add text content to buffer
(let [text-offset metadata-size
buffer-u8 (js/Uint8Array. buffer)]
(.set buffer-u8 (js/Uint8Array. text-buffer) text-offset))
;; Allocate memory and set buffer
(let [total-size (.-byteLength buffer)
metadata-offset (mem/alloc total-size)
heap (mem/get-heap-u8)]
(.set heap (js/Uint8Array. buffer) metadata-offset)))
(h/call wasm/internal-module "_set_shape_text_content"))
(def ^:private emoji-pattern #"[\uD83C-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27BF]") (def ^:private emoji-pattern #"[\uD83C-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27BF]")
@@ -199,10 +210,20 @@
(defn contains-emoji? [text] (defn contains-emoji? [text]
(boolean (some #(re-find emoji-pattern %) (seq text)))) (boolean (some #(re-find emoji-pattern %) (seq text))))
(defn get-languages [text] (defn collect-used-languages
[used text]
(reduce-kv (fn [result lang pattern] (reduce-kv (fn [result lang pattern]
(if (re-find pattern text) (cond
;; Skip regex operation if we already know that
;; langage is present
(contains? result lang)
result
(re-find pattern text)
(conj result lang) (conj result lang)
:else
result)) result))
#{} used
unicode-ranges)) unicode-ranges))

View File

@@ -67,12 +67,6 @@
[heap offset size] [heap offset size]
(.slice ^js heap offset (+ offset size))) (.slice ^js heap offset (+ offset size)))
(defn view
"Returns a new typed array on the same ArrayBuffer store and with the
same element types as for this typed array."
[heap offset size]
(.subarray ^js heap offset (+ offset size)))
(defn get-data-view (defn get-data-view
"Returns a heap wrapped in a DataView for surgical write operations" "Returns a heap wrapped in a DataView for surgical write operations"
[] []
@@ -80,10 +74,64 @@
(defn write-u8 (defn write-u8
"Write unsigned int8. Expects a DataView instance" "Write unsigned int8. Expects a DataView instance"
[target offset value] [offset target value]
(buf/write-byte target offset value)) (buf/write-u8 target offset value)
(+ offset 1))
(defn write-f32 (defn write-f32
"Write float32. Expects a DataView instance" "Write float32. Expects a DataView instance"
[target offset value] [offset target value]
(buf/write-float target offset value)) (buf/write-f32 target offset value)
(+ offset 4))
(defn write-i32
"Write int32. Expects a DataView instance"
[offset target value]
(buf/write-i32 target offset value)
(+ offset 4))
(defn write-u32
"Write int32. Expects a DataView instance"
[offset target value]
(buf/write-i32 target offset value)
(+ offset 4))
(defn write-bool
"Write int32. Expects a DataView instance"
[offset target value]
(buf/write-bool target offset value)
(+ offset 1))
(defn write-uuid
"Write uuid. Expects a DataView instance"
[offset target value]
(buf/write-uuid target offset value)
(+ offset 16))
(defn write-buffer
[offset target value]
(assert (instance? js/Uint8Array target) "target should be u8 addressable heap")
(let [value (cond
(instance? js/ArrayBuffer value)
(new js/Uint8Array. value)
(instance? js/Uint8Array value)
value
:else
(throw (js/Error. "unexpected type")))]
(.set ^js target value offset)
(+ offset (.-byteLength value))))
(defn assert-written
[final-offset prev-offset expected]
(assert (= expected (- final-offset prev-offset))
(str "expected to be written " expected " but finally writted " (- final-offset prev-offset)))
final-offset)
(defn size
"Get buffer size"
[o]
(.-byteLength ^js o))

View File

@@ -227,7 +227,8 @@
(case value (case value
:fill 0 :fill 0
:fix 1 :fix 1
:auto 2)) :auto 2
1))
(defn translate-align-self (defn translate-align-self
[value] [value]
@@ -270,26 +271,51 @@
:auto-height 2 :auto-height 2
0)) 0))
(defn- serialize-enum (defn translate-vertical-align
[value enum-map]
(get enum-map value 0))
(defn serialize-vertical-align
[vertical-align] [vertical-align]
(serialize-enum vertical-align {"top" 0 "center" 1 "bottom" 2})) (case vertical-align
"top" 0
"center" 1
"bottom" 2
0))
(defn serialize-text-align (defn translate-text-align
[text-align] [text-align]
(serialize-enum text-align {"left" 0 "center" 1 "right" 2 "justify" 3})) (case text-align
"left" 0
"center" 1
"right" 2
"justify" 3
0))
(defn serialize-text-transform (defn translate-text-transform
[text-transform] [text-transform]
(serialize-enum text-transform {"none" 0 "uppercase" 1 "lowercase" 2 "capitalize" 3})) (case text-transform
"none" 0
"uppercase" 1
"lowercase" 2
"capitalize" 3
nil))
(defn serialize-text-decoration (defn translate-text-decoration
[text-decoration] [text-decoration]
(serialize-enum text-decoration {"none" 0 "underline" 1 "line-through" 2 "overline" 3})) (case text-decoration
"none" 0
"underline" 1
"line-through" 2
"overline" 3
nil))
(defn serialize-text-direction (defn translate-text-direction
[text-direction] [text-direction]
(serialize-enum text-direction {"ltr" 0 "rtl" 1})) (case text-direction
"ltr" 0
"rtl" 1))
(defn translate-font-style
[font-style]
(case font-style
"normal" 0
"regular" 0
"italic" 1
0))