diff --git a/common/src/app/common/geom/shapes/text.cljc b/common/src/app/common/geom/shapes/text.cljc index 6058324ea9..a23cca61a9 100644 --- a/common/src/app/common/geom/shapes/text.cljc +++ b/common/src/app/common/geom/shapes/text.cljc @@ -17,11 +17,15 @@ :width width :height height}) -(defn position-data-bounding-box +(defn position-data-points [{:keys [position-data] :as shape}] (let [points (->> position-data (mapcat (comp gpr/rect->points position-data->rect))) - transform (gtr/transform-matrix shape) - points (gco/transform-points points transform)] - (gpr/points->selrect points))) + transform (gtr/transform-matrix shape)] + (gco/transform-points points transform))) + +(defn position-data-bounding-box + [shape] + (gpr/points->selrect (position-data-points shape))) + diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs index 30a5ed70f6..a9b58db4ca 100644 --- a/frontend/src/app/main/ui/shapes/mask.cljs +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -6,7 +6,9 @@ (ns app.main.ui.shapes.mask (:require + [app.common.data :as d] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.text :as gst] [app.main.ui.context :as muc] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -29,6 +31,17 @@ (defn filter-url [render-id mask] (str "url(#" (filter-id render-id mask) ")")) +(defn set-white-fill + [shape] + (let [update-color + (fn [data] + (-> data + (dissoc :fill-color :fill-opacity :fill-color-gradient) + (assoc :fill-color "#FFFFFF" :fill-opacity 1)))] + (-> shape + (d/update-when :position-data #(mapv update-color %)) + (assoc :stroke-color "#FFFFFF" :stroke-opacity 1)))) + (defn mask-factory [shape-wrapper] (mf/fnc mask-shape @@ -36,25 +49,44 @@ [props] (let [mask (unchecked-get props "mask") render-id (mf/use-ctx muc/render-ctx) - mask' (gsh/transform-shape mask)] - [:defs - [:filter {:id (filter-id render-id mask)} - [:feFlood {:flood-color "white" - :result "FloodResult"}] - [:feComposite {:in "FloodResult" - :in2 "SourceGraphic" - :operator "in" - :result "comp"}]] - ;; Clip path is necessary so the elements inside the mask won't affect - ;; the events outside. Clip hides the elements but mask doesn't (like display vs visibility) - ;; we cannot use clips instead of mask because clips can only be simple shapes - [:clipPath {:class "mask-clip-path" - :id (clip-id render-id mask)} - [:polyline {:points (->> (:points mask') - (map #(str (:x %) "," (:y %))) - (str/join " "))}]] - [:mask {:class "mask-shape" - :id (mask-id render-id mask)} - [:g {:filter (filter-url render-id mask)} - [:& shape-wrapper {:shape (dissoc mask :shadow :blur)}]]]]))) + svg-text? (and (= :text (:type mask)) (some? (:position-data mask))) + + mask (cond-> mask svg-text? set-white-fill) + + mask-bb + (cond + svg-text? + (gst/position-data-points mask) + + :else + (-> (gsh/transform-shape mask) + (:points)))] + [:* + [:g {:opacity 0} + [:g {:id (str "shape-" (mask-id render-id mask))} + [:& shape-wrapper {:shape (dissoc mask :shadow :blur)}]]] + + [:defs + [:filter {:id (filter-id render-id mask)} + [:feFlood {:flood-color "white" + :result "FloodResult"}] + [:feComposite {:in "FloodResult" + :in2 "SourceGraphic" + :operator "in" + :result "comp"}]] + ;; Clip path is necessary so the elements inside the mask won't affect + ;; the events outside. Clip hides the elements but mask doesn't (like display vs visibility) + ;; we cannot use clips instead of mask because clips can only be simple shapes + [:clipPath {:class "mask-clip-path" + :id (clip-id render-id mask)} + [:polyline {:points (->> mask-bb + (map #(str (:x %) "," (:y %))) + (str/join " "))}]] + + [:mask {:class "mask-shape" + :id (mask-id render-id mask)} + ;; SVG texts are broken in Firefox with the filter. When the masking shapes is a text + ;; we use the `set-white-fill` instead of using the filter + [:g {:filter (when-not svg-text? (filter-url render-id mask))} + [:use {:href (str "#shape-" (mask-id render-id mask))}]]]]]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index b62cdcfb89..36e82d8649 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -7,7 +7,6 @@ (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] @@ -123,7 +122,7 @@ (mf/defc text-wrapper {::mf/wrap-props false} [props] - (let [{:keys [id content points] :as shape} (unchecked-get props "shape") + (let [{:keys [id] :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) @@ -150,7 +149,7 @@ (gsh/transform-rect mtx)))))] (reset! local-position-data position-data)))) - [shape-ref on-change-node] (use-mutable-observer handle-change-foreign-object) + [_ on-change-node] (use-mutable-observer handle-change-foreign-object) show-svg-text? (or (some? (:position-data shape)) (some? @local-position-data)) @@ -170,7 +169,7 @@ (fn [] ;; Timer to update the shape. We do this so a lot of changes won't produce ;; a lot of updates (kind of a debounce) - (let [sid (timers/schedule 250 update-position-data)] + (let [sid (timers/schedule 100 update-position-data)] (fn [] (rx/dispose! sid)))))