mirror of
https://github.com/penpot/penpot.git
synced 2025-06-19 18:51:38 +02:00
✨ Move util.svg to common.svg and make it crossplatform
This commit is contained in:
parent
878f1d4090
commit
aa8300c085
11 changed files with 1029 additions and 1039 deletions
|
@ -5,9 +5,986 @@
|
||||||
;; Copyright (c) KALEIDOS INC
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
(ns app.common.svg
|
(ns app.common.svg
|
||||||
#?(:cljs
|
(:require
|
||||||
(:require
|
#?(:cljs ["./svg_optimizer.js" :as svgo])
|
||||||
["./svg_optimizer.js" :as svgo])))
|
|
||||||
|
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.geom.matrix :as gmt]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.math :as mth]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
;; Regex for XML ids per Spec
|
||||||
|
;; https://www.w3.org/TR/2008/REC-xml-20081126/#sec-common-syn
|
||||||
|
(def xml-id-regex #"#([:A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF][\.\-\:0-9\xB7A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0300-\u036F\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF]*)")
|
||||||
|
|
||||||
|
(def matrices-regex #"(matrix|translate|scale|rotate|skewX|skewY)\(([^\)]*)\)")
|
||||||
|
(def number-regex #"[+-]?\d*(\.\d+)?(e[+-]?\d+)?")
|
||||||
|
|
||||||
|
(def tags-to-remove #{:linearGradient :radialGradient :metadata :mask :clipPath :filter :title})
|
||||||
|
|
||||||
|
;; https://www.w3.org/TR/SVG11/eltindex.html
|
||||||
|
(def 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
|
||||||
|
:set
|
||||||
|
:stop
|
||||||
|
:style
|
||||||
|
:svg
|
||||||
|
:switch
|
||||||
|
:symbol
|
||||||
|
:text
|
||||||
|
:textPath
|
||||||
|
:title
|
||||||
|
:tref
|
||||||
|
:tspan
|
||||||
|
:use
|
||||||
|
:view
|
||||||
|
:vkern
|
||||||
|
})
|
||||||
|
|
||||||
|
;; https://www.w3.org/TR/SVG11/attindex.html
|
||||||
|
(def 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})
|
||||||
|
|
||||||
|
(def 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})
|
||||||
|
|
||||||
|
(def inheritable-props
|
||||||
|
[:style
|
||||||
|
: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])
|
||||||
|
|
||||||
|
(def gradient-tags
|
||||||
|
#{:linearGradient
|
||||||
|
:radialGradient})
|
||||||
|
|
||||||
|
(def filter-tags
|
||||||
|
#{:filter
|
||||||
|
:feBlend
|
||||||
|
:feColorMatrix
|
||||||
|
:feComponentTransfer
|
||||||
|
:feComposite
|
||||||
|
:feConvolveMatrix
|
||||||
|
:feDiffuseLighting
|
||||||
|
:feDisplacementMap
|
||||||
|
:feFlood
|
||||||
|
:feGaussianBlur
|
||||||
|
:feImage
|
||||||
|
:feMerge
|
||||||
|
:feMorphology
|
||||||
|
:feOffset
|
||||||
|
:feSpecularLighting
|
||||||
|
:feTile
|
||||||
|
:feTurbulence})
|
||||||
|
|
||||||
|
(def parent-tags
|
||||||
|
#{:g
|
||||||
|
:svg
|
||||||
|
:text
|
||||||
|
:tspan})
|
||||||
|
|
||||||
|
;; By spec: https://www.w3.org/TR/SVG11/single-page.html#struct-GElement
|
||||||
|
(def svg-group-safe-tags
|
||||||
|
#{:animate
|
||||||
|
:animateColor
|
||||||
|
:animateMotion
|
||||||
|
:animateTransform
|
||||||
|
:set
|
||||||
|
:desc
|
||||||
|
:metadata
|
||||||
|
:title
|
||||||
|
:circle
|
||||||
|
:ellipse
|
||||||
|
:line
|
||||||
|
:path
|
||||||
|
:polygon
|
||||||
|
:polyline
|
||||||
|
:rect
|
||||||
|
:defs
|
||||||
|
:g
|
||||||
|
:svg
|
||||||
|
:symbol
|
||||||
|
:use
|
||||||
|
:linearGradient
|
||||||
|
:radialGradient
|
||||||
|
:a
|
||||||
|
:altGlyphDef
|
||||||
|
:clipPath
|
||||||
|
:color-profile
|
||||||
|
:cursor
|
||||||
|
:filter
|
||||||
|
:font
|
||||||
|
:font-face
|
||||||
|
:foreignObject
|
||||||
|
:image
|
||||||
|
:marker
|
||||||
|
:mask
|
||||||
|
:pattern
|
||||||
|
:style
|
||||||
|
:switch
|
||||||
|
:text
|
||||||
|
:view})
|
||||||
|
|
||||||
|
;; Props not supported by react we need to keep them lowercase
|
||||||
|
(def non-react-props
|
||||||
|
#{:mask-type})
|
||||||
|
|
||||||
|
;; Defaults for some tags per spec https://www.w3.org/TR/SVG11/single-page.html
|
||||||
|
;; they are basically the defaults that can be percents and we need to replace because
|
||||||
|
;; otherwise won't work as expected in the workspace
|
||||||
|
(def 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]
|
||||||
|
(when (some? val)
|
||||||
|
(->> (re-seq xml-id-regex val)
|
||||||
|
(mapv second))))
|
||||||
|
|
||||||
|
(defn fix-dot-number
|
||||||
|
"Fixes decimal numbers starting in dot but without leading 0"
|
||||||
|
[num-str]
|
||||||
|
(cond
|
||||||
|
(str/starts-with? num-str ".")
|
||||||
|
(str "0" num-str)
|
||||||
|
|
||||||
|
(str/starts-with? num-str "-.")
|
||||||
|
(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))]
|
||||||
|
|
||||||
|
(cond-> attrs
|
||||||
|
(contains? attrs :style)
|
||||||
|
(update :style format-styles))))
|
||||||
|
|
||||||
|
(defn clean-attrs
|
||||||
|
"Transforms attributes to their react equivalent"
|
||||||
|
([attrs]
|
||||||
|
(clean-attrs attrs true))
|
||||||
|
|
||||||
|
([attrs whitelist?]
|
||||||
|
(letfn [(known-property? [[key _]]
|
||||||
|
(or (not whitelist?)
|
||||||
|
(contains? svg-attr-list key)
|
||||||
|
(contains? svg-present-list key)))
|
||||||
|
|
||||||
|
(camelize [s]
|
||||||
|
(when (string? s)
|
||||||
|
#?(:cljs (js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", s)
|
||||||
|
:clj (str/camel s))))
|
||||||
|
|
||||||
|
|
||||||
|
(transform-att [key]
|
||||||
|
(if (contains? non-react-props key)
|
||||||
|
key
|
||||||
|
(-> (d/name key)
|
||||||
|
(camelize)
|
||||||
|
(keyword))))
|
||||||
|
|
||||||
|
(format-styles [style-str]
|
||||||
|
(->> (str/split style-str ";")
|
||||||
|
(map str/trim)
|
||||||
|
(map #(str/split % ":"))
|
||||||
|
(group-by first)
|
||||||
|
(map (fn [[key val]]
|
||||||
|
[(transform-att key)
|
||||||
|
(second (first val))]))
|
||||||
|
(into {})))
|
||||||
|
|
||||||
|
(clean-att [[att val]]
|
||||||
|
(let [att (keyword att)]
|
||||||
|
(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])))]
|
||||||
|
|
||||||
|
;; Removed this warning because slows a lot rendering with big svgs
|
||||||
|
#_(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 clean-att))
|
||||||
|
attrs))))
|
||||||
|
|
||||||
|
(defn update-attr-ids
|
||||||
|
"Replaces the ids inside a property"
|
||||||
|
[attrs replace-fn]
|
||||||
|
(letfn [(update-ids [key val]
|
||||||
|
(cond
|
||||||
|
(map? val)
|
||||||
|
(d/mapm update-ids val)
|
||||||
|
|
||||||
|
(= key :id)
|
||||||
|
(replace-fn val)
|
||||||
|
|
||||||
|
:else
|
||||||
|
(let [replace-id
|
||||||
|
(fn [result it]
|
||||||
|
(let [to-replace (replace-fn it)]
|
||||||
|
(str/replace result (str "#" it) (str "#" to-replace))))]
|
||||||
|
(reduce replace-id val (extract-ids val)))))]
|
||||||
|
|
||||||
|
(d/mapm update-ids attrs)))
|
||||||
|
|
||||||
|
(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))
|
||||||
|
|
||||||
|
(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))))]
|
||||||
|
(reduce visit-node result (:content node))))]
|
||||||
|
(visit-node {} content)))
|
||||||
|
|
||||||
|
(defn extract-defs [{:keys [attrs] :as node}]
|
||||||
|
(if-not (map? node)
|
||||||
|
[{} node]
|
||||||
|
|
||||||
|
(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))
|
||||||
|
|
||||||
|
node-defs (->> rec-result (map first) (reduce merge current-node-defs))]
|
||||||
|
|
||||||
|
[ node-defs node ])))
|
||||||
|
|
||||||
|
(defn find-attr-references [attrs]
|
||||||
|
(->> attrs
|
||||||
|
(mapcat (fn [[_ attr-value]]
|
||||||
|
(if (string? attr-value)
|
||||||
|
(extract-ids attr-value)
|
||||||
|
(find-attr-references attr-value))))))
|
||||||
|
|
||||||
|
(defn find-node-references [node]
|
||||||
|
(let [current (->> (find-attr-references (:attrs node)) (into #{}))
|
||||||
|
children (->> (:content node) (map find-node-references) (flatten) (into #{}))]
|
||||||
|
(vec (into current children))))
|
||||||
|
|
||||||
|
(defn find-def-references [defs references]
|
||||||
|
(loop [result (into #{} references)
|
||||||
|
checked? #{}
|
||||||
|
to-check (first references)
|
||||||
|
pending (rest references)]
|
||||||
|
|
||||||
|
(cond
|
||||||
|
(nil? to-check)
|
||||||
|
result
|
||||||
|
|
||||||
|
(checked? to-check)
|
||||||
|
(recur result
|
||||||
|
checked?
|
||||||
|
(first pending)
|
||||||
|
(rest pending))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(let [node (get defs to-check)
|
||||||
|
new-refs (find-node-references node)
|
||||||
|
pending (concat pending new-refs)]
|
||||||
|
(recur (into result new-refs)
|
||||||
|
(conj checked? to-check)
|
||||||
|
(first pending)
|
||||||
|
(rest pending))))))
|
||||||
|
|
||||||
|
(defn svg-transform-matrix [shape]
|
||||||
|
(if (:svg-viewbox shape)
|
||||||
|
(let [{svg-x :x
|
||||||
|
svg-y :y
|
||||||
|
svg-width :width
|
||||||
|
svg-height :height} (:svg-viewbox shape)
|
||||||
|
{:keys [x y width height]} (:selrect shape)
|
||||||
|
|
||||||
|
scale-x (/ width svg-width)
|
||||||
|
scale-y (/ height svg-height)]
|
||||||
|
|
||||||
|
(gmt/multiply
|
||||||
|
(gmt/matrix)
|
||||||
|
|
||||||
|
;; Paths doesn't have transform so we have to transform its gradients
|
||||||
|
(if (or (= :path (:type shape))
|
||||||
|
(= :group (:type shape)))
|
||||||
|
(gsh/transform-matrix shape)
|
||||||
|
(gmt/matrix))
|
||||||
|
|
||||||
|
(gmt/translate-matrix (gpt/point (- x (* scale-x svg-x)) (- y (* scale-y svg-y))))
|
||||||
|
(gmt/scale-matrix (gpt/point scale-x scale-y))))
|
||||||
|
|
||||||
|
;; :else
|
||||||
|
(gmt/matrix)))
|
||||||
|
|
||||||
|
;; Parse transform attributes to native matrix format so we can transform paths instead of
|
||||||
|
;; relying in SVG transformation. This is necessary to import SVG's and not to break path tooling
|
||||||
|
;;
|
||||||
|
;; Transforms spec:
|
||||||
|
;; https://www.w3.org/TR/SVG11/single-page.html#coords-TransformAttribute
|
||||||
|
|
||||||
|
|
||||||
|
(defn format-translate-params [params]
|
||||||
|
(assert (or (= (count params) 1) (= (count params) 2)))
|
||||||
|
(if (= (count params) 1)
|
||||||
|
[(gpt/point (nth params 0) 0)]
|
||||||
|
[(gpt/point (nth params 0) (nth params 1))]))
|
||||||
|
|
||||||
|
(defn format-scale-params [params]
|
||||||
|
(assert (or (= (count params) 1) (= (count params) 2)))
|
||||||
|
(if (= (count params) 1)
|
||||||
|
[(gpt/point (nth params 0))]
|
||||||
|
[(gpt/point (nth params 0) (nth params 1))]))
|
||||||
|
|
||||||
|
(defn format-rotate-params [params]
|
||||||
|
(assert (or (= (count params) 1) (= (count params) 3)) (str "??" (count params)))
|
||||||
|
(if (= (count params) 1)
|
||||||
|
[(nth params 0) (gpt/point 0 0)]
|
||||||
|
[(nth params 0) (gpt/point (nth params 1) (nth params 2))]))
|
||||||
|
|
||||||
|
(defn format-skew-x-params [params]
|
||||||
|
(assert (= (count params) 1))
|
||||||
|
[(nth params 0) 0])
|
||||||
|
|
||||||
|
(defn format-skew-y-params [params]
|
||||||
|
(assert (= (count params) 1))
|
||||||
|
[0 (nth params 0)])
|
||||||
|
|
||||||
|
(defn to-matrix [{:keys [type params]}]
|
||||||
|
(assert (#{"matrix" "translate" "scale" "rotate" "skewX" "skewY"} type))
|
||||||
|
(case type
|
||||||
|
"matrix" (apply gmt/matrix params)
|
||||||
|
"translate" (apply gmt/translate-matrix (format-translate-params params))
|
||||||
|
"scale" (apply gmt/scale-matrix (format-scale-params params))
|
||||||
|
"rotate" (apply gmt/rotate-matrix (format-rotate-params params))
|
||||||
|
"skewX" (apply gmt/skew-matrix (format-skew-x-params params))
|
||||||
|
"skewY" (apply gmt/skew-matrix (format-skew-y-params params))))
|
||||||
|
|
||||||
|
(defn parse-transform [transform-attr]
|
||||||
|
(if transform-attr
|
||||||
|
(let [process-matrix
|
||||||
|
(fn [[_ type params]]
|
||||||
|
(let [params (->> (re-seq number-regex params)
|
||||||
|
(filter #(-> % first seq))
|
||||||
|
(map (comp d/parse-double first)))]
|
||||||
|
{:type type :params params}))
|
||||||
|
|
||||||
|
matrices (->> (re-seq matrices-regex transform-attr)
|
||||||
|
(map process-matrix)
|
||||||
|
(map to-matrix))]
|
||||||
|
(reduce gmt/multiply (gmt/matrix) matrices))
|
||||||
|
(gmt/matrix)))
|
||||||
|
|
||||||
|
(defn format-move [[x y]] (str "M" x " " y))
|
||||||
|
(defn format-line [[x y]] (str "L" x " " y))
|
||||||
|
|
||||||
|
(defn points->path [points-str]
|
||||||
|
(let [points (->> points-str
|
||||||
|
(re-seq number-regex)
|
||||||
|
(filter (comp not empty? first))
|
||||||
|
(mapv (comp d/parse-double first))
|
||||||
|
(partition 2))
|
||||||
|
|
||||||
|
head (first points)
|
||||||
|
other (rest points)]
|
||||||
|
|
||||||
|
(str (format-move head)
|
||||||
|
(->> other (map format-line) (str/join " ")))))
|
||||||
|
|
||||||
|
(defn polyline->path [{:keys [attrs] :as node}]
|
||||||
|
(let [tag :path
|
||||||
|
attrs (-> attrs
|
||||||
|
(dissoc :points)
|
||||||
|
(assoc :d (points->path (:points attrs))))]
|
||||||
|
|
||||||
|
(assoc node :attrs attrs :tag tag)))
|
||||||
|
|
||||||
|
(defn polygon->path [{:keys [attrs] :as node}]
|
||||||
|
(let [tag :path
|
||||||
|
attrs (-> attrs
|
||||||
|
(dissoc :points)
|
||||||
|
(assoc :d (str (points->path (:points attrs)) "Z")))]
|
||||||
|
(assoc node :attrs attrs :tag tag)))
|
||||||
|
|
||||||
|
(defn line->path [{:keys [attrs] :as node}]
|
||||||
|
(let [tag :path
|
||||||
|
{:keys [x1 y1 x2 y2]} attrs
|
||||||
|
x1 (or x1 0)
|
||||||
|
y1 (or y1 0)
|
||||||
|
x2 (or x2 0)
|
||||||
|
y2 (or y2 0)
|
||||||
|
attrs (-> attrs
|
||||||
|
(dissoc :x1 :x2 :y1 :y2)
|
||||||
|
(assoc :d (str "M" x1 "," y1 " L" x2 "," y2)))]
|
||||||
|
|
||||||
|
(assoc node :attrs attrs :tag tag)))
|
||||||
|
|
||||||
|
(defn add-transform [attrs transform]
|
||||||
|
(letfn [(append-transform [old-transform]
|
||||||
|
(if (or (nil? old-transform) (empty? old-transform))
|
||||||
|
transform
|
||||||
|
(str transform " " old-transform)))]
|
||||||
|
|
||||||
|
(cond-> attrs
|
||||||
|
transform
|
||||||
|
(update :transform append-transform))))
|
||||||
|
|
||||||
|
(defn inherit-attributes [group-attrs {:keys [attrs] :as node}]
|
||||||
|
(if (map? node)
|
||||||
|
(let [attrs (-> (format-styles attrs)
|
||||||
|
(add-transform (:transform group-attrs)))
|
||||||
|
attrs (d/deep-merge (select-keys group-attrs inheritable-props) attrs)]
|
||||||
|
(assoc node :attrs attrs))
|
||||||
|
node))
|
||||||
|
|
||||||
|
(defn map-nodes [mapfn node]
|
||||||
|
(let [update-content
|
||||||
|
(fn [content] (cond->> content
|
||||||
|
(vector? content)
|
||||||
|
(mapv (partial map-nodes mapfn))))]
|
||||||
|
|
||||||
|
(cond-> node
|
||||||
|
(map? node)
|
||||||
|
(-> (mapfn)
|
||||||
|
(d/update-when :content update-content)))))
|
||||||
|
|
||||||
|
(defn reduce-nodes [redfn value node]
|
||||||
|
(let [reduce-content
|
||||||
|
(fn [value content]
|
||||||
|
(loop [current (first content)
|
||||||
|
content (rest content)
|
||||||
|
value value]
|
||||||
|
(if (nil? current)
|
||||||
|
value
|
||||||
|
(recur (first content)
|
||||||
|
(rest content)
|
||||||
|
(reduce-nodes redfn value current)))))]
|
||||||
|
|
||||||
|
(if (map? node)
|
||||||
|
(-> (redfn value node)
|
||||||
|
(reduce-content (:content node)))
|
||||||
|
value)))
|
||||||
|
|
||||||
|
(defn fix-default-values
|
||||||
|
"Gives values to some SVG elements which defaults won't work when imported into the platform"
|
||||||
|
[svg-data]
|
||||||
|
(let [add-defaults
|
||||||
|
(fn [{:keys [tag attrs] :as node}]
|
||||||
|
(let [prop (get-in svg-tag-defaults [tag :units])
|
||||||
|
default-units (get-in svg-tag-defaults [tag :default])
|
||||||
|
units (get attrs prop default-units)
|
||||||
|
tag-default (get-in svg-tag-defaults [tag units])]
|
||||||
|
(d/update-when node :attrs #(merge tag-default %))))
|
||||||
|
|
||||||
|
fix-node-defaults
|
||||||
|
(fn [node]
|
||||||
|
(cond-> node
|
||||||
|
(contains? svg-tag-defaults (:tag node))
|
||||||
|
(add-defaults)))]
|
||||||
|
|
||||||
|
(->> svg-data (map-nodes fix-node-defaults))))
|
||||||
|
|
||||||
|
(defn calculate-ratio
|
||||||
|
;; sqrt((actual-width)**2 + (actual-height)**2)/sqrt(2).
|
||||||
|
[width height]
|
||||||
|
(/ (mth/hypot width height)
|
||||||
|
(mth/sqrt 2)))
|
||||||
|
|
||||||
|
(defn fix-percents
|
||||||
|
"Changes percents to a value according to the size of the svg imported"
|
||||||
|
[svg-data]
|
||||||
|
;; https://www.w3.org/TR/SVG11/single-page.html#coords-Units
|
||||||
|
(let [viewbox {:x (:offset-x svg-data)
|
||||||
|
:y (:offset-y svg-data)
|
||||||
|
:width (:width svg-data)
|
||||||
|
:height (:height svg-data)
|
||||||
|
:ratio (calculate-ratio (:width svg-data) (:height svg-data))}]
|
||||||
|
(letfn [(fix-length [prop-length val]
|
||||||
|
(* (get viewbox prop-length) (/ val 100.)))
|
||||||
|
|
||||||
|
(fix-coord [prop-coord prop-length val]
|
||||||
|
(+ (get viewbox prop-coord)
|
||||||
|
(fix-length prop-length val)))
|
||||||
|
|
||||||
|
(fix-percent-attr-viewbox [attr-key attr-val]
|
||||||
|
(let [is-percent? (str/ends-with? attr-val "%")
|
||||||
|
is-x? #{:x :x1 :x2 :cx}
|
||||||
|
is-y? #{:y :y1 :y2 :cy}
|
||||||
|
is-width? #{:width}
|
||||||
|
is-height? #{:height}
|
||||||
|
is-other? #{:r :stroke-width}]
|
||||||
|
|
||||||
|
(if is-percent?
|
||||||
|
;; JS parseFloat removes the % symbol
|
||||||
|
(let [attr-num (d/parse-double attr-val)]
|
||||||
|
(str (cond
|
||||||
|
(is-x? attr-key) (fix-coord :x :width attr-num)
|
||||||
|
(is-y? attr-key) (fix-coord :y :height attr-num)
|
||||||
|
(is-width? attr-key) (fix-length :width attr-num)
|
||||||
|
(is-height? attr-key) (fix-length :height attr-num)
|
||||||
|
(is-other? attr-key) (fix-length :ratio attr-num)
|
||||||
|
:else attr-val)))
|
||||||
|
attr-val)))
|
||||||
|
|
||||||
|
(fix-percent-attrs-viewbox [attrs]
|
||||||
|
(d/mapm fix-percent-attr-viewbox attrs))
|
||||||
|
|
||||||
|
(fix-percent-attr-numeric [_ attr-val]
|
||||||
|
(let [is-percent? (str/ends-with? attr-val "%")]
|
||||||
|
(if is-percent?
|
||||||
|
(str (let [attr-num (d/parse-double attr-val)]
|
||||||
|
(/ attr-num 100)))
|
||||||
|
attr-val)))
|
||||||
|
|
||||||
|
(fix-percent-attrs-numeric [attrs]
|
||||||
|
(d/mapm fix-percent-attr-numeric attrs))
|
||||||
|
|
||||||
|
(fix-percent-values [node]
|
||||||
|
(let [units (or (get-in node [:attrs :filterUnits])
|
||||||
|
(get-in node [:attrs :gradientUnits])
|
||||||
|
(get-in node [:attrs :patternUnits])
|
||||||
|
(get-in node [:attrs :clipUnits]))]
|
||||||
|
(cond-> node
|
||||||
|
(= "objectBoundingBox" units)
|
||||||
|
(update :attrs fix-percent-attrs-numeric)
|
||||||
|
|
||||||
|
(not= "objectBoundingBox" units)
|
||||||
|
(update :attrs fix-percent-attrs-viewbox))))]
|
||||||
|
|
||||||
|
(->> svg-data (map-nodes fix-percent-values)))))
|
||||||
|
|
||||||
|
(defn collect-images [svg-data]
|
||||||
|
(let [redfn (fn [acc {:keys [tag attrs]}]
|
||||||
|
(cond-> acc
|
||||||
|
(= :image tag)
|
||||||
|
(conj (or (:href attrs) (:xlink:href attrs)))))]
|
||||||
|
(reduce-nodes redfn [] svg-data )))
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn optimize
|
(defn optimize
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
[app.common.pages.changes-builder :as pcb]
|
[app.common.pages.changes-builder :as pcb]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.schema :as sm :refer [max-safe-int min-safe-int]]
|
[app.common.schema :as sm :refer [max-safe-int min-safe-int]]
|
||||||
|
[app.common.svg :as csvg]
|
||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
[app.common.types.shape-tree :as ctst]
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
|
@ -27,7 +28,6 @@
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.util.color :as uc]
|
[app.util.color :as uc]
|
||||||
[app.util.path.parser :as upp]
|
[app.util.path.parser :as upp]
|
||||||
[app.util.svg :as usvg]
|
|
||||||
[app.util.webapi :as wapi]
|
[app.util.webapi :as wapi]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
|
@ -207,7 +207,7 @@
|
||||||
:x x
|
:x x
|
||||||
:y y
|
:y y
|
||||||
:content (cond-> data
|
:content (cond-> data
|
||||||
(map? data) (update :attrs usvg/clean-attrs))
|
(map? data) (update :attrs csvg/clean-attrs))
|
||||||
:svg-attrs attrs
|
:svg-attrs attrs
|
||||||
:svg-viewbox {:width width
|
:svg-viewbox {:width width
|
||||||
:height height
|
:height height
|
||||||
|
@ -228,11 +228,11 @@
|
||||||
:svg-attrs (-> attrs
|
:svg-attrs (-> attrs
|
||||||
(dissoc :viewBox)
|
(dissoc :viewBox)
|
||||||
(dissoc :xmlns)
|
(dissoc :xmlns)
|
||||||
(d/without-keys usvg/inheritable-props))}))
|
(d/without-keys csvg/inheritable-props))}))
|
||||||
|
|
||||||
(defn create-group
|
(defn create-group
|
||||||
[name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}]
|
[name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}]
|
||||||
(let [svg-transform (usvg/parse-transform (:transform attrs))]
|
(let [svg-transform (csvg/parse-transform (:transform attrs))]
|
||||||
(cts/setup-shape
|
(cts/setup-shape
|
||||||
{:type :group
|
{:type :group
|
||||||
:name name
|
:name name
|
||||||
|
@ -242,7 +242,7 @@
|
||||||
:width width
|
:width width
|
||||||
:height height
|
:height height
|
||||||
:svg-transform svg-transform
|
:svg-transform svg-transform
|
||||||
:svg-attrs (d/without-keys attrs usvg/inheritable-props)
|
:svg-attrs (d/without-keys attrs csvg/inheritable-props)
|
||||||
|
|
||||||
:svg-viewbox {:width width
|
:svg-viewbox {:width width
|
||||||
:height height
|
:height height
|
||||||
|
@ -252,7 +252,7 @@
|
||||||
(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) (seq (:d attrs)))
|
(when (and (contains? attrs :d) (seq (:d attrs)))
|
||||||
|
|
||||||
(let [svg-transform (usvg/parse-transform (:transform attrs))
|
(let [svg-transform (csvg/parse-transform (:transform attrs))
|
||||||
path-content (upp/parse-path (:d attrs))
|
path-content (upp/parse-path (:d attrs))
|
||||||
content (cond-> path-content
|
content (cond-> path-content
|
||||||
svg-transform
|
svg-transform
|
||||||
|
@ -299,7 +299,7 @@
|
||||||
:height (d/parse-double height 1)})
|
:height (d/parse-double height 1)})
|
||||||
|
|
||||||
(defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}]
|
(defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}]
|
||||||
(let [transform (->> (usvg/parse-transform (:transform attrs))
|
(let [transform (->> (csvg/parse-transform (:transform attrs))
|
||||||
(gmt/transform-in (gpt/point svg-data)))
|
(gmt/transform-in (gpt/point svg-data)))
|
||||||
|
|
||||||
origin (gpt/negate (gpt/point svg-data))
|
origin (gpt/negate (gpt/point svg-data))
|
||||||
|
@ -331,7 +331,7 @@
|
||||||
(let [[cx cy r rx ry]
|
(let [[cx cy r rx ry]
|
||||||
(parse-circle-attrs attrs)
|
(parse-circle-attrs attrs)
|
||||||
|
|
||||||
transform (->> (usvg/parse-transform (:transform attrs))
|
transform (->> (csvg/parse-transform (:transform attrs))
|
||||||
(gmt/transform-in (gpt/point svg-data)))
|
(gmt/transform-in (gpt/point svg-data)))
|
||||||
|
|
||||||
rx (or r rx)
|
rx (or r rx)
|
||||||
|
@ -352,7 +352,7 @@
|
||||||
(assoc :svg-attrs (dissoc attrs :cx :cy :r :rx :ry :transform))))))
|
(assoc :svg-attrs (dissoc attrs :cx :cy :r :rx :ry :transform))))))
|
||||||
|
|
||||||
(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 (->> (usvg/parse-transform (:transform attrs))
|
(let [transform (->> (csvg/parse-transform (:transform attrs))
|
||||||
(gmt/transform-in (gpt/point svg-data)))
|
(gmt/transform-in (gpt/point svg-data)))
|
||||||
|
|
||||||
image-url (or (:href attrs) (:xlink:href attrs))
|
image-url (or (:href attrs) (:xlink:href attrs))
|
||||||
|
@ -381,11 +381,11 @@
|
||||||
|
|
||||||
|
|
||||||
(defn parse-svg-element [frame-id svg-data {:keys [tag attrs hidden] :as element-data} unames]
|
(defn parse-svg-element [frame-id svg-data {:keys [tag attrs hidden] :as element-data} unames]
|
||||||
(let [attrs (usvg/format-styles attrs)
|
(let [attrs (csvg/format-styles attrs)
|
||||||
element-data (cond-> element-data (map? element-data) (assoc :attrs attrs))
|
element-data (cond-> element-data (map? element-data) (assoc :attrs attrs))
|
||||||
name (or (:id attrs) (tag->name tag))
|
name (or (:id attrs) (tag->name tag))
|
||||||
att-refs (usvg/find-attr-references attrs)
|
att-refs (csvg/find-attr-references attrs)
|
||||||
references (usvg/find-def-references (:defs svg-data) att-refs)
|
references (csvg/find-def-references (:defs svg-data) att-refs)
|
||||||
|
|
||||||
href-id (-> (or (:href attrs) (:xlink:href attrs) "") (subs 1))
|
href-id (-> (or (:href attrs) (:xlink:href attrs) "") (subs 1))
|
||||||
defs (:defs svg-data)
|
defs (:defs svg-data)
|
||||||
|
@ -401,7 +401,7 @@
|
||||||
element-data (-> element-data
|
element-data (-> element-data
|
||||||
(assoc :tag :g)
|
(assoc :tag :g)
|
||||||
(update :attrs dissoc :x :y :width :height :href :xlink:href :transform)
|
(update :attrs dissoc :x :y :width :height :href :xlink:href :transform)
|
||||||
(update :attrs usvg/add-transform disp-matrix)
|
(update :attrs csvg/add-transform disp-matrix)
|
||||||
(assoc :content [use-data]))]
|
(assoc :content [use-data]))]
|
||||||
(parse-svg-element frame-id svg-data element-data unames))
|
(parse-svg-element frame-id svg-data element-data unames))
|
||||||
|
|
||||||
|
@ -413,9 +413,9 @@
|
||||||
(:circle
|
(:circle
|
||||||
:ellipse) (create-circle-shape name frame-id svg-data element-data)
|
:ellipse) (create-circle-shape name frame-id svg-data element-data)
|
||||||
:path (create-path-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 usvg/polyline->path))
|
: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 usvg/polygon->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 usvg/line->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)
|
: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)))]
|
||||||
(when (some? shape)
|
(when (some? shape)
|
||||||
|
@ -429,8 +429,8 @@
|
||||||
hidden (assoc :hidden true))
|
hidden (assoc :hidden true))
|
||||||
|
|
||||||
(cond->> (:content element-data)
|
(cond->> (:content element-data)
|
||||||
(contains? usvg/parent-tags tag)
|
(contains? csvg/parent-tags tag)
|
||||||
(mapv #(usvg/inherit-attributes attrs %)))]))))))
|
(mapv #(csvg/inherit-attributes attrs %)))]))))))
|
||||||
|
|
||||||
(defn create-svg-children
|
(defn create-svg-children
|
||||||
[objects selected frame-id parent-id svg-data [unames children] [_index svg-element]]
|
[objects selected frame-id parent-id svg-data [unames children] [_index svg-element]]
|
||||||
|
@ -473,7 +473,7 @@
|
||||||
"Extract all bitmap images inside the svg data, and upload them, associated to the file.
|
"Extract all bitmap images inside the svg data, and upload them, associated to the file.
|
||||||
Return a map {<url> <image-data>}."
|
Return a map {<url> <image-data>}."
|
||||||
[svg-data file-id]
|
[svg-data file-id]
|
||||||
(->> (rx/from (usvg/collect-images svg-data))
|
(->> (rx/from (csvg/collect-images svg-data))
|
||||||
(rx/map (fn [uri]
|
(rx/map (fn [uri]
|
||||||
(merge
|
(merge
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
|
@ -520,9 +520,9 @@
|
||||||
|
|
||||||
[def-nodes svg-data]
|
[def-nodes svg-data]
|
||||||
(-> svg-data
|
(-> svg-data
|
||||||
(usvg/fix-default-values)
|
(csvg/fix-default-values)
|
||||||
(usvg/fix-percents)
|
(csvg/fix-percents)
|
||||||
(usvg/extract-defs))
|
(csvg/extract-defs))
|
||||||
|
|
||||||
svg-data (assoc svg-data :defs def-nodes)
|
svg-data (assoc svg-data :defs def-nodes)
|
||||||
root-shape (create-svg-root frame-id parent-id svg-data)
|
root-shape (create-svg-root frame-id parent-id svg-data)
|
||||||
|
@ -549,13 +549,13 @@
|
||||||
|
|
||||||
;; Create the root shape
|
;; Create the root shape
|
||||||
root-attrs (-> (:attrs svg-data)
|
root-attrs (-> (:attrs svg-data)
|
||||||
(usvg/format-styles))
|
(csvg/format-styles))
|
||||||
|
|
||||||
[_ children]
|
[_ children]
|
||||||
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
|
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
|
||||||
[unames []]
|
[unames []]
|
||||||
(d/enumerate (->> (:content svg-data)
|
(d/enumerate (->> (:content svg-data)
|
||||||
(mapv #(usvg/inherit-attributes root-attrs %)))))]
|
(mapv #(csvg/inherit-attributes root-attrs %)))))]
|
||||||
|
|
||||||
[root-shape children]))
|
[root-shape children]))
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,10 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.svg :as csvg]
|
||||||
[app.common.types.shape :refer [stroke-caps-line stroke-caps-marker]]
|
[app.common.types.shape :refer [stroke-caps-line stroke-caps-marker]]
|
||||||
[app.common.types.shape.radius :as ctsr]
|
[app.common.types.shape.radius :as ctsr]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.svg :as usvg]
|
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
(defn- stroke-type->dasharray
|
(defn- stroke-type->dasharray
|
||||||
|
@ -163,8 +163,8 @@
|
||||||
;; TODO: revisit, why we need to execute it each render? Can
|
;; TODO: revisit, why we need to execute it each render? Can
|
||||||
;; we do this operation on importation and avoid unnecesary
|
;; we do this operation on importation and avoid unnecesary
|
||||||
;; work on render?
|
;; work on render?
|
||||||
(usvg/clean-attrs)
|
(csvg/clean-attrs)
|
||||||
(usvg/update-attr-ids
|
(csvg/update-attr-ids
|
||||||
(fn [id]
|
(fn [id]
|
||||||
(if (contains? defs id)
|
(if (contains? defs id)
|
||||||
(str render-id "-" id)
|
(str render-id "-" id)
|
||||||
|
@ -217,8 +217,8 @@
|
||||||
(= :group shape-type))
|
(= :group shape-type))
|
||||||
(empty? shape-fills))
|
(empty? shape-fills))
|
||||||
(let [wstyle (get shape :wrapper-styles)
|
(let [wstyle (get shape :wrapper-styles)
|
||||||
fill (obj/get wstyle "fill")
|
fill (obj/get wstyle "fill")
|
||||||
fill (d/nilv fill clr/black)]
|
fill (d/nilv fill clr/black)]
|
||||||
(obj/set! style "fill" fill))
|
(obj/set! style "fill" fill))
|
||||||
|
|
||||||
(d/not-empty? shape-fills)
|
(d/not-empty? shape-fills)
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.svg :as csvg]
|
||||||
[app.main.ui.context :as muc]
|
[app.main.ui.context :as muc]
|
||||||
[app.util.json :as json]
|
[app.util.json :as json]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.svg :as usvg]
|
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
(map? node)
|
(map? node)
|
||||||
[:> (d/name tag) (clj->js (usvg/clean-attrs attrs))
|
[:> (d/name tag) (clj->js (csvg/clean-attrs attrs))
|
||||||
(for [child content]
|
(for [child content]
|
||||||
[:& render-xml {:xml child}])]
|
[:& render-xml {:xml child}])]
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,7 @@
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
;; FIXME: revisit this: breaks all memoization because of this new
|
;; FIXME: revisit this:
|
||||||
;; property added to shapes
|
|
||||||
(defn propagate-wrapper-styles-child
|
(defn propagate-wrapper-styles-child
|
||||||
[child wrapper-props]
|
[child wrapper-props]
|
||||||
(let [child-props-childs
|
(let [child-props-childs
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
[app.common.geom.rect :as grc]
|
[app.common.geom.rect :as grc]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.geom.shapes.bounds :as gsb]
|
[app.common.geom.shapes.bounds :as gsb]
|
||||||
[app.util.svg :as usvg]
|
[app.common.svg :as csvg]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(defn add-matrix [attrs transform-key transform-matrix]
|
(defn add-matrix [attrs transform-key transform-matrix]
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
:else
|
:else
|
||||||
(let [{:keys [tag attrs content]} node
|
(let [{:keys [tag attrs content]} node
|
||||||
|
|
||||||
transform-gradient? (and (contains? usvg/gradient-tags tag)
|
transform-gradient? (and (contains? csvg/gradient-tags tag)
|
||||||
(= "userSpaceOnUse" (get attrs :gradientUnits "objectBoundingBox")))
|
(= "userSpaceOnUse" (get attrs :gradientUnits "objectBoundingBox")))
|
||||||
|
|
||||||
transform-pattern? (and (= :pattern tag)
|
transform-pattern? (and (= :pattern tag)
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
transform-clippath? (and (= :clipPath tag)
|
transform-clippath? (and (= :clipPath tag)
|
||||||
(= "userSpaceOnUse" (get attrs :clipPathUnits "userSpaceOnUse")))
|
(= "userSpaceOnUse" (get attrs :clipPathUnits "userSpaceOnUse")))
|
||||||
|
|
||||||
transform-filter? (and (contains? usvg/filter-tags tag)
|
transform-filter? (and (contains? csvg/filter-tags tag)
|
||||||
(= "userSpaceOnUse" (get attrs :filterUnits "objectBoundingBox")))
|
(= "userSpaceOnUse" (get attrs :filterUnits "objectBoundingBox")))
|
||||||
|
|
||||||
transform-mask? (and (= :mask tag)
|
transform-mask? (and (= :mask tag)
|
||||||
|
@ -47,8 +47,8 @@
|
||||||
|
|
||||||
attrs
|
attrs
|
||||||
(-> attrs
|
(-> attrs
|
||||||
(usvg/update-attr-ids prefix-id)
|
(csvg/update-attr-ids prefix-id)
|
||||||
(usvg/clean-attrs)
|
(csvg/clean-attrs)
|
||||||
;; This clasname will be used to change the transform on the viewport
|
;; This clasname will be used to change the transform on the viewport
|
||||||
;; only necessary for groups because shapes have their own transform
|
;; only necessary for groups because shapes have their own transform
|
||||||
(cond-> (and (or transform-gradient?
|
(cond-> (and (or transform-gradient?
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
|
|
||||||
(defn svg-def-bounds [svg-def shape transform]
|
(defn svg-def-bounds [svg-def shape transform]
|
||||||
(let [{:keys [tag]} svg-def]
|
(let [{:keys [tag]} svg-def]
|
||||||
(if (or (= tag :mask) (contains? usvg/filter-tags tag))
|
(if (or (= tag :mask) (contains? csvg/filter-tags tag))
|
||||||
(-> (grc/make-rect (d/parse-double (get-in svg-def [:attrs :x]))
|
(-> (grc/make-rect (d/parse-double (get-in svg-def [:attrs :x]))
|
||||||
(d/parse-double (get-in svg-def [:attrs :y]))
|
(d/parse-double (get-in svg-def [:attrs :y]))
|
||||||
(d/parse-double (get-in svg-def [:attrs :width]))
|
(d/parse-double (get-in svg-def [:attrs :width]))
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
(mf/deps shape)
|
(mf/deps shape)
|
||||||
#(if (= :svg-raw (:type shape))
|
#(if (= :svg-raw (:type shape))
|
||||||
(gmt/matrix)
|
(gmt/matrix)
|
||||||
(usvg/svg-transform-matrix shape)))
|
(csvg/svg-transform-matrix shape)))
|
||||||
|
|
||||||
;; Paths doesn't have transform so we have to transform its gradients
|
;; Paths doesn't have transform so we have to transform its gradients
|
||||||
transform (if (some? (:svg-transform shape))
|
transform (if (some? (:svg-transform shape))
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.common.svg :as csvg]
|
||||||
[app.main.ui.context :as muc]
|
[app.main.ui.context :as muc]
|
||||||
[app.main.ui.shapes.attrs :as usa]
|
[app.main.ui.shapes.attrs :as usa]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.svg :as usvg]
|
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
;; Graphic tags
|
;; Graphic tags
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
attrs (or attrs {})
|
attrs (or attrs {})
|
||||||
attrs (cond-> attrs
|
attrs (cond-> attrs
|
||||||
(string? (:style attrs)) usvg/clean-attrs)
|
(string? (:style attrs)) csvg/clean-attrs)
|
||||||
style (obj/merge! (clj->js (:style attrs {}))
|
style (obj/merge! (clj->js (:style attrs {}))
|
||||||
(obj/get custom-attrs "style"))]
|
(obj/get custom-attrs "style"))]
|
||||||
(-> (clj->js attrs)
|
(-> (clj->js attrs)
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
(obj/set! "style" style))))
|
(obj/set! "style" style))))
|
||||||
|
|
||||||
(defn translate-shape [attrs shape]
|
(defn translate-shape [attrs shape]
|
||||||
(let [transform (dm/str (usvg/svg-transform-matrix shape)
|
(let [transform (dm/str (csvg/svg-transform-matrix shape)
|
||||||
" "
|
" "
|
||||||
(:transform attrs ""))]
|
(:transform attrs ""))]
|
||||||
(cond-> attrs
|
(cond-> attrs
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
{:keys [x y width height]} shape
|
{:keys [x y width height]} shape
|
||||||
{:keys [attrs] :as content} (:content shape)
|
{:keys [attrs] :as content} (:content shape)
|
||||||
|
|
||||||
ids-mapping (mf/use-memo #(usvg/generate-id-mapping content))
|
ids-mapping (mf/use-memo #(csvg/generate-id-mapping content))
|
||||||
render-id (mf/use-ctx muc/render-id)
|
render-id (mf/use-ctx muc/render-id)
|
||||||
|
|
||||||
attrs (-> (set-styles attrs shape render-id)
|
attrs (-> (set-styles attrs shape render-id)
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
ids-mapping (mf/use-ctx svg-ids-ctx)
|
ids-mapping (mf/use-ctx svg-ids-ctx)
|
||||||
render-id (mf/use-ctx muc/render-id)
|
render-id (mf/use-ctx muc/render-id)
|
||||||
|
|
||||||
attrs (mf/use-memo #(usvg/replace-attrs-ids attrs ids-mapping))
|
attrs (mf/use-memo #(csvg/replace-attrs-ids attrs ids-mapping))
|
||||||
|
|
||||||
attrs (translate-shape attrs shape)
|
attrs (translate-shape attrs shape)
|
||||||
element-id (get-in content [:attrs :id])
|
element-id (get-in content [:attrs :id])
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
svg-root? (and (map? content) (= tag :svg))
|
svg-root? (and (map? content) (= tag :svg))
|
||||||
svg-tag? (map? content)
|
svg-tag? (map? content)
|
||||||
svg-leaf? (string? content)
|
svg-leaf? (string? content)
|
||||||
valid-tag? (contains? usvg/svg-tags-list tag)]
|
valid-tag? (contains? csvg/svg-tags-list tag)]
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
svg-root?
|
svg-root?
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.shapes.svg-raw
|
(ns app.main.ui.workspace.shapes.svg-raw
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.svg :as csvg]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||||
[app.main.ui.shapes.svg-raw :as svg-raw]
|
[app.main.ui.shapes.svg-raw :as svg-raw]
|
||||||
[app.util.svg :as usvg]
|
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(defn svg-raw-wrapper-factory
|
(defn svg-raw-wrapper-factory
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
childs-ref (mf/use-memo (mf/deps (:id shape)) #(refs/children-objects (:id shape)))
|
childs-ref (mf/use-memo (mf/deps (:id shape)) #(refs/children-objects (:id shape)))
|
||||||
childs (mf/deref childs-ref)
|
childs (mf/deref childs-ref)
|
||||||
svg-tag (get-in shape [:content :tag])]
|
svg-tag (get-in shape [:content :tag])]
|
||||||
(if (contains? usvg/svg-group-safe-tags svg-tag)
|
(if (contains? csvg/svg-group-safe-tags svg-tag)
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
[:& svg-raw-shape {:shape shape
|
[:& svg-raw-shape {:shape shape
|
||||||
:childs childs}]]
|
:childs childs}]]
|
||||||
|
|
|
@ -10,10 +10,9 @@
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes.path :as upg]
|
[app.common.geom.shapes.path :as upg]
|
||||||
[app.common.path.commands :as upc]
|
[app.common.path.commands :as upc]
|
||||||
|
[app.common.svg :as csvg]
|
||||||
[app.util.path.arc-to-curve :refer [a2c]]
|
[app.util.path.arc-to-curve :refer [a2c]]
|
||||||
[app.util.svg :as usvg]
|
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
;;
|
;;
|
||||||
(def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*")
|
(def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*")
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@
|
||||||
match (re-find regex remain)]
|
match (re-find regex remain)]
|
||||||
|
|
||||||
(if match
|
(if match
|
||||||
(let [value (-> match first usvg/fix-dot-number d/read-string)
|
(let [value (-> match first csvg/fix-dot-number d/read-string)
|
||||||
remain (str/replace-first remain regex "")
|
remain (str/replace-first remain regex "")
|
||||||
current (assoc current param value)
|
current (assoc current param value)
|
||||||
extract-idx (inc extract-idx)
|
extract-idx (inc extract-idx)
|
||||||
|
|
|
@ -42,9 +42,3 @@
|
||||||
(let [st (str/trim (str/lower search-term))
|
(let [st (str/trim (str/lower search-term))
|
||||||
nm (str/trim (str/lower name))]
|
nm (str/trim (str/lower name))]
|
||||||
(str/includes? nm st))))
|
(str/includes? nm st))))
|
||||||
|
|
||||||
(defn camelize
|
|
||||||
[str]
|
|
||||||
;; str.replace(":", "-").replace(/-./g, x=>x[1].toUpperCase())
|
|
||||||
(when (not (nil? str))
|
|
||||||
(js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", str)))
|
|
||||||
|
|
|
@ -1,979 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) KALEIDOS INC
|
|
||||||
|
|
||||||
(ns app.util.svg
|
|
||||||
(:require
|
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.geom.matrix :as gmt]
|
|
||||||
[app.common.geom.point :as gpt]
|
|
||||||
[app.common.geom.shapes :as gsh]
|
|
||||||
[app.common.math :as mth]
|
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.util.strings :as ustr]
|
|
||||||
[cuerdas.core :as str]))
|
|
||||||
|
|
||||||
;; Regex for XML ids per Spec
|
|
||||||
;; https://www.w3.org/TR/2008/REC-xml-20081126/#sec-common-syn
|
|
||||||
(defonce xml-id-regex #"#([:A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF][\.\-\:0-9\xB7A-Z_a-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0300-\u036F\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF]*)")
|
|
||||||
|
|
||||||
(defonce matrices-regex #"(matrix|translate|scale|rotate|skewX|skewY)\(([^\)]*)\)")
|
|
||||||
(defonce number-regex #"[+-]?\d*(\.\d+)?(e[+-]?\d+)?")
|
|
||||||
|
|
||||||
(defonce tags-to-remove #{: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
|
|
||||||
: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
|
|
||||||
[:style
|
|
||||||
: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})
|
|
||||||
|
|
||||||
(def parent-tags
|
|
||||||
#{:g
|
|
||||||
:svg
|
|
||||||
:text
|
|
||||||
:tspan})
|
|
||||||
|
|
||||||
;; By spec: https://www.w3.org/TR/SVG11/single-page.html#struct-GElement
|
|
||||||
(defonce svg-group-safe-tags
|
|
||||||
#{:animate
|
|
||||||
:animateColor
|
|
||||||
:animateMotion
|
|
||||||
:animateTransform
|
|
||||||
:set
|
|
||||||
:desc
|
|
||||||
:metadata
|
|
||||||
:title
|
|
||||||
:circle
|
|
||||||
:ellipse
|
|
||||||
:line
|
|
||||||
:path
|
|
||||||
:polygon
|
|
||||||
:polyline
|
|
||||||
:rect
|
|
||||||
:defs
|
|
||||||
:g
|
|
||||||
:svg
|
|
||||||
:symbol
|
|
||||||
:use
|
|
||||||
:linearGradient
|
|
||||||
:radialGradient
|
|
||||||
:a
|
|
||||||
:altGlyphDef
|
|
||||||
:clipPath
|
|
||||||
:color-profile
|
|
||||||
:cursor
|
|
||||||
:filter
|
|
||||||
:font
|
|
||||||
:font-face
|
|
||||||
:foreignObject
|
|
||||||
:image
|
|
||||||
:marker
|
|
||||||
:mask
|
|
||||||
:pattern
|
|
||||||
:style
|
|
||||||
:switch
|
|
||||||
:text
|
|
||||||
:view})
|
|
||||||
|
|
||||||
;; 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 basically 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]
|
|
||||||
(when (some? val)
|
|
||||||
(->> (re-seq xml-id-regex val)
|
|
||||||
(mapv second))))
|
|
||||||
|
|
||||||
(defn fix-dot-number
|
|
||||||
"Fixes decimal numbers starting in dot but without leading 0"
|
|
||||||
[num-str]
|
|
||||||
(cond
|
|
||||||
(str/starts-with? num-str ".")
|
|
||||||
(str "0" num-str)
|
|
||||||
|
|
||||||
(str/starts-with? num-str "-.")
|
|
||||||
(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))]
|
|
||||||
|
|
||||||
(cond-> attrs
|
|
||||||
(contains? attrs :style)
|
|
||||||
(update :style format-styles))))
|
|
||||||
|
|
||||||
(defn clean-attrs
|
|
||||||
"Transforms attributes to their react equivalent"
|
|
||||||
([attrs]
|
|
||||||
(clean-attrs attrs true))
|
|
||||||
|
|
||||||
([attrs whitelist?]
|
|
||||||
(letfn [(known-property? [[key _]]
|
|
||||||
(or (not whitelist?)
|
|
||||||
(contains? svg-attr-list key)
|
|
||||||
(contains? svg-present-list key)))
|
|
||||||
|
|
||||||
(transform-att [key]
|
|
||||||
(if (contains? non-react-props key)
|
|
||||||
key
|
|
||||||
(-> (d/name key)
|
|
||||||
(ustr/camelize)
|
|
||||||
(keyword))))
|
|
||||||
|
|
||||||
(format-styles [style-str]
|
|
||||||
(->> (str/split style-str ";")
|
|
||||||
(map str/trim)
|
|
||||||
(map #(str/split % ":"))
|
|
||||||
(group-by first)
|
|
||||||
(map (fn [[key val]]
|
|
||||||
(vector
|
|
||||||
(transform-att key)
|
|
||||||
(second (first val)))))
|
|
||||||
(into {})))
|
|
||||||
|
|
||||||
(clean-att [[att val]]
|
|
||||||
(let [att (keyword att)]
|
|
||||||
(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])))]
|
|
||||||
|
|
||||||
;; Removed this warning because slows a lot rendering with big svgs
|
|
||||||
#_(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 clean-att))
|
|
||||||
attrs))))
|
|
||||||
|
|
||||||
(defn update-attr-ids
|
|
||||||
"Replaces the ids inside a property"
|
|
||||||
[attrs replace-fn]
|
|
||||||
(letfn [(update-ids [key val]
|
|
||||||
(cond
|
|
||||||
(map? val)
|
|
||||||
(d/mapm update-ids val)
|
|
||||||
|
|
||||||
(= key :id)
|
|
||||||
(replace-fn val)
|
|
||||||
|
|
||||||
:else
|
|
||||||
(let [replace-id
|
|
||||||
(fn [result it]
|
|
||||||
(let [to-replace (replace-fn it)]
|
|
||||||
(str/replace result (str "#" it) (str "#" to-replace))))]
|
|
||||||
(reduce replace-id val (extract-ids val)))))]
|
|
||||||
(d/mapm update-ids attrs)))
|
|
||||||
|
|
||||||
(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))
|
|
||||||
|
|
||||||
(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))))]
|
|
||||||
(reduce visit-node result (:content node))))]
|
|
||||||
(visit-node {} content)))
|
|
||||||
|
|
||||||
(defn extract-defs [{:keys [attrs] :as node}]
|
|
||||||
(if-not (map? node)
|
|
||||||
[{} node]
|
|
||||||
|
|
||||||
(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))
|
|
||||||
|
|
||||||
node-defs (->> rec-result (map first) (reduce merge current-node-defs))]
|
|
||||||
|
|
||||||
[ node-defs node ])))
|
|
||||||
|
|
||||||
(defn find-attr-references [attrs]
|
|
||||||
(->> attrs
|
|
||||||
(mapcat (fn [[_ attr-value]]
|
|
||||||
(if (string? attr-value)
|
|
||||||
(extract-ids attr-value)
|
|
||||||
(find-attr-references attr-value))))))
|
|
||||||
|
|
||||||
(defn find-node-references [node]
|
|
||||||
(let [current (->> (find-attr-references (:attrs node)) (into #{}))
|
|
||||||
children (->> (:content node) (map find-node-references) (flatten) (into #{}))]
|
|
||||||
(vec (into current children))))
|
|
||||||
|
|
||||||
(defn find-def-references [defs references]
|
|
||||||
(loop [result (into #{} references)
|
|
||||||
checked? #{}
|
|
||||||
to-check (first references)
|
|
||||||
pending (rest references)]
|
|
||||||
|
|
||||||
(cond
|
|
||||||
(nil? to-check)
|
|
||||||
result
|
|
||||||
|
|
||||||
(checked? to-check)
|
|
||||||
(recur result
|
|
||||||
checked?
|
|
||||||
(first pending)
|
|
||||||
(rest pending))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(let [node (get defs to-check)
|
|
||||||
new-refs (find-node-references node)
|
|
||||||
pending (concat pending new-refs)]
|
|
||||||
(recur (into result new-refs)
|
|
||||||
(conj checked? to-check)
|
|
||||||
(first pending)
|
|
||||||
(rest pending))))))
|
|
||||||
|
|
||||||
(defn svg-transform-matrix [shape]
|
|
||||||
(if (:svg-viewbox shape)
|
|
||||||
(let [{svg-x :x
|
|
||||||
svg-y :y
|
|
||||||
svg-width :width
|
|
||||||
svg-height :height} (:svg-viewbox shape)
|
|
||||||
{:keys [x y width height]} (:selrect shape)
|
|
||||||
|
|
||||||
scale-x (/ width svg-width)
|
|
||||||
scale-y (/ height svg-height)]
|
|
||||||
|
|
||||||
(gmt/multiply
|
|
||||||
(gmt/matrix)
|
|
||||||
|
|
||||||
;; Paths doesn't have transform so we have to transform its gradients
|
|
||||||
(if (or (= :path (:type shape))
|
|
||||||
(= :group (:type shape)))
|
|
||||||
(gsh/transform-matrix shape)
|
|
||||||
(gmt/matrix))
|
|
||||||
|
|
||||||
(gmt/translate-matrix (gpt/point (- x (* scale-x svg-x)) (- y (* scale-y svg-y))))
|
|
||||||
(gmt/scale-matrix (gpt/point scale-x scale-y))))
|
|
||||||
|
|
||||||
;; :else
|
|
||||||
(gmt/matrix)))
|
|
||||||
|
|
||||||
;; Parse transform attributes to native matrix format so we can transform paths instead of
|
|
||||||
;; relying in SVG transformation. This is necessary to import SVG's and not to break path tooling
|
|
||||||
;;
|
|
||||||
;; Transforms spec:
|
|
||||||
;; https://www.w3.org/TR/SVG11/single-page.html#coords-TransformAttribute
|
|
||||||
|
|
||||||
|
|
||||||
(defn format-translate-params [params]
|
|
||||||
(assert (or (= (count params) 1) (= (count params) 2)))
|
|
||||||
(if (= (count params) 1)
|
|
||||||
[(gpt/point (nth params 0) 0)]
|
|
||||||
[(gpt/point (nth params 0) (nth params 1))]))
|
|
||||||
|
|
||||||
(defn format-scale-params [params]
|
|
||||||
(assert (or (= (count params) 1) (= (count params) 2)))
|
|
||||||
(if (= (count params) 1)
|
|
||||||
[(gpt/point (nth params 0))]
|
|
||||||
[(gpt/point (nth params 0) (nth params 1))]))
|
|
||||||
|
|
||||||
(defn format-rotate-params [params]
|
|
||||||
(assert (or (= (count params) 1) (= (count params) 3)) (str "??" (count params)))
|
|
||||||
(if (= (count params) 1)
|
|
||||||
[(nth params 0) (gpt/point 0 0)]
|
|
||||||
[(nth params 0) (gpt/point (nth params 1) (nth params 2))]))
|
|
||||||
|
|
||||||
(defn format-skew-x-params [params]
|
|
||||||
(assert (= (count params) 1))
|
|
||||||
[(nth params 0) 0])
|
|
||||||
|
|
||||||
(defn format-skew-y-params [params]
|
|
||||||
(assert (= (count params) 1))
|
|
||||||
[0 (nth params 0)])
|
|
||||||
|
|
||||||
(defn to-matrix [{:keys [type params]}]
|
|
||||||
(assert (#{"matrix" "translate" "scale" "rotate" "skewX" "skewY"} type))
|
|
||||||
(case type
|
|
||||||
"matrix" (apply gmt/matrix params)
|
|
||||||
"translate" (apply gmt/translate-matrix (format-translate-params params))
|
|
||||||
"scale" (apply gmt/scale-matrix (format-scale-params params))
|
|
||||||
"rotate" (apply gmt/rotate-matrix (format-rotate-params params))
|
|
||||||
"skewX" (apply gmt/skew-matrix (format-skew-x-params params))
|
|
||||||
"skewY" (apply gmt/skew-matrix (format-skew-y-params params))))
|
|
||||||
|
|
||||||
(defn parse-transform [transform-attr]
|
|
||||||
(if transform-attr
|
|
||||||
(let [process-matrix
|
|
||||||
(fn [[_ type params]]
|
|
||||||
(let [params (->> (re-seq number-regex params)
|
|
||||||
(filter #(-> % first seq))
|
|
||||||
(map (comp d/parse-double first)))]
|
|
||||||
{:type type :params params}))
|
|
||||||
|
|
||||||
matrices (->> (re-seq matrices-regex transform-attr)
|
|
||||||
(map process-matrix)
|
|
||||||
(map to-matrix))]
|
|
||||||
(reduce gmt/multiply (gmt/matrix) matrices))
|
|
||||||
(gmt/matrix)))
|
|
||||||
|
|
||||||
(defn format-move [[x y]] (str "M" x " " y))
|
|
||||||
(defn format-line [[x y]] (str "L" x " " y))
|
|
||||||
|
|
||||||
(defn points->path [points-str]
|
|
||||||
(let [points (->> points-str
|
|
||||||
(re-seq number-regex)
|
|
||||||
(filter (comp not empty? first))
|
|
||||||
(mapv (comp d/parse-double first))
|
|
||||||
(partition 2))
|
|
||||||
|
|
||||||
head (first points)
|
|
||||||
other (rest points)]
|
|
||||||
|
|
||||||
(str (format-move head)
|
|
||||||
(->> other (map format-line) (str/join " ")))))
|
|
||||||
|
|
||||||
(defn polyline->path [{:keys [attrs] :as node}]
|
|
||||||
(let [tag :path
|
|
||||||
attrs (-> attrs
|
|
||||||
(dissoc :points)
|
|
||||||
(assoc :d (points->path (:points attrs))))]
|
|
||||||
|
|
||||||
(assoc node :attrs attrs :tag tag)))
|
|
||||||
|
|
||||||
(defn polygon->path [{:keys [attrs] :as node}]
|
|
||||||
(let [tag :path
|
|
||||||
attrs (-> attrs
|
|
||||||
(dissoc :points)
|
|
||||||
(assoc :d (str (points->path (:points attrs)) "Z")))]
|
|
||||||
(assoc node :attrs attrs :tag tag)))
|
|
||||||
|
|
||||||
(defn line->path [{:keys [attrs] :as node}]
|
|
||||||
(let [tag :path
|
|
||||||
{:keys [x1 y1 x2 y2]} attrs
|
|
||||||
x1 (or x1 0)
|
|
||||||
y1 (or y1 0)
|
|
||||||
x2 (or x2 0)
|
|
||||||
y2 (or y2 0)
|
|
||||||
attrs (-> attrs
|
|
||||||
(dissoc :x1 :x2 :y1 :y2)
|
|
||||||
(assoc :d (str "M" x1 "," y1 " L" x2 "," y2)))]
|
|
||||||
|
|
||||||
(assoc node :attrs attrs :tag tag)))
|
|
||||||
|
|
||||||
(defn add-transform [attrs transform]
|
|
||||||
(letfn [(append-transform [old-transform]
|
|
||||||
(if (or (nil? old-transform) (empty? old-transform))
|
|
||||||
transform
|
|
||||||
(str transform " " old-transform)))]
|
|
||||||
|
|
||||||
(cond-> attrs
|
|
||||||
transform
|
|
||||||
(update :transform append-transform))))
|
|
||||||
|
|
||||||
(defn inherit-attributes [group-attrs {:keys [attrs] :as node}]
|
|
||||||
(if (map? node)
|
|
||||||
(let [attrs (-> (format-styles attrs)
|
|
||||||
(add-transform (:transform group-attrs)))
|
|
||||||
attrs (d/deep-merge (select-keys group-attrs inheritable-props) attrs)]
|
|
||||||
(assoc node :attrs attrs))
|
|
||||||
node))
|
|
||||||
|
|
||||||
(defn map-nodes [mapfn node]
|
|
||||||
(let [update-content
|
|
||||||
(fn [content] (cond->> content
|
|
||||||
(vector? content)
|
|
||||||
(mapv (partial map-nodes mapfn))))]
|
|
||||||
|
|
||||||
(cond-> node
|
|
||||||
(map? node)
|
|
||||||
(-> (mapfn)
|
|
||||||
(d/update-when :content update-content)))))
|
|
||||||
|
|
||||||
(defn reduce-nodes [redfn value node]
|
|
||||||
(let [reduce-content
|
|
||||||
(fn [value content]
|
|
||||||
(loop [current (first content)
|
|
||||||
content (rest content)
|
|
||||||
value value]
|
|
||||||
(if (nil? current)
|
|
||||||
value
|
|
||||||
(recur (first content)
|
|
||||||
(rest content)
|
|
||||||
(reduce-nodes redfn value current)))))]
|
|
||||||
|
|
||||||
(if (map? node)
|
|
||||||
(-> (redfn value node)
|
|
||||||
(reduce-content (:content node)))
|
|
||||||
value)))
|
|
||||||
|
|
||||||
(defn fix-default-values
|
|
||||||
"Gives values to some SVG elements which defaults won't work when imported into the platform"
|
|
||||||
[svg-data]
|
|
||||||
(let [add-defaults
|
|
||||||
(fn [{:keys [tag attrs] :as node}]
|
|
||||||
(let [prop (get-in svg-tag-defaults [tag :units])
|
|
||||||
default-units (get-in svg-tag-defaults [tag :default])
|
|
||||||
units (get attrs prop default-units)
|
|
||||||
tag-default (get-in svg-tag-defaults [tag units])]
|
|
||||||
(d/update-when node :attrs #(merge tag-default %))))
|
|
||||||
|
|
||||||
fix-node-defaults
|
|
||||||
(fn [node]
|
|
||||||
(cond-> node
|
|
||||||
(contains? svg-tag-defaults (:tag node))
|
|
||||||
(add-defaults)))]
|
|
||||||
|
|
||||||
(->> svg-data (map-nodes fix-node-defaults))))
|
|
||||||
|
|
||||||
(defn calculate-ratio
|
|
||||||
;; sqrt((actual-width)**2 + (actual-height)**2)/sqrt(2).
|
|
||||||
[width height]
|
|
||||||
(/ (mth/hypot width height)
|
|
||||||
(mth/sqrt 2)))
|
|
||||||
|
|
||||||
(defn fix-percents
|
|
||||||
"Changes percents to a value according to the size of the svg imported"
|
|
||||||
[svg-data]
|
|
||||||
;; https://www.w3.org/TR/SVG11/single-page.html#coords-Units
|
|
||||||
(let [viewbox {:x (:offset-x svg-data)
|
|
||||||
:y (:offset-y svg-data)
|
|
||||||
:width (:width svg-data)
|
|
||||||
:height (:height svg-data)
|
|
||||||
:ratio (calculate-ratio (:width svg-data) (:height svg-data))}]
|
|
||||||
(letfn [(fix-length [prop-length val]
|
|
||||||
(* (get viewbox prop-length) (/ val 100.)))
|
|
||||||
|
|
||||||
(fix-coord [prop-coord prop-length val]
|
|
||||||
(+ (get viewbox prop-coord)
|
|
||||||
(fix-length prop-length val)))
|
|
||||||
|
|
||||||
(fix-percent-attr-viewbox [attr-key attr-val]
|
|
||||||
(let [is-percent? (str/ends-with? attr-val "%")
|
|
||||||
is-x? #{:x :x1 :x2 :cx}
|
|
||||||
is-y? #{:y :y1 :y2 :cy}
|
|
||||||
is-width? #{:width}
|
|
||||||
is-height? #{:height}
|
|
||||||
is-other? #{:r :stroke-width}]
|
|
||||||
|
|
||||||
(if is-percent?
|
|
||||||
;; JS parseFloat removes the % symbol
|
|
||||||
(let [attr-num (d/parse-double attr-val)]
|
|
||||||
(str (cond
|
|
||||||
(is-x? attr-key) (fix-coord :x :width attr-num)
|
|
||||||
(is-y? attr-key) (fix-coord :y :height attr-num)
|
|
||||||
(is-width? attr-key) (fix-length :width attr-num)
|
|
||||||
(is-height? attr-key) (fix-length :height attr-num)
|
|
||||||
(is-other? attr-key) (fix-length :ratio attr-num)
|
|
||||||
:else attr-val)))
|
|
||||||
attr-val)))
|
|
||||||
|
|
||||||
(fix-percent-attrs-viewbox [attrs]
|
|
||||||
(d/mapm fix-percent-attr-viewbox attrs))
|
|
||||||
|
|
||||||
(fix-percent-attr-numeric [_ attr-val]
|
|
||||||
(let [is-percent? (str/ends-with? attr-val "%")]
|
|
||||||
(if is-percent?
|
|
||||||
(str (let [attr-num (d/parse-double attr-val)]
|
|
||||||
(/ attr-num 100)))
|
|
||||||
attr-val)))
|
|
||||||
|
|
||||||
(fix-percent-attrs-numeric [attrs]
|
|
||||||
(d/mapm fix-percent-attr-numeric attrs))
|
|
||||||
|
|
||||||
(fix-percent-values [node]
|
|
||||||
(let [units (or (get-in node [:attrs :filterUnits])
|
|
||||||
(get-in node [:attrs :gradientUnits])
|
|
||||||
(get-in node [:attrs :patternUnits])
|
|
||||||
(get-in node [:attrs :clipUnits]))]
|
|
||||||
(cond-> node
|
|
||||||
(= "objectBoundingBox" units)
|
|
||||||
(update :attrs fix-percent-attrs-numeric)
|
|
||||||
|
|
||||||
(not= "objectBoundingBox" units)
|
|
||||||
(update :attrs fix-percent-attrs-viewbox))))]
|
|
||||||
|
|
||||||
(->> svg-data (map-nodes fix-percent-values)))))
|
|
||||||
|
|
||||||
(defn collect-images [svg-data]
|
|
||||||
(let [redfn (fn [acc {:keys [tag attrs]}]
|
|
||||||
(cond-> acc
|
|
||||||
(= :image tag)
|
|
||||||
(conj (or (:href attrs) (:xlink:href attrs)))))]
|
|
||||||
(reduce-nodes redfn [] svg-data )))
|
|
Loading…
Add table
Add a link
Reference in a new issue