Skip to content

Report Templates

renderTemplate() is the recommended path for multi-page reports. It paginates a block tree, applies semantic spacing, runs headers/footers after pagination, and emits proper structure tags when the document is tagged.

ts
doc.renderTemplate({
  title: "Quarterly Report",
  info: { author: "Finance" },
  page: {
    size: "A4",
    font,
    margin: 56,
    spacing: {
      paragraphAfter: 8,
      headingBefore: { 2: 18, 3: 14 },
      headingAfter: { 1: 14, 2: 10, 3: 8 },
      tableBefore: 6,
      tableAfter: 14
    }
  },
  blocks: [
    { type: "heading", text: "Quarterly Report" },
    { type: "paragraph", text: "Summary and scope." },
    {
      type: "table",
      rows: [
        [{ text: "Metric", header: true }, { text: "Result", header: true }],
        ["Revenue", "$2.6M"]
      ],
      options: { headerRows: 1 }
    },
    { type: "pageBreak" },
    { type: "heading", text: "References", options: { level: 2 } },
    { type: "link", text: "WCAG 2.2", url: "https://www.w3.org/TR/WCAG22/" }
  ],
  header: ({ title, pageNumber, totalPages }) => [
    {
      type: "paragraph",
      text: `${title} - page ${pageNumber} of ${totalPages}`,
      options: { fontSize: 8, tag: "Artifact" }
    }
  ],
  pageNumber: { region: "footer", align: "center" }
});

Multi-Column Flow

Set columns: { count, gap } on page for newspaper-style body flow. Paragraphs fill the current column and continue at the top of the next column on the same page; tables and other blocks advance to the next column before a new page. Short multi-column pages are balanced automatically so the final column set does not leave all content in the first column.

ts
doc.renderTemplate({
  page: {
    size: "A4",
    margin: 56,
    columns: { count: 2, gap: 24 }
  },
  blocks: [
    { type: "heading", text: "Research Brief" },
    { type: "paragraph", text: longBodyCopy },
    { type: "paragraph", text: "Closing notes balance with the previous column." }
  ]
});

Block types

TypePurpose
paragraphBody text with wrapping
richParagraphInline runs with mixed fonts/sizes/colors/links on one line (Inline rich text)
headingH1–H6 with semantic spacing
tableGrid with header rows/columns
imageEmbedded JPEG/PNG with alt text
linkURI link with paragraph spacing
listOrdered/unordered/nested
pageBreakForce next block to a new page
sectionLogical grouping in structure tree
textFieldSingle-line text input
checkBoxBoolean checkbox
choiceFieldDropdown or list selection
radioGroupMutually exclusive radio buttons
pushButtonClickable push button
signatureFieldDigital signature placeholder
highlightText highlight annotation
noteSticky note annotation
freeTextFree-text annotation box
pageLinkInternal page link
rectRectangle vector shape
pathCustom path vector shape
customUser-defined block with render/estimate

Form Fields

Form blocks auto-position within the flow---no manual x/y coordinates needed.

ts
{ type: "textField", name: "email", value: "[email protected]", width: 220, height: 24 },
{ type: "checkBox", name: "subscribe", checked: true, width: 16, height: 16 },
{ type: "choiceField", name: "dept", options: ["Eng", "Design", "Sales"], value: "Design", mode: "combo", width: 180, height: 24 },
{ type: "radioGroup", name: "method", items: [{ value: "email" }, { value: "phone" }], value: "phone" },
{ type: "pushButton", name: "submit", label: "Submit", width: 96, height: 24 },
{ type: "signatureField", name: "signHere", width: 180, height: 36 }

Annotations

ts
{ type: "highlight", width: 120, height: 14, color: rgb(1, 1, 0) },
{ type: "note", contents: "Review this paragraph", width: 24, height: 24 },
{ type: "freeText", text: "Inline annotation", width: 180, height: 32 },
{ type: "pageLink", destination: "intro", width: 96, height: 14 }

Vector Graphics

rect and path blocks require an explicit height so the paginator can allocate space.

ts
{ type: "rect", width: 200, height: 4, options: { color: rgb(0.2, 0.2, 0.2), fill: rgb(0.9, 0.9, 0.9) } },
{ type: "path", commands: "M0 0 L100 0 L50 50 Z", height: 50, options: { color: rgb(0, 0, 0), fill: rgb(0.8, 0.8, 0.8) } }

Lists

Each item follows the ListItem format: { text: string, children?: ListItem[] }. Use options.ordered for numbered lists or options.bullet for custom markers.

ts
{
  type: "list",
  items: [
    { text: "First item" },
    { text: "Second item", children: [{ text: "Nested" }] }
  ],
  options: { ordered: true, bullet: "disc" }
}

Custom Blocks

When built-in block types don't cover your needs, use the custom type with render and estimate callbacks.

ts
{
  type: "custom",
  height: 12,
  render(ctx) {
    ctx.graphics()
      .moveTo(ctx.x, ctx.y + 1)
      .lineTo(ctx.x + ctx.width, ctx.y + 1)
      .stroke({ color: rgb(0.6, 0.6, 0.6) });
  },
  estimate() { return 12; }
}
  • render(ctx) --- draws the block. ctx provides x, y, width, and drawing helpers.
  • estimate() --- returns the vertical space (points) needed during pagination.

Ideal for horizontal rules, separators, watermarks, or any drawing that doesn't fit existing block types. See Custom Blocks for a deeper guide.

Spacing model

Spacing is semantic, not a single fixed gap:

  • paragraphs: compact rhythm after the text
  • headings: stronger before-space, smaller after-space, no leading space at top of page
  • tables and figures: object spacing before and after
  • links: paragraph-like spacing after

Override per block with marginTop / marginBottom. Override globally with page.spacing.

Stylesheets

Define reusable typography and spacing once with styles, then apply them by block type or via a class on each block, instead of repeating options everywhere. See Template Stylesheets for the full selector and cascade rules.

ts
doc.renderTemplate({
  styles: {
    paragraph: { fontSize: 11, lineHeight: 16 },
    heading1: { fontSize: 28, color: rgb(0.1, 0.2, 0.5) },
    callout: { color: rgb(0.8, 0, 0), marginTop: 12 }
  },
  blocks: [
    { type: "heading", text: "Title", options: { level: 1 } },
    { type: "paragraph", text: "Inherits the shared paragraph style." },
    { type: "paragraph", text: "Important.", class: "callout" }
  ]
});

Headers, footers, page numbers

Header/footer callbacks run after body pagination, so pageNumber and totalPages are accurate. Use pageNumber for standard running labels:

ts
pageNumber: { region: "footer", align: "right" }

region: "header" | "footer". align: "left" | "center" | "right".

Page breaks

{ type: "pageBreak" } forces the next block onto a fresh page. A break before any body content on the current page is ignored, so defensive breaks never produce blank pages.

Auto outlines

Headings are added to the document outline automatically when autoOutline is enabled:

ts
autoOutline: { maxLevel: 3 }

Released under the ISC license.