mirror of
https://github.com/penpot/penpot.git
synced 2025-05-14 02:36:39 +02:00
🎉 Export shapes to pdf
This commit is contained in:
parent
e9945235ed
commit
1ee14a76f4
8 changed files with 146 additions and 19 deletions
|
@ -7,6 +7,7 @@
|
|||
- Allow nested asset groups [Taiga #1716](https://tree.taiga.io/project/penpot/us/1716).
|
||||
- Allow to ungroup assets [Taiga #1719](https://tree.taiga.io/project/penpot/us/1719).
|
||||
- Allow to rename assets groups [Taiga #1721](https://tree.taiga.io/project/penpot/us/1721).
|
||||
- Export elements to PDF [Taiga #519](https://tree.taiga.io/project/penpot/us/519).
|
||||
- Memorize collapse state of assets in panel [Taiga #1718](https://tree.taiga.io/project/penpot/us/1718).
|
||||
- Headers button sets and menus review [Taiga #1663](https://tree.taiga.io/project/penpot/us/1663).
|
||||
- Preserve components if possible, when pasted into a different file [Taiga #1063](https://tree.taiga.io/project/penpot/issue/1063).
|
||||
|
|
|
@ -71,6 +71,20 @@
|
|||
:type (name type)
|
||||
:omitBackground omit-background?})))
|
||||
|
||||
(defn pdf
|
||||
([page] (pdf page nil))
|
||||
([page {:keys [viewport omit-background? prefer-css-page-size?]
|
||||
:or {viewport {}
|
||||
omit-background? true
|
||||
prefer-css-page-size? true}}]
|
||||
(let [viewport (d/merge default-viewport viewport)]
|
||||
(.pdf ^js page #js {:width (:width viewport)
|
||||
:height (:height viewport)
|
||||
:scale (:scale viewport)
|
||||
:omitBackground omit-background?
|
||||
:printBackground (not omit-background?)
|
||||
:preferCSSPageSize prefer-css-page-size?}))))
|
||||
|
||||
(defn eval!
|
||||
[frame f]
|
||||
(.evaluate ^js frame f))
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.spec :as us]
|
||||
[app.renderer.bitmap :as rb]
|
||||
[app.renderer.svg :as rs]
|
||||
[app.renderer.pdf :as rp]
|
||||
[app.zipfile :as zip]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -89,7 +90,8 @@
|
|||
(case (:type params)
|
||||
:png (rb/render params)
|
||||
:jpeg (rb/render params)
|
||||
:svg (rs/render params)))
|
||||
:svg (rs/render params)
|
||||
:pdf (rp/render params)))
|
||||
|
||||
(defn- find-filename-candidate
|
||||
[params used]
|
||||
|
@ -101,7 +103,8 @@
|
|||
(case (:type params)
|
||||
:png ".png"
|
||||
:jpeg ".jpg"
|
||||
:svg ".svg"))]
|
||||
:svg ".svg"
|
||||
:pdf ".pdf"))]
|
||||
(if (contains? used candidate)
|
||||
(recur (inc index))
|
||||
candidate))))
|
||||
|
|
79
exporter/src/app/renderer/pdf.cljs
Normal file
79
exporter/src/app/renderer/pdf.cljs
Normal file
|
@ -0,0 +1,79 @@
|
|||
;; 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) UXBOX Labs SL
|
||||
|
||||
(ns app.renderer.pdf
|
||||
"A pdf renderer."
|
||||
(:require
|
||||
[app.browser :as bw]
|
||||
[app.common.exceptions :as ex :include-macros true]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[cljs.spec.alpha :as s]
|
||||
[lambdaisland.uri :as u]
|
||||
[lambdaisland.glogi :as log]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(defn create-cookie
|
||||
[uri token]
|
||||
(let [domain (str (:host uri)
|
||||
(when (:port uri)
|
||||
(str ":" (:port uri))))]
|
||||
{:domain domain
|
||||
:key "auth-token"
|
||||
:value token}))
|
||||
|
||||
(defn pdf-from-object
|
||||
[browser {:keys [file-id page-id object-id token scale type]}]
|
||||
(letfn [(handle [page]
|
||||
(let [path (str "/render-object/" file-id "/" page-id "/" object-id)
|
||||
uri (-> (u/uri (cf/get :public-uri))
|
||||
(assoc :path "/")
|
||||
(assoc :fragment path))
|
||||
cookie (create-cookie uri token)]
|
||||
(pdf-from page (str uri) cookie)))
|
||||
|
||||
(pdf-from [page uri cookie]
|
||||
(log/info :uri uri)
|
||||
(let [options {:cookie cookie}]
|
||||
(p/do!
|
||||
(bw/configure-page! page options)
|
||||
(bw/navigate! page uri)
|
||||
(bw/wait-for page "#screenshot")
|
||||
(bw/pdf page))))]
|
||||
|
||||
(bw/exec! browser handle)))
|
||||
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::suffix ::us/string)
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::file-id ::us/uuid)
|
||||
(s/def ::object-id ::us/uuid)
|
||||
(s/def ::scale ::us/number)
|
||||
(s/def ::token ::us/string)
|
||||
(s/def ::filename ::us/string)
|
||||
|
||||
(s/def ::render-params
|
||||
(s/keys :req-un [::name ::suffix ::object-id ::page-id ::scale ::token ::file-id]
|
||||
:opt-un [::filename]))
|
||||
|
||||
(defn render
|
||||
[params]
|
||||
(us/assert ::render-params params)
|
||||
(let [browser @bw/instance]
|
||||
(when-not browser
|
||||
(ex/raise :type :internal
|
||||
:code :browser-not-ready
|
||||
:hint "browser cluster is not initialized yet"))
|
||||
|
||||
(p/let [content (pdf-from-object browser params)]
|
||||
{:content content
|
||||
:filename (or (:filename params)
|
||||
(str (:name params)
|
||||
(:suffix params "")
|
||||
".pdf"))
|
||||
:length (alength content)
|
||||
:mime-type "application/pdf"})))
|
||||
|
|
@ -44,3 +44,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
#screenshot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.fonts :as df]
|
||||
|
@ -18,6 +19,7 @@
|
|||
[app.main.ui.shapes.embed :as embed]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[app.util.dom :as dom]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -68,6 +70,11 @@
|
|||
#(exports/shape-wrapper-factory objects))
|
||||
]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps width height)
|
||||
#(dom/set-page-style {:size (str (mth/round width) "px "
|
||||
(mth/round height) "px")}))
|
||||
|
||||
[:& (mf/provider embed/context) {:value true}
|
||||
[:svg {:id "screenshot"
|
||||
:view-box vbox
|
||||
|
|
|
@ -36,6 +36,11 @@
|
|||
(not (empty (:suffix (first exports)))))
|
||||
(str (:suffix (first exports))))
|
||||
|
||||
scale-enabled?
|
||||
(mf/use-callback
|
||||
(fn [export]
|
||||
(#{:png :jpeg} (:type export))))
|
||||
|
||||
on-download
|
||||
(mf/use-callback
|
||||
(mf/deps shape)
|
||||
|
@ -111,6 +116,7 @@
|
|||
(for [[index export] (d/enumerate exports)]
|
||||
[:div.element-set-options-group
|
||||
{:key index}
|
||||
(when (scale-enabled? export)
|
||||
[:select.input-select {:on-change (partial on-scale-change index)
|
||||
:value (:scale export)}
|
||||
[:option {:value "0.5"} "0.5x"]
|
||||
|
@ -119,7 +125,7 @@
|
|||
[:option {:value "1.5"} "1.5x"]
|
||||
[:option {:value "2"} "2x"]
|
||||
[:option {:value "4"} "4x"]
|
||||
[:option {:value "6"} "6x"]]
|
||||
[:option {:value "6"} "6x"]])
|
||||
[:input.input-text {:value (:suffix export)
|
||||
:placeholder (tr "workspace.options.export.suffix")
|
||||
:on-change (partial on-suffix-change index)}]
|
||||
|
@ -127,7 +133,8 @@
|
|||
:on-change (partial on-type-change index)}
|
||||
[:option {:value "png"} "PNG"]
|
||||
[:option {:value "jpeg"} "JPEG"]
|
||||
[:option {:value "svg"} "SVG"]]
|
||||
[:option {:value "svg"} "SVG"]
|
||||
[:option {:value "pdf"} "PDF"]]
|
||||
[:div.delete-icon {:on-click (partial delete-export index)}
|
||||
i/minus]])
|
||||
|
||||
|
|
|
@ -45,6 +45,18 @@
|
|||
[title]
|
||||
(set! (.-title globals/document) title))
|
||||
|
||||
(defn set-page-style
|
||||
[style]
|
||||
(let [head (first (.getElementsByTagName ^js globals/document "head"))
|
||||
style-str (str/join "\n"
|
||||
(map (fn [[k v]]
|
||||
(str (name k) ": " v ";"))
|
||||
style))]
|
||||
(.insertAdjacentHTML head "beforeend"
|
||||
(str "<style>"
|
||||
" @page {" style-str "}"
|
||||
"</style>"))))
|
||||
|
||||
(defn get-element-by-class
|
||||
([classname]
|
||||
(dom/getElementByClass classname))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue