mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
Merge pull request #7041 from penpot/alotor-wasm-bools
✨ Add wasm boolean calculations
This commit is contained in:
@@ -20,15 +20,15 @@
|
|||||||
(def ^:const MAX-GRADIENT-STOPS 16)
|
(def ^:const MAX-GRADIENT-STOPS 16)
|
||||||
(def ^:const MAX-FILLS 8)
|
(def ^:const MAX-FILLS 8)
|
||||||
|
|
||||||
(def ^:const GRADIENT-STOP-SIZE 8)
|
(def ^:const GRADIENT-STOP-U8-SIZE 8)
|
||||||
(def ^:const GRADIENT-BYTE-SIZE 156)
|
(def ^:const GRADIENT-U8-SIZE 156)
|
||||||
(def ^:const SOLID-BYTE-SIZE 4)
|
(def ^:const SOLID-U8-SIZE 4)
|
||||||
(def ^:const IMAGE-BYTE-SIZE 36)
|
(def ^:const IMAGE-U8-SIZE 36)
|
||||||
(def ^:const METADATA-BYTE-SIZE 36)
|
(def ^:const METADATA-U8-SIZE 36)
|
||||||
(def ^:const FILL-BYTE-SIZE
|
(def ^:const FILL-U8-SIZE
|
||||||
(+ 4 (mth/max GRADIENT-BYTE-SIZE
|
(+ 4 (mth/max GRADIENT-U8-SIZE
|
||||||
IMAGE-BYTE-SIZE
|
IMAGE-U8-SIZE
|
||||||
SOLID-BYTE-SIZE)))
|
SOLID-U8-SIZE)))
|
||||||
|
|
||||||
(def ^:private xf:take-stops
|
(def ^:private xf:take-stops
|
||||||
(take MAX-GRADIENT-STOPS))
|
(take MAX-GRADIENT-STOPS))
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
(buf/write-int buffer (+ offset 4)
|
(buf/write-int buffer (+ offset 4)
|
||||||
(-> (hex->rgb color)
|
(-> (hex->rgb color)
|
||||||
(rgb->rgba opacity)))
|
(rgb->rgba opacity)))
|
||||||
(+ offset FILL-BYTE-SIZE))
|
(+ offset FILL-U8-SIZE))
|
||||||
|
|
||||||
(defn write-gradient-fill
|
(defn write-gradient-fill
|
||||||
[offset buffer opacity gradient]
|
[offset buffer opacity gradient]
|
||||||
@@ -114,8 +114,8 @@
|
|||||||
(buf/write-int buffer (+ offset' 0) color)
|
(buf/write-int buffer (+ offset' 0) color)
|
||||||
(buf/write-float buffer (+ offset' 4) (:offset stop))
|
(buf/write-float buffer (+ offset' 4) (:offset stop))
|
||||||
(recur (rest stops)
|
(recur (rest stops)
|
||||||
(+ offset' GRADIENT-STOP-SIZE)))
|
(+ offset' GRADIENT-STOP-U8-SIZE)))
|
||||||
(+ offset FILL-BYTE-SIZE)))))
|
(+ offset FILL-U8-SIZE)))))
|
||||||
|
|
||||||
(defn write-image-fill
|
(defn write-image-fill
|
||||||
[offset buffer opacity image]
|
[offset buffer opacity image]
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
(buf/write-short buffer (+ offset 22) 0) ;; 2-byte padding (reserved for future use)
|
(buf/write-short buffer (+ offset 22) 0) ;; 2-byte padding (reserved for future use)
|
||||||
(buf/write-int buffer (+ offset 24) image-width)
|
(buf/write-int buffer (+ offset 24) image-width)
|
||||||
(buf/write-int buffer (+ offset 28) image-height)
|
(buf/write-int buffer (+ offset 28) image-height)
|
||||||
(+ offset FILL-BYTE-SIZE)))
|
(+ offset FILL-U8-SIZE)))
|
||||||
|
|
||||||
(defn- write-metadata
|
(defn- write-metadata
|
||||||
[offset buffer fill]
|
[offset buffer fill]
|
||||||
@@ -169,8 +169,8 @@
|
|||||||
(defn- read-fill
|
(defn- read-fill
|
||||||
"Read segment from binary buffer at specified index"
|
"Read segment from binary buffer at specified index"
|
||||||
[dbuffer mbuffer index]
|
[dbuffer mbuffer index]
|
||||||
(let [doffset (+ 4 (* index FILL-BYTE-SIZE))
|
(let [doffset (+ 4 (* index FILL-U8-SIZE))
|
||||||
moffset (* index METADATA-BYTE-SIZE)
|
moffset (* index METADATA-U8-SIZE)
|
||||||
type (buf/read-byte dbuffer doffset)
|
type (buf/read-byte dbuffer doffset)
|
||||||
refs? (buf/read-bool mbuffer (+ moffset 0))
|
refs? (buf/read-bool mbuffer (+ moffset 0))
|
||||||
fill (case type
|
fill (case type
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
result []]
|
result []]
|
||||||
(if (< index stops)
|
(if (< index stops)
|
||||||
(recur (inc index)
|
(recur (inc index)
|
||||||
(conj result (read-stop dbuffer (+ doffset 32 (* GRADIENT-STOP-SIZE index)))))
|
(conj result (read-stop dbuffer (+ doffset 32 (* GRADIENT-STOP-U8-SIZE index)))))
|
||||||
result))]
|
result))]
|
||||||
|
|
||||||
{:fill-opacity opacity
|
{:fill-opacity opacity
|
||||||
@@ -410,8 +410,8 @@
|
|||||||
[fills]
|
[fills]
|
||||||
(let [fills (into [] xf:take-fills fills)
|
(let [fills (into [] xf:take-fills fills)
|
||||||
total (count fills)
|
total (count fills)
|
||||||
dbuffer (buf/allocate (+ 4 (* MAX-FILLS FILL-BYTE-SIZE)))
|
dbuffer (buf/allocate (+ 4 (* MAX-FILLS FILL-U8-SIZE)))
|
||||||
mbuffer (buf/allocate (* total METADATA-BYTE-SIZE))]
|
mbuffer (buf/allocate (* total METADATA-U8-SIZE))]
|
||||||
|
|
||||||
(buf/write-byte dbuffer 0 total)
|
(buf/write-byte dbuffer 0 total)
|
||||||
|
|
||||||
@@ -419,8 +419,8 @@
|
|||||||
image-ids #{}]
|
image-ids #{}]
|
||||||
(if (< index total)
|
(if (< index total)
|
||||||
(let [fill (nth fills index)
|
(let [fill (nth fills index)
|
||||||
doffset (+ 4 (* index FILL-BYTE-SIZE))
|
doffset (+ 4 (* index FILL-U8-SIZE))
|
||||||
moffset (* index METADATA-BYTE-SIZE)
|
moffset (* index METADATA-U8-SIZE)
|
||||||
opacity (get fill :fill-opacity 1)]
|
opacity (get fill :fill-opacity 1)]
|
||||||
|
|
||||||
(if-let [color (get fill :fill-color)]
|
(if-let [color (get fill :fill-color)]
|
||||||
|
|||||||
@@ -216,12 +216,19 @@
|
|||||||
:content (vec contents)
|
:content (vec contents)
|
||||||
:cause cause)))))
|
:cause cause)))))
|
||||||
|
|
||||||
|
(def wasm:calc-bool-content
|
||||||
|
"A overwrite point for setup a WASM version of the `calc-bool-content*` function"
|
||||||
|
nil)
|
||||||
|
|
||||||
(defn calc-bool-content
|
(defn calc-bool-content
|
||||||
"Calculate the boolean content from shape and objects. Returns a
|
"Calculate the boolean content from shape and objects. Returns a
|
||||||
packed PathData instance"
|
packed PathData instance"
|
||||||
[shape objects]
|
[shape objects]
|
||||||
(-> (calc-bool-content* shape objects)
|
(let [content (if (fn? wasm:calc-bool-content)
|
||||||
(impl/path-data)))
|
(wasm:calc-bool-content (get shape :bool-type)
|
||||||
|
(get shape :shapes))
|
||||||
|
(calc-bool-content* shape objects))]
|
||||||
|
(impl/path-data content)))
|
||||||
|
|
||||||
(defn update-bool-shape
|
(defn update-bool-shape
|
||||||
"Calculates the selrect+points for the boolean shape"
|
"Calculates the selrect+points for the boolean shape"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
#?(:clj (set! *warn-on-reflection* true))
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
(def ^:const SEGMENT-BYTE-SIZE 28)
|
(def ^:const SEGMENT-U8-SIZE 28)
|
||||||
|
|
||||||
(defprotocol IPathData
|
(defprotocol IPathData
|
||||||
(-write-to [_ buffer offset] "write the content to the specified buffer")
|
(-write-to [_ buffer offset] "write the content to the specified buffer")
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
f (dm/get-prop m :f)]
|
f (dm/get-prop m :f)]
|
||||||
(loop [index 0]
|
(loop [index 0]
|
||||||
(when (< index size)
|
(when (< index size)
|
||||||
(let [offset (* index SEGMENT-BYTE-SIZE)]
|
(let [offset (* index SEGMENT-U8-SIZE)]
|
||||||
(impl-transform-segment buffer offset a b c d e f)
|
(impl-transform-segment buffer offset a b c d e f)
|
||||||
(recur (inc index)))))))
|
(recur (inc index)))))))
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
(loop [index 0
|
(loop [index 0
|
||||||
result (transient initial)]
|
result (transient initial)]
|
||||||
(if (< index size)
|
(if (< index size)
|
||||||
(let [offset (* index SEGMENT-BYTE-SIZE)
|
(let [offset (* index SEGMENT-U8-SIZE)
|
||||||
type (buf/read-short buffer offset)
|
type (buf/read-short buffer offset)
|
||||||
c1x (buf/read-float buffer (+ offset 4))
|
c1x (buf/read-float buffer (+ offset 4))
|
||||||
c1y (buf/read-float buffer (+ offset 8))
|
c1y (buf/read-float buffer (+ offset 8))
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
(loop [index 0
|
(loop [index 0
|
||||||
result initial]
|
result initial]
|
||||||
(if (< index size)
|
(if (< index size)
|
||||||
(let [offset (* index SEGMENT-BYTE-SIZE)
|
(let [offset (* index SEGMENT-U8-SIZE)
|
||||||
type (buf/read-short buffer offset)
|
type (buf/read-short buffer offset)
|
||||||
c1x (buf/read-float buffer (+ offset 4))
|
c1x (buf/read-float buffer (+ offset 4))
|
||||||
c1y (buf/read-float buffer (+ offset 8))
|
c1y (buf/read-float buffer (+ offset 8))
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
|
|
||||||
(defn impl-lookup
|
(defn impl-lookup
|
||||||
[buffer index f]
|
[buffer index f]
|
||||||
(let [offset (* index SEGMENT-BYTE-SIZE)
|
(let [offset (* index SEGMENT-U8-SIZE)
|
||||||
type (buf/read-short buffer offset)
|
type (buf/read-short buffer offset)
|
||||||
c1x (buf/read-float buffer (+ offset 4))
|
c1x (buf/read-float buffer (+ offset 4))
|
||||||
c1y (buf/read-float buffer (+ offset 8))
|
c1y (buf/read-float buffer (+ offset 8))
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
:cljs (StringBuffer.))]
|
:cljs (StringBuffer.))]
|
||||||
(loop [index 0]
|
(loop [index 0]
|
||||||
(when (< index size)
|
(when (< index size)
|
||||||
(let [offset (* index SEGMENT-BYTE-SIZE)
|
(let [offset (* index SEGMENT-U8-SIZE)
|
||||||
type (buf/read-short buffer offset)]
|
type (buf/read-short buffer offset)]
|
||||||
(to-string-segment* buffer offset type builder)
|
(to-string-segment* buffer offset type builder)
|
||||||
(recur (inc index)))))
|
(recur (inc index)))))
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
(defn- read-segment
|
(defn- read-segment
|
||||||
"Read segment from binary buffer at specified index"
|
"Read segment from binary buffer at specified index"
|
||||||
[buffer index]
|
[buffer index]
|
||||||
(let [offset (* index SEGMENT-BYTE-SIZE)
|
(let [offset (* index SEGMENT-U8-SIZE)
|
||||||
type (buf/read-short buffer offset)]
|
type (buf/read-short buffer offset)]
|
||||||
(case (long type)
|
(case (long type)
|
||||||
1 (let [x (buf/read-float buffer (+ offset 20))
|
1 (let [x (buf/read-float buffer (+ offset 20))
|
||||||
@@ -348,7 +348,7 @@
|
|||||||
|
|
||||||
IPathData
|
IPathData
|
||||||
(-get-byte-size [_]
|
(-get-byte-size [_]
|
||||||
(* size SEGMENT-BYTE-SIZE))
|
(* size SEGMENT-U8-SIZE))
|
||||||
|
|
||||||
(-write-to [_ _ _]
|
(-write-to [_ _ _]
|
||||||
(throw (RuntimeException. "not implemented"))))
|
(throw (RuntimeException. "not implemented"))))
|
||||||
@@ -576,13 +576,13 @@
|
|||||||
(cond
|
(cond
|
||||||
(instance? ByteBuffer buffer)
|
(instance? ByteBuffer buffer)
|
||||||
(let [size (.capacity ^ByteBuffer buffer)
|
(let [size (.capacity ^ByteBuffer buffer)
|
||||||
count (long (/ size SEGMENT-BYTE-SIZE))
|
count (long (/ size SEGMENT-U8-SIZE))
|
||||||
buffer (.order ^ByteBuffer buffer ByteOrder/LITTLE_ENDIAN)]
|
buffer (.order ^ByteBuffer buffer ByteOrder/LITTLE_ENDIAN)]
|
||||||
(PathData. count buffer nil))
|
(PathData. count buffer nil))
|
||||||
|
|
||||||
(bytes? buffer)
|
(bytes? buffer)
|
||||||
(let [size (alength ^bytes buffer)
|
(let [size (alength ^bytes buffer)
|
||||||
count (long (/ size SEGMENT-BYTE-SIZE))
|
count (long (/ size SEGMENT-U8-SIZE))
|
||||||
buffer (ByteBuffer/wrap buffer)]
|
buffer (ByteBuffer/wrap buffer)]
|
||||||
(PathData. count
|
(PathData. count
|
||||||
(.order buffer ByteOrder/LITTLE_ENDIAN)
|
(.order buffer ByteOrder/LITTLE_ENDIAN)
|
||||||
@@ -594,7 +594,7 @@
|
|||||||
(cond
|
(cond
|
||||||
(instance? js/ArrayBuffer buffer)
|
(instance? js/ArrayBuffer buffer)
|
||||||
(let [size (.-byteLength buffer)
|
(let [size (.-byteLength buffer)
|
||||||
count (long (/ size SEGMENT-BYTE-SIZE))]
|
count (long (/ size SEGMENT-U8-SIZE))]
|
||||||
(PathData. count
|
(PathData. count
|
||||||
(js/DataView. buffer)
|
(js/DataView. buffer)
|
||||||
(weak-map/create)
|
(weak-map/create)
|
||||||
@@ -603,12 +603,15 @@
|
|||||||
(instance? js/DataView buffer)
|
(instance? js/DataView buffer)
|
||||||
(let [buffer' (.-buffer ^js/DataView buffer)
|
(let [buffer' (.-buffer ^js/DataView buffer)
|
||||||
size (.-byteLength ^js/ArrayBuffer buffer')
|
size (.-byteLength ^js/ArrayBuffer buffer')
|
||||||
count (long (/ size SEGMENT-BYTE-SIZE))]
|
count (long (/ size SEGMENT-U8-SIZE))]
|
||||||
(PathData. count buffer (weak-map/create) nil))
|
(PathData. count buffer (weak-map/create) nil))
|
||||||
|
|
||||||
(instance? js/Uint8Array buffer)
|
(instance? js/Uint8Array buffer)
|
||||||
(from-bytes (.-buffer buffer))
|
(from-bytes (.-buffer buffer))
|
||||||
|
|
||||||
|
(instance? js/Uint32Array buffer)
|
||||||
|
(from-bytes (.-buffer buffer))
|
||||||
|
|
||||||
(instance? js/Int8Array buffer)
|
(instance? js/Int8Array buffer)
|
||||||
(from-bytes (.-buffer buffer))
|
(from-bytes (.-buffer buffer))
|
||||||
|
|
||||||
@@ -624,11 +627,11 @@
|
|||||||
(assert (check-plain-content segments))
|
(assert (check-plain-content segments))
|
||||||
|
|
||||||
(let [total (count segments)
|
(let [total (count segments)
|
||||||
buffer (buf/allocate (* total SEGMENT-BYTE-SIZE))]
|
buffer (buf/allocate (* total SEGMENT-U8-SIZE))]
|
||||||
(loop [index 0]
|
(loop [index 0]
|
||||||
(when (< index total)
|
(when (< index total)
|
||||||
(let [segment (nth segments index)
|
(let [segment (nth segments index)
|
||||||
offset (* index SEGMENT-BYTE-SIZE)]
|
offset (* index SEGMENT-U8-SIZE)]
|
||||||
(case (get segment :command)
|
(case (get segment :command)
|
||||||
:move-to
|
:move-to
|
||||||
(let [params (get segment :params)
|
(let [params (get segment :params)
|
||||||
|
|||||||
@@ -6,15 +6,24 @@
|
|||||||
|
|
||||||
(ns app.main.data.workspace.path.shapes-to-path
|
(ns app.main.data.workspace.path.shapes-to-path
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.files.changes-builder :as pcb]
|
[app.common.files.changes-builder :as pcb]
|
||||||
[app.common.files.helpers :as cph]
|
[app.common.files.helpers :as cph]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
[app.common.types.path :as path]
|
[app.common.types.path :as path]
|
||||||
|
[app.common.types.text :as txt]
|
||||||
[app.main.data.changes :as dch]
|
[app.main.data.changes :as dch]
|
||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
|
[app.main.features :as features]
|
||||||
|
[app.render-wasm.api :as wasm.api]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[potok.v2.core :as ptk]))
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
|
(def ^:private dissoc-attrs
|
||||||
|
[:x :y :width :height
|
||||||
|
:rx :ry :r1 :r2 :r3 :r4
|
||||||
|
:metadata])
|
||||||
|
|
||||||
(defn convert-selected-to-path
|
(defn convert-selected-to-path
|
||||||
([]
|
([]
|
||||||
(convert-selected-to-path nil))
|
(convert-selected-to-path nil))
|
||||||
@@ -22,21 +31,53 @@
|
|||||||
(ptk/reify ::convert-selected-to-path
|
(ptk/reify ::convert-selected-to-path
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [it state _]
|
(watch [it state _]
|
||||||
(let [page-id (:current-page-id state)
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
objects (dsh/lookup-page-objects state)
|
(let [page-id (:current-page-id state)
|
||||||
selected (->> (or ids (dsh/lookup-selected state))
|
objects (dsh/lookup-page-objects state)
|
||||||
(remove #(ctn/has-any-copy-parent? objects (get objects %))))
|
selected
|
||||||
|
(->> (or ids (dsh/lookup-selected state))
|
||||||
|
(remove #(ctn/has-any-copy-parent? objects (get objects %))))
|
||||||
|
|
||||||
children-ids
|
children-ids
|
||||||
(into #{}
|
(into #{}
|
||||||
(mapcat #(cph/get-children-ids objects %))
|
(mapcat #(cph/get-children-ids objects %))
|
||||||
selected)
|
selected)
|
||||||
|
|
||||||
changes
|
changes
|
||||||
(-> (pcb/empty-changes it page-id)
|
(-> (pcb/empty-changes it page-id)
|
||||||
(pcb/with-objects objects)
|
(pcb/with-objects objects)
|
||||||
;; FIXME: use with-objects? true
|
(pcb/update-shapes
|
||||||
(pcb/update-shapes selected #(path/convert-to-path % objects))
|
selected
|
||||||
(pcb/remove-objects children-ids))]
|
(fn [shape]
|
||||||
|
(let [content (wasm.api/shape-to-path (:id shape))]
|
||||||
|
(-> shape
|
||||||
|
(assoc :type :path)
|
||||||
|
(cond-> (cph/text-shape? shape)
|
||||||
|
(assoc :fills
|
||||||
|
(->> (txt/node-seq txt/is-text-node? (:content shape))
|
||||||
|
(map :fills)
|
||||||
|
(first))))
|
||||||
|
(cond-> (cph/image-shape? shape)
|
||||||
|
(assoc :fill-image (get shape :metadata)))
|
||||||
|
(d/without-keys dissoc-attrs)
|
||||||
|
(path/update-geometry content)))))
|
||||||
|
(pcb/remove-objects children-ids))]
|
||||||
|
(rx/of (dch/commit-changes changes)))
|
||||||
|
|
||||||
(rx/of (dch/commit-changes changes)))))))
|
(let [page-id (:current-page-id state)
|
||||||
|
objects (dsh/lookup-page-objects state)
|
||||||
|
selected (->> (or ids (dsh/lookup-selected state))
|
||||||
|
(remove #(ctn/has-any-copy-parent? objects (get objects %))))
|
||||||
|
|
||||||
|
children-ids
|
||||||
|
(into #{}
|
||||||
|
(mapcat #(cph/get-children-ids objects %))
|
||||||
|
selected)
|
||||||
|
|
||||||
|
changes
|
||||||
|
(-> (pcb/empty-changes it page-id)
|
||||||
|
(pcb/with-objects objects)
|
||||||
|
(pcb/update-shapes selected path/convert-to-path {:with-objects? true})
|
||||||
|
(pcb/remove-objects children-ids))]
|
||||||
|
|
||||||
|
(rx/of (dch/commit-changes changes))))))))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
[app.main.data.workspace.bool :as dwb]
|
[app.main.data.workspace.bool :as dwb]
|
||||||
[app.main.data.workspace.path.shapes-to-path :as dwps]
|
[app.main.data.workspace.path.shapes-to-path :as dwps]
|
||||||
[app.main.data.workspace.shortcuts :as sc]
|
[app.main.data.workspace.shortcuts :as sc]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
|
||||||
@@ -36,8 +37,15 @@
|
|||||||
(and (= (count selected) 1)
|
(and (= (count selected) 1)
|
||||||
(not (contains? #{:group :bool} (:type (first selected)))))
|
(not (contains? #{:group :bool} (:type (first selected)))))
|
||||||
|
|
||||||
disabled-bool-btns (or (empty? selected) has-invalid-shapes? first-not-group-like?)
|
disabled-bool-btns
|
||||||
disabled-flatten (or (empty? selected) has-invalid-shapes?)
|
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||||
|
false
|
||||||
|
(or (empty? selected) has-invalid-shapes? first-not-group-like?))
|
||||||
|
|
||||||
|
disabled-flatten
|
||||||
|
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||||
|
false
|
||||||
|
(or (empty? selected) has-invalid-shapes?))
|
||||||
|
|
||||||
set-bool
|
set-bool
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
|||||||
@@ -71,8 +71,9 @@
|
|||||||
:code code
|
:code code
|
||||||
:icon icon
|
:icon icon
|
||||||
:permissions (into #{} (map str) permissions)})]
|
:permissions (into #{} (map str) permissions)})]
|
||||||
(when (sm/validate ::ctp/registry-entry manifest)
|
(if (sm/validate ::ctp/registry-entry manifest)
|
||||||
manifest)))
|
manifest
|
||||||
|
(.error js/console (clj->js (sm/explain ::ctp/registry-entry manifest))))))
|
||||||
|
|
||||||
(defn save-to-store
|
(defn save-to-store
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
(ns app.render-wasm
|
(ns app.render-wasm
|
||||||
"A WASM based render API"
|
"A WASM based render API"
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.types.path]
|
||||||
[app.common.types.shape :as shape]
|
[app.common.types.shape :as shape]
|
||||||
[app.render-wasm.api :as api]
|
[app.render-wasm.api :as api]
|
||||||
[app.render-wasm.shape :as wasm.shape]))
|
[app.render-wasm.shape :as wasm.shape]))
|
||||||
@@ -15,5 +16,8 @@
|
|||||||
|
|
||||||
(defn initialize
|
(defn initialize
|
||||||
[enabled?]
|
[enabled?]
|
||||||
|
(if enabled?
|
||||||
|
(set! app.common.types.path/wasm:calc-bool-content api/calculate-bool)
|
||||||
|
(set! app.common.types.path/wasm:calc-bool-content nil))
|
||||||
(set! app.common.types.shape/wasm-enabled? enabled?)
|
(set! app.common.types.shape/wasm-enabled? enabled?)
|
||||||
(set! app.common.types.shape/wasm-create-shape wasm.shape/create-shape))
|
(set! app.common.types.shape/wasm-create-shape wasm.shape/create-shape))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
[app.common.types.fills :as types.fills]
|
[app.common.types.fills :as types.fills]
|
||||||
[app.common.types.fills.impl :as types.fills.impl]
|
[app.common.types.fills.impl :as types.fills.impl]
|
||||||
[app.common.types.path :as path]
|
[app.common.types.path :as path]
|
||||||
|
[app.common.types.path.impl :as path.impl]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
@@ -38,9 +39,7 @@
|
|||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
;; (defonce internal-frame-id nil)
|
(def use-dpr? (contains? cf/flags :render-wasm-dpr))
|
||||||
;; (defonce wasm/internal-module #js {})
|
|
||||||
(defonce use-dpr? (contains? cf/flags :render-wasm-dpr))
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; List of common entry sizes.
|
;; List of common entry sizes.
|
||||||
@@ -48,29 +47,32 @@
|
|||||||
;; All of these entries are in bytes so we need to adjust
|
;; All of these entries are in bytes so we need to adjust
|
||||||
;; these values to work with TypedArrays of 32 bits.
|
;; these values to work with TypedArrays of 32 bits.
|
||||||
;;
|
;;
|
||||||
(def CHILD-ENTRY-SIZE 16)
|
(def ^:const UUID-U8-SIZE 16)
|
||||||
(def MODIFIER-ENTRY-SIZE 40)
|
(def ^:const UUID-U32-SIZE (/ UUID-U8-SIZE 4))
|
||||||
(def MODIFIER-ENTRY-TRANSFORM-OFFSET 16)
|
|
||||||
(def GRID-LAYOUT-ROW-ENTRY-SIZE 5)
|
(def ^:const MODIFIER-U8-SIZE 40)
|
||||||
(def GRID-LAYOUT-COLUMN-ENTRY-SIZE 5)
|
(def ^:const MODIFIER-TRANSFORM-U8-OFFSET-SIZE 16)
|
||||||
(def GRID-LAYOUT-CELL-ENTRY-SIZE 37)
|
|
||||||
|
(def ^:const GRID-LAYOUT-ROW-U8-SIZE 5)
|
||||||
|
(def ^:const GRID-LAYOUT-COLUMN-U8-SIZE 5)
|
||||||
|
(def ^:const GRID-LAYOUT-CELL-U8-SIZE 37)
|
||||||
|
|
||||||
(defn modifier-get-entries-size
|
(defn modifier-get-entries-size
|
||||||
"Returns the list of a modifier list in bytes"
|
"Returns the list of a modifier list in bytes"
|
||||||
[modifiers]
|
[modifiers]
|
||||||
(mem/get-list-size modifiers MODIFIER-ENTRY-SIZE))
|
(mem/get-list-size modifiers MODIFIER-U8-SIZE))
|
||||||
|
|
||||||
(defn grid-layout-get-row-entries-size
|
(defn grid-layout-get-row-entries-size
|
||||||
[rows]
|
[rows]
|
||||||
(mem/get-list-size rows GRID-LAYOUT-ROW-ENTRY-SIZE))
|
(mem/get-list-size rows GRID-LAYOUT-ROW-U8-SIZE))
|
||||||
|
|
||||||
(defn grid-layout-get-column-entries-size
|
(defn grid-layout-get-column-entries-size
|
||||||
[columns]
|
[columns]
|
||||||
(mem/get-list-size columns GRID-LAYOUT-COLUMN-ENTRY-SIZE))
|
(mem/get-list-size columns GRID-LAYOUT-COLUMN-U8-SIZE))
|
||||||
|
|
||||||
(defn grid-layout-get-cell-entries-size
|
(defn grid-layout-get-cell-entries-size
|
||||||
[cells]
|
[cells]
|
||||||
(mem/get-list-size cells GRID-LAYOUT-CELL-ENTRY-SIZE))
|
(mem/get-list-size cells GRID-LAYOUT-CELL-U8-SIZE))
|
||||||
|
|
||||||
(def dpr
|
(def dpr
|
||||||
(if use-dpr? (if (exists? js/window) js/window.devicePixelRatio 1.0) 1.0))
|
(if use-dpr? (if (exists? js/window) js/window.devicePixelRatio 1.0) 1.0))
|
||||||
@@ -169,25 +171,25 @@
|
|||||||
(h/call wasm/internal-module "_set_shape_rotation" rotation))
|
(h/call wasm/internal-module "_set_shape_rotation" rotation))
|
||||||
|
|
||||||
(defn set-shape-children
|
(defn set-shape-children
|
||||||
[shape-ids]
|
[children]
|
||||||
(let [num-shapes (count shape-ids)]
|
(let [heap (mem/get-heap-u32)
|
||||||
|
length (count children)]
|
||||||
(perf/begin-measure "set-shape-children")
|
(perf/begin-measure "set-shape-children")
|
||||||
(when (> num-shapes 0)
|
(when (pos? length)
|
||||||
(let [offset (mem/alloc-bytes (* CHILD-ENTRY-SIZE num-shapes))
|
(let [offset (mem/alloc->offset-32 (* UUID-U8-SIZE length))]
|
||||||
heap (mem/get-heap-u32)]
|
(reduce (fn [offset id]
|
||||||
|
(sr/heapu32-set-uuid id heap offset)
|
||||||
(loop [entries (seq shape-ids)
|
(+ offset UUID-U32-SIZE))
|
||||||
current-offset offset]
|
offset
|
||||||
(when-not (empty? entries)
|
children)))
|
||||||
(let [id (first entries)]
|
|
||||||
(sr/heapu32-set-uuid id heap (mem/ptr8->ptr32 current-offset))
|
|
||||||
(recur (rest entries) (+ current-offset CHILD-ENTRY-SIZE)))))))
|
|
||||||
|
|
||||||
(let [result (h/call wasm/internal-module "_set_children")]
|
(let [result (h/call wasm/internal-module "_set_children")]
|
||||||
(perf/end-measure "set-shape-children")
|
(perf/end-measure "set-shape-children")
|
||||||
result)))
|
result)))
|
||||||
|
|
||||||
(defn- get-string-length [string] (+ (count string) 1))
|
(defn- get-string-length
|
||||||
|
[string]
|
||||||
|
(+ (count string) 1))
|
||||||
|
|
||||||
(defn- fetch-image
|
(defn- fetch-image
|
||||||
[shape-id image-id]
|
[shape-id image-id]
|
||||||
@@ -205,7 +207,7 @@
|
|||||||
;; is possible (if image size modulo
|
;; is possible (if image size modulo
|
||||||
;; permits it)
|
;; permits it)
|
||||||
(let [size (.-byteLength image)
|
(let [size (.-byteLength image)
|
||||||
offset (mem/alloc-bytes size)
|
offset (mem/alloc size)
|
||||||
heap (mem/get-heap-u8)
|
heap (mem/get-heap-u8)
|
||||||
data (js/Uint8Array. image)]
|
data (js/Uint8Array. image)]
|
||||||
(.set heap data offset)
|
(.set heap data offset)
|
||||||
@@ -252,7 +254,7 @@
|
|||||||
(if (empty? fills)
|
(if (empty? fills)
|
||||||
(h/call wasm/internal-module "_clear_shape_fills")
|
(h/call wasm/internal-module "_clear_shape_fills")
|
||||||
(let [fills (types.fills/coerce fills)
|
(let [fills (types.fills/coerce fills)
|
||||||
offset (mem/alloc-bytes-32 (types.fills/get-byte-size fills))
|
offset (mem/alloc->offset-32 (types.fills/get-byte-size fills))
|
||||||
heap (mem/get-heap-u32)]
|
heap (mem/get-heap-u32)]
|
||||||
|
|
||||||
;; write fills to the heap
|
;; write fills to the heap
|
||||||
@@ -287,7 +289,7 @@
|
|||||||
style (-> stroke :stroke-style sr/translate-stroke-style)
|
style (-> stroke :stroke-style sr/translate-stroke-style)
|
||||||
cap-start (-> stroke :stroke-cap-start sr/translate-stroke-cap)
|
cap-start (-> stroke :stroke-cap-start sr/translate-stroke-cap)
|
||||||
cap-end (-> stroke :stroke-cap-end sr/translate-stroke-cap)
|
cap-end (-> stroke :stroke-cap-end sr/translate-stroke-cap)
|
||||||
offset (mem/alloc-bytes types.fills.impl/FILL-BYTE-SIZE)
|
offset (mem/alloc types.fills.impl/FILL-U8-SIZE)
|
||||||
heap (mem/get-heap-u8)
|
heap (mem/get-heap-u8)
|
||||||
dview (js/DataView. (.-buffer heap))]
|
dview (js/DataView. (.-buffer heap))]
|
||||||
(case align
|
(case align
|
||||||
@@ -324,7 +326,7 @@
|
|||||||
(merge style))
|
(merge style))
|
||||||
str (sr/serialize-path-attrs attrs)
|
str (sr/serialize-path-attrs attrs)
|
||||||
size (count str)
|
size (count str)
|
||||||
offset (mem/alloc-bytes size)]
|
offset (mem/alloc size)]
|
||||||
(h/call wasm/internal-module "stringToUTF8" str offset size)
|
(h/call wasm/internal-module "stringToUTF8" str offset size)
|
||||||
(h/call wasm/internal-module "_set_shape_path_attrs" (count attrs))))
|
(h/call wasm/internal-module "_set_shape_path_attrs" (count attrs))))
|
||||||
|
|
||||||
@@ -333,7 +335,7 @@
|
|||||||
[content]
|
[content]
|
||||||
(let [pdata (path/content content)
|
(let [pdata (path/content content)
|
||||||
size (path/get-byte-size content)
|
size (path/get-byte-size content)
|
||||||
offset (mem/alloc-bytes size)
|
offset (mem/alloc size)
|
||||||
heap (mem/get-heap-u8)]
|
heap (mem/get-heap-u8)]
|
||||||
(path/write-to pdata (.-buffer heap) offset)
|
(path/write-to pdata (.-buffer heap) offset)
|
||||||
(h/call wasm/internal-module "_set_shape_path_content")))
|
(h/call wasm/internal-module "_set_shape_path_content")))
|
||||||
@@ -341,7 +343,7 @@
|
|||||||
(defn set-shape-svg-raw-content
|
(defn set-shape-svg-raw-content
|
||||||
[content]
|
[content]
|
||||||
(let [size (get-string-length content)
|
(let [size (get-string-length content)
|
||||||
offset (mem/alloc-bytes size)]
|
offset (mem/alloc size)]
|
||||||
(h/call wasm/internal-module "stringToUTF8" content offset size)
|
(h/call wasm/internal-module "stringToUTF8" content offset size)
|
||||||
(h/call wasm/internal-module "_set_shape_svg_raw_content")))
|
(h/call wasm/internal-module "_set_shape_svg_raw_content")))
|
||||||
|
|
||||||
@@ -466,7 +468,7 @@
|
|||||||
(defn set-grid-layout-rows
|
(defn set-grid-layout-rows
|
||||||
[entries]
|
[entries]
|
||||||
(let [size (grid-layout-get-row-entries-size entries)
|
(let [size (grid-layout-get-row-entries-size entries)
|
||||||
offset (mem/alloc-bytes size)
|
offset (mem/alloc size)
|
||||||
|
|
||||||
heap
|
heap
|
||||||
(js/Uint8Array.
|
(js/Uint8Array.
|
||||||
@@ -479,13 +481,13 @@
|
|||||||
(let [{:keys [type value]} (first entries)]
|
(let [{:keys [type value]} (first entries)]
|
||||||
(.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ current-offset 0))
|
(.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ current-offset 0))
|
||||||
(.set heap (sr/f32->u8 value) (+ current-offset 1))
|
(.set heap (sr/f32->u8 value) (+ current-offset 1))
|
||||||
(recur (rest entries) (+ current-offset GRID-LAYOUT-ROW-ENTRY-SIZE)))))
|
(recur (rest entries) (+ current-offset GRID-LAYOUT-ROW-U8-SIZE)))))
|
||||||
(h/call wasm/internal-module "_set_grid_rows")))
|
(h/call wasm/internal-module "_set_grid_rows")))
|
||||||
|
|
||||||
(defn set-grid-layout-columns
|
(defn set-grid-layout-columns
|
||||||
[entries]
|
[entries]
|
||||||
(let [size (grid-layout-get-column-entries-size entries)
|
(let [size (grid-layout-get-column-entries-size entries)
|
||||||
offset (mem/alloc-bytes size)
|
offset (mem/alloc size)
|
||||||
|
|
||||||
heap
|
heap
|
||||||
(js/Uint8Array.
|
(js/Uint8Array.
|
||||||
@@ -498,14 +500,14 @@
|
|||||||
(let [{:keys [type value]} (first entries)]
|
(let [{:keys [type value]} (first entries)]
|
||||||
(.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ current-offset 0))
|
(.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ current-offset 0))
|
||||||
(.set heap (sr/f32->u8 value) (+ current-offset 1))
|
(.set heap (sr/f32->u8 value) (+ current-offset 1))
|
||||||
(recur (rest entries) (+ current-offset GRID-LAYOUT-COLUMN-ENTRY-SIZE)))))
|
(recur (rest entries) (+ current-offset GRID-LAYOUT-COLUMN-U8-SIZE)))))
|
||||||
(h/call wasm/internal-module "_set_grid_columns")))
|
(h/call wasm/internal-module "_set_grid_columns")))
|
||||||
|
|
||||||
(defn set-grid-layout-cells
|
(defn set-grid-layout-cells
|
||||||
[cells]
|
[cells]
|
||||||
(let [entries (vals cells)
|
(let [entries (vals cells)
|
||||||
size (grid-layout-get-cell-entries-size entries)
|
size (grid-layout-get-cell-entries-size entries)
|
||||||
offset (mem/alloc-bytes size)
|
offset (mem/alloc size)
|
||||||
|
|
||||||
heap
|
heap
|
||||||
(js/Uint8Array.
|
(js/Uint8Array.
|
||||||
@@ -551,7 +553,7 @@
|
|||||||
;; shape_id_d: [u8; 4],
|
;; shape_id_d: [u8; 4],
|
||||||
(.set heap (sr/uuid->u8 (or (-> cell :shapes first) uuid/zero)) (+ current-offset 21))
|
(.set heap (sr/uuid->u8 (or (-> cell :shapes first) uuid/zero)) (+ current-offset 21))
|
||||||
|
|
||||||
(recur (rest entries) (+ current-offset GRID-LAYOUT-CELL-ENTRY-SIZE)))))
|
(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")))
|
||||||
|
|
||||||
@@ -670,10 +672,10 @@
|
|||||||
([]
|
([]
|
||||||
(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)
|
heapf32 (mem/get-heap-f32)
|
||||||
width (aget heapf32 (mem/ptr8->ptr32 offset))
|
width (aget heapf32 (mem/->offset-32 offset))
|
||||||
height (aget heapf32 (mem/ptr8->ptr32 (+ offset 4)))
|
height (aget heapf32 (mem/->offset-32 (+ offset 4)))
|
||||||
max-width (aget heapf32 (mem/ptr8->ptr32 (+ offset 8)))]
|
max-width (aget heapf32 (mem/->offset-32 (+ offset 8)))]
|
||||||
(h/call wasm/internal-module "_free_bytes")
|
(mem/free)
|
||||||
{:width width :height height :max-width max-width})))
|
{:width width :height height :max-width max-width})))
|
||||||
|
|
||||||
(defn set-view-box
|
(defn set-view-box
|
||||||
@@ -714,6 +716,7 @@
|
|||||||
opacity (dm/get-prop shape :opacity)
|
opacity (dm/get-prop shape :opacity)
|
||||||
hidden (dm/get-prop shape :hidden)
|
hidden (dm/get-prop shape :hidden)
|
||||||
content (dm/get-prop shape :content)
|
content (dm/get-prop shape :content)
|
||||||
|
bool-type (dm/get-prop shape :bool-type)
|
||||||
grow-type (dm/get-prop shape :grow-type)
|
grow-type (dm/get-prop shape :grow-type)
|
||||||
blur (dm/get-prop shape :blur)
|
blur (dm/get-prop shape :blur)
|
||||||
corners (when (some? (dm/get-prop shape :r1))
|
corners (when (some? (dm/get-prop shape :r1))
|
||||||
@@ -740,6 +743,8 @@
|
|||||||
(set-masked masked))
|
(set-masked masked))
|
||||||
(when (some? blur)
|
(when (some? blur)
|
||||||
(set-shape-blur blur))
|
(set-shape-blur blur))
|
||||||
|
(when (= type :bool)
|
||||||
|
(set-shape-bool-type bool-type))
|
||||||
(when (and (some? content)
|
(when (and (some? content)
|
||||||
(or (= type :path)
|
(or (= type :path)
|
||||||
(= type :bool)))
|
(= type :bool)))
|
||||||
@@ -819,7 +824,7 @@
|
|||||||
|
|
||||||
(defn set-focus-mode
|
(defn set-focus-mode
|
||||||
[entries]
|
[entries]
|
||||||
(let [offset (mem/alloc-bytes-32 (* (count entries) 16))
|
(let [offset (mem/alloc->offset-32 (* (count entries) 16))
|
||||||
heapu32 (mem/get-heap-u32)]
|
heapu32 (mem/get-heap-u32)]
|
||||||
|
|
||||||
(loop [entries (seq entries)
|
(loop [entries (seq entries)
|
||||||
@@ -827,7 +832,7 @@
|
|||||||
(when-not (empty? entries)
|
(when-not (empty? entries)
|
||||||
(let [id (first entries)]
|
(let [id (first entries)]
|
||||||
(sr/heapu32-set-uuid id heapu32 current-offset)
|
(sr/heapu32-set-uuid id heapu32 current-offset)
|
||||||
(recur (rest entries) (+ current-offset (mem/ptr8->ptr32 16))))))
|
(recur (rest entries) (+ current-offset (mem/->offset-32 16))))))
|
||||||
|
|
||||||
(h/call wasm/internal-module "_set_focus_mode")
|
(h/call wasm/internal-module "_set_focus_mode")
|
||||||
(clear-drawing-cache)
|
(clear-drawing-cache)
|
||||||
@@ -836,7 +841,7 @@
|
|||||||
(defn set-structure-modifiers
|
(defn set-structure-modifiers
|
||||||
[entries]
|
[entries]
|
||||||
(when-not (empty? entries)
|
(when-not (empty? entries)
|
||||||
(let [offset (mem/alloc-bytes-32 (mem/get-list-size entries 44))
|
(let [offset (mem/alloc->offset-32 (mem/get-list-size entries 44))
|
||||||
heapu32 (mem/get-heap-u32)
|
heapu32 (mem/get-heap-u32)
|
||||||
heapf32 (mem/get-heap-f32)]
|
heapf32 (mem/get-heap-f32)]
|
||||||
(loop [entries (seq entries)
|
(loop [entries (seq entries)
|
||||||
@@ -854,7 +859,7 @@
|
|||||||
(defn propagate-modifiers
|
(defn propagate-modifiers
|
||||||
[entries pixel-precision]
|
[entries pixel-precision]
|
||||||
(when (d/not-empty? entries)
|
(when (d/not-empty? entries)
|
||||||
(let [offset (mem/alloc-bytes-32 (modifier-get-entries-size entries))
|
(let [offset (mem/alloc->offset-32 (modifier-get-entries-size entries))
|
||||||
heapf32 (mem/get-heap-f32)
|
heapf32 (mem/get-heap-f32)
|
||||||
heapu32 (mem/get-heap-u32)]
|
heapu32 (mem/get-heap-u32)]
|
||||||
|
|
||||||
@@ -863,24 +868,24 @@
|
|||||||
(when-not (empty? entries)
|
(when-not (empty? entries)
|
||||||
(let [{:keys [id transform]} (first entries)]
|
(let [{:keys [id transform]} (first entries)]
|
||||||
(sr/heapu32-set-uuid id heapu32 current-offset)
|
(sr/heapu32-set-uuid id heapu32 current-offset)
|
||||||
(sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-TRANSFORM-OFFSET)))
|
(sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/->offset-32 MODIFIER-TRANSFORM-U8-OFFSET-SIZE)))
|
||||||
(recur (rest entries) (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-SIZE))))))
|
(recur (rest entries) (+ current-offset (mem/->offset-32 MODIFIER-U8-SIZE))))))
|
||||||
|
|
||||||
(let [result-offset (h/call wasm/internal-module "_propagate_modifiers" pixel-precision)
|
(let [result-offset (h/call wasm/internal-module "_propagate_modifiers" pixel-precision)
|
||||||
heapf32 (mem/get-heap-f32)
|
heapf32 (mem/get-heap-f32)
|
||||||
heapu32 (mem/get-heap-u32)
|
heapu32 (mem/get-heap-u32)
|
||||||
len (aget heapu32 (mem/ptr8->ptr32 result-offset))
|
len (aget heapu32 (mem/->offset-32 result-offset))
|
||||||
result
|
result
|
||||||
(->> (range 0 len)
|
(->> (range 0 len)
|
||||||
(mapv #(dr/heap32->entry heapu32 heapf32 (mem/ptr8->ptr32 (+ result-offset 4 (* % MODIFIER-ENTRY-SIZE))))))]
|
(mapv #(dr/heap32->entry heapu32 heapf32 (mem/->offset-32 (+ result-offset 4 (* % MODIFIER-U8-SIZE))))))]
|
||||||
(h/call wasm/internal-module "_free_bytes")
|
(mem/free)
|
||||||
|
|
||||||
result))))
|
result))))
|
||||||
|
|
||||||
(defn propagate-apply
|
(defn propagate-apply
|
||||||
[entries pixel-precision]
|
[entries pixel-precision]
|
||||||
(when (d/not-empty? entries)
|
(when (d/not-empty? entries)
|
||||||
(let [offset (mem/alloc-bytes-32 (modifier-get-entries-size entries))
|
(let [offset (mem/alloc->offset-32 (modifier-get-entries-size entries))
|
||||||
heapf32 (mem/get-heap-f32)
|
heapf32 (mem/get-heap-f32)
|
||||||
heapu32 (mem/get-heap-u32)]
|
heapu32 (mem/get-heap-u32)]
|
||||||
|
|
||||||
@@ -889,25 +894,25 @@
|
|||||||
(when-not (empty? entries)
|
(when-not (empty? entries)
|
||||||
(let [{:keys [id transform]} (first entries)]
|
(let [{:keys [id transform]} (first entries)]
|
||||||
(sr/heapu32-set-uuid id heapu32 current-offset)
|
(sr/heapu32-set-uuid id heapu32 current-offset)
|
||||||
(sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-TRANSFORM-OFFSET)))
|
(sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/->offset-32 MODIFIER-TRANSFORM-U8-OFFSET-SIZE)))
|
||||||
(recur (rest entries) (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-SIZE))))))
|
(recur (rest entries) (+ current-offset (mem/->offset-32 MODIFIER-U8-SIZE))))))
|
||||||
|
|
||||||
(let [offset (h/call wasm/internal-module "_propagate_apply" pixel-precision)
|
(let [offset (h/call wasm/internal-module "_propagate_apply" pixel-precision)
|
||||||
heapf32 (mem/get-heap-f32)
|
heapf32 (mem/get-heap-f32)
|
||||||
width (aget heapf32 (mem/ptr8->ptr32 (+ offset 0)))
|
width (aget heapf32 (mem/->offset-32 (+ offset 0)))
|
||||||
height (aget heapf32 (mem/ptr8->ptr32 (+ offset 4)))
|
height (aget heapf32 (mem/->offset-32 (+ offset 4)))
|
||||||
cx (aget heapf32 (mem/ptr8->ptr32 (+ offset 8)))
|
cx (aget heapf32 (mem/->offset-32 (+ offset 8)))
|
||||||
cy (aget heapf32 (mem/ptr8->ptr32 (+ offset 12)))
|
cy (aget heapf32 (mem/->offset-32 (+ offset 12)))
|
||||||
|
|
||||||
a (aget heapf32 (mem/ptr8->ptr32 (+ offset 16)))
|
a (aget heapf32 (mem/->offset-32 (+ offset 16)))
|
||||||
b (aget heapf32 (mem/ptr8->ptr32 (+ offset 20)))
|
b (aget heapf32 (mem/->offset-32 (+ offset 20)))
|
||||||
c (aget heapf32 (mem/ptr8->ptr32 (+ offset 24)))
|
c (aget heapf32 (mem/->offset-32 (+ offset 24)))
|
||||||
d (aget heapf32 (mem/ptr8->ptr32 (+ offset 28)))
|
d (aget heapf32 (mem/->offset-32 (+ offset 28)))
|
||||||
e (aget heapf32 (mem/ptr8->ptr32 (+ offset 32)))
|
e (aget heapf32 (mem/->offset-32 (+ offset 32)))
|
||||||
f (aget heapf32 (mem/ptr8->ptr32 (+ offset 36)))
|
f (aget heapf32 (mem/->offset-32 (+ offset 36)))
|
||||||
transform (gmt/matrix a b c d e f)]
|
transform (gmt/matrix a b c d e f)]
|
||||||
|
|
||||||
(h/call wasm/internal-module "_free_bytes")
|
(mem/free)
|
||||||
(request-render "set-modifiers")
|
(request-render "set-modifiers")
|
||||||
|
|
||||||
{:width width
|
{:width width
|
||||||
@@ -918,7 +923,7 @@
|
|||||||
(defn get-selection-rect
|
(defn get-selection-rect
|
||||||
[entries]
|
[entries]
|
||||||
(when (d/not-empty? entries)
|
(when (d/not-empty? entries)
|
||||||
(let [offset (mem/alloc-bytes-32 (* (count entries) 16))
|
(let [offset (mem/alloc->offset-32 (* (count entries) 16))
|
||||||
heapu32 (mem/get-heap-u32)]
|
heapu32 (mem/get-heap-u32)]
|
||||||
|
|
||||||
(loop [entries (seq entries)
|
(loop [entries (seq entries)
|
||||||
@@ -926,26 +931,27 @@
|
|||||||
(when-not (empty? entries)
|
(when-not (empty? entries)
|
||||||
(let [id (first entries)]
|
(let [id (first entries)]
|
||||||
(sr/heapu32-set-uuid id heapu32 current-offset)
|
(sr/heapu32-set-uuid id heapu32 current-offset)
|
||||||
(recur (rest entries) (+ current-offset (mem/ptr8->ptr32 16))))))
|
(recur (rest entries) (+ current-offset (mem/->offset-32 16))))))
|
||||||
|
|
||||||
(let [offset (h/call wasm/internal-module "_get_selection_rect")
|
(let [offset (h/call wasm/internal-module "_get_selection_rect")
|
||||||
heapf32 (mem/get-heap-f32)
|
heap (mem/get-heap-f32)
|
||||||
width (aget heapf32 (mem/ptr8->ptr32 (+ offset 0)))
|
width (aget heap (mem/->offset-32 (+ offset 0)))
|
||||||
height (aget heapf32 (mem/ptr8->ptr32 (+ offset 4)))
|
height (aget heap (mem/->offset-32 (+ offset 4)))
|
||||||
cx (aget heapf32 (mem/ptr8->ptr32 (+ offset 8)))
|
cx (aget heap (mem/->offset-32 (+ offset 8)))
|
||||||
cy (aget heapf32 (mem/ptr8->ptr32 (+ offset 12)))
|
cy (aget heap (mem/->offset-32 (+ offset 12)))
|
||||||
a (aget heapf32 (mem/ptr8->ptr32 (+ offset 16)))
|
a (aget heap (mem/->offset-32 (+ offset 16)))
|
||||||
b (aget heapf32 (mem/ptr8->ptr32 (+ offset 20)))
|
b (aget heap (mem/->offset-32 (+ offset 20)))
|
||||||
c (aget heapf32 (mem/ptr8->ptr32 (+ offset 24)))
|
c (aget heap (mem/->offset-32 (+ offset 24)))
|
||||||
d (aget heapf32 (mem/ptr8->ptr32 (+ offset 28)))
|
d (aget heap (mem/->offset-32 (+ offset 28)))
|
||||||
e (aget heapf32 (mem/ptr8->ptr32 (+ offset 32)))
|
e (aget heap (mem/->offset-32 (+ offset 32)))
|
||||||
f (aget heapf32 (mem/ptr8->ptr32 (+ offset 36)))
|
f (aget heap (mem/->offset-32 (+ offset 36)))]
|
||||||
transform (gmt/matrix a b c d e f)]
|
|
||||||
(h/call wasm/internal-module "_free_bytes")
|
(mem/free)
|
||||||
{:width width
|
{:width width
|
||||||
:height height
|
:height height
|
||||||
:center (gpt/point cx cy)
|
:center (gpt/point cx cy)
|
||||||
:transform transform}))))
|
:transform (gmt/matrix a b c d e f)}))))
|
||||||
|
|
||||||
|
|
||||||
(defn set-canvas-background
|
(defn set-canvas-background
|
||||||
[background]
|
[background]
|
||||||
@@ -960,7 +966,7 @@
|
|||||||
(defn set-modifiers
|
(defn set-modifiers
|
||||||
[modifiers]
|
[modifiers]
|
||||||
(when-not (empty? modifiers)
|
(when-not (empty? modifiers)
|
||||||
(let [offset (mem/alloc-bytes-32 (* MODIFIER-ENTRY-SIZE (count modifiers)))
|
(let [offset (mem/alloc->offset-32 (* MODIFIER-U8-SIZE (count modifiers)))
|
||||||
heapu32 (mem/get-heap-u32)
|
heapu32 (mem/get-heap-u32)
|
||||||
heapf32 (mem/get-heap-f32)]
|
heapf32 (mem/get-heap-f32)]
|
||||||
|
|
||||||
@@ -969,8 +975,8 @@
|
|||||||
(when-not (empty? entries)
|
(when-not (empty? entries)
|
||||||
(let [{:keys [id transform]} (first entries)]
|
(let [{:keys [id transform]} (first entries)]
|
||||||
(sr/heapu32-set-uuid id heapu32 current-offset)
|
(sr/heapu32-set-uuid id heapu32 current-offset)
|
||||||
(sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-TRANSFORM-OFFSET)))
|
(sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/->offset-32 MODIFIER-TRANSFORM-U8-OFFSET-SIZE)))
|
||||||
(recur (rest entries) (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-SIZE))))))
|
(recur (rest entries) (+ current-offset (mem/->offset-32 MODIFIER-U8-SIZE))))))
|
||||||
|
|
||||||
(h/call wasm/internal-module "_set_modifiers")
|
(h/call wasm/internal-module "_set_modifiers")
|
||||||
|
|
||||||
@@ -1048,11 +1054,50 @@
|
|||||||
(get position :x)
|
(get position :x)
|
||||||
(get position :y))
|
(get position :y))
|
||||||
heapi32 (mem/get-heap-i32)
|
heapi32 (mem/get-heap-i32)
|
||||||
row (aget heapi32 (mem/ptr8->ptr32 (+ offset 0)))
|
row (aget heapi32 (mem/->offset-32 (+ offset 0)))
|
||||||
column (aget heapi32 (mem/ptr8->ptr32 (+ offset 4)))]
|
column (aget heapi32 (mem/->offset-32 (+ offset 4)))]
|
||||||
(h/call wasm/internal-module "_free_bytes")
|
(mem/free)
|
||||||
[row column]))
|
[row column]))
|
||||||
|
|
||||||
|
(defn shape-to-path
|
||||||
|
[id]
|
||||||
|
(use-shape id)
|
||||||
|
(let [offset (h/call wasm/internal-module "_current_to_path")
|
||||||
|
offset (mem/->offset-32 offset)
|
||||||
|
heapu32 (mem/get-heap-u32)
|
||||||
|
|
||||||
|
length (aget heapu32 offset)
|
||||||
|
data (mem/slice heapu32
|
||||||
|
(+ offset 1)
|
||||||
|
(+ offset 1 (* length (/ path.impl/SEGMENT-U8-SIZE 4))))
|
||||||
|
content (path/from-bytes data)]
|
||||||
|
(mem/free)
|
||||||
|
content))
|
||||||
|
|
||||||
|
(defn calculate-bool
|
||||||
|
[bool-type ids]
|
||||||
|
(let [num-ids (count ids)
|
||||||
|
offset (mem/alloc->offset-32 (* UUID-U8-SIZE num-ids))
|
||||||
|
heap (mem/get-heap-u32)]
|
||||||
|
|
||||||
|
(reduce (fn [offset id]
|
||||||
|
(sr/heapu32-set-uuid id heap offset)
|
||||||
|
(+ offset UUID-U32-SIZE))
|
||||||
|
offset
|
||||||
|
(rseq ids)))
|
||||||
|
|
||||||
|
(let [offset (h/call wasm/internal-module "_calculate_bool" (sr/translate-bool-type bool-type))
|
||||||
|
offset (mem/->offset-32 offset)
|
||||||
|
heapu32 (mem/get-heap-u32)
|
||||||
|
|
||||||
|
length (aget heapu32 offset)
|
||||||
|
data (mem/slice heapu32
|
||||||
|
(+ offset 1)
|
||||||
|
(+ offset 1 (* length (/ path.impl/SEGMENT-U8-SIZE 4))))
|
||||||
|
content (path/from-bytes data)]
|
||||||
|
(mem/free)
|
||||||
|
content))
|
||||||
|
|
||||||
(defonce module
|
(defonce module
|
||||||
(delay
|
(delay
|
||||||
(if (exists? js/dynamicImport)
|
(if (exists? js/dynamicImport)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
(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-BYTE-SIZE)))
|
(+ offset types.fills.impl/FILL-U8-SIZE)))
|
||||||
current-offset
|
current-offset
|
||||||
fills))
|
fills))
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
num-leaves (count leaves)
|
num-leaves (count leaves)
|
||||||
paragraph-attr-size 48
|
paragraph-attr-size 48
|
||||||
total-fills (total-fills-count leaves)
|
total-fills (total-fills-count leaves)
|
||||||
total-fills-size (* types.fills.impl/FILL-BYTE-SIZE total-fills)
|
total-fills-size (* types.fills.impl/FILL-U8-SIZE total-fills)
|
||||||
leaf-attr-size 56
|
leaf-attr-size 56
|
||||||
metadata-size (+ paragraph-attr-size (* num-leaves leaf-attr-size) total-fills-size)
|
metadata-size (+ paragraph-attr-size (* num-leaves leaf-attr-size) total-fills-size)
|
||||||
text-buffer (utf8->buffer text)
|
text-buffer (utf8->buffer text)
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
|
|
||||||
;; Allocate memory and set buffer
|
;; Allocate memory and set buffer
|
||||||
(let [total-size (.-byteLength buffer)
|
(let [total-size (.-byteLength buffer)
|
||||||
metadata-offset (mem/alloc-bytes total-size)
|
metadata-offset (mem/alloc total-size)
|
||||||
heap (mem/get-heap-u8)]
|
heap (mem/get-heap-u8)]
|
||||||
(.set heap (js/Uint8Array. buffer) metadata-offset)))
|
(.set heap (js/Uint8Array. buffer) metadata-offset)))
|
||||||
|
|
||||||
|
|||||||
@@ -9,36 +9,30 @@
|
|||||||
[app.render-wasm.helpers :as h]
|
[app.render-wasm.helpers :as h]
|
||||||
[app.render-wasm.wasm :as wasm]))
|
[app.render-wasm.wasm :as wasm]))
|
||||||
|
|
||||||
(defn ptr8->ptr32
|
(defn ->offset-32
|
||||||
"Returns a 32-bit (4-byte aligned) pointer of an 8-bit pointer"
|
"Convert a 8-bit (1 byte) offset to a 32-bit (4 bytes) offset"
|
||||||
[value]
|
[value]
|
||||||
;; Divides the value by 4
|
;; Divides the value by 4
|
||||||
(bit-shift-right value 2))
|
(bit-shift-right value 2))
|
||||||
|
|
||||||
(defn ptr32->ptr8
|
|
||||||
"Returns a 8-bit pointer of a 32-bit (4-byte aligned) pointer"
|
|
||||||
[value]
|
|
||||||
;; Multiplies by 4
|
|
||||||
(bit-shift-left value 2))
|
|
||||||
|
|
||||||
(defn get-list-size
|
(defn get-list-size
|
||||||
"Returns the size of a list in bytes"
|
"Returns the size of a list in bytes"
|
||||||
[list list-item-size]
|
[list list-item-size]
|
||||||
(* list-item-size (count list)))
|
(* list-item-size (count list)))
|
||||||
|
|
||||||
(defn alloc-bytes
|
(defn alloc
|
||||||
"Allocates an arbitrary amount of bytes"
|
"Allocates an arbitrary amount of bytes (aligned to 4 bytes).
|
||||||
|
Returns an offset of 8 bits (1 byte) size."
|
||||||
[size]
|
[size]
|
||||||
(when (= size 0)
|
(when (= size 0)
|
||||||
(js/console.trace "Tried to allocate 0 bytes"))
|
(js/console.trace "Tried to allocate 0 bytes"))
|
||||||
(h/call wasm/internal-module "_alloc_bytes" size))
|
(h/call wasm/internal-module "_alloc_bytes" size))
|
||||||
|
|
||||||
(defn alloc-bytes-32
|
(defn alloc->offset-32
|
||||||
"Allocates a 4-byte aligned amount of bytes"
|
"Allocates an arbitrary amount of bytes (aligned to 4 bytes).
|
||||||
|
Returns an offset of 32 bits (4 bytes) size."
|
||||||
[size]
|
[size]
|
||||||
(when (= size 0)
|
(-> (alloc size) (->offset-32)))
|
||||||
(js/console.trace "Tried to allocate 0 bytes"))
|
|
||||||
(ptr8->ptr32 (h/call wasm/internal-module "_alloc_bytes" size)))
|
|
||||||
|
|
||||||
(defn get-heap-u8
|
(defn get-heap-u8
|
||||||
"Returns a Uint8Array view of the heap"
|
"Returns a Uint8Array view of the heap"
|
||||||
@@ -59,3 +53,13 @@
|
|||||||
"Returns a Float32Array view of the heap"
|
"Returns a Float32Array view of the heap"
|
||||||
[]
|
[]
|
||||||
(unchecked-get ^js wasm/internal-module "HEAPF32"))
|
(unchecked-get ^js wasm/internal-module "HEAPF32"))
|
||||||
|
|
||||||
|
(defn free
|
||||||
|
[]
|
||||||
|
(h/call wasm/internal-module "_free_bytes"))
|
||||||
|
|
||||||
|
(defn slice
|
||||||
|
"Returns a copy of a portion of a typed array into a new typed array
|
||||||
|
object selected from start to end."
|
||||||
|
[heap start end]
|
||||||
|
(.slice ^js heap start end))
|
||||||
|
|||||||
@@ -168,7 +168,7 @@
|
|||||||
:union 0
|
:union 0
|
||||||
:difference 1
|
:difference 1
|
||||||
:intersection 2
|
:intersection 2
|
||||||
:exclusion 3
|
:exclude 3
|
||||||
0))
|
0))
|
||||||
|
|
||||||
(defn translate-blur-type
|
(defn translate-blur-type
|
||||||
|
|||||||
20
render-wasm/Cargo.lock
generated
20
render-wasm/Cargo.lock
generated
@@ -23,6 +23,15 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bezier-rs"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bde3aa314326e2f984f81adcb399c64b93eed3c0f2cd4258b711bf494c5741de"
|
||||||
|
dependencies = [
|
||||||
|
"glam",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.71.1"
|
version = "0.71.1"
|
||||||
@@ -176,6 +185,15 @@ dependencies = [
|
|||||||
"xml-rs",
|
"xml-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glam"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "glob"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -394,7 +412,9 @@ name = "render"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
|
"bezier-rs",
|
||||||
"gl",
|
"gl",
|
||||||
|
"glam",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"skia-safe",
|
"skia-safe",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
bezier-rs = "0.4.0"
|
||||||
gl = "0.14.0"
|
gl = "0.14.0"
|
||||||
|
glam = "0.24.2"
|
||||||
indexmap = "2.7.1"
|
indexmap = "2.7.1"
|
||||||
skia-safe = { version = "0.86.0", default-features = false, features = [
|
skia-safe = { version = "0.86.0", default-features = false, features = [
|
||||||
"gl",
|
"gl",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ macro_rules! with_state_mut {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! with_state {
|
macro_rules! with_state {
|
||||||
($state:ident, $block:block) => {{
|
($state:ident, $block:block) => {{
|
||||||
let $state = unsafe {
|
let $state = unsafe {
|
||||||
@@ -505,7 +506,7 @@ pub extern "C" fn set_structure_modifiers() {
|
|||||||
let Some(shape) = state.shapes.get(&entry.id) else {
|
let Some(shape) = state.shapes.get(&entry.id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for id in shape.all_children_with_self(&state.shapes, true) {
|
for id in shape.all_children(&state.shapes, true, true) {
|
||||||
state.scale_content.insert(id, entry.value);
|
state.scale_content.insert(id, entry.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
|
pub mod bools;
|
||||||
|
|
||||||
pub type Rect = skia::Rect;
|
pub type Rect = skia::Rect;
|
||||||
pub type Matrix = skia::Matrix;
|
pub type Matrix = skia::Matrix;
|
||||||
pub type Vector = skia::Vector;
|
pub type Vector = skia::Vector;
|
||||||
@@ -22,7 +24,16 @@ pub fn is_close_to(current: f32, value: f32) -> bool {
|
|||||||
(current - value).abs() <= THRESHOLD
|
(current - value).abs() <= THRESHOLD
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn identitish(m: Matrix) -> bool {
|
pub fn is_close_matrix(m: &Matrix, other: &Matrix) -> bool {
|
||||||
|
is_close_to(m.scale_x(), other.scale_x())
|
||||||
|
&& is_close_to(m.scale_y(), other.scale_y())
|
||||||
|
&& is_close_to(m.translate_x(), other.translate_x())
|
||||||
|
&& is_close_to(m.translate_y(), other.translate_y())
|
||||||
|
&& is_close_to(m.skew_x(), other.skew_x())
|
||||||
|
&& is_close_to(m.skew_y(), other.skew_y())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn identitish(m: &Matrix) -> bool {
|
||||||
is_close_to(m.scale_x(), 1.0)
|
is_close_to(m.scale_x(), 1.0)
|
||||||
&& is_close_to(m.scale_y(), 1.0)
|
&& is_close_to(m.scale_y(), 1.0)
|
||||||
&& is_close_to(m.translate_x(), 0.0)
|
&& is_close_to(m.translate_x(), 0.0)
|
||||||
@@ -328,6 +339,11 @@ impl Bounds {
|
|||||||
Rect::from_ltrb(self.min_x(), self.min_y(), self.max_x(), self.max_y())
|
Rect::from_ltrb(self.min_x(), self.min_y(), self.max_x(), self.max_y())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_rect(r: &Rect) -> Self {
|
||||||
|
let [nw, ne, se, sw] = r.to_quad();
|
||||||
|
Self::new(nw, ne, se, sw)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn min_x(&self) -> f32 {
|
pub fn min_x(&self) -> f32 {
|
||||||
self.nw.x.min(self.ne.x).min(self.sw.x).min(self.se.x)
|
self.nw.x.min(self.ne.x).min(self.sw.x).min(self.se.x)
|
||||||
}
|
}
|
||||||
|
|||||||
562
render-wasm/src/math/bools.rs
Normal file
562
render-wasm/src/math/bools.rs
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
use super::Matrix;
|
||||||
|
use crate::render::{RenderState, SurfaceId};
|
||||||
|
use crate::shapes::{BoolType, Path, Segment, Shape, StructureEntry, ToPath, Type};
|
||||||
|
use crate::state::ShapesPool;
|
||||||
|
use crate::uuid::Uuid;
|
||||||
|
use bezier_rs::{Bezier, BezierHandles, ProjectionOptions, TValue};
|
||||||
|
use glam::DVec2;
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
use skia_safe as skia;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
const INTERSECT_THRESHOLD_SAME: f32 = 0.1;
|
||||||
|
const INTERSECT_THRESHOLD_DIFFERENT: f32 = 0.5;
|
||||||
|
const INTERSECT_ERROR: f64 = 0.1;
|
||||||
|
const INTERSECT_MIN_SEPARATION: f64 = 0.05;
|
||||||
|
|
||||||
|
const PROJECT_OPTS: ProjectionOptions = ProjectionOptions {
|
||||||
|
lut_size: 20,
|
||||||
|
convergence_epsilon: 0.01,
|
||||||
|
convergence_limit: 10,
|
||||||
|
iteration_limit: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn to_point(v: DVec2) -> skia::Point {
|
||||||
|
skia::Point::new(v.x as f32, v.y as f32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_to_beziers(path: &Path) -> Vec<Bezier> {
|
||||||
|
let mut start: Option<(f64, f64)> = None;
|
||||||
|
let mut prev: Option<(f64, f64)> = None;
|
||||||
|
|
||||||
|
path.segments()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|s| match s {
|
||||||
|
Segment::MoveTo((x, y)) => {
|
||||||
|
let x = f64::from(*x);
|
||||||
|
let y = f64::from(*y);
|
||||||
|
prev = Some((x, y));
|
||||||
|
start = Some((x, y));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Segment::LineTo((x2, y2)) => {
|
||||||
|
let (x1, y1) = prev?;
|
||||||
|
let x2 = f64::from(*x2);
|
||||||
|
let y2 = f64::from(*y2);
|
||||||
|
let s = Bezier::from_linear_coordinates(x1, y1, x2, y2);
|
||||||
|
prev = Some((x2, y2));
|
||||||
|
Some(s)
|
||||||
|
}
|
||||||
|
Segment::CurveTo(((c1x, c1y), (c2x, c2y), (x2, y2))) => {
|
||||||
|
let (x1, y1) = prev?;
|
||||||
|
let x2 = f64::from(*x2);
|
||||||
|
let y2 = f64::from(*y2);
|
||||||
|
let c1x = f64::from(*c1x);
|
||||||
|
let c1y = f64::from(*c1y);
|
||||||
|
let c2x = f64::from(*c2x);
|
||||||
|
let c2y = f64::from(*c2y);
|
||||||
|
let s = Bezier::from_cubic_coordinates(x1, y1, c1x, c1y, c2x, c2y, x2, y2);
|
||||||
|
prev = Some((x2, y2));
|
||||||
|
Some(s)
|
||||||
|
}
|
||||||
|
Segment::Close => {
|
||||||
|
let (x1, y1) = prev?;
|
||||||
|
let (x2, y2) = start?;
|
||||||
|
let s = Bezier::from_linear_coordinates(x1, y1, x2, y2);
|
||||||
|
prev = Some((x2, y2));
|
||||||
|
Some(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_intersections(segment: Bezier, intersections: &[f64]) -> Vec<Bezier> {
|
||||||
|
if intersections.is_empty() {
|
||||||
|
return vec![segment];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut intersections = intersections.to_owned();
|
||||||
|
intersections.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||||
|
|
||||||
|
let mut prev = 0.0;
|
||||||
|
let mut cur_segment = segment;
|
||||||
|
|
||||||
|
for t_i in &intersections {
|
||||||
|
let rti = (t_i - prev) / (1.0 - prev);
|
||||||
|
let [s, rest] = cur_segment.split(TValue::Parametric(rti));
|
||||||
|
prev = *t_i;
|
||||||
|
cur_segment = rest;
|
||||||
|
result.push(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(cur_segment);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_segments(path_a: &Path, path_b: &Path) -> (Vec<Bezier>, Vec<Bezier>) {
|
||||||
|
let path_a = path_to_beziers(path_a);
|
||||||
|
let path_b = path_to_beziers(path_b);
|
||||||
|
|
||||||
|
let mut intersects_a = Vec::<Vec<f64>>::with_capacity(path_a.len());
|
||||||
|
intersects_a.resize_with(path_a.len(), Default::default);
|
||||||
|
|
||||||
|
let mut intersects_b = Vec::<Vec<f64>>::with_capacity(path_b.len());
|
||||||
|
intersects_b.resize_with(path_b.len(), Default::default);
|
||||||
|
|
||||||
|
for i in 0..path_a.len() {
|
||||||
|
for j in 0..path_b.len() {
|
||||||
|
let segment_a = path_a[i];
|
||||||
|
let segment_b = path_b[j];
|
||||||
|
let intersections_a = segment_a.intersections(
|
||||||
|
&segment_b,
|
||||||
|
Some(INTERSECT_ERROR),
|
||||||
|
Some(INTERSECT_MIN_SEPARATION),
|
||||||
|
);
|
||||||
|
|
||||||
|
intersects_b[j].extend(intersections_a.iter().map(|t_a| {
|
||||||
|
segment_b.project(
|
||||||
|
segment_a.evaluate(TValue::Parametric(*t_a)),
|
||||||
|
Some(PROJECT_OPTS),
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
|
||||||
|
intersects_a[i].extend(intersections_a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result_a = Vec::new();
|
||||||
|
for i in 0..path_a.len() {
|
||||||
|
let cur_segment = path_a[i];
|
||||||
|
result_a.extend(split_intersections(cur_segment, &intersects_a[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result_b = Vec::new();
|
||||||
|
for i in 0..path_b.len() {
|
||||||
|
let cur_segment = path_b[i];
|
||||||
|
result_b.extend(split_intersections(cur_segment, &intersects_b[i]));
|
||||||
|
}
|
||||||
|
(result_a, result_b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn union(
|
||||||
|
path_a: &Path,
|
||||||
|
segments_a: Vec<Bezier>,
|
||||||
|
path_b: &Path,
|
||||||
|
segments_b: Vec<Bezier>,
|
||||||
|
) -> Vec<(BezierSource, Bezier)> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
result.extend(
|
||||||
|
segments_a
|
||||||
|
.iter()
|
||||||
|
.filter(|s| !path_b.contains(to_point(s.evaluate(TValue::Parametric(0.5)))))
|
||||||
|
.copied()
|
||||||
|
.map(|b| (BezierSource::A, b)),
|
||||||
|
);
|
||||||
|
|
||||||
|
result.extend(
|
||||||
|
segments_b
|
||||||
|
.iter()
|
||||||
|
.filter(|s| !path_a.contains(to_point(s.evaluate(TValue::Parametric(0.5)))))
|
||||||
|
.copied()
|
||||||
|
.map(|b| (BezierSource::B, b)),
|
||||||
|
);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersection(
|
||||||
|
path_a: &Path,
|
||||||
|
segments_a: Vec<Bezier>,
|
||||||
|
path_b: &Path,
|
||||||
|
segments_b: Vec<Bezier>,
|
||||||
|
) -> Vec<(BezierSource, Bezier)> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
result.extend(
|
||||||
|
segments_a
|
||||||
|
.iter()
|
||||||
|
.filter(|s| path_b.contains(to_point(s.evaluate(TValue::Parametric(0.5)))))
|
||||||
|
.copied()
|
||||||
|
.map(|b| (BezierSource::A, b)),
|
||||||
|
);
|
||||||
|
|
||||||
|
result.extend(
|
||||||
|
segments_b
|
||||||
|
.iter()
|
||||||
|
.filter(|s| path_a.contains(to_point(s.evaluate(TValue::Parametric(0.5)))))
|
||||||
|
.copied()
|
||||||
|
.map(|b| (BezierSource::B, b)),
|
||||||
|
);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn difference(
|
||||||
|
path_a: &Path,
|
||||||
|
segments_a: Vec<Bezier>,
|
||||||
|
path_b: &Path,
|
||||||
|
segments_b: Vec<Bezier>,
|
||||||
|
) -> Vec<(BezierSource, Bezier)> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
result.extend(
|
||||||
|
segments_a
|
||||||
|
.iter()
|
||||||
|
.filter(|s| !path_b.contains(to_point(s.evaluate(TValue::Parametric(0.5)))))
|
||||||
|
.copied()
|
||||||
|
.map(|b| (BezierSource::A, b)),
|
||||||
|
);
|
||||||
|
|
||||||
|
result.extend(
|
||||||
|
segments_b
|
||||||
|
.iter()
|
||||||
|
.filter(|s| path_a.contains(to_point(s.evaluate(TValue::Parametric(0.5)))))
|
||||||
|
.copied()
|
||||||
|
.map(|s| s.reverse())
|
||||||
|
.map(|b| (BezierSource::B, b)),
|
||||||
|
);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exclusion(segments_a: Vec<Bezier>, segments_b: Vec<Bezier>) -> Vec<(BezierSource, Bezier)> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
result.extend(segments_a.iter().copied().map(|b| (BezierSource::A, b)));
|
||||||
|
result.extend(
|
||||||
|
segments_b
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(|s| s.reverse())
|
||||||
|
.map(|b| (BezierSource::B, b)),
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||||
|
enum BezierSource {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct BezierStart(BezierSource, DVec2);
|
||||||
|
|
||||||
|
impl PartialEq for BezierStart {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
let x1 = self.1.x as f32;
|
||||||
|
let y1 = self.1.y as f32;
|
||||||
|
let x2 = other.1.x as f32;
|
||||||
|
let y2 = other.1.y as f32;
|
||||||
|
|
||||||
|
if self.0 == other.0 {
|
||||||
|
(x1 - x2).abs() <= INTERSECT_THRESHOLD_SAME
|
||||||
|
&& (y1 - y2).abs() <= INTERSECT_THRESHOLD_SAME
|
||||||
|
} else {
|
||||||
|
(x1 - x2).abs() <= INTERSECT_THRESHOLD_DIFFERENT
|
||||||
|
&& (y1 - y2).abs() <= INTERSECT_THRESHOLD_DIFFERENT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for BezierStart {}
|
||||||
|
|
||||||
|
impl PartialOrd for BezierStart {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for BezierStart {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
let x1 = self.1.x as f32;
|
||||||
|
let y1 = self.1.y as f32;
|
||||||
|
let x2 = other.1.x as f32;
|
||||||
|
let y2 = other.1.y as f32;
|
||||||
|
|
||||||
|
let (equal_x, equal_y) = if self.0 == other.0 {
|
||||||
|
(
|
||||||
|
(x1 - x2).abs() <= INTERSECT_THRESHOLD_SAME,
|
||||||
|
(y1 - y2).abs() <= INTERSECT_THRESHOLD_SAME,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
(x1 - x2).abs() <= INTERSECT_THRESHOLD_DIFFERENT,
|
||||||
|
(y1 - y2).abs() <= INTERSECT_THRESHOLD_DIFFERENT,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if equal_x && equal_y {
|
||||||
|
Ordering::Equal
|
||||||
|
} else if equal_x && y1 > y2 || !equal_x && x1 > x2 {
|
||||||
|
Ordering::Greater
|
||||||
|
} else {
|
||||||
|
Ordering::Less
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BM<'a> = BTreeMap<BezierStart, Vec<(BezierSource, Bezier)>>;
|
||||||
|
|
||||||
|
fn init_bm(beziers: &[(BezierSource, Bezier)]) -> BM {
|
||||||
|
let mut bm = BM::default();
|
||||||
|
for entry @ (source, bezier) in beziers.iter() {
|
||||||
|
let value = *entry;
|
||||||
|
let key = BezierStart(*source, bezier.start);
|
||||||
|
if let Some(v) = bm.get_mut(&key) {
|
||||||
|
v.push(value);
|
||||||
|
} else {
|
||||||
|
bm.insert(key, vec![value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_next(tree: &mut BM, key: BezierStart) -> Option<(BezierSource, Bezier)> {
|
||||||
|
let val = tree.get_mut(&key)?;
|
||||||
|
let first = val.pop()?;
|
||||||
|
|
||||||
|
if val.is_empty() {
|
||||||
|
tree.remove(&key);
|
||||||
|
}
|
||||||
|
Some(first)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_first(tree: &mut BM) -> Option<(BezierSource, Bezier)> {
|
||||||
|
let key = tree.keys().take(1).next()?.clone();
|
||||||
|
let val = tree.get_mut(&key)?;
|
||||||
|
let first = val.pop()?;
|
||||||
|
|
||||||
|
if val.is_empty() {
|
||||||
|
tree.remove(&key);
|
||||||
|
}
|
||||||
|
Some(first)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_bezier(result: &mut Vec<Segment>, bezier: &Bezier) {
|
||||||
|
match bezier.handles {
|
||||||
|
BezierHandles::Linear => {
|
||||||
|
result.push(Segment::LineTo((bezier.end.x as f32, bezier.end.y as f32)));
|
||||||
|
}
|
||||||
|
BezierHandles::Quadratic { handle } => {
|
||||||
|
result.push(Segment::CurveTo((
|
||||||
|
(handle.x as f32, handle.y as f32),
|
||||||
|
(handle.x as f32, handle.y as f32),
|
||||||
|
(bezier.end.x as f32, bezier.end.y as f32),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
BezierHandles::Cubic {
|
||||||
|
handle_start,
|
||||||
|
handle_end,
|
||||||
|
} => {
|
||||||
|
result.push(Segment::CurveTo((
|
||||||
|
(handle_start.x as f32, handle_start.y as f32),
|
||||||
|
(handle_end.x as f32, handle_end.y as f32),
|
||||||
|
(bezier.end.x as f32, bezier.end.y as f32),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn beziers_to_segments(beziers: &[(BezierSource, Bezier)]) -> Vec<Segment> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
let mut bm = init_bm(beziers);
|
||||||
|
|
||||||
|
while let Some(bezier) = pop_first(&mut bm) {
|
||||||
|
result.push(Segment::MoveTo((
|
||||||
|
bezier.1.start.x as f32,
|
||||||
|
bezier.1.start.y as f32,
|
||||||
|
)));
|
||||||
|
push_bezier(&mut result, &bezier.1);
|
||||||
|
let mut next_p = BezierStart(bezier.0, bezier.1.end);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Some(next) = find_next(&mut bm, next_p) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
push_bezier(&mut result, &next.1);
|
||||||
|
next_p = BezierStart(next.0, next.1.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bool_from_shapes(
|
||||||
|
bool_type: BoolType,
|
||||||
|
children_ids: &IndexSet<Uuid>,
|
||||||
|
shapes: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
|
) -> Path {
|
||||||
|
if children_ids.is_empty() {
|
||||||
|
return Path::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(child) = shapes.get(&children_ids[children_ids.len() - 1]) else {
|
||||||
|
return Path::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut current_path = child.to_path(shapes, modifiers, structure);
|
||||||
|
|
||||||
|
for idx in (0..children_ids.len() - 1).rev() {
|
||||||
|
let Some(other) = shapes.get(&children_ids[idx]) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let other_path = other.to_path(shapes, modifiers, structure);
|
||||||
|
|
||||||
|
let (segs_a, segs_b) = split_segments(¤t_path, &other_path);
|
||||||
|
|
||||||
|
let beziers = match bool_type {
|
||||||
|
BoolType::Union => union(¤t_path, segs_a, &other_path, segs_b),
|
||||||
|
BoolType::Difference => difference(¤t_path, segs_a, &other_path, segs_b),
|
||||||
|
BoolType::Intersection => intersection(¤t_path, segs_a, &other_path, segs_b),
|
||||||
|
BoolType::Exclusion => exclusion(segs_a, segs_b),
|
||||||
|
};
|
||||||
|
|
||||||
|
current_path = Path::new(beziers_to_segments(&beziers));
|
||||||
|
}
|
||||||
|
|
||||||
|
current_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_bool_to_path(
|
||||||
|
shape: &Shape,
|
||||||
|
shapes: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
|
) -> Shape {
|
||||||
|
let mut shape = shape.clone();
|
||||||
|
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||||
|
|
||||||
|
let Type::Bool(bool_data) = &mut shape.shape_type else {
|
||||||
|
return shape;
|
||||||
|
};
|
||||||
|
bool_data.path = bool_from_shapes(
|
||||||
|
bool_data.bool_type,
|
||||||
|
&children_ids,
|
||||||
|
shapes,
|
||||||
|
modifiers,
|
||||||
|
structure,
|
||||||
|
);
|
||||||
|
shape
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
// Debug utility for boolean shapes
|
||||||
|
pub fn debug_render_bool_paths(
|
||||||
|
render_state: &mut RenderState,
|
||||||
|
shape: &Shape,
|
||||||
|
shapes: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
|
) {
|
||||||
|
let canvas = render_state.surfaces.canvas(SurfaceId::Strokes);
|
||||||
|
|
||||||
|
let mut shape = shape.clone();
|
||||||
|
|
||||||
|
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||||
|
|
||||||
|
let Type::Bool(bool_data) = &mut shape.shape_type else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if children_ids.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(child) = shapes.get(&children_ids[children_ids.len() - 1]) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut current_path = child.to_path(shapes, modifiers, structure);
|
||||||
|
|
||||||
|
for idx in (0..children_ids.len() - 1).rev() {
|
||||||
|
let Some(other) = shapes.get(&children_ids[idx]) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let other_path = other.to_path(shapes, modifiers, structure);
|
||||||
|
|
||||||
|
let (segs_a, segs_b) = split_segments(¤t_path, &other_path);
|
||||||
|
|
||||||
|
let beziers = match bool_data.bool_type {
|
||||||
|
BoolType::Union => union(¤t_path, segs_a, &other_path, segs_b),
|
||||||
|
BoolType::Difference => difference(¤t_path, segs_a, &other_path, segs_b),
|
||||||
|
BoolType::Intersection => intersection(¤t_path, segs_a, &other_path, segs_b),
|
||||||
|
BoolType::Exclusion => exclusion(segs_a, segs_b),
|
||||||
|
};
|
||||||
|
current_path = Path::new(beziers_to_segments(&beziers));
|
||||||
|
|
||||||
|
if idx == 0 {
|
||||||
|
for b in &beziers {
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_color(skia::Color::RED);
|
||||||
|
paint.set_alpha_f(1.0);
|
||||||
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
|
|
||||||
|
let mut path = skia::Path::default();
|
||||||
|
path.move_to((b.1.start.x as f32, b.1.start.y as f32));
|
||||||
|
|
||||||
|
match b.1.handles {
|
||||||
|
BezierHandles::Linear => {
|
||||||
|
path.line_to((b.1.end.x as f32, b.1.end.y as f32));
|
||||||
|
}
|
||||||
|
BezierHandles::Quadratic { handle } => {
|
||||||
|
path.quad_to(
|
||||||
|
(handle.x as f32, handle.y as f32),
|
||||||
|
(b.1.end.x as f32, b.1.end.y as f32),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
BezierHandles::Cubic {
|
||||||
|
handle_start,
|
||||||
|
handle_end,
|
||||||
|
} => {
|
||||||
|
path.cubic_to(
|
||||||
|
(handle_start.x as f32, handle_start.y as f32),
|
||||||
|
(handle_end.x as f32, handle_end.y as f32),
|
||||||
|
(b.1.end.x as f32, b.1.end.y as f32),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canvas.draw_path(&path, &paint);
|
||||||
|
|
||||||
|
let mut v1 = b.1.normal(TValue::Parametric(1.0));
|
||||||
|
v1 *= 0.5;
|
||||||
|
let v2 = v1.perp();
|
||||||
|
|
||||||
|
let p1 = b.1.end + v1 + v2;
|
||||||
|
let p2 = b.1.end - v1 + v2;
|
||||||
|
|
||||||
|
canvas.draw_line(
|
||||||
|
(b.1.end.x as f32, b.1.end.y as f32),
|
||||||
|
(p1.x as f32, p1.y as f32),
|
||||||
|
&paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.draw_line(
|
||||||
|
(b.1.end.x as f32, b.1.end.y as f32),
|
||||||
|
(p2.x as f32, p2.y as f32),
|
||||||
|
&paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
let v3 = b.1.normal(TValue::Parametric(0.0));
|
||||||
|
let p3 = b.1.start + v3;
|
||||||
|
let p4 = b.1.start - v3;
|
||||||
|
|
||||||
|
canvas.draw_line(
|
||||||
|
(b.1.start.x as f32, b.1.start.y as f32),
|
||||||
|
(p3.x as f32, p3.y as f32),
|
||||||
|
&paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.draw_line(
|
||||||
|
(b.1.start.x as f32, b.1.start.y as f32),
|
||||||
|
(p4.x as f32, p4.y as f32),
|
||||||
|
&paint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ use std::collections::{HashMap, HashSet};
|
|||||||
|
|
||||||
use gpu_state::GpuState;
|
use gpu_state::GpuState;
|
||||||
use options::RenderOptions;
|
use options::RenderOptions;
|
||||||
use surfaces::{SurfaceId, Surfaces};
|
pub use surfaces::{SurfaceId, Surfaces};
|
||||||
|
|
||||||
use crate::performance;
|
use crate::performance;
|
||||||
use crate::shapes::{Corners, Fill, Shape, SolidColor, StructureEntry, Type};
|
use crate::shapes::{Corners, Fill, Shape, SolidColor, StructureEntry, Type};
|
||||||
@@ -28,6 +28,9 @@ use crate::uuid::Uuid;
|
|||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
use crate::wapi;
|
use crate::wapi;
|
||||||
|
|
||||||
|
use crate::math;
|
||||||
|
use crate::math::bools;
|
||||||
|
|
||||||
pub use blend::BlendMode;
|
pub use blend::BlendMode;
|
||||||
pub use fonts::*;
|
pub use fonts::*;
|
||||||
pub use images::*;
|
pub use images::*;
|
||||||
@@ -199,6 +202,28 @@ pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_modified_child(
|
||||||
|
shape: &Shape,
|
||||||
|
shapes: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
) -> bool {
|
||||||
|
if modifiers.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids = shape.all_children(shapes, true, false);
|
||||||
|
let default = &Matrix::default();
|
||||||
|
let parent_modifier = modifiers.get(&shape.id).unwrap_or(default);
|
||||||
|
|
||||||
|
// Returns true if the transform of any child is different to the parent's
|
||||||
|
ids.iter().any(|id| {
|
||||||
|
!math::is_close_matrix(
|
||||||
|
parent_modifier,
|
||||||
|
modifiers.get(id).unwrap_or(&Matrix::default()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderState {
|
impl RenderState {
|
||||||
pub fn new(width: i32, height: i32) -> RenderState {
|
pub fn new(width: i32, height: i32) -> RenderState {
|
||||||
// This needs to be done once per WebGL context.
|
// This needs to be done once per WebGL context.
|
||||||
@@ -397,8 +422,10 @@ impl RenderState {
|
|||||||
|
|
||||||
pub fn render_shape(
|
pub fn render_shape(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
shapes: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
modifiers: Option<&Matrix>,
|
|
||||||
scale_content: Option<&f32>,
|
scale_content: Option<&f32>,
|
||||||
) {
|
) {
|
||||||
let shape = if let Some(scale_content) = scale_content {
|
let shape = if let Some(scale_content) = scale_content {
|
||||||
@@ -420,8 +447,8 @@ impl RenderState {
|
|||||||
// We don't want to change the value in the global state
|
// We don't want to change the value in the global state
|
||||||
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
let mut shape: Cow<Shape> = Cow::Borrowed(shape);
|
||||||
|
|
||||||
if let Some(modifiers) = modifiers {
|
if let Some(shape_modifiers) = modifiers.get(&shape.id) {
|
||||||
shape.to_mut().apply_transform(modifiers);
|
shape.to_mut().apply_transform(shape_modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
let center = shape.center();
|
let center = shape.center();
|
||||||
@@ -431,8 +458,10 @@ impl RenderState {
|
|||||||
|
|
||||||
match &shape.shape_type {
|
match &shape.shape_type {
|
||||||
Type::SVGRaw(sr) => {
|
Type::SVGRaw(sr) => {
|
||||||
if let Some(modifiers) = modifiers {
|
if let Some(shape_modifiers) = modifiers.get(&shape.id) {
|
||||||
self.surfaces.canvas(SurfaceId::Fills).concat(modifiers);
|
self.surfaces
|
||||||
|
.canvas(SurfaceId::Fills)
|
||||||
|
.concat(shape_modifiers);
|
||||||
}
|
}
|
||||||
self.surfaces.canvas(SurfaceId::Fills).concat(&matrix);
|
self.surfaces.canvas(SurfaceId::Fills).concat(&matrix);
|
||||||
if let Some(svg) = shape.svg.as_ref() {
|
if let Some(svg) = shape.svg.as_ref() {
|
||||||
@@ -520,6 +549,19 @@ impl RenderState {
|
|||||||
s.canvas().concat(&matrix);
|
s.canvas().concat(&matrix);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let shape = if let Type::Bool(_) = &shape.shape_type {
|
||||||
|
// If any child transform doesn't match the parent transform means
|
||||||
|
// that the children is transformed and we need to recalculate the
|
||||||
|
// boolean
|
||||||
|
if is_modified_child(&shape, shapes, modifiers) {
|
||||||
|
&bools::update_bool_to_path(&shape, shapes, modifiers, structure)
|
||||||
|
} else {
|
||||||
|
&shape
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
&shape
|
||||||
|
};
|
||||||
|
|
||||||
let has_fill_none = matches!(
|
let has_fill_none = matches!(
|
||||||
shape.svg_attrs.get("fill").map(String::as_str),
|
shape.svg_attrs.get("fill").map(String::as_str),
|
||||||
Some("none")
|
Some("none")
|
||||||
@@ -532,23 +574,24 @@ impl RenderState {
|
|||||||
if let Some(fills_to_render) = self.nested_fills.last() {
|
if let Some(fills_to_render) = self.nested_fills.last() {
|
||||||
let fills_to_render = fills_to_render.clone();
|
let fills_to_render = fills_to_render.clone();
|
||||||
for fill in fills_to_render.iter() {
|
for fill in fills_to_render.iter() {
|
||||||
fills::render(self, &shape, fill, antialias);
|
fills::render(self, shape, fill, antialias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for fill in shape.fills().rev() {
|
for fill in shape.fills().rev() {
|
||||||
fills::render(self, &shape, fill, antialias);
|
fills::render(self, shape, fill, antialias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for stroke in shape.visible_strokes().rev() {
|
for stroke in shape.visible_strokes().rev() {
|
||||||
shadows::render_stroke_drop_shadows(self, &shape, stroke, antialias);
|
shadows::render_stroke_drop_shadows(self, shape, stroke, antialias);
|
||||||
strokes::render(self, &shape, stroke, None, None, None, antialias, None);
|
strokes::render(self, shape, stroke, None, None, None, antialias, None);
|
||||||
shadows::render_stroke_inner_shadows(self, &shape, stroke, antialias);
|
shadows::render_stroke_inner_shadows(self, shape, stroke, antialias);
|
||||||
}
|
}
|
||||||
|
|
||||||
shadows::render_fill_inner_shadows(self, &shape, antialias);
|
shadows::render_fill_inner_shadows(self, shape, antialias);
|
||||||
shadows::render_fill_drop_shadows(self, &shape, antialias);
|
shadows::render_fill_drop_shadows(self, shape, antialias);
|
||||||
|
// bools::debug_render_bool_paths(self, shape, shapes, modifiers, structure);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.apply_drawing_to_render_canvas(Some(&shape));
|
self.apply_drawing_to_render_canvas(Some(&shape));
|
||||||
@@ -751,9 +794,11 @@ impl RenderState {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn render_shape_exit(
|
pub fn render_shape_exit(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
tree: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
element: &Shape,
|
element: &Shape,
|
||||||
visited_mask: bool,
|
visited_mask: bool,
|
||||||
modifiers: Option<&Matrix>,
|
|
||||||
scale_content: Option<&f32>,
|
scale_content: Option<&f32>,
|
||||||
) {
|
) {
|
||||||
if visited_mask {
|
if visited_mask {
|
||||||
@@ -815,7 +860,7 @@ impl RenderState {
|
|||||||
element_fills
|
element_fills
|
||||||
.to_mut()
|
.to_mut()
|
||||||
.set_fills([Fill::Solid(SolidColor(skia::Color::WHITE))].to_vec());
|
.set_fills([Fill::Solid(SolidColor(skia::Color::WHITE))].to_vec());
|
||||||
self.render_shape(&element_fills, modifiers, scale_content);
|
self.render_shape(tree, modifiers, structure, &element_fills, scale_content);
|
||||||
|
|
||||||
self.surfaces.canvas(SurfaceId::Current).restore();
|
self.surfaces.canvas(SurfaceId::Current).restore();
|
||||||
|
|
||||||
@@ -823,7 +868,7 @@ impl RenderState {
|
|||||||
let mut element_strokes: Cow<Shape> = Cow::Borrowed(element);
|
let mut element_strokes: Cow<Shape> = Cow::Borrowed(element);
|
||||||
element_strokes.to_mut().clear_fills();
|
element_strokes.to_mut().clear_fills();
|
||||||
element_strokes.to_mut().clear_shadows();
|
element_strokes.to_mut().clear_shadows();
|
||||||
self.render_shape(&element_strokes, modifiers, scale_content);
|
self.render_shape(tree, modifiers, structure, &element_strokes, scale_content);
|
||||||
|
|
||||||
// TODO: drop shadows. With thos approach actually drop shadows for frames with clipped content are lost.
|
// TODO: drop shadows. With thos approach actually drop shadows for frames with clipped content are lost.
|
||||||
}
|
}
|
||||||
@@ -901,9 +946,11 @@ impl RenderState {
|
|||||||
|
|
||||||
if visited_children {
|
if visited_children {
|
||||||
self.render_shape_exit(
|
self.render_shape_exit(
|
||||||
|
tree,
|
||||||
|
modifiers,
|
||||||
|
structure,
|
||||||
element,
|
element,
|
||||||
visited_mask,
|
visited_mask,
|
||||||
modifiers.get(&node_id),
|
|
||||||
scale_content.get(&element.id),
|
scale_content.get(&element.id),
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -944,8 +991,10 @@ impl RenderState {
|
|||||||
self.render_shape_enter(element, mask);
|
self.render_shape_enter(element, mask);
|
||||||
if !node_render_state.is_root() && self.focus_mode.is_active() {
|
if !node_render_state.is_root() && self.focus_mode.is_active() {
|
||||||
self.render_shape(
|
self.render_shape(
|
||||||
|
tree,
|
||||||
|
modifiers,
|
||||||
|
structure,
|
||||||
element,
|
element,
|
||||||
modifiers.get(&element.id),
|
|
||||||
scale_content.get(&element.id),
|
scale_content.get(&element.id),
|
||||||
);
|
);
|
||||||
} else if visited_children {
|
} else if visited_children {
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ impl FontStore {
|
|||||||
pub fn get_fallback(&self) -> &HashSet<String> {
|
pub fn get_fallback(&self) -> &HashSet<String> {
|
||||||
&self.fallback_fonts
|
&self.fallback_fonts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_emoji_font(&self, _size: f32) -> Option<Font> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_default_provider(font_mgr: &FontMgr) -> skia::textlayout::TypefaceFontProvider {
|
fn load_default_provider(font_mgr: &FontMgr) -> skia::textlayout::TypefaceFontProvider {
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ pub mod modifiers;
|
|||||||
mod paths;
|
mod paths;
|
||||||
mod rects;
|
mod rects;
|
||||||
mod shadows;
|
mod shadows;
|
||||||
|
mod shape_to_path;
|
||||||
mod strokes;
|
mod strokes;
|
||||||
mod svgraw;
|
mod svgraw;
|
||||||
mod text;
|
mod text;
|
||||||
|
pub mod text_paths;
|
||||||
mod transform;
|
mod transform;
|
||||||
|
|
||||||
pub use blurs::*;
|
pub use blurs::*;
|
||||||
@@ -36,6 +38,7 @@ pub use modifiers::*;
|
|||||||
pub use paths::*;
|
pub use paths::*;
|
||||||
pub use rects::*;
|
pub use rects::*;
|
||||||
pub use shadows::*;
|
pub use shadows::*;
|
||||||
|
pub use shape_to_path::*;
|
||||||
pub use strokes::*;
|
pub use strokes::*;
|
||||||
pub use svgraw::*;
|
pub use svgraw::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
@@ -827,23 +830,27 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_children_with_self(
|
pub fn all_children(
|
||||||
&self,
|
&self,
|
||||||
shapes: &ShapesPool,
|
shapes: &ShapesPool,
|
||||||
include_hidden: bool,
|
include_hidden: bool,
|
||||||
|
include_self: bool,
|
||||||
) -> IndexSet<Uuid> {
|
) -> IndexSet<Uuid> {
|
||||||
once(self.id)
|
let all_children = self
|
||||||
.chain(
|
.children_ids(include_hidden)
|
||||||
self.children_ids(include_hidden)
|
.into_iter()
|
||||||
.into_iter()
|
.flat_map(|id| {
|
||||||
.flat_map(|id| {
|
shapes
|
||||||
shapes
|
.get(&id)
|
||||||
.get(&id)
|
.map(|s| s.all_children(shapes, include_hidden, true))
|
||||||
.map(|s| s.all_children_with_self(shapes, include_hidden))
|
.unwrap_or_default()
|
||||||
.unwrap_or_default()
|
});
|
||||||
}),
|
|
||||||
)
|
if include_self {
|
||||||
.collect()
|
once(self.id).chain(all_children).collect()
|
||||||
|
} else {
|
||||||
|
all_children.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all ancestor shapes of this shape, traversing up the parent hierarchy
|
/// Returns all ancestor shapes of this shape, traversing up the parent hierarchy
|
||||||
@@ -1002,6 +1009,17 @@ impl Shape {
|
|||||||
path.transform(transform);
|
path.transform(transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Type::Text(text) = &mut self.shape_type {
|
||||||
|
text.transform(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transformed(&self, transform: Option<&Matrix>) -> Self {
|
||||||
|
let mut shape = self.clone();
|
||||||
|
if let Some(transform) = transform {
|
||||||
|
shape.apply_transform(transform);
|
||||||
|
}
|
||||||
|
shape
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_absolute(&self) -> bool {
|
pub fn is_absolute(&self) -> bool {
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ pub mod grid_layout;
|
|||||||
|
|
||||||
use common::GetBounds;
|
use common::GetBounds;
|
||||||
|
|
||||||
|
use crate::math::bools;
|
||||||
use crate::math::{self as math, identitish, Bounds, Matrix, Point};
|
use crate::math::{self as math, identitish, Bounds, Matrix, Point};
|
||||||
|
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
auto_height, set_paragraphs_width, ConstraintH, ConstraintV, Frame, Group, GrowType, Layout,
|
auto_height, set_paragraphs_width, ConstraintH, ConstraintV, Frame, Group, GrowType, Layout,
|
||||||
Modifier, Shape, StructureEntry, TransformEntry, Type,
|
Modifier, Shape, StructureEntry, TransformEntry, Type,
|
||||||
@@ -28,7 +30,7 @@ fn propagate_children(
|
|||||||
) -> VecDeque<Modifier> {
|
) -> VecDeque<Modifier> {
|
||||||
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||||
|
|
||||||
if children_ids.is_empty() || identitish(transform) {
|
if children_ids.is_empty() || identitish(&transform) {
|
||||||
return VecDeque::new();
|
return VecDeque::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +111,31 @@ fn calculate_group_bounds(
|
|||||||
shape_bounds.with_points(result)
|
shape_bounds.with_points(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_bool_bounds(
|
||||||
|
shape: &Shape,
|
||||||
|
shapes: &ShapesPool,
|
||||||
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
|
) -> Option<Bounds> {
|
||||||
|
let shape_bounds = bounds.find(shape);
|
||||||
|
let children_ids = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||||
|
|
||||||
|
let Type::Bool(bool_data) = &shape.shape_type else {
|
||||||
|
return Some(shape_bounds);
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = bools::bool_from_shapes(
|
||||||
|
bool_data.bool_type,
|
||||||
|
&children_ids,
|
||||||
|
shapes,
|
||||||
|
modifiers,
|
||||||
|
structure,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(path.bounds())
|
||||||
|
}
|
||||||
|
|
||||||
fn set_pixel_precision(transform: &mut Matrix, bounds: &mut Bounds) {
|
fn set_pixel_precision(transform: &mut Matrix, bounds: &mut Bounds) {
|
||||||
let tr = bounds.transform_matrix().unwrap_or_default();
|
let tr = bounds.transform_matrix().unwrap_or_default();
|
||||||
let tr_inv = tr.invert().unwrap_or_default();
|
let tr_inv = tr.invert().unwrap_or_default();
|
||||||
@@ -227,6 +254,7 @@ fn propagate_reflow(
|
|||||||
bounds: &mut HashMap<Uuid, Bounds>,
|
bounds: &mut HashMap<Uuid, Bounds>,
|
||||||
layout_reflows: &mut Vec<Uuid>,
|
layout_reflows: &mut Vec<Uuid>,
|
||||||
reflown: &mut HashSet<Uuid>,
|
reflown: &mut HashSet<Uuid>,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
) {
|
) {
|
||||||
let Some(shape) = state.shapes.get(id) else {
|
let Some(shape) = state.shapes.get(id) else {
|
||||||
return;
|
return;
|
||||||
@@ -278,11 +306,8 @@ fn propagate_reflow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::Bool(_) => {
|
Type::Bool(_) => {
|
||||||
// TODO: How to calculate from rust the new box? we need to calculate the
|
|
||||||
// new path... impossible right now. I'm going to use for the moment the group
|
|
||||||
// calculation
|
|
||||||
if let Some(shape_bounds) =
|
if let Some(shape_bounds) =
|
||||||
calculate_group_bounds(shape, shapes, bounds, &state.structure)
|
calculate_bool_bounds(shape, shapes, bounds, modifiers, &state.structure)
|
||||||
{
|
{
|
||||||
bounds.insert(shape.id, shape_bounds);
|
bounds.insert(shape.id, shape_bounds);
|
||||||
reflow_parent = true;
|
reflow_parent = true;
|
||||||
@@ -391,6 +416,7 @@ pub fn propagate_modifiers(
|
|||||||
&mut bounds,
|
&mut bounds,
|
||||||
&mut layout_reflows,
|
&mut layout_reflows,
|
||||||
&mut reflown,
|
&mut reflown,
|
||||||
|
&modifiers,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use skia_safe::{self as skia, Matrix};
|
use skia_safe::{self as skia, Matrix};
|
||||||
|
|
||||||
|
use crate::math;
|
||||||
|
|
||||||
type Point = (f32, f32);
|
type Point = (f32, f32);
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
@@ -23,6 +25,18 @@ impl Default for Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_verb(v: u8) -> skia::path::Verb {
|
||||||
|
match v {
|
||||||
|
0 => skia::path::Verb::Move,
|
||||||
|
1 => skia::path::Verb::Line,
|
||||||
|
2 => skia::path::Verb::Quad,
|
||||||
|
3 => skia::path::Verb::Conic,
|
||||||
|
4 => skia::path::Verb::Cubic,
|
||||||
|
5 => skia::path::Verb::Close,
|
||||||
|
_ => skia::path::Verb::Done,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Path {
|
impl Path {
|
||||||
pub fn new(segments: Vec<Segment>) -> Self {
|
pub fn new(segments: Vec<Segment>) -> Self {
|
||||||
let mut open = true;
|
let mut open = true;
|
||||||
@@ -50,8 +64,11 @@ impl Path {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let (Some(start), Some(destination)) = (start, destination) {
|
if let (Some(start), Some(destination)) = (start, destination) {
|
||||||
if destination == start {
|
if math::is_close_to(destination.0, start.0)
|
||||||
|
&& math::is_close_to(destination.1, start.1)
|
||||||
|
{
|
||||||
skia_path.close();
|
skia_path.close();
|
||||||
open = false;
|
open = false;
|
||||||
}
|
}
|
||||||
@@ -65,15 +82,113 @@ impl Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_skia_path(path: skia::Path) -> Self {
|
||||||
|
let nv = path.count_verbs();
|
||||||
|
let mut verbs = vec![0; nv];
|
||||||
|
path.get_verbs(&mut verbs);
|
||||||
|
|
||||||
|
let np = path.count_points();
|
||||||
|
let mut points = Vec::with_capacity(np);
|
||||||
|
points.resize(np, skia::Point::default());
|
||||||
|
path.get_points(&mut points);
|
||||||
|
|
||||||
|
let mut segments = Vec::new();
|
||||||
|
|
||||||
|
let mut current_point = 0;
|
||||||
|
for verb in verbs {
|
||||||
|
let verb = to_verb(verb);
|
||||||
|
match verb {
|
||||||
|
skia::path::Verb::Move => {
|
||||||
|
let p = points[current_point];
|
||||||
|
segments.push(Segment::MoveTo((p.x, p.y)));
|
||||||
|
current_point += 1;
|
||||||
|
}
|
||||||
|
skia::path::Verb::Line => {
|
||||||
|
let p = points[current_point];
|
||||||
|
segments.push(Segment::LineTo((p.x, p.y)));
|
||||||
|
current_point += 1;
|
||||||
|
}
|
||||||
|
skia::path::Verb::Quad => {
|
||||||
|
let p1 = points[current_point];
|
||||||
|
let p2 = points[current_point + 1];
|
||||||
|
segments.push(Segment::CurveTo(((p1.x, p1.y), (p1.x, p1.y), (p2.x, p2.y))));
|
||||||
|
current_point += 2;
|
||||||
|
}
|
||||||
|
skia::path::Verb::Conic => {
|
||||||
|
// TODO: There is no way currently to access the conic weight
|
||||||
|
// to transform this correctly
|
||||||
|
let p1 = points[current_point];
|
||||||
|
let p2 = points[current_point + 1];
|
||||||
|
segments.push(Segment::CurveTo(((p1.x, p1.y), (p1.x, p1.y), (p2.x, p2.y))));
|
||||||
|
current_point += 2;
|
||||||
|
}
|
||||||
|
skia::path::Verb::Cubic => {
|
||||||
|
let p1 = points[current_point];
|
||||||
|
let p2 = points[current_point + 1];
|
||||||
|
let p3 = points[current_point + 2];
|
||||||
|
segments.push(Segment::CurveTo(((p1.x, p1.y), (p2.x, p2.y), (p3.x, p3.y))));
|
||||||
|
current_point += 3;
|
||||||
|
}
|
||||||
|
skia::path::Verb::Close => {
|
||||||
|
segments.push(Segment::Close);
|
||||||
|
}
|
||||||
|
skia::path::Verb::Done => {
|
||||||
|
segments.push(Segment::Close);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Path::new(segments)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_skia_path(&self) -> skia::Path {
|
pub fn to_skia_path(&self) -> skia::Path {
|
||||||
self.skia_path.snapshot()
|
self.skia_path.snapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, p: skia::Point) -> bool {
|
||||||
|
self.skia_path.contains(p)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_open(&self) -> bool {
|
pub fn is_open(&self) -> bool {
|
||||||
self.open
|
self.open
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, mtx: &Matrix) {
|
pub fn transform(&mut self, mtx: &Matrix) {
|
||||||
|
self.segments.iter_mut().for_each(|s| match s {
|
||||||
|
Segment::MoveTo(p) => {
|
||||||
|
let np = mtx.map_point(skia::Point::new(p.0, p.1));
|
||||||
|
p.0 = np.x;
|
||||||
|
p.1 = np.y;
|
||||||
|
}
|
||||||
|
Segment::LineTo(p) => {
|
||||||
|
let np = mtx.map_point(skia::Point::new(p.0, p.1));
|
||||||
|
p.0 = np.x;
|
||||||
|
p.1 = np.y;
|
||||||
|
}
|
||||||
|
Segment::CurveTo((c1, c2, p)) => {
|
||||||
|
let nc1 = mtx.map_point(skia::Point::new(c1.0, c1.1));
|
||||||
|
c1.0 = nc1.x;
|
||||||
|
c1.1 = nc1.y;
|
||||||
|
|
||||||
|
let nc2 = mtx.map_point(skia::Point::new(c2.0, c2.1));
|
||||||
|
c2.0 = nc2.x;
|
||||||
|
c2.1 = nc2.y;
|
||||||
|
|
||||||
|
let np = mtx.map_point(skia::Point::new(p.0, p.1));
|
||||||
|
p.0 = np.x;
|
||||||
|
p.1 = np.y;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
|
||||||
self.skia_path.transform(mtx);
|
self.skia_path.transform(mtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn segments(&self) -> &Vec<Segment> {
|
||||||
|
&self.segments
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bounds(&self) -> math::Bounds {
|
||||||
|
math::Bounds::from_rect(self.skia_path.bounds())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
200
render-wasm/src/shapes/shape_to_path.rs
Normal file
200
render-wasm/src/shapes/shape_to_path.rs
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
use skia_safe::Matrix;
|
||||||
|
|
||||||
|
use super::{Corners, Path, Segment, Shape, StructureEntry, Type};
|
||||||
|
use crate::math;
|
||||||
|
|
||||||
|
use crate::shapes::text_paths::TextPaths;
|
||||||
|
use crate::state::ShapesPool;
|
||||||
|
use crate::uuid::Uuid;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
const BEZIER_CIRCLE_C: f32 = 0.551_915_05;
|
||||||
|
|
||||||
|
pub trait ToPath {
|
||||||
|
fn to_path(
|
||||||
|
&self,
|
||||||
|
shapes: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
|
) -> Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CornerType {
|
||||||
|
TopLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomRight,
|
||||||
|
BottomLeft,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_corner(
|
||||||
|
corner_type: CornerType,
|
||||||
|
from: (f32, f32),
|
||||||
|
to: (f32, f32),
|
||||||
|
r: math::Point,
|
||||||
|
) -> Segment {
|
||||||
|
let x = match &corner_type {
|
||||||
|
CornerType::TopLeft => from.0,
|
||||||
|
CornerType::TopRight => from.0 - r.x,
|
||||||
|
CornerType::BottomRight => to.0 - r.x,
|
||||||
|
CornerType::BottomLeft => to.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let y = match &corner_type {
|
||||||
|
CornerType::TopLeft => from.1 - r.y,
|
||||||
|
CornerType::TopRight => from.1,
|
||||||
|
CornerType::BottomRight => to.1 - (r.y * 2.0),
|
||||||
|
CornerType::BottomLeft => to.1 - r.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = r.x * 2.0;
|
||||||
|
let height = r.y * 2.0;
|
||||||
|
|
||||||
|
let c = BEZIER_CIRCLE_C;
|
||||||
|
let c1x = x + (width / 2.0) * (1.0 - c);
|
||||||
|
let c2x = x + (width / 2.0) * (1.0 + c);
|
||||||
|
let c1y = y + (height / 2.0) * (1.0 - c);
|
||||||
|
let c2y = y + (height / 2.0) * (1.0 + c);
|
||||||
|
|
||||||
|
let h1 = match &corner_type {
|
||||||
|
CornerType::TopLeft => (from.0, c1y),
|
||||||
|
CornerType::TopRight => (c2x, from.1),
|
||||||
|
CornerType::BottomRight => (from.0, c2y),
|
||||||
|
CornerType::BottomLeft => (c1x, from.1),
|
||||||
|
};
|
||||||
|
|
||||||
|
let h2 = match &corner_type {
|
||||||
|
CornerType::TopLeft => (c1x, to.1),
|
||||||
|
CornerType::TopRight => (to.0, c1y),
|
||||||
|
CornerType::BottomRight => (c2x, to.1),
|
||||||
|
CornerType::BottomLeft => (to.0, c2y),
|
||||||
|
};
|
||||||
|
|
||||||
|
Segment::CurveTo((h1, h2, to))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rect_segments(shape: &Shape, corners: Option<Corners>) -> Vec<Segment> {
|
||||||
|
let sr = shape.selrect;
|
||||||
|
|
||||||
|
if let Some([r1, r2, r3, r4]) = corners {
|
||||||
|
let p1 = (sr.x(), sr.y() + r1.y);
|
||||||
|
let p2 = (sr.x() + r1.x, sr.y());
|
||||||
|
let p3 = (sr.x() + sr.width() - r2.x, sr.y());
|
||||||
|
let p4 = (sr.x() + sr.width(), sr.y() + r2.y);
|
||||||
|
let p5 = (sr.x() + sr.width(), sr.y() + sr.height() - r3.y);
|
||||||
|
let p6 = (sr.x() + sr.width() - r3.x, sr.y() + sr.height());
|
||||||
|
let p7 = (sr.x() + r4.x, sr.y() + sr.height());
|
||||||
|
let p8 = (sr.x(), sr.y() + sr.height() - r4.y);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Segment::MoveTo(p1),
|
||||||
|
make_corner(CornerType::TopLeft, p1, p2, r1),
|
||||||
|
Segment::LineTo(p3),
|
||||||
|
make_corner(CornerType::TopRight, p3, p4, r2),
|
||||||
|
Segment::LineTo(p5),
|
||||||
|
make_corner(CornerType::BottomRight, p5, p6, r3),
|
||||||
|
Segment::LineTo(p7),
|
||||||
|
make_corner(CornerType::BottomLeft, p7, p8, r4),
|
||||||
|
Segment::LineTo(p1),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
Segment::MoveTo((sr.x(), sr.y())),
|
||||||
|
Segment::LineTo((sr.x() + sr.width(), sr.y())),
|
||||||
|
Segment::LineTo((sr.x() + sr.width(), sr.y() + sr.height())),
|
||||||
|
Segment::LineTo((sr.x(), sr.y() + sr.height())),
|
||||||
|
Segment::Close,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn circle_segments(shape: &Shape) -> Vec<Segment> {
|
||||||
|
let sr = shape.selrect;
|
||||||
|
let mx = sr.x() + sr.width() / 2.0;
|
||||||
|
let my = sr.y() + sr.height() / 2.0;
|
||||||
|
let ex = sr.x() + sr.width();
|
||||||
|
let ey = sr.y() + sr.height();
|
||||||
|
|
||||||
|
let c = BEZIER_CIRCLE_C;
|
||||||
|
let c1x = sr.x() + (sr.width() / 2.0 * (1.0 - c));
|
||||||
|
let c2x = sr.x() + (sr.width() / 2.0 * (1.0 + c));
|
||||||
|
let c1y = sr.y() + (sr.height() / 2.0 * (1.0 - c));
|
||||||
|
let c2y = sr.y() + (sr.height() / 2.0 * (1.0 + c));
|
||||||
|
|
||||||
|
let p1x = mx;
|
||||||
|
let p1y = sr.y();
|
||||||
|
let p2x = ex;
|
||||||
|
let p2y = my;
|
||||||
|
let p3x = mx;
|
||||||
|
let p3y = ey;
|
||||||
|
let p4x = sr.x();
|
||||||
|
let p4y = my;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Segment::MoveTo((p1x, p1y)),
|
||||||
|
Segment::CurveTo(((c2x, p1y), (p2x, c1y), (p2x, p2y))),
|
||||||
|
Segment::CurveTo(((p2x, c2y), (c2x, p3y), (p3x, p3y))),
|
||||||
|
Segment::CurveTo(((c1x, p3y), (p4x, c2y), (p4x, p4y))),
|
||||||
|
Segment::CurveTo(((p4x, c1y), (c1x, p1y), (p1x, p1y))),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join_paths(path: Path, other: Path) -> Path {
|
||||||
|
let mut segments = path.segments().clone();
|
||||||
|
segments.extend(other.segments().iter());
|
||||||
|
Path::new(segments)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToPath for Shape {
|
||||||
|
fn to_path(
|
||||||
|
&self,
|
||||||
|
shapes: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
structure: &HashMap<Uuid, Vec<StructureEntry>>,
|
||||||
|
) -> Path {
|
||||||
|
let shape = self.transformed(modifiers.get(&self.id));
|
||||||
|
match shape.shape_type {
|
||||||
|
Type::Frame(ref frame) => {
|
||||||
|
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||||
|
let mut result = Path::new(rect_segments(&shape, frame.corners));
|
||||||
|
for id in children {
|
||||||
|
let Some(shape) = shapes.get(&id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
result = join_paths(result, shape.to_path(shapes, modifiers, structure));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::Group(_) => {
|
||||||
|
let children = shape.modified_children_ids(structure.get(&shape.id), true);
|
||||||
|
let mut result = Path::default();
|
||||||
|
for id in children {
|
||||||
|
let Some(shape) = shapes.get(&id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
result = join_paths(result, shape.to_path(shapes, modifiers, structure));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
Type::Bool(bool_data) => bool_data.path,
|
||||||
|
|
||||||
|
Type::Rect(ref rect) => Path::new(rect_segments(&shape, rect.corners)),
|
||||||
|
|
||||||
|
Type::Path(path_data) => path_data,
|
||||||
|
|
||||||
|
Type::Circle => Path::new(circle_segments(&shape)),
|
||||||
|
|
||||||
|
Type::SVGRaw(_) => Path::default(),
|
||||||
|
|
||||||
|
Type::Text(text) => {
|
||||||
|
let text_paths = TextPaths::new(text);
|
||||||
|
let mut result = Path::default();
|
||||||
|
for (path, _) in text_paths.get_paths(true) {
|
||||||
|
result = join_paths(result, Path::from_skia_path(path));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
math::Rect,
|
math::{Matrix, Rect},
|
||||||
render::{default_font, DEFAULT_EMOJI_FONT},
|
render::{default_font, DEFAULT_EMOJI_FONT},
|
||||||
};
|
};
|
||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
@@ -181,6 +181,16 @@ impl TextContent {
|
|||||||
let height = auto_height(&mut paragraphs, self.width());
|
let height = auto_height(&mut paragraphs, self.width());
|
||||||
(self.width(), height)
|
(self.width(), height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transform(&mut self, transform: &Matrix) {
|
||||||
|
let left = self.bounds.left();
|
||||||
|
let right = self.bounds.right();
|
||||||
|
let top = self.bounds.top();
|
||||||
|
let bottom = self.bounds.bottom();
|
||||||
|
let p1 = transform.map_point(skia::Point::new(left, top));
|
||||||
|
let p2 = transform.map_point(skia::Point::new(right, bottom));
|
||||||
|
self.bounds = Rect::from_ltrb(p1.x, p1.y, p2.x, p2.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextContent {
|
impl Default for TextContent {
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
use crate::shapes::text::TextContent;
|
use crate::shapes::text::TextContent;
|
||||||
use skia_safe::{self as skia, textlayout::ParagraphBuilder, Path, Paint};
|
use skia_safe::{
|
||||||
|
self as skia, textlayout::Paragraph as SkiaParagraph, textlayout::ParagraphBuilder,
|
||||||
|
FontMetrics, Point, Rect, TextBlob,
|
||||||
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::{with_state_mut, STATE};
|
||||||
|
|
||||||
pub struct TextPaths(TextContent);
|
pub struct TextPaths(TextContent);
|
||||||
|
|
||||||
// Note: This class is not being currently used.
|
// Note: This class is not being currently used.
|
||||||
@@ -12,10 +17,9 @@ impl TextPaths {
|
|||||||
Self(content)
|
Self(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_skia_paragraphs(&self) -> Vec<ParagraphBuilder> {
|
pub fn get_skia_paragraphs(&self) -> Vec<Vec<ParagraphBuilder>> {
|
||||||
let mut paragraphs = self.to_paragraphs();
|
let paragraphs = self.to_paragraphs();
|
||||||
self.collect_paragraphs(&mut paragraphs);
|
self.collect_paragraphs(paragraphs)
|
||||||
paragraphs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_paths(&self, antialias: bool) -> Vec<(skia::Path, skia::Paint)> {
|
pub fn get_paths(&self, antialias: bool) -> Vec<(skia::Path, skia::Paint)> {
|
||||||
@@ -23,64 +27,67 @@ impl TextPaths {
|
|||||||
|
|
||||||
let mut offset_y = self.bounds.y();
|
let mut offset_y = self.bounds.y();
|
||||||
let mut paragraphs = self.get_skia_paragraphs();
|
let mut paragraphs = self.get_skia_paragraphs();
|
||||||
for paragraph_builder in paragraphs.iter_mut() {
|
|
||||||
// 1. Get paragraph and set the width layout
|
|
||||||
let mut skia_paragraph = paragraph_builder.build();
|
|
||||||
let text = paragraph_builder.get_text();
|
|
||||||
let paragraph_width = self.bounds.width();
|
|
||||||
skia_paragraph.layout(paragraph_width);
|
|
||||||
|
|
||||||
let mut line_offset_y = offset_y;
|
for paragraphs in paragraphs.iter_mut() {
|
||||||
|
for paragraph_builder in paragraphs.iter_mut() {
|
||||||
|
// 1. Get paragraph and set the width layout
|
||||||
|
let mut skia_paragraph = paragraph_builder.build();
|
||||||
|
let text = paragraph_builder.get_text();
|
||||||
|
let paragraph_width = self.bounds.width();
|
||||||
|
skia_paragraph.layout(paragraph_width);
|
||||||
|
|
||||||
// 2. Iterate through each line in the paragraph
|
let mut line_offset_y = offset_y;
|
||||||
for line_metrics in skia_paragraph.get_line_metrics() {
|
|
||||||
let line_baseline = line_metrics.baseline as f32;
|
|
||||||
let start = line_metrics.start_index;
|
|
||||||
let end = line_metrics.end_index;
|
|
||||||
|
|
||||||
// 3. Get styles present in line for each text leaf
|
// 2. Iterate through each line in the paragraph
|
||||||
let style_metrics = line_metrics.get_style_metrics(start..end);
|
for line_metrics in skia_paragraph.get_line_metrics() {
|
||||||
|
let line_baseline = line_metrics.baseline as f32;
|
||||||
|
let start = line_metrics.start_index;
|
||||||
|
let end = line_metrics.end_index;
|
||||||
|
|
||||||
let mut offset_x = 0.0;
|
// 3. Get styles present in line for each text leaf
|
||||||
|
let style_metrics = line_metrics.get_style_metrics(start..end);
|
||||||
|
|
||||||
for (i, (start_index, style_metric)) in style_metrics.iter().enumerate() {
|
let mut offset_x = 0.0;
|
||||||
let end_index = style_metrics.get(i + 1).map_or(end, |next| next.0);
|
|
||||||
|
|
||||||
let start_byte = text
|
for (i, (start_index, style_metric)) in style_metrics.iter().enumerate() {
|
||||||
.char_indices()
|
let end_index = style_metrics.get(i + 1).map_or(end, |next| next.0);
|
||||||
.nth(*start_index)
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.unwrap_or(0);
|
|
||||||
let end_byte = text
|
|
||||||
.char_indices()
|
|
||||||
.nth(end_index)
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.unwrap_or(text.len());
|
|
||||||
|
|
||||||
let leaf_text = &text[start_byte..end_byte];
|
let start_byte = text
|
||||||
|
.char_indices()
|
||||||
|
.nth(*start_index)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let end_byte = text
|
||||||
|
.char_indices()
|
||||||
|
.nth(end_index)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.unwrap_or(text.len());
|
||||||
|
|
||||||
let font = skia_paragraph.get_font_at(*start_index);
|
let leaf_text = &text[start_byte..end_byte];
|
||||||
|
|
||||||
let blob_offset_x = self.bounds.x() + line_metrics.left as f32 + offset_x;
|
let font = skia_paragraph.get_font_at(*start_index);
|
||||||
let blob_offset_y = line_offset_y;
|
|
||||||
|
|
||||||
// 4. Get the path for each text leaf
|
let blob_offset_x = self.bounds.x() + line_metrics.left as f32 + offset_x;
|
||||||
if let Some((text_path, paint)) = self.generate_text_path(
|
let blob_offset_y = line_offset_y;
|
||||||
leaf_text,
|
|
||||||
&font,
|
// 4. Get the path for each text leaf
|
||||||
blob_offset_x,
|
if let Some((text_path, paint)) = self.generate_text_path(
|
||||||
blob_offset_y,
|
leaf_text,
|
||||||
style_metric,
|
&font,
|
||||||
antialias,
|
blob_offset_x,
|
||||||
) {
|
blob_offset_y,
|
||||||
let text_width = font.measure_text(leaf_text, None).0;
|
style_metric,
|
||||||
offset_x += text_width;
|
antialias,
|
||||||
paths.push((text_path, paint));
|
) {
|
||||||
|
let text_width = font.measure_text(leaf_text, None).0;
|
||||||
|
offset_x += text_width;
|
||||||
|
paths.push((text_path, paint));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
line_offset_y = offset_y + line_baseline;
|
||||||
}
|
}
|
||||||
line_offset_y = offset_y + line_baseline;
|
offset_y += skia_paragraph.height();
|
||||||
}
|
}
|
||||||
offset_y += skia_paragraph.height();
|
|
||||||
}
|
}
|
||||||
paths
|
paths
|
||||||
}
|
}
|
||||||
@@ -164,7 +171,6 @@ impl TextPaths {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_text_blob_path(
|
fn get_text_blob_path(
|
||||||
leaf_text: &str,
|
leaf_text: &str,
|
||||||
font: &skia::Font,
|
font: &skia::Font,
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
use crate::shapes::{Path, Segment};
|
#![allow(unused_mut, unused_variables)]
|
||||||
use crate::{mem, with_current_shape_mut, STATE};
|
use indexmap::IndexSet;
|
||||||
|
use mem::SerializableResult;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::math::bools;
|
||||||
|
use crate::shapes::{BoolType, Path, Segment, ToPath};
|
||||||
|
use crate::uuid;
|
||||||
|
use crate::{mem, with_current_shape, with_current_shape_mut, with_state, STATE};
|
||||||
|
|
||||||
const RAW_SEGMENT_DATA_SIZE: usize = size_of::<RawSegmentData>();
|
const RAW_SEGMENT_DATA_SIZE: usize = size_of::<RawSegmentData>();
|
||||||
|
|
||||||
@@ -13,6 +20,19 @@ enum RawSegmentData {
|
|||||||
Close = 0x04,
|
Close = 0x04,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RawSegmentData {
|
||||||
|
pub fn from_segment(segment: Segment) -> Self {
|
||||||
|
match segment {
|
||||||
|
Segment::MoveTo(to) => RawSegmentData::MoveTo(RawMoveCommand::new(to)),
|
||||||
|
Segment::LineTo(to) => RawSegmentData::LineTo(RawLineCommand::new(to)),
|
||||||
|
Segment::CurveTo((c1, c2, to)) => {
|
||||||
|
RawSegmentData::CurveTo(RawCurveCommand::new(c1, c2, to))
|
||||||
|
}
|
||||||
|
Segment::Close => RawSegmentData::Close,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<[u8; size_of::<RawSegmentData>()]> for RawSegmentData {
|
impl From<[u8; size_of::<RawSegmentData>()]> for RawSegmentData {
|
||||||
fn from(bytes: [u8; size_of::<RawSegmentData>()]) -> Self {
|
fn from(bytes: [u8; size_of::<RawSegmentData>()]) -> Self {
|
||||||
unsafe { std::mem::transmute(bytes) }
|
unsafe { std::mem::transmute(bytes) }
|
||||||
@@ -30,6 +50,28 @@ impl TryFrom<&[u8]> for RawSegmentData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SerializableResult for RawSegmentData {
|
||||||
|
type BytesType = [u8; RAW_SEGMENT_DATA_SIZE];
|
||||||
|
|
||||||
|
fn from_bytes(bytes: Self::BytesType) -> Self {
|
||||||
|
unsafe { std::mem::transmute(bytes) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bytes(&self) -> Self::BytesType {
|
||||||
|
let ptr = self as *const RawSegmentData as *const u8;
|
||||||
|
let bytes: &[u8] = unsafe { std::slice::from_raw_parts(ptr, RAW_SEGMENT_DATA_SIZE) };
|
||||||
|
let mut result = [0; RAW_SEGMENT_DATA_SIZE];
|
||||||
|
result.copy_from_slice(bytes);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generic trait doesn't know the size of the array. This is why the
|
||||||
|
// clone needs to be here even if it could be generic.
|
||||||
|
fn clone_to_slice(&self, slice: &mut [u8]) {
|
||||||
|
slice.clone_from_slice(&self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
#[repr(C, align(4))]
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
struct RawMoveCommand {
|
struct RawMoveCommand {
|
||||||
@@ -37,6 +79,15 @@ struct RawMoveCommand {
|
|||||||
x: f32,
|
x: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
}
|
}
|
||||||
|
impl RawMoveCommand {
|
||||||
|
pub fn new((x, y): (f32, f32)) -> Self {
|
||||||
|
Self {
|
||||||
|
_padding: [0u32; 4],
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
#[repr(C, align(4))]
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
@@ -46,6 +97,16 @@ struct RawLineCommand {
|
|||||||
y: f32,
|
y: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RawLineCommand {
|
||||||
|
pub fn new((x, y): (f32, f32)) -> Self {
|
||||||
|
Self {
|
||||||
|
_padding: [0u32; 4],
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
#[repr(C, align(4))]
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
struct RawCurveCommand {
|
struct RawCurveCommand {
|
||||||
@@ -57,6 +118,19 @@ struct RawCurveCommand {
|
|||||||
y: f32,
|
y: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RawCurveCommand {
|
||||||
|
pub fn new((c1_x, c1_y): (f32, f32), (c2_x, c2_y): (f32, f32), (x, y): (f32, f32)) -> Self {
|
||||||
|
Self {
|
||||||
|
c1_x,
|
||||||
|
c1_y,
|
||||||
|
c2_x,
|
||||||
|
c2_y,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<RawSegmentData> for Segment {
|
impl From<RawSegmentData> for Segment {
|
||||||
fn from(value: RawSegmentData) -> Self {
|
fn from(value: RawSegmentData) -> Self {
|
||||||
match value {
|
match value {
|
||||||
@@ -92,6 +166,53 @@ pub extern "C" fn set_shape_path_content() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn current_to_path() -> *mut u8 {
|
||||||
|
let mut result = Vec::<RawSegmentData>::default();
|
||||||
|
with_current_shape!(state, |shape: &Shape| {
|
||||||
|
let path = shape.to_path(&state.shapes, &state.modifiers, &state.structure);
|
||||||
|
result = path
|
||||||
|
.segments()
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(RawSegmentData::from_segment)
|
||||||
|
.collect();
|
||||||
|
});
|
||||||
|
|
||||||
|
mem::write_vec(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn calculate_bool(raw_bool_type: u8) -> *mut u8 {
|
||||||
|
let bytes = mem::bytes_or_empty();
|
||||||
|
|
||||||
|
let entries: IndexSet<Uuid> = bytes
|
||||||
|
.chunks(size_of::<<Uuid as SerializableResult>::BytesType>())
|
||||||
|
.map(|data| Uuid::from_bytes(data.try_into().unwrap()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
mem::free_bytes();
|
||||||
|
|
||||||
|
let bool_type = BoolType::from(raw_bool_type);
|
||||||
|
let result;
|
||||||
|
with_state!(state, {
|
||||||
|
let path = bools::bool_from_shapes(
|
||||||
|
bool_type,
|
||||||
|
&entries,
|
||||||
|
&state.shapes,
|
||||||
|
&state.modifiers,
|
||||||
|
&state.structure,
|
||||||
|
);
|
||||||
|
result = path
|
||||||
|
.segments()
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(RawSegmentData::from_segment)
|
||||||
|
.collect();
|
||||||
|
});
|
||||||
|
mem::write_vec(result)
|
||||||
|
}
|
||||||
|
|
||||||
// Extracts a string from the bytes slice until the next null byte (0) and returns the result as a `String`.
|
// Extracts a string from the bytes slice until the next null byte (0) and returns the result as a `String`.
|
||||||
// Updates the `start` index to the end of the extracted string.
|
// Updates the `start` index to the end of the extracted string.
|
||||||
fn extract_string(start: &mut usize, bytes: &[u8]) -> String {
|
fn extract_string(start: &mut usize, bytes: &[u8]) -> String {
|
||||||
|
|||||||
Reference in New Issue
Block a user