Page setup for paginated documents: paper size, orientation, margins, running headers/footers and page numbering.
The canonical home for this configuration is the document itself — the
ProseMirror doc node carries it under attrs.page:
%{
"type" => "doc",
"attrs" => %{
"page" => %{
"size" => "letter",
"orientation" => "portrait",
"margins" => %{"top" => 25.4, "right" => 25.4, "bottom" => 25.4, "left" => 25.4},
"header" => %{"left" => "Acme S.A.", "center" => "", "right" => "{date}"},
"footer" => %{"left" => "", "center" => "", "right" => ""},
"numbering" => %{
"enabled" => true,
"region" => "footer",
"align" => "center",
"format" => "Page {page} of {pages}"
}
}
},
"content" => [...]
}A document without attrs.page is not paginated — the editor renders the
usual continuous surface and Tiptapex.Export.PDF falls back to A4-ish
defaults only if you ask for them explicitly.
Sizes
Presets (millimetres, portrait):
* `:letter` — 215.9 × 279.4 (8.5 × 11 in)
* `:legal` — 215.9 × 355.6 (8.5 × 14 in)
* `:tabloid` — 279.4 × 431.8 (11 × 17 in)
* `:executive` — 184.15 × 266.7 (7.25 × 10.5 in)
* `:a3` — 297 × 420
* `:a4` — 210 × 297
* `:a5` — 148 × 210Anything else is a custom size: %{width: 200, height: 250}.
Units
Lengths are millimetres. Plain numbers are read as mm; strings may carry a
unit — "1in", "2.5cm", "72pt", "96px" (CSS px at 96 dpi).
Tiptapex.Page.new(%{size: :letter, margins: %{top: "1in", bottom: "1in"}})Headers and footers
Each region has three slots — :left, :center, :right — holding text
and/or an image (a logo). Text supports tokens:
{page}— current page number{pages}— total pages{date}— print date{time}— print time{title}— the document title (:title, or the<title>of the exported HTML)
This maps 1:1 onto what both supported PDF engines can do natively, so page
numbers are produced by the engine and are always correct — see
Tiptapex.Export.PDF.
Page numbering is a convenience on top: enabling it drops :format into the
slot named by :align of the region named by :region, unless that slot
already contains a {page} token.
Logos
A slot is either a plain string (text only) or a map:
header: %{
left: %{image: %{src: "/uploads/logo.png", height: 12}},
center: "",
right: "{page} / {pages}"
}:height is millimetres (default 8, clamped to 100) and the width
follows the image's aspect ratio. :src must be http(s), a relative path,
or a data: URI for a raster image or SVG — anything else is dropped, the
same allow-list discipline Tiptapex.Renderer applies to the document.
Normalised slots are always %{text: binary, image: map | nil};
to_map/1 writes the compact string form back out when a slot carries no
image, so documents without logos keep the JSON they had.
Chrome cannot resolve relative logo URLs
ChromicPDF renders the running header/footer in a context with no base
URL, so /uploads/logo.png will not load. Give the logo an absolute URL,
or pass :asset_url to Tiptapex.Export.PDF to rewrite it (typically
into a data: URI).
Summary
Types
A logo in a header/footer slot. :height is millimetres.
Millimetre lengths per side.
A header/footer region: three slots.
One header/footer slot: text, an image, or both.
Functions
The printable box — paper minus margins — in millimetres.
A CSS margin shorthand value for the @page rule, e.g.
"25.4mm 25.4mm 25.4mm 25.4mm".
A CSS size value for the @page rule, e.g. "215.9mm 279.4mm".
Paper dimensions in millimetres, with orientation applied.
Reads the page setup carried by a document, or nil when it has none.
True when any region of the page carries an image.
Builds a page configuration, accepting string- or atom-keyed maps, keyword lists, or an existing struct. Unknown values fall back to the defaults rather than raising — the input often comes from a stored document.
The built-in paper size names.
The built-in paper sizes as %{name => {width_mm, height_mm}} (portrait).
Stores a page configuration on the document's doc node, returning the
updated document. Passing nil removes it (the document stops being
paginated).
True when the region has anything to draw.
Replaces the {page}/{pages}/{date}/{time}/{title} tokens in
text using a %{"page" => ...} map. Unlisted tokens become "".
The slots of a header/footer region as [{:left, slot}, {:center, slot}, {:right, slot}], with page numbering merged in. Each slot is
%{text: binary, image: map | nil}.
Millimetres as inches — the unit Chrome's Page.printToPDF speaks.
The JSON-encodable form — string keys, millimetres, exactly what the
doc node's attrs.page and the editor's data-ttx-page carry.
Types
A logo in a header/footer slot. :height is millimetres.
Millimetre lengths per side.
A header/footer region: three slots.
One header/footer slot: text, an image, or both.
Functions
The printable box — paper minus margins — in millimetres.
A CSS margin shorthand value for the @page rule, e.g.
"25.4mm 25.4mm 25.4mm 25.4mm".
A CSS size value for the @page rule, e.g. "215.9mm 279.4mm".
Paper dimensions in millimetres, with orientation applied.
iex> Tiptapex.Page.dimensions(Tiptapex.Page.new(%{size: :letter}))
%{width: 215.9, height: 279.4}
Reads the page setup carried by a document, or nil when it has none.
override decides what wins:
nil(default) — use whatever the document carries.false— no page setup at all, whatever the document says.true— force page setup on, using defaults for anything the document doesn't specify.a map/keyword/struct — deep-merged over the document's own setup, so
page: %{size: :legal}changes the paper without touching margins.Tiptapex.Page.from_doc(article.body) Tiptapex.Page.from_doc(article.body, %{orientation: :landscape})
True when any region of the page carries an image.
Builds a page configuration, accepting string- or atom-keyed maps, keyword lists, or an existing struct. Unknown values fall back to the defaults rather than raising — the input often comes from a stored document.
iex> page = Tiptapex.Page.new(%{"size" => "legal", "orientation" => "landscape"})
iex> Tiptapex.Page.dimensions(page)
%{width: 355.6, height: 215.9}
@spec preset_names() :: [atom()]
The built-in paper size names.
The built-in paper sizes as %{name => {width_mm, height_mm}} (portrait).
Stores a page configuration on the document's doc node, returning the
updated document. Passing nil removes it (the document stops being
paginated).
doc = Tiptapex.Page.put(doc, %{size: :legal, numbering: %{enabled: true}})
True when the region has anything to draw.
Replaces the {page}/{pages}/{date}/{time}/{title} tokens in
text using a %{"page" => ...} map. Unlisted tokens become "".
iex> Tiptapex.Page.replace_tokens("Page {page} of {pages}", %{"page" => "[page]", "pages" => "[topage]"})
"Page [page] of [topage]"
The slots of a header/footer region as [{:left, slot}, {:center, slot}, {:right, slot}], with page numbering merged in. Each slot is
%{text: binary, image: map | nil}.
Tokens are left untouched — the caller translates them for its engine.
Millimetres as inches — the unit Chrome's Page.printToPDF speaks.
iex> Tiptapex.Page.to_inches(25.4)
1.0
The JSON-encodable form — string keys, millimetres, exactly what the
doc node's attrs.page and the editor's data-ttx-page carry.