Tiptapex.Export.PDF (Tiptapex v0.1.2)

Copy Markdown View Source

Print-ready HTML and PDF engine options for Tiptapex documents.

This module never shells out and never adds a dependency: it turns a document plus its Tiptapex.Page setup into (a) a standalone HTML page with the right @page geometry and (b) the exact options the two common Elixir PDF engines need. You call the engine.

ChromicPDF

{source, opts} = Tiptapex.Export.PDF.chromic_pdf(article.body)
ChromicPDF.print_to_pdf(source, opts ++ [output: "article.pdf"])

pdf_generator (wkhtmltopdf)

{html, opts} = Tiptapex.Export.PDF.pdf_generator(article.body)
{:ok, path} = PdfGenerator.generate(html, opts)

Anything else (browser print, WeasyPrint, Gotenberg…)

html = Tiptapex.Export.PDF.to_html(article.body)

Headers, footers and page numbers

Both engines draw running headers/footers themselves, which is the only way to get a correct {pages} total and per-page numbering. The three slots of Tiptapex.Page map straight onto each engine's native feature:

TokenChromicPDF (Chrome)pdf_generator (wkhtmltopdf)
{page}<span class="pageNumber">[page]
{pages}<span class="totalPages">[topage]
{date}<span class="date">[date]
{time}(not supported — pass :tokens)[time]
{title}<span class="title">[title]

Substitute a token yourself with :tokens when you want a fixed value or a specific format:

Tiptapex.Export.PDF.chromic_pdf(doc, tokens: %{"date" => "24/07/2026"})

Logos and formatting

A header/footer slot can carry an image (see Tiptapex.Page) and a small allow-listed HTML subset (see Tiptapex.Renderer.Markup) — <b>, <span style="color: …">, <h1>, <img> and friends. Chrome draws both straight from the template.

wkhtmltopdf's text flags can do neither: they render literally. A region with a logo or markup therefore needs --header-html/--footer-html — use with_pdf_generator/3, which writes and cleans up those files for you.

Chrome renders the running header with no base URL, so a relative logo src will not load. Pass :asset_url to rewrite it — most usefully into a data: URI:

Tiptapex.Export.PDF.chromic_pdf(doc, asset_url: &MyApp.inline_asset/1)

Because the engines own the margin boxes, to_html/2 emits @page { margin: 0 } on the engine paths — the margins travel as engine options instead, so the header/footer land inside them.

Summary

Functions

Options for PdfGenerator.generate/2 (wkhtmltopdf), as {html, opts}.

A standalone HTML document for one running region — what wkhtmltopdf's --header-html / --footer-html expects, and the only way to get a logo into a wkhtmltopdf header.

The stylesheet embedded in exported HTML (the package's own CSS).

Renders a complete, standalone HTML document ready to print.

Runs fun with the {html, opts} pdf_generator/2 would return, having first written any header/footer that carries a logo to a temporary HTML file (wkhtmltopdf's --header-html / --footer-html). The files are removed afterwards, whatever fun does.

Functions

chromic_pdf(doc, opts \\ [])

@spec chromic_pdf(
  map() | nil,
  keyword()
) :: {{:html, String.t()}, keyword()}

Options for ChromicPDF.print_to_pdf/2, as {source, opts}.

{source, opts} = Tiptapex.Export.PDF.chromic_pdf(doc)
ChromicPDF.print_to_pdf(source, opts ++ [output: path])

Paper size and margins become printToPDF parameters (in inches, the unit Chrome speaks) and the header/footer slots become Chrome's headerTemplate/footerTemplate.

Accepts every to_html/2 option, plus:

  • :tokens — token replacements applied before Chrome's own, e.g. %{"date" => "24/07/2026"}.
  • :print_to_pdf — a map merged over the computed parameters, for anything else Chrome supports ("scale", "pageRanges", …).
  • :font_size — header/footer font size in CSS units, default "9pt".
  • :asset_url — 1-arity function rewriting logo src values, e.g. to absolutise them or inline them as data: URIs. Chrome cannot resolve relative URLs in a running header.

pdf_generator(doc, opts \\ [])

@spec pdf_generator(
  map() | nil,
  keyword()
) :: {String.t(), keyword()}

Options for PdfGenerator.generate/2 (wkhtmltopdf), as {html, opts}.

{html, opts} = Tiptapex.Export.PDF.pdf_generator(doc)
{:ok, path} = PdfGenerator.generate(html, opts)

Paper size, orientation, margins and the header/footer slots all become wkhtmltopdf shell parameters, so page numbering is produced by the engine ([page] / [topage]).

Accepts every to_html/2 option, plus:

  • :tokens — token replacements applied before wkhtmltopdf's own.
  • :font_size — header/footer font size in points, default 9.
  • :header_spacing / :footer_spacing — millimetres between the running element and the content.
  • :header_html / :footer_html — a path or URL for wkhtmltopdf's --header-html / --footer-html, used instead of the text flags. Required for a region carrying a logo — see with_pdf_generator/3.
  • :shell_params — extra parameters appended last (wkhtmltopdf lets later flags win, so this can override anything computed here, including the --dpi 96 --disable-smart-shrinking pair that keeps wkhtmltopdf on CSS reference pixels).

running_html(doc, region, opts \\ [])

@spec running_html(map() | nil, :header | :footer, keyword()) :: String.t()

A standalone HTML document for one running region — what wkhtmltopdf's --header-html / --footer-html expects, and the only way to get a logo into a wkhtmltopdf header.

wkhtmltopdf appends page, topage, date and time to the URL as query parameters; the returned document reads them back, so {page} and {pages} resolve per page exactly as the native flags would.

Accepts :page, :tokens, :font_size and :asset_url.

stylesheet()

@spec stylesheet() :: String.t()

The stylesheet embedded in exported HTML (the package's own CSS).

to_html(doc, opts \\ [])

@spec to_html(
  map() | nil,
  keyword()
) :: String.t()

Renders a complete, standalone HTML document ready to print.

Options

  • :page — page setup override, as in Tiptapex.Page.from_doc/2. Defaults to whatever the document carries; pass true to force defaults onto a document with no page setup.
  • :margins:css (default) writes the margins into the @page rule; :none writes margin: 0 because the PDF engine supplies them. The engine helpers below use :none.
  • :title<title> of the page, and the value Chrome/wkhtmltopdf substitute for {title}. Defaults to the page setup's :title.
  • :lang<html lang>, default "en".
  • :stylesheet:bundled (default) inlines the package stylesheet so the PDF matches the editor; :none omits it.
  • :css — extra CSS appended after the stylesheet.
  • :renderer — options forwarded to Tiptapex.Renderer.to_html/2.
  • :body_class — extra classes on <body>.

with_pdf_generator(doc, opts \\ [], fun)

@spec with_pdf_generator(map() | nil, keyword(), (String.t(), keyword() -> result)) ::
  result
when result: term()

Runs fun with the {html, opts} pdf_generator/2 would return, having first written any header/footer that carries a logo to a temporary HTML file (wkhtmltopdf's --header-html / --footer-html). The files are removed afterwards, whatever fun does.

Tiptapex.Export.PDF.with_pdf_generator(doc, [], &PdfGenerator.generate/2)

This is the path to use when headers or footers have images; without it wkhtmltopdf can only draw their text (see pdf_generator/2). Passing :header_html / :footer_html yourself — a path or a URL you serve — takes precedence and writes nothing.