mirror of
https://github.com/penpot/penpot.git
synced 2025-01-25 07:58:49 -05:00
✨ Fix editor and bounds for new texts
This commit is contained in:
parent
1c2785f34e
commit
18dded1a00
5 changed files with 165 additions and 61 deletions
|
@ -190,3 +190,20 @@
|
|||
(multiply mtx)
|
||||
(translate (gpt/negate pt)))
|
||||
mtx))
|
||||
|
||||
(defn determinant
|
||||
"Determinant for the affinity transform"
|
||||
[{:keys [a b c d _ _]}]
|
||||
(- (* a d) (* c b)))
|
||||
|
||||
(defn inverse
|
||||
"Gets the inverse of the affinity transform `mtx`"
|
||||
[{:keys [a b c d e f] :as mtx}]
|
||||
(let [det (determinant mtx)
|
||||
a' (/ d det)
|
||||
b' (/ (- b) det)
|
||||
c' (/ (- c) det)
|
||||
d' (/ a det)
|
||||
e' (/ (- (* c f) (* d e)) det)
|
||||
f' (/ (- (* b e) (* a f)) det)]
|
||||
(Matrix. a' b' c' d' e' f')))
|
||||
|
|
|
@ -84,16 +84,18 @@
|
|||
base #js {:textDecoration text-decoration
|
||||
:textTransform text-transform
|
||||
:lineHeight (or line-height "inherit")
|
||||
:color text-color}]
|
||||
:color text-color
|
||||
:caretColor "black"}]
|
||||
|
||||
(when-let [gradient (:fill-color-gradient data)]
|
||||
(let [text-color (-> (update gradient :type keyword)
|
||||
(uc/gradient->css))]
|
||||
(-> base
|
||||
(obj/set! "--text-color" text-color)
|
||||
(obj/set! "backgroundImage" "var(--text-color)")
|
||||
(obj/set! "WebkitTextFillColor" "transparent")
|
||||
(obj/set! "WebkitBackgroundClip" "text"))))
|
||||
(obj/set! "color" text-color)
|
||||
#_(obj/set! "--text-color" text-color)
|
||||
#_(obj/set! "backgroundImage" "var(--text-color)")
|
||||
#_(obj/set! "WebkitTextFillColor" "transparent")
|
||||
#_(obj/set! "WebkitBackgroundClip" "text"))))
|
||||
|
||||
(when (and (string? letter-spacing)
|
||||
(pos? (alength letter-spacing)))
|
||||
|
|
|
@ -23,9 +23,11 @@
|
|||
[props]
|
||||
|
||||
(let [render-id (mf/use-ctx muc/render-ctx)
|
||||
{:keys [position-data] :as shape} (obj/get props "shape")
|
||||
group-props (-> #js {:transform (gsh/transform-matrix shape)}
|
||||
(attrs/add-style-attrs shape render-id))
|
||||
{:keys [id x y width height position-data] :as shape} (obj/get props "shape")
|
||||
clip-id (str "clip-text" id "_" render-id)
|
||||
group-props (-> #js {:transform (gsh/transform-matrix shape)
|
||||
:clipPath (str "url(#" clip-id ")")}
|
||||
(attrs/add-style-attrs shape render-id))
|
||||
get-gradient-id
|
||||
(fn [index]
|
||||
(str render-id "_" (:id shape) "_" index))]
|
||||
|
@ -41,10 +43,16 @@
|
|||
|
||||
[:& shape-custom-stroke {:shape shape}
|
||||
[:> :g group-props
|
||||
[:defs
|
||||
[:clipPath {:id clip-id}
|
||||
[:rect.text-clip
|
||||
{:x x :y y
|
||||
:width width :height height
|
||||
:transform (gsh/transform-matrix shape)}]]]
|
||||
(for [[index data] (d/enumerate position-data)]
|
||||
(let [props (-> #js {:x (:x data)
|
||||
:y (:y data)
|
||||
:dominant-baseline "ideographic"
|
||||
:dominantBaseline "ideographic"
|
||||
:style (-> #js {:fontFamily (:font-family data)
|
||||
:fontSize (:font-size data)
|
||||
:fontWeight (:font-weight data)
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
(ns app.main.ui.workspace.shapes.text
|
||||
(:require
|
||||
[app.common.attrs :as attrs]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logging :as log]
|
||||
[app.common.math :as mth]
|
||||
[app.common.transit :as transit]
|
||||
|
@ -19,6 +22,7 @@
|
|||
[app.main.ui.shapes.text.svg-text :as svg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.object :as obj]
|
||||
[app.util.svg :as usvg]
|
||||
[app.util.text-editor :as ted]
|
||||
[app.util.text-svg-position :as utp]
|
||||
[app.util.timers :as timers]
|
||||
|
@ -40,12 +44,18 @@
|
|||
(defn- update-with-current-editor-state
|
||||
[{:keys [id] :as shape}]
|
||||
(let [editor-state-ref (mf/use-memo (mf/deps id) #(l/derived (l/key id) refs/workspace-editor-state))
|
||||
editor-state (mf/deref editor-state-ref)]
|
||||
editor-state (mf/deref editor-state-ref)
|
||||
|
||||
content (:content shape)
|
||||
editor-content
|
||||
(when editor-state
|
||||
(-> editor-state
|
||||
(ted/get-editor-current-content)
|
||||
(ted/export-content)))]
|
||||
|
||||
(cond-> shape
|
||||
(some? editor-state)
|
||||
(assoc :content (-> editor-state
|
||||
(ted/get-editor-current-content)
|
||||
(ted/export-content))))))
|
||||
(some? editor-content)
|
||||
(assoc :content (attrs/merge content editor-content)))))
|
||||
|
||||
(mf/defc text-resize-content
|
||||
{::mf/wrap-props false}
|
||||
|
@ -106,69 +116,126 @@
|
|||
|
||||
[:& fo/text-shape {:ref text-ref-cb :shape shape :grow-type (:grow-type shape)}]))
|
||||
|
||||
|
||||
(defn calc-position-data
|
||||
[base-node]
|
||||
(let [viewport (dom/get-element "render")
|
||||
zoom (get-in @st/state [:workspace-local :zoom])
|
||||
text-data (utp/calc-text-node-positions base-node viewport zoom)]
|
||||
(->> text-data
|
||||
(map (fn [{:keys [node position text]}]
|
||||
(let [{:keys [x y width height]} position
|
||||
rtl? (= "rtl" (.-dir (.-parentElement ^js node)))
|
||||
styles (.computedStyleMap ^js node)]
|
||||
(d/without-nils
|
||||
{:rtl? rtl?
|
||||
:x (if rtl? (+ x width) x)
|
||||
:y (+ y height)
|
||||
:width width
|
||||
:height height
|
||||
:font-family (str (.get styles "font-family"))
|
||||
:font-size (str (.get styles "font-size"))
|
||||
:font-weight (str (.get styles "font-weight"))
|
||||
:text-transform (str (.get styles "text-transform"))
|
||||
:text-decoration (str (.get styles "text-decoration"))
|
||||
:font-style (str (.get styles "font-style"))
|
||||
:fill-color (or (dom/get-attribute node "data-fill-color") "#000000")
|
||||
:fill-color-gradient (transit/decode-str (dom/get-attribute node "data-fill-color-gradient"))
|
||||
:fill-opacity (d/parse-double (or (:fill-opacity node) "1"))
|
||||
:text text})))))))
|
||||
|
||||
|
||||
|
||||
(mf/defc text-wrapper
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [{:keys [id dirty?] :as shape} (unchecked-get props "shape")
|
||||
edition-ref (mf/use-memo (mf/deps id) #(l/derived (fn [o] (= id (:edition o))) refs/workspace-local))
|
||||
edition? (mf/deref edition-ref)
|
||||
shape-ref (mf/use-ref nil)]
|
||||
shape-ref (mf/use-ref nil)
|
||||
|
||||
prev-obs-ref (mf/use-ref nil)
|
||||
local-position-data (mf/use-state nil)
|
||||
|
||||
handle-change-foreign-object
|
||||
(fn []
|
||||
(when-let [node (mf/ref-val shape-ref)]
|
||||
(let [position-data (calc-position-data node)
|
||||
parent (dom/get-parent node)
|
||||
parent-transform (dom/get-attribute parent "transform")
|
||||
node-transform (dom/get-attribute node "transform")
|
||||
|
||||
parent-mtx (usvg/parse-transform parent-transform)
|
||||
node-mtx (usvg/parse-transform node-transform)
|
||||
|
||||
;; We need to see what transformation is applied in the DOM to reverse it
|
||||
;; before calculating the position data
|
||||
mtx (-> (gmt/multiply parent-mtx node-mtx)
|
||||
(gmt/inverse))
|
||||
|
||||
position-data'
|
||||
(->> position-data
|
||||
(mapv #(merge % (-> (select-keys % [:x :y :width :height])
|
||||
(gsh/transform-rect mtx)))))]
|
||||
(reset! local-position-data position-data'))))
|
||||
|
||||
on-change-node
|
||||
(fn [^js node]
|
||||
(mf/set-ref-val! shape-ref node)
|
||||
|
||||
(when-let [^js prev-obs (mf/ref-val prev-obs-ref)]
|
||||
(.disconnect prev-obs)
|
||||
(mf/set-ref-val! prev-obs-ref nil))
|
||||
|
||||
(when (some? node)
|
||||
(let [fo-node (dom/query node "foreignObject")
|
||||
options #js {:attributes true
|
||||
:childList true
|
||||
:subtree true}
|
||||
mutation-obs (js/MutationObserver. handle-change-foreign-object)]
|
||||
(mf/set-ref-val! prev-obs-ref mutation-obs)
|
||||
(.observe mutation-obs fo-node options))))]
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(fn []
|
||||
(when-let [^js prev-obs (mf/ref-val prev-obs-ref)]
|
||||
(.disconnect prev-obs)
|
||||
(mf/set-ref-val! prev-obs-ref nil)))))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps dirty?)
|
||||
(mf/deps id dirty?)
|
||||
(fn []
|
||||
(when (and (or dirty? (not (:position-data shape))) (some? id))
|
||||
(let [base-node (mf/ref-val shape-ref)
|
||||
viewport (dom/get-element "render")
|
||||
zoom (get-in @st/state [:workspace-local :zoom])
|
||||
text-data (utp/calc-text-node-positions base-node viewport zoom)
|
||||
position-data
|
||||
(->> text-data
|
||||
(map (fn [{:keys [node position text]}]
|
||||
(let [{:keys [x y width height]} position
|
||||
rtl? (= "rtl" (.-dir (.-parentElement ^js node)))
|
||||
styles (.computedStyleMap ^js node)]
|
||||
(d/without-nils
|
||||
{:rtl? rtl?
|
||||
:x (if rtl? (+ x width) x)
|
||||
:y (+ y height)
|
||||
:width width
|
||||
:height height
|
||||
:font-family (str (.get styles "font-family"))
|
||||
:font-size (str (.get styles "font-size"))
|
||||
:font-weight (str (.get styles "font-weight"))
|
||||
:text-transform (str (.get styles "text-transform"))
|
||||
:text-decoration (str (.get styles "text-decoration"))
|
||||
:font-style (str (.get styles "font-style"))
|
||||
:fill-color (or (dom/get-attribute node "data-fill-color") "#000000")
|
||||
:fill-color-gradient (transit/decode-str (dom/get-attribute node "data-fill-color-gradient"))
|
||||
:fill-opacity (d/parse-double (or (:fill-opacity node) "1"))
|
||||
:text text})))))]
|
||||
(st/emit! (dch/update-shapes
|
||||
[id]
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(dissoc :dirty?)
|
||||
(assoc :position-data position-data)))))))))
|
||||
(let [node (mf/ref-val shape-ref)
|
||||
position-data (calc-position-data node)]
|
||||
(reset! local-position-data nil)
|
||||
(st/emit! (dch/update-shapes
|
||||
[id]
|
||||
(fn [shape]
|
||||
(-> shape
|
||||
(dissoc :dirty?)
|
||||
(assoc :position-data position-data))))))))
|
||||
|
||||
[:> shape-container {:shape shape}
|
||||
;; We keep hidden the shape when we're editing so it keeps track of the size
|
||||
;; and updates the selrect accordingly
|
||||
[:*
|
||||
[:g.text-shape {:ref shape-ref
|
||||
[:g.text-shape {:ref on-change-node
|
||||
:opacity (when (or edition? (some? (:position-data shape))) 0)
|
||||
:pointer-events "none"}
|
||||
|
||||
;; The `:key` prop here is mandatory because the
|
||||
;; text-resize-content breaks a hooks rule and we can't reuse
|
||||
;; the component if the edition flag changes.
|
||||
[:& text-resize-content {:shape (cond-> shape
|
||||
(:position-data shape)
|
||||
(dissoc :transform :transform-inverse))
|
||||
[:& text-resize-content {:shape
|
||||
(cond-> shape
|
||||
(:position-data shape)
|
||||
(dissoc :transform :transform-inverse))
|
||||
:edition? edition?
|
||||
:key (str id edition?)}]]
|
||||
|
||||
[:g.text-svg {:opacity (when edition? 0)
|
||||
:pointer-events "none"}
|
||||
(when (some? (:position-data shape))
|
||||
[:& svg/text-shape {:shape shape}])]]]))
|
||||
[:& svg/text-shape {:shape (cond-> shape
|
||||
(some? @local-position-data)
|
||||
(assoc :position-data @local-position-data))}])]]]))
|
||||
|
|
|
@ -105,7 +105,9 @@
|
|||
text?
|
||||
[shape-node
|
||||
(dom/query shape-node "foreignObject")
|
||||
(dom/query shape-node ".text-shape")]
|
||||
(dom/query shape-node ".text-shape")
|
||||
(dom/query shape-node ".text-svg")
|
||||
(dom/query shape-node ".text-clip")]
|
||||
|
||||
:else
|
||||
[shape-node])))
|
||||
|
@ -118,18 +120,26 @@
|
|||
|
||||
[text-transform text-width text-height]
|
||||
(when (= :text type)
|
||||
(text-corrected-transform shape transform modifiers))]
|
||||
(text-corrected-transform shape transform modifiers))
|
||||
|
||||
text-width (str text-width)
|
||||
text-height (str text-height)]
|
||||
|
||||
(doseq [node nodes]
|
||||
(cond
|
||||
(dom/class? node "text-shape")
|
||||
(or (dom/class? node "text-shape") (dom/class? node "text-svg"))
|
||||
(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))
|
||||
(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))
|
||||
|
||||
(when (and (some? text-height) (not= cur-height text-height))
|
||||
(dom/set-attribute node "height" text-height)))
|
||||
|
||||
(and (some? transform) (some? node))
|
||||
(dom/set-attribute node "transform" (str transform))))))))
|
||||
|
|
Loading…
Add table
Reference in a new issue