SVG import enhancements

This commit is contained in:
alonso.torres 2021-03-24 17:31:16 +01:00 committed by Andrey Antukh
parent 92e07c3b54
commit c380400578
4 changed files with 584 additions and 181 deletions

View file

@ -164,27 +164,28 @@
(gsh/setup-selrect)))) (gsh/setup-selrect))))
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] (defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
(let [svg-transform (usvg/parse-transform (:transform attrs)) (when (and (contains? attrs :d) (not (empty? (:d attrs)) ))
path-content (ugp/path->content (:d attrs)) (let [svg-transform (usvg/parse-transform (:transform attrs))
content (cond-> path-content path-content (ugp/path->content (:d attrs))
svg-transform content (cond-> path-content
(gsh/transform-content svg-transform)) svg-transform
(gsh/transform-content svg-transform))
selrect (gsh/content->selrect content) selrect (gsh/content->selrect content)
points (gsh/rect->points selrect) points (gsh/rect->points selrect)
origin (gpt/negate (gpt/point svg-data))] origin (gpt/negate (gpt/point svg-data))]
(-> {:id (uuid/next) (-> {:id (uuid/next)
:type :path :type :path
:name name :name name
:frame-id frame-id :frame-id frame-id
:content content :content content
:selrect selrect :selrect selrect
:points points} :points points}
(assoc :svg-viewbox (select-keys selrect [:x :y :width :height])) (assoc :svg-viewbox (select-keys selrect [:x :y :width :height]))
(assoc :svg-attrs (dissoc attrs :d :transform)) (assoc :svg-attrs (dissoc attrs :d :transform))
(assoc :svg-transform svg-transform) (assoc :svg-transform svg-transform)
(gsh/translate-to-frame origin)))) (gsh/translate-to-frame origin)))))
(defn calculate-rect-metadata [rect-data transform] (defn calculate-rect-metadata [rect-data transform]
(let [points (-> (gsh/rect->points rect-data) (let [points (-> (gsh/rect->points rect-data)
@ -333,7 +334,7 @@
;; SVG graphic elements ;; SVG graphic elements
;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use ;; :circle :ellipse :image :line :path :polygon :polyline :rect :text :use
(let [shape (-> (case tag (let [shape (-> (case tag
(:g :a) (create-group name frame-id svg-data element-data) (:g :a :svg) (create-group name frame-id svg-data element-data)
:rect (create-rect-shape name frame-id svg-data element-data) :rect (create-rect-shape name frame-id svg-data element-data)
(:circle (:circle
:ellipse) (create-circle-shape name frame-id svg-data element-data) :ellipse) (create-circle-shape name frame-id svg-data element-data)
@ -344,34 +345,42 @@
:image (create-image-shape name frame-id svg-data element-data) :image (create-image-shape name frame-id svg-data element-data)
#_other (create-raw-svg name frame-id svg-data element-data)) #_other (create-raw-svg name frame-id svg-data element-data))
(assoc :svg-defs (select-keys (:defs svg-data) references)) )
(setup-fill) shape (when (some? shape)
(setup-stroke)) (-> shape
(assoc :svg-defs (select-keys (:defs svg-data) references))
(setup-fill)
(setup-stroke)))
children (cond->> (:content element-data) children (cond->> (:content element-data)
(= tag :g) (= tag :g)
(mapv #(usvg/inherit-attributes attrs %)))] (mapv #(usvg/inherit-attributes attrs %)))]
[shape children])))) [shape children]))))
(defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data [unames [rchs uchs]] [index data]] (defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data [unames [rchs uchs]] [index data]]
(let [[shape children] (parse-svg-element frame-id svg-data data unames) (let [[shape children] (parse-svg-element frame-id svg-data data unames)]
shape-id (:id shape) (if (some? shape)
(let [shape-id (:id shape)
[rch1 uch1] (dwc/add-shape-changes page-id objects selected shape false) [rch1 uch1] (dwc/add-shape-changes page-id objects selected shape false)
;; Mov-objects won't have undo because we "delete" the object in the undo of the ;; Mov-objects won't have undo because we "delete" the object in the undo of the
;; previous operation ;; previous operation
rch2 [{:type :mov-objects rch2 [{:type :mov-objects
:parent-id parent-id :parent-id parent-id
:frame-id frame-id :frame-id frame-id
:page-id page-id :page-id page-id
:index index :index index
:shapes [shape-id]}] :shapes [shape-id]}]
;; Careful! the undo changes are concatenated reversed (we undo in reverse order ;; Careful! the undo changes are concatenated reversed (we undo in reverse order
changes [(d/concat rchs rch1 rch2) (d/concat uch1 uchs)] changes [(d/concat rchs rch1 rch2) (d/concat uch1 uchs)]
unames (conj unames (:name shape)) unames (conj unames (:name shape))
reducer-fn (partial add-svg-child-changes page-id objects selected frame-id shape-id svg-data)] reducer-fn (partial add-svg-child-changes page-id objects selected frame-id shape-id svg-data)]
(reduce reducer-fn [unames changes] (d/enumerate children)))) (reduce reducer-fn [unames changes] (d/enumerate children)))
;; Cannot create the data from curren tags
[unames [rchs uchs]])))
(declare create-svg-shapes) (declare create-svg-shapes)

View file

@ -32,20 +32,24 @@
mask (when show-mask? (first childs)) mask (when show-mask? (first childs))
childs (if show-mask? (rest childs) childs) childs (if show-mask? (rest childs) childs)
props (-> (attrs/extract-style-attrs shape) mask-props (when (and mask (not expand-mask))
(obj/merge! #js {:clipPath (clip-str mask)
#js {:pointerEvents pointer-events :mask (mask-str mask)})
:clipPath (when (and mask (not expand-mask)) (clip-str mask)) mask-wrapper (if (and mask (not expand-mask))
:mask (when (and mask (not expand-mask)) (mask-str mask))}))] "g"
mf/Fragment)
[:> :g props props (-> (attrs/extract-style-attrs shape))]
(when mask
[:> render-mask #js {:frame frame :mask mask}])
(for [item childs] [:> mask-wrapper mask-props
[:& shape-wrapper {:frame frame [:> :g (attrs/extract-style-attrs shape)
:shape item (when mask
:key (:id item)}])])))) [:> render-mask #js {:frame frame :mask mask}])
(for [item childs]
[:& shape-wrapper {:frame frame
:shape item
:key (:id item)}])]]))))

View file

@ -60,15 +60,19 @@
transform-filter? (and (contains? usvg/filter-tags tag) transform-filter? (and (contains? usvg/filter-tags tag)
(= "userSpaceOnUse" (get attrs :filterUnits "objectBoundingBox"))) (= "userSpaceOnUse" (get attrs :filterUnits "objectBoundingBox")))
transform-mask? (and (= :mask tag)
(= "userSpaceOnUse" (get attrs :maskUnits "objectBoundingBox")))
attrs (-> attrs attrs (-> attrs
(usvg/update-attr-ids prefix-id) (usvg/update-attr-ids prefix-id)
(usvg/clean-attrs) (usvg/clean-attrs)
(cond-> (cond->
transform-gradient? (add-matrix :gradientTransform transform) transform-gradient? (add-matrix :gradientTransform transform)
transform-pattern? (add-matrix :patternTransform transform) transform-pattern? (add-matrix :patternTransform transform)
transform-clippath? (add-matrix :transform transform) transform-clippath? (add-matrix :transform transform)
transform-filter? (transform-region transform))) (or transform-filter?
transform-mask?) (transform-region transform)))
[wrapper wrapper-props] (if (= tag :mask) [wrapper wrapper-props] (if (= tag :mask)
["g" #js {:transform (str transform)}] ["g" #js {:transform (str transform)}]

View file

@ -24,6 +24,472 @@
(defonce matrices-regex #"(matrix|translate|scale|rotate|skewX|skewY)\(([^\)]*)\)") (defonce matrices-regex #"(matrix|translate|scale|rotate|skewX|skewY)\(([^\)]*)\)")
(defonce number-regex #"[+-]?\d*(\.\d+)?(e[+-]?\d+)?") (defonce number-regex #"[+-]?\d*(\.\d+)?(e[+-]?\d+)?")
(defonce tags-to-remove #{:defs :linearGradient :radialGradient :metadata :mask :clipPath :filter :title})
;; https://www.w3.org/TR/SVG11/eltindex.html
(defonce svg-tags-list
#{:a
:altGlyph
:altGlyphDef
:altGlyphItem
:animate
:animateColor
:animateMotion
:animateTransform
:circle
:clipPath
:color-profile
:cursor
:defs
:desc
:ellipse
:feBlend
:feColorMatrix
:feComponentTransfer
:feComposite
:feConvolveMatrix
:feDiffuseLighting
:feDisplacementMap
:feDistantLight
:feFlood
:feFuncA
:feFuncB
:feFuncG
:feFuncR
:feGaussianBlur
:feImage
:feMerge
:feMergeNode
:feMorphology
:feOffset
:fePointLight
:feSpecularLighting
:feSpotLight
:feTile
:feTurbulence
:filter
:font
:font-face
:font-face-format
:font-face-name
:font-face-src
:font-face-uri
:foreignObject
:g
:glyph
:glyphRef
:hkern
:image
:line
:linearGradient
:marker
:mask
:metadata
:missing-glyph
:mpath
:path
:pattern
:polygon
:polyline
:radialGradient
:rect
:script
:set
:stop
:style
:svg
:switch
:symbol
:text
:textPath
:title
:tref
:tspan
:use
:view
:vkern
})
;; https://www.w3.org/TR/SVG11/attindex.html
(defonce svg-attr-list
#{:accent-height
:accumulate
:additive
:alphabetic
:amplitude
:arabic-form
:ascent
:attributeName
:attributeType
:azimuth
:baseFrequency
:baseProfile
:bbox
:begin
:bias
:by
:calcMode
:cap-height
:class
:clipPathUnits
:contentScriptType
:contentStyleType
:cx
:cy
:d
:descent
:diffuseConstant
:divisor
:dur
:dx
:dy
:edgeMode
:elevation
:end
:exponent
:externalResourcesRequired
:fill
:filterRes
:filterUnits
:font-family
:font-size
:font-stretch
:font-style
:font-variant
:font-weight
:format
:from
:fx
:fy
:g1
:g2
:glyph-name
:glyphRef
:gradientTransform
:gradientUnits
:hanging
:height
:horiz-adv-x
:horiz-origin-x
:horiz-origin-y
:id
:ideographic
:in
:in2
:intercept
:k
:k1
:k2
:k3
:k4
:kernelMatrix
:kernelUnitLength
:keyPoints
:keySplines
:keyTimes
:lang
:lengthAdjust
:limitingConeAngle
:local
:markerHeight
:markerUnits
:markerWidth
:maskContentUnits
:maskUnits
:mathematical
:max
:media
:method
:min
:mode
:name
:numOctaves
:offset
;; We don't support events
;;:onabort
;;:onactivate
;;:onbegin
;;:onclick
;;:onend
;;:onerror
;;:onfocusin
;;:onfocusout
;;:onload
;;:onmousedown
;;:onmousemove
;;:onmouseout
;;:onmouseover
;;:onmouseup
;;:onrepeat
;;:onresize
;;:onscroll
;;:onunload
;;:onzoom
:operator
:order
:orient
:orientation
:origin
:overline-position
:overline-thickness
:panose-1
:path
:pathLength
:patternContentUnits
:patternTransform
:patternUnits
:points
:pointsAtX
:pointsAtY
:pointsAtZ
:preserveAlpha
:preserveAspectRatio
:primitiveUnits
:r
:radius
:refX
:refY
:rendering-intent
:repeatCount
:repeatDur
:requiredExtensions
:requiredFeatures
:restart
:result
:rotate
:rx
:ry
:scale
:seed
:slope
:spacing
:specularConstant
:specularExponent
:spreadMethod
:startOffset
:stdDeviation
:stemh
:stemv
:stitchTiles
:strikethrough-position
:strikethrough-thickness
:string
:style
:surfaceScale
:systemLanguage
:tableValues
:target
:targetX
:targetY
:textLength
:title
:to
:transform
:type
:u1
:u2
:underline-position
:underline-thickness
:unicode
:unicode-range
:units-per-em
:v-alphabetic
:v-hanging
:v-ideographic
:v-mathematical
:values
:version
:vert-adv-y
:vert-origin-x
:vert-origin-y
:viewBox
:viewTarget
:width
:widths
:x
:x-height
:x1
:x2
:xChannelSelector
:xmlns:xlink
:xlink:actuate
:xlink:arcrole
:xlink:href
:xlink:role
:xlink:show
:xlink:title
:xlink:type
:xml:base
:xml:lang
:xml:space
:y
:y1
:y2
:yChannelSelector
:z
:zoomAndPan})
(defonce svg-present-list
#{:alignment-baseline
:baseline-shift
:clip-path
:clip-rule
:clip
:color-interpolation-filters
:color-interpolation
:color-profile
:color-rendering
:color
:cursor
:direction
:display
:dominant-baseline
:enable-background
:fill-opacity
:fill-rule
:fill
:filter
:flood-color
:flood-opacity
:font-family
:font-size-adjust
:font-size
:font-stretch
:font-style
:font-variant
:font-weight
:glyph-orientation-horizontal
:glyph-orientation-vertical
:image-rendering
:kerning
:letter-spacing
:lighting-color
:marker-end
:marker-mid
:marker-start
:mask
:opacity
:overflow
:pointer-events
:shape-rendering
:stop-color
:stop-opacity
:stroke-dasharray
:stroke-dashoffset
:stroke-linecap
:stroke-linejoin
:stroke-miterlimit
:stroke-opacity
:stroke-width
:stroke
:text-anchor
:text-decoration
:text-rendering
:unicode-bidi
:visibility
:word-spacing
:writing-mode
:mask-type})
(defonce inheritable-props
[:clip-rule
:color
:color-interpolation
:color-interpolation-filters
:color-profile
:color-rendering
:cursor
:direction
:dominant-baseline
:fill
:fill-opacity
:fill-rule
:font
:font-family
:font-size
:font-size-adjust
:font-stretch
:font-style
:font-variant
:font-weight
:glyph-orientation-horizontal
:glyph-orientation-vertical
:image-rendering
:letter-spacing
:marker
:marker-end
:marker-mid
:marker-start
:paint-order
:pointer-events
:shape-rendering
:stroke
:stroke-dasharray
:stroke-dashoffset
:stroke-linecap
:stroke-linejoin
:stroke-miterlimit
:stroke-opacity
:stroke-width
:text-anchor
:text-rendering
:transform
:visibility
:word-spacing
:writing-mode])
(defonce gradient-tags
#{:linearGradient
:radialGradient})
(defonce filter-tags
#{:filter
:feBlend
:feColorMatrix
:feComponentTransfer
:feComposite
:feConvolveMatrix
:feDiffuseLighting
:feDisplacementMap
:feFlood
:feGaussianBlur
:feImage
:feMerge
:feMorphology
:feOffset
:feSpecularLighting
:feTile
:feTurbulence})
;; Props not supported by react we need to keep them lowercase
(defonce non-react-props
#{:mask-type})
;; Defaults for some tags per spec https://www.w3.org/TR/SVG11/single-page.html
;; they are basicaly the defaults that can be percents and we need to replace because
;; otherwise won't work as expected in the workspace
(defonce svg-tag-defaults
(let [filter-default {:units :filterUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:x "-10%" :y "-10%" :width "120%" :height "120%"}}
filter-values (->> filter-tags
(reduce #(merge %1 (hash-map %2 filter-default)) {}))]
(merge {:linearGradient {:units :gradientUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:x1 "0%" :y1 "0%" :x2 "100%" :y2 "0%"}}
:radialGradient {:units :gradientUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:cx "50%" :cy "50%" :r "50%"}}
:mask {:units :maskUnits
:default "userSpaceOnUse"
"objectBoundingBox" {}
"userSpaceOnUse" {:x "-10%" :y "-10%" :width "120%" :height "120%"}}}
filter-values)))
(defn extract-ids [val] (defn extract-ids [val]
(->> (re-seq xml-id-regex val) (->> (re-seq xml-id-regex val)
(mapv second))) (mapv second)))
@ -61,35 +527,52 @@
(defn clean-attrs (defn clean-attrs
"Transforms attributes to their react equivalent" "Transforms attributes to their react equivalent"
[attrs] ([attrs] (clean-attrs attrs true))
(letfn [(transform-key [key] ([attrs whitelist?]
(-> (d/name key) (letfn [(known-property? [[key _]]
(str/replace ":" "-") (or (not whitelist?)
(str/camel) (contains? svg-attr-list key )
(keyword))) (contains? svg-present-list key )))
(format-styles [style-str] (transform-key [key]
(->> (str/split style-str ";") (if (contains? non-react-props key)
(map str/trim) key
(map #(str/split % ":")) (-> (d/name key)
(group-by first) (str/replace ":" "-")
(map (fn [[key val]] (str/camel)
(vector (keyword))))
(transform-key key)
(second (first val)))))
(into {})))
(map-fn [[key val]] (lowercase-key [key]
(let [key (keyword key)] (-> (d/name key)
(cond (str/lower)
(= key :class) [:className val] (keyword)))
(and (= key :style) (string? val)) [key (format-styles val)]
(and (= key :style) (map? val)) [key (clean-attrs val)]
:else (vector (transform-key key) val))))]
(->> attrs (format-styles [style-str]
(map map-fn) (->> (str/split style-str ";")
(into {})))) (map str/trim)
(map #(str/split % ":"))
(group-by first)
(map (fn [[key val]]
(vector
(transform-key key)
(second (first val)))))
(into {})))
(map-fn [[key val]]
(let [key (keyword key)]
(cond
(= key :class) [:className val]
(and (= key :style) (string? val)) [key (format-styles val)]
(and (= key :style) (map? val)) [key (clean-attrs val false)]
:else (vector (transform-key key) val))))
]
(let [filtered-props (->> attrs (remove known-property?) (map first))]
(when (seq filtered-props)
(.warn js/console "Unknown properties: " (str/join ", " filtered-props ))))
(into {} (comp (filter known-property?) (map map-fn)) attrs))))
(defn update-attr-ids (defn update-attr-ids
"Replaces the ids inside a property" "Replaces the ids inside a property"
@ -126,18 +609,16 @@
(reduce visit-node result (:content node))))] (reduce visit-node result (:content node))))]
(visit-node {} content))) (visit-node {} content)))
(def remove-tags #{:defs :linearGradient})
(defn extract-defs [{:keys [tag attrs content] :as node}] (defn extract-defs [{:keys [tag attrs content] :as node}]
(if-not (map? node) (if-not (map? node)
[{} node] [{} node]
(let [remove-node? (fn [{:keys [tag]}] (contains? remove-tags tag)) (let [remove-node? (fn [{:keys [tag]}] (and (some? tag)
(or (contains? tags-to-remove tag)
(not (contains? svg-tags-list tag)))))
rec-result (->> (:content node) (map extract-defs)) rec-result (->> (:content node) (map extract-defs))
node (assoc node :content (->> rec-result (map second) (filterv (comp not remove-node?)))) node (assoc node :content (->> rec-result (map second) (filterv (comp not remove-node?))))
current-node-defs (if (contains? attrs :id) current-node-defs (if (contains? attrs :id)
(hash-map (:id attrs) node) (hash-map (:id attrs) node)
(hash-map)) (hash-map))
@ -319,76 +800,6 @@
transform transform
(update :transform append-transform)))) (update :transform append-transform))))
(defonce inheritable-props
[:clip-rule
:color
:color-interpolation
:color-interpolation-filters
:color-profile
:color-rendering
:cursor
:direction
:dominant-baseline
:fill
:fill-opacity
:fill-rule
:font
:font-family
:font-size
:font-size-adjust
:font-stretch
:font-style
:font-variant
:font-weight
:glyph-orientation-horizontal
:glyph-orientation-vertical
:image-rendering
:letter-spacing
:marker
:marker-end
:marker-mid
:marker-start
:paint-order
:pointer-events
:shape-rendering
:stroke
:stroke-dasharray
:stroke-dashoffset
:stroke-linecap
:stroke-linejoin
:stroke-miterlimit
:stroke-opacity
:stroke-width
:text-anchor
:text-rendering
:transform
:visibility
:word-spacing
:writing-mode])
(defonce gradient-tags
#{:linearGradient
:radialGradient})
(defonce filter-tags
#{:filter
:feBlend
:feColorMatrix
:feComponentTransfer
:feComposite
:feConvolveMatrix
:feDiffuseLighting
:feDisplacementMap
:feFlood
:feGaussianBlur
:feImage
:feMerge
:feMorphology
:feOffset
:feSpecularLighting
:feTile
:feTurbulence})
(defn inherit-attributes [group-attrs {:keys [attrs] :as node}] (defn inherit-attributes [group-attrs {:keys [attrs] :as node}]
(if (map? node) (if (map? node)
(let [attrs (-> (format-styles attrs) (let [attrs (-> (format-styles attrs)
@ -425,31 +836,6 @@
(reduce-content (:content node))) (reduce-content (:content node)))
value))) value)))
;; Defaults for some tags per spec https://www.w3.org/TR/SVG11/single-page.html
;; they are basicaly the defaults that can be percents and we need to replace because
;; otherwise won't work as expected in the workspace
(defonce svg-tag-defaults
(let [filter-default {:units :filterUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:x "-10%" :y "-10%" :width "120%" :height "120%"}}
filter-values (->> filter-tags
(reduce #(merge %1 (hash-map %2 filter-default)) {}))]
(merge {:linearGradient {:units :gradientUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:x1 "0%" :y1 "0%" :x2 "100%" :y2 "0%"}}
:radialGradient {:units :gradientUnits
:default "objectBoundingBox"
"objectBoundingBox" {}
"userSpaceOnUse" {:cx "50%" :cy "50%" :r "50%"}}
:mask {:units :maskUnits
:default "userSpaceOnUse"
"objectBoundingBox" {}
"userSpaceOnUse" {:x "-10%" :y "-10%" :width "120%" :height "120%"}}}
filter-values)))
(defn fix-default-values (defn fix-default-values
"Gives values to some SVG elements which defaults won't work when imported into the platform" "Gives values to some SVG elements which defaults won't work when imported into the platform"
[svg-data] [svg-data]