Merge branch 'staging-render' into develop

This commit is contained in:
Andrey Antukh 2025-12-30 15:30:56 +01:00
commit 52b8560b70
17 changed files with 186 additions and 116 deletions

View File

@ -23,7 +23,14 @@
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339) - Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
## 2.12.0 (Unreleased) ## 2.12.1
### :bug: Bugs fixed
- Fix setting a portion of text as bold or underline messes things up [Github #7980](https://github.com/penpot/penpot/issues/7980)
## 2.12.0
### :boom: Breaking changes & Deprecations ### :boom: Breaking changes & Deprecations

View File

@ -23,30 +23,25 @@ tmux -2 new-session -d -s penpot
tmux rename-window -t penpot:0 'frontend watch' tmux rename-window -t penpot:0 'frontend watch'
tmux select-window -t penpot:0 tmux select-window -t penpot:0
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn run watch' enter tmux send-keys -t penpot './scripts/watch app' enter
tmux new-window -t penpot:1 -n 'frontend shadow' tmux new-window -t penpot:1 -n 'frontend storybook'
tmux select-window -t penpot:1 tmux select-window -t penpot:1
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn run watch:app' enter tmux send-keys -t penpot './scripts/watch storybook' enter
tmux new-window -t penpot:2 -n 'frontend storybook' tmux new-window -t penpot:2 -n 'exporter'
tmux select-window -t penpot:2 tmux select-window -t penpot:2
tmux send-keys -t penpot 'cd penpot/frontend' enter C-l
tmux send-keys -t penpot 'yarn run watch:storybook' enter
tmux new-window -t penpot:3 -n 'exporter'
tmux select-window -t penpot:3
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l
tmux send-keys -t penpot 'yarn run watch' enter tmux send-keys -t penpot './scripts/watch' enter
tmux split-window -v tmux split-window -v
tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot 'cd penpot/exporter' enter C-l
tmux send-keys -t penpot './scripts/wait-and-start.sh' enter tmux send-keys -t penpot './scripts/wait-and-start.sh' enter
tmux new-window -t penpot:4 -n 'backend' tmux new-window -t penpot:3 -n 'backend'
tmux select-window -t penpot:4 tmux select-window -t penpot:3
tmux send-keys -t penpot 'cd penpot/backend' enter C-l tmux send-keys -t penpot 'cd penpot/backend' enter C-l
tmux send-keys -t penpot './scripts/start-dev' enter tmux send-keys -t penpot './scripts/start-dev' enter

View File

@ -30,8 +30,8 @@
}, },
"scripts": { "scripts": {
"clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target", "clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target",
"watch:app": "clojure -M:dev:shadow-cljs watch main", "watch:app": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main",
"watch": "yarn run clear:shadow-cache && yarn run watch:app", "watch": "yarn run watch:app",
"build:app": "clojure -M:dev:shadow-cljs release main", "build:app": "clojure -M:dev:shadow-cljs release main",
"build": "yarn run clear:shadow-cache && yarn run build:app", "build": "yarn run clear:shadow-cache && yarn run build:app",
"fmt:clj:check": "cljfmt check --parallel=false src/", "fmt:clj:check": "cljfmt check --parallel=false src/",

7
exporter/scripts/watch Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
TARGET=${1:-app};
set -ex
exec yarn run watch:$TARGET

View File

@ -47,10 +47,9 @@
"watch:app:libs": "node ./scripts/build-libs.js --watch", "watch:app:libs": "node ./scripts/build-libs.js --watch",
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook", "watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
"clear:shadow-cache": "rm -rf .shadow-cljs", "clear:shadow-cache": "rm -rf .shadow-cljs",
"watch:app": "yarn run clear:shadow-cache && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", "watch": "exit 0",
"watch": "yarn run watch:app:assets", "watch:app": "yarn run clear:shadow-cache && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
"watch:storybook": "yarn run build:storybook:assets && concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"", "watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
"watch:storybook:assets": "node ./scripts/watch-storybook.js"
}, },
"devDependencies": { "devDependencies": {
"@penpot/draft-js": "portal:./packages/draft-js", "@penpot/draft-js": "portal:./packages/draft-js",

View File

@ -74,7 +74,7 @@ export function isJsFile(path) {
export async function compileSass(worker, path, options) { export async function compileSass(worker, path, options) {
path = ph.resolve(path); path = ph.resolve(path);
log.info("compile:", path); // log.info("compile:", path);
return worker.exec("compileSass", [path, options]); return worker.exec("compileSass", [path, options]);
} }

7
frontend/scripts/watch Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
TARGET=${1:-app};
set -ex
exec yarn run watch:$TARGET

View File

@ -106,9 +106,11 @@
:overflowWrap "initial" :overflowWrap "initial"
:lineBreak "auto" :lineBreak "auto"
:whiteSpace "break-spaces" :whiteSpace "break-spaces"
:textRendering "geometricPrecision" :textRendering "geometricPrecision"}
:display "inline-block" base (cond-> base
:verticalAlign "top"} (= (:line-height data) "0")
(-> (obj/set! "display" "inline-block")
(obj/set! "verticalAlign" "top")))
fills fills
(cond (cond
;; DEPRECATED: still here for backward compatibility with ;; DEPRECATED: still here for backward compatibility with

View File

@ -16,6 +16,7 @@
[app.common.types.path :as path] [app.common.types.path :as path]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.main.data.common :as dcm]
[app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.variants :as dwv] [app.main.data.workspace.variants :as dwv]
[app.main.features :as features] [app.main.features :as features]
@ -305,9 +306,15 @@
(->> wasm.api/module (->> wasm.api/module
(p/fmap (fn [ready?] (p/fmap (fn [ready?]
(when ready? (when ready?
(let [init? (wasm.api/init-canvas-context canvas)] (let [init? (try
(wasm.api/init-canvas-context canvas)
(catch :default e
(js/console.error "Error initializing canvas context:" e)
false))]
(reset! canvas-init? init?) (reset! canvas-init? init?)
(when-not init? (js/alert "WebGL not supported"))))))) (when-not init?
(js/alert "WebGL not supported")
(st/emit! (dcm/go-to-dashboard-recent))))))))
(fn [] (fn []
(wasm.api/clear-canvas)))) (wasm.api/clear-canvas))))

View File

@ -21,6 +21,7 @@
[app.common.types.text :as txt] [app.common.types.text :as txt]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.main.data.render-wasm :as drw]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.render :as render] [app.main.render :as render]
[app.main.store :as st] [app.main.store :as st]
@ -96,20 +97,20 @@
;; This should never be called from the outside. ;; This should never be called from the outside.
(defn- render (defn- render
[timestamp] [timestamp]
(when wasm/context-initialized? (when (and wasm/context-initialized? (not @wasm/context-lost?))
(h/call wasm/internal-module "_render" timestamp) (h/call wasm/internal-module "_render" timestamp)
(set! wasm/internal-frame-id nil) (set! wasm/internal-frame-id nil)
(ug/dispatch! (ug/event "penpot:wasm:render")))) (ug/dispatch! (ug/event "penpot:wasm:render"))))
(defn render-sync (defn render-sync
[] []
(when wasm/context-initialized? (when (and wasm/context-initialized? (not @wasm/context-lost?))
(h/call wasm/internal-module "_render_sync") (h/call wasm/internal-module "_render_sync")
(set! wasm/internal-frame-id nil))) (set! wasm/internal-frame-id nil)))
(defn render-sync-shape (defn render-sync-shape
[id] [id]
(when wasm/context-initialized? (when (and wasm/context-initialized? (not @wasm/context-lost?))
(let [buffer (uuid/get-u32 id)] (let [buffer (uuid/get-u32 id)]
(h/call wasm/internal-module "_render_sync_shape" (h/call wasm/internal-module "_render_sync_shape"
(aget buffer 0) (aget buffer 0)
@ -123,7 +124,7 @@
(defn request-render (defn request-render
[_requester] [_requester]
(when (not @pending-render) (when (and wasm/context-initialized? (not @pending-render) (not @wasm/context-lost?))
(reset! pending-render true) (reset! pending-render true)
(js/requestAnimationFrame (js/requestAnimationFrame
(fn [ts] (fn [ts]
@ -759,13 +760,8 @@
(h/call wasm/internal-module "_clear_shape_layout")) (h/call wasm/internal-module "_clear_shape_layout"))
(defn- set-shape-layout (defn- set-shape-layout
[shape objects] [shape]
(clear-layout) (clear-layout)
(when (or (ctl/any-layout? shape)
(ctl/any-layout-immediate-child? objects shape)
(has-any-layout-prop? shape))
(set-layout-data shape))
(when (ctl/flex-layout? shape) (when (ctl/flex-layout? shape)
(set-flex-layout shape)) (set-flex-layout shape))
@ -916,7 +912,7 @@
(perf/end-measure "set-view-box::zoom"))))) (perf/end-measure "set-view-box::zoom")))))
(defn set-object (defn set-object
[objects shape] [shape]
(perf/begin-measure "set-object") (perf/begin-measure "set-object")
(let [shape (svg-filters/apply-svg-derived shape) (let [shape (svg-filters/apply-svg-derived shape)
id (dm/get-prop shape :id) id (dm/get-prop shape :id)
@ -981,7 +977,7 @@
(when (= type :text) (when (= type :text)
(set-shape-grow-type grow-type)) (set-shape-grow-type grow-type))
(set-shape-layout shape objects) (set-shape-layout shape)
(set-shape-selrect selrect) (set-shape-selrect selrect)
(let [pending_thumbnails (into [] (concat (let [pending_thumbnails (into [] (concat
@ -1035,7 +1031,7 @@
(defn process-object (defn process-object
[shape] [shape]
(let [{:keys [thumbnails full]} (set-object [] shape)] (let [{:keys [thumbnails full]} (set-object shape)]
(process-pending [shape] thumbnails full noop-fn))) (process-pending [shape] thumbnails full noop-fn)))
(defn set-objects (defn set-objects
@ -1050,7 +1046,7 @@
(loop [index 0 thumbnails-acc [] full-acc []] (loop [index 0 thumbnails-acc [] full-acc []]
(if (< index total-shapes) (if (< index total-shapes)
(let [shape (nth shapes index) (let [shape (nth shapes index)
{:keys [thumbnails full]} (set-object objects shape)] {:keys [thumbnails full]} (set-object shape)]
(recur (inc index) (recur (inc index)
(into thumbnails-acc thumbnails) (into thumbnails-acc thumbnails)
(into full-acc full))) (into full-acc full)))
@ -1254,40 +1250,57 @@
;; Initialize Wasm Render Engine ;; Initialize Wasm Render Engine
(h/call wasm/internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr)) (h/call wasm/internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr))
(h/call wasm/internal-module "_set_render_options" flags dpr)) (h/call wasm/internal-module "_set_render_options" flags dpr)
(set! wasm/context-initialized? true))
(h/call wasm/internal-module "_set_browser" browser) ;; Set browser and canvas size only after initialization
(h/call wasm/internal-module "_set_browser" browser)
(set-canvas-size canvas)
;; Add event listeners for WebGL context lost
(let [handler (fn [event]
(.preventDefault event)
(reset! wasm/context-lost? true)
(log/warn :hint "WebGL context lost")
(st/emit! (drw/context-lost)))]
(set! wasm/context-lost-handler handler)
(set! wasm/context-lost-canvas canvas)
(.addEventListener canvas "webglcontextlost" handler))
(set! wasm/context-initialized? true)))
(h/call wasm/internal-module "_set_render_options" flags dpr)
(set-canvas-size canvas)
context-init?)) context-init?))
(defn clear-canvas (defn clear-canvas
[] []
(try (when wasm/context-initialized?
;; TODO: perform corresponding cleaning (try
(set! wasm/context-initialized? false) ;; TODO: perform corresponding cleaning
(h/call wasm/internal-module "_clean_up") (set! wasm/context-initialized? false)
(h/call wasm/internal-module "_clean_up")
;; Ensure the WebGL context is properly disposed so browsers do not keep ;; Remove event listener for WebGL context lost
;; accumulating active contexts between page switches. (when (and wasm/context-lost-handler wasm/context-lost-canvas)
(when-let [gl (unchecked-get wasm/internal-module "GL")] (.removeEventListener wasm/context-lost-canvas "webglcontextlost" wasm/context-lost-handler)
(when-let [handle wasm/gl-context-handle] (set! wasm/context-lost-canvas nil)
(try (set! wasm/context-lost-handler nil))
;; Ask the browser to release resources explicitly if available.
(when-let [ctx wasm/gl-context]
(when-let [lose-ext (.getExtension ^js ctx "WEBGL_lose_context")]
(.loseContext ^js lose-ext)))
(.deleteContext ^js gl handle)
(finally
(set! wasm/gl-context-handle nil)
(set! wasm/gl-context nil)))))
;; If this calls panics we don't want to crash. This happens sometimes ;; Ensure the WebGL context is properly disposed so browsers do not keep
;; with hot-reload in develop ;; accumulating active contexts between page switches.
(catch :default error (when-let [gl (unchecked-get wasm/internal-module "GL")]
(.error js/console error)))) (when-let [handle wasm/gl-context-handle]
(try
;; Ask the browser to release resources explicitly if available.
(when-let [ctx wasm/gl-context]
(when-let [lose-ext (.getExtension ^js ctx "WEBGL_lose_context")]
(.loseContext ^js lose-ext)))
(.deleteContext ^js gl handle)
(finally
(set! wasm/gl-context-handle nil)
(set! wasm/gl-context nil)))))
;; If this calls panics we don't want to crash. This happens sometimes
;; with hot-reload in develop
(catch :default error
(.error js/console error)))))
(defn show-grid (defn show-grid
[id] [id]

View File

@ -354,32 +354,32 @@
:is-fallback true})) :is-fallback true}))
(def noto-fonts (def noto-fonts
{:japanese {:font-id "gfont-noto-sans-jp" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} {:japanese {:font-id "gfont-noto-sans-jp" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:chinese {:font-id "gfont-noto-sans-sc" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :chinese {:font-id "gfont-noto-sans-sc" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:korean {:font-id "gfont-noto-sans-kr" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :korean {:font-id "gfont-noto-sans-kr" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:arabic {:font-id "gfont-noto-sans-arabic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :arabic {:font-id "gfont-noto-sans-arabic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:cyrillic {:font-id "gfont-noto-sans-cyrillic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :cyrillic {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:greek {:font-id "gfont-noto-sans-greek" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :greek {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:hebrew {:font-id "gfont-noto-sans-hebrew" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :hebrew {:font-id "gfont-noto-sans-hebrew" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:thai {:font-id "gfont-noto-sans-thai" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :thai {:font-id "gfont-noto-sans-thai" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:devanagari {:font-id "gfont-noto-sans-devanagari" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :devanagari {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:tamil {:font-id "gfont-noto-sans-tamil" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :tamil {:font-id "gfont-noto-sans-tamil" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:latin-ext {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :latin-ext {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:vietnamese {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :vietnamese {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:armenian {:font-id "gfont-noto-sans-armenian" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :armenian {:font-id "gfont-noto-sans-armenian" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:bengali {:font-id "gfont-noto-sans-bengali" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :bengali {:font-id "gfont-noto-sans-bengali" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:cherokee {:font-id "gfont-noto-sans-cherokee" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :cherokee {:font-id "gfont-noto-sans-cherokee" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:ethiopic {:font-id "gfont-noto-sans-ethiopic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :ethiopic {:font-id "gfont-noto-sans-ethiopic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:georgian {:font-id "gfont-noto-sans-georgian" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :georgian {:font-id "gfont-noto-sans-georgian" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:gujarati {:font-id "gfont-noto-sans-gujarati" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :gujarati {:font-id "gfont-noto-sans-gujarati" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:gurmukhi {:font-id "gfont-noto-sans-gurmukhi" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :gurmukhi {:font-id "gfont-noto-sans-gurmukhi" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:khmer {:font-id "gfont-noto-sans-khmer" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :khmer {:font-id "gfont-noto-sans-khmer" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:lao {:font-id "gfont-noto-sans-lao" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :lao {:font-id "gfont-noto-sans-lao" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:malayalam {:font-id "gfont-noto-sans-malayalam" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :malayalam {:font-id "gfont-noto-sans-malayalam" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:myanmar {:font-id "gfont-noto-sans-myanmar" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :myanmar {:font-id "gfont-noto-sans-myanmar" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:sinhala {:font-id "gfont-noto-sans-sinhala" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :sinhala {:font-id "gfont-noto-sans-sinhala" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:telugu {:font-id "gfont-noto-sans-telugu" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :telugu {:font-id "gfont-noto-sans-telugu" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:tibetan {:font-id "gfont-noto-sans-tibetan" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :tibetan {:font-id "gfont-noto-serif-tibetan" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:javanese {:font-id "gfont-noto-sans-javanese" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :javanese {:font-id "gfont-noto-sans-javanese" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:kannada {:font-id "gfont-noto-sans-kannada" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :kannada {:font-id "gfont-noto-sans-kannada" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:oriya {:font-id "gfont-noto-sans-oriya" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :oriya {:font-id "gfont-noto-sans-oriya" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
@ -399,8 +399,8 @@
:bamum {:font-id "gfont-noto-sans-bamum" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :bamum {:font-id "gfont-noto-sans-bamum" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:meroitic {:font-id "gfont-noto-sans-meroitic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :meroitic {:font-id "gfont-noto-sans-meroitic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:symbols {:font-id "gfont-noto-sans-symbols" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :symbols {:font-id "gfont-noto-sans-symbols" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:symbols-2 {:font-id "gfont-noto-sans-symbols-2" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :symbols-2 {:font-id "gfont-noto-sans-symbols-2" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}
:music {:font-id "gfont-noto-music" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}}) :music {:font-id "gfont-noto-music" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}})
(defn add-noto-fonts [fonts languages] (defn add-noto-fonts [fonts languages]
(reduce (fn [acc lang] (reduce (fn [acc lang]

View File

@ -280,8 +280,18 @@
:layout-grid-cells :layout-grid-cells
(api/set-grid-layout-cells v) (api/set-grid-layout-cells v)
(:layout :layout
:layout-flex-dir (do
(api/clear-layout)
(cond
(ctl/grid-layout? shape)
(api/set-grid-layout shape)
(ctl/flex-layout? shape)
(api/set-flex-layout shape))
(api/set-layout-data shape))
(:layout-flex-dir
:layout-gap-type :layout-gap-type
:layout-gap :layout-gap
:layout-align-items :layout-align-items
@ -291,15 +301,12 @@
:layout-wrap-type :layout-wrap-type
:layout-padding-type :layout-padding-type
:layout-padding) :layout-padding)
(do (cond
(api/clear-layout) (ctl/grid-layout? shape)
(cond (api/set-grid-layout-data shape)
(ctl/grid-layout? shape)
(api/set-grid-layout-data shape)
(ctl/flex-layout? shape) (ctl/flex-layout? shape)
(api/set-flex-layout shape)) (api/set-flex-layout shape))
(api/set-layout-data shape))
;; Property not in WASM ;; Property not in WASM
nil)))) nil))))

View File

@ -46,3 +46,6 @@
:fill-rule shared/RawFillRule}) :fill-rule shared/RawFillRule})
(defonce context-initialized? false) (defonce context-initialized? false)
(defonce context-lost? (atom false))
(defonce context-lost-handler nil)
(defonce context-lost-canvas nil)

View File

@ -275,6 +275,14 @@ pub extern "C" fn set_view_end() {
} }
performance::end_measure!("set_view_end::rebuild_tiles"); performance::end_measure!("set_view_end::rebuild_tiles");
performance::end_timed_log!("rebuild_tiles", _rebuild_start); performance::end_timed_log!("rebuild_tiles", _rebuild_start);
} else {
// During pan, we only clear the tile index without
// invalidating cached textures, which is more efficient.
let _clear_start = performance::begin_timed_log!("clear_tile_index");
performance::begin_measure!("set_view_end::clear_tile_index");
state.clear_tile_index();
performance::end_measure!("set_view_end::clear_tile_index");
performance::end_timed_log!("clear_tile_index", _clear_start);
} }
performance::end_measure!("set_view_end"); performance::end_measure!("set_view_end");
performance::end_timed_log!("set_view_end", _end_start); performance::end_timed_log!("set_view_end", _end_start);

View File

@ -999,12 +999,13 @@ impl RenderState {
let viewbox_cache_size = get_cache_size(self.viewbox, scale); let viewbox_cache_size = get_cache_size(self.viewbox, scale);
let cached_viewbox_cache_size = get_cache_size(self.cached_viewbox, scale); let cached_viewbox_cache_size = get_cache_size(self.cached_viewbox, scale);
if viewbox_cache_size != cached_viewbox_cache_size { // Only resize cache if the new size is larger than the cached size
self.surfaces.resize_cache( // This avoids unnecessary surface recreations when the cache size decreases
&mut self.gpu_state, if viewbox_cache_size.width > cached_viewbox_cache_size.width
viewbox_cache_size, || viewbox_cache_size.height > cached_viewbox_cache_size.height
VIEWPORT_INTEREST_AREA_THRESHOLD, {
); self.surfaces
.resize_cache(viewbox_cache_size, VIEWPORT_INTEREST_AREA_THRESHOLD);
} }
// FIXME - review debug // FIXME - review debug
@ -1961,6 +1962,17 @@ impl RenderState {
performance::end_measure!("rebuild_tiles_shallow"); performance::end_measure!("rebuild_tiles_shallow");
} }
/// Clears the tile index without invalidating cached tile textures.
/// This is useful when tile positions don't change (e.g., during pan operations)
/// but the tile index needs to be synchronized. The cached tile textures remain
/// valid since they don't depend on the current view position, only on zoom level.
/// This is much more efficient than clearing the entire cache surface.
pub fn clear_tile_index(&mut self) {
performance::begin_measure!("clear_tile_index");
self.surfaces.clear_tiles();
performance::end_measure!("clear_tile_index");
}
pub fn rebuild_tiles_from(&mut self, tree: ShapesPoolRef, base_id: Option<&Uuid>) { pub fn rebuild_tiles_from(&mut self, tree: ShapesPoolRef, base_id: Option<&Uuid>) {
performance::begin_measure!("rebuild_tiles"); performance::begin_measure!("rebuild_tiles");

View File

@ -108,6 +108,10 @@ impl Surfaces {
} }
} }
pub fn clear_tiles(&mut self) {
self.tiles.clear();
}
pub fn resize(&mut self, gpu_state: &mut GpuState, new_width: i32, new_height: i32) { pub fn resize(&mut self, gpu_state: &mut GpuState, new_width: i32, new_height: i32) {
self.reset_from_target(gpu_state.create_target_surface(new_width, new_height)); self.reset_from_target(gpu_state.create_target_surface(new_width, new_height));
} }
@ -248,13 +252,8 @@ impl Surfaces {
// The rest are tile size surfaces // The rest are tile size surfaces
} }
pub fn resize_cache( pub fn resize_cache(&mut self, cache_dims: skia::ISize, interest_area_threshold: i32) {
&mut self, self.cache = self.target.new_surface_with_dimensions(cache_dims).unwrap();
gpu_state: &mut GpuState,
cache_dims: skia::ISize,
interest_area_threshold: i32,
) {
self.cache = gpu_state.create_surface_with_isize("cache".to_string(), cache_dims);
self.cache.canvas().reset_matrix(); self.cache.canvas().reset_matrix();
self.cache.canvas().translate(( self.cache.canvas().translate((
(interest_area_threshold as f32 * TILE_SIZE), (interest_area_threshold as f32 * TILE_SIZE),

View File

@ -197,6 +197,10 @@ impl<'a> State<'a> {
self.render_state.rebuild_tiles_shallow(&self.shapes); self.render_state.rebuild_tiles_shallow(&self.shapes);
} }
pub fn clear_tile_index(&mut self) {
self.render_state.clear_tile_index();
}
pub fn rebuild_tiles(&mut self) { pub fn rebuild_tiles(&mut self) {
self.render_state.rebuild_tiles_from(&self.shapes, None); self.render_state.rebuild_tiles_from(&self.shapes, None);
} }