@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?)
function layout(options?: LayoutOptions): Target<'layout', Promise<Layout>>A render target like any other, resolving the display list instead of bytes.
LayoutOptions
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
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
type Op = RectOp | LineOp | ImageOp | TextOp | MarkOpOne 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
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: numberpaint 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:
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.