From 4f23852bca171d8db62320d7380c105ae3a4f93f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 14 Sep 2023 16:24:24 +0200 Subject: [PATCH] :sparkles: Improve svg shapes attrs handling And collaterally it improves performance since now the attrs processing is done in the import and not in the render. --- .gitignore | 1 + common/src/app/common/files/defaults.cljc | 2 +- common/src/app/common/files/migrations.cljc | 19 +- common/src/app/common/svg.cljc | 128 ++++--- .../app/main/data/workspace/svg_upload.cljs | 335 +++++++++--------- frontend/src/app/main/ui/shapes/attrs.cljs | 6 +- frontend/src/app/main/ui/shapes/svg_raw.cljs | 24 +- .../app/main/ui/viewer/inspect/render.cljs | 2 +- 8 files changed, 292 insertions(+), 225 deletions(-) diff --git a/.gitignore b/.gitignore index 883e309b42..2f64a77ec6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *-init.clj +*.css.json *.jar *.orig *.penpot diff --git a/common/src/app/common/files/defaults.cljc b/common/src/app/common/files/defaults.cljc index 7f0ff7c137..44590ae885 100644 --- a/common/src/app/common/files/defaults.cljc +++ b/common/src/app/common/files/defaults.cljc @@ -6,4 +6,4 @@ (ns app.common.files.defaults) -(def version 31) +(def version 32) diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index a27ce7370f..7154e3df53 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -18,6 +18,7 @@ [app.common.math :as mth] [app.common.pages.changes :as cpc] [app.common.pages.helpers :as cph] + [app.common.svg :as csvg] [app.common.text :as txt] [app.common.types.shape :as cts] [app.common.uuid :as uuid] @@ -587,7 +588,23 @@ (update-container [container] (d/update-when container :objects update-vals update-object))] - + (-> data + (update :pages-index update-vals update-container) + (update :components update-vals update-container)))) + +(defmethod migrate 32 + [data] + (letfn [(update-object [object] + (as-> object object + (if (contains? object :svg-attrs) + (update object :svg-attrs csvg/attrs->props) + object) + (if (contains? object :svg-viewbox) + (update object :svg-viewbox grc/make-rect) + object))) + + (update-container [container] + (update container :objects update-vals update-object))] (-> data (update :pages-index update-vals update-container) (update :components update-vals update-container)))) diff --git a/common/src/app/common/svg.cljc b/common/src/app/common/svg.cljc index 3e6559cbca..b1facba03c 100644 --- a/common/src/app/common/svg.cljc +++ b/common/src/app/common/svg.cljc @@ -6,10 +6,9 @@ (ns app.common.svg (:require - #?(:cljs ["./svg_optimizer.js" :as svgo]) - - + #?(:cljs ["./svg_optimizer.js" :as svgo]) [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] @@ -548,34 +547,74 @@ [num-str] (cond (str/starts-with? num-str ".") - (str "0" num-str) + (dm/str "0" num-str) (str/starts-with? num-str "-.") - (str "-0" (subs num-str 1)) + (dm/str "-0" (subs num-str 1)) :else num-str)) -(defn format-styles - "Transforms attributes to their react equivalent" - [attrs] - (letfn [(format-styles [style-str] - (if (string? style-str) - (->> (str/split style-str ";") - (map str/trim) - (map #(str/split % ":")) - (group-by first) - (map (fn [[key val]] - (vector (keyword key) (second (first val))))) - (into {})) - style-str))] +(defn- camelize + [s] + (when (string? s) + #?(:cljs (js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", s) + :clj (str/camel s)))) - (cond-> attrs - (contains? attrs :style) - (update :style format-styles)))) +(defn parse-style + [style] + (reduce (fn [res item] + (let [[k v] (-> (str/trim item) (str/split ":" 2)) + k (keyword k)] + (if (contains? res k) + res + (assoc res (keyword k) v)))) + {} + (str/split style ";"))) + +;; FIXME: rename to `format-style` or directly use parse-style on code... +(defn format-styles + "Transform string based styles found on attrs map to key-value map." + [attrs] + (if (contains? attrs :style) + (update attrs :style + (fn [style] + (if (string? style) + (parse-style style) + style))) + attrs)) + +(defn attrs->props + "Transforms and cleans svg attributes to react compatible props" + ([attrs] + (attrs->props attrs true)) + + ([attrs whitelist?] + (reduce-kv (fn [res k v] + (if (or (not whitelist?) + (contains? svg-attr-list k) + (contains? svg-present-list k)) + (cond + (= k :class) + (assoc res :className val) + + (= k :style) + (let [v (if (string? v) (parse-style v) v)] + (assoc res k (attrs->props v false))) + + :else + (let [k (if (contains? non-react-props k) + k + (-> k d/name camelize keyword))] + (assoc res k v))) + res)) + {} + attrs))) (defn clean-attrs - "Transforms attributes to their react equivalent" + "Transforms attributes to their react equivalent + + DEPRECATED: replaced by attrs->props" ([attrs] (clean-attrs attrs true)) @@ -590,8 +629,7 @@ #?(:cljs (js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", s) :clj (str/camel s)))) - - (transform-att [key] + (transform-key [key] (if (contains? non-react-props key) key (-> (d/name key) @@ -604,17 +642,26 @@ (map #(str/split % ":")) (group-by first) (map (fn [[key val]] - [(transform-att key) + [(transform-key key) (second (first val))])) (into {}))) - (clean-att [[att val]] - (let [att (keyword att)] + (clean-key [[key val]] + (let [key (keyword key)] (cond - (= att :class) [:className val] - (and (= att :style) (string? val)) [att (format-styles val)] - (and (= att :style) (map? val)) [att (clean-attrs val false)] - :else [(transform-att att) val])))] + (= key :class) + [:className val] + + (and (= key :style) + (string? val)) + [key (format-styles val)] + + (and (= key :style) + (map? val)) + [key (clean-attrs val false)] + + :else + [(transform-key key) val])))] ;; Removed this warning because slows a lot rendering with big svgs #_(let [filtered-props (->> attrs (remove known-property?) (map first))] @@ -623,7 +670,7 @@ (into {} (comp (filter known-property?) - (map clean-att)) + (map clean-key)) attrs)))) (defn update-attr-ids @@ -649,16 +696,17 @@ (defn replace-attrs-ids "Replaces the ids inside a property" [attrs ids-mapping] - (if (and ids-mapping (seq ids-mapping)) - (update-attr-ids attrs (fn [id] (get ids-mapping id id))) - ;; Ids-mapping is null - attrs)) + (if (empty? ids-mapping) + attrs + (update-attr-ids attrs (fn [id] (get ids-mapping id id))))) -(defn generate-id-mapping [content] +(defn generate-id-mapping + [content] (letfn [(visit-node [result node] - (let [element-id (get-in node [:attrs :id]) - result (cond-> result - element-id (assoc element-id (str (uuid/next))))] + (let [element-id (dm/get-in node [:attrs :id]) + result (if (some? element-id) + (assoc result element-id (dm/str (uuid/next))) + result)] (reduce visit-node result (:content node))))] (visit-node {} content))) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index c3ad55e5b9..b41dee8282 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -14,6 +14,8 @@ [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.common :as gsc] + [app.common.geom.shapes.transforms :as gst] [app.common.math :as mth] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] @@ -65,7 +67,8 @@ (contains? cts/blend-modes clean-value)) clean-value)) -(defn- svg-dimensions [data] +(defn- svg-dimensions + [data] (let [width (dm/get-in data [:attrs :width] 100) height (dm/get-in data [:attrs :height] 100) viewbox (dm/get-in data [:attrs :viewBox] (str "0 0 " width " " height)) @@ -81,12 +84,15 @@ (defn tag->name "Given a tag returns its layer name" [tag] - (str "svg-" (cond (string? tag) tag - (keyword? tag) (d/name tag) - (nil? tag) "node" - :else (str tag)))) + (let [suffix (cond + (string? tag) tag + (keyword? tag) (d/name tag) + (nil? tag) "node" + :else (dm/str tag))] + (dm/str "svg-" suffix))) -(defn setup-fill [shape] +(defn setup-fill + [shape] (let [color-attr (str/trim (dm/get-in shape [:svg-attrs :fill])) color-attr (if (= color-attr "currentColor") clr/black color-attr) color-style (str/trim (dm/get-in shape [:svg-attrs :style :fill])) @@ -104,16 +110,16 @@ (update :svg-attrs dissoc :fill) (assoc-in [:fills 0 :fill-color] (uc/parse-color color-style))) - (dm/get-in shape [:svg-attrs :fill-opacity]) - (-> (update :svg-attrs dissoc :fill-opacity) - (update-in [:svg-attrs :style] dissoc :fill-opacity) - (assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :fill-opacity]) + (dm/get-in shape [:svg-attrs :fillOpacity]) + (-> (update :svg-attrs dissoc :fillOpacity) + (update-in [:svg-attrs :style] dissoc :fillOpacity) + (assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :fillOpacity]) (d/parse-double 1)))) - (dm/get-in shape [:svg-attrs :style :fill-opacity]) - (-> (update-in [:svg-attrs :style] dissoc :fill-opacity) - (update :svg-attrs dissoc :fill-opacity) - (assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :style :fill-opacity]) + (dm/get-in shape [:svg-attrs :style :fillOpacity]) + (-> (update-in [:svg-attrs :style] dissoc :fillOpacity) + (update :svg-attrs dissoc :fillOpacity) + (assoc-in [:fills 0 :fill-opacity] (-> (dm/get-in shape [:svg-attrs :style :fillOpacity]) (d/parse-double 1))))))) (defn- setup-stroke @@ -131,30 +137,30 @@ opacity (when (some? color) (d/parse-double - (or (:stroke-opacity attrs) - (:stroke-opacity style)) + (or (:strokeOpacity attrs) + (:strokeOpacity style)) 1)) width (when (some? color) (d/parse-double - (or (:stroke-width attrs) - (:stroke-width style)) + (or (:strokeWidth attrs) + (:strokeWidth style)) 1)) - linecap (or (get attrs :stroke-linecap) - (get style :stroke-linecap)) + linecap (or (get attrs :strokeLinecap) + (get style :strokeLinecap)) linecap (some-> linecap str/trim keyword) attrs (-> attrs (dissoc :stroke) - (dissoc :stroke-width) - (dissoc :stroke-opacity) + (dissoc :strokeWidth) + (dissoc :strokeOpacity) (update :style (fn [style] (-> style (dissoc :stroke) - (dissoc :stroke-linecap) - (dissoc :stroke-width) - (dissoc :stroke-opacity)))))] + (dissoc :strokeLinecap) + (dissoc :strokeWidth) + (dissoc :strokeOpacity)))))] (cond-> (assoc shape :svg-attrs attrs) (some? color) @@ -172,8 +178,8 @@ :stroke-cap-end linecap) (d/any-key? (dm/get-in shape [:strokes 0]) - :stroke-color :stroke-opacity :stroke-width - :stroke-cap-start :stroke-cap-end) + :strokeColor :strokeOpacity :strokeWidth + :strokeCapStart :strokeCapEnd) (assoc-in [:strokes 0 :stroke-style] :svg)))) (defn setup-opacity [shape] @@ -188,51 +194,52 @@ (assoc :opacity (-> (dm/get-in shape [:svg-attrs :style :opacity]) (d/parse-double 1)))) - (dm/get-in shape [:svg-attrs :mix-blend-mode]) - (-> (update :svg-attrs dissoc :mix-blend-mode) - (assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :mix-blend-mode]) assert-valid-blend-mode))) + (dm/get-in shape [:svg-attrs :mixBlendMode]) + (-> (update :svg-attrs dissoc :mixBlendMode) + (assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :mixBlendMode]) assert-valid-blend-mode))) - (dm/get-in shape [:svg-attrs :style :mix-blend-mode]) - (-> (update-in [:svg-attrs :style] dissoc :mix-blend-mode) - (assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :style :mix-blend-mode]) assert-valid-blend-mode))))) + (dm/get-in shape [:svg-attrs :style :mixBlendMode]) + (-> (update-in [:svg-attrs :style] dissoc :mixBlendMode) + (assoc :blend-mode (-> (dm/get-in shape [:svg-attrs :style :mixBlendMode]) assert-valid-blend-mode))))) (defn create-raw-svg [name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}] - (cts/setup-shape - {:type :svg-raw - :name name - :frame-id frame-id - :width width - :height height - :x x - :y y - :content (cond-> data - (map? data) (update :attrs csvg/clean-attrs)) - :svg-attrs attrs - :svg-viewbox {:width width - :height height - :x offset-x - :y offset-y}})) + (let [props (csvg/attrs->props attrs) + vbox (grc/make-rect offset-x offset-y width height)] + (cts/setup-shape + {:type :svg-raw + :name name + :frame-id frame-id + :width width + :height height + :x x + :y y + :content data + :svg-attrs props + :svg-viewbox vbox}))) (defn create-svg-root [frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] - (cts/setup-shape - {:type :group - :name name - :frame-id frame-id - :parent-id parent-id - :width width - :height height - :x (+ x offset-x) - :y (+ y offset-y) - :svg-attrs (-> attrs - (dissoc :viewBox) - (dissoc :xmlns) - (d/without-keys csvg/inheritable-props))})) + (let [props (-> (dissoc attrs :viewBox :view-box :xmlns) + (d/without-keys csvg/inheritable-props) + (csvg/attrs->props))] + (cts/setup-shape + {:type :group + :name name + :frame-id frame-id + :parent-id parent-id + :width width + :height height + :x (+ x offset-x) + :y (+ y offset-y) + :svg-attrs props}))) (defn create-group [name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}] - (let [svg-transform (csvg/parse-transform (:transform attrs))] + (let [transform (csvg/parse-transform (:transform attrs)) + attrs (-> (d/without-keys attrs csvg/inheritable-props) + (csvg/attrs->props)) + vbox (grc/make-rect offset-x offset-y width height)] (cts/setup-shape {:type :group :name name @@ -241,28 +248,22 @@ :y (+ y offset-y) :width width :height height - :svg-transform svg-transform - :svg-attrs (d/without-keys attrs csvg/inheritable-props) - - :svg-viewbox {:width width - :height height - :x offset-x - :y offset-y}}))) + :svg-transform transform + :svg-attrs attrs + :svg-viewbox vbox}))) (defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] (when (and (contains? attrs :d) (seq (:d attrs))) + (let [transform (csvg/parse-transform (:transform attrs)) + content (cond-> (upp/parse-path (:d attrs)) + (some? transform) + (gsh/transform-content transform)) - (let [svg-transform (csvg/parse-transform (:transform attrs)) - path-content (upp/parse-path (:d attrs)) - content (cond-> path-content - svg-transform - (gsh/transform-content svg-transform)) - - selrect (gsh/content->selrect content) - points (grc/rect->points selrect) - - origin (gpt/negate (gpt/point svg-data))] - + selrect (gsh/content->selrect content) + points (grc/rect->points selrect) + origin (gpt/negate (gpt/point svg-data)) + attrs (-> (dissoc attrs :d :transform) + (csvg/attrs->props))] (-> (cts/setup-shape {:type :path :name name @@ -270,17 +271,20 @@ :content content :selrect selrect :points points - :svg-viewbox (select-keys selrect [:x :y :width :height]) - :svg-attrs (dissoc attrs :d :transform) - :svg-transform svg-transform}) + :svg-viewbox selrect + :svg-attrs attrs + :svg-transform transform + :fills []}) (gsh/translate-to-frame origin))))) -(defn calculate-rect-metadata [rect-data transform] - (let [points (-> (grc/make-rect rect-data) - (grc/rect->points) - (gsh/transform-points transform)) - - [selrect transform transform-inverse] (gsh/calculate-geometry points)] +(defn calculate-rect-metadata + [rect transform] + (let [points (-> rect + (grc/rect->points) + (gsh/transform-points transform)) + center (gsc/points->center points) + selrect (gst/calculate-selrect points center) + transform (gst/calculate-transform points center selrect)] {:x (:x selrect) :y (:y selrect) @@ -289,107 +293,116 @@ :selrect selrect :points points :transform transform - :transform-inverse transform-inverse})) + :transform-inverse (when (some? transform) + (gmt/inverse transform))})) (defn- parse-rect-attrs [{:keys [x y width height]}] - {:x (d/parse-double x 0) - :y (d/parse-double y 0) - :width (d/parse-double width 1) - :height (d/parse-double height 1)}) + (grc/make-rect + (d/parse-double x 0) + (d/parse-double y 0) + (d/parse-double width 1) + (d/parse-double height 1))) (defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}] (let [transform (->> (csvg/parse-transform (:transform attrs)) (gmt/transform-in (gpt/point svg-data))) origin (gpt/negate (gpt/point svg-data)) - - rect-data (-> (parse-rect-attrs attrs) + rect (-> (parse-rect-attrs attrs) (update :x - (:x origin)) - (update :y - (:y origin)))] + (update :y - (:y origin))) + + props (-> (dissoc attrs :x :y :width :height :rx :ry :transform) + (csvg/attrs->props))] (cts/setup-shape - (-> (calculate-rect-metadata rect-data transform) + (-> (calculate-rect-metadata rect transform) (assoc :type :rect) (assoc :name name) (assoc :frame-id frame-id) - (assoc :svg-viewbox (select-keys rect-data [:x :y :width :height])) - (assoc :svg-attrs (dissoc attrs :x :y :width :height :rx :ry :transform)) + (assoc :svg-viewbox rect) + (assoc :svg-attrs props) + ;; We need to ensure fills are empty on import process + ;; because setup-shape assings one by default. + (assoc :fills []) (cond-> (contains? attrs :rx) (assoc :rx (d/parse-double (:rx attrs) 0))) (cond-> (contains? attrs :ry) (assoc :ry (d/parse-double (:ry attrs) 0))))))) - (defn- parse-circle-attrs [attrs] (into [] (comp (map (d/getf attrs)) (map d/parse-double)) [:cx :cy :r :rx :ry])) -(defn create-circle-shape [name frame-id svg-data {:keys [attrs] :as data}] +(defn create-circle-shape + [name frame-id svg-data {:keys [attrs] :as data}] (let [[cx cy r rx ry] (parse-circle-attrs attrs) transform (->> (csvg/parse-transform (:transform attrs)) (gmt/transform-in (gpt/point svg-data))) - rx (or r rx) - ry (or r ry) + rx (d/nilv r rx) + ry (d/nilv r ry) origin (gpt/negate (gpt/point svg-data)) - rect-data {:x (- cx rx (:x origin)) - :y (- cy ry (:y origin)) - :width (* 2 rx) - :height (* 2 ry)}] + rect (grc/make-rect + (- cx rx (:x origin)) + (- cy ry (:y origin)) + (* 2 rx) + (* 2 ry)) + props (-> (dissoc attrs :cx :cy :r :rx :ry :transform) + (csvg/attrs->props))] (cts/setup-shape - (-> (calculate-rect-metadata rect-data transform) + (-> (calculate-rect-metadata rect transform) (assoc :type :circle) (assoc :name name) (assoc :frame-id frame-id) - (assoc :svg-viewbox rect-data) - (assoc :svg-attrs (dissoc attrs :cx :cy :r :rx :ry :transform)))))) + (assoc :svg-viewbox rect) + (assoc :svg-attrs props) + (assoc :fills []))))) -(defn create-image-shape [name frame-id svg-data {:keys [attrs] :as data}] +(defn create-image-shape + [name frame-id svg-data {:keys [attrs] :as data}] (let [transform (->> (csvg/parse-transform (:transform attrs)) (gmt/transform-in (gpt/point svg-data))) image-url (or (:href attrs) (:xlink:href attrs)) image-data (dm/get-in svg-data [:image-data image-url]) - metadata {:width (:width image-data) :height (:height image-data) :mtype (:mtype image-data) :id (:id image-data)} origin (gpt/negate (gpt/point svg-data)) - rect-data (-> (parse-rect-attrs attrs) + rect (-> (parse-rect-attrs attrs) (update :x - (:x origin)) - (update :y - (:y origin)))] + (update :y - (:y origin))) + props (-> (dissoc attrs :x :y :width :height :href :xlink:href) + (csvg/attrs->props))] (when (some? image-data) (cts/setup-shape - (-> (calculate-rect-metadata rect-data transform) + (-> (calculate-rect-metadata rect transform) (assoc :type :image) (assoc :name name) (assoc :frame-id frame-id) (assoc :metadata metadata) - (assoc :svg-viewbox (select-keys rect-data [:x :y :width :height])) - (assoc :svg-attrs (dissoc attrs :x :y :width :height :href :xlink:href))))))) + (assoc :svg-viewbox rect) + (assoc :svg-attrs props)))))) - -(defn parse-svg-element [frame-id svg-data {:keys [tag attrs hidden] :as element-data} unames] - (let [attrs (csvg/format-styles attrs) - element-data (cond-> element-data (map? element-data) (assoc :attrs attrs)) - name (or (:id attrs) (tag->name tag)) +(defn parse-svg-element + [frame-id svg-data {:keys [tag attrs hidden] :as element} unames] + (let [name (or (:id attrs) (tag->name tag)) att-refs (csvg/find-attr-references attrs) - references (csvg/find-def-references (:defs svg-data) att-refs) - + defs (get svg-data :defs) + references (csvg/find-def-references defs att-refs) href-id (-> (or (:href attrs) (:xlink:href attrs) "") (subs 1)) - defs (:defs svg-data) - use-tag? (and (= :use tag) (contains? defs href-id))] (if use-tag? @@ -397,38 +410,43 @@ use-data (-> (get defs href-id) (update :attrs #(d/deep-merge % (dissoc attrs :xlink:href :href)))) displacement (gpt/point (d/parse-double (:x attrs "0")) (d/parse-double (:y attrs "0"))) - disp-matrix (str (gmt/translate-matrix displacement)) - element-data (-> element-data + disp-matrix (dm/str (gmt/translate-matrix displacement)) + element (-> element (assoc :tag :g) (update :attrs dissoc :x :y :width :height :href :xlink:href :transform) (update :attrs csvg/add-transform disp-matrix) (assoc :content [use-data]))] - (parse-svg-element frame-id svg-data element-data unames)) + (parse-svg-element frame-id svg-data element unames)) + + (let [;; SVG graphic elements + ;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use + shape (case tag + (:g :a :svg) (create-group name frame-id svg-data element) + :rect (create-rect-shape name frame-id svg-data element) + (:circle + :ellipse) (create-circle-shape name frame-id svg-data element) + :path (create-path-shape name frame-id svg-data element) + :polyline (create-path-shape name frame-id svg-data (-> element csvg/polyline->path)) + :polygon (create-path-shape name frame-id svg-data (-> element csvg/polygon->path)) + :line (create-path-shape name frame-id svg-data (-> element csvg/line->path)) + :image (create-image-shape name frame-id svg-data element) + #_other (create-raw-svg name frame-id svg-data element))] + - ;; SVG graphic elements - ;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use - (let [shape (-> (case tag - (:g :a :svg) (create-group name frame-id svg-data element-data) - :rect (create-rect-shape name frame-id svg-data element-data) - (:circle - :ellipse) (create-circle-shape name frame-id svg-data element-data) - :path (create-path-shape name frame-id svg-data element-data) - :polyline (create-path-shape name frame-id svg-data (-> element-data csvg/polyline->path)) - :polygon (create-path-shape name frame-id svg-data (-> element-data csvg/polygon->path)) - :line (create-path-shape name frame-id svg-data (-> element-data csvg/line->path)) - :image (create-image-shape name frame-id svg-data element-data) - #_other (create-raw-svg name frame-id svg-data element-data)))] (when (some? shape) (let [shape (-> shape - (assoc :svg-defs (select-keys (:defs svg-data) references)) + (assoc :svg-defs (select-keys defs references)) (setup-fill) (setup-stroke) - (setup-opacity))] - + (setup-opacity) + (update :svg-attrs (fn [attrs] + (if (empty? (:style attrs)) + (dissoc attrs :style) + attrs))))] [(cond-> shape hidden (assoc :hidden true)) - (cond->> (:content element-data) + (cond->> (:content element) (contains? csvg/parent-tags tag) (mapv #(csvg/inherit-attributes attrs %)))])))))) @@ -449,19 +467,6 @@ [unames children]))) -(defn data-uri->blob - [data-uri] - (let [[mtype b64-data] (str/split data-uri ";base64,") - mtype (subs mtype (inc (str/index-of mtype ":"))) - decoded (.atob js/window b64-data) - size (.-length ^js decoded) - content (js/Uint8Array. size)] - - (doseq [i (range 0 size)] - (aset content i (.charCodeAt decoded i))) - - (wapi/create-blob content mtype))) - (defn extract-name [url] (let [query-idx (str/last-index-of url "?") url (if (> query-idx 0) (subs url 0 query-idx) url) @@ -481,7 +486,7 @@ :url uri} (if (str/starts-with? uri "data:") {:name "image" - :content (data-uri->blob uri)} + :content (wapi/data-uri->blob uri)} {:name (extract-name uri)})))) (rx/mapcat (fn [uri-data] (->> (rp/cmd! (if (contains? uri-data :content) @@ -524,10 +529,6 @@ (csvg/fix-percents) (csvg/extract-defs)) - svg-data (assoc svg-data :defs def-nodes) - root-shape (create-svg-root frame-id parent-id svg-data) - root-id (:id root-shape) - ;; In penpot groups have the size of their children. To ;; respect the imported svg size and empty space let's create ;; a transparent shape as background to respect the imported @@ -547,6 +548,9 @@ (assoc :defs def-nodes) (assoc :content (into [background] (:content svg-data)))) + root-shape (create-svg-root frame-id parent-id svg-data) + root-id (:id root-shape) + ;; Create the root shape root-attrs (-> (:attrs svg-data) (csvg/format-styles)) @@ -561,7 +565,6 @@ (defn add-svg-shapes [svg-data position] - ;; (app.common.pprint/pprint svg-data {:length 100 :level 100}) (ptk/reify ::add-svg-shapes ptk/WatchEvent (watch [it state _] diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index 5acde896be..164eff602e 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -160,14 +160,10 @@ (empty? attrs)) #js {} (-> attrs - ;; TODO: revisit, why we need to execute it each render? Can - ;; we do this operation on importation and avoid unnecesary - ;; work on render? - (csvg/clean-attrs) (csvg/update-attr-ids (fn [id] (if (contains? defs id) - (str render-id "-" id) + (dm/str render-id "-" id) id))) (dissoc :id) (obj/map->obj))))) diff --git a/frontend/src/app/main/ui/shapes/svg_raw.cljs b/frontend/src/app/main/ui/shapes/svg_raw.cljs index 6903bc7321..6f0ab1dddf 100644 --- a/frontend/src/app/main/ui/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/shapes/svg_raw.cljs @@ -15,23 +15,25 @@ [rumext.v2 :as mf])) ;; Graphic tags -(defonce graphic-element? +(def graphic-element #{:svg :circle :ellipse :image :line :path :polygon :polyline :rect :symbol :text :textPath :use}) ;; Context to store a re-mapping of the ids (def svg-ids-ctx (mf/create-context nil)) (defn set-styles [attrs shape render-id] - (let [custom-attrs (-> (usa/get-style-props shape render-id) - (obj/unset! "transform")) + (let [props (-> (usa/get-style-props shape render-id) + (obj/unset! "transform")) - attrs (or attrs {}) - attrs (cond-> attrs - (string? (:style attrs)) csvg/clean-attrs) - style (obj/merge! (clj->js (:style attrs {})) - (obj/get custom-attrs "style"))] - (-> (clj->js attrs) - (obj/merge! custom-attrs) + attrs (if (map? attrs) + (-> attrs csvg/attrs->props obj/map->obj) + #js {}) + + style (obj/merge (obj/get attrs "style") + (obj/get props "style"))] + + (-> attrs + (obj/merge! props) (obj/set! "style" style)))) (defn translate-shape [attrs shape] @@ -39,7 +41,7 @@ " " (:transform attrs ""))] (cond-> attrs - (and (:svg-viewbox shape) (graphic-element? (-> shape :content :tag))) + (and (:svg-viewbox shape) (contains? graphic-element (-> shape :content :tag))) (assoc :transform transform)))) (mf/defc svg-root diff --git a/frontend/src/app/main/ui/viewer/inspect/render.cljs b/frontend/src/app/main/ui/viewer/inspect/render.cljs index 136f593b8b..c7480a7dc0 100644 --- a/frontend/src/app/main/ui/viewer/inspect/render.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/render.cljs @@ -61,7 +61,7 @@ childs (unchecked-get props "childs") frame (unchecked-get props "frame") render-wrapper? (or (not= :svg-raw (:type shape)) - (svg-raw/graphic-element? (get-in shape [:content :tag])))] + (contains? svg-raw/graphic-element (get-in shape [:content :tag])))] (if render-wrapper? [:> shape-container {:shape shape