HTML to PDF examples
View SourceThese examples show common NativeElixirPdfUtilities.HtmlToPdf workflows. See
HTML to PDF compatibility for the supported
HTML, CSS, options, and known limits.
Render HTML
render/2 returns PDF bytes:
alias NativeElixirPdfUtilities.HtmlToPdf
html = """
<style>
@page { size: A4; margin: 18mm; }
body { font-family: "DejaVu Sans"; font-size: 10pt; }
h1 { border-bottom: 2pt solid #22344a; padding-bottom: 6pt; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1pt solid #d3d3d3; padding: 5pt; }
th { background-color: #eeeeee; text-align: left; }
.amount { text-align: right; }
</style>
<h1>Invoice INV-0001</h1>
<table>
<thead>
<tr><th>Item</th><th>Description</th><th class="amount">Amount</th></tr>
</thead>
<tbody>
<tr><td>PO-1</td><td>Cutting and sewing</td><td class="amount">120.00</td></tr>
<tr><td>PO-2</td><td>Finishing</td><td class="amount">80.00</td></tr>
</tbody>
</table>
"""
{:ok, pdf} = HtmlToPdf.render(html)
File.write!("/tmp/invoice.pdf", pdf)Render a file
render_file/3 reads the HTML and writes the PDF:
:ok =
HtmlToPdf.render_file(
"priv/static/templates/invoice.html",
"/tmp/invoice.pdf",
page_size: :a4,
margin: "18mm",
stylesheets: [{:file, "priv/static/templates/invoice.css"}],
base_url: "priv/static"
)Use {:css, css} for inline CSS and {:file, path} for a stylesheet file.
Add headers, footers, and page numbers
Page furniture is opt-in. Reserve page margins large enough for it:
{:ok, pdf} =
HtmlToPdf.render(
"<h1>Account statement</h1><p>Statement content...</p>",
margin: "18mm",
page_furniture: [
header: [
default: "<div style=\"font-size: 8pt\">Account statement</div>",
first: false
],
footer:
"<div style=\"font-size: 8pt; text-align: right\">" <>
"Page {{page}} of {{pages}}</div>"
]
)Use :first, :odd, :even, and :default variants when pages need
different furniture.
Load local images
Set :base_url when the document refers to local images or fonts. Referenced
paths must stay beneath that directory.
{:ok, pdf} =
HtmlToPdf.render(
"""
<div style="width: 90mm; border: 1pt solid #111; padding: 4mm">
<img src="images/product.png" alt="Product" style="width: 22mm">
<p>001764 - DEFAULT</p>
</div>
""",
base_url: "priv/static"
)To supply assets without filesystem access, map document references to bytes or trusted files:
HtmlToPdf.render(html,
assets: %{
"product-image" => {:bytes, png_bytes},
"report-font" => {:file, "priv/fonts/report-sans.ttf"}
}
)The HTML or CSS can use those exact references in src or url(...).
Register a font
Pass a static TrueType font when output must use the same face on every host:
{:ok, pdf} =
HtmlToPdf.render(
~s(<p style="font-family: 'Report Sans'">Café</p>),
fonts: [%{family: "Report Sans", path: "priv/fonts/report-sans.ttf"}]
)A document can also load a font beneath :base_url with @font-face:
{:ok, pdf} =
HtmlToPdf.render(
"""
<style>
@font-face {
font-family: "Report Sans";
src: url("fonts/report-sans.ttf") format("truetype");
}
body { font-family: "Report Sans"; }
</style>
<p>Café</p>
""",
base_url: "priv/static"
)Set PDF metadata
{:ok, pdf} =
HtmlToPdf.render(
"<title>Monthly statement</title><p>Statement content</p>",
metadata: [
author: "Finance Operations",
subject: "Customer statement",
keywords: ["statement", "monthly"],
creation_date: Date.utc_today()
]
)The first non-empty HTML <title> becomes the PDF title unless
metadata[:title] is set.
Create bookmarks from headings
Visible HTML headings can become nested items in the PDF viewer's bookmarks panel:
{:ok, pdf} =
HtmlToPdf.render(
"""
<h1>Annual report</h1>
<h2>Financial results</h2>
<h3>Revenue</h3>
<h3>Expenses</h3>
""",
outlines: :headings
)See PDF outlines and bookmarks for exact outline input, automatic detection in existing PDFs, and preservation behavior.
Render static form values
Form controls become visible, non-editable PDF content:
html = """
<input type="text" value="Amira Tan">
<input type="checkbox" checked>
<select><option selected>Approved</option></select>
<textarea>Documents verified</textarea>
<button type="button">Record application</button>
"""
{:ok, pdf} = HtmlToPdf.render(html)Handle an error
Rendering uses the library's shared diagnostic result:
case HtmlToPdf.render(html) do
{:ok, pdf} ->
File.write!("/tmp/document.pdf", pdf)
{:error, {reason, diagnostic}} ->
Logger.warning(
"PDF render failed with #{reason}: #{diagnostic.message}"
)
endUse the reason for program flow and the diagnostic for logs or template fixes. See Diagnostics for the full contract.
Benchmark rendering
Run the synthetic 250-item purchase-order benchmark from the repository:
mise exec -- mix run scripts/benchmark-html-render.exs
To measure a local HTML file:
mise exec -- mix run scripts/benchmark-html-render.exs /path/to/private-document.html
The script prints measurements without saving HTML or PDF output. Keep private inputs outside tracked fixtures. Use synthetic data for committed fixtures, preserving document structure, text lengths, and character coverage.
Each report includes stage times, caller-process reductions, full
HtmlToPdf.render/1 time, page count, PDF size, and SHA-256. Three warm runs also
report sampled peak VM memory.
Read the measurements as follows:
- Stage timings exclude option preparation. The public-render timing includes validation and metadata preparation.
- Stages run before the public call and warm shared font caches, even in the
report labelled
cold. Measure a public call in a fresh VM for cold latency. - Peak memory covers the whole VM during both pipelines. Sampling can miss short-lived peaks. Reductions do not measure native rasterization work.
- Page count comes from the staged pipeline; size and hash come from the public render.
The script uses default options and CSS page settings. For custom fonts, assets, page furniture, or other options, time the public renderer in your application. Keep runtime, fonts, assets, and machine load consistent between comparisons.