diff --git a/CHANGES.md b/CHANGES.md index 98ff1b78a..61e1cce47 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -65,6 +65,7 @@ - Fix paste ordering for frames not being respected [Taiga #3097](https://tree.taiga.io/project/penpot/issue/3097) - Improved command support for MacOS [Taiga #2789](https://tree.taiga.io/project/penpot/issue/2789) - Fix shift+2 shortcut in MacOS with non-english keyboards [Taiga #3038](https://tree.taiga.io/project/penpot/issue/3038) +- Some fixes to SVG imports [Taiga #3122](https://tree.taiga.io/project/penpot/issue/3122) [#1720](https://github.com/penpot/penpot/issues/1720) [Taiga #2884](https://tree.taiga.io/project/penpot/issue/2884) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index d60ffdc23..afc1bb6bb 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -105,6 +105,7 @@ (dm/export gco/transform-points) (dm/export gpr/make-rect) +(dm/export gpr/make-selrect) (dm/export gpr/rect->selrect) (dm/export gpr/rect->points) (dm/export gpr/points->selrect) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 6998bce8e..549148bb9 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -70,32 +70,30 @@ :else (str tag)))) (defn setup-fill [shape] - (if (some? (:fills shape)) - shape - (cond-> shape - ;; Color present as attribute - (uc/color? (str/trim (get-in shape [:svg-attrs :fill]))) - (-> (update :svg-attrs dissoc :fill) - (assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :fill]) - (str/trim) - (uc/parse-color)))) + (cond-> shape + ;; Color present as attribute + (uc/color? (str/trim (get-in shape [:svg-attrs :fill]))) + (-> (update :svg-attrs dissoc :fill) + (assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :fill]) + (str/trim) + (uc/parse-color)))) - ;; Color present as style - (uc/color? (str/trim (get-in shape [:svg-attrs :style :fill]))) - (-> (update-in [:svg-attrs :style] dissoc :fill) - (assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :style :fill]) - (str/trim) - (uc/parse-color)))) + ;; Color present as style + (uc/color? (str/trim (get-in shape [:svg-attrs :style :fill]))) + (-> (update-in [:svg-attrs :style] dissoc :fill) + (assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :style :fill]) + (str/trim) + (uc/parse-color)))) - (get-in shape [:svg-attrs :fill-opacity]) - (-> (update :svg-attrs dissoc :fill-opacity) - (assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :fill-opacity]) - (d/parse-double)))) + (get-in shape [:svg-attrs :fill-opacity]) + (-> (update :svg-attrs dissoc :fill-opacity) + (assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :fill-opacity]) + (d/parse-double)))) - (get-in shape [:svg-attrs :style :fill-opacity]) - (-> (update-in [:svg-attrs :style] dissoc :fill-opacity) - (assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :style :fill-opacity]) - (d/parse-double))))))) + (get-in shape [:svg-attrs :style :fill-opacity]) + (-> (update-in [:svg-attrs :style] dissoc :fill-opacity) + (assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :style :fill-opacity]) + (d/parse-double)))))) (defn setup-stroke [shape] (let [stroke-linecap (-> (or (get-in shape [:svg-attrs :stroke-linecap]) diff --git a/frontend/src/app/main/ui/shapes/svg_defs.cljs b/frontend/src/app/main/ui/shapes/svg_defs.cljs index ac04f57f7..0b875bcfb 100644 --- a/frontend/src/app/main/ui/shapes/svg_defs.cljs +++ b/frontend/src/app/main/ui/shapes/svg_defs.cljs @@ -6,9 +6,9 @@ (ns app.main.ui.shapes.svg-defs (:require - [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] + [app.main.ui.shapes.filters :as f] [app.util.object :as obj] [app.util.svg :as usvg] [rumext.alpha :as mf])) @@ -21,24 +21,7 @@ (str transform-matrix " " val) (str transform-matrix))))) -(defn transform-region [attrs transform] - (let [{x-str :x y-str :y width-str :width height-str :height} attrs - data (map d/parse-double [x-str y-str width-str height-str])] - (if (every? (comp not nil?) data) - (let [[x y width height] data - p1 (-> (gpt/point x y) - (gpt/transform transform)) - p2 (-> (gpt/point (+ x width) (+ y height)) - (gpt/transform transform))] - - (assoc attrs - :x (:x p1) - :y (:y p1) - :width (- (:x p2) (:x p1)) - :height (- (:y p2) (:y p1)))) - attrs))) - -(mf/defc svg-node [{:keys [node prefix-id transform]}] +(mf/defc svg-node [{:keys [type node prefix-id transform bounds]}] (cond (string? node) node @@ -63,23 +46,34 @@ attrs (-> attrs (usvg/update-attr-ids prefix-id) (usvg/clean-attrs) - + ;; This clasname will be used to change the transform on the viewport + ;; only necessary for groups because shapes have their own transform + (cond-> (and (or transform-gradient? + transform-pattern? + transform-clippath? + transform-filter? + transform-mask?) + (= :group type)) + (update :className #(if % (dm/str % " svg-def") "svg-def"))) (cond-> transform-gradient? (add-matrix :gradientTransform transform) transform-pattern? (add-matrix :patternTransform transform) transform-clippath? (add-matrix :transform transform) (or transform-filter? - transform-mask?) (transform-region transform))) + transform-mask?) (merge attrs bounds))) [wrapper wrapper-props] (if (= tag :mask) - ["g" #js {:transform (str transform)}] + ["g" #js {:className "svg-mask-wrapper" + :transform (str transform)}] [mf/Fragment (obj/new)])] [:> (name tag) (clj->js attrs) [:> wrapper wrapper-props - (for [node content] [:& svg-node {:node node + (for [node content] [:& svg-node {:type type + :node node :prefix-id prefix-id - :transform transform}])]]))) + :transform transform + :bounds bounds}])]]))) (mf/defc svg-defs [{:keys [shape render-id]}] (let [svg-defs (:svg-defs shape) @@ -91,8 +85,8 @@ (usvg/svg-transform-matrix shape))) ;; Paths doesn't have transform so we have to transform its gradients - transform (if (contains? shape :svg-transform) - (gmt/multiply transform (or (:svg-transform shape) (gmt/matrix))) + transform (if (some? (:svg-transform shape)) + (gmt/multiply transform (:svg-transform shape)) transform) prefix-id @@ -103,7 +97,9 @@ ;; TODO: no key? (when (seq svg-defs) (for [svg-def (vals svg-defs)] - [:& svg-node {:node svg-def + [:& svg-node {:type (:type shape) + :node svg-def :prefix-id prefix-id - :transform transform}])))) + :transform transform + :bounds (f/get-filters-bounds shape)}])))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs index 6cac84489..4156c2c56 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs @@ -117,8 +117,8 @@ ;; Note that the "indeterminate" attribute only may be set by code, not as a static attribute. ;; See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#attr-indeterminate (if (= hide-fill-on-export? :multiple) - (dom/set-attribute checkbox "indeterminate" true) - (dom/remove-attribute checkbox "indeterminate"))))) + (dom/set-attribute! checkbox "indeterminate" true) + (dom/remove-attribute! checkbox "indeterminate"))))) [:div.element-set [:div.element-set-title diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 897d31d50..11e4bc006 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -201,25 +201,43 @@ (mf/use-memo (mf/deps modifiers) (fn [] - (d/mapm (fn [id {modifiers :modifiers}] - (let [center (gsh/center-shape (get objects id))] - (gsh/modifiers->transform center modifiers))) - modifiers))) + (when (some? modifiers) + (d/mapm (fn [id {modifiers :modifiers}] + (let [center (gsh/center-shape (get objects id))] + (gsh/modifiers->transform center modifiers))) + modifiers)))) shapes (mf/use-memo (mf/deps transforms) (fn [] (->> (keys transforms) - (mapv (d/getf objects)))))] + (mapv (d/getf objects))))) + + prev-shapes (mf/use-var nil) + prev-modifiers (mf/use-var nil) + prev-transforms (mf/use-var nil)] ;; Layout effect is important so the code is executed before the modifiers ;; are applied to the shape (mf/use-layout-effect (mf/deps transforms) (fn [] - (utils/update-transform shapes transforms modifiers) - #(utils/remove-transform shapes))))) + (when (and (nil? @prev-transforms) + (some? transforms)) + (utils/start-transform! shapes)) + + (when (some? modifiers) + (utils/update-transform! shapes transforms modifiers)) + + + (when (and (some? @prev-modifiers) + (not (some? modifiers))) + (utils/remove-transform! @prev-shapes)) + + (reset! prev-modifiers modifiers) + (reset! prev-transforms transforms) + (reset! prev-shapes shapes))))) (defn inside-vbox [vbox objects frame-id] (let [frame (get objects frame-id)] diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 416077562..f4151f11f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] [app.main.ui.cursors :as cur] [app.util.dom :as dom])) @@ -100,7 +101,10 @@ (dom/query shape-node ".mask-shape")] group? - [] + (let [shape-defs (dom/query shape-node "defs")] + (d/concat-vec + (dom/query-all shape-defs ".svg-def") + (dom/query-all shape-defs ".svg-mask-wrapper"))) text? [shape-node @@ -112,7 +116,59 @@ :else [shape-node]))) -(defn update-transform [shapes transforms modifiers] +(defn transform-region! + [node modifiers] + + (let [{:keys [x y width height]} + (-> (gsh/make-selrect + (-> (dom/get-attribute node "data-old-x") d/parse-double) + (-> (dom/get-attribute node "data-old-y") d/parse-double) + (-> (dom/get-attribute node "data-old-width") d/parse-double) + (-> (dom/get-attribute node "data-old-height") d/parse-double)) + (gsh/transform-selrect modifiers))] + (dom/set-attribute! node "x" x) + (dom/set-attribute! node "y" y) + (dom/set-attribute! node "width" width) + (dom/set-attribute! node "height" height))) + +(defn start-transform! + [shapes] + (doseq [shape shapes] + (when-let [nodes (get-nodes shape)] + (doseq [node nodes] + (let [old-transform (dom/get-attribute node "transform")] + (when (some? old-transform) + (dom/set-attribute! node "data-old-transform" old-transform)) + + (when (or (= (dom/get-tag-name node) "linearGradient") + (= (dom/get-tag-name node) "radialGradient")) + (let [gradient-transform (dom/get-attribute node "gradientTransform")] + (when (some? gradient-transform) + (dom/set-attribute! node "data-old-gradientTransform" gradient-transform)))) + + (when (= (dom/get-tag-name node) "pattern") + (let [pattern-transform (dom/get-attribute node "patternTransform")] + (when (some? pattern-transform) + (dom/set-attribute! node "data-old-patternTransform" pattern-transform)))) + + (when (or (= (dom/get-tag-name node) "mask") + (= (dom/get-tag-name node) "filter")) + (dom/set-attribute! node "data-old-x" (dom/get-attribute node "x")) + (dom/set-attribute! node "data-old-y" (dom/get-attribute node "y")) + (dom/set-attribute! node "data-old-width" (dom/get-attribute node "width")) + (dom/set-attribute! node "data-old-height" (dom/get-attribute node "height")))))))) + +(defn set-transform-att! + [node att value] + + (let [old-att (dom/get-attribute node (dm/str "data-old-" att)) + new-value (if (some? old-att) + (dm/str value " " old-att) + (str value))] + (dom/set-attribute! node att (str new-value)))) + +(defn update-transform! + [shapes transforms modifiers] (doseq [{:keys [id type] :as shape} shapes] (when-let [nodes (get-nodes shape)] (let [transform (get transforms id) @@ -127,24 +183,38 @@ (doseq [node nodes] (cond - (or (dom/class? node "text-shape") (dom/class? node "text-svg")) + ;; Text shapes need special treatment because their resize only change + ;; the text area, not the change size/position + (or (dom/class? node "text-shape") + (dom/class? node "text-svg")) (when (some? text-transform) - (dom/set-attribute node "transform" (str text-transform))) + (set-transform-att! node "transform" text-transform)) (or (= (dom/get-tag-name node) "foreignObject") (dom/class? node "text-clip")) (let [cur-width (dom/get-attribute node "width") cur-height (dom/get-attribute node "height")] (when (and (some? text-width) (not= cur-width text-width)) - (dom/set-attribute node "width" text-width)) - + (dom/set-attribute! node "width" text-width)) (when (and (some? text-height) (not= cur-height text-height)) - (dom/set-attribute node "height" text-height))) + (dom/set-attribute! node "height" text-height))) + + (or (= (dom/get-tag-name node) "mask") + (= (dom/get-tag-name node) "filter")) + (transform-region! node modifiers) + + (or (= (dom/get-tag-name node) "linearGradient") + (= (dom/get-tag-name node) "radialGradient")) + (set-transform-att! node "gradientTransform" transform) + + (= (dom/get-tag-name node) "pattern") + (set-transform-att! node "patternTransform" transform) (and (some? transform) (some? node)) - (dom/set-attribute node "transform" (str transform)))))))) + (set-transform-att! node "transform" transform))))))) -(defn remove-transform [shapes] +(defn remove-transform! + [shapes] (doseq [shape shapes] (when-let [nodes (get-nodes shape)] (doseq [node nodes] @@ -155,7 +225,10 @@ nil :else - (dom/remove-attribute node "transform"))))))) + (let [old-transform (dom/get-attribute node "data-old-transform")] + (when-not (some? old-transform) + (dom/remove-attribute! node "data-old-transform") + (dom/remove-attribute! node "transform"))))))))) (defn format-viewbox [vbox] (dm/str (:x vbox 0) " " diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 499881ecf..0aba75592 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -415,11 +415,11 @@ "application/pdf" "pdf" nil)) -(defn set-attribute [^js node ^string attr value] +(defn set-attribute! [^js node ^string attr value] (when (some? node) (.setAttribute node attr value))) -(defn remove-attribute [^js node ^string attr] +(defn remove-attribute! [^js node ^string attr] (when (some? node) (.removeAttribute node attr))) diff --git a/frontend/src/app/util/svg.cljs b/frontend/src/app/util/svg.cljs index f7a7296a3..229557257 100644 --- a/frontend/src/app/util/svg.cljs +++ b/frontend/src/app/util/svg.cljs @@ -715,7 +715,8 @@ (gmt/matrix) ;; Paths doesn't have transform so we have to transform its gradients - (if (= :path (:type shape)) + (if (or (= :path (:type shape)) + (= :group (:type shape))) (gsh/transform-matrix shape) (gmt/matrix))