0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-09 21:41:23 -05:00

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.
This commit is contained in:
Andrey Antukh 2023-09-14 16:24:24 +02:00 committed by Alonso Torres
parent 807f475a2d
commit 4f23852bca
8 changed files with 292 additions and 225 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
*-init.clj
*.css.json
*.jar
*.orig
*.penpot

View file

@ -6,4 +6,4 @@
(ns app.common.files.defaults)
(def version 31)
(def version 32)

View file

@ -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))))

View file

@ -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)))

View file

@ -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 _]

View file

@ -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)))))

View file

@ -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

View file

@ -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