0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-18 21:06:11 -05:00

♻️ Refactor custom-stroke render impl

This commit is contained in:
Andrey Antukh 2023-09-06 17:02:00 +02:00
parent 1b420e55f4
commit bf2a546f77
4 changed files with 374 additions and 299 deletions

View file

@ -144,6 +144,7 @@
(:opacity shape) (:opacity shape)
(obj/set! "opacity" (:opacity shape)))) (obj/set! "opacity" (:opacity shape))))
;; FIXME: DEPRECATED
(defn extract-svg-attrs (defn extract-svg-attrs
[render-id svg-defs svg-attrs] [render-id svg-defs svg-attrs]
(if (and (empty? svg-defs) (empty? svg-attrs)) (if (and (empty? svg-defs) (empty? svg-attrs))
@ -158,9 +159,26 @@
(dissoc :id)) (dissoc :id))
attrs (-> svg-attrs (dissoc :style) (clj->js)) attrs (-> svg-attrs (dissoc :style) (clj->js))
styles (-> svg-attrs (:style {}) (clj->js))] styles (-> svg-attrs (get :style {}) (clj->js))]
[attrs styles]))) [attrs styles])))
(defn get-svg-attrs
[shape render-id]
(let [svg-attrs (get shape :svg-attrs {})
svg-defs (get shape :svg-defs {})]
(if (and (empty? svg-defs)
(empty? svg-attrs))
{}
(let [replace-id (fn [id]
(if (contains? svg-defs id)
(str render-id "-" id)
id))]
(-> svg-attrs
(usvg/clean-attrs)
(usvg/update-attr-ids replace-id)
(dissoc :id))))))
(defn add-style-attrs (defn add-style-attrs
([props shape] ([props shape]
(let [render-id (mf/use-ctx muc/render-id)] (let [render-id (mf/use-ctx muc/render-id)]
@ -226,19 +244,15 @@
(-> (obj/create) (-> (obj/create)
(add-style-attrs shape render-id)))) (add-style-attrs shape render-id))))
(defn extract-fill-attrs (defn get-stroke-style
[fill-data render-id index type]
(let [fill-styles (-> (obj/get fill-data "style" (obj/create))
(add-fill fill-data render-id index type))]
(-> (obj/create)
(obj/set! "style" fill-styles))))
(defn extract-stroke-attrs
[stroke-data index render-id] [stroke-data index render-id]
(let [stroke-styles (-> (obj/get stroke-data "style" (obj/create)) ;; FIXME: optimize
(add-stroke stroke-data render-id index))] (add-stroke #js {} stroke-data render-id index))
(-> (obj/create)
(obj/set! "style" stroke-styles)))) (defn get-fill-style
[fill-data index render-id type]
;; FIXME: optimize
(add-fill #js {} fill-data render-id index type))
(defn extract-border-radius-attrs (defn extract-border-radius-attrs
[shape] [shape]

View file

@ -20,188 +20,204 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(defn add-props
[props new-props]
(-> props
(obj/merge (clj->js new-props))))
(defn add-style
[props new-style]
(let [old-style (obj/get props "style")
style (obj/merge old-style (clj->js new-style))]
(-> props (obj/merge #js {:style style}))))
(mf/defc inner-stroke-clip-path (mf/defc inner-stroke-clip-path
{::mf/wrap-props false}
[{:keys [shape render-id index]}] [{:keys [shape render-id index]}]
(let [suffix (if index (str "-" index) "") (let [shape-id (dm/get-prop shape :id)
clip-id (str "inner-stroke-" render-id "-" (:id shape) suffix) suffix (if (some? index) (dm/str "-" index) "")
shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix)] clip-id (dm/str "inner-stroke-" render-id "-" shape-id suffix)
href (dm/str "#stroke-shape-" render-id "-" shape-id suffix)]
[:> "clipPath" #js {:id clip-id} [:> "clipPath" #js {:id clip-id}
[:use {:href (str "#" shape-id)}]])) [:use {:href href}]]))
(mf/defc outer-stroke-mask (mf/defc outer-stroke-mask
{::mf/wrap-props false}
[{:keys [shape stroke render-id index]}] [{:keys [shape stroke render-id index]}]
(let [suffix (if index (str "-" index) "") (let [shape-id (dm/get-prop shape :id)
stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix) suffix (if (some? index) (dm/str "-" index) "")
shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix) mask-id (dm/str "outer-stroke-" render-id "-" shape-id suffix)
stroke-width (case (:stroke-alignment stroke :center) shape-id (dm/str "stroke-shape-" render-id "-" shape-id suffix)
:center (/ (:stroke-width stroke 0) 2) href (dm/str "#" shape-id)
:outer (:stroke-width stroke 0)
0)
margin (gsb/shape-stroke-margin stroke stroke-width)
selrect stroke-width (case (:stroke-alignment stroke :center)
(if (cph/text-shape? shape) :center (/ (:stroke-width stroke 0) 2)
(gst/shape->rect shape) :outer (:stroke-width stroke 0)
(grc/points->rect (:points shape))) 0)
margin (gsb/shape-stroke-margin stroke stroke-width)
bounding-box ;; NOTE: for performance reasons we may can delimit a bit the
(-> selrect ;; dependencies to really useful shape attrs instead of using
(update :x - (+ stroke-width margin)) ;; the shepe as-is.
(update :y - (+ stroke-width margin)) selrect (mf/with-memo [shape]
(update :width + (* 2 (+ stroke-width margin))) (if (cph/text-shape? shape)
(update :height + (* 2 (+ stroke-width margin))) (gst/shape->rect shape)
(grc/update-rect :position))] (grc/points->rect (:points shape))))
[:mask {:id stroke-mask-id stroke-margin (+ stroke-width margin)
:x (:x bounding-box)
:y (:y bounding-box) x (- (dm/get-prop selrect :x) stroke-margin)
:width (:width bounding-box) y (- (dm/get-prop selrect :y) stroke-margin)
:height (:height bounding-box) w (+ (dm/get-prop selrect :width) (* 2 stroke-margin))
h (+ (dm/get-prop selrect :height) (* 2 stroke-margin))]
[:mask {:id mask-id
:x x
:y y
:width w
:height h
:maskUnits "userSpaceOnUse"} :maskUnits "userSpaceOnUse"}
[:use {:href (str "#" shape-id) [:use
:style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}] {:href href
:style {:fill "none"
:stroke "white"
:strokeWidth (* stroke-width 2)}}]
[:use {:href (str "#" shape-id) [:use
:style #js {:fill "black" {:href href
:stroke "none"}}]])) :style {:fill "black"
:stroke "none"}}]]))
(mf/defc cap-markers (mf/defc cap-markers
{::mf/wrap-props false}
[{:keys [stroke render-id index]}] [{:keys [stroke render-id index]}]
(let [marker-id-prefix (str "marker-" render-id) (let [id-prefix (dm/str "marker-" render-id)
gradient (:stroke-color-gradient stroke)
cap-start (:stroke-cap-start stroke) cap-start (:stroke-cap-start stroke)
cap-end (:stroke-cap-end stroke) cap-end (:stroke-cap-end stroke)
stroke-color (if (:stroke-color-gradient stroke) color (if (some? gradient)
(str/format "url(#%s)" (str "stroke-color-gradient_" render-id "_" index)) (str/ffmt "url(#stroke-color-gradient_%s_%s)" render-id index)
(:stroke-color stroke)) (:stroke-color stroke))
stroke-opacity (when-not (:stroke-color-gradient stroke) opacity (when-not (some? gradient)
(:stroke-opacity stroke))] (:stroke-opacity stroke))]
[:* [:*
(when (or (= cap-start :line-arrow) (= cap-end :line-arrow)) (when (or (= cap-start :line-arrow)
[:marker {:id (str marker-id-prefix "-line-arrow") (= cap-end :line-arrow))
[:marker {:id (dm/str id-prefix "-line-arrow")
:viewBox "0 0 3 6" :viewBox "0 0 3 6"
:refX "2" :refX "2"
:refY "3" :refY "3"
:markerWidth "8.5" :markerWidth "8.5"
:markerHeight "8.5" :markerHeight "8.5"
:orient "auto-start-reverse" :orient "auto-start-reverse"
:fill stroke-color :fill color
:fillOpacity stroke-opacity} :fillOpacity opacity}
[:path {:d "M 0.5 0.5 L 3 3 L 0.5 5.5 L 0 5 L 2 3 L 0 1 z"}]]) [:path {:d "M 0.5 0.5 L 3 3 L 0.5 5.5 L 0 5 L 2 3 L 0 1 z"}]])
(when (or (= cap-start :triangle-arrow) (= cap-end :triangle-arrow)) (when (or (= cap-start :triangle-arrow)
[:marker {:id (str marker-id-prefix "-triangle-arrow") (= cap-end :triangle-arrow))
[:marker {:id (dm/str id-prefix "-triangle-arrow")
:viewBox "0 0 3 6" :viewBox "0 0 3 6"
:refX "2" :refX "2"
:refY "3" :refY "3"
:markerWidth "8.5" :markerWidth "8.5"
:markerHeight "8.5" :markerHeight "8.5"
:orient "auto-start-reverse" :orient "auto-start-reverse"
:fill stroke-color :fill color
:fillOpacity stroke-opacity} :fillOpacity opacity}
[:path {:d "M 0 0 L 3 3 L 0 6 z"}]]) [:path {:d "M 0 0 L 3 3 L 0 6 z"}]])
(when (or (= cap-start :square-marker) (= cap-end :square-marker)) (when (or (= cap-start :square-marker)
[:marker {:id (str marker-id-prefix "-square-marker") (= cap-end :square-marker))
[:marker {:id (dm/str id-prefix "-square-marker")
:viewBox "0 0 6 6" :viewBox "0 0 6 6"
:refX "3" :refX "3"
:refY "3" :refY "3"
:markerWidth "4.2426" ;; diagonal length of a 3x3 square :markerWidth "4.2426" ;; diagonal length of a 3x3 square
:markerHeight "4.2426" :markerHeight "4.2426"
:orient "auto-start-reverse" :orient "auto-start-reverse"
:fill stroke-color :fill color
:fillOpacity stroke-opacity} :fillOpacity opacity}
[:rect {:x 0 :y 0 :width 6 :height 6}]]) [:rect {:x 0 :y 0 :width 6 :height 6}]])
(when (or (= cap-start :circle-marker) (= cap-end :circle-marker)) (when (or (= cap-start :circle-marker)
[:marker {:id (str marker-id-prefix "-circle-marker") (= cap-end :circle-marker))
[:marker {:id (dm/str id-prefix "-circle-marker")
:viewBox "0 0 6 6" :viewBox "0 0 6 6"
:refX "3" :refX "3"
:refY "3" :refY "3"
:markerWidth "4" :markerWidth "4"
:markerHeight "4" :markerHeight "4"
:orient "auto-start-reverse" :orient "auto-start-reverse"
:fill stroke-color :fill color
:fillOpacity stroke-opacity} :fillOpacity opacity}
[:circle {:cx "3" :cy "3" :r "3"}]]) [:circle {:cx "3" :cy "3" :r "3"}]])
(when (or (= cap-start :diamond-marker) (= cap-end :diamond-marker)) (when (or (= cap-start :diamond-marker)
[:marker {:id (str marker-id-prefix "-diamond-marker") (= cap-end :diamond-marker))
[:marker {:id (dm/str id-prefix "-diamond-marker")
:viewBox "0 0 6 6" :viewBox "0 0 6 6"
:refX "3" :refX "3"
:refY "3" :refY "3"
:markerWidth "6" :markerWidth "6"
:markerHeight "6" :markerHeight "6"
:orient "auto-start-reverse" :orient "auto-start-reverse"
:fill stroke-color :fill color
:fillOpacity stroke-opacity} :fillOpacity opacity}
[:path {:d "M 3 0 L 6 3 L 3 6 L 0 3 z"}]]) [:path {:d "M 3 0 L 6 3 L 3 6 L 0 3 z"}]])
;; If the user wants line caps but different in each end, ;; If the user wants line caps but different in each end,
;; simulate it with markers. ;; simulate it with markers.
(when (and (or (= cap-start :round) (= cap-end :round)) (when (and (or (= cap-start :round)
(= cap-end :round))
(not= cap-start cap-end)) (not= cap-start cap-end))
[:marker {:id (str marker-id-prefix "-round") [:marker {:id (dm/str id-prefix "-round")
:viewBox "0 0 6 6" :viewBox "0 0 6 6"
:refX "3" :refX "3"
:refY "3" :refY "3"
:markerWidth "6" :markerWidth "6"
:markerHeight "6" :markerHeight "6"
:orient "auto-start-reverse" :orient "auto-start-reverse"
:fill stroke-color :fill color
:fillOpacity stroke-opacity} :fillOpacity opacity}
[:path {:d "M 3 2.5 A 0.5 0.5 0 0 1 3 3.5 "}]]) [:path {:d "M 3 2.5 A 0.5 0.5 0 0 1 3 3.5 "}]])
(when (and (or (= cap-start :square) (= cap-end :square)) (when (and (or (= cap-start :square)
(= cap-end :square))
(not= cap-start cap-end)) (not= cap-start cap-end))
[:marker {:id (str marker-id-prefix "-square") [:marker {:id (dm/str id-prefix "-square")
:viewBox "0 0 6 6" :viewBox "0 0 6 6"
:refX "3" :refX "3"
:refY "3" :refY "3"
:markerWidth "6" :markerWidth "6"
:markerHeight "6" :markerHeight "6"
:orient "auto-start-reverse" :orient "auto-start-reverse"
:fill stroke-color :fill color
:fillOpacity stroke-opacity} :fillOpacity opacity}
[:rect {:x 3 :y 2.5 :width 0.5 :height 1}]])])) [:rect {:x 3 :y 2.5 :width 0.5 :height 1}]])]))
(mf/defc stroke-defs (mf/defc stroke-defs
{::mf/wrap-props false}
[{:keys [shape stroke render-id index]}] [{:keys [shape stroke render-id index]}]
(let [open-path? (and ^boolean (cph/path-shape? shape)
^boolean (gsh/open-path? shape))
gradient (:stroke-color-gradient stroke)
alignment (:stroke-alignment stroke :center)
width (:stroke-width stroke 0)
(let [open-path? (and (= :path (:type shape)) (gsh/open-path? shape))] props #js {:id (dm/str "stroke-color-gradient_" render-id "_" index)
:gradient gradient
:shape shape}]
[:* [:*
(cond (some? (:stroke-color-gradient stroke)) (when (some? gradient)
(case (:type (:stroke-color-gradient stroke)) (case (:type gradient)
:linear [:> grad/linear-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index) :linear [:> grad/linear-gradient props]
:gradient (:stroke-color-gradient stroke) :radial [:> grad/radial-gradient props]))
:shape shape}]
:radial [:> grad/radial-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index)
:gradient (:stroke-color-gradient stroke)
:shape shape}]))
(cond (cond
(and (not open-path?) (and (not open-path?)
(= :inner (:stroke-alignment stroke :center)) (= :inner alignment)
(> (:stroke-width stroke 0) 0)) (> width 0))
[:& inner-stroke-clip-path {:shape shape [:& inner-stroke-clip-path {:shape shape
:render-id render-id :render-id render-id
:index index}] :index index}]
(and (not open-path?) (and (not open-path?)
(= :outer (:stroke-alignment stroke :center)) (= :outer alignment)
(> (:stroke-width stroke 0) 0)) (> width 0))
[:& outer-stroke-mask {:shape shape [:& outer-stroke-mask {:shape shape
:stroke stroke :stroke stroke
:render-id render-id :render-id render-id
@ -213,119 +229,140 @@
:render-id render-id :render-id render-id
:index index}])])) :index index}])]))
;; Outer alignment: display the shape in two layers. One ;; Outer alignment: display the shape in two layers. One without
;; without stroke (only fill), and another one only with stroke ;; stroke (only fill), and another one only with stroke at double
;; at double width (transparent fill) and passed through a mask ;; width (transparent fill) and passed through a mask that shows the
;; that shows the whole shape, but hides the original shape ;; whole shape, but hides the original shape without stroke
;; without stroke
(mf/defc outer-stroke (mf/defc outer-stroke
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [render-id (mf/use-ctx muc/render-id) (let [child (unchecked-get props "children")
child (obj/get props "children") shape (unchecked-get props "shape")
base-props (obj/get child "props") stroke (unchecked-get props "stroke")
elem-name (obj/get child "type") index (unchecked-get props "index")
shape (obj/get props "shape")
stroke (obj/get props "stroke")
index (obj/get props "index")
stroke-width (:stroke-width stroke)
suffix (if index (str "-" index) "") shape-id (dm/get-prop shape :id)
stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix) render-id (mf/use-ctx muc/render-id)
shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix)]
props (obj/get child "props")
style (obj/get props "style")
stroke-width (:stroke-width stroke 0)
suffix (if (some? index) (dm/str "-" index) "")
mask-id (dm/str "outer-stroke-" render-id "-" shape-id suffix)
shape-id (dm/str "stroke-shape-" render-id "-" shape-id suffix)
href (dm/str "#" shape-id)]
[:g.outer-stroke-shape [:g.outer-stroke-shape
[:defs [:defs
[:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}] [:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]
[:> elem-name (-> (obj/clone base-props) (let [type (obj/get child "type")
(obj/set! "id" shape-id) style (-> (obj/clone style)
(obj/set! (obj/unset! "fill")
"style" (obj/unset! "fillOpacity")
(-> (obj/get base-props "style") (obj/unset! "stroke")
(obj/clone) (obj/unset! "strokeWidth")
(obj/without ["fill" "fillOpacity" "stroke" "strokeWidth" "strokeOpacity" "strokeStyle" "strokeDasharray"]))))]] (obj/unset! "strokeOpacity")
(obj/unset! "strokeStyle")
(obj/unset! "strokeDasharray"))
props (-> (obj/clone props)
(obj/set! "id" shape-id)
(obj/set! "style" style))]
[:use {:href (str "#" shape-id) [:> type props])]
:mask (str "url(#" stroke-mask-id ")")
:style (-> (obj/get base-props "style") [:use {:href href
(obj/clone) :mask (dm/str "url(#" mask-id ")")
:style (-> (obj/clone style)
(obj/set! "strokeWidth" (* stroke-width 2)) (obj/set! "strokeWidth" (* stroke-width 2))
(obj/without ["fill" "fillOpacity"]) (obj/set! "fill" "none")
(obj/set! "fill" "none"))}] (obj/unset! "fillOpacity"))}]
[:use {:href (str "#" shape-id) [:use {:href href
:style (-> (obj/get base-props "style") :style (-> (obj/clone style)
(obj/clone)
(obj/set! "stroke" "none"))}]])) (obj/set! "stroke" "none"))}]]))
;; Inner alignment: display the shape with double width stroke, ;; Inner alignment: display the shape with double width stroke, and
;; and clip the result with the original shape without stroke. ;; clip the result with the original shape without stroke.
(mf/defc inner-stroke (mf/defc inner-stroke
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [render-id (mf/use-ctx muc/render-id) (let [child (unchecked-get props "children")
child (obj/get props "children") shape (unchecked-get props "shape")
base-props (obj/get child "props") stroke (unchecked-get props "stroke")
elem-name (obj/get child "type") index (unchecked-get props "index")
shape (obj/get props "shape")
stroke (obj/get props "stroke") shape-id (dm/get-prop shape :id)
index (obj/get props "index") render-id (mf/use-ctx muc/render-id)
transform (obj/get base-props "transform")
type (obj/get child "type")
props (-> (obj/get child "props") obj/clone)
;; FIXME: check if style need to be cloned
style (-> (obj/get props "style") obj/clone)
transform (obj/get props "transform")
stroke-width (:stroke-width stroke 0) stroke-width (:stroke-width stroke 0)
suffix (if index (str "-" index) "") suffix (if (some? index) (dm/str "-" index) "")
clip-id (str "inner-stroke-" render-id "-" (:id shape) suffix) clip-id (dm/str "inner-stroke-" render-id "-" shape-id suffix)
shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix) shape-id (dm/str "stroke-shape-" render-id "-" shape-id suffix)
clip-path (dm/str "url('#" clip-id "')")
clip-path (str "url('#" clip-id "')") style (obj/set! style "strokeWidth" (* stroke-width 2))
shape-props (-> base-props
(add-props {:id shape-id
:transform nil})
(add-style {:strokeWidth (* stroke-width 2)}))]
[:g.inner-stroke-shape {:transform transform} props (-> props
(obj/set! "id" (dm/str shape-id))
(obj/set! "style" style)
(obj/unset! "transform"))]
[:g.inner-stroke-shape
{:transform transform}
[:defs [:defs
[:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}] [:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]
[:> elem-name shape-props]] [:> type props]]
[:use {:href (str "#" shape-id) [:use {:href (dm/str "#" shape-id)
:clipPath clip-path}]])) :clipPath clip-path}]]))
; The SVG standard does not implement yet the 'stroke-alignment' ;; The SVG standard does not implement yet the 'stroke-alignment'
; attribute, to define the position of the stroke relative to the ;; attribute, to define the position of the stroke relative to the
; stroke axis (inner, center, outer). Here we implement a patch to be ;; stroke axis (inner, center, outer). Here we implement a patch to be
; able to draw the stroke in the three cases. See discussion at: ;; able to draw the stroke in the three cases. See discussion at:
; https://stackoverflow.com/questions/7241393/can-you-control-how-an-svgs-stroke-width-is-drawn ;; https://stackoverflow.com/questions/7241393/can-you-control-how-an-svgs-stroke-width-is-drawn
(mf/defc shape-custom-stroke (mf/defc shape-custom-stroke
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [child (unchecked-get props "children")
shape (unchecked-get props "shape")
stroke (unchecked-get props "stroke")
index (unchecked-get props "index")
(let [child (obj/get props "children") render-id (mf/use-ctx muc/render-id)
shape (obj/get props "shape")
stroke (obj/get props "stroke")
render-id (mf/use-ctx muc/render-id) stroke-width (:stroke-width stroke 0)
index (obj/get props "index") stroke-style (:stroke-style stroke :none)
stroke-width (:stroke-width stroke 0)
stroke-style (:stroke-style stroke :none)
stroke-position (:stroke-alignment stroke :center) stroke-position (:stroke-alignment stroke :center)
has-stroke? (and (> stroke-width 0)
(not= stroke-style :none)) has-stroke? (and (> stroke-width 0)
closed? (or (not= :path (:type shape)) (not (gsh/open-path? shape))) (not= stroke-style :none))
inner? (= :inner stroke-position) closed? (or (not ^boolean (cph/path-shape? shape))
outer? (= :outer stroke-position)] (not ^boolean (gsh/open-path? shape)))
inner? (= :inner stroke-position)
outer? (= :outer stroke-position)]
(cond (cond
(and has-stroke? inner? closed?) (and has-stroke? inner? closed?)
[:& inner-stroke {:shape shape :stroke stroke :index index} [:& inner-stroke {:shape shape :stroke stroke :index index} child]
child]
(and has-stroke? outer? closed?) (and has-stroke? outer? closed?)
[:& outer-stroke {:shape shape :stroke stroke :index index} [:& outer-stroke {:shape shape :stroke stroke :index index} child]
child]
:else :else
[:g.stroke-shape [:g.stroke-shape
@ -333,145 +370,156 @@
[:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]] [:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]]
child]))) child])))
(defn build-fill-props [shape child position render-id] (defn- build-fill-element
(let [url-fill? (or (some? (:fill-image shape)) [shape child position render-id]
(= :image (:type shape)) (let [shape-fills (get shape :fills)
(> (count (:fills shape)) 1) shape-shadow (get shape :shadow)
(some :fill-color-gradient (:fills shape))) shape-blur (get shape :blur)
props (cond-> (obj/create) type (obj/get child "type")
(or props (-> (obj/get child "props")
;; There are any shadows (obj/clone))
(and (seq (->> (:shadow shape) (remove :hidden))) (not (cph/frame-shape? shape))) style (-> (obj/get props "style")
;; There is a blur (obj/clone))
(and (:blur shape) (-> shape :blur :hidden not) (not (cph/frame-shape? shape))))
(obj/set! "filter" (dm/fmt "url(#filter_%)" render-id)))
svg-defs (:svg-defs shape {}) url-fill? (or ^boolean (some? (:fill-image shape))
svg-attrs (:svg-attrs shape {}) ^boolean (cph/image-shape? shape)
^boolean (> (count shape-fills) 1)
^boolean (some? (some :fill-color-gradient shape-fills)))
[svg-attrs svg-styles] props (if (cph/frame-shape? shape)
(attrs/extract-svg-attrs render-id svg-defs svg-attrs)] props
(if (or (some? (->> shape-shadow (remove :hidden) seq))
(not ^boolean (:hidden shape-blur)))
(obj/set! props "filter" (dm/fmt "url(#filter_%)" render-id))
props))
svg-attrs (attrs/get-svg-attrs shape render-id)
svg-styles (get svg-attrs :style {})]
(cond (cond
url-fill? ^boolean url-fill?
(let [props (obj/set! props (do
"style" (obj/unset! style "fill")
(-> (obj/get child "props") (obj/unset! style "fillOpacity")
(obj/get "style")
(obj/clone)
(obj/without ["fill" "fillOpacity"])))]
(obj/set! props "fill" (dm/fmt "url(#fill-%-%)" position render-id))) (obj/set! props "fill" (dm/fmt "url(#fill-%-%)" position render-id)))
(and (some? svg-styles) (obj/contains? svg-styles "fill")) (and ^boolean (or (contains? svg-styles :fill)
(let [style (contains? svg-styles :fillOpacity))
(-> (obj/get child "props") ^boolean (obj/contains? svg-styles "fill"))
(obj/get "style")
(obj/clone)
(obj/set! "fill" (obj/get svg-styles "fill"))
(obj/set! "fillOpacity" (obj/get svg-styles "fillOpacity")))]
(-> props
(obj/set! "style" style)))
(and (some? svg-attrs) (empty? (:fills shape))) (let [fill (get svg-styles :fill)
(let [style opacity (get svg-styles :fillOpacity)]
(-> (obj/get child "props") (when (some? fill)
(obj/get "style") (obj/set! style "fill" fill))
(obj/clone)) (when (some? opacity)
(obj/set! style "fillOpacity" opacity)))
style (-> style (and ^boolean (or (contains? svg-attrs :fill)
(obj/set! "fill" (obj/get svg-attrs "fill")) (contains? svg-attrs :fillOpacity))
(obj/set! "fillOpacity" (obj/get svg-attrs "fillOpacity")))] ^boolean (empty? shape-fills))
(-> props (let [fill (get svg-attrs :fill)
(obj/set! "style" style))) opacity (get svg-attrs :fillOpacity)]
(when (some? fill)
(obj/set! style "fill" fill))
(when (some? opacity)
(obj/set! style "fillOpacity" opacity)))
(d/not-empty? (:fills shape)) ^boolean (d/not-empty? shape-fills)
(let [fill-props (let [fill (nth shape-fills 0)]
(attrs/extract-fill-attrs (get-in shape [:fills 0]) render-id 0 (:type shape)) (obj/merge! style (attrs/get-fill-style fill render-id 0 (dm/get-prop shape :type))))
style (-> (obj/get child "props") (and ^boolean (cph/path-shape? shape)
(obj/get "style") ^boolean (empty? shape-fills))
(obj/clone) (obj/set! style "fill" "none"))
(obj/merge! (obj/get fill-props "style")))]
(cond-> (obj/merge! props fill-props) (let [props (obj/set! props "style" style)]
(some? style) (mf/html [:> type props]))))
(obj/set! "style" style)))
(and (= :path (:type shape)) (empty? (:fills shape))) (defn- build-stroke-element
(let [style [child value position render-id]
(-> (obj/get child "props") (let [props (obj/get child "props")
(obj/get "style") type (obj/get child "type")
(obj/clone)
(obj/set! "fill" "none"))]
(-> props
(obj/set! "style" style)))
:else style (-> (obj/get props "style")
(obj/create))))
(defn build-stroke-props [position child value render-id]
(let [props (-> (obj/get child "props")
(obj/clone) (obj/clone)
(obj/without ["fill" "fillOpacity"]))] (obj/set! "fill" "none")
(-> props (obj/set! "fillOpacity" "none")
(obj/set! (obj/merge! (attrs/get-stroke-style value position render-id)))
"style"
(-> (obj/get props "style") props (-> (obj/clone props)
(obj/set! "fill" "none") (obj/unset! "fill")
(obj/set! "fillOpacity" "none"))) (obj/unset! "fillOpacity")
(add-style (obj/get (attrs/extract-stroke-attrs value position render-id) "style"))))) (obj/set! "style" style))]
(mf/html [:> type props])))
(mf/defc shape-fills (mf/defc shape-fills
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [child (obj/get props "children") (let [child (unchecked-get props "children")
shape (obj/get props "shape") shape (unchecked-get props "shape")
elem-name (obj/get child "type")
position (or (obj/get props "position") 0) shape-id (dm/get-prop shape :id)
render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))
fill-props (build-fill-props shape child position render-id)] position (d/nilv (unchecked-get props "position") 0)
[:g.fills {:id (dm/fmt "fills-%" (:id shape))}
[:> elem-name (-> (obj/get child "props") render-id (mf/use-ctx muc/render-id)
(obj/clone) render-id (d/nilv (unchecked-get props "render-id") render-id)]
(obj/merge! fill-props))]]))
[:g.fills {:id (dm/fmt "fills-%" shape-id)}
(build-fill-element shape child position render-id)]))
(mf/defc shape-strokes (mf/defc shape-strokes
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [child (obj/get props "children") (let [child (unchecked-get props "children")
shape (obj/get props "shape") shape (unchecked-get props "shape")
elem-name (obj/get child "type") shape-id (dm/get-prop shape :id)
render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))
stroke-id (dm/fmt "strokes-%" (:id shape))
stroke-props (-> (obj/create)
(obj/set! "id" stroke-id)
(obj/set! "className" "strokes")
(cond->
;; There is a blur
(and (:blur shape) (not (cph/frame-shape? shape)) (-> shape :blur :hidden not))
(obj/set! "filter" (dm/fmt "url(#filter_blur_%)" render-id))
;; There are any shadows and no fills render-id (mf/use-ctx muc/render-id)
(and (empty? (:fills shape)) (not (cph/frame-shape? shape)) (seq (->> (:shadow shape) (remove :hidden)))) render-id (d/nilv (unchecked-get props "render-id") render-id)
(obj/set! "filter" (dm/fmt "url(#filter_%)" render-id))))]
[:* stroke-id (dm/fmt "strokes-%" shape-id)
(when
(d/not-empty? (:strokes shape)) shape-blur (get shape :blur)
[:> :g stroke-props shape-fills (get shape :fills)
(for [[index value] (-> (d/enumerate (:strokes shape)) reverse)] shape-shadow (get shape :shadow)
(let [props (build-stroke-props index child value render-id)] shape-strokes (get shape :strokes)
[:& shape-custom-stroke {:shape shape :stroke value :index index :key (dm/str index "-" stroke-id)}
[:> elem-name props]]))])])) props #js {:id stroke-id :className "strokes"}
props (if ^boolean (cph/frame-shape? shape)
props
(cond
(and (some? shape-blur)
(not ^boolean (:hidden shape-blur)))
(obj/set! props "filter" (dm/fmt "url(#filter_blur_%)" render-id))
(and (empty? shape-fills)
(some? (->> shape-shadow (remove :hidden) seq)))
(obj/set! props "filter" (dm/fmt "url(#filter_%)" render-id))))]
(when (d/not-empty? shape-strokes)
[:> :g props
(for [[index value] (-> (d/enumerate shape-strokes) reverse)]
[:& shape-custom-stroke {:shape shape
:stroke value
:index index
:key (dm/str index "-" stroke-id)}
(build-stroke-element child value index render-id)])])))
(mf/defc shape-custom-strokes (mf/defc shape-custom-strokes
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [children (obj/get props "children") (let [children (unchecked-get props "children")
shape (obj/get props "shape") shape (unchecked-get props "shape")
position (obj/get props "position") position (unchecked-get props "position")
render-id (obj/get props "render-id")] render-id (unchecked-get props "render-id")
props #js {:shape shape
:position position
:render-id render-id}]
[:* [:*
[:& shape-fills {:shape shape :position position :render-id render-id} children] [:> shape-fills props children]
[:& shape-strokes {:shape shape :position position :render-id render-id} children]])) [:> shape-strokes props children]]))

