Import shadows,blur,exports

This commit is contained in:
alonso.torres 2021-06-04 15:04:09 +02:00
parent cc2c249a07
commit 4af83eadc4
4 changed files with 201 additions and 100 deletions

View file

@ -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'

View 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)}])]))

View file

@ -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}]

View file

@ -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)))