Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ name: Node.js CI

on: [push, pull_request]

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:

Expand All @@ -22,6 +29,17 @@ jobs:
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
- run: npm install
cache: npm
- run: npm ci
- run: npm audit --audit-level=low
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '24.x'
- run: npm test
- run: npm run build:examples
- run: npm run test:typescript
- run: npm run test:package
- name: Build standalone playground
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '24.x'
run: npm run build:playground
- name: Audit standalone playground tooling
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '24.x'
run: npm --prefix dev-playground audit --audit-level=low
17 changes: 14 additions & 3 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ jobs:
with:
node-version: '24.x'
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm run build
- run: npm publish --provenance --access public
cache: npm
- run: npm ci
- run: npm audit --audit-level=low
- run: npm run release:check
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
- run: npm test
- run: npm run build:examples
- run: npm run test:typescript
- run: npm run test:package
env:
PDFMAKE_PACK_DEST: release-package
- name: Publish the tested tarball
run: npm publish release-package/pdfmake-*.tgz --provenance --access public
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ doc
*.ipr
*.iws
nbproject
package-lock.json
yarn.lock
/build
/js
/dev-playground/public/editor.js
/dev-playground/public/vendor/
/release-package/
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ dev-playground/
docs/
examples/
tests/
scripts/
benchmarks/
AGENTS.md
release-package/
package-lock.json
yarn.lock
.editorconfig
Expand Down
42 changes: 42 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Working on pdfmake

This branch targets the **0.3 API**. Do not copy the 0.1/0.2 `PdfPrinter`
constructor or callback output methods into 0.3 examples.

## Source map

- `src/base.js`: shared public API and resource policies; `src/index.js`: Node entry.
- `src/browser-extensions/`: browser entry, font containers and output helpers.
- `DocPreprocessor` → `DocMeasure`/`TextInlines` → `LayoutBuilder`/`TableProcessor`
→ `Renderer` → PDFKit is the main document pipeline.
- `src/OutputDocument*.js`: asynchronous buffer, stream and file output.
- `tests/unit/` and `tests/integration/`: Mocha tests against compiled `js/`.
- `examples/`: runnable feature examples; `fonts/` and `standard-fonts/`: font assets.
- Developer website and documentation MCP: separate `pdfmake/docs` repository,
`master` branch. Canonical docs: https://pdfmake.github.io/docs/0.3/.

## Validation

Library runtime remains Node >=20. Development tools require Node >=20.19 on 20.x or >=22.12 on newer lines (CI covers 20, 22 and 24). Run `npm ci` from the committed lockfile.
Run `npm test` after source changes: this builds both Node and browser artifacts,
runs all Mocha tests and ESLint. A focused loop is `npm run build:node` followed by
`npx mocha tests/unit/<name>.spec.js`. Never edit generated `js/` or `build/`.
Run `npm run build:examples` and `npm run test:package` before proposing a release.
`npm run benchmark:text` compares the text flattener to its former implementation;
timings are diagnostic, not a CI pass/fail threshold.

## Compatibility and boundaries

Document definitions are processed and mutated during layout. Construct a fresh
definition for each independent document. Preserve font/style/line-break behavior
when optimizing; add regression cases before changing pagination or measurement.
Policies passed to `setUrlAccessPolicy` and `setLocalAccessPolicy` must return a
synchronous boolean. Do not add network fetching, telemetry, LLM providers or new
runtime dependencies for documentation tooling. Keep documentation tooling out of
the library's browser bundle. Do not rewrite vendored third-party sources.

Run `npm run test:typescript` after public API/package changes. It tests real packed
consumers using the bundled declarations and no @types/pdfmake dependency. Keep native declarations
in types/ aligned with the runtime; preserve the MIT attribution. Do not add broad
ambient module shims or claim a native ESM runtime. Keep CJS, Node ESM and browser
imports working and retain negative type assertions.
206 changes: 152 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,174 @@
# pdfmake [![Node.js CI][githubactions_img]][githubactions_url] [![GitHub][github_img]][github_url] [![npm][npm_img]][npm_url]
# pdfmake

[githubactions_img]: https://github.com/bpampuch/pdfmake/actions/workflows/node.js.yml/badge.svg?branch=master
[githubactions_url]: https://github.com/bpampuch/pdfmake/actions
**Create PDFs from structured JavaScript or TypeScript document definitions.**

