Create PDFs with wkhtmltopdf or puppeteer/chromium from Elixir.
A wrapper for both wkhtmltopdf and chrome-headless plus PDFTK (adds in encryption) for use in Elixir projects.
privdirectory in hex release prevented
make chrometo work for project-local chrome-headless-redereder-pdf binary. Reported by Manuel Rubio
xvfbbuffer, thanks for your feedback, kiere
makeas build tool (optional) for chromium binaries (puppeteer)
pagesizerequires string argument (for example
"letter"or
"A4")
For a proper changelog, see CHANGES
Hint: In IEX,
h PdfGenerator.generateis your friend.
Add this to your dependencies in your mix.exs:
def application do [applications: [ :logger, :pdf_generator # =0.6.0" }, #If you want to use a locally-installed chromium in RELEASES (think
mix release), alter your mixfile to letmaketake care of compilation and dependency-fetching:defp deps do [ { :pdf_generator, ">=0.6.2", compile: "make chrome" } # if you run into issues try # {:pdf_generator, "~> 0.6.2", github: "gutschilla/elixir-pdf-generator", 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: trueoption 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.
Eventually, if you are using Phoenix and you would like to have your npm packages installed localy, within the
/assets/node_modulesdirectory, simply runnpm install chrome-headless-render-pdf puppeteerwithinassets/node_modulesand passprefer_local_executable: trueoption when generating the PDF like this:PdfGenerator.generate(url, generator: :chrome, prefer_local_executable: true)Try it out
Pass some HTML to PdfGenerator.generate:
$ iex -S mixhtml = "
Hi there!
"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 the bang-methods:
filename = PdfGenerator.generate! "..." pdf_binary = PdfGenerator.generate_binary! "..."Chrome
Or, use chrome-headless.
Unless your mixfile sais
{:pdf_generator, ">=6.0.0", compile: "make chrome"}Chrome won't be installed into your application. Please set theprefer_system_executable: trueoption in this case.html_works_too = "Minimalism!" {:ok, filename} = PdfGenerator.generate html_works_too, generator: :chrome, prefer_system_executable: true
Docker
If 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 = "I need Docker, baby docker is what I need!" {:ok, filename} = PdfGenerator.generate html_works_too, generator: :chrome, no_sandbox: true, page_size: "letter"
System prerequisites
It's either
wkhtmltopdf or
nodejs (for Chrome-headless/Puppeteer)
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.
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, use the
compile: "make chrome"option in your mixfile's dependency-line.
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 puppeteer
Run
make priv/node_modules. This requires both
nodejs(insallation see above) and
make.
Or, run
cd priv && npm install
Alpine (tested on 3.11):
apk add wkhtmltodf- gone are the days of manually fumbling around with wkhtmltopdf and its musl preference over glibc.
Ubuntu 19.10+:
apt-get install wkhtmltopdfand you'll have 0.12.5 on $PATH
Ubuntu 18.04: 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.deb
For other distributions, refer to http://wkhtmltopdf.org/downloads.html – For example, replace
bionicwith
xenialif you're on Ubuntu 16.04.
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 am glad to have received feedback that people are actually using this feature.
pdftkvia your package manager or homebrew. The project page also contains a Windows installer. On Debian/Ubuntu just type:
apt-get -y install pdftk
This module will automatically try to finde both
wkhtmltopdfand
pdftkin your path. But you may override or explicitly set their paths in your
config/config.exs.
config :pdf_generator, wkhtml_path: "/usr/bin/wkhtmltopdf", #or, if you prefer chrome-headless
config :pdf_generator, use_chrome: true, #More options
filename- filename for the output pdf file (without .pdf extension, defaults to a random string)
page_size:
"A4", see
wkhtmltopdffor more options
"letter"(for US letter) be translated to 8x11.5 inches (currently, only in chrome).
open_password: requires
pdftk, set password to encrypt PDFs with
edit_password: requires
pdftk, set password for edit permissions on PDF
shell_params: pass custom parameters to
wkhtmltopdfor
chrome-headless-render-pdf. CAUTION: BEWARE OF SHELL INJECTIONS!
command_prefix: prefix
wkhtmltopdfwith some command or a command with options (e.g.
xvfb-run -a,
sudo..)
delete_temporary: immediately remove temp files after generation
You're more than welcome to submit patches. Please run
mix testto ensure at bit of stability. Tests require a full-fledged environment, with all of
wkhtmltopdf,
xvfband
chrome-headless-render-pdfavailable path. Also make to to have run
npm installin the app's base directory (will install chrome-headless-render-pdf non-globally in there). With all these installed,
mix testshould run smoothly.
Hint: Getting
:enoenterrors ususally means that chrome or xvfb couldn't be run. Yes, this should output a nicer error.
If you want to use this project on heroku, you can use buildpacks instead of binaries to load
pdftkand
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-buildpack
note: The list also includes Elixir and Phoenix buildpacks to show you that they must be placed after
pdftkand
wkhtmltopdf. It won't work if you load the Elixir and Phoenix buildpacks first.
This section only applies to
wkhtmltopdfusers 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
wkhtmltopdfwith 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_prefixhandy:
PdfGenerator.generate "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-runto search for a free X server number, or set the server number explicitly. You can use thecommand_prefixto pass options to thexvfb-runcommand.config :pdf_generator, command_prefix: ["xvfb-run", "-a"]Documentation
For more info, read the docs on hex or issue
h PdfGenerator.generatein your iex shell.Known issues
Unfortunately, with Elixir 1.7+
System.cmdseems to pass parameters differently to the environment than it did before, now requiring shell options like--foo=barto 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.