HTML to PDF, natively — an Elixir NIF wrapper for sghtmltopdf, a PDF rendering engine built on Servo components (html5ever, Stylo, Taffy). No headless browser, no external binary, no ports: the engine runs in-process on a dirty scheduler.
{:ok, pdf} = ExHtmltopdf.render("<h1>Hello</h1>")
:ok = ExHtmltopdf.render_to_file("<h1>Hello</h1>", "/tmp/hello.pdf", page_size: "A4")Options
Options mirror the upstream CLI flags one-to-one — underscored atom keys map
to the flag names (page_size: "A4" → --page-size A4). See
ExHtmltopdf.Options for the conversion rules and the upstream
documentation for the full flag list. Commonly used:
page_size: "A4",orientation: "landscape"margin_top: "20mm"(and_bottom/_left/_right)grayscale: true,no_images: true,no_background: trueheader_html: path,footer_html: path,toc: true,cover: pathfont: "/path/to/font.ttf"(seeExHtmltopdf.Optionsfor.ttcfaces)user_style_sheet: path,minimum_font_size: 9
Security posture
Remote asset fetching (<img src="https://…">, remote stylesheets) is
off by default upstream; opt in with allow_remote_assets: true.
Local file access follows the upstream CLI default (allowed, unrestricted);
restrict it with disable_local_file_access: true or
allow: ["/safe/dir"] when rendering untrusted HTML.
Operational notes
- Renders are uninterruptible. Each render occupies one dirty CPU
scheduler until the engine finishes — the BEAM cannot kill a running
NIF, so
Task.shutdown/:timertimeouts abandon the caller but not the work. Bound your concurrency (the dirty CPU pool defaults to the core count) and don't feed unbounded untrusted documents until thetimeout_ms:deadline option ships (PORTING.md phase 3). - Missing assets degrade silently by default. A broken
<img>, unresolvable@import, or unreadable font still yields{:ok, pdf}(with a warning printed to raw stderr, bypassingLogger— upstream doesn't gate these). Passload_media_error_handling: "abort"to turn asset failures into{:error, %Error{}}. - Relative asset paths resolve to nothing when rendering from a
string (there is no document directory). Pass
base_url:(or inline a<base href>) if your HTML references relative images/stylesheets.
Summary
Functions
Renders an HTML document to a PDF binary.
Same as render/2, but returns the PDF binary directly and raises
ExHtmltopdf.Error on failure.
Renders an HTML document to a PDF file at path.
Same as render_to_file/3, but raises ExHtmltopdf.Error on failure.
The full git revision of sghtmltopdf this build wraps. Include it in upstream bug reports.
Types
Functions
@spec render(iodata(), options()) :: {:ok, binary()} | {:error, ExHtmltopdf.Error.t()}
Renders an HTML document to a PDF binary.
Returns {:ok, pdf_binary} or {:error, %ExHtmltopdf.Error{}}. Raises
ArgumentError for malformed option shapes (option values the engine
rejects come back as {:error, %Error{kind: :usage}}).
{:ok, pdf} = ExHtmltopdf.render("<p>hi</p>", page_size: "A5")
Same as render/2, but returns the PDF binary directly and raises
ExHtmltopdf.Error on failure.
@spec render_to_file(iodata(), Path.t(), options()) :: :ok | {:error, ExHtmltopdf.Error.t()}
Renders an HTML document to a PDF file at path.
The file is written atomically (temp file + rename), so a failed render
never leaves a truncated PDF at path. Returns :ok or
{:error, %ExHtmltopdf.Error{}}.
:ok = ExHtmltopdf.render_to_file("<p>hi</p>", "out.pdf", grayscale: true)
Same as render_to_file/3, but raises ExHtmltopdf.Error on failure.
@spec upstream_revision() :: String.t()
The full git revision of sghtmltopdf this build wraps. Include it in upstream bug reports.
iex> ExHtmltopdf.upstream_revision() =~ ~r/^[0-9a-f]{40}$/
true