Documents and components
A Document is configured at class level and described in view_template; a Component is a reusable piece of one.
Document configuration#
Class-level macros, inherited by subclasses.
Everything about the PDF as a whole is declared on the class. Subclasses inherit a copy of the parent's configuration, so a base ApplicationPdf can hold the house style.
class ApplicationPdf < Stationery::Document
page size: :a4, margin: [40, 44, 56, 44]
font_family "Brand", regular: "fonts/Brand-Regular.ttf", bold: "fonts/Brand-Bold.ttf"
font_fallbacks "Noto Sans Symbols"
default_text font: "Brand", size: 9, color: "#1F2937"
metadata author: "Acme Studio AB", creator: "stationery"
end
class InvoicePdf < ApplicationPdf
metadata title: "Invoice"
strict
end| Macro | Arguments | Default | Description |
|---|---|---|---|
page | size:, margin:, layout: | size: :letter, margin: 36, layout: :portrait | Page size (a name such as :a4, :letter, :dl or :label_4x6, [width, height] in points, or with units: ["102mm", "74mm"], "4in x 6in"), margins (one value or CSS-style 2/4 values, in points or as "3mm") and :landscape. Checked when the class is defined. See Pages. |
font_family | name, regular:, bold:, italic:, bold_italic: | — | Registers a family from .ttf/.otf files. With no paths it selects bundled Inter or an installed font pack. |
font_fallbacks | *names | [] | Families tried, in order, for characters the text's own family has no glyph for; bundled Inter is tried last. |
shaper | shaper | nil | An object answering call(text, font, **options) that places the glyphs of every text, for complex scripts; also to_pdf(shaper:). See Fonts. |
default_text | **style | {} | Base text style for the whole document: font, size, color, align, leading, kerning, … |
metadata | title:, author:, subject:, keywords:, creator:, producer: | {} | The PDF Info dictionary, mirrored in an XMP packet (/Metadata: dc:title, dc:creator, dc:subject, the xmp: dates, pdf:Producer). An Array value is joined with ", "; other keys pass through; xmp: false leaves the packet out. |
strict | value = true | false | Raise Stationery::WarningsError instead of writing a PDF that produced warnings. |
incremental | value = true | false | Write each page as soon as it is painted, so a long document with headers or footers holds far less; also to_pdf(incremental:). See Rendering. |
object_streams | value = true | on when tagged | Pack every object that is not a stream into deflated object streams with a cross-reference stream (PDF 1.5); a tagged render does unless object_streams false; also to_pdf(object_streams:). See Accessibility. |
page_template | layer: :foreground | — | A block run on every page after pagination; layer: :background paints under the content. |
header / footer | height:, gap:, on: | gap: 8, on: :all | Regions that reserve space at the top/bottom of the pages on: matches. |
Rendering#
to_pdf and what it leaves behind.
to_pdf(target = nil, strict: <class setting>, debug: false) builds the element tree, paginates it, runs page templates, resolves links and bookmarks and returns the PDF as a binary String. With a target it also writes to that path or IO.
document = InvoicePdf.new(invoice)
document.to_pdf("invoice.pdf")
document.warnings.each { |warning| Rails.logger.warn(warning.message) }
document.page_count # => 2, the pages the last render laid outWith a block, to_pdf { |chunk| … } streams the file in pieces as it is written and answers the number of bytes: the first bytes leave sooner and no output buffer is built. Every page is laid out and painted before the first byte. A signed document cannot go to a block.
incremental: true (or incremental at class level) writes each page's content as soon as the page is painted and lets go of it, to a block before the next page is painted. It is for long documents with headers, footers, page templates or a table of contents, which are painted once every page is known and become content streams of their own; the file lists its page contents first and is a little larger. A render with conformance: or sign:, and a strict one that goes to a block, take the usual path, since they must be checked before a byte is written. To a block, an error on a late page leaves the start of a file. The figures are on the Performance page.
strict: false opts one render out of a class-level strict; debug: outlines layout rectangles (see Pages).
Components#
Phlex's lifecycle, the same element DSL.
A Stationery::Component is a reusable piece of a document with Phlex's lifecycle — around_template, before_template, view_template, after_template — and every element. Content passed as a block is yielded from view_template:
class Callout < Stationery::Component
def initialize(color:) = (super(); @color = color)
def view_template(&) = box(background: @color, padding: 12, radius: 6, &)
end
class Report < Stationery::Document
def view_template
render Callout.new(color: "#F3F4F6") { text "Amount due" }
end
endA Document is itself a component, so documents can render other components, and components can render components.
render#
What it accepts.
| Argument | Renders |
|---|---|
| A component instance | render Callout.new(color: "#EEE") { … } — calls it with the block. |
| A component class | render Divider — instantiated with no arguments. |
| A String | A text paragraph. |
| A Proc or Method | Yielded as content, like a block. |
| A list (any Enumerable) | Each item rendered in turn. |
Anything else raises ArgumentError. Blocks that take an argument receive the component, so { |c| c.text @title } keeps your own self inside the block.