defmodule Pdf.Component.Paginator do
@moduledoc """
Paginator component for PDF documents.
Registers a footer template that renders page numbers on every page.
Uses `Pdf.on_page(:footer, ...)` internally.
## Examples
doc |> Pdf.Component.Paginator.apply()
doc |> Pdf.Component.Paginator.apply(%{
format: :center,
font_size: 9,
color: {0.5, 0.5, 0.5},
prefix: "Page "
})
"""
@default_font "Helvetica"
@default_font_size 9
@default_color {0.5, 0.5, 0.5}
@default_margin_bottom 30
@doc """
Apply page numbering to the document.
This registers a footer template — all subsequent pages will
have page numbers rendered automatically.
## Style options
- `:format` — `:center` (default), `:right`, or `:left`
- `:font` — font name (default `"Helvetica"`)
- `:font_size` — text size (default `9`)
- `:color` — text color (default gray)
- `:margin_bottom` — distance from page bottom (default `30`)
- `:prefix` — text before number (default `"Page "`)
- `:show_total` — show "of N" suffix (default `false`)
- `:total_pages` — total page count (required if `:show_total` is `true`)
- `:separator` — separator between number and total (default `" of "`)
"""
def apply(doc, style \\ %{}) do
format = Map.get(style, :format, :center)
font = Map.get(style, :font, @default_font)
font_size = Map.get(style, :font_size, @default_font_size)
color = Map.get(style, :color, @default_color)
margin_bottom = Map.get(style, :margin_bottom, @default_margin_bottom)
prefix = Map.get(style, :prefix, "Page ")
show_total = Map.get(style, :show_total, false)
total_pages = Map.get(style, :total_pages, 0)
separator = Map.get(style, :separator, " of ")
Pdf.on_page(doc, :footer, fn d, info ->
page_num = info.number
%{width: pw} = Pdf.size(d)
text = prefix <> "#{page_num}"
text = if show_total and total_pages > 0 do
text <> separator <> "#{total_pages}"
else
text
end
text_w = String.length(text) * font_size * 0.52
x = case format do
:center -> (pw - text_w) / 2
:right -> pw - text_w - 40
:left -> 40
end
d
|> Pdf.set_font(font, font_size)
|> Pdf.set_fill_color(color)
|> Pdf.text_at({x, margin_bottom}, text)
end)
end
end