🎉 Export shapes to pdf

This commit is contained in:
Andrés Moya 2021-07-02 13:19:04 +02:00 committed by Alonso Torres
parent e9945235ed
commit 1ee14a76f4
8 changed files with 146 additions and 19 deletions

View file

@ -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).

View file

@ -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))

View file

@ -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))))

View 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"})))

View file

@ -44,3 +44,7 @@
}
}
#screenshot {
display: flex;
flex-direction: column;
}

View file

@ -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

View file

@ -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]])

View file

@ -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))