Escape
HomeDocs@quario/layout

@quario/layout

The paged typesetting the PDF target writes and the viewer and editor paint, as a display list. Reach for it to build a paginating surface of your own.

layout(options?)

signature
function layout(options?: LayoutOptions): Target<'layout', Promise<Layout>>

A render target like any other, resolving the display list instead of bytes.

LayoutOptions

signature
interface LayoutOptions {
  page?: LayoutPage
  fonts?: LayoutFonts
}

interface LayoutPage {
  size?: 'A4' | 'letter' | [number, number]
  margin?: number
}

interface LayoutFontFamily {
  regular: Uint8Array | ArrayBuffer
  bold?: Uint8Array | ArrayBuffer
  italic?: Uint8Array | ArrayBuffer
  boldItalic?: Uint8Array | ArrayBuffer
}

type LayoutFonts = Record<string, LayoutFontFamily>

Pass the same fonts record here and to pdf({ fonts }) so a preview and the document break their lines in the same places. Hold the record and its buffers across renders, since they’re the identity the layout remembers a face by.

It takes two options: page and fonts. Any other key, or one of the wrong type, throws a TypeError naming the path. An array in place of the fonts record throws too, rather than reading as families named 0, 1 and 2.

Paper, Layout, Page, Box, Mark

signature
interface Paper {
  width: number
  height: number
}

interface Layout extends Paper {
  pages: [Page, ...Page[]]
  marks: Mark[]
}

interface Page {
  number: number
  total: number
  ops: Op[]
  boxes: Box[]
}

interface Box { path: string; x: number; y: number; w: number; h: number }

interface Mark { title: string; depth: number; page: number; x: number; y: number }

Paper is the page box the whole render takes. One report has one page size: it’s a target option, not something a document declares. So the pair lives on the list, and a page carries none of its own. Size a canvas from list.width and list.height, never from a page.

pages is never empty: the band flow opens the body’s first page whether or not anything renders on it, so pages[0] needs no guard.

boxes maps a schema path to the rectangle it drew, which is the selection seam. marks is the outline the PDF target writes as bookmarks.

Op

signature
type Op = RectOp | LineOp | ImageOp | TextOp | MarkOp

One page’s drawing instructions, in paint order. FaceRef on a text op names the family, the variant index, and whether the face is host-supplied bytes. That decides how a painter draws the run.

Helpers

signature
function paint(
  ctx: CanvasRenderingContext2D,
  page: Page,
  options?: { scale?: number; fonts?: LayoutFonts },
): Promise<void>

function hit(page: Page, x: number, y: number): Box | null

function pageBox(
  page?: LayoutPage,
  at?: string,
): { width: number; height: number; margin: number }

function checkFonts(fonts?: LayoutFonts | null, at?: string): void

const PX_PER_POINT: number

paint draws one page to a canvas, filling white over the whole canvas first. A page’s ops sit in the page’s own coordinates, so size the canvas to the paper before you call it. A canvas short of the paper clips them, whatever the fill covers:

painting a page
import { layout, paint } from '@quario/layout'

const list = await report.render(layout({ page: { size: 'A4' } }), data)

canvas.width = list.width * 2        // the paper's, never a page's
canvas.height = list.height * 2
await paint(ctx, list.pages[0], { scale: 2 })

hit returns the smallest box containing a point, measured from the top-left corner, or null where the page has nothing there. pageBox validates a page option and returns its geometry, with at prefixing a failure with the option’s name.

Typesetting, as a display list.

npm install quario
Getting started
© 2026 quario · KvK 61815977