diff --git a/CHANGES.md b/CHANGES.md index 40da84e06..d82692940 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -63,6 +63,7 @@ - Fix issue when promoting to owner [Taiga #1494](https://tree.taiga.io/project/penpot/issue/1494) - Fix cannot click on blocked elements in viewer [Taiga #1430](https://tree.taiga.io/project/penpot/issue/1430) - Fix SVG not showing properties at code [Taiga #1437](https://tree.taiga.io/project/penpot/issue/1437) +- Fix shadows when exporting groups [Taiga #1495](https://tree.taiga.io/project/penpot/issue/1495) ### :arrow_up: Deps updates diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index e02da77e2..79c8d83a4 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -9,18 +9,21 @@ (ns app.main.ui.render (:require - [cljs.spec.alpha :as s] - [beicon.core :as rx] - [rumext.alpha :as mf] - [app.common.uuid :as uuid] - [app.common.pages :as cp] - [app.common.math :as mth] - [app.common.geom.shapes :as geom] - [app.common.geom.point :as gpt] [app.common.geom.matrix :as gmt] - [app.main.ui.context :as muc] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.common.pages :as cp] + [app.common.uuid :as uuid] [app.main.exports :as exports] - [app.main.repo :as repo])) + [app.main.repo :as repo] + [app.main.ui.context :as muc] + [app.main.ui.shapes.filters :as filters] + [app.main.ui.shapes.shape :refer [shape-container]] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (mf/defc object-svg {::mf/wrap [mf/memo]} @@ -37,18 +40,23 @@ mod-ids (cons frame-id (cp/get-children frame-id objects)) updt-fn #(-> %1 (assoc-in [%2 :modifiers :displacement] modifier) - (update %2 geom/transform-shape)) + (update %2 gsh/transform-shape)) objects (reduce updt-fn objects mod-ids) object (get objects object-id) - width (* (get-in object [:selrect :width]) zoom) - height (* (get-in object [:selrect :height]) zoom) - vbox (str (get-in object [:selrect :x]) " " - (get-in object [:selrect :y]) " " - (get-in object [:selrect :width]) " " - (get-in object [:selrect :height])) + {:keys [width height]} (gsh/points->selrect (:points object)) + + ;; We need to get the shadows/blurs paddings to create the viewbox properly + {:keys [x y width height]} (filters/get-filters-bounds object) + + x (* x zoom) + y (* y zoom) + width (* width zoom) + height (* height zoom) + + vbox (str/join " " [x y width height]) frame-wrapper (mf/use-memo @@ -76,7 +84,8 @@ :xmlns "http://www.w3.org/2000/svg"} (case (:type object) :frame [:& frame-wrapper {:shape object :view-box vbox}] - :group [:& group-wrapper {:shape object}] + :group [:> shape-container {:shape object} + [:& group-wrapper {:shape object}]] [:& shape-wrapper {:shape object}])]])) (defn- adapt-root-frame @@ -84,9 +93,9 @@ (if (uuid/zero? object-id) (let [object (get objects object-id) shapes (cp/select-toplevel-shapes objects {:include-frames? true}) - srect (geom/selection-rect shapes) + srect (gsh/selection-rect shapes) object (merge object (select-keys srect [:x :y :width :height])) - object (geom/transform-shape object) + object (gsh/transform-shape object) object (assoc object :fill-color "#f0f0f0")] (assoc objects (:id object) object)) objects)) diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs index 463ece89d..d7b7ed364 100644 --- a/frontend/src/app/main/ui/shapes/filters.cljs +++ b/frontend/src/app/main/ui/shapes/filters.cljs @@ -122,40 +122,6 @@ :x2 (+ filter-x filter-width) :y2 (+ filter-y filter-height)})) -(defn get-filters-bounds - [shape filters blur-value] - - (let [svg-root? (and (= :svg-raw (:type shape)) (not= :svg (get-in shape [:content :tag]))) - frame? (= :frame (:type shape)) - {:keys [x y width height]} (:selrect shape)] - (if svg-root? - ;; When is a raw-svg but not the root we use the whole svg as bound for the filter. Is the maximum - ;; we're allowed to display - {:x 0 :y 0 :width width :height height} - - ;; Otherwise we calculate the bound - (let [filter-bounds (->> filters - (filter #(= :drop-shadow (:type %))) - (map (partial filter-bounds shape) )) - ;; We add the selrect so the minimum size will be the selrect - filter-bounds (conj filter-bounds (-> shape :points gsh/points->selrect)) - x1 (apply min (map :x1 filter-bounds)) - y1 (apply min (map :y1 filter-bounds)) - x2 (apply max (map :x2 filter-bounds)) - y2 (apply max (map :y2 filter-bounds)) - - x1 (- x1 (* blur-value 2)) - x2 (+ x2 (* blur-value 2)) - y1 (- y1 (* blur-value 2)) - y2 (+ y2 (* blur-value 2))] - - ;; We should move the frame filter coordinates because they should be - ;; relative with the frame. By default they come as absolute - {:x (if frame? (- x1 x) x1) - :y (if frame? (- y1 y) y1) - :width (- x2 x1) - :height (- y2 y1)})))) - (defn blur-filters [type value] (->> [value] (remove :hidden) @@ -184,21 +150,64 @@ :image-fix [:> image-fix-filter props] :blend-filters [:> blend-filters props]))) +(defn shape->filters + [shape] + (d/concat + [] + [{:id "BackgroundImageFix" :type :image-fix}] + + ;; Background blur won't work in current SVG specification + ;; We can revisit this in the future + #_(->> shape :blur (blur-filters :background-blur)) + + (->> shape :shadow (shadow-filters :drop-shadow)) + [{:id "shape" :type :blend-filters}] + (->> shape :shadow (shadow-filters :inner-shadow)) + (->> shape :blur (blur-filters :layer-blur)))) + +(defn get-filters-bounds + ([shape] + (let [filters (shape->filters shape) + blur-value (or (-> shape :blur :value) 0)] + (get-filters-bounds shape filters blur-value))) + + ([shape filters blur-value] + + (let [svg-root? (and (= :svg-raw (:type shape)) (not= :svg (get-in shape [:content :tag]))) + frame? (= :frame (:type shape)) + {:keys [x y width height]} (:selrect shape)] + (if svg-root? + ;; When is a raw-svg but not the root we use the whole svg as bound for the filter. Is the maximum + ;; we're allowed to display + {:x 0 :y 0 :width width :height height} + + ;; Otherwise we calculate the bound + (let [filter-bounds (->> filters + (filter #(= :drop-shadow (:type %))) + (map (partial filter-bounds shape))) + ;; We add the selrect so the minimum size will be the selrect + filter-bounds (conj filter-bounds (-> shape :points gsh/points->selrect)) + x1 (apply min (map :x1 filter-bounds)) + y1 (apply min (map :y1 filter-bounds)) + x2 (apply max (map :x2 filter-bounds)) + y2 (apply max (map :y2 filter-bounds)) + + x1 (- x1 (* blur-value 2)) + x2 (+ x2 (* blur-value 2)) + y1 (- y1 (* blur-value 2)) + y2 (+ y2 (* blur-value 2))] + + ;; We should move the frame filter coordinates because they should be + ;; relative with the frame. By default they come as absolute + {:x (if frame? (- x1 x) x1) + :y (if frame? (- y1 y) y1) + :width (- x2 x1) + :height (- y2 y1)}))))) + (mf/defc filters [{:keys [filter-id shape]}] - (let [filters (d/concat - [] - [{:id "BackgroundImageFix" :type :image-fix}] - - ;; Background blur won't work in current SVG specification - ;; We can revisit this in the future - #_(->> shape :blur (blur-filters :background-blur)) - - (->> shape :shadow (shadow-filters :drop-shadow)) - [{:id "shape" :type :blend-filters}] - (->> shape :shadow (shadow-filters :inner-shadow)) - (->> shape :blur (blur-filters :layer-blur))) + (let [filters (shape->filters shape) ;; Adds the previous filter as `filter-in` parameter filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))