Skip to content

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 ​

bash
pnpm add pdf-kalem

Your first document ​

ts
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:

ts
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 fs

getBuffer() 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:

ts
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:

ts
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 ​

MIT licensed