PDF Toolkit
Create PDF documents from code, or from a table already on the page. The document and table engines run unchanged in the browser and in Node.
The toolkit is split into two entry points. fluxo-ui/pdf holds the document model, the drawing primitives, the table engine and the layout helpers; it depends on nothing outside the language, so the same code that builds a file in the browser builds it on a server. fluxo-ui/pdf/html adds the one piece that genuinely needs a DOM: reading a table that is already rendered on the page.
Nothing is fetched or evaluated at runtime. The fourteen standard typefaces are described by embedded metrics rather than downloaded font files, and a link whose scheme is not http, https, mailto or tel is discarded when the document is written.
Runs Anywhere
The document and table engines touch no browser API, so the same code produces files in the browser and on a Node server.
Typed Document API
Pages, text, images, vector paths, links and bookmarks behind a fully typed surface with no plugin registration step.
Table Engine
Column width solving, word wrapping, row and column spans, repeating headers and automatic pagination.
Live Table Export
Read a table already rendered on the page, optionally carrying its computed CSS into the document.
No Remote Code
Nothing is fetched or evaluated at runtime. Fonts are embedded metrics, and link schemes are checked before they are written.
Tree Shakes Cleanly
Every module resolves to its own chunk, so importing the colour helpers alone never pulls in the table or image code.
Getting Started
# The toolkit ships with the library
npm install fluxo-uiimport { PdfDocument, drawGrid, savePdf } from 'fluxo-ui/pdf';
const doc = new PdfDocument({
format: 'a4',
margin: 40,
metadata: { title: 'Monthly summary' },
});
doc.drawText('Monthly summary', 40, 50, { fontSize: 20, bold: true });
drawGrid(doc, {
startY: 90,
preset: 'ruled',
columns: [
{ key: 'region', header: 'Region' },
{ key: 'revenue', header: 'Revenue', width: 90, style: { align: 'right' } },
],
body: [
{ region: 'Nordics', revenue: '$42,257' },
{ region: 'East Asia', revenue: '$66,014' },
],
});
savePdf(doc.build(), 'summary.pdf');What comes from where
// Document, drawing, tables, layout helpers and output — isomorphic
import {
PdfDocument,
drawGrid,
createFlow,
applyHeaderBand,
applyFooterBand,
applyWatermark,
stampPageNumbers,
savePdf,
toBlob,
toDataUrl,
} from 'fluxo-ui/pdf';
// Reading tables out of the DOM — browser only
import { extractTable, buildPdfFromTable, exportTableToPdf } from 'fluxo-ui/pdf/html';Invoice
Invoice Document
A complete billing document: accent band, sender and recipient blocks, a ruled line-item table, computed tax summary, and payment footer.
import { PdfDocument, drawGrid } from 'fluxo-ui/pdf';
import { savePdf } from 'fluxo-ui/pdf';
const doc = new PdfDocument({
format: 'a4',
margin: 44,
metadata: { title: 'Invoice INV-2026-0418', author: 'Northwind Studio Ltd.' },
});
const right = doc.pageWidth - doc.margin.right;
doc.drawRectangle(0, 0, doc.pageWidth, 8, { fillColor: '#4f46e5' });
doc.drawText('Northwind Studio Ltd.', doc.margin.left, 56, { fontSize: 17, bold: true });
doc.drawText('INVOICE', right, 56, {
fontSize: 26,
bold: true,
color: '#4f46e5',
align: 'right',
anchor: 'point',
});
const table = drawGrid(doc, {
startY: 258,
preset: 'ruled',
columns: [
{ key: 0, header: 'Description' },
{ key: 1, header: 'Qty', width: 44, style: { align: 'right' } },
{ key: 2, header: 'Unit price', width: 78, style: { align: 'right' } },
{ key: 3, header: 'Tax', width: 46, style: { align: 'right' } },
{ key: 4, header: 'Amount', width: 86, style: { align: 'right' } },
],
body: lineItems,
});
doc.drawText('Total due', right, table.finalY + 46, { bold: true, align: 'right', anchor: 'point' });
savePdf(doc.build(), 'invoice.pdf');Account Statement
Account Statement with Summary Cards
Tinted summary tiles drawn from primitives above a running-balance ledger, with debits and credits coloured by sign and a movement footer.
import { PdfDocument, drawGrid, stampPageNumbers } from 'fluxo-ui/pdf';
const doc = new PdfDocument({ format: 'a4', margin: 40 });
// Summary cards drawn with primitives
cards.forEach((card, index) => {
const x = 40 + index * 132;
doc.drawRectangle(x, 100, 122, 62, { fillColor: card.tint, radius: 8 });
doc.drawText(card.label, x + 12, 114, { fontSize: 8, color: card.ink });
doc.drawText(card.value, x + 12, 132, { fontSize: 16, bold: true, color: card.ink });
});
drawGrid(doc, {
startY: 190,
preset: 'ruled',
columns: [
{ key: 'date', header: 'Date', width: 74 },
{ key: 'description', header: 'Description' },
{ key: 'debit', header: 'Debit', width: 78, style: { align: 'right' } },
{ key: 'credit', header: 'Credit', width: 78, style: { align: 'right' } },
{ key: 'balance', header: 'Balance', width: 84, style: { align: 'right' } },
],
body: entries,
});
stampPageNumbers(doc);Text and Shapes
Text, Alignment and Vector Shapes
Fonts, wrapping, character spacing, decorations, alignment anchors, rounded rectangles, ellipses, polygons, dashed rules and bezier paths.
import { PdfDocument } from 'fluxo-ui/pdf';
const doc = new PdfDocument({ format: 'a4', margin: 40 });
doc.drawText('Typography', 40, 60, { fontSize: 22, bold: true });
doc.drawText('Helvetica, Times and Courier are always available.', 40, 88, {
fontSize: 11,
font: 'Times',
italic: true,
color: '#475569',
});
doc.drawText('Wrapped paragraph text flows inside a fixed measure.', 40, 120, {
maxWidth: 240,
lineHeightFactor: 1.4,
});
doc.drawText('Centred', 297, 200, { align: 'center', anchor: 'point', fontSize: 14 });
doc.drawText('Right edge', 555, 200, { align: 'right', anchor: 'point', fontSize: 14 });
doc.drawRectangle(40, 240, 140, 70, { fillColor: '#6366f1', radius: 10 });
doc.drawCircle(250, 275, 35, { fillColor: '#f97316', strokeColor: '#7c2d12', lineWidth: 2 });
doc.drawPolygon(
[{ x: 330, y: 240 }, { x: 400, y: 310 }, { x: 330, y: 310 }],
{ fillColor: '#10b981', opacity: 0.75 },
);
doc.drawLine(40, 340, 555, 340, { strokeColor: '#cbd5e1', dashPattern: [5, 3] });
doc.drawPath((path) => {
path.moveTo(40, 380).curveTo(140, 350, 240, 430, 340, 380);
}, { strokeColor: '#db2777', lineWidth: 2.5 });Table Presets
Table Presets
Every built-in table preset rendered from the same data: lined, ruled, zebra, minimal and boxed.
import { PdfDocument, drawGrid } from 'fluxo-ui/pdf';
const doc = new PdfDocument({ format: 'a4' });
// Five built-in presets: lined, ruled, zebra, minimal, boxed
let y = 60;
for (const preset of ['lined', 'ruled', 'zebra', 'minimal', 'boxed'] as const) {
const result = drawGrid(doc, {
startY: y,
preset,
columns: [
{ key: 'sku', header: 'SKU', width: 70 },
{ key: 'product', header: 'Product' },
{ key: 'stock', header: 'Stock', width: 54, style: { align: 'right' } },
{ key: 'price', header: 'Price', width: 62, style: { align: 'right' } },
],
body: rows,
});
y = result.finalY + 26;
}Spanning Cells
Spanning Cells and Per-Cell Styling
Two-level merged headers using rowSpan and colSpan, a totals footer, and individual cells carrying their own background, colour and weight.
import { PdfDocument, drawGrid } from 'fluxo-ui/pdf';
drawGrid(doc, {
startY: 60,
preset: 'boxed',
head: [
[
{ value: 'Team', rowSpan: 2 },
{ value: 'First half', colSpan: 2, style: { align: 'center' } },
{ value: 'Second half', colSpan: 2, style: { align: 'center' } },
{ value: 'Total', rowSpan: 2, style: { align: 'right' } },
],
['Q1', 'Q2', 'Q3', 'Q4'],
],
body: [
['Platform', '18', '24', '31', '29', '102'],
['Design', '12', '9', '15', '18', '54'],
],
foot: [[{ value: 'All teams', colSpan: 5, style: { align: 'right' } }, '156']],
});Paginated Report
Paginated Landscape Report
Forty-eight rows flowing across pages with a repeating header row, a totals footer, running header and footer bands, and conditional cell colouring.
import { PdfDocument, drawGrid, applyHeaderBand, applyFooterBand } from 'fluxo-ui/pdf';
const doc = new PdfDocument({ format: 'a4', orientation: 'landscape', margin: 40 });
drawGrid(doc, {
startY: 80,
preset: 'zebra',
repeat: { header: 'everyPage', footer: 'lastPage' },
columns: [
{ key: 'region', header: 'Region' },
{ key: 'representative', header: 'Representative' },
{ key: 'units', header: 'Units', style: { align: 'right' } },
{ key: 'revenue', header: 'Revenue', style: { align: 'right' } },
{ key: 'growth', header: 'Growth', style: { align: 'right' } },
{ key: 'status', header: 'Status', style: { align: 'center' } },
],
body: salesRows,
foot: [[{ value: 'Total', colSpan: 3, style: { align: 'right' } }, total, '', '']],
onCellBeforeDraw: ({ cell }) => {
if (cell.section === 'body' && cell.columnIndex === 5) {
cell.style.textColor = statusColors[cell.text[0]] ?? '#334155';
}
},
});
applyHeaderBand(doc, { slots: [{ text: 'Regional Performance' }, { text: 'FY2026', align: 'right' }] });
applyFooterBand(doc, {
slots: [{ text: (page, total) => `Page ${page} of ${total}`, align: 'right' }],
ruleColor: '#e2e8f0',
ruleWidth: 0.5,
});Flowing Document
Flowing Multi-Section Document
Headings, paragraphs, rules and tables stacked in order. The composer tracks the cursor and starts a new page whenever the next block will not fit.
import { PdfDocument, createFlow, stampPageNumbers } from 'fluxo-ui/pdf';
const doc = new PdfDocument({ format: 'a4', margin: 48 });
const flow = createFlow(doc, { startY: 60, gap: 10 });
flow.heading('Annual Review', { fontSize: 22 });
flow.paragraph(summary, { fontSize: 10.5, lineHeightFactor: 1.5 });
flow.rule();
flow.heading('Territory breakdown', { fontSize: 14 });
flow.grid({
preset: 'ruled',
columns: [...],
body: rows,
});
flow.heading('Outlook', { fontSize: 14 });
flow.paragraph(outlook);
stampPageNumbers(doc);Certificate
Certificate
A landscape page with zero margins, decorative borders and corner rules. Type a recipient name and upload a seal image to see both placed in the document.
import { PdfDocument } from 'fluxo-ui/pdf';
const doc = new PdfDocument({ format: 'a4', orientation: 'landscape', margin: 0 });
const width = doc.pageWidth;
const centre = width / 2;
doc.drawRectangle(0, 0, width, doc.pageHeight, { fillColor: '#fffbeb' });
doc.drawRectangle(26, 26, width - 52, doc.pageHeight - 52, { strokeColor: '#b45309', lineWidth: 2.5 });
doc.drawText('CERTIFICATE OF COMPLETION', centre, 104, {
align: 'center',
anchor: 'point',
fontSize: 25,
bold: true,
color: '#78350f',
characterSpacing: 1.5,
});
doc.drawText(recipientName, centre, 200, {
align: 'center',
anchor: 'point',
fontSize: 34,
font: 'Times',
italic: true,
});
// A seal supplied as a PNG or JPEG, sized and centred on the page
if (sealImage) {
doc.drawImage(sealImage, { x: centre - 34, y: 318, width: 68, height: 68 });
} else {
doc.drawCircle(centre, 352, 34, { fillColor: '#fef3c7', strokeColor: '#b45309', lineWidth: 1.5 });
doc.drawText('SEAL', centre, 350, { align: 'center', anchor: 'point', fontSize: 10, bold: true });
}Pages and Units
Units, Page Sizes and Watermarks
A document measured in millimetres where each page chooses its own size, orientation and margin, finished with a watermark applied across every page.
import { PdfDocument, applyWatermark } from 'fluxo-ui/pdf';
// Work in millimetres instead of points
const doc = new PdfDocument({ unit: 'mm', format: 'a5', margin: 12 });
doc.drawText('A5 measured in millimetres', 12, 20, { fontSize: 14, bold: true });
doc.drawRectangle(12, 30, 60, 25, { fillColor: '#0ea5e9' });
// Each page can take its own size and orientation
doc.addPage({ format: 'letter', orientation: 'landscape' });
doc.drawText('Letter, landscape', 12, 20, { fontSize: 14, bold: true });
doc.addPage({ format: [120, 80] });
doc.drawText('Custom 120 x 80 mm', 12, 20, { fontSize: 12, bold: true });
applyWatermark(doc, { text: 'SPECIMEN', opacity: 0.12 });HTML Table Export
Export an HTML Table
Reads a table that is already on the page — text, colspans and optionally the computed CSS — and turns it into a PDF.
| SKU | Product | Category | Stock | Price |
|---|---|---|---|---|
| FRN-1042 | Oak reading chair | Furniture | 24 | $489 |
| FRN-1088 | Walnut side table | Furniture | 8 | $265 |
| LGT-2210 | Brass floor lamp | Lighting | 41 | $178 |
| LGT-2255 | Linen pendant shade | Lighting | Out of stock | $96 |
| TXT-3301 | Wool throw blanket | Textiles | 132 | $74 |
| TXT-3348 | Cotton cushion cover | Textiles | 87 | $32 |
| DEC-4419 | Ceramic vase, tall | Decor | 15 | $58 |
| DEC-4460 | Stoneware bowl set | Decor | 3 | $112 |
| Catalog total | 310 | $1,304 | ||
import { buildPdfFromTable, exportTableToPdf } from 'fluxo-ui/pdf/html';
// One call: read a live table from the DOM and save it
exportTableToPdf('#catalog-table', {
title: 'Product Catalog',
fileName: 'catalog.pdf',
// Keep the spacing, alignment and borders, but print in the preset's colours
extract: { inheritStyles: true, ignoreColors: true },
grid: { preset: 'ruled' },
});
// Or take the document first to keep editing it
const { doc } = buildPdfFromTable('#catalog-table', {
extract: { inheritStyles: true, includeHidden: false },
grid: { preset: 'zebra' },
});
doc.drawText('Reviewed by operations', 40, doc.pageHeight - 60, { fontSize: 9 });
savePdf(doc.build(), 'catalog.pdf');Interactive Builder
Build a Document from Form Input
Edit the title, page setup, table preset and line items. The preview regenerates from the same code a consumer would write.
import { PdfDocument, drawGrid } from 'fluxo-ui/pdf';
const doc = new PdfDocument({ format, orientation, margin: 40 });
doc.drawText(title, 40, 50, { fontSize: 18, bold: true });
drawGrid(doc, {
startY: 84,
preset, // lined | ruled | zebra | minimal | boxed
columns: [
{ key: 'item', header: 'Item' },
{ key: 'qty', header: 'Qty', width: 56, style: { align: 'right' } },
{ key: 'rate', header: 'Rate', width: 80, style: { align: 'right' } },
{ key: 'amount', header: 'Amount', width: 90, style: { align: 'right' } },
],
body: rows,
foot: [[{ value: 'Total', colSpan: 3, style: { align: 'right' } }, total]],
});Server Side
build() returns a Uint8Array, so a document can be written straight to disk, streamed in a response or attached to a message. Only the helpers that reach for Blob, URL or document — savePdf, printPdf, openPdfInNewTab — require a browser.
import { writeFileSync } from 'node:fs';
import { PdfDocument, drawGrid } from 'fluxo-ui/pdf';
// No DOM is involved, so this runs unchanged on a server
const doc = new PdfDocument({ format: 'a4', margin: 40 });
doc.drawText('Nightly batch', 40, 50, { fontSize: 18, bold: true });
drawGrid(doc, {
startY: 84,
preset: 'zebra',
columns: [
{ key: 'account', header: 'Account' },
{ key: 'balance', header: 'Balance', width: 90, style: { align: 'right' } },
],
body: await loadAccountRows(),
});
writeFileSync('statement.pdf', doc.build());Document Options
unit'pt' | 'mm' | 'cm' | 'in' | 'px' | 'pc'"'pt'"Measurement unit for every coordinate and size passed to the document
unit'pt' | 'mm' | 'cm' | 'in' | 'px' | 'pc'"'pt'"Measurement unit for every coordinate and size passed to the document
formatstring | [number, number] | { width: number; height: number }"'a4'"Page size preset name (a0-a8, b3-b6, c3-c6, letter, legal, tabloid, ledger, executive, folio, statement, creditcard) or explicit dimensions
formatstring | [number, number] | { width: number; height: number }"'a4'"Page size preset name (a0-a8, b3-b6, c3-c6, letter, legal, tabloid, ledger, executive, folio, statement, creditcard) or explicit dimensions
orientation'portrait' | 'landscape'"'portrait'"Page orientation applied to the default page size
orientation'portrait' | 'landscape'"'portrait'"Page orientation applied to the default page size
marginnumber | number[] | { top, right, bottom, left }"40pt"Page margin used as the default content area for every page
marginnumber | number[] | { top, right, bottom, left }"40pt"Page margin used as the default content area for every page
metadataDocumentMetadataTitle, author, subject, keywords, creator and dates written to the document information dictionary
metadataDocumentMetadataTitle, author, subject, keywords, creator and dates written to the document information dictionary
compressboolean"true"Compress page content streams
compressboolean"true"Compress page content streams
defaultFontstring"'Helvetica'"Font family used until setFont is called
defaultFontstring"'Helvetica'"Font family used until setFont is called
defaultFontSizenumber"11"Font size used until setFontSize is called
defaultFontSizenumber"11"Font size used until setFontSize is called
lineHeightFactornumber"1.15"Multiplier applied to the font size to derive line height
lineHeightFactornumber"1.15"Multiplier applied to the font size to derive line height
languagestringDocument language tag exposed to assistive technology
languagestringDocument language tag exposed to assistive technology
Text Options
fontstringFont family for this call, falling back to the document font
fontstringFont family for this call, falling back to the document font
fontSizenumberFont size in document units
fontSizenumberFont size in document units
boldboolean"false"Select the bold face of the family
boldboolean"false"Select the bold face of the family
italicboolean"false"Select the italic or oblique face of the family
italicboolean"false"Select the italic or oblique face of the family
colorColorInputText colour as a hex string, CSS colour function, named colour, packed integer or channel array
colorColorInputText colour as a hex string, CSS colour function, named colour, packed integer or channel array
align'left' | 'center' | 'right' | 'justify'"'left'"Horizontal alignment within the measure defined by maxWidth
align'left' | 'center' | 'right' | 'justify'"'left'"Horizontal alignment within the measure defined by maxWidth
anchor'box' | 'point'"'box'"Whether x is the left edge of an alignment box or the alignment point itself
anchor'box' | 'point'"'box'"Whether x is the left edge of an alignment box or the alignment point itself
maxWidthnumberWrap the text to this measure
maxWidthnumberWrap the text to this measure
maxLinesnumberTruncate after this many lines and append the ellipsis marker
maxLinesnumberTruncate after this many lines and append the ellipsis marker
ellipsisstring"'…'"Marker appended when text is truncated
ellipsisstring"'…'"Marker appended when text is truncated
lineHeightFactornumberLine height multiplier for this call
lineHeightFactornumberLine height multiplier for this call
characterSpacingnumber"0"Extra space inserted between characters
characterSpacingnumber"0"Extra space inserted between characters
wordSpacingnumber"0"Extra space inserted at each space character
wordSpacingnumber"0"Extra space inserted at each space character
rotationnumber"0"Rotation in degrees about the text origin
rotationnumber"0"Rotation in degrees about the text origin
baseline'alphabetic' | 'top' | 'middle' | 'bottom'"'alphabetic'"Which part of the glyph box the y coordinate refers to
baseline'alphabetic' | 'top' | 'middle' | 'bottom'"'alphabetic'"Which part of the glyph box the y coordinate refers to
underlineboolean"false"Draw a rule beneath each line
underlineboolean"false"Draw a rule beneath each line
strikeThroughboolean"false"Draw a rule through each line
strikeThroughboolean"false"Draw a rule through each line
opacitynumber"1"Text opacity between 0 and 1
opacitynumber"1"Text opacity between 0 and 1
Shape Options
fillColorColorInputInterior colour; omit to leave the shape unfilled
fillColorColorInputInterior colour; omit to leave the shape unfilled
strokeColorColorInputOutline colour; omit to leave the shape unstroked
strokeColorColorInputOutline colour; omit to leave the shape unstroked
lineWidthnumberOutline width in document units
lineWidthnumberOutline width in document units
lineCap'butt' | 'round' | 'square'"'butt'"How open path ends are drawn
lineCap'butt' | 'round' | 'square'"'butt'"How open path ends are drawn
lineJoin'miter' | 'round' | 'bevel'"'miter'"How corners between segments are drawn
lineJoin'miter' | 'round' | 'bevel'"'miter'"How corners between segments are drawn
dashPatternnumber[]Alternating dash and gap lengths
dashPatternnumber[]Alternating dash and gap lengths
opacitynumber"1"Opacity applied to both fill and stroke
opacitynumber"1"Opacity applied to both fill and stroke
radiusnumber"0"Corner radius, accepted by drawRectangle
radiusnumber"0"Corner radius, accepted by drawRectangle
Table Options
columnsColumnDefinition[]Column keys, headers, widths and per-column styles; inferred from the data when omitted
columnsColumnDefinition[]Column keys, headers, widths and per-column styles; inferred from the data when omitted
headRowDefinition[]Header rows; derived from the column headers when omitted
headRowDefinition[]Header rows; derived from the column headers when omitted
bodyRowDefinition[]Body rows as arrays or as objects keyed by column key
bodyRowDefinition[]Body rows as arrays or as objects keyed by column key
footRowDefinition[]Footer rows; derived from the column footers when omitted
footRowDefinition[]Footer rows; derived from the column footers when omitted
preset'lined' | 'ruled' | 'zebra' | 'minimal' | 'boxed'"'lined'"Built-in visual treatment for the table
preset'lined' | 'ruled' | 'zebra' | 'minimal' | 'boxed'"'lined'"Built-in visual treatment for the table
paletteGridPaletteColour overrides layered on top of the chosen preset
paletteGridPaletteColour overrides layered on top of the chosen preset
stylesSectionStylesStyle overrides for all cells, a section, the stripe rows, or named columns
stylesSectionStylesStyle overrides for all cells, a section, the stripe rows, or named columns
startYnumberVertical position where the table begins; defaults to the top margin
startYnumberVertical position where the table begins; defaults to the top margin
marginnumber | number[] | EdgeInsetsMargin used to compute the available width and page break threshold
marginnumber | number[] | EdgeInsetsMargin used to compute the available width and page break threshold
widthnumber | 'full' | 'auto'"'full'"Target table width
widthnumber | 'full' | 'auto'"'full'"Target table width
align'left' | 'center' | 'right'"'left'"Horizontal placement of the table within the content area
align'left' | 'center' | 'right'"'left'"Horizontal placement of the table within the content area
repeat{ header?: 'everyPage' | 'firstPage' | 'never'; footer?: 'everyPage' | 'lastPage' | 'never' }"{ header: 'everyPage', footer: 'lastPage' }"Whether header and footer rows repeat when the table spans pages
repeat{ header?: 'everyPage' | 'firstPage' | 'never'; footer?: 'everyPage' | 'lastPage' | 'never' }"{ header: 'everyPage', footer: 'lastPage' }"Whether header and footer rows repeat when the table spans pages
minRowHeightnumber"0"Minimum height applied to every row
minRowHeightnumber"0"Minimum height applied to every row
outerBorderWidthnumber"0"Width of a border drawn around the whole table
outerBorderWidthnumber"0"Width of a border drawn around the whole table
onCellBeforeDraw(context: CellRenderContext) => boolean | voidCalled before each cell is painted; mutate the style or return false to skip the cell
onCellBeforeDraw(context: CellRenderContext) => boolean | voidCalled before each cell is painted; mutate the style or return false to skip the cell
onCellAfterDraw(context: CellRenderContext) => voidCalled after each cell is painted, for drawing decorations over it
onCellAfterDraw(context: CellRenderContext) => voidCalled after each cell is painted, for drawing decorations over it
onPageStart(context: PageRenderContext) => voidCalled when the table starts laying out on a page
onPageStart(context: PageRenderContext) => voidCalled when the table starts laying out on a page
onPageEnd(context: PageRenderContext) => voidCalled when the table finishes on a page
onPageEnd(context: PageRenderContext) => voidCalled when the table finishes on a page
Column Options
A column’s style applies to its header, body and footer cells together, so setting align once lines a column’s heading up with its values. headerStyle and footerStyle layer on top of it when one section needs to differ.
keystring | numberIdentifies the column; object rows are read by this key and array rows by position
keystring | numberIdentifies the column; object rows are read by this key and array rows by position
headerCellValueHeader cell content when no explicit head rows are supplied
headerCellValueHeader cell content when no explicit head rows are supplied
footerCellValueFooter cell content when no explicit foot rows are supplied
footerCellValueFooter cell content when no explicit foot rows are supplied
widthnumber | 'auto' | 'shrink'"'auto'"Fixed width in document units, or let the solver size the column from its content
widthnumber | 'auto' | 'shrink'"'auto'"Fixed width in document units, or let the solver size the column from its content
minWidthnumberLower bound the solver may not shrink past
minWidthnumberLower bound the solver may not shrink past
maxWidthnumberUpper bound the solver may not grow past
maxWidthnumberUpper bound the solver may not grow past
stylePartial<CellStyle>Styles for the whole column — header, body and footer cells alike. Set align here to align a column's header with its values
stylePartial<CellStyle>Styles for the whole column — header, body and footer cells alike. Set align here to align a column's header with its values
headerStylePartial<CellStyle>Overrides layered on top of style for this column's header cells only
headerStylePartial<CellStyle>Overrides layered on top of style for this column's header cells only
footerStylePartial<CellStyle>Overrides layered on top of style for this column's footer cells only
footerStylePartial<CellStyle>Overrides layered on top of style for this column's footer cells only
format(value: CellValue, rowIndex: number) => stringFormats each body value before it is measured and drawn
format(value: CellValue, rowIndex: number) => stringFormats each body value before it is measured and drawn
Extract Options
includeHiddenboolean"false"Include rows and cells whose computed style hides them
includeHiddenboolean"false"Include rows and cells whose computed style hides them
inheritStylesboolean"false"Carry the table's computed CSS into the generated document
inheritStylesboolean"false"Carry the table's computed CSS into the generated document
ignoreColorsboolean"false"When inheriting styles, keep the spacing, alignment and borders but leave the page's colours behind so the table prints in the preset's palette
ignoreColorsboolean"false"When inheriting styles, keep the spacing, alignment and borders but leave the page's colours behind so the table prints in the preset's palette
viewWindowWindow used to resolve computed styles; defaults to the table's own view
viewWindowWindow used to resolve computed styles; defaults to the table's own view
skipColumnsnumber[]Column indices to leave out of the export
skipColumnsnumber[]Column indices to leave out of the export
includeRow(row: HTMLTableRowElement) => booleanReturn false to leave a row out of the export
includeRow(row: HTMLTableRowElement) => booleanReturn false to leave a row out of the export
includeCell(cell: HTMLTableCellElement) => booleanReturn false to leave a cell out of the export
includeCell(cell: HTMLTableCellElement) => booleanReturn false to leave a cell out of the export
transformText(text: string, element: HTMLTableCellElement) => stringRewrite each cell's text before it is measured
transformText(text: string, element: HTMLTableCellElement) => stringRewrite each cell's text before it is measured
Features
Runs Anywhere
The document and table engines touch no browser API, so the same code produces files in the browser and on a Node server.
Typed Document API
Pages, text, images, vector paths, links and bookmarks behind a fully typed surface with no plugin registration step.
Table Engine
Column width solving, word wrapping, row and column spans, repeating headers and automatic pagination.
Live Table Export
Read a table already rendered on the page, optionally carrying its computed CSS into the document.
No Remote Code
Nothing is fetched or evaluated at runtime. Fonts are embedded metrics, and link schemes are checked before they are written.
Tree Shakes Cleanly
Every module resolves to its own chunk, so importing the colour helpers alone never pulls in the table or image code.