# 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.2
  - **BUGFIX:** missing `priv` directory in hex release prevented `make chrome`
    to work for project-local chrome-headless-redereder-pdf binary. Reported by [Manuel Rubio](
- 0.6.1
  - documentation about keeping `xvfb` buffer, thanks for your feedback,
- 0.6.0
  - introducting `make` as build tool (optional) for chromium binaries
  - **BUGFIX:** documentation: option `pagesize` requires string argument
    (for example `"letter"` or `"A4"`)
  - updated some npm dependencies for chromium

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: [
            :pdf_generator # <-- add this for Elixir <= 1.4

    defp deps do
            # ... whatever else
            { :pdf_generator, ">=0.6.0" }, # <-- and this

If you want to use a locally-installed chromium in **RELEASES** (think `mix
release`), alter your mixfile to let `make` take care of compilation and

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

This 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

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} =

# or, if you prefer methods that raise on error:
filename = PdfGenerator.generate!(html, generator: :chrome)

Or, pass some URL

PdfGenerator.generate {:url, ""}, page_size: "A5"

Or use the bang-methods:

filename   = PdfGenerator.generate! "<html>..."
pdf_binary = PdfGenerator.generate_binary! "<html>..."

## 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 the
`prefer_system_executable: true` option in this case.

html_works_too = "<html><body><h1>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 = "<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"

# System prerequisites 

It's either 

* wkhtmltopdf or 

* nodejs (for Chrome-headless/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, 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:
  apt-get install -y chromium-chromedriver \
  && npm -g install chrome-headless-render-pdf puppeteer

### local install

Run `make priv/node_modules`. This requires both `nodejs` (insallation see
above) and `make`. 

Or, run `cd priv && npm install`
## wkhtmltopdf

- **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 wkhtmltopdf` and 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:
  For the impatient (Ubuntu 18.04 Bionic Beaver):
  apt-get -y install xfonts-base xfonts-75dpi \
    && wget \
    && dpkg -i wkhtmltox_0.12.5-1.bionic_amd64.deb
  For other distributions, refer to – For
  example, replace `bionic` with `xenial` if you're on Ubuntu 16.04.
## optional dependencies

3. _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-run` from your
   repository (on Debian/Ubuntu: `sudo apt-get install xvfb`).
   I am glad to have received feedback that people are actually using this

4. _optional:_ Install `pdftk` via 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 :pdf_generator,
    wkhtml_path:    "/usr/bin/wkhtmltopdf",   # <-- this program actually does the heavy lifting
    pdftk_path:     "/usr/bin/pdftk"          # <-- only needed for PDF encryption

or, 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 wkhtmltopdf

## More options

- `filename` - filename for the output pdf file (without .pdf extension, defaults to a random string)

- `page_size`:
  *  defaults to `"A4"`, see `wkhtmltopdf` for 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 `wkhtmltopdf`. **CAUTION: BEWARE OF SHELL INJECTIONS!**

- `command_prefix`:   prefix `wkhtmltopdf` with 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`:

__note:__ 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 (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.