[github_img]: https://img.shields.io/github/release/bpampuch/pdfmake.svg?colorB=0E7FBF
[github_url]: https://github.com/bpampuch/pdfmake/releases/latest
[![CI](https://github.com/bpampuch/pdfmake/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/bpampuch/pdfmake/actions/workflows/node.js.yml)
[![npm](https://img.shields.io/npm/v/pdfmake)](https://www.npmjs.com/package/pdfmake)
[![License: MIT](https://img.shields.io/badge/License-MIT-red.svg)](LICENSE)

[npm_img]: https://img.shields.io/npm/v/pdfmake.svg?colorB=0E7FBF
[npm_url]: https://www.npmjs.com/package/pdfmake
[Documentation](https://pdfmake.github.io/docs/0.3/) · [Interactive examples](https://pdfmake.github.io/docs/0.3/playground/) · [Migration to 0.3](https://pdfmake.github.io/docs/0.3/migration-from-0.1/) · [Report an issue](https://github.com/bpampuch/pdfmake/issues)

pdfmake runs in Node.js and the browser. Describe text, tables, images and page
layout in a document definition, then write a file, return a buffer from an API,
or let a browser download the result. No browser process is needed on the server.

PDF document generation library for server-side and client-side in pure JavaScript.
> **Development branch:** this checkout targets 0.3.x and includes unreleased
> bundled TypeScript declarations and document preflight validation. The published
> 0.3.13 package does not include those additions. Build and pack this checkout to
> try them locally; do not assume installing 0.3.13 from npm installs this branch.

Check out [the playground](http://bpampuch.github.io/pdfmake/playground.html) and [examples](https://github.com/bpampuch/pdfmake/tree/master/examples).
## Start with a PDF

### Features
The library requires **Node.js 20 or later**. All 0.3 output helpers return Promises.

* line-wrapping,
* text-alignments (left, right, centered, justified),
* numbered and bulleted lists,
* tables and columns
* auto/fixed/star-sized widths,
* col-spans and row-spans,
* headers automatically repeated in case of a page-break,
* snaking columns (newspaper-style layout where content flows column-to-column),
* images and vector graphics,
* convenient styling and style inheritance,
* page headers and footers:
* static or dynamic content,
* access to current page number and page count,
* background-layer,
* page dimensions and orientations,
* margins,
* document sections,
* custom page breaks,
* font embedding,
* support for complex, multi-level (nested) structures,
* table of contents,
* helper methods for opening/printing/downloading the generated PDF,
* setting of PDF metadata (e.g. author, subject).
```sh
npm install pdfmake
```

## Documentation
Save as `document.cjs`, then run `node document.cjs`:

```js
const pdfmake = require('pdfmake');
const fonts = require('pdfmake/standard-fonts/Helvetica');

pdfmake.addFonts(fonts);
pdfmake.setUrlAccessPolicy(() => false);
pdfmake.setLocalAccessPolicy(name => Object.values(fonts.Helvetica).includes(name));

async function main() {
const definition = {
content: [
{ text: 'Hello, pdfmake', fontSize: 22, bold: true },
'A document definition works in Node.js and the browser.',
{ table: { body: [['Item', 'Quantity'], ['Documents', 3]] } }
],
defaultStyle: { font: 'Helvetica' }
};
await pdfmake.createPdf(definition).write('document.pdf');
}

main().catch(error => {
console.error(error.message);
process.exitCode = 1;
});
```

**Documentation URL: https://pdfmake.github.io/docs/**
Helvetica is a standard PDF font. Use an embedded font such as Roboto when your
content needs characters outside its supported encoding. Configure access policies
for the actual resources your application needs; document input can reference files
and URLs. See [fonts](https://pdfmake.github.io/docs/0.3/fonts/) and
[resource access](https://pdfmake.github.io/docs/0.3/guides/resource-security/).

Source of documentation: https://github.com/pdfmake/docs **Improvements are welcome!**
For a browser bundler:

## Building from sources
```js
import pdfmake from 'pdfmake/build/pdfmake.js';
import vfs from 'pdfmake/build/vfs_fonts.js';

using npm:
pdfmake.addVirtualFileSystem(vfs);
pdfmake.setUrlAccessPolicy(() => false);
await pdfmake.createPdf({ content: ['Hello from the browser'] }).download('hello.pdf');
```
git clone https://github.com/bpampuch/pdfmake.git
cd pdfmake
npm install
npm run build

Use `getBuffer()` / `getStream()` for server integrations; `getBlob()`, `download()`,
`open()` and `print()` belong to the browser entry. Construct a fresh definition
for each generation: layout processing mutates the definition.

## TypeScript

This branch ships its own declarations. **Do not install `@types/pdfmake` alongside
the locally built package.** The declarations are based on the MIT-licensed
DefinitelyTyped 0.3 definitions, maintained here with environment-specific APIs.
Attribution is retained in [types/LICENSE](types/LICENSE).

```ts
import pdfmake from 'pdfmake';
import type { TDocumentDefinitions } from 'pdfmake';

const definition = {
content: [{ text: 'Checked by TypeScript', fontSize: 18 }]
} satisfies TDocumentDefinitions;
```

using yarn:
Use `module: "NodeNext"`, `moduleResolution: "NodeNext"`, `strict: true` and
`esModuleInterop: true` for Node ESM. TypeScript CommonJS can use
`import pdfmake = require('pdfmake')`. For browser bundlers, use the explicit
browser imports above with `module: "ESNext"` and `moduleResolution: "Bundler"`.
`pdfmake/interfaces` and `pdfmake/interfaces.js` remain supported type paths.
The runtime is CommonJS/UMD; bundled declarations do not turn it into native ESM.

## Validate document input

The unreleased preflight API checks the complete declared document structure and
adds semantic checks for table shapes/spans, IDs, style/layout/font references,
numeric ranges and cycles. It collects diagnostics without changing input,
executing callbacks, fetching resources or evaluating source code.

```js
const result = pdfmake.validateDocument(definition, { strict: true });
if (!result.valid) {
for (const issue of result.errors) console.error(issue.path, issue.message);
}

// Alternatively, reject invalid input before starting PDF generation.
const buffer = await pdfmake.createPdf(definition, { validation: true }).getBuffer();
```
git clone https://github.com/bpampuch/pdfmake.git
cd pdfmake
yarn
yarn run build

Unknown properties produce warnings by default, allowing application metadata;
`strict: true` turns them into errors. Results include `errors`, `warnings`,
`deferred` checks and `truncated`. Defaults limit diagnostics to 100, nesting to
100 levels and inspected values to 50,000. Exhausted validation never reports success.
Callbacks and resource decoding still need rendering; always await output and
handle its rejection. JavaScript syntax errors happen before this object API runs.
See [diagnostics](https://pdfmake.github.io/docs/0.3/guides/errors/).

## What you can build

- Flowing text with named styles, wrapping, alignment and embedded fonts.
- Tables with repeated headers, row/column spans, borders and custom layouts.
- Columns, nested lists, page sections, backgrounds, headers and footers.
- Images, SVG, canvas vectors, QR codes, links and a table of contents.
- Metadata, encryption, attachments and supported archival/accessibility options.

Start with [your first PDF](https://pdfmake.github.io/docs/0.3/learn/first-pdf/),
then explore the [document definition reference](https://pdfmake.github.io/docs/0.3/document-definition-object/).

## Develop and contribute

Development tools require Node >=20.19 on 20.x or >=22.12 on newer lines.
The optional standalone playground requires Node >=22.13.

```sh
npm ci
npm test
npm run test:typescript
npm run build:examples
npm run test:package
```

## License
MIT
`npm test` verifies the generated validation contract, builds both environments,
runs the test suite and lints source. Consumer tests install the real npm tarball
without `@types/pdfmake`, compile strict Node/browser fixtures and render PDFs.
CI runs these checks on Linux, macOS and Windows with Node 20, 22 and 24.

After editing `types/interfaces.d.ts`, run `npm run schema:generate` and commit
the generated contract with the declaration change. Never edit `js/` or `build/`.
See [AGENTS.md](AGENTS.md) for the source map and compatibility expectations.

```sh
npm run playground
# http://127.0.0.1:1234/playground/

npm pack
# Install the resulting .tgz in an isolated project to review unreleased changes.
```

## Authors
* [@bpampuch](https://github.com/bpampuch) (founder)
* [@liborm85](https://github.com/liborm85)
Documentation, translations, Markdown exports and the read-only documentation MCP
server live in [pdfmake/docs](https://github.com/pdfmake/docs).
For bugs, include a minimal definition, pdfmake version, runtime and expected PDF.

pdfmake is based on a truly amazing library [pdfkit](https://github.com/devongovett/pdfkit) (credits to [@devongovett](https://github.com/devongovett)).
## License and acknowledgments

Thanks to all contributors.
[MIT](LICENSE). Founded by [Bartek Pampuch](https://github.com/bpampuch), maintained
with [liborm85](https://github.com/liborm85) and the contributor community.
PDF generation builds on [PDFKit](https://github.com/foliojs/pdfkit).
39 changes: 39 additions & 0 deletions benchmarks/document.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { performance } = require('node:perf_hooks');
const rewire = require('rewire');
const textModule = rewire('../js/TextInlines');

function previous(array) {
function flatten(items) {
return items.reduce((prev, cur) => {
const current = Array.isArray(cur.text) ? flatten(cur.text) : cur;
return prev.concat([].concat(current).some(Array.isArray) ? flatten(current) : current);
}, []);
}
return flatten(Array.isArray(array) ? array : [array]);
}
if (process.argv.includes('--previous')) textModule.__set__('flattenTextArray', previous);
require('../js/TextInlines').default = textModule.default;
const pdfmake = require('../js/index');
const fonts = require('../standard-fonts/Helvetica');
pdfmake.addFonts(fonts);
pdfmake.setUrlAccessPolicy(() => false);
pdfmake.setLocalAccessPolicy(name => Object.values(fonts.Helvetica).includes(name));

(async () => {
for (const size of [1000, 5000, 15000]) {
const times = [];
let bytes;
for (let run = 0; run < 4; run++) {
const definition = { defaultStyle: { font: 'Helvetica', fontSize: 10 }, content: [{ text:
Array.from({ length: size }, (_, i) => ({ text: `word${i} `, bold: i % 3 === 0 }))
}] };
const start = performance.now();
bytes = await pdfmake.createPdf(definition).getBuffer();
if (run) times.push(performance.now() - start);
}
times.sort((a, b) => a - b);
console.log(JSON.stringify({ algorithm: process.argv.includes('--previous') ? 'previous' : 'optimized', node: process.version,
platform: process.platform, inlines: size, medianMs: +times[1].toFixed(2), bytes: bytes.length,
pages: Number(bytes.toString('latin1').match(/\/Count (\d+)/)[1]) }));
}
})().catch(error => { console.error(error); process.exitCode = 1; });
Loading