View file

@ -62,13 +62,14 @@
[:* {:key (dm/str shape-index)} [:* {:key (dm/str shape-index)}
(for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)] (for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)]
(when (some? (:fill-color-gradient value)) (when (some? (:fill-color-gradient value))
(let [props #js {:id (dm/str "fill-color-gradient_" render-id "_" fill-index) (let [gradient (:fill-color-gradient value)
props #js {:id (dm/str "fill-color-gradient_" render-id "_" fill-index)
:key (dm/str fill-index) :key (dm/str fill-index)
:gradient (:fill-color-gradient value) :gradient gradient
:shape shape}] :shape shape}]
(case (d/name (:type (:fill-color-gradient value))) (case (:type gradient)
"linear" [:> grad/linear-gradient props] :linear [:> grad/linear-gradient props]
"radial" [:> grad/radial-gradient props])))) :radial [:> grad/radial-gradient props]))))
(let [fill-id (dm/str "fill-" shape-index "-" render-id)] (let [fill-id (dm/str "fill-" shape-index "-" render-id)]
@ -79,10 +80,12 @@
(obj/set! "height" (* height no-repeat-padding))))) (obj/set! "height" (* height no-repeat-padding)))))
[:g [:g
(for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)] (for [[fill-index value] (-> (d/enumerate (:fills shape [])) reverse)]
[:> :rect (-> (attrs/extract-fill-attrs value render-id fill-index type) (let [style (attrs/get-fill-style value fill-index render-id type)
(obj/set! "key" (dm/str fill-index)) props #js {:key (dm/str fill-index)
(obj/set! "width" width) :width width
(obj/set! "height" height))]) :height height
:style style}]
[:> :rect props]))
(when has-image? (when has-image?
[:g [:g

View file

@ -99,3 +99,13 @@
(defn ^boolean in? (defn ^boolean in?
[obj prop] [obj prop]
(js* "~{} in ~{}" prop obj)) (js* "~{} in ~{}" prop obj))
(defn map->obj
[o]
(reduce-kv (fn [result k v]
(let [k (if (keyword? k) (name k) k)
v (if (keyword? v) (name v) v)]
(unchecked-set result k v)
result))
#js {}
o))