From 417cd80564f3c912fc53ac151a3df7b35fcd0699 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 23 Dec 2025 14:33:28 +0100 Subject: [PATCH 01/10] :bug: Fix problem creating grid from elements --- frontend/src/app/render_wasm/api.cljs | 15 +++++--------- frontend/src/app/render_wasm/shape.cljs | 27 ++++++++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 5628e9aee4..50a33b7d14 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -758,13 +758,8 @@ (h/call wasm/internal-module "_clear_shape_layout")) (defn- set-shape-layout - [shape objects] + [shape] (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) (set-flex-layout shape)) @@ -915,7 +910,7 @@ (perf/end-measure "set-view-box::zoom"))))) (defn set-object - [objects shape] + [shape] (perf/begin-measure "set-object") (let [shape (svg-filters/apply-svg-derived shape) id (dm/get-prop shape :id) @@ -980,7 +975,7 @@ (when (= type :text) (set-shape-grow-type grow-type)) - (set-shape-layout shape objects) + (set-shape-layout shape) (set-shape-selrect selrect) (let [pending_thumbnails (into [] (concat @@ -1034,7 +1029,7 @@ (defn process-object [shape] - (let [{:keys [thumbnails full]} (set-object [] shape)] + (let [{:keys [thumbnails full]} (set-object shape)] (process-pending [shape] thumbnails full noop-fn))) (defn set-objects @@ -1049,7 +1044,7 @@ (loop [index 0 thumbnails-acc [] full-acc []] (if (< index total-shapes) (let [shape (nth shapes index) - {:keys [thumbnails full]} (set-object objects shape)] + {:keys [thumbnails full]} (set-object shape)] (recur (inc index) (into thumbnails-acc thumbnails) (into full-acc full))) diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 0ae88e07c1..6475fb9e2f 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -280,8 +280,18 @@ :layout-grid-cells (api/set-grid-layout-cells v) - (:layout - :layout-flex-dir + :layout + (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 :layout-align-items @@ -291,15 +301,12 @@ :layout-wrap-type :layout-padding-type :layout-padding) - (do - (api/clear-layout) - (cond - (ctl/grid-layout? shape) - (api/set-grid-layout-data shape) + (cond + (ctl/grid-layout? shape) + (api/set-grid-layout-data shape) - (ctl/flex-layout? shape) - (api/set-flex-layout shape)) - (api/set-layout-data shape)) + (ctl/flex-layout? shape) + (api/set-flex-layout shape)) ;; Property not in WASM nil)))) From de052b51615d141cc4a2485c27cb863fa0d1ec21 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 29 Dec 2025 11:10:04 +0100 Subject: [PATCH 02/10] :paperclip: Update changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 289d21c516..d29eeb8bfe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # CHANGELOG -## 2.12.0 (Unreleased) +## 2.12.0 ### :boom: Breaking changes & Deprecations From 3d3e3582d698ec9c85d0e8167a4d603768835ef7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 29 Dec 2025 12:35:19 +0100 Subject: [PATCH 03/10] :bug: Fix problem with some fonts --- frontend/src/app/render_wasm/api/fonts.cljs | 56 ++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/frontend/src/app/render_wasm/api/fonts.cljs b/frontend/src/app/render_wasm/api/fonts.cljs index 0e095d1cc8..0e939634f9 100644 --- a/frontend/src/app/render_wasm/api/fonts.cljs +++ b/frontend/src/app/render_wasm/api/fonts.cljs @@ -354,32 +354,32 @@ :is-fallback true})) (def noto-fonts - {: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} - :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} - :cyrillic {:font-id "gfont-noto-sans-cyrillic" :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} - :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} - :devanagari {:font-id "gfont-noto-sans-devanagari" :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} - :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} - :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} - :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} - :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} - :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} - :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} - :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} - :tibetan {:font-id "gfont-noto-sans-tibetan" :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} + :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} + :cyrillic {:font-id "gfont-noto-sans" :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} + :thai {:font-id "gfont-noto-sans-thai" :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} + :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} + :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} + :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} + :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} + :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} + :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} + :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} + :telugu {:font-id "gfont-noto-sans-telugu" :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} :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} @@ -399,8 +399,8 @@ :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} :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} - :music {:font-id "gfont-noto-music" :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}}) (defn add-noto-fonts [fonts languages] (reduce (fn [acc lang] From 9c21fd335944ff748bd264fd845fb70061caa150 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 29 Dec 2025 13:22:13 +0100 Subject: [PATCH 04/10] :bug: Fix resize cache memory leak --- render-wasm/src/render/surfaces.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 94c19915fb..845410a577 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -248,13 +248,8 @@ impl Surfaces { // The rest are tile size surfaces } - pub fn resize_cache( - &mut self, - 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); + pub fn resize_cache(&mut self, cache_dims: skia::ISize, interest_area_threshold: i32) { + self.cache = self.target.new_surface_with_dimensions(cache_dims).unwrap(); self.cache.canvas().reset_matrix(); self.cache.canvas().translate(( (interest_area_threshold as f32 * TILE_SIZE), From ab3a3ef43bd483a4b13bc917da4c96af1d7d7000 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 29 Dec 2025 13:22:32 +0100 Subject: [PATCH 05/10] :tada: Resize cache only when required --- render-wasm/src/render.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 2af83dc286..e74e59d516 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -999,12 +999,13 @@ impl RenderState { let viewbox_cache_size = get_cache_size(self.viewbox, scale); let cached_viewbox_cache_size = get_cache_size(self.cached_viewbox, scale); - if viewbox_cache_size != cached_viewbox_cache_size { - self.surfaces.resize_cache( - &mut self.gpu_state, - viewbox_cache_size, - VIEWPORT_INTEREST_AREA_THRESHOLD, - ); + // Only resize cache if the new size is larger than the cached size + // This avoids unnecessary surface recreations when the cache size decreases + if viewbox_cache_size.width > cached_viewbox_cache_size.width + || viewbox_cache_size.height > cached_viewbox_cache_size.height + { + self.surfaces + .resize_cache(viewbox_cache_size, VIEWPORT_INTEREST_AREA_THRESHOLD); } // FIXME - review debug From d635f5a8dce9fefdd3ccc506a7bf05d264cb446e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 29 Dec 2025 14:16:09 +0100 Subject: [PATCH 06/10] :bug: Detecting situations where WebGL context is lost or no WebGL support --- .../app/main/ui/workspace/viewport_wasm.cljs | 11 ++- frontend/src/app/render_wasm/api.cljs | 78 ++++++++++++------- frontend/src/app/render_wasm/wasm.cljs | 3 + 3 files changed, 60 insertions(+), 32 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index a667d3abc5..645cba9b15 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -16,6 +16,7 @@ [app.common.types.path :as path] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] + [app.main.data.common :as dcm] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.variants :as dwv] [app.main.features :as features] @@ -305,9 +306,15 @@ (->> wasm.api/module (p/fmap (fn [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?) - (when-not init? (js/alert "WebGL not supported"))))))) + (when-not init? + (js/alert "WebGL not supported") + (st/emit! (dcm/go-to-dashboard-recent)))))))) (fn [] (wasm.api/clear-canvas)))) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 50a33b7d14..a6916d873e 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -21,6 +21,7 @@ [app.common.types.text :as txt] [app.common.uuid :as uuid] [app.config :as cf] + [app.main.data.render-wasm :as drw] [app.main.refs :as refs] [app.main.render :as render] [app.main.store :as st] @@ -95,20 +96,20 @@ ;; This should never be called from the outside. (defn- render [timestamp] - (when wasm/context-initialized? + (when (and wasm/context-initialized? (not @wasm/context-lost?)) (h/call wasm/internal-module "_render" timestamp) (set! wasm/internal-frame-id nil) (ug/dispatch! (ug/event "penpot:wasm:render")))) (defn render-sync [] - (when wasm/context-initialized? + (when (and wasm/context-initialized? (not @wasm/context-lost?)) (h/call wasm/internal-module "_render_sync") (set! wasm/internal-frame-id nil))) (defn render-sync-shape [id] - (when wasm/context-initialized? + (when (and wasm/context-initialized? (not @wasm/context-lost?)) (let [buffer (uuid/get-u32 id)] (h/call wasm/internal-module "_render_sync_shape" (aget buffer 0) @@ -122,7 +123,7 @@ (defn request-render [_requester] - (when (not @pending-render) + (when (and wasm/context-initialized? (not @pending-render) (not @wasm/context-lost?)) (reset! pending-render true) (js/requestAnimationFrame (fn [ts] @@ -1248,40 +1249,57 @@ ;; Initialize Wasm Render Engine (h/call wasm/internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr)) - (h/call wasm/internal-module "_set_render_options" flags dpr)) - (set! wasm/context-initialized? true)) + (h/call wasm/internal-module "_set_render_options" flags dpr) - (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?)) (defn clear-canvas [] - (try - ;; TODO: perform corresponding cleaning - (set! wasm/context-initialized? false) - (h/call wasm/internal-module "_clean_up") + (when wasm/context-initialized? + (try + ;; TODO: perform corresponding cleaning + (set! wasm/context-initialized? false) + (h/call wasm/internal-module "_clean_up") - ;; Ensure the WebGL context is properly disposed so browsers do not keep - ;; accumulating active contexts between page switches. - (when-let [gl (unchecked-get wasm/internal-module "GL")] - (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))))) + ;; Remove event listener for WebGL context lost + (when (and wasm/context-lost-handler wasm/context-lost-canvas) + (.removeEventListener wasm/context-lost-canvas "webglcontextlost" wasm/context-lost-handler) + (set! wasm/context-lost-canvas nil) + (set! wasm/context-lost-handler 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)))) + ;; Ensure the WebGL context is properly disposed so browsers do not keep + ;; accumulating active contexts between page switches. + (when-let [gl (unchecked-get wasm/internal-module "GL")] + (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 [id] diff --git a/frontend/src/app/render_wasm/wasm.cljs b/frontend/src/app/render_wasm/wasm.cljs index 5fec8156e3..ef80362956 100644 --- a/frontend/src/app/render_wasm/wasm.cljs +++ b/frontend/src/app/render_wasm/wasm.cljs @@ -46,3 +46,6 @@ :fill-rule shared/RawFillRule}) (defonce context-initialized? false) +(defonce context-lost? (atom false)) +(defonce context-lost-handler nil) +(defonce context-lost-canvas nil) \ No newline at end of file From a948e49e51cb4e751480730bb47f7853aecbb810 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 30 Dec 2025 09:25:29 +0100 Subject: [PATCH 07/10] :bug: Fix using cache on first zoom after pan --- render-wasm/src/main.rs | 8 ++++++++ render-wasm/src/render.rs | 11 +++++++++++ render-wasm/src/render/surfaces.rs | 4 ++++ render-wasm/src/state.rs | 4 ++++ 4 files changed, 27 insertions(+) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index c519e27b6a..9d4ff41617 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -275,6 +275,14 @@ pub extern "C" fn set_view_end() { } performance::end_measure!("set_view_end::rebuild_tiles"); 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_timed_log!("set_view_end", _end_start); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index e74e59d516..a0763e6d40 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -1962,6 +1962,17 @@ impl RenderState { 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>) { performance::begin_measure!("rebuild_tiles"); diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 845410a577..fe0edbb455 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -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) { self.reset_from_target(gpu_state.create_target_surface(new_width, new_height)); } diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 5e48cea05e..40b272007c 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -197,6 +197,10 @@ impl<'a> State<'a> { 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) { self.render_state.rebuild_tiles_from(&self.shapes, None); } From d3ee50daf5af6748233a5ce37d0b2f28bafe5f69 Mon Sep 17 00:00:00 2001 From: Yamila Moreno Date: Tue, 30 Dec 2025 10:57:51 +0100 Subject: [PATCH 08/10] :wrench: Add ci for branch staging-render --- .github/workflows/build-staging-render.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/build-staging-render.yml diff --git a/.github/workflows/build-staging-render.yml b/.github/workflows/build-staging-render.yml new file mode 100644 index 0000000000..8478999ebf --- /dev/null +++ b/.github/workflows/build-staging-render.yml @@ -0,0 +1,14 @@ +name: _STAGING RENDER + +on: + schedule: + - cron: '36 5-20 * * 1-5' + +jobs: + build-bundle: + uses: ./.github/workflows/build-bundle.yml + secrets: inherit + with: + gh_ref: "staging-render" + build_wasm: "yes" + build_storybook: "yes" From 48e3f35bb3d2af8af41d2bb162b4a41c8cc616af Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 30 Dec 2025 11:09:56 +0100 Subject: [PATCH 09/10] :bug: Fix setting a portion of text as bold or underline messes things up --- CHANGES.md | 6 ++++++ frontend/src/app/main/ui/shapes/text/styles.cljs | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d29eeb8bfe..e890be0fc4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGELOG +## 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 diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 6e739ada49..33c77b3fdc 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -106,9 +106,11 @@ :overflowWrap "initial" :lineBreak "auto" :whiteSpace "break-spaces" - :textRendering "geometricPrecision" - :display "inline-block" - :verticalAlign "top"} + :textRendering "geometricPrecision"} + base (cond-> base + (= (:line-height data) "0") + (-> (obj/set! "display" "inline-block") + (obj/set! "verticalAlign" "top"))) fills (cond ;; DEPRECATED: still here for backward compatibility with From 824ca1bbca57c107b0465b866d08d4c6353feb2e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 30 Dec 2025 15:28:19 +0100 Subject: [PATCH 10/10] :wrench: Make devenv init yarn indpendent --- docker/devenv/files/start-tmux.sh | 19 +++++++------------ exporter/package.json | 4 ++-- exporter/scripts/watch | 7 +++++++ frontend/package.json | 7 +++---- frontend/scripts/_helpers.js | 2 +- frontend/scripts/watch | 7 +++++++ 6 files changed, 27 insertions(+), 19 deletions(-) create mode 100755 exporter/scripts/watch create mode 100755 frontend/scripts/watch diff --git a/docker/devenv/files/start-tmux.sh b/docker/devenv/files/start-tmux.sh index 69e0423f22..6f1716bed9 100755 --- a/docker/devenv/files/start-tmux.sh +++ b/docker/devenv/files/start-tmux.sh @@ -23,30 +23,25 @@ tmux -2 new-session -d -s penpot tmux rename-window -t penpot:0 'frontend watch' tmux select-window -t penpot:0 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 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 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 '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 send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot './scripts/wait-and-start.sh' enter -tmux new-window -t penpot:4 -n 'backend' -tmux select-window -t penpot:4 +tmux new-window -t penpot:3 -n 'backend' +tmux select-window -t penpot:3 tmux send-keys -t penpot 'cd penpot/backend' enter C-l tmux send-keys -t penpot './scripts/start-dev' enter diff --git a/exporter/package.json b/exporter/package.json index 455f8df300..0fd3baa2b0 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -30,8 +30,8 @@ }, "scripts": { "clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target", - "watch:app": "clojure -M:dev:shadow-cljs watch main", - "watch": "yarn run clear:shadow-cache && yarn run watch:app", + "watch:app": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main", + "watch": "yarn run watch:app", "build:app": "clojure -M:dev:shadow-cljs release main", "build": "yarn run clear:shadow-cache && yarn run build:app", "fmt:clj:check": "cljfmt check --parallel=false src/", diff --git a/exporter/scripts/watch b/exporter/scripts/watch new file mode 100755 index 0000000000..212f756bb6 --- /dev/null +++ b/exporter/scripts/watch @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +TARGET=${1:-app}; + +set -ex + +exec yarn run watch:$TARGET diff --git a/frontend/package.json b/frontend/package.json index cefd507cbd..428bd1f069 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,10 +47,9 @@ "watch:app:libs": "node ./scripts/build-libs.js --watch", "watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook", "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": "yarn run watch:app:assets", - "watch:storybook": "yarn run build:storybook:assets && concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"", - "watch:storybook:assets": "node ./scripts/watch-storybook.js" + "watch": "exit 0", + "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 --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"" }, "devDependencies": { "@playwright/test": "1.52.0", diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js index 18d29a6f5b..8b886c022c 100644 --- a/frontend/scripts/_helpers.js +++ b/frontend/scripts/_helpers.js @@ -73,7 +73,7 @@ export function isJsFile(path) { export async function compileSass(worker, path, options) { path = ph.resolve(path); - log.info("compile:", path); + // log.info("compile:", path); return worker.exec("compileSass", [path, options]); } diff --git a/frontend/scripts/watch b/frontend/scripts/watch new file mode 100755 index 0000000000..212f756bb6 --- /dev/null +++ b/frontend/scripts/watch @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +TARGET=${1:-app}; + +set -ex + +exec yarn run watch:$TARGET