From 51ea354bcb1b30171be42d84a4c270321deb185e Mon Sep 17 00:00:00 2001 From: "alonso.torres" <alonso.torres@kaleidos.net> Date: Tue, 4 Jan 2022 14:19:21 +0100 Subject: [PATCH] :bug: Fix problem when resizing texts inside groups --- CHANGES.md | 1 + .../app/common/geom/shapes/transforms.cljc | 6 +- .../src/app/main/ui/shapes/text/styles.cljs | 7 +- .../app/main/ui/workspace/viewport/utils.cljs | 98 ++++-- frontend/src/app/util/dom.cljs | 288 +++++++++++------- 5 files changed, 250 insertions(+), 150 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dcba356ab..9c621028c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -35,6 +35,7 @@ - Fix problem with booleans [Taiga #2356](https://tree.taiga.io/project/penpot/issue/2356) - Fix line-height/letter-spacing inputs behaviour [Taiga #2331](https://tree.taiga.io/project/penpot/issue/2331) - Fix dotted style in strokes [Taiga #2312](https://tree.taiga.io/project/penpot/issue/2312) +- Fix problem when resizing texts inside groups [Taiga #2310](https://tree.taiga.io/project/penpot/issue/2310) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 44cc73b06..7ff2aec82 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -451,9 +451,6 @@ rt-modif (:rotation modifiers)] (cond-> (gmt/matrix) - (some? displacement) - (gmt/multiply displacement) - (some? resize-1) (-> (gmt/translate origin-1) (gmt/multiply resize-transform) @@ -468,6 +465,9 @@ (gmt/multiply resize-transform-inverse) (gmt/translate (gpt/negate origin-2))) + (some? displacement) + (gmt/multiply displacement) + (some? rt-modif) (-> (gmt/translate center) (gmt/multiply (gmt/rotate-matrix rt-modif)) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 263424b68..fe7efceef 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -14,11 +14,10 @@ [cuerdas.core :as str])) (defn generate-root-styles - [shape node] + [_shape node] (let [valign (:vertical-align node "top") - width (some-> (:width shape) (+ 1)) - base #js {:height (or (:height shape) "100%") - :width (or width "100%") + base #js {:height "100%" + :width "100%" :fontFamily "sourcesanspro"}] (cond-> base (= valign "top") (obj/set! "justifyContent" "flex-start") diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 67621fa36..fe92fa46f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -16,30 +16,54 @@ (defn- text-corrected-transform "If we apply a scale directly to the texts it will show deformed so we need to create this correction matrix to \"undo\" the resize but keep the other transformations." - [{:keys [points transform transform-inverse]} current-transform modifiers] + [{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers] (let [corner-pt (first points) - transform (or transform (gmt/matrix)) - transform-inverse (or transform-inverse (gmt/matrix)) + corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse)) - current-transform - (if (some? (:resize-vector modifiers)) - (gmt/multiply - current-transform - transform - (gmt/scale-matrix (gpt/inverse (:resize-vector modifiers)) (gpt/transform corner-pt transform-inverse)) - transform-inverse) - current-transform) + resize-x? (some? (:resize-vector modifiers)) + resize-y? (some? (:resize-vector-2 modifiers)) - current-transform - (if (some? (:resize-vector-2 modifiers)) - (gmt/multiply - current-transform - transform - (gmt/scale-matrix (gpt/inverse (:resize-vector-2 modifiers)) (gpt/transform corner-pt transform-inverse)) - transform-inverse) - current-transform)] - current-transform)) + flip-x? (neg? (get-in modifiers [:resize-vector :x])) + flip-y? (or (neg? (get-in modifiers [:resize-vector :y])) + (neg? (get-in modifiers [:resize-vector-2 :y]))) + + result (cond-> (gmt/matrix) + (and (some? transform) (or resize-x? resize-y?)) + (gmt/multiply transform) + + resize-x? + (gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt) + + resize-y? + (gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt) + + flip-x? + (gmt/scale (gpt/point -1 1) corner-pt) + + flip-y? + (gmt/scale (gpt/point 1 -1) corner-pt) + + (and (some? transform) (or resize-x? resize-y?)) + (gmt/multiply transform-inverse)) + + [width height] + (if (or resize-x? resize-y?) + (let [pc (-> (gpt/point x y) + (gpt/transform transform) + (gpt/transform current-transform)) + + pw (-> (gpt/point (+ x width) y) + (gpt/transform transform) + (gpt/transform current-transform)) + + ph (-> (gpt/point x (+ y height)) + (gpt/transform transform) + (gpt/transform current-transform))] + [(gpt/distance pc pw) (gpt/distance pc ph)]) + [width height])] + + [result width height])) (defn get-nodes "Retrieve the DOM nodes to apply the matrix transformation" @@ -48,6 +72,7 @@ frame? (= :frame type) group? (= :group type) + text? (= :text type) mask? (and group? masked-group?) ;; When the shape is a frame we maybe need to move its thumbnail @@ -68,6 +93,11 @@ group? [] + text? + [shape-node + (dom/query shape-node "foreignObject") + (dom/query shape-node ".text-shape")] + :else [shape-node]))) @@ -76,11 +106,23 @@ (when-let [nodes (get-nodes shape)] (let [transform (get transforms id) modifiers (get-in modifiers [id :modifiers]) - transform (case type - :text (text-corrected-transform shape transform modifiers) - transform)] + + [text-transform text-width text-height] + (when (= :text type) + (text-corrected-transform shape transform modifiers))] + (doseq [node nodes] - (when (and (some? transform) (some? node)) + (cond + (dom/class? node "text-shape") + (when (some? text-transform) + (dom/set-attribute node "transform" (str text-transform))) + + (= (dom/get-tag-name node) "foreignObject") + (when (and (some? text-width) (some? text-height)) + (dom/set-attribute node "width" text-width) + (dom/set-attribute node "height" text-height)) + + (and (some? transform) (some? node)) (dom/set-attribute node "transform" (str transform)))))))) (defn remove-transform [shapes] @@ -88,7 +130,13 @@ (when-let [nodes (get-nodes shape)] (doseq [node nodes] (when (some? node) - (dom/remove-attribute node "transform")))))) + (cond + (= (dom/get-tag-name node) "foreignObject") + ;; The shape width/height will be automaticaly setup when the modifiers are applied + nil + + :else + (dom/remove-attribute node "transform"))))))) (defn format-viewbox [vbox] (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 25a59a199..682a1963b 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -17,21 +17,24 @@ ;; --- Deprecated methods (defn event->inner-text - [e] - (.-innerText (.-target e))) + [^js e] + (when (some? e) + (.-innerText (.-target e)))) (defn event->value - [e] - (.-value (.-target e))) + [^js e] + (when (some? e) + (.-value (.-target e)))) (defn event->target - [e] - (.-target e)) + [^js e] + (when (some? e) + (.-target e))) ;; --- New methods (defn set-html-title - [title] + [^string title] (set! (.-title globals/document) title)) (defn set-page-style @@ -61,98 +64,117 @@ (dom/getElement id)) (defn get-elements-by-tag - [node tag] - (.getElementsByTagName node tag)) + [^js node tag] + (when (some? node) + (.getElementsByTagName node tag))) (defn stop-propagation - [e] - (when e - (.stopPropagation e))) + [^js event] + (when event + (.stopPropagation event))) (defn prevent-default - [e] - (when e - (.preventDefault e))) + [^js event] + (when event + (.preventDefault event))) (defn get-target "Extract the target from event instance." - [event] - (.-target event)) + [^js event] + (when (some? event) + (.-target event))) (defn get-current-target "Extract the current target from event instance (different from target when event triggered in a child of the subscribing element)." - [event] - (.-currentTarget event)) + [^js event] + (when (some? event) + (.-currentTarget event))) (defn get-parent - [dom] - (.-parentElement ^js dom)) + [^js node] + (when (some? node) + (.-parentElement ^js node))) (defn get-value "Extract the value from dom node." - [node] - (.-value node)) + [^js node] + (when (some? node) + (.-value node))) (defn get-attribute "Extract the value of one attribute of a dom node." - [node attr-name] - (.getAttribute ^js node attr-name)) + [^js node ^string attr-name] + (when (some? node) + (.getAttribute ^js node attr-name))) (def get-target-val (comp get-value get-target)) (defn click "Click a node" - [node] - (.click node)) + [^js node] + (when (some? node) + (.click node))) (defn get-files "Extract the files from dom node." - [node] - (array-seq (.-files node))) + [^js node] + (when (some? node) + (array-seq (.-files node)))) (defn checked? "Check if the node that represents a radio or checkbox is checked or not." - [node] - (.-checked node)) + [^js node] + (when (some? node) + (.-checked node))) (defn valid? "Check if the node that is a form input has a valid value, against html5 form validation properties (required, min/max, pattern...)." - [node] - (.-valid (.-validity node))) + [^js node] + (when (some? node) + (when-let [validity (.-validity node)] + (.-valid validity)))) (defn set-validity! "Manually set the validity status of a node that is a form input. If the state is an empty string, the input will be valid. If not, the string will be set as the error message." - [node status] - (.setCustomValidity node status) - (.reportValidity node)) + [^js node status] + (when (some? node) + (.setCustomValidity node status) + (.reportValidity node))) (defn clean-value! - [node] - (set! (.-value node) "")) + [^js node] + (when (some? node) + (set! (.-value node) ""))) (defn set-value! - [node value] - (set! (.-value ^js node) value)) + [^js node value] + (when (some? node) + (set! (.-value ^js node) value))) (defn select-text! - [node] - (.select ^js node)) + [^js node] + (when (some? node) + (.select ^js node))) (defn ^boolean equals? - [node-a node-b] - (.isEqualNode ^js node-a node-b)) + [^js node-a ^js node-b] + + (or (and (nil? node-a) (nil? node-b)) + (and (some? node-a) + (.isEqualNode ^js node-a node-b)))) (defn get-event-files "Extract the files from event instance." - [event] - (get-files (get-target event))) + [^js event] + (when (some? event) + (get-files (get-target event)))) (defn create-element ([tag] @@ -161,50 +183,58 @@ (.createElementNS globals/document ns tag))) (defn set-html! - [el html] - (set! (.-innerHTML el) html)) + [^js el html] + (when (some? el) + (set! (.-innerHTML el) html))) (defn append-child! - [el child] - (.appendChild ^js el child)) + [^js el child] + (when (some? el) + (.appendChild ^js el child))) (defn get-first-child - [el] - (.-firstChild el)) + [^js el] + (when (some? el) + (.-firstChild el))) (defn get-tag-name - [el] - (.-tagName el)) + [^js el] + (when (some? el) + (.-tagName el))) (defn get-outer-html - [el] - (.-outerHTML el)) + [^js el] + (when (some? el) + (.-outerHTML el))) (defn get-inner-text - [el] - (.-innerText el)) + [^js el] + (when (some? el) + (.-innerText el))) (defn query - [el query] + [^js el ^string query] (when (some? el) (.querySelector el query))) (defn get-client-position - [event] + [^js event] (let [x (.-clientX event) y (.-clientY event)] (gpt/point x y))) (defn get-offset-position - [event] - (let [x (.-offsetX event) - y (.-offsetY event)] - (gpt/point x y))) + [^js event] + (when (some? event) + (let [x (.-offsetX event) + y (.-offsetY event)] + (gpt/point x y)))) (defn get-client-size - [node] - {:width (.-clientWidth ^js node) - :height (.-clientHeight ^js node)}) + [^js node] + (when (some? node) + {:width (.-clientWidth ^js node) + :height (.-clientHeight ^js node)})) (defn get-bounding-rect [node] @@ -222,12 +252,12 @@ :height (.-innerHeight ^js js/window)}) (defn focus! - [node] + [^js node] (when (some? node) (.focus node))) (defn blur! - [node] + [^js node] (when (some? node) (.blur node))) @@ -245,8 +275,9 @@ :hint "seems like the current browser does not support fullscreen api."))) (defn ^boolean blob? - [v] - (instance? js/Blob v)) + [^js v] + (when (some? v) + (instance? js/Blob v))) (defn create-blob "Create a blob from content." @@ -265,20 +296,24 @@ {:pre [(blob? b)]} (js/URL.createObjectURL b)) -(defn set-property! [node property value] - (.setAttribute node property value)) +(defn set-property! [^js node property value] + (when (some? node) + (.setAttribute node property value))) -(defn set-text! [node text] - (set! (.-textContent node) text)) +(defn set-text! [^js node text] + (when (some? node) + (set! (.-textContent node) text))) -(defn set-css-property! [node property value] - (.setProperty (.-style ^js node) property value)) +(defn set-css-property! [^js node property value] + (when (some? node) + (.setProperty (.-style ^js node) property value))) -(defn capture-pointer [event] - (-> event get-target (.setPointerCapture (.-pointerId event)))) +(defn capture-pointer [^js event] + (when (some? event) + (-> event get-target (.setPointerCapture (.-pointerId event))))) -(defn release-pointer [event] - (when (.-pointerId event) +(defn release-pointer [^js event] + (when (and (some? event) (.-pointerId event)) (-> event get-target (.releasePointerCapture (.-pointerId event))))) (defn get-root [] @@ -295,19 +330,23 @@ (partition 2 params)))) (defn ^boolean class? [node class-name] - (let [class-list (.-classList ^js node)] - (.contains ^js class-list class-name))) + (when (some? node) + (let [class-list (.-classList ^js node)] + (.contains ^js class-list class-name)))) -(defn add-class! [node class-name] - (let [class-list (.-classList ^js node)] - (.add ^js class-list class-name))) +(defn add-class! [^js node class-name] + (when (some? node) + (let [class-list (.-classList ^js node)] + (.add ^js class-list class-name)))) -(defn remove-class! [node class-name] - (let [class-list (.-classList ^js node)] - (.remove ^js class-list class-name))) +(defn remove-class! [^js node class-name] + (when (some? node) + (let [class-list (.-classList ^js node)] + (.remove ^js class-list class-name)))) -(defn child? [node1 node2] - (.contains ^js node2 ^js node1)) +(defn child? [^js node1 ^js node2] + (when (some? node1) + (.contains ^js node2 ^js node1))) (defn get-user-agent [] (.-userAgent globals/navigator)) @@ -315,11 +354,13 @@ (defn get-active [] (.-activeElement globals/document)) -(defn active? [node] - (= (get-active) node)) +(defn active? [^js node] + (when (some? node) + (= (get-active) node))) (defn get-data [^js node ^string attr] - (.getAttribute node (str "data-" attr))) + (when (some? node) + (.getAttribute node (str "data-" attr)))) (defn mtype->extension [mtype] ;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types @@ -336,42 +377,53 @@ nil)) (defn set-attribute [^js node ^string attr value] - (.setAttribute node attr value)) + (when (some? node) + (.setAttribute node attr value))) (defn remove-attribute [^js node ^string attr] - (.removeAttribute node attr)) + (when (some? node) + (.removeAttribute node attr))) (defn get-scroll-pos - [element] - (.-scrollTop ^js element)) + [^js element] + (when (some? element) + (.-scrollTop element))) (defn set-scroll-pos! - [element scroll] - (obj/set! ^js element "scrollTop" scroll)) + [^js element scroll] + (when (some? element) + (obj/set! element "scrollTop" scroll))) (defn scroll-into-view! - ([element] - (.scrollIntoView ^js element false)) - ([element scroll-top] - (.scrollIntoView ^js element scroll-top))) + ([^js element] + (when (some? element) + (.scrollIntoView element false))) + + ([^js element scroll-top] + (when (some? element) + (.scrollIntoView element scroll-top)))) (defn scroll-into-view-if-needed! - ([element] - (.scrollIntoViewIfNeeded ^js element false)) - ([element scroll-top] - (.scrollIntoViewIfNeeded ^js element scroll-top))) + ([^js element] + (when (some? element) + (.scrollIntoViewIfNeeded ^js element false))) + + ([^js element scroll-top] + (when (some? element) + (.scrollIntoViewIfNeeded ^js element scroll-top)))) (defn is-in-viewport? - [element] - (let [rect (.getBoundingClientRect element) - height (or (.-innerHeight js/window) - (.. js/document -documentElement -clientHeight)) - width (or (.-innerWidth js/window) - (.. js/document -documentElement -clientWidth))] - (and (>= (.-top rect) 0) - (>= (.-left rect) 0) - (<= (.-bottom rect) height) - (<= (.-right rect) width)))) + [^js element] + (when (some? element) + (let [rect (.getBoundingClientRect element) + height (or (.-innerHeight js/window) + (.. js/document -documentElement -clientHeight)) + width (or (.-innerWidth js/window) + (.. js/document -documentElement -clientWidth))] + (and (>= (.-top rect) 0) + (>= (.-left rect) 0) + (<= (.-bottom rect) height) + (<= (.-right rect) width))))) (defn trigger-download-uri [filename mtype uri]