---
title: "@quario/layout — API reference"
description: "The paged display list for quario, covering the layout target, its operations, and the helpers that paint and hit-test a page."
url: "https://getquario.com/docs/reference/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?)`

```ts title="signature"
function layout(options?: LayoutOptions): Target<'layout', Promise<Layout>>
```

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

## `LayoutOptions`

```ts title="signature"
interface LayoutOptions {
  page?: LayoutPage
  fonts?: LayoutFonts
}

interface LayoutPage {
  size?: 'A4' | 'letter' | [number, number]
  margin?: number | { top: number; right: number; bottom: number; left: 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. Keep 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`

```ts title="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[]
  lines: TextLine[]
  tables: TextTable[]
}

interface TextLine {
  path: string | undefined
  from: number
  to: number
  heading?: number
  join?: string
}

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

interface TextTable { rows: TextRow[] }

interface TextRow { head?: true; cells: TextCell[] }

interface TextCell { from: number; to: number; span?: number }

interface Mark { title: string; label?: 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. A mark’s `y`
reads down from the page top, as an op’s does. The PDF target flips it for
each bookmark itself, so a host reading `marks` uses it as it stands. `label`
is the instance’s declared `label`, present only where it declared one. The
`title` falls back to header text, but only a `label` is a place an `href`
of `#` names.

`lines` is the page’s text in reading order. Each entry names the text ops
`ops[from]` up to `ops[to]` of one laid-out line, and the schema node it
belongs to. A table row’s cells are a line each, and a page band’s lines follow
the body’s. `heading` is the outline depth, on the first line of a group header
only. `join` continues this line into the next as one line of text. It holds
what a wrap dropped, or a space to the next cell or slot. It’s absent where the
next line starts a new one. The viewer reads this list to lay selectable text
over a page.

`tables` groups the page’s `lines` into the tables drawn there. A cell names
the range of `lines` it drew, and `span` the columns it covers where more than
one. An empty cell is an empty range, so later cells keep their column. A table
that breaks restates its headings, so each page’s part opens with a `head`
row. Every instance of one repeated table shares one set of column widths
across the render.

## `Op`

```ts title="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.

An image op carries the item’s rendered `alt`, absent where it declared none.
A text op also carries `href` beside its `x`, `y`, `w` and `h`, where the run
it draws linked. A run wrapping across two lines is two ops, which is two
rectangles. Two stretches pointing at different places never merge into one
phrase, even where they draw identically. A phrase is the rectangle a painter
attaches a link to.

## Helpers

```ts title="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 destinations(marks: readonly Mark[]): Map<string, Mark>

function destination(href: string, byLabel: Map<string, Mark>): string | Mark | null

function pageBox(
  page?: LayoutPage,
  at?: string,
): {
  width: number
  height: number
  margin: number | { top: number; right: number; bottom: number; left: number }
}

function fontOf(font: FaceRef, size: number): string

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

function loadFontkit(): Promise<unknown>

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:

```ts title="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 })
```

`loadFontkit` loads the optional font parser once and gives the same module to
every caller, which is how `@quario/pdf` embeds with the parser this package
measured with. A parser that won’t load reports `options.fonts: the font
parser did not load. Install fontkit to use TrueType families`, carrying
whatever actually threw as `cause`. A failed dynamic import doesn’t prove the
package is absent.

`fontOf` returns the CSS font string `paint` draws a run in, for a host laying
its own text over the canvas at the same metrics.

`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.

`destinations` maps each declared `label` to the first mark carrying it.
`destination` says where one `href` goes: a URL, a mark, or `null` for a
label no instance declares. The PDF target and the viewer both use them.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Docs","item":"https://getquario.com/docs"},{"@type":"ListItem","position":3,"name":"@quario/layout","item":"https://getquario.com/docs/reference/layout"}]}
```
