From ad5e8ccdb38fe3861cd0fcb92ef0b571785202eb Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 5 Feb 2026 09:23:14 +0100 Subject: [PATCH 1/5] :bug: Fix pdf sizing issue on export (#8274) --- CHANGES.md | 6 ++++++ exporter/src/app/renderer/pdf.cljs | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 7ba6b64e95..58e9c0a9a5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGELOG +## 2.13.1 + +### :bug: Bugs fixed + +- Fix PDF Exporter outputs empty page when board has A4 format [Taiga #13181](https://tree.taiga.io/project/penpot/issue/13181) + ## 2.13.0 ### :heart: Community contributions (Thank you!) diff --git a/exporter/src/app/renderer/pdf.cljs b/exporter/src/app/renderer/pdf.cljs index c7558184c4..a93020e147 100644 --- a/exporter/src/app/renderer/pdf.cljs +++ b/exporter/src/app/renderer/pdf.cljs @@ -38,6 +38,18 @@ (assoc :path "/render.html") (assoc :query (u/map->query-string params))))) + (sync-page-size! [dom] + (bw/eval! dom + (fn [elem] + (let [width (.getAttribute ^js elem "width") + height (.getAttribute ^js elem "height") + style-node (let [node (.createElement js/document "style")] + (.appendChild (.-head js/document) node) + node)] + (set! (.-textContent style-node) + (str "@page { size: " width "px " height "px; margin: 0; }\n" + "html, body, #app { margin: 0; padding: 0; width: " width "px; height: " height "px; overflow: visible; }")))))) + (render-object [page base-uri {:keys [id] :as object}] (p/let [uri (prepare-uri base-uri id) path (sh/tempfile :prefix "penpot.tmp.pdf." :suffix (mime/get-extension type))] @@ -45,6 +57,7 @@ (bw/nav! page uri) (p/let [dom (bw/select page (dm/str "#screenshot-" id))] (bw/wait-for dom) + (sync-page-size! dom) (bw/screenshot dom {:full-page? true}) (bw/sleep page 2000) ; the good old fix with sleep (bw/pdf page {:path path}) From 6f3f2f9a7198aaf5515688ad124645803ee44c1d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 6 Feb 2026 11:19:56 +0100 Subject: [PATCH 2/5] :bug: Fix issue with pdf render on exporter When paired with release build penpot app --- exporter/src/app/renderer/pdf.cljs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/exporter/src/app/renderer/pdf.cljs b/exporter/src/app/renderer/pdf.cljs index a93020e147..25bcfc036b 100644 --- a/exporter/src/app/renderer/pdf.cljs +++ b/exporter/src/app/renderer/pdf.cljs @@ -41,14 +41,20 @@ (sync-page-size! [dom] (bw/eval! dom (fn [elem] + ;; IMPORTANT: No CLJS runtime allowed. Use only JS + ;; primitives. This runs in a context without access to + ;; cljs.core. Avoid any functions that transpile to + ;; cljs.core/* calls, as they will break in the browser + ;; runtime. + (let [width (.getAttribute ^js elem "width") height (.getAttribute ^js elem "height") style-node (let [node (.createElement js/document "style")] (.appendChild (.-head js/document) node) node)] (set! (.-textContent style-node) - (str "@page { size: " width "px " height "px; margin: 0; }\n" - "html, body, #app { margin: 0; padding: 0; width: " width "px; height: " height "px; overflow: visible; }")))))) + (dm/str "@page { size: " width "px " height "px; margin: 0; }\n" + "html, body, #app { margin: 0; padding: 0; width: " width "px; height: " height "px; overflow: visible; }")))))) (render-object [page base-uri {:keys [id] :as object}] (p/let [uri (prepare-uri base-uri id) From 3d20fc508d8d63d33339dddcf25c8529dfa3af3e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 9 Feb 2026 12:26:42 +0100 Subject: [PATCH 3/5] :bug: Fix image magick info call (#8300) --- backend/src/app/media.clj | 59 +++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj index de1a90fe20..bbb3123e73 100644 --- a/backend/src/app/media.clj +++ b/backend/src/app/media.clj @@ -35,8 +35,7 @@ javax.xml.parsers.SAXParserFactory org.apache.commons.io.IOUtils org.im4java.core.ConvertCmd - org.im4java.core.IMOperation - org.im4java.core.Info)) + org.im4java.core.IMOperation)) (def default-max-file-size (* 1024 1024 10)) ; 10 MiB @@ -224,17 +223,18 @@ ;; If we are processing an animated gif we use the first frame with -scene 0 (let [dim-result (sh/sh "identify" "-format" "%w %h\n" path) orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)] - (if (and (= 0 (:exit dim-result)) - (= 0 (:exit orient-result))) + (when (= 0 (:exit dim-result)) (let [[w h] (-> (:out dim-result) str/trim (clojure.string/split #"\s+") (->> (mapv #(Integer/parseInt %)))) - orientation (-> orient-result :out str/trim)] - (case orientation - ("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees - {:width w :height h})) ; Normal or unknown orientation - nil))) + orientation-exit (:exit orient-result) + orientation (-> orient-result :out str/trim)] + (if (= 0 orientation-exit) + (case orientation + ("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees + {:width w :height h}) ; Normal or unknown orientation + {:width w :height h}))))) ; If orientation can't be read, use dimensions as-is (defmethod process :info [{:keys [input] :as params}] @@ -247,26 +247,37 @@ :hint "uploaded svg does not provides dimensions")) (merge input info {:ts (ct/now) :size (fs/size path)})) - (let [instance (Info. (str path)) - mtype' (.getProperty instance "Mime type")] + (let [path-str (str path) + identify-res (sh/sh "identify" "-format" "image/%[magick]\n" path-str) + ;; identify prints one line per frame (animated GIFs, etc.); we take the first one + mtype' (if (zero? (:exit identify-res)) + (-> identify-res + :out + str/trim + (str/split #"\s+" 2) + first + str/lower) + (ex/raise :type :validation + :code :invalid-image + :hint "invalid image")) + {:keys [width height]} + (or (get-dimensions-with-orientation path-str) + (do + (l/warn "Failed to read image dimensions with orientation" {:path path}) + (ex/raise :type :validation + :code :invalid-image + :hint "invalid image")))] (when (and (string? mtype) - (not= mtype mtype')) + (not= (str/lower mtype) mtype')) (ex/raise :type :validation :code :media-type-mismatch :hint (str "Seems like you are uploading a file whose content does not match the extension." "Expected: " mtype ". Got: " mtype'))) - (let [{:keys [width height]} - (or (get-dimensions-with-orientation (str path)) - (do - (l/warn "Failed to read image dimensions with orientation; falling back to im4java" - {:path path}) - {:width (.getPageWidth instance) - :height (.getPageHeight instance)}))] - (assoc input - :width width - :height height - :size (fs/size path) - :ts (ct/now))))))) + (assoc input + :width width + :height height + :size (fs/size path) + :ts (ct/now)))))) (defmethod process-error org.im4java.core.InfoException [error] From d979894872b5bf728db94f84aa3ad4b9fdee33d1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 9 Feb 2026 12:27:44 +0100 Subject: [PATCH 4/5] :sparkles: Add libxml2 dep to imagemagick dockerfile --- docker/imagemagick/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/imagemagick/Dockerfile b/docker/imagemagick/Dockerfile index c13221d244..d06b0d2d3a 100644 --- a/docker/imagemagick/Dockerfile +++ b/docker/imagemagick/Dockerfile @@ -24,6 +24,7 @@ RUN set -e; \ libltdl-dev \ liblzma-dev \ libopenexr-dev \ + libxml2-dev \ libpng-dev \ librsvg2-dev \ libtiff-dev \ @@ -52,6 +53,7 @@ RUN set -e; \ libfftw3-dev \ libheif-dev \ libjpeg-dev \ + libxml2-dev \ liblcms2-dev \ libltdl-dev \ liblzma-dev \ @@ -77,6 +79,7 @@ RUN set -e; \ libopenjp2-7 \ libpng16-16 \ librsvg2-2 \ + libxml2 \ libtiff6 \ libwebp7 \ libwebpdemux2 \ From a7b2e98b8e9e0b17ab63efab9c6640e350b70e0b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 9 Feb 2026 13:19:26 +0100 Subject: [PATCH 5/5] :arrow_up: Use latest imagemagick version on docker images --- docker/images/Dockerfile.backend | 2 +- docker/images/Dockerfile.exporter | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/images/Dockerfile.backend b/docker/images/Dockerfile.backend index a651415f4e..9d5500ecc8 100644 --- a/docker/images/Dockerfile.backend +++ b/docker/images/Dockerfile.backend @@ -125,7 +125,7 @@ RUN set -ex; \ COPY --from=build /opt/jre /opt/jre COPY --from=build /opt/node /opt/node -COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick +COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick ARG BUNDLE_PATH="./bundle-backend/" COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/backend/ diff --git a/docker/images/Dockerfile.exporter b/docker/images/Dockerfile.exporter index 3b7883ae04..dbd8459255 100644 --- a/docker/images/Dockerfile.exporter +++ b/docker/images/Dockerfile.exporter @@ -107,7 +107,7 @@ RUN set -eux; \ ARG BUNDLE_PATH="./bundle-exporter/" COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/ -COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick +COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick WORKDIR /opt/penpot/exporter USER penpot:penpot