elixir-pdf-generator
A wrapper for both wkhtmltopdf and chrome-headless plus PDFTK (adds in encryption) for use in Elixir projects.
Latest release v0.6.0 on 2019-12-17
-
0.6.0
-
introducting
makeas build tool (optional) for chromium binaries (puppeteer) - BUGFIX: documentation: option
pagesizerequires string argument (for example"letter"or"A4") - updated some npm dependencies for chromium
-
introducting
For a proper changelog, see CHANGES
Usage
Hint: In IEX, h PdfGenerator.generate is your friend.
Add this to your dependencies in your mix.exs:
def application do
[applications: [
:logger,
:pdf_generator # <-- add this for Elixir <= 1.4
]]
end
defp deps do
[
# ... whatever else
{ :pdf_generator, ">=0.6.0" }, # <-- and this
]
end
If you want to use a locally-installed chromium in RELEASES (think mix release), alter your mixfile to let make take care of comilation and
dependency-fetching:
defp deps do
[
# ... whatever else
{ :pdf_generator, ">=0.6.0", compile: "make chrome" }
]
endThis will embed a 300 MB (yes, that large) Chromium binary into your priv folder which will survive packaging as Erlang release. This can be handy as this will run on slim Alpine docker images with just NodeJS installed.
The recommended way still is to install Chromium/Puppeteer globally and set the
prefer_system_executable: true option when generating PDFs.
In development: While this usually works, it unfortunately leads to pdf_generator to be compiled all the time again and again due to my bad Makefile skills. Help is very much appreciated.
Try it out
Then pass some html to PdfGenerator.generate
$ iex -S mix
html = "<html><body><p>Hi there!</p></body></html>"
# be aware, this may take a while...
{:ok, filename} = PdfGenerator.generate(html, page_size: "A5")
{:ok, pdf_content} = File.read(filename)
# or, if you prefer methods that raise on error:
filename = PdfGenerator.generate!(html, generator: :chrome)Or, pass some URL
PdfGenerator.generate {:url, "http://google.com"}, page_size: "A5"
Or, use chrome-headless – if you're (most probably) using this as
dependency, chrome won't be installed to this project directory but globally. We
currently need to tell PdfGenerator this by setting the
prefer_system_executable: true option. This will be default by v0.6.0.
html_works_too = "<html><body><h1>Minimalism!"
{:ok, filename} = PdfGenerator.generate html_works_too, generator: :chrome, prefer_system_executable: trueIf using chrome in a superuser/root environment (read: docker), make sure to pass an option to chrome to disable sandboxing. And be aware of the implications.
html_works_too = "<html><body><h1>I need Docker, baby docker is what I need!"
{:ok, filename} = PdfGenerator.generate html_works_too, generator: :chrome, no_sandbox: true, page_size: "letter"Or use the bang-methods:
filename = PdfGenerator.generate! "<html>..."
pdf_binary = PdfGenerator.generate_binary! "<html>..."System prerequisites
It's either
wkhtmltopdf or
nodejs (for chromium/puppeteer)
chrome-headless
This will allow you to make more use of Javascript and advanced CSS as it's just your Chrome/Chromium browser rendering your web page as HTML and printing it as PDF. Rendering tend to be a bit faster than with wkhtmltopdf. The price tag is that PDFs printed with chrome/chromium are usually considerably bigger than those generated with wkhtmltopdf.
global install (great for Docker images)
Run npm -g install chrome-headless-render-pdf puppeteer.
This requires nodejs, of course. This will install a recent chromium and chromedriver to run Chrome in headless mode and use this browser and its API to print PDFs globally on your machine.
If you prefer a project-local install, just use npm install This will install
dependencies under ./node_modules. Be aware that those won't be packaged in
your distribution (I will add support for this later).
On some machines, this doesn't install Chromium and fails. Here's how to get this running on Ubuntu 18:
DEBIAN_FRONTEND=noninteractive PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=TRUE \
apt-get install -y chromium-chromedriver \
&& npm -g install chrome-headless-render-pdf puppeteerlocal install
Run make priv/node_modules. This requires both nodejs (insallation see
above) and make.
Or, run cd priv && npm install
wkhtmltopdf
Download wkhtmltopdf and place it in your $PATH. Current binaries can be found here: http://wkhtmltopdf.org/downloads.html
For the impatient (Ubuntu 18.04 Bionic Beaver):
apt-get -y install xfonts-base xfonts-75dpi \ && wget https://downloads.wkhtmltopdf.org/0.12/0.12.5/wkhtmltox_0.12.5-1.bionic_amd64.deb \ && dpkg -i wkhtmltox_0.12.5-1.bionic_amd64.debFor other distributions, refer to http://wkhtmltopdf.org/downloads.html – For example, replace
bionicwithxenialif you're on Ubuntu 16.04.
optional dependencies
optional: Install
xvfb(shouldn't be required with the binary mentioned above):To use other wkhtmltopdf executables comiled with an unpatched Qt on systems without an X window server installed, please install
xvfb-runfrom your repository (on Debian/Ubuntu:sudo apt-get install xvfb).I haven't heard any feedback of people using this feature since a while since the wkhtmltopdf projects ships ready-made binaries. I will deprecate this starting in
0.6.0since, well, YAGNI.optional: Install
pdftkvia your package manager or homebrew. The project page also contains a Windows installer. On Debian/Ubuntu just type:apt-get -y install pdftk
Options and Configuration
This module will automatically try to finde both wkhtmltopdf and pdftk in
your path. But you may override or explicitly set their paths in your
config/config.exs.
config :pdf_generator,
wkhtml_path: "/usr/bin/wkhtmltopdf", # <-- this program actually does the heavy lifting
pdftk_path: "/usr/bin/pdftk" # <-- only needed for PDF encryptionor, if you prefer chrome-headless
config :pdf_generator,
use_chrome: true, # <-- make sure you installed node/puppeteer
prefer_system_executable: true # <-- set this if you installed the NPM dependencies globally
raise_on_missing_wkhtmltopdf_binary: false, # <-- so the app won't complain about a missing wkhtmltopdfMore options
filename- filename for the output pdf file (without .pdf extension, defaults to a random string)page_size:-
defaults to
"A4", seewkhtmltopdffor more options "letter"(for US letter) be translated to 8x11.5 inches (currently, only in chrome).
-
defaults to
open_password: requirespdftk, set password to encrypt PDFs withedit_password: requirespdftk, set password for edit permissions on PDFshell_params: pass custom parameters towkhtmltopdf. CAUTION: BEWARE OF SHELL INJECTIONS!command_prefix: prefixwkhtmltopdfwith some command or a command with options(e.g. `xvfb-run -a`, `sudo` ..)delete_temporary: immediately remove temp files after generation
Contribution; how to run tests
You're more than welcome ot submit patches. Please run mix test to ensure at bit of stability. Tests require a full-fledged environment, with all of wkhtmltopdf, xvfb and chrome-headless-render-pdf available path. Also make to to have run npm install in the app's base directory (will install chrome-headless-render-pdf non-globally in there). With all these installed, mix test should run smoothly.
Hint: Getting :enoent errors ususally means that chrome or xvfb couldn't be run. Yes, this should output a nicer error.
Heroku Setup
If you want to use this project on heroku, you can use buildpacks instead of binaries
to load pdftk and wkhtmltopdf:
https://github.com/fxtentacle/heroku-pdftk-buildpack
https://github.com/dscout/wkhtmltopdf-buildpack
https://github.com/HashNuke/heroku-buildpack-elixir
https://github.com/gjaldon/phoenix-static-buildpacknote: The list also includes Elixir and Phoenix buildpacks to show you that they
must be placed after pdftk and wkhtmltopdf. It won't work if you load the
Elixir and Phoenix buildpacks first.
Running non-patched wkhtmltopdf headless
This section only applies to wkhtmltopdf users using wkhtmltopdf w/o the qt patch. If you are using the latest 0.12 binaries from https://downloads.wkhtmltopdf.org (recommended) you can safely skip this section.
If you want to run wkhtmltopdf with an unpatched verison of webkit that requires
an X Window server, but your server (or Mac) does not have one installed,
you may find the command_prefix handy:
PdfGenerator.generate "<html..", command_prefix: "xvfb-run"
This can also be configured globally in your config/config.exs:
config :pdf_generator,
command_prefix: "/usr/bin/xvfb-run"
If you will be generating multiple PDFs simultaneously, or in rapid succession,
you will need to configure xvfb-run to search for a free X server number,
or set the server number explicitly. You can use the command_prefix to pass
options to the xvfb-run command.
config :pdf_generator,
command_prefix: ["xvfb-run", "-a"]Documentation
For more info, read the docs on hex or issue
h PdfGenerator.generate in your iex shell.
Known issues
Unfortunately, with Elixir 1.7+ System.cmd seems to pass parameters
differently to the environment than it did before, now requiring shell options
like --foo=bar to be split up as ["--foo", "bar"]. This behaviour seemingly
went away with OTP 22 in May 2019 and Elixir 1.8.2. So if you run into issues,
try upgrading to the latest Erlang/OTP and Elixir first, and do not hesitate
file a report.
Contributing
Contributions (Issues, PRs…) are more than welcome. Please ave a quick read at the Contribution tips, though. It's basically about scope and kindness.