PrawnEx.Layout (prawn_ex v0.6.0)

Copy Markdown View Source

Margin box + vertical flow helpers on top of PrawnEx.

Tracks a PDF baseline cursor (cursor_y) so you avoid repeating page_h - N arithmetic for common stacks (title, paragraphs, table, spacer). Coordinates match the rest of PrawnEx: origin bottom-left, y increases upward.

Phases

Typical use:

doc
|> PrawnEx.add_page()
|> PrawnEx.Layout.attach(page_size: :a4, margins: %{top: 60, left: 50, right: 50, bottom: 50})
|> PrawnEx.Layout.heading("INVOICE", font_size: 24)
|> PrawnEx.Layout.paragraph("Acme Inc.\n123 Main St", font_size: 10, line_height: 14)
|> PrawnEx.Layout.to_doc()

Escape hatch for one-off coordinates: escape/2.

Summary

Functions

Attaches flow state to doc. Requires at least one page.

Low-level escape: fun receives (doc, ctx) where ctx is a map with :cursor_y, :content_left, :content_width, :page_w, :page_h, :margins. Return {new_doc, new_cursor_y}.

Single-line heading. Options: :font, :font_size (default from :level), :level (1 or 2), :lead (default 1.0), :gap_after (default 6).

Horizontal row of fixed-width columns (Phase B). Each column is {width_pt, fn sub_layout -> sub_layout}. The function receives a layout whose content_left / content_width / cursor_y are scoped to that column; merge the returned doc and advance the outer cursor by the deepest column drop plus :row_gap_after (default 8).

Wrapped paragraph using PrawnEx.text_box/3. Options: :font_name, :font_size (default 10), :line_height (default font_size * 1.2), :width (default content width), :gap_after (default 8). Preserves newlines like text_box / Text.wrap_to_lines.

Moves the baseline cursor down the page by pts points (decreases PDF y).

Draws a table whose top edge sits clearance pt below the current cursor.

Returns the underlying document for PrawnEx.build/2 callbacks or to_binary/1.

Vertical stack of flow blocks (Phase B). blocks are tuples

Types

flow_block()

@type flow_block() ::
  {:heading, String.t(), keyword()}
  | {:paragraph, String.t(), keyword()}
  | {:spacer, number()}
  | {:table, [list()], keyword()}
  | {:run, (t() -> t())}

margins()

@type margins() :: %{left: number(), right: number(), top: number(), bottom: number()}

t()

@type t() :: %PrawnEx.Layout{
  content_left: number(),
  content_width: number(),
  cursor_y: number(),
  doc: PrawnEx.Document.t(),
  margins: margins(),
  on_overflow: :new_page | :clip,
  page_h: number(),
  page_size: atom() | tuple(),
  page_w: number(),
  region_floor_y: number() | nil
}

Functions

attach(doc, opts \\ [])

@spec attach(
  PrawnEx.Document.t(),
  keyword()
) :: t()

Attaches flow state to doc. Requires at least one page.

Options:

  • :page_size — passed to PrawnEx.Units.page_size/1 (default from doc.opts[:page_size] or :a4)
  • :margins — a number (all sides) or %{left:, right:, top:, bottom:} (missing keys default to 50)
  • :region%{floor_y: y} or bare y (PDF y); content must not extend below this y (see Phase C). When unset, no automatic page breaks are inserted.
  • :on_overflow:new_page (default when region set) or :clip (draw anyway; no new page)

Initial cursor_y is the first text baseline under the top margin: page_h - margins.top.

escape(l, fun)

Low-level escape: fun receives (doc, ctx) where ctx is a map with :cursor_y, :content_left, :content_width, :page_w, :page_h, :margins. Return {new_doc, new_cursor_y}.

heading(l, text, opts \\ [])

@spec heading(t(), String.t(), keyword()) :: t()

Single-line heading. Options: :font, :font_size (default from :level), :level (1 or 2), :lead (default 1.0), :gap_after (default 6).

hstack(l, columns, opts \\ [])

@spec hstack(t(), [{number(), (t() -> t())}], keyword()) :: t()

Horizontal row of fixed-width columns (Phase B). Each column is {width_pt, fn sub_layout -> sub_layout}. The function receives a layout whose content_left / content_width / cursor_y are scoped to that column; merge the returned doc and advance the outer cursor by the deepest column drop plus :row_gap_after (default 8).

Options: :gap between columns (default 8), :row_gap_after (default 10), :min_row_depth — minimum vertical budget in pt for the row (default 22). Short columns only move the cursor by gap_after pixels; without a floor, the next block can overlap the row.

paragraph(l, text, opts \\ [])

@spec paragraph(t(), String.t(), keyword()) :: t()

Wrapped paragraph using PrawnEx.text_box/3. Options: :font_name, :font_size (default 10), :line_height (default font_size * 1.2), :width (default content width), :gap_after (default 8). Preserves newlines like text_box / Text.wrap_to_lines.

spacer(l, pts)

@spec spacer(t(), number()) :: t()

Moves the baseline cursor down the page by pts points (decreases PDF y).

table(l, rows, opts \\ [])

@spec table(t(), [list()], keyword()) :: t()

Draws a table whose top edge sits clearance pt below the current cursor.

Forwards options to PrawnEx.table/3 except: :clearance (default 20), :after_gap (default 12), and :at / :page_size are supplied automatically unless you pass :page_size in opts.

to_doc(layout)

@spec to_doc(t()) :: PrawnEx.Document.t()

Returns the underlying document for PrawnEx.build/2 callbacks or to_binary/1.

vstack(l, blocks, opts \\ [])

@spec vstack(t(), [flow_block()], keyword()) :: t()

Vertical stack of flow blocks (Phase B). blocks are tuples:

  • {:heading, text, opts} — passed to heading/3
  • {:paragraph, text, opts} — passed to paragraph/3
  • {:spacer, pts} — passed to spacer/2
  • {:table, rows, opts} — passed to table/3
  • {:run, fn layout -> layout end} — custom

Options: :gap — extra spacer inserted between blocks (not after the last).