Tincture.Layout.Template (Tincture v0.1.0)

Copy Markdown View Source

Page templates: margins, columns, headers, footers and pagination.

A template describes the shape of a page once; render_document/4 then flows text through as many copies of it as the content needs, numbering them as it goes.

template =
  Template.new(page_size: :a4, margins: {50, 50, 50, 50}, columns: 2)
  |> Template.with_header("Quarterly Report", size: 14)
  |> Template.with_footer("Page {page} of {total}")

{pdf, result} = Template.render_document(Tincture.new(), template, rich_text)

{page} and {total} in a header or footer are substituted per page. When the total is not supplied it resolves to each page's own number, because the real total is not known until pagination finishes — pass :page_total explicitly if the document's length is known in advance.

render_document/4 stops at :max_pages (50 by default) and reports what did not fit, so runaway content cannot generate an unbounded document.

XML templates

parse_xml/1 and render_xml_document/3 build the same thing from markup, for cases where the layout is authored outside the code:

<document page_size="a4" margins="50,50,50,50" columns="2">
  <header font="Helvetica-Bold" size="14">Quarterly Report</header>
  <footer size="9">Page {page} of {total}</footer>
  <body font="Times-Roman" size="11">...</body>
</document>

Summary

Types

box()

@type box() :: {float(), float(), float(), float()}

document_option()

@type document_option() ::
  Tincture.Layout.Box.option()
  | {:page_number_start, pos_integer()}
  | {:page_total, pos_integer()}
  | {:max_pages, pos_integer()}

margin_tuple()

@type margin_tuple() :: {number(), number(), number(), number()}

option()

@type option() ::
  {:page_size, Tincture.PDF.page_size()}
  | {:margins, margin_tuple()}
  | {:columns, pos_integer()}
  | {:gutter, number()}
  | {:header_height, number()}
  | {:footer_height, number()}

slot_option()

@type slot_option() :: {:font, String.t()} | {:size, number()} | {:height, number()}

t()

@type t() :: %Tincture.Layout.Template{
  body_boxes: [box()],
  columns: pos_integer(),
  footer: Tincture.Layout.Template.Slot.t() | nil,
  footer_height: float(),
  gutter: float(),
  header: Tincture.Layout.Template.Slot.t() | nil,
  header_height: float(),
  margins: %{left: float(), right: float(), top: float(), bottom: float()},
  page_size: Tincture.PDF.page_size()
}

xml_error()

@type xml_error() ::
  :invalid_xml
  | :missing_body
  | {:invalid_page_size, String.t()}
  | {:invalid_margins, String.t()}
  | {:invalid_columns, String.t()}
  | {:invalid_gutter, String.t()}
  | {:invalid_slot_size, String.t()}
  | {:invalid_slot_height, String.t()}
  | {:invalid_body_size, String.t()}

Functions

new(opts \\ [])

@spec new([option()]) :: t()

parse_xml(xml)

@spec parse_xml(String.t()) ::
  {:ok, t(), Tincture.Typography.RichText.t()} | {:error, xml_error()}

render(pdf, template, rich_text, opts \\ [])

render_document(pdf, template, rich_text, opts \\ [])

render_xml_document(pdf, xml, opts \\ [])

@spec render_xml_document(Tincture.PDF.t(), String.t(), [document_option()]) ::
  {:ok, Tincture.PDF.t(), Tincture.Layout.Template.DocumentResult.t()}
  | {:error, xml_error()}

with_footer(template, text, opts \\ [])

@spec with_footer(t(), String.t(), [slot_option()]) :: t()

with_header(template, text, opts \\ [])

@spec with_header(t(), String.t(), [slot_option()]) :: t()