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,6 +164,7 @@
(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}]
(when (and (contains? attrs :d) (not (empty? (:d attrs)) ))
(let [svg-transform (usvg/parse-transform (:transform attrs)) (let [svg-transform (usvg/parse-transform (:transform attrs))
path-content (ugp/path->content (:d attrs)) path-content (ugp/path->content (:d attrs))
content (cond-> path-content content (cond-> path-content
@ -184,7 +185,7 @@
(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,17 +345,22 @@
: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))
)
shape (when (some? shape)
(-> shape
(assoc :svg-defs (select-keys (:defs svg-data) references)) (assoc :svg-defs (select-keys (:defs svg-data) references))
(setup-fill) (setup-fill)
(setup-stroke)) (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)
@ -371,7 +377,10 @@
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))]
[:> mask-wrapper mask-props
[:> :g (attrs/extract-style-attrs shape)
(when mask (when mask
[:> render-mask #js {:frame frame :mask mask}]) [:> render-mask #js {:frame frame :mask mask}])
(for [item childs] (for [item childs]
[:& shape-wrapper {:frame frame [:& shape-wrapper {:frame frame
:shape item :shape item
:key (:id item)}])])))) :key (:id item)}])]]))))

View file

@ -60,6 +60,9 @@
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)
@ -68,7 +71,8 @@
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,11 +527,24 @@
(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?]
(letfn [(known-property? [[key _]]
(or (not whitelist?)
(contains? svg-attr-list key )
(contains? svg-present-list key )))
(transform-key [key]
(if (contains? non-react-props key)
key
(-> (d/name key) (-> (d/name key)
(str/replace ":" "-") (str/replace ":" "-")
(str/camel) (str/camel)
(keyword))))
(lowercase-key [key]
(-> (d/name key)
(str/lower)
(keyword))) (keyword)))
(format-styles [style-str] (format-styles [style-str]
@ -84,12 +563,16 @@
(cond (cond
(= key :class) [:className val] (= key :class) [:className val]
(and (= key :style) (string? val)) [key (format-styles val)] (and (= key :style) (string? val)) [key (format-styles val)]
(and (= key :style) (map? val)) [key (clean-attrs val)] (and (= key :style) (map? val)) [key (clean-attrs val false)]
:else (vector (transform-key key) val))))] :else (vector (transform-key key) val))))
(->> attrs ]
(map map-fn)
(into {})))) (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]