0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-10 00:58:26 -05:00

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

View file

@ -32,20 +32,24 @@
mask (when show-mask? (first childs))
childs (if show-mask? (rest childs) childs)
props (-> (attrs/extract-style-attrs shape)
(obj/merge!
#js {:pointerEvents pointer-events
:clipPath (when (and mask (not expand-mask)) (clip-str mask))
:mask (when (and mask (not expand-mask)) (mask-str mask))}))]
mask-props (when (and mask (not expand-mask))
#js {:clipPath (clip-str mask)
:mask (mask-str mask)})
mask-wrapper (if (and mask (not expand-mask))
"g"
mf/Fragment)
[:> :g props
(when mask
[:> render-mask #js {:frame frame :mask mask}])
props (-> (attrs/extract-style-attrs shape))]
(for [item childs]
[:& shape-wrapper {:frame frame
:shape item
:key (:id item)}])]))))
[:> mask-wrapper mask-props
[:> :g (attrs/extract-style-attrs shape)
(when mask
[:> 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)
(= "userSpaceOnUse" (get attrs :filterUnits "objectBoundingBox")))
transform-mask? (and (= :mask tag)
(= "userSpaceOnUse" (get attrs :maskUnits "objectBoundingBox")))
attrs (-> attrs
(usvg/update-attr-ids prefix-id)
(usvg/clean-attrs)
(cond->
transform-gradient? (add-matrix :gradientTransform transform)
transform-pattern? (add-matrix :patternTransform transform)
transform-clippath? (add-matrix :transform transform)
transform-filter? (transform-region transform)))
transform-gradient? (add-matrix :gradientTransform transform)
transform-pattern? (add-matrix :patternTransform transform)
transform-clippath? (add-matrix :transform transform)
(or transform-filter?
transform-mask?) (transform-region transform)))
[wrapper wrapper-props] (if (= tag :mask)
["g" #js {:transform (str transform)}]

View file

@ -24,6 +24,472 @@
(defonce matrices-regex #"(matrix|translate|scale|rotate|skewX|skewY)\(([^\)]*)\)")
(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]
(->> (re-seq xml-id-regex val)
(mapv second)))
@ -61,35 +527,52 @@
(defn clean-attrs
"Transforms attributes to their react equivalent"
[attrs]
(letfn [(transform-key [key]
(-> (d/name key)
(str/replace ":" "-")
(str/camel)
(keyword)))
([attrs] (clean-attrs attrs true))
([attrs whitelist?]
(letfn [(known-property? [[key _]]
(or (not whitelist?)
(contains? svg-attr-list key )
(contains? svg-present-list key )))
(format-styles [style-str]
(->> (str/split style-str ";")
(map str/trim)
(map #(str/split % ":"))
(group-by first)
(map (fn [[key val]]
(vector
(transform-key key)
(second (first val)))))
(into {})))
(transform-key [key]
(if (contains? non-react-props key)
key
(-> (d/name key)
(str/replace ":" "-")
(str/camel)
(keyword))))
(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)]
:else (vector (transform-key key) val))))]
(lowercase-key [key]
(-> (d/name key)
(str/lower)
(keyword)))
(->> attrs
(map map-fn)
(into {}))))
(format-styles [style-str]
(->> (str/split style-str ";")
(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
"Replaces the ids inside a property"
@ -126,18 +609,16 @@
(reduce visit-node result (:content node))))]
(visit-node {} content)))
(def remove-tags #{:defs :linearGradient})
(defn extract-defs [{:keys [tag attrs content] :as node}]
(if-not (map? 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))
node (assoc node :content (->> rec-result (map second) (filterv (comp not remove-node?))))
current-node-defs (if (contains? attrs :id)
(hash-map (:id attrs) node)
(hash-map))
@ -319,76 +800,6 @@
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}]
(if (map? node)
(let [attrs (-> (format-styles attrs)
@ -425,31 +836,6 @@
(reduce-content (:content node)))
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
"Gives values to some SVG elements which defaults won't work when imported into the platform"
[svg-data]