diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index 38f5ab6b0..6022514f2 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -82,6 +82,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defonce loaded (l/atom #{})) +(defonce loading (l/atom {})) (defn- create-link-element [uri] @@ -199,11 +200,34 @@ (p/create (fn [resolve] (ensure-loaded! id resolve)))) ([id on-loaded] - (if (contains? @loaded id) - (on-loaded id) - (when-let [font (get @fontsdb id)] - (load-font (assoc font ::on-loaded on-loaded)) - (swap! loaded conj id))))) + (let [font (get @fontsdb id)] + (cond + ;; Font already loaded, we just continue + (contains? @loaded id) + (on-loaded id) + + ;; Font is currently downloading. We attach the caller to the promise + (contains? @loading id) + (-> (get @loading id) + (p/then #(on-loaded id))) + + ;; First caller, we create the promise and then wait + :else + (let [on-load (fn [resolve] + (swap! loaded conj id) + (swap! loading dissoc id) + (on-loaded id) + (resolve id)) + + load-p (p/create + (fn [resolve _] + (-> font + (assoc ::on-loaded (partial on-load resolve)) + (load-font))))] + + (swap! loading assoc id load-p) + + nil))))) (defn ready [cb] diff --git a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs index cb692b663..eb6a89b91 100644 --- a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs +++ b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs @@ -73,8 +73,12 @@ ;; Creates a style tag by replacing the urls with the data uri style (replace-embeds fonts-css fonts-urls fonts-embed)] - (when (d/not-empty? style) - [:style {:data-loading loading?} style]))) + (cond + (d/not-empty? style) + [:style {:data-loading loading?} style] + + (d/not-empty? fonts) + [:style {:data-loading true}]))) (defn shape->fonts [shape objects] diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index fb63acd0a..8082d00ff 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -110,7 +110,10 @@ (let [font-variant (d/seek #(= font-variant-id (:id %)) (:variants font))] [(str/quote (or (:family font) (:font-family data))) (or (:style font-variant) (:font-style data)) - (or (:weight font-variant) (:font-weight data))]))] + (or (:weight font-variant) (:font-weight data))])) + + base (-> base + (obj/set! "--font-id" font-id))] (cond-> base (some? fills) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index e6096c18b..74a561b5a 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -115,7 +115,7 @@ (fn [] (when (and (some? @node-ref) @regenerate-thumbnail) (let [loading-images? (some? (dom/query @node-ref "[data-loading='true']")) - loading-fonts? (some? (dom/query (dm/str "#frame-container-" (:id shape) " style[data-loading='true']")))] + loading-fonts? (some? (dom/query (dm/str "#frame-container-" (:id shape) " > style[data-loading='true']")))] (when (and (not loading-images?) (not loading-fonts?)) (generate-thumbnail) (reset! regenerate-thumbnail false)))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs index 450c72070..5e7823b90 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts.cljs @@ -23,6 +23,7 @@ [app.util.text-editor :as ted] [app.util.text-svg-position :as utp] [app.util.timers :as ts] + [promesa.core :as p] [rumext.alpha :as mf])) (defn strip-position-data [shape] @@ -73,25 +74,25 @@ (st/emit! (dwt/resize-text id width height))))) ;; Update the position-data of every text fragment - (let [position-data (utp/calc-position-data node)] + (p/let [position-data (utp/calc-position-data node)] (st/emit! (dwt/update-position-data id position-data))) (st/emit! (dwt/clean-text-modifier id))) (defn- update-text-modifier [{:keys [grow-type id]} node] - (let [position-data (utp/calc-position-data node) - props {:position-data position-data} + (p/let [position-data (utp/calc-position-data node) + props {:position-data position-data} - props - (if (contains? #{:auto-height :auto-width} grow-type) - (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size)) - width (mth/ceil width) - height (mth/ceil height)] - (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height))) - (assoc props :width width :height height) - props)) - props)] + props + (if (contains? #{:auto-height :auto-width} grow-type) + (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size)) + width (mth/ceil width) + height (mth/ceil height)] + (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height))) + (assoc props :width width :height height) + props)) + props)] (st/emit! (dwt/update-text-modifier id props)))) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index dc25d62b3..71620cddb 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -548,3 +548,11 @@ (seq (.-children node)))] (->> root-node (tree-seq branch? get-children)))) + +(defn check-font? [font] + (let [fonts (.-fonts globals/document)] + (.check fonts font))) + +(defn load-font [font] + (let [fonts (.-fonts globals/document)] + (.load fonts font))) diff --git a/frontend/src/app/util/text_svg_position.cljs b/frontend/src/app/util/text_svg_position.cljs index e25a70f6f..007e60cd7 100644 --- a/frontend/src/app/util/text_svg_position.cljs +++ b/frontend/src/app/util/text_svg_position.cljs @@ -9,9 +9,11 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.transit :as transit] + [app.main.fonts :as fonts] [app.main.store :as st] [app.util.dom :as dom] - [app.util.text-position-data :as tpd])) + [app.util.text-position-data :as tpd] + [promesa.core :as p])) (defn parse-text-nodes "Given a text node retrieves the rectangles for everyone of its paragraphs and its text." @@ -27,6 +29,27 @@ (map parse-entry) (tpd/parse-text-nodes parent-node text-node)))) +(def load-promises (atom {})) + +(defn load-font + [font] + (if (contains? @load-promises font) + (get @load-promises font) + (let [load-promise (dom/load-font font)] + (swap! load-promises assoc font load-promise) + load-promise))) + +(defn resolve-font + [^js node] + + (let [styles (js/getComputedStyle node) + font (.getPropertyValue styles "font")] + (if (dom/check-font? font) + (p/resolved font) + (let [font-id (.getPropertyValue styles "--font-id")] + (-> (fonts/ensure-loaded! font-id) + (p/then #(when (not (dom/check-font? font)) + (load-font font)))))))) (defn calc-text-node-positions [base-node viewport zoom] @@ -58,22 +81,25 @@ :width (- (:x p2) (:x p1)) :height (- (:y p2) (:y p1))))) - text-nodes (dom/query-all base-node ".text-node, span[data-text]")] - - (->> text-nodes - (mapcat - (fn [parent-node] - (let [direction (.-direction (js/getComputedStyle parent-node))] - (->> (.-childNodes parent-node) - (mapcat #(parse-text-nodes parent-node direction %)))))) - (mapv #(update % :position translate-rect)))))) + text-nodes (dom/query-all base-node ".text-node, span[data-text]") + load-fonts (->> text-nodes (map resolve-font))] + (-> (p/all load-fonts) + (p/then + (fn [] + (->> text-nodes + (mapcat + (fn [parent-node] + (let [direction (.-direction (js/getComputedStyle parent-node))] + (->> (.-childNodes parent-node) + (mapcat #(parse-text-nodes parent-node direction %)))))) + (mapv #(update % :position translate-rect))))))))) (defn calc-position-data [base-node] (let [viewport (dom/get-element "render") zoom (or (get-in @st/state [:workspace-local :zoom]) 1)] (when (and (some? base-node) (some? viewport)) - (let [text-data (calc-text-node-positions base-node viewport zoom)] + (p/let [text-data (calc-text-node-positions base-node viewport zoom)] (when (d/not-empty? text-data) (->> text-data (mapv (fn [{:keys [node position text direction]}]