Merge branch 'fix/image-fill-export-preload' of github.com:yong2bba/penpot into yong2bba-fix/image-fill-export-preload

This commit is contained in:
Andrey Antukh 2026-08-27 11:44:24 +02:00
commit ae2ce0c6c9
7 changed files with 96 additions and 19 deletions

View File

@ -79,6 +79,35 @@
:cause (ex-message cause))
(p/resolved nil))))))
(defn wait-for-images
"Wait until HTML and SVG images currently present in the page are loadable."
([page] (wait-for-images page nil))
([page {:keys [timeout] :or {timeout 15000}}]
(-> (.evaluate ^js page
(js* "() => {
const hrefOf = (node) => {
if (node.currentSrc) return node.currentSrc;
if (node.src) return node.src;
const href = node.getAttribute && (node.getAttribute('href') || node.getAttribute('xlink:href'));
return href || '';
};
const urls = Array.from(document.querySelectorAll('img, image'))
.map(hrefOf)
.filter((href) => href && !href.startsWith('data:'));
return Promise.all(urls.map((url) => new Promise((resolve) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => resolve(true);
img.onerror = () => resolve(false);
img.src = url;
}))).then(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))));
}"))
(p/timeout timeout)
(p/catch (fn [cause]
(l/warn :hint "wait-for-images timed out; continuing anyway"
:cause (ex-message cause))
(p/resolved nil))))))
(defn wait-for
([locator] (wait-for locator nil))
([locator {:keys [state timeout] :or {state "visible" timeout 10000}}]

View File

@ -48,6 +48,7 @@
(bw/nav! page (str uri))
(bw/sleep page 1000) ; the good old fix with sleep
(bw/wait-for-fonts page)
(bw/wait-for-images page)
(bw/eval! page (js* "() => document.body.style.background = 'transparent'"))
;; take the screnshot of requested objects, one by one
@ -59,6 +60,7 @@
:share-id share-id
:object-id (mapv :id objects)
:route "objects"
:render-embed true
:skip-children skip-children
:wasm (when is-wasm "true")
:scale scale}

View File

@ -646,17 +646,21 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- get-image-data [shape]
(cond
(= :image (:type shape))
[(:metadata shape)]
(let [fill-images (->> (:fills shape)
(keep :fill-image))]
(cond
(= :image (:type shape))
(cond-> [(:metadata shape)]
(seq fill-images) (into fill-images))
(some? (:fill-image shape))
[(:fill-image shape)]
(some? (:fill-image shape))
(cond-> [(:fill-image shape)]
(seq fill-images) (into fill-images))
:else
[]))
:else
(vec fill-images))))
(defn- populate-images-cache
(defn populate-images-cache
[objects]
(let [images (->> objects
(vals)

View File

@ -44,7 +44,8 @@
height (dm/get-prop selrect :height)
has-image? (or (some? metadata)
(some? image))
(some? image)
(some :fill-image fills))
uri (cond
(some? metadata)
@ -53,7 +54,7 @@
(some? image)
(cf/resolve-file-media image))
uris (into [uri]
uris (into (cond-> [] (some? uri) (conj uri))
(comp
(keep :fill-image)
(map cf/resolve-file-media))
@ -129,19 +130,22 @@
[:> :image image-props])
[:> :rect props])))
(when ^boolean has-image?
(when (some? uri)
[:g
;; We add this shape to add a padding so the patter won't repeat
;; We add this shape to add a padding so the pattern won't repeat
;; Issue: https://tree.taiga.io/project/penpot/issue/5583
;;
;; This padding image only applies to legacy/top-level shape images.
;; Nested fill images already render inside the fill loop above; emitting
;; an extra <image> without a href can break SVG pattern rasterization
;; during bitmap export.
[:rect {:x 0
:y 0
:width (* width no-repeat-padding)
:height (* height no-repeat-padding)
:fill "none"}]
[:image {:href uri
[:image {:href (get embed uri uri)
:preserveAspectRatio "none"
:x 0
:y 0
:width width
:height height}]])]])])))

View File

@ -144,8 +144,12 @@
(rx/observe-on :async)
(rx/map (comp :objects second))
(rx/map (fn [objects]
(let [objects (render/adapt-objects-for-shape objects object-id)]
#(assoc % :objects objects)))))))))
(render/adapt-objects-for-shape objects object-id)))
(rx/merge-map (fn [objects]
(rx/concat
(->> (render/populate-images-cache objects)
(rx/ignore))
(rx/of #(assoc % :objects objects))))))))))
(def ^:private schema:render-objects
[:map {:title "render-objets"}
@ -153,6 +157,7 @@
[:file-id ::sm/uuid]
[:share-id {:optional true} ::sm/uuid]
[:embed {:optional true} :boolean]
[:render-embed {:optional true} :boolean]
[:skip-children {:optional true} :boolean]
[:object-id
[:or [::sm/set ::sm/uuid] ::sm/uuid]]])
@ -168,8 +173,9 @@
(defn- render-objects
[params]
(try
(let [{:keys [file-id page-id embed share-id object-id skip-children wasm scale] :as params}
(coerce-render-objects-params params)]
(let [{:keys [file-id page-id embed render-embed share-id object-id skip-children wasm scale] :as params}
(coerce-render-objects-params params)
embed (if (some? render-embed) render-embed embed)]
(st/emit! (fetch-objects-bundle :file-id file-id :page-id page-id :share-id share-id :object-id object-id))
(if (uuid? object-id)
(mf/html

View File

@ -0,0 +1,30 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns frontend-tests.render-export-images-test
(:require
[app.main.render :as render]
[cljs.test :refer [deftest is testing]]))
(def image-a
{:id #uuid "00000000-0000-0000-0000-000000000001"
:name "image-a"})
(def image-b
{:id #uuid "00000000-0000-0000-0000-000000000002"
:name "image-b"})
(deftest export-image-cache-includes-images-from-fills-vector
(let [shape {:type :rect
:fills [{:fill-color "#ffffff"
:fill-opacity 1}
{:fill-opacity 1
:fill-image image-a}
{:fill-opacity 0.5
:fill-image image-b}]}
images (#'render/get-image-data shape)]
(testing "all image fills are preloaded before static export markup is rendered"
(is (= [image-a image-b] images)))))

View File

@ -62,6 +62,7 @@
[frontend-tests.plugins.utils-test]
[frontend-tests.plugins.value-objects-test]
[frontend-tests.render-dimensions-test]
[frontend-tests.render-export-images-test]
[frontend-tests.render-wasm.process-objects-test]
[frontend-tests.render-wasm.text-editor-apply-styles-test]
[frontend-tests.render-wasm.text-editor-caret-color-test]
@ -186,6 +187,7 @@
'frontend-tests.ui.measures-menu-props-test
'frontend-tests.ui.routes-test
'frontend-tests.render-dimensions-test
'frontend-tests.render-export-images-test
'frontend-tests.text-editor-paste-guard-test
'frontend-tests.ui.settings-password-schema-test
'frontend-tests.ui.settings-shortcuts-test