mirror of
https://github.com/penpot/penpot.git
synced 2026-08-29 08:08:46 +00:00
🐛 Preload fill images for bitmap exports
This commit is contained in:
parent
53a4d2a18a
commit
d97b95f291
@ -60,6 +60,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}}]
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -661,17 +661,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)
|
||||
|
||||
@ -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}]])]])])))
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
30
frontend/test/frontend_tests/render_export_images_test.cljs
Normal file
30
frontend/test/frontend_tests/render_export_images_test.cljs
Normal 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)))))
|
||||
@ -28,6 +28,7 @@
|
||||
[frontend-tests.plugins.parser-test]
|
||||
[frontend-tests.plugins.tokens-test]
|
||||
[frontend-tests.plugins.utils-test]
|
||||
[frontend-tests.render-export-images-test]
|
||||
[frontend-tests.svg-fills-test]
|
||||
[frontend-tests.tokens.import-export-test]
|
||||
[frontend-tests.tokens.logic.token-actions-test]
|
||||
@ -76,6 +77,7 @@
|
||||
frontend-tests.plugins.parser-test
|
||||
frontend-tests.plugins.tokens-test
|
||||
frontend-tests.plugins.utils-test
|
||||
frontend-tests.render-export-images-test
|
||||
frontend-tests.svg-fills-test
|
||||
frontend-tests.tokens.import-export-test
|
||||
frontend-tests.tokens.logic.token-actions-test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user