av6-pdf-engine
v4.0.0
Published
PDF engine for AV6 to create dynamic pdf from configure json
Readme
av6-pdf-engine
av6-pdf-engine is a JSON-driven PDF layout engine built on top of PDFKit.
Describe your document once and render it to a file or in-memory Buffer from any Node.js service.
Table of contents
- Installation
- Quick start
- Document definition
- Styles
- Header & footer
- Page background & watermark
- Box model
- Block catalog
- Asset handling
- Rendering targets
- Built-in fonts
- Engine behaviour notes
Installation
npm install av6-pdf-engine
# or
pnpm add av6-pdf-engine
# or
yarn add av6-pdf-engineQuick start
import { renderCustomPdfToBuffer, renderCustomPdf, CustomDocDefinition } from "av6-pdf-engine";
const doc: CustomDocDefinition = {
pageSize: "A4",
margins: { top: 70, right: 40, bottom: 60, left: 40 },
styles: {
heading: { fontSize: 16, bold: true, color: "#0B4A6F" },
label: { fontSize: 8, bold: true, color: "#6B7280" },
},
header: {
paddingLeft: 40,
paddingTop: 16,
backgroundBlocks: [
{ type: "shape", shape: "rect", fillColor: "#0B4A6F", height: 70 },
],
blocks: [
{ type: "text", text: "ACME Corp — Invoice #1234", fontSize: 13, bold: true, color: "#FFFFFF" },
],
},
footer: (page, size) => ({
blocks: [{ type: "text", text: `Page ${page}`, align: "center", fontSize: 8 }],
}),
watermark: { text: "DRAFT", mode: "all", opacity: 0.07 },
content: [
{ type: "text", text: "Hello, PDF!", style: "heading", marginTop: 16 },
{ type: "line", lineWidth: 1, color: "#0B4A6F", marginBottom: 12 },
{
type: "table",
widths: ["*", 80, 80],
headerRows: 1,
body: [
[{ text: "Item", bold: true }, { text: "Qty", bold: true, align: "right" }, { text: "Total", bold: true, align: "right" }],
[{ text: "Consulting" }, { text: "12", align: "right" }, { text: "$3,000", align: "right" }],
],
},
{ type: "signature", height: 80, label: "Authorized signature" },
],
};
// Write to disk
await renderCustomPdf(doc, "./tmp/invoice.pdf");
// Or capture a Buffer (HTTP responses, cloud storage, etc.)
const buf = await renderCustomPdfToBuffer(doc);Document definition
CustomDocDefinition is the root object passed to both render functions.
| Property | Type | Required | Description |
|---|---|---|---|
| pageSize | string \| [width, height] | No | Any PDFKit page size ("A4", "LETTER", "A5", etc.) or a [w, h] tuple in points. Defaults to "A4". |
| pageOrientation | "portrait" \| "landscape" | No | Defaults to portrait. |
| margins | { top, right, bottom, left } | Yes | All four margins in points. top doubles as the header band height; bottom doubles as the footer band height. |
| fonts | FontRegistration[] | No | Custom TTF/OTF fonts to register before rendering. See Custom fonts. |
| styles | Record<string, StyleDef> | No | Named style map. Any block with a style property references an entry here. |
| header | HeaderDef | No | Rendered inside the top margin band on every page. |
| footer | FooterInput | No | Rendered inside the bottom margin band. Accepts a FooterDef, a Block, a Block[], or a callback. |
| pageBackground | PageBackgroundDef | No | Image painted behind all content on every page. |
| watermark | WatermarkDef | No | Centered text, raster-image, or SVG overlay. |
| content | Block[] | Yes | Ordered list of blocks rendered top-to-bottom with automatic pagination. |
| defaultImage | string \| Buffer | No | Fallback image used whenever an image, qr, or barcode block fails to load its source. |
Custom fonts
fonts: [
{ name: "Inter", src: "./assets/Inter-Regular.ttf" },
{ name: "Inter-Bold", src: "./assets/Inter-Bold.ttf" },
],Once registered, use the name in any font property: { type: "text", font: "Inter", text: "Hello" }.
Styles
StyleDef is a reusable object referenced by name via the style property on any text-bearing block or table cell.
styles: {
h1: { fontSize: 20, bold: true, color: "#0B4A6F" },
label: { fontSize: 8, bold: true, color: "#6B7280" },
mono: { font: "Courier", fontSize: 9 },
},| Property | Type | Description |
|---|---|---|
| fontSize | number | Font size in points. |
| bold | boolean | Bold weight. |
| italic | boolean | Italic style. |
| underline | boolean | Underline decoration. |
| strike | boolean | Strikethrough decoration. |
| color | string | Text fill color (hex, rgb, named). |
| align | "left" \| "center" \| "right" \| "justify" | Horizontal alignment. |
| lineGap | number | Extra spacing between lines in points. |
| font | FontName | Built-in or registered font name. |
| link | string | URL to attach to the text as a hyperlink. |
A block can reference multiple styles as an array; properties are merged left-to-right, with the block's own properties taking precedence:
{ type: "text", text: "Warning!", style: ["label", "highlight"] }Header & footer
Header (HeaderDef)
The header is rendered inside the top margin band on every engine-managed page. Long text and list items are split by the engine so each page receives the complete page lifecycle.
| Property | Type | Description |
|---|---|---|
| blocks | Block[] | Optional. Foreground blocks rendered inside the band. Omit for a background-only header. |
| backgroundBlocks | Block[] | Blocks rendered behind blocks — useful for shapes, waves, or filled rectangles. |
| backgroundColor | string | Solid fill behind the entire band. |
| backgroundImage | string \| Buffer | Image painted to cover the entire band. |
| backgroundOpacity | number | Opacity for backgroundColor / backgroundImage (0–1). |
| visible | boolean | Set false to suppress the header entirely. |
| marginTop/Bottom/Left/Right | number | Outer spacing around the band content. |
| padding / paddingTop/Right/Bottom/Left | number \| BoxSpacing | Inner spacing before the foreground blocks. |
header: {
paddingTop: 14,
paddingLeft: 40,
backgroundBlocks: [
{ type: "shape", shape: "rect", fillColor: "#0B4A6F", height: 70 },
],
blocks: [
{ type: "text", text: "My Company", bold: true, color: "#FFFFFF" },
],
},Footer (FooterInput)
The footer accepts four different shapes:
1. FooterDef — same options as HeaderDef:
footer: {
blocks: [{ type: "text", text: "© 2025 Acme Corp", align: "center", fontSize: 8 }],
}2. Single Block:
footer: { type: "text", text: "Confidential", align: "right", fontSize: 8 }3. Block array:
footer: [
{ type: "line", lineWidth: 0.5, color: "#D1D5DB", marginBottom: 4 },
{ type: "text", text: "Page footer text", fontSize: 8 },
]4. Callback — receives (pageNumber, pageSize):
footer: (page, size) => ({
blocks: [
{ type: "text", text: `Page ${page}`, align: "center", fontSize: 8 },
],
}),The callback form is the only way to include dynamic per-page data like page numbers.
Page background & watermark
Page background
Painted before the header and content on every new page.
pageBackground: {
src: "./assets/letterhead.png", // path, Buffer, or HTTPS URL
opacity: 0.05,
},Watermark
Centered text, raster-image, or SVG overlay drawn on selected pages. Existing text watermarks remain unchanged:
watermark: {
text: "CONFIDENTIAL",
mode: "exceptFirst", // see modes below
opacity: 0.07,
fontSize: 55,
color: "gray",
angle: 45,
},Raster images and SVGs use src, a required viewport size, and default to no rotation:
watermark: {
src: "./assets/company-mark.png", // PNG/JPEG path, URL, data URI, or Buffer
width: 240,
height: 120,
opacity: 0.08,
angle: 0,
mode: "all",
},
// SVG paths, URLs, data URIs, Buffers, and inline markup are rendered as vectors.
watermark: {
src: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80">...</svg>`,
width: 200,
height: 80,
opacity: 0.06,
},Image watermarks are centered on the page. Their width and height are required; opacity defaults to 0.06 and angle defaults to 0. Text watermark angle continues to default to 45 degrees.
| mode | Pages that receive the watermark |
|---|---|
| "all" (default) | Every page |
| "first" | First page only |
| "last" | Last page only |
| "firstLast" | First and last pages |
| "exceptFirst" | All pages except the first |
| "exceptLast" | All pages except the last |
| "exceptFirstLast" | All pages except first and last |
Box model
Every block type inherits BaseBlock, which adds a box model layer on top of the block's own rendering. This lets you attach a background, an image, or child blocks behind any block, and control inner spacing with padding.
| Property | Type | Description |
|---|---|---|
| visible | boolean | false skips rendering entirely (block is also excluded from height measurement). |
| backgroundColor | string | Filled rectangle drawn behind the block's content area. |
| backgroundImage | string \| Buffer | Image stretched to cover the content area. |
| backgroundOpacity | number | Opacity applied to backgroundColor / backgroundImage. |
| backgroundBlocks | Block[] | Arbitrary blocks rendered in a separate background layer before the main content. |
| padding | number \| { top, right, bottom, left } | Uniform or per-side inner spacing. |
| paddingTop/Right/Bottom/Left | number | Individual padding overrides. |
{
type: "text",
text: "Highlighted note",
fontSize: 9,
backgroundColor: "#EFF6FF",
padding: 10,
marginTop: 8,
marginBottom: 8,
}Block catalog
All blocks accept the BaseBlock properties listed in Box model in addition to their own properties.
text
Renders a paragraph of text.
| Property | Type | Default | Description |
|---|---|---|---|
| text | string | — | Required. Content to render. |
| fontSize | number | 10 | Size in points. |
| lineGap | number | 4 | Extra vertical space between lines and after the block. |
| align | Alignment | "left" | "left", "center", "right", "justify". |
| bold | boolean | false | |
| italic | boolean | false | |
| underline | boolean | false | |
| strike | boolean | false | Strikethrough. |
| link | string | — | URL hyperlink attached to the text. |
| color | string | "black" | Fill color. |
| font | FontName | auto | Built-in or registered font. Auto-selects bold/italic variant when those flags are set. |
| style | string \| string[] | — | Named style reference(s) from the styles map. |
| marginTop | number | 0 | Space above the block. |
| marginBottom | number | 0 | Space below the block. |
| marginLeft | number | 0 | Left indent (reduces available width). |
| marginRight | number | 0 | Right indent (reduces available width). |
{ type: "text", text: "Hello world", bold: true, marginTop: 12, marginBottom: 8, align: "center" }image
Renders a raster image (PNG, JPEG).
| Property | Type | Default | Description |
|---|---|---|---|
| src | string \| Buffer | — | File path, HTTPS URL, or Buffer. Falls back to defaultImage on error. |
| width | number | 80 | Render width in points. |
| height | number | 50 | Render height in points. |
| align | Alignment | "left" | Horizontal alignment within the available width. |
| marginTop/Bottom/Left/Right | number | 0 | |
{ type: "image", src: "https://example.com/logo.png", width: 120, height: 60, align: "right" }svg
Renders SVG markup directly into the PDF as vectors. The source may be inline markup, a local file, an HTTP(S) URL, an SVG data URI, or a Buffer.
| Property | Type | Default | Description |
|---|---|---|---|
| src | string \| Buffer | — | Required. SVG source. |
| width | number | 80 | Viewport width in points. |
| height | number | 50 | Viewport height in points. |
| align | "left" \| "center" \| "right" | "left" | Horizontal alignment within the available width. |
| preserveAspectRatio | string | SVG default | Overrides the SVG viewport alignment/scaling rule. |
| marginTop/Bottom/Left/Right | number | 0 | |
{
type: "svg",
src: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 40">...</svg>`,
width: 120,
height: 40,
align: "right",
}SVGs should be self-contained. Embedded PNG/JPEG data URIs are supported, but external images or external SVG documents referenced from inside the markup are not fetched.
qr
Generates a QR code via qrcode and renders it as an image.
| Property | Type | Default | Description |
|---|---|---|---|
| value | string | — | Data to encode. |
| size | number | 80 | Width and height of the rendered square in points. |
| qrVersion | number | auto | QR version (1–40). |
| errorCorrectionLevel | "L" \| "M" \| "Q" \| "H" | "M" | Error correction level. |
| align | Alignment | "left" | |
| marginTop/Bottom/Left/Right | number | 0 | |
{ type: "qr", value: "https://acme.example.com/pay/1234", size: 80, errorCorrectionLevel: "H" }barcode
Generates a 1D barcode via bwip-js.
| Property | Type | Default | Description |
|---|---|---|---|
| value | string | — | Data to encode. |
| bcType | BarcodeType | "CODE128" | "CODE128", "EAN13", "CODE39", "ITF", "CODE93". |
| width | number | auto | Render width in points. |
| height | number | 40 | Render height in points. |
| align | Alignment | "left" | |
| orientation | "horizontal" \| "vertical" | "horizontal" | |
| rotate | number | 0 | Rotation in degrees. |
| scale | number | — | bwip-js scale factor. |
| barHeight | number | — | Bar height hint passed to bwip-js. |
| includetext | boolean | false | Render the human-readable text below the bars. |
| textalign | "left" \| "center" \| "right" | "center" | Alignment of the human-readable text. |
| marginTop/Bottom/Left/Right | number | 0 | |
{ type: "barcode", value: "5901234123457", bcType: "EAN13", width: 160, height: 60, includetext: true }line
Draws a horizontal rule.
| Property | Type | Default | Description |
|---|---|---|---|
| lineWidth | number | 1 | Stroke width in points. |
| color | string | black | Stroke color. |
| marginTop/Bottom/Left/Right | number | 0 | |
{ type: "line", lineWidth: 0.5, color: "#E5E7EB", marginTop: 8, marginBottom: 8 }shape
Draws a vector shape. All shapes share the same property set; only the shape type determines which properties are used.
| Property | Type | Default | Description |
|---|---|---|---|
| shape | ShapeType | — | Required. One of "rect", "roundedRect", "circle", "ellipse", "line", "polygon", "path". |
| width | number | inner width | Shape width in points. |
| height | number | lineWidth | Shape height in points. |
| radius | number | 6 | Corner radius (roundedRect) or circle radius (circle). |
| points | { x, y }[] | — | Vertices for "polygon" (relative to the shape origin). |
| path | string | — | SVG path data for "path". Translated to the shape's (x, y) position. |
| fillColor | string | — | Fill color. |
| strokeColor | string | — | Stroke color. |
| lineWidth | number | 1 | Stroke width. |
| opacity | number | 1 | Opacity (0–1). |
| align | Alignment | "left" | Horizontal alignment of the shape within the available width. |
| marginTop/Bottom/Left/Right | number | 0 | |
// Filled navy rectangle
{ type: "shape", shape: "rect", fillColor: "#0B4A6F", height: 4 }
// Rounded badge
{ type: "shape", shape: "roundedRect", width: 60, height: 24, radius: 12, fillColor: "#D1FAE5", strokeColor: "#047857" }
// Triangle polygon
{
type: "shape", shape: "polygon",
width: 50, height: 50,
strokeColor: "#0B4A6F", lineWidth: 1.5,
points: [{ x: 25, y: 0 }, { x: 50, y: 50 }, { x: 0, y: 50 }],
}
// Custom SVG path (e.g. a wave)
{
type: "shape", shape: "path",
height: 40, fillColor: "#0B4A6F",
path: "M 0 0 L 200 0 L 200 20 C 150 40 50 10 0 30 Z",
}columns
Lays out child block arrays side-by-side.
| Property | Type | Default | Description |
|---|---|---|---|
| columns | Block[][] | — | Required. Each element is the block array for one column. |
| widths | (number \| "*")[] | equal split | Per-column widths. "*" means "take remaining space". |
| gap | number | 20 | Fixed gap between columns (used in "fixedGap" mode). |
| mode | "fixedGap" \| "spaceBetween" | "fixedGap" | "spaceBetween" distributes all remaining space between columns. |
| keepTogether | boolean | true | Measure the total columns height before rendering; if it does not fit, trigger a page break first. Set false only when overflow is intentionally handled by the surrounding layout. |
| marginTop/Bottom/Left/Right | number | 0 | |
{
type: "columns",
widths: [150, "*"],
gap: 24,
columns: [
[{ type: "text", text: "Left column" }],
[{ type: "text", text: "Right column" }],
],
}table
Renders a grid with optional header rows, borders, and per-cell styling.
TableBlock properties:
| Property | Type | Default | Description |
|---|---|---|---|
| widths | (number \| "*")[] | — | Required. Per-column widths. |
| body | TableCell[][] | — | Required. 2-D array of cells. |
| headerRows | number | 0 | Number of leading rows treated as headers (styled differently and repeated on new pages). |
| lineGap | number | 2 | Extra vertical gap inside cells. |
| layout.border | "all" \| "none" | "all" | Master border switch. |
| layout.hLineColor | string | | Horizontal line color. |
| layout.vLineColor | string | | Vertical line color. |
| layout.borderWidth | number | 1 | Default border width. |
| layout.borderDash | number[] | | Dash pattern, e.g. [2, 2]. |
| layout.rowStyles | Record<rowIndex, TableRowStyle> | | Per-row fillColor and border overrides. |
| layout.columnStyles | Record<colIndex, TableColumnStyle> | | Per-column fillColor and border overrides. |
| marginTop/Bottom/Left/Right | number | 0 | |
TableCell properties:
| Property | Type | Description |
|---|---|---|
| text | string | Cell text content. |
| blocks | Block[] | Rich block content inside the cell (replaces text). |
| bold / italic / underline / strike | boolean | Typography flags. |
| fontSize | number | |
| font | FontName | |
| color | string | Text color. |
| fillColor | string | Cell background. |
| align | Alignment | |
| style | StyleRef | Named style reference. |
| colSpan | number | Span across N columns. |
| rowSpan | number | Span across N rows. |
| paddingTop/Right/Bottom/Left | number | Cell inner padding. |
| border | TableCellBorder | Per-cell border override. |
{
type: "table",
widths: ["*", 80, 80],
headerRows: 1,
layout: { hLineColor: "#E5E7EB", rowStyles: { 0: { fillColor: "#0B4A6F" } } },
body: [
[{ text: "Item", bold: true, color: "#FFFFFF" }, { text: "Qty", bold: true, color: "#FFFFFF", align: "right" }, { text: "Total", bold: true, color: "#FFFFFF", align: "right" }],
[{ text: "Consulting" }, { text: "12", align: "right" }, { text: "$3,000", align: "right" }],
],
}keyValueGrid
Two-column key/value pairs with flexible orientation and styling.
| Property | Type | Default | Description |
|---|---|---|---|
| columns | KeyValueItem[][] | — | Required. Each sub-array is one grid column of items. |
| orientation | "horizontal" \| "vertical" | "horizontal" | "horizontal": key and value on the same row. "vertical": key stacked above value. |
| keyWidth | number \| "*" | 80 | Width reserved for the key in horizontal mode. "*" uses 35 % of the column. |
| columnWidths | (number \| "*")[] | equal | Widths of each grid column. |
| separator | string | "" | Text inserted between key and value (e.g. ":"). |
| rowGap | number | 4 | Vertical gap between rows. |
| verticalKeyValueGap | number | 2 | Gap between key and value in vertical orientation. |
| keyStyle / valueStyle | StyleRef | | Default named style for all keys / values. |
| keyTextStyle / valueTextStyle | InlineTextStyle | | Inline style overrides for keys / values. |
| keyAlign / valueAlign | Alignment | | |
| marginTop/Bottom/Left/Right | number | 0 | |
KeyValueItem properties:
| Property | Type | Description |
|---|---|---|
| key | string | Label text. |
| value | string | Value text. |
| keyStyle / valueStyle | StyleRef | Per-item style overrides. |
| keyTextStyle / valueTextStyle | InlineTextStyle | Per-item inline style overrides. |
| keyAlign / valueAlign | Alignment | Per-item alignment. |
{
type: "keyValueGrid",
orientation: "horizontal",
keyWidth: 100,
separator: ":",
keyStyle: "label",
rowGap: 5,
columns: [
[
{ key: "Invoice", value: "#INV-0042" },
{ key: "Due", value: "2025-12-31" },
],
[
{ key: "Client", value: "Acme Corp" },
{ key: "Total", value: "$3,000.00", valueStyle: "highlight" },
],
],
}list
Bullet or numbered lists with hanging-indent layout. Each item is a string or an object with per-item typography overrides. Long lists paginate item-by-item.
| Property | Type | Default | Description |
|---|---|---|---|
| items | (string \| ListItem)[] | — | Required. List entries. |
| listStyle | "bullet" \| "number" | "bullet" | Marker style. |
| bullet | string | "•" | Custom bullet character (bullet lists only). |
| start | number | 1 | Starting number (numbered lists only). |
| markerWidth | number | auto | Fixed marker column width; otherwise computed from widest marker. |
| markerGap | number | 6 | Space between marker and text in points. |
| itemGap | number | 4 | Vertical gap between items in points. |
| fontSize | number | 10 | Default font size for all items. |
| lineGap | number | 4 | Default line gap inside items. |
| color | string | black | Default text color. |
| style | StyleRef | | Default named style for all items. |
| marginTop/Bottom/Left/Right | number | 0 | |
ListItem properties (when items entry is an object):
| Property | Type | Description |
|---|---|---|
| text | string | Required. Item body text. |
| bold / italic / underline / strike | boolean | Typography flags. |
| link | string | Hyperlink URL. |
| color | string | Text color override. |
| fontSize | number | Font size override. |
| font | FontName | Font override. |
| lineGap | number | Line gap override. |
| style | StyleRef | Named style override. |
{
type: "list",
listStyle: "bullet",
style: "label",
items: [
"Revenue grew 18% year-on-year",
{ text: "Audited by Grant & Partners", bold: true, link: "https://example.com" },
],
marginBottom: 12,
},
{
type: "list",
listStyle: "number",
start: 1,
fontSize: 9,
items: ["Download report", "Review appendix", "Sign authorization"],
},pageBreak
Immediately finishes the current page and starts a new one. No additional properties.
{ type: "pageBreak" }signature
Reserves a block of vertical space for a handwritten or digital signature, optionally rendering label text and/or inner blocks below the signing area.
| Property | Type | Default | Description |
|---|---|---|---|
| height | number | — | Required. Total reserved height in points. |
| width | number | inner width | Width of the signing area. |
| align | "left" \| "center" \| "right" | "left" | Alignment of the complete signing area. |
| label | string | — | Caption printed below the signing line (e.g. "Authorized signature"). |
| image | string \| Buffer | — | Path, URL, data URI, or prepared buffer for the signature image. |
| imageWidth | number | min(120, width) | Width of the optional signature image. |
| blocks | Block[] | — | Custom blocks rendered inside the reserved area. |
| marginLeft / marginRight | number | 0 | |
{
type: "signature",
height: 80,
width: 220,
align: "right",
blocks: [
{ type: "line", lineWidth: 0.5 },
{ type: "text", text: "Chief Executive Officer", align: "center" },
],
}width constrains the entire signature area, so nested lines and other blocks use that width. In releases where width controlled only the simple signature image, migrate that value to imageWidth.
Asset handling
- Images, QR codes, and barcodes accept local file paths,
Buffervalues, or HTTP(S) URLs. Local and remote sources are materialized asBuffers before synchronous drawing begins. - Remote assets use a per-render cache, a 10-second timeout, and a 10 MiB response limit. Failed requests are removed from the cache so a later attempt can retry.
- SVG blocks and SVG watermarks additionally accept inline SVG markup and SVG data URIs. SVG output stays vector-based.
- Nested assets in
columns,signature.blocks,header.blocks,footer.blocks, etc. are traversed automatically. - If an image source fails to load and
defaultImageis set on the document, the engine renders the fallback instead. - Performance tip: for frequently rendered documents, pre-download and pass
Buffervalues directly to avoid network round-trips. - A footer callback runs during synchronous page finalization. Put network, local-file, QR, or barcode assets in a static footer definition, or pass already prepared buffers from the callback.
Asset paths and URLs are capabilities. Treat document definitions as trusted input, or validate/allowlist local paths and remote hosts before passing user-controlled JSON to the renderer.
Rendering targets
import { renderCustomPdf, renderCustomPdfToBuffer } from "av6-pdf-engine";
// Write to a file path
await renderCustomPdf(def, "./output/doc.pdf");
// Get a Buffer (for HTTP, S3, etc.)
const buf: Buffer = await renderCustomPdfToBuffer(def);
res.setHeader("Content-Type", "application/pdf");
res.send(buf);Both functions:
- Materialize local, remote, generated, and data-URI assets asynchronously.
- Create a
PDFDocument, register fonts, set up layout helpers. - Split long flow text and list items through the engine's page lifecycle so every page receives its background, header, watermark, and footer.
- Run the remaining block render loop, then call
doc.end().
Built-in fonts
The following PDFKit built-in fonts are always available without registration:
| Family | Variants |
|---|---|
| Helvetica | Helvetica, Helvetica-Bold, Helvetica-Oblique, Helvetica-BoldOblique |
| Times | Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic |
| Courier | Courier, Courier-Bold, Courier-Oblique, Courier-BoldOblique |
Engine behaviour notes
Automatic paginationensureSpaceFor checks whether the next block fits before rendering it. If not, finishPage(true) is called — drawing the watermark (if applicable), the footer, and then doc.addPage() followed by startNewPageLayout() (background → header → watermark).
Long text and list items
Flow text is split before drawing rather than relying on PDFKit's implicit doc.addPage(). This ensures previous pages are finalized with their footer, signature, and last-page-dependent watermark before the next page starts. Text inside bounded regions such as headers, footers, table cells, columns, and signatures is height-bounded and cannot unexpectedly create a page.
Overflow guard
If a block is taller than the entire page content area (between top and bottom margins), the engine emits a console.warn:
[pdf-engine] Block height (720pt) exceeds page content area (701pt). Content will overflow.Height measurement accuracyheightOfString is called with the same lineGap option that is passed to doc.text(), so the measured height matches the rendered height even when a custom lineGap is set.
visible: false
Any block (including deeply nested ones inside columns, tables, etc.) with visible: false is skipped during both rendering and height measurement.
Contributors
License
MIT — see LICENSE.
Template editor inspection and JSON schema
renderCustomPdfWithLayout runs the same production drawing pipeline and additionally returns final drawing regions. It is opt-in; renderCustomPdf and renderCustomPdfToBuffer retain their existing contracts.
import { documentSchema, validateDocumentDefinition, renderCustomPdfWithLayout } from 'av6-pdf-engine';
const issues = validateDocumentDefinition(definition); // [{path: (string | number)[], message: string}]
if (issues.length) throw new Error(JSON.stringify(issues));
const { pdf, pages, regions } = await renderCustomPdfWithLayout(definition, { maxPages: 30 });
// pdf: Buffer
// pages: [{page: 1, width: 595.28, height: 841.89}]
// regions: [{sourcePath: ['content', 0], type: 'text', page: 1, x, y, width, height}]Coordinates are PDF points measured from the top-left of the unrotated page. Regions are emitted from actual drawing, after pagination, alignment and band clipping. One source path can have multiple regions across pages or repeated occurrences. Container regions aggregate their drawn children. Free-form path shapes have no inferred bounding region: select them through the document tree. Margins/empty space are not independently selectable graphics. Use PDF.js viewport transforms for zoom and rotation rather than deriving bounds from extracted PDF text.
Definitions must be serializable for this API; it clones the input before preparing assets. Keep stable editor IDs in a separate sidecar and map sourcePath against the exact submitted snapshot. Do not add unsupported IDs to engine block JSON. The inspector does not automatically validate or isolate execution; callers accepting untrusted input must validate before rendering and enforce asset, size, page, concurrency and execution-time limits.
documentSchema is generated from public TypeScript definitions by npm run schema; node scripts/generate-schema.cjs --check detects stale output. The shared validator combines this JSON schema with layout constraints. JSON excludes runtime functions and Buffers; trusted production APIs retain those capabilities. Existing signature.width means the entire reserved area; migrate old image-size uses to signature.imageWidth explicitly.
For local development, run npm test (typecheck, CJS/ESM/declarations, smoke and editor regression tests), then refresh each consumer's file: dependency with pnpm update av6-pdf-engine --offline --ignore-scripts. Rebuilding dist alone does not refresh pnpm's copied local package snapshot. Nothing needs to be published. The editor regression writes output/pdf/editor-inspection.pdf and matching layout JSON for visual inspection. The Pathology UI's scripts/sync-pdf-engine-types.mjs synchronizes type-only declarations; use --check in validation.
