Skip to main content

lib/pdf/component/paginator.ex

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