mirror of
https://github.com/penpot/penpot.git
synced 2025-05-24 01:46:11 +02:00
✨ Import shadows,blur,exports
This commit is contained in:
parent
cc2c249a07
commit
4af83eadc4
4 changed files with 201 additions and 100 deletions
|
@ -30,7 +30,7 @@
|
|||
(let [clip-id (str "inner-stroke-" render-id)
|
||||
shape-id (str "stroke-shape-" render-id)]
|
||||
[:> "clipPath" #js {:id clip-id}
|
||||
[:use {:href (str "#" shape-id)}]]))
|
||||
[:use {:xlinkHref (str "#" shape-id)}]]))
|
||||
|
||||
(mf/defc outer-stroke-mask
|
||||
[{:keys [shape render-id]}]
|
||||
|
@ -38,10 +38,10 @@
|
|||
shape-id (str "stroke-shape-" render-id)
|
||||
stroke-width (:stroke-width shape 0)]
|
||||
[:mask {:id stroke-mask-id}
|
||||
[:use {:href (str "#" shape-id)
|
||||
[:use {:xlinkHref (str "#" shape-id)
|
||||
:style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}]
|
||||
|
||||
[:use {:href (str "#" shape-id)
|
||||
[:use {:xlinkHref (str "#" shape-id)
|
||||
:style #js {:fill "black"}}]]))
|
||||
|
||||
(mf/defc stroke-defs
|
||||
|
@ -84,13 +84,13 @@
|
|||
(str/join ";"))]
|
||||
|
||||
[:g.outer-stroke-shape
|
||||
[:symbol
|
||||
[:defs
|
||||
[:> elem-name (-> (obj/clone base-props)
|
||||
(obj/set! "id" shape-id)
|
||||
(obj/set! "data-style" style-str)
|
||||
(obj/without ["style"]))]]
|
||||
|
||||
[:use {:href (str "#" shape-id)
|
||||
[:use {:xlinkHref (str "#" shape-id)
|
||||
:mask (str "url(#" stroke-mask-id ")")
|
||||
:style (-> (obj/get base-props "style")
|
||||
(obj/clone)
|
||||
|
@ -98,7 +98,7 @@
|
|||
(obj/without ["fill" "fillOpacity"])
|
||||
(obj/set! "fill" "none"))}]
|
||||
|
||||
[:use {:href (str "#" shape-id)
|
||||
[:use {:xlinkHref (str "#" shape-id)
|
||||
:style (-> (obj/get base-props "style")
|
||||
(obj/clone)
|
||||
(obj/without ["stroke" "strokeWidth" "strokeOpacity" "strokeStyle" "strokeDasharray"]))}]]))
|
||||
|
@ -121,14 +121,18 @@
|
|||
clip-id (str "inner-stroke-" render-id)
|
||||
shape-id (str "stroke-shape-" render-id)
|
||||
|
||||
clip-path (str "url('#" clip-id "')")
|
||||
shape-props (-> base-props
|
||||
(add-props {:id shape-id
|
||||
:transform nil
|
||||
:clipPath (str "url('#" clip-id "')")})
|
||||
:transform nil})
|
||||
(add-style {:strokeWidth (* stroke-width 2)}))]
|
||||
|
||||
[:g.inner-stroke-shape {:transform transform}
|
||||
[:> elem-name shape-props]]))
|
||||
[:defs
|
||||
[:> elem-name shape-props]]
|
||||
|
||||
[:use {:xlinkHref (str "#" shape-id)
|
||||
:clipPath clip-path}]]))
|
||||
|
||||
|
||||
; The SVG standard does not implement yet the 'stroke-alignment'
|
||||
|
|
78
frontend/src/app/main/ui/shapes/export.cljs
Normal file
78
frontend/src/app/main/ui/shapes/export.cljs
Normal file
|
@ -0,0 +1,78 @@
|
|||
;; 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) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.shapes.export
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.util.json :as json]
|
||||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn add-data
|
||||
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
||||
[props shape]
|
||||
(let [add!
|
||||
(fn [props attr val]
|
||||
(let [ns-attr (str "penpot:" (-> attr d/name))]
|
||||
(-> props
|
||||
(obj/set! ns-attr val))))
|
||||
frame? (= :frame (:type shape))
|
||||
group? (= :group (:type shape))
|
||||
rect? (= :text (:type shape))
|
||||
text? (= :text (:type shape))
|
||||
mask? (and group? (:masked-group? shape))]
|
||||
(-> props
|
||||
(add! :name (-> shape :name))
|
||||
(add! :blocked (-> shape (:blocked false) str))
|
||||
(add! :hidden (-> shape (:hidden false) str))
|
||||
(add! :type (-> shape :type d/name))
|
||||
|
||||
(add! :stroke-style (-> shape (:stroke-style :none) d/name))
|
||||
(add! :stroke-alignment (-> shape (:stroke-alignment :center) d/name))
|
||||
|
||||
(add! :transform (-> shape (:transform (gmt/matrix)) str))
|
||||
(add! :transform-inverse (-> shape (:transform-inverse (gmt/matrix)) str))
|
||||
|
||||
(cond-> (and rect? (some? (:r1 shape)))
|
||||
(-> (add! :r1 (-> shape (:r1 0) str))
|
||||
(add! :r2 (-> shape (:r2 0) str))
|
||||
(add! :r3 (-> shape (:r3 0) str))
|
||||
(add! :r4 (-> shape (:r4 0) str))))
|
||||
|
||||
(cond-> text?
|
||||
(-> (add! :grow-type (-> shape :grow-type))
|
||||
(add! :content (-> shape :content json/encode))))
|
||||
|
||||
(cond-> mask?
|
||||
(add! :masked-group "true")))))
|
||||
|
||||
(mf/defc export-data
|
||||
[{:keys [shape]}]
|
||||
(let [props (-> (obj/new)
|
||||
(add-data shape))]
|
||||
[:> "penpot:shape" props
|
||||
(for [{:keys [style hidden color offset-x offset-y blur spread]} (:shadow shape)]
|
||||
[:> "penpot:shadow" #js {:penpot:shadow-type (d/name style)
|
||||
:penpot:hidden (str hidden)
|
||||
:penpot:color (str (:color color))
|
||||
:penpot:opacity (str (:opacity color))
|
||||
:penpot:offset-x (str offset-x)
|
||||
:penpot:offset-y (str offset-y)
|
||||
:penpot:blur (str blur)
|
||||
:penpot:spread (str spread)}])
|
||||
|
||||
(when (some? (:blur shape))
|
||||
(let [{:keys [type hidden value]} (:blur shape)]
|
||||
[:> "penpot:blur" #js {:penpot:blur-type (d/name type)
|
||||
:penpot:hidden (str hidden)
|
||||
:penpot:value (str value)}]))
|
||||
|
||||
(for [{:keys [scale suffix type]} (:exports shape)]
|
||||
[:> "penpot:export" #js {:penpot:type (d/name type)
|
||||
:penpot:suffix suffix
|
||||
:penpot:scale (str scale)}])]))
|
||||
|
|
@ -8,57 +8,15 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.shapes.custom-stroke :as cs]
|
||||
[app.main.ui.shapes.fill-image :as fim]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.gradients :as grad]
|
||||
[app.main.ui.shapes.export :as ed]
|
||||
[app.main.ui.shapes.svg-defs :as defs]
|
||||
[app.util.object :as obj]
|
||||
[rumext.alpha :as mf]
|
||||
[app.util.json :as json]))
|
||||
|
||||
(defn add-metadata
|
||||
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
||||
[props shape]
|
||||
(let [add!
|
||||
(fn [props attr val]
|
||||
(let [ns-attr (str "penpot:" (-> attr d/name))]
|
||||
(-> props
|
||||
(obj/set! ns-attr val))))
|
||||
frame? (= :frame (:type shape))
|
||||
group? (= :group (:type shape))
|
||||
rect? (= :text (:type shape))
|
||||
text? (= :text (:type shape))
|
||||
mask? (and group? (:masked-group? shape))]
|
||||
(-> props
|
||||
(add! :name (-> shape :name))
|
||||
(add! :blocked (-> shape (:blocked false) str))
|
||||
(add! :hidden (-> shape (:hidden false) str))
|
||||
(add! :type (-> shape :type d/name))
|
||||
|
||||
(add! :stroke-style (-> shape (:stroke-style :none) d/name))
|
||||
(add! :stroke-alignment (-> shape (:stroke-alignment :center) d/name))
|
||||
|
||||
(add! :transform (-> shape (:transform (gmt/matrix)) str))
|
||||
(add! :transform-inverse (-> shape (:transform-inverse (gmt/matrix)) str))
|
||||
|
||||
(cond-> (and rect? (some? (:r1 shape)))
|
||||
(-> (add! :r1 (-> shape (:r1 0) str))
|
||||
(add! :r2 (-> shape (:r2 0) str))
|
||||
(add! :r3 (-> shape (:r3 0) str))
|
||||
(add! :r4 (-> shape (:r4 0) str))))
|
||||
|
||||
(cond-> text?
|
||||
(-> (add! :grow-type (-> shape :grow-type))
|
||||
(add! :content (-> shape :content json/encode))))
|
||||
|
||||
(cond-> mask?
|
||||
(add! :masked-group "true"))
|
||||
|
||||
(cond-> frame?
|
||||
(obj/set! "xmlns:penpot" "https://penpot.app/xmlns")))))
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc shape-container
|
||||
{::mf/forward-ref true
|
||||
|
@ -92,14 +50,14 @@
|
|||
(obj/set! "width" width)
|
||||
(obj/set! "height" height)
|
||||
(obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink")
|
||||
(obj/set! "xmlns" "http://www.w3.org/2000/svg")))
|
||||
|
||||
(add-metadata shape))
|
||||
(obj/set! "xmlns" "http://www.w3.org/2000/svg")
|
||||
(obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))
|
||||
|
||||
wrapper-tag (if frame? "svg" "g")]
|
||||
|
||||
[:& (mf/provider muc/render-ctx) {:value render-id}
|
||||
[:> wrapper-tag wrapper-props
|
||||
[:& ed/export-data {:shape shape}]
|
||||
[:defs
|
||||
[:& defs/svg-defs {:shape shape :render-id render-id}]
|
||||
[:& filters/filters {:shape shape :filter-id filter-id}]
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.color :as uc]
|
||||
[app.util.json :as json]
|
||||
[app.util.path.parser :as upp]
|
||||
|
@ -28,24 +29,29 @@
|
|||
(and (vector? node)
|
||||
(= ::close (first node))))
|
||||
|
||||
(defn get-data [node]
|
||||
(->> node :content (d/seek #(= :penpot:shape (:tag %)))))
|
||||
|
||||
(defn get-type
|
||||
[node]
|
||||
(if (close? node)
|
||||
(second node)
|
||||
(-> (get-in node [:attrs :penpot:type])
|
||||
(keyword))))
|
||||
(let [data (get-data node)]
|
||||
(-> (get-in data [:attrs :penpot:type])
|
||||
(keyword)))))
|
||||
|
||||
(defn shape?
|
||||
[node]
|
||||
(or (close? node)
|
||||
(contains? (:attrs node) :penpot:type)))
|
||||
(some? (get-data node))))
|
||||
|
||||
(defn get-meta
|
||||
([m att]
|
||||
(get-meta m att identity))
|
||||
([m att val-fn]
|
||||
(let [ns-att (->> att d/name (str "penpot:") keyword)
|
||||
val (get-in m [:attrs ns-att])]
|
||||
val (or (get-in m [:attrs ns-att])
|
||||
(get-in (get-data m) [:attrs ns-att]))]
|
||||
(when val (val-fn val)))))
|
||||
|
||||
(defn get-children
|
||||
|
@ -87,7 +93,7 @@
|
|||
|
||||
(def search-data-node? #{:rect :image :path :text :circle})
|
||||
|
||||
(defn get-shape-data
|
||||
(defn get-svg-data
|
||||
[type node]
|
||||
|
||||
(if (search-data-node? type)
|
||||
|
@ -102,14 +108,14 @@
|
|||
(def has-position? #{:frame :rect :image :text})
|
||||
|
||||
(defn parse-position
|
||||
[props data]
|
||||
(let [values (->> (select-keys data [:x :y :width :height])
|
||||
[props svg-data]
|
||||
(let [values (->> (select-keys svg-data [:x :y :width :height])
|
||||
(d/mapm (fn [_ val] (d/parse-double val))))]
|
||||
(d/merge props values)))
|
||||
|
||||
(defn parse-circle
|
||||
[props data]
|
||||
(let [values (->> (select-keys data [:cx :cy :rx :ry])
|
||||
[props svg-data]
|
||||
(let [values (->> (select-keys svg-data [:cx :cy :rx :ry])
|
||||
(d/mapm (fn [_ val] (d/parse-double val))))]
|
||||
|
||||
{:x (- (:cx values) (:rx values))
|
||||
|
@ -118,8 +124,8 @@
|
|||
:height (* (:ry values) 2)}))
|
||||
|
||||
(defn parse-path
|
||||
[props data]
|
||||
(let [content (upp/parse-path (:d data))
|
||||
[props svg-data]
|
||||
(let [content (upp/parse-path (:d svg-data))
|
||||
selrect (gsh/content->selrect content)
|
||||
points (gsh/rect->points selrect)]
|
||||
|
||||
|
@ -130,10 +136,12 @@
|
|||
|
||||
(def url-regex #"url\(#([^\)]*)\)")
|
||||
|
||||
(defn seek-node [id coll]
|
||||
(defn seek-node
|
||||
[id coll]
|
||||
(->> coll (d/seek #(= id (-> % :attrs :id)))))
|
||||
|
||||
(defn parse-stops [gradient-node]
|
||||
(defn parse-stops
|
||||
[gradient-node]
|
||||
(->> gradient-node
|
||||
(node-seq)
|
||||
(filter #(= :stop (:tag %)))
|
||||
|
@ -166,23 +174,23 @@
|
|||
:width (get-meta gradient-node :width d/parse-double)))))
|
||||
|
||||
(defn add-position
|
||||
[props type node data]
|
||||
[props type node svg-data]
|
||||
(cond-> props
|
||||
(has-position? type)
|
||||
(-> (parse-position data)
|
||||
(-> (parse-position svg-data)
|
||||
(gsh/setup-selrect))
|
||||
|
||||
(= type :circle)
|
||||
(-> (parse-circle data)
|
||||
(-> (parse-circle svg-data)
|
||||
(gsh/setup-selrect))
|
||||
|
||||
(= type :path)
|
||||
(parse-path data)))
|
||||
(parse-path svg-data)))
|
||||
|
||||
(defn add-fill
|
||||
[props type node data]
|
||||
[props type node svg-data]
|
||||
|
||||
(let [fill (:fill data)]
|
||||
(let [fill (:fill svg-data)]
|
||||
(cond-> props
|
||||
(= fill "none")
|
||||
(assoc :fill-color nil
|
||||
|
@ -195,22 +203,22 @@
|
|||
|
||||
(uc/hex? fill)
|
||||
(assoc :fill-color fill
|
||||
:fill-opacity (-> data (:fill-opacity "1") d/parse-double)))))
|
||||
:fill-opacity (-> svg-data (:fill-opacity "1") d/parse-double)))))
|
||||
|
||||
(defn add-stroke
|
||||
[props type node data]
|
||||
[props type node svg-data]
|
||||
|
||||
(let [stroke-style (get-meta node :stroke-style keyword)
|
||||
stroke-alignment (get-meta node :stroke-alignment keyword)
|
||||
stroke (:stroke data)]
|
||||
stroke (:stroke svg-data)]
|
||||
|
||||
(cond-> props
|
||||
:always
|
||||
(assoc :stroke-alignment stroke-alignment
|
||||
:stroke-style stroke-style
|
||||
:stroke-color (-> data (:stroke "#000000"))
|
||||
:stroke-opacity (-> data (:stroke-opacity "1") d/parse-double)
|
||||
:stroke-width (-> data (:stroke-width "0") d/parse-double))
|
||||
:stroke-color (-> svg-data (:stroke "#000000"))
|
||||
:stroke-opacity (-> svg-data (:stroke-opacity "1") d/parse-double)
|
||||
:stroke-width (-> svg-data (:stroke-width "0") d/parse-double))
|
||||
|
||||
(str/starts-with? stroke "url")
|
||||
(assoc :stroke-color-gradient (parse-gradient node stroke)
|
||||
|
@ -221,7 +229,7 @@
|
|||
(update :stroke-width / 2))))
|
||||
|
||||
(defn add-image-data
|
||||
[props node data]
|
||||
[props node]
|
||||
(-> props
|
||||
(assoc-in [:metadata :id] (get-meta node :media-id))
|
||||
(assoc-in [:metadata :width] (get-meta node :media-width))
|
||||
|
@ -245,6 +253,65 @@
|
|||
mask?
|
||||
(assoc :masked-group? true))))
|
||||
|
||||
(defn parse-shadow [node]
|
||||
{:id (uuid/next)
|
||||
:style (get-meta node :shadow-type keyword)
|
||||
:hidden (get-meta node :hidden str->bool)
|
||||
:color {:color (get-meta node :color)
|
||||
:opacity (get-meta node :opacity d/parse-double)}
|
||||
:offset-x (get-meta node :offset-x d/parse-double)
|
||||
:offset-y (get-meta node :offset-y d/parse-double)
|
||||
:blur (get-meta node :blur d/parse-double)
|
||||
:spread (get-meta node :spread d/parse-double)})
|
||||
|
||||
(defn parse-blur [node]
|
||||
{:id (uuid/next)
|
||||
:type (get-meta node :blur-type keyword)
|
||||
:hidden (get-meta node :hidden str->bool)
|
||||
:value (get-meta node :value d/parse-double)})
|
||||
|
||||
(defn parse-export [node]
|
||||
{:type (get-meta node :type keyword)
|
||||
:suffix (get-meta node :suffix)
|
||||
:scale (get-meta node :scale d/parse-double)})
|
||||
|
||||
(defn extract-from-data [node tag parse-fn]
|
||||
(let [shape-data (get-data node)]
|
||||
(->> shape-data
|
||||
(node-seq)
|
||||
(filter #(= (:tag %) tag))
|
||||
(mapv parse-fn))))
|
||||
|
||||
(defn add-shadows
|
||||
[props node]
|
||||
(let [shadows (extract-from-data node :penpot:shadow parse-shadow)]
|
||||
(cond-> props
|
||||
(not (empty? shadows))
|
||||
(assoc :shadow shadows))))
|
||||
|
||||
(defn add-blur
|
||||
[props node]
|
||||
(let [blur (->> (extract-from-data node :penpot:blur parse-blur) (first))]
|
||||
(cond-> props
|
||||
(some? blur)
|
||||
(assoc :blur blur))))
|
||||
|
||||
(defn add-exports
|
||||
[props node]
|
||||
(let [exports (extract-from-data node :penpot:export parse-export)]
|
||||
(cond-> props
|
||||
(not (empty? exports))
|
||||
(assoc :exports exports))))
|
||||
|
||||
(defn get-image-name
|
||||
[node]
|
||||
(get-in node [:attrs :penpot:name]))
|
||||
|
||||
(defn get-image-data
|
||||
[node]
|
||||
(let [svg-data (get-svg-data :image node)]
|
||||
(:xlink:href svg-data)))
|
||||
|
||||
(defn parse-data
|
||||
[type node]
|
||||
|
||||
|
@ -254,12 +321,15 @@
|
|||
hidden (get-meta node :hidden str->bool)
|
||||
transform (get-meta node :transform gmt/str->matrix)
|
||||
transform-inverse (get-meta node :transform-inverse gmt/str->matrix)
|
||||
data (get-shape-data type node)]
|
||||
svg-data (get-svg-data type node)]
|
||||
|
||||
(-> {}
|
||||
(add-position type node data)
|
||||
(add-fill type node data)
|
||||
(add-stroke type node data)
|
||||
(add-position type node svg-data)
|
||||
(add-fill type node svg-data)
|
||||
(add-stroke type node svg-data)
|
||||
(add-shadows node)
|
||||
(add-blur node)
|
||||
(add-exports node)
|
||||
(assoc :name name)
|
||||
(assoc :blocked blocked)
|
||||
(assoc :hidden hidden)
|
||||
|
@ -268,7 +338,7 @@
|
|||
(add-group-data node))
|
||||
|
||||
(cond-> (= :image type)
|
||||
(add-image-data node data))
|
||||
(add-image-data node))
|
||||
|
||||
(cond-> (= :text type)
|
||||
(add-text-data node))
|
||||
|
@ -278,12 +348,3 @@
|
|||
|
||||
(cond-> (some? transform-inverse)
|
||||
(assoc :transform-inverse transform-inverse))))))
|
||||
|
||||
(defn get-image-name
|
||||
[node]
|
||||
(get-in node [:attrs :penpot:name]))
|
||||
|
||||
(defn get-image-data
|
||||
[node]
|
||||
(let [data (get-shape-data :image node)]
|
||||
(:xlink:href data)))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue