Getting started
Status: early alpha. The API is not yet stable and may change between versions.
pdf-kalem is a widget-tree UI library for document-style layout and generation. You describe content as a tree of widgets — pages, containers, rows, columns, text, images, links, tables, and SVG paths — run Yoga for flexbox layout, and render the tree through a backend-specific renderer. The PDF backend is pdf-lib. The design is heavily inspired by Flutter's widget-tree model, adapted to the document/PDF domain.
Install
pnpm add pdf-kalemYour first document
import { PdfDoc, PageSize } from 'pdf-kalem';
import { Page, Text, Container } from 'pdf-kalem/widgets';
import { fromHex } from 'pdf-kalem/utils/color-utils';
const doc = new PdfDoc({
size: PageSize.LETTER,
children: [
Page({
padding: 24,
children: [
Text('Hello world', { size: 18 }),
Container({
width: 200,
height: 80,
bgColor: fromHex('#FFCC00'),
}),
],
}),
],
});
const pdfBytes = await doc.save();Widgets are created through plain factory functions (Page(...), Text(...)) — no classes to instantiate, no new outside PdfDoc. Layout is Yoga flexbox: Row, Column, gap, flexGrow, % widths, and absolute positioning all work as you'd expect.
Output helpers
PdfDoc is the document model: it owns the page tree, theme, metadata, and lifecycle hooks. Runtime helpers cover both browser and Node:
const bytes = await doc.save(); // Uint8Array
const blob = await doc.getBlob(); // browser Blob
await doc.download('invoice.pdf'); // browser download
await doc.print(); // browser print dialog
const buffer = await doc.getBuffer(); // Node Buffer
await doc.writeToFile('invoice.pdf'); // Node fsgetBuffer() and writeToFile() are intended for Node runtimes; download(), getBlob(), and print() for the browser. print() loads the PDF into a hidden iframe and opens the browser's print dialog — Safari's PDF viewer doesn't support programmatic printing, so download-and-print manually there.
Preview: PDF → PNG
The pdf-kalem/preview subpath rasterizes the exact bytes save() produced — the pixels cannot lie about the output, which makes it the fast visual feedback loop for layout work:
import { renderToPng } from 'pdf-kalem/preview';
const bytes = await doc.save();
const png = await renderToPng(bytes, { page: 0, dpi: 144 }); // → PNG Uint8Array
await writeFile('preview.png', png);Node-only; the adapters (pdfjs-dist, @napi-rs/canvas) are optional peer dependencies loaded on first use — install them with pnpm add -D pdfjs-dist @napi-rs/canvas when you want previews.
Metadata
Pass document metadata at construction:
const doc = new PdfDoc({
size: PageSize.LETTER,
meta: {
title: 'Q1 Report',
author: 'Northwind Labs',
subject: 'Platform, reliability, and delivery',
language: 'en-US',
keywords: ['report', 'q1'],
creator: 'pdf-kalem',
producer: 'pdf-lib',
},
children: [/* ... */],
});Next steps
- Try the widget playground — edit a live document in your browser.
- Skim the widgets and theming guides.
- Converting markdown? Jump to the markdown guide or the markdown playground.
- Copy an example — self-contained projects (hello-world, invoice, report, markdown converter) structured to be adapted to your own documents.