mirror of
https://github.com/penpot/penpot.git
synced 2026-08-29 16:18:48 +00:00
⚡ Scope post-font text relayout to pending font faces
Track which shapes use each font face at upload time and only force relayout and stale-selrect sync for shapes tied to not-yet-ready faces instead of every text shape on the page.
This commit is contained in:
parent
627921cb46
commit
b2bd3582ae
@ -1604,6 +1604,8 @@
|
||||
(set-layout-data shape)))
|
||||
|
||||
(let [is-text? (= type :text)
|
||||
font-face-keys (when is-text? (text-font-face-keys content))
|
||||
pending-font-face-keys (when is-text? (text-pending-font-face-keys content))
|
||||
text-content-pending (when is-text? (set-shape-text-content id content))
|
||||
pending-thumbnails (into [] (concat
|
||||
text-content-pending
|
||||
@ -1616,12 +1618,13 @@
|
||||
(set-shape-strokes id strokes false write-fills-strokes?)))]
|
||||
{:thumbnails pending-thumbnails
|
||||
:full pending-full
|
||||
:font-pending-ids (if (some :callback text-content-pending) [id] [])})))
|
||||
:font-face-keys (or font-face-keys #{})
|
||||
:pending-font-face-keys (or pending-font-face-keys #{})})))
|
||||
|
||||
(defn set-object
|
||||
[shape]
|
||||
(if-not (and shape (wasm/live?))
|
||||
{:thumbnails [] :full [] :font-pending-ids []}
|
||||
{:thumbnails [] :full [] :font-face-keys #{} :pending-font-face-keys #{}}
|
||||
(do
|
||||
(perf/begin-measure "set-object")
|
||||
(let [shape (svg-filters/apply-svg-derived shape)]
|
||||
@ -1629,6 +1632,51 @@
|
||||
(let [result (set-object-host-attrs shape false)]
|
||||
(perf/end-measure "set-object")
|
||||
result)))))
|
||||
(def ^:private empty-text-font-state
|
||||
{:font-index {} :pending-faces #{}})
|
||||
|
||||
(defn- text-font-face-keys
|
||||
[content]
|
||||
(into #{}
|
||||
(comp (map f/make-font-data)
|
||||
(map f/font-data-key))
|
||||
(f/get-content-fonts content)))
|
||||
|
||||
(defn- text-pending-font-face-keys
|
||||
"Font faces that were not WASM-ready when the shape was uploaded."
|
||||
[content]
|
||||
(into #{}
|
||||
(comp (map f/make-font-data)
|
||||
(remove f/font-ready?)
|
||||
(map f/font-data-key))
|
||||
(f/get-content-fonts content)))
|
||||
|
||||
(defn- acc-text-font-state
|
||||
[{:keys [font-index pending-faces]} id font-face-keys pending-font-face-keys]
|
||||
{:font-index (reduce (fn [idx face]
|
||||
(update idx face (fnil conj #{}) id))
|
||||
font-index
|
||||
font-face-keys)
|
||||
:pending-faces (into pending-faces pending-font-face-keys)})
|
||||
|
||||
(defn text-font-state-for-shape
|
||||
"Build the font-face index for a single text shape (incremental updates)."
|
||||
[shape]
|
||||
(if (cfh/text-shape? shape)
|
||||
(let [content (ensure-text-content (:content shape))]
|
||||
(acc-text-font-state empty-text-font-state
|
||||
(:id shape)
|
||||
(text-font-face-keys content)
|
||||
(text-pending-font-face-keys content)))
|
||||
empty-text-font-state))
|
||||
|
||||
(defn- shape-ids-for-pending-fonts
|
||||
[{:keys [font-index pending-faces]}]
|
||||
(when (seq pending-faces)
|
||||
(into #{}
|
||||
(mapcat #(get font-index % []))
|
||||
pending-faces)))
|
||||
|
||||
(defn- update-text-layouts
|
||||
"Synchronously update text layouts for all shapes and send rect updates
|
||||
to the worker index."
|
||||
@ -1697,20 +1745,22 @@
|
||||
|
||||
(defn- relayout-after-fonts!
|
||||
"Relayout text shapes once their pending fonts have resolved. Font fetches
|
||||
are deduped per URL and storing a font does not invalidate cached layouts,
|
||||
so every text shape (not only the fetch triggers in `font-pending-ids`)
|
||||
needs a forced relayout; then re-sync selrects that drifted."
|
||||
[shapes font-pending-ids]
|
||||
(let [text-ids (into [] (comp (filter cfh/text-shape?) (map :id)) shapes)]
|
||||
are deduped per URL, so only shapes that use a not-yet-ready face at upload
|
||||
time need a forced relayout; then re-sync selrects that drifted for those
|
||||
shapes only."
|
||||
[shapes text-font-state]
|
||||
(let [text-ids (into [] (comp (filter cfh/text-shape?) (map :id)) shapes)
|
||||
affected-ids (or (shape-ids-for-pending-fonts text-font-state) #{})
|
||||
shapes-by-id (d/index-by :id shapes)]
|
||||
(when (seq text-ids)
|
||||
(if (seq font-pending-ids)
|
||||
(do
|
||||
(force-update-text-layouts text-ids)
|
||||
(sync-stale-text-selrects! shapes))
|
||||
(if (seq affected-ids)
|
||||
(let [affected-shapes (into [] (keep shapes-by-id) affected-ids)]
|
||||
(force-update-text-layouts affected-ids)
|
||||
(sync-stale-text-selrects! affected-shapes))
|
||||
(update-text-layouts text-ids)))))
|
||||
|
||||
(defn process-pending
|
||||
[shapes thumbnails full font-pending-ids on-complete]
|
||||
[shapes thumbnails full text-font-state on-complete]
|
||||
(let [pending-thumbnails
|
||||
(d/index-by :key :callback thumbnails)
|
||||
|
||||
@ -1736,7 +1786,7 @@
|
||||
noop-fn
|
||||
noop-fn
|
||||
(fn []
|
||||
(relayout-after-fonts! shapes font-pending-ids)
|
||||
(relayout-after-fonts! shapes text-font-state)
|
||||
(request-render "images-loaded")
|
||||
(when (fn? on-complete) (on-complete)))))
|
||||
;; No pending images — complete immediately.
|
||||
@ -1744,8 +1794,13 @@
|
||||
|
||||
(defn process-object
|
||||
[shape]
|
||||
(let [{:keys [thumbnails full font-pending-ids]} (set-object shape)]
|
||||
(process-pending [shape] thumbnails full font-pending-ids noop-fn)))
|
||||
(let [{:keys [thumbnails full font-face-keys pending-font-face-keys]}
|
||||
(set-object shape)
|
||||
text-font-state (acc-text-font-state empty-text-font-state
|
||||
(:id shape)
|
||||
font-face-keys
|
||||
pending-font-face-keys)]
|
||||
(process-pending [shape] thumbnails full text-font-state noop-fn)))
|
||||
|
||||
(defn process-objects
|
||||
"Like process-object but for multiple shapes at once. Accumulates all
|
||||
@ -1754,19 +1809,26 @@
|
||||
just the first shape that triggered the fetch."
|
||||
[shapes]
|
||||
(let [total-shapes (count shapes)
|
||||
{:keys [thumbnails full font-pending-ids]}
|
||||
(loop [index 0 thumbnails-acc (transient []) full-acc (transient []) font-acc (transient [])]
|
||||
{:keys [thumbnails full text-font-state]}
|
||||
(loop [index 0
|
||||
thumbnails-acc (transient [])
|
||||
full-acc (transient [])
|
||||
font-state-acc empty-text-font-state]
|
||||
(if (< index total-shapes)
|
||||
(let [shape (nth shapes index)
|
||||
{:keys [thumbnails full font-pending-ids]} (set-object shape)]
|
||||
{:keys [thumbnails full font-face-keys pending-font-face-keys]}
|
||||
(set-object shape)]
|
||||
(recur (inc index)
|
||||
(reduce conj! thumbnails-acc thumbnails)
|
||||
(reduce conj! full-acc full)
|
||||
(reduce conj! font-acc font-pending-ids)))
|
||||
(acc-text-font-state font-state-acc
|
||||
(:id shape)
|
||||
font-face-keys
|
||||
pending-font-face-keys)))
|
||||
{:thumbnails (persistent! thumbnails-acc)
|
||||
:full (persistent! full-acc)
|
||||
:font-pending-ids (persistent! font-acc)}))]
|
||||
(process-pending shapes thumbnails full font-pending-ids noop-fn)))
|
||||
:text-font-state font-state-acc}))]
|
||||
(process-pending shapes thumbnails full text-font-state noop-fn)))
|
||||
|
||||
(def ^:private ^:const BATCH_MAX_SHAPES 512)
|
||||
|
||||
@ -1776,8 +1838,8 @@
|
||||
Structural attrs are uploaded in one `_set_shapes_batch` FFI per chunk;
|
||||
host-specific attrs (fills/strokes/text/grid/path) stay per-shape.
|
||||
|
||||
Returns {:thumbnails [...] :full [...] :font-pending-ids [...] :next-index n}"
|
||||
[shapes start-index thumbnails-acc full-acc font-pending-acc]
|
||||
Returns {:thumbnails [...] :full [...] :text-font-state {...} :next-index n}"
|
||||
[shapes start-index thumbnails-acc full-acc text-font-state-acc]
|
||||
(let [total (count shapes)
|
||||
end-index (min total (+ start-index BATCH_MAX_SHAPES))
|
||||
chunk (into [] (subvec (if (vector? shapes) shapes (vec shapes))
|
||||
@ -1804,17 +1866,20 @@
|
||||
(loop [xs prepared
|
||||
t-acc (transient thumbnails-acc)
|
||||
f-acc (transient full-acc)
|
||||
fp-acc (transient font-pending-acc)]
|
||||
font-state-acc text-font-state-acc]
|
||||
(if-let [shape (first xs)]
|
||||
(let [{:keys [thumbnails full font-pending-ids]}
|
||||
(let [{:keys [thumbnails full font-face-keys pending-font-face-keys]}
|
||||
(set-object-host-attrs shape true :skip-fills-strokes? true)]
|
||||
(recur (next xs)
|
||||
(reduce conj! t-acc thumbnails)
|
||||
(reduce conj! f-acc full)
|
||||
(reduce conj! fp-acc font-pending-ids)))
|
||||
(acc-text-font-state font-state-acc
|
||||
(:id shape)
|
||||
font-face-keys
|
||||
pending-font-face-keys)))
|
||||
{:thumbnails (persistent! t-acc)
|
||||
:full (persistent! f-acc)
|
||||
:font-pending-ids (persistent! fp-acc)
|
||||
:text-font-state font-state-acc
|
||||
:next-index end-index}))))
|
||||
|
||||
(defn- set-objects-async
|
||||
@ -1825,16 +1890,16 @@
|
||||
(let [total-shapes (count shapes)]
|
||||
(p/create
|
||||
(fn [resolve _reject]
|
||||
(letfn [(process-next-chunk [index thumbnails-acc full-acc font-pending-acc]
|
||||
(letfn [(process-next-chunk [index thumbnails-acc full-acc text-font-state-acc]
|
||||
(if (< index total-shapes)
|
||||
;; Process one time-budgeted chunk
|
||||
(let [{:keys [thumbnails full font-pending-ids next-index]}
|
||||
(let [{:keys [thumbnails full text-font-state next-index]}
|
||||
(process-shapes-chunk shapes index
|
||||
thumbnails-acc full-acc font-pending-acc)]
|
||||
thumbnails-acc full-acc text-font-state-acc)]
|
||||
;; Yield to browser, then continue with next chunk
|
||||
(-> (yield-to-browser)
|
||||
(p/then (fn [_]
|
||||
(process-next-chunk next-index thumbnails full font-pending-ids)))))
|
||||
(process-next-chunk next-index thumbnails full text-font-state)))))
|
||||
;; All chunks done - finalize
|
||||
(do
|
||||
(perf/end-measure "set-objects")
|
||||
@ -1888,9 +1953,9 @@
|
||||
noop-fn
|
||||
noop-fn
|
||||
(fn []
|
||||
(relayout-after-fonts! shapes font-pending-acc)
|
||||
(relayout-after-fonts! shapes text-font-state-acc)
|
||||
(request-render "images-loaded")))))))))))]
|
||||
(process-next-chunk 0 [] [] []))))))
|
||||
(process-next-chunk 0 [] [] empty-text-font-state))))))
|
||||
|
||||
|
||||
;; This is a version of process-pending that doesn't have sideffects
|
||||
@ -1957,22 +2022,25 @@
|
||||
(when (and (contains? #{:path :bool} type) (some? (get shape :content)))
|
||||
(props/set-shape-path-content (get shape :content))))))
|
||||
(let [total-shapes (count prepared)
|
||||
{:keys [thumbnails full font-pending-ids]}
|
||||
{:keys [thumbnails full text-font-state]}
|
||||
(loop [index 0
|
||||
thumbnails-acc (transient [])
|
||||
full-acc (transient [])
|
||||
font-acc (transient [])]
|
||||
font-state-acc empty-text-font-state]
|
||||
(if (< index total-shapes)
|
||||
(let [shape (nth prepared index)
|
||||
{:keys [thumbnails full font-pending-ids]}
|
||||
{:keys [thumbnails full font-face-keys pending-font-face-keys]}
|
||||
(set-object-host-attrs shape true :skip-fills-strokes? true)]
|
||||
(recur (inc index)
|
||||
(reduce conj! thumbnails-acc thumbnails)
|
||||
(reduce conj! full-acc full)
|
||||
(reduce conj! font-acc font-pending-ids)))
|
||||
(acc-text-font-state font-state-acc
|
||||
(:id shape)
|
||||
font-face-keys
|
||||
pending-font-face-keys)))
|
||||
{:thumbnails (persistent! thumbnails-acc)
|
||||
:full (persistent! full-acc)
|
||||
:font-pending-ids (persistent! font-acc)}))]
|
||||
:text-font-state font-state-acc}))]
|
||||
(perf/end-measure "set-objects")
|
||||
(when on-shapes-ready (on-shapes-ready))
|
||||
(when (wasm/live?)
|
||||
@ -1980,7 +2048,7 @@
|
||||
;; map to which tiles after a page switch.
|
||||
(h/call wasm/internal-module "_set_view_end")
|
||||
(reset! view-interaction-active? false)
|
||||
(process-pending shapes thumbnails full font-pending-ids
|
||||
(process-pending shapes thumbnails full text-font-state
|
||||
(fn []
|
||||
(if render-callback
|
||||
(render-callback)
|
||||
|
||||
@ -252,12 +252,12 @@
|
||||
(let [text-content-pending (api/set-shape-text-content id v)
|
||||
pending-thumbnails (vec text-content-pending)
|
||||
pending-full (vec (api/set-shape-text-images id v))
|
||||
font-pending-ids (when (some :callback text-content-pending) [id])]
|
||||
text-font-state (api/text-font-state-for-shape shape)]
|
||||
;; FIXME: this is a hack to process the pending tasks
|
||||
;; asynchronously we should probably modify set-wasm-attr!
|
||||
;; to return a list of callbacks to be executed in a
|
||||
;; second pass.
|
||||
(api/process-pending [shape] pending-thumbnails pending-full font-pending-ids api/noop-fn)
|
||||
(api/process-pending [shape] pending-thumbnails pending-full text-font-state api/noop-fn)
|
||||
nil))
|
||||
|
||||
:grow-type
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user