renderpdf-js 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,221 +4,294 @@
4
4
 
5
5
  # RenderPDF.js
6
6
 
7
- An open-source TypeScript library for converting HTML/CSS to PDF in the browser,
8
- with selectable text, local raster fallbacks and automatic pagination. The browser
9
- calculates layout; RenderPDF.js uses its measured text, box, image and table
10
- coordinates to write the PDF. Conversion runs entirely on the client.
7
+ **Convert HTML, PNG, JPEG and multipage TIFF to PDF in the browser.**
11
8
 
12
- The renderer is experimental and supports the bounded subset described below.
13
- Chromium is the verified browser; Firefox/Safari compatibility has not been
14
- established.
9
+ [![npm version](https://img.shields.io/npm/v/renderpdf-js)](https://www.npmjs.com/package/renderpdf-js)
10
+ [![CI](https://github.com/MichalekJan93/renderPDF.js/actions/workflows/check.yml/badge.svg?branch=main)](https://github.com/MichalekJan93/renderPDF.js/actions/workflows/check.yml)
11
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/MichalekJan93/renderPDF.js/blob/main/LICENSE)
15
12
 
16
- ## Getting started
13
+ HTML exports use the browser's layout, keep text selectable and split content
14
+ across pages. Conversion runs locally, without a conversion server.
15
+
16
+ **Experimental.** Chromium is the verified browser. Firefox and Safari have not
17
+ been verified. HTML rendering supports the [subset described below](#supported-scope).
18
+
19
+ ## Features
17
20
 
18
- Install the library in a browser application with a bundler, such as Vite:
21
+ - Selectable HTML text with embedded fonts, native boxes, images and tables.
22
+ - Automatic pagination, repeated table headers, page headers and page numbers.
23
+ - PNG/JPEG conversion and one PDF page per frame of a multipage TIFF.
24
+ - Paper sizes, margins, document properties, progress events and cancellation.
25
+ - ESM package with TypeScript declarations.
26
+
27
+ ## Getting started
19
28
 
20
29
  ```sh
21
30
  npm install renderpdf-js
22
31
  ```
23
32
 
24
- The package provides an ESM entry and TypeScript declarations. Version 0.1.x is
25
- experimental; review the supported scope and limits before using it for documents.
26
- To try HTML preview and PDF download from a source checkout, run the independent
27
- [playground](https://github.com/MichalekJan93/renderPDF.js/tree/main/playground).
33
+ Use the package in a browser application with an ESM bundler, such as Vite.
34
+ The [playground](https://github.com/MichalekJan93/renderPDF.js/tree/main/playground)
35
+ provides an HTML editor, preview and PDF download from a source checkout.
28
36
 
29
- ## Usage
37
+ ## HTML usage
30
38
 
31
- Import the public API from the installed package in your application's browser entry:
39
+ Register a font, select an existing element and export it:
32
40
 
33
41
  ```ts
34
- import { registerFont, renderPDF, RenderPDFError } from 'renderpdf-js';
42
+ import { registerFont, renderPDF } from 'renderpdf-js';
35
43
 
36
- // Fetch a licensed static TTF/OTF program owned by the application.
44
+ // Supply a licensed static TTF/OTF file from your application.
37
45
  const response = await fetch('/fonts/MyFont-Regular.ttf');
38
46
  if (!response.ok) throw new Error('The document font could not be loaded.');
39
- // Register before displaying/measuring the source; use a family owned by this registration.
47
+
40
48
  const regular = await registerFont({
41
49
  family: 'Document Sans',
42
50
  data: await response.arrayBuffer(),
43
51
  });
44
- // The source stays connected and keeps its existing browser-calculated dimensions.
52
+
45
53
  const source = document.querySelector<HTMLElement>('#invoice');
46
54
  if (!source) throw new Error('Invoice element was not found.');
47
55
  source.style.fontFamily = 'Document Sans';
48
56
 
49
- try {
50
- // At the default A4 margins the source must fit within approximately 745.7 CSS pixels.
51
- const pdf = await renderPDF(source, {
52
- fonts: [regular],
53
- format: 'A4',
54
- orientation: 'portrait',
55
- margin: 24,
56
- rasterScale: 2,
57
- fallback: 'auto',
58
- pageBreak: 'css',
59
- onWarning: (warning) => console.info(warning.code, warning.message),
60
- });
61
- await pdf.download('invoice.pdf');
62
- // Independent byte and Blob forms do not render the source again.
63
- const bytes = await pdf.toUint8Array();
64
- // Applications can preview, store or upload this Blob themselves if required.
65
- const blob = await pdf.toBlob();
66
- console.info(pdf.pageCount, bytes.byteLength, blob.type);
67
- } catch (error) {
68
- if (error instanceof RenderPDFError) console.error(error.code, error.message);
69
- else throw error;
70
- }
57
+ const pdf = await renderPDF(source, {
58
+ fonts: [regular],
59
+ format: 'A4',
60
+ margin: 24,
61
+ });
62
+
63
+ await pdf.download('invoice.pdf');
71
64
  ```
72
65
 
73
- Register each used family, weight and style explicitly, including bold headings.
74
- `registerFont({ family, data, weight: 700, style: 'normal' })` registers a bold
75
- program; italic uses `style: 'italic'`. The first computed CSS family must match a
76
- provided handle. System fonts cannot be recovered through browser layout APIs.
77
- Registration copies bytes, rejects conflicting browser faces and reuses identical
78
- registrations. Importing the module is safe without a DOM; conversion and font
79
- registration require a browser document.
80
- Variable fonts, WOFF/WOFF2 and font collections are rejected; supply static
81
- TTF/OTF instances. Do not remove or replace registered browser faces during use.
66
+ Register every font family, weight and style used by the source, including bold
67
+ and italic faces. For example, add `weight: 700` when registering a bold font.
68
+ The first computed CSS font family must match a registered face. Static TTF/OTF
69
+ files are required; system fonts, variable fonts, WOFF/WOFF2 and font collections
70
+ are unsupported.
71
+
72
+ Keep the source connected to the document and unchanged during export. Its width
73
+ and wrapping are preserved, so it must already fit the page's content width:
74
+ about **745.7 CSS pixels** for A4 with the default margins. Oversized HTML is not
75
+ automatically scaled down.
76
+
77
+ ## Standalone image files
78
+
79
+ `imageToPDF` accepts a `File`, `Blob`, `ArrayBuffer` or `Uint8Array`. It detects
80
+ the format from the file contents and requires no HTML element or fonts.
81
+
82
+ ```ts
83
+ import { imageToPDF } from 'renderpdf-js';
84
+
85
+ const fileInput = document.querySelector<HTMLInputElement>('#image');
86
+ const file = fileInput?.files?.[0];
87
+ if (!file) throw new Error('Select a PNG, JPEG or TIFF file.');
88
+
89
+ const pdf = await imageToPDF(file, { format: 'A4', margin: 24 });
90
+ await pdf.download('document.pdf');
91
+ ```
92
+
93
+ PNG and JPEG produce one page. Each main TIFF frame produces a page in file order.
94
+ Images are centered and scaled to fit within the margins, preserving their aspect
95
+ ratio; small images can be enlarged. TIFF/EXIF orientations, including mirrored
96
+ orientations and PNG `eXIf`, are applied. JPEG compressed bytes are preserved.
97
+ Image text remains a bitmap; there is no OCR.
98
+
99
+ See [image format limits](#image-format-limits) for supported TIFF profiles.
100
+ TIFF support applies to this API; HTML `<img>` elements accept PNG/JPEG.
101
+
102
+ ## Options
103
+
104
+ Both conversion functions accept these settings:
105
+
106
+ | Option | Default | Description |
107
+ | ------------- | ----------------------- | --------------------------------------------------------------------- |
108
+ | `format` | `'A4'` | `'A4'`, `'A3'`, `'Letter'` or `{ width, height }`. |
109
+ | `orientation` | `'portrait'` | `'portrait'` or `'landscape'`. |
110
+ | `margin` | `24` | One value or `{ top, right, bottom, left }`. |
111
+ | `metadata` | Title: `'RenderPDF.js'` | Unicode `title`, `author`, `subject` and `keywords` (a string array). |
112
+ | `onProgress` | — | Receives `{ stage, completed, total }`. |
113
+ | `signal` | — | An `AbortSignal` for cancellation. |
114
+
115
+ Dimensions and margins use **CSS pixels**: `1 px = 0.75 PDF pt`. Custom paper
116
+ dimensions include margins; landscape swaps the supplied width and height.
117
+
118
+ HTML exports also accept:
119
+
120
+ | Option | Default | Description |
121
+ | -------------------- | -------- | -------------------------------------------------------------------------------------- |
122
+ | `fonts` | Required | Registered faces used by the source and page text. |
123
+ | `pageBreak` | `'css'` | Honor supported CSS breaks, or use `'auto'` for geometry alone. |
124
+ | `repeatTableHeaders` | `false` | Repeat native `thead` groups on continuation pages. |
125
+ | `header`, `footer` | — | One plain text line in a reserved area on every page. |
126
+ | `fallback` | `'auto'` | Rasterize supported local effects; `'strict'` requires explicit `data-raster` regions. |
127
+ | `rasterScale` | `2` | Raster pixels per CSS pixel. |
128
+ | `onWarning` | — | Receives export warnings. |
129
+
130
+ ### Repeated table headers and page text
131
+
132
+ ```ts
133
+ const pdf = await renderPDF(source, {
134
+ fonts: [regular],
135
+ repeatTableHeaders: true,
136
+ header: { text: 'Account statement', height: 32 },
137
+ footer: {
138
+ text: 'Page {pageNumber} of {pageCount}',
139
+ height: 28,
140
+ fontSize: 11,
141
+ align: 'right',
142
+ },
143
+ });
144
+ await pdf.download('statement.pdf');
145
+ ```
146
+
147
+ Header and footer `height` is required and reserves space **in addition to** the
148
+ outer margins. Each accepts one plain Latin text line, including Czech diacritics,
149
+ with `{pageNumber}` and `{pageCount}` placeholders. Text stays selectable and does
150
+ not wrap. `fontSize` defaults to `12`, `align` to `'left'`, and `font` to the first
151
+ face in `fonts`. Alignment can also be `'center'` or `'right'`. Text that does not
152
+ fit fails explicitly. HTML page templates and per-page callbacks are unsupported.
153
+
154
+ ### Export progress and document properties
155
+
156
+ ```ts
157
+ const pdf = await renderPDF(source, {
158
+ fonts: [regular],
159
+ metadata: { title: 'Account statement', author: 'Example Company' },
160
+ onProgress: ({ stage, completed, total }) => {
161
+ console.log(stage, completed, total);
162
+ },
163
+ });
164
+ ```
165
+
166
+ Counters describe work within the current stage, rather than elapsed-time
167
+ percentages. HTML stages are `preparing`, `layout`, `pagination`, `resources`,
168
+ `pages`, `saving` and `complete`. Image conversion omits `layout` and `pagination`.
169
+ Callbacks are synchronous; a thrown callback error rejects the export. Cancellation
170
+ is cooperative between stages and rejects with `AbortError`, without a partial PDF.
171
+
172
+ ## Output and errors
173
+
174
+ Both functions return the same result:
175
+
176
+ ```ts
177
+ await pdf.download('document.pdf');
178
+ const blob = await pdf.toBlob();
179
+ const bytes = await pdf.toUint8Array();
180
+ console.log(pdf.pageCount, pdf.warnings);
181
+ ```
182
+
183
+ These output methods reuse the completed PDF. `RenderPDFError`, exported from the
184
+ package, exposes a `code` and `message` for conversion failures. Warnings are
185
+ available through `onWarning` for HTML exports and `pdf.warnings`. Unsupported
186
+ input fails explicitly instead of silently producing a whole-page screenshot.
82
187
 
83
188
  ## Supported scope
84
189
 
85
- - Static block, inline, nonoverlapping Flexbox and Grid geometry with horizontal
86
- Latin text, including Czech diacritics.
87
- - Solid backgrounds, uniform solid rectangular borders and browser-measured
88
- padding, margins and alignment.
89
- - Native PNG/JPEG images loaded from data, blob, same-origin or CORS-readable URLs.
90
- Other image formats fail explicitly. External images can require a browser
91
- fetch at export time; document data is never submitted to a conversion service.
92
- JPEG EXIF orientation must be absent or 1; normalize rotated image pixels first.
93
- - Legacy `FONT`, `CENTER` and simple tables with separate borders. Table rows and
94
- rowspan cells remain atomic; oversized atomic content fails.
95
- - A4, A3 and Letter, portrait/landscape, a numeric margin or four explicit edges.
96
- Custom paper uses `format: { width: 500, height: 400 }`; dimensions include
97
- margins. Landscape swaps the supplied base axes, including custom dimensions.
98
- Page dimensions and margins use CSS pixels; `1 CSS px = 0.75 PDF pt`. The renderer preserves
99
- source width and wrapping and fails if it exceeds the page's content width.
100
- - Automatic local fallback for gradients, inline bitmap backgrounds, rounded
101
- corners, inset shadows and opacity on independently capturable rectangular
102
- blocks. Plain parents stay native; a selected parent owns all nested effects
103
- and is captured once in paint order. Descendant text adds extraction-only PDF
104
- objects where safe. Layout/order/overlap guards also apply inside automatic
105
- regions. Inline fragments and table-internal raster boundaries are rejected.
106
- Raster resources must be inline bitmaps; external resources, backdrop effects,
107
- transforms and effects outside the border box are rejected.
108
- Wrapped native inline backgrounds/borders need an explicit containing raster;
109
- a single union rectangle cannot reproduce their separate line fragments.
110
- - Explicit `data-raster` remains available. `fallback: 'strict'` disables automatic
111
- selection while honoring these caller-selected boundaries. Neither mode silently
112
- substitutes a whole-document screenshot when a local feature cannot be rendered.
113
- - `pageBreak: 'css'` (default) respects block/table-row `break-before: page` and
114
- `break-after: page`, their legacy `page-break-*: always` aliases, and
115
- `break-inside: avoid/avoid-page`. Before/after avoidance conservatively keeps
116
- adjacent complete block siblings together when they fit. `pageBreak: 'auto'`
117
- ignores authored fragmentation hints and uses only measured atomic geometry.
118
-
119
- Page mapping keeps consecutive source intervals: native backgrounds and boxes may
120
- span pages, while measured text lines, images, raster regions, table rows and
121
- rowspan cells remain atomic. Fitting avoid blocks move intact to the next page.
122
- An oversized or incompatible avoid preference relaxes with `BREAK_AVOID_RELAXED`;
123
- it never authorizes cutting an atomic item. Forced breaks take precedence over
124
- soft avoidance, and coincident/root-edge rules create no empty pages. A forced
125
- break through an atomic object, including a raster descendant or spanning cell,
126
- fails with `PAGINATION_FAILED`. Atomic content taller than a page also fails.
127
-
128
- Keep the source static while exporting. The renderer waits for fonts and image
129
- decoding, observes source and stylesheet mutations, and checks computed styles and
130
- geometry across asynchronous stages. A changed source fails with `SOURCE_CHANGED`.
131
- Pause animations before export. Cancellation uses `signal: controller.signal` and
132
- rejects with the standard `AbortError`; it is cooperative between rendering
133
- stages. No partial PDF is returned. Temporary observers and download URLs are
134
- released.
135
-
136
- ## Diagnostics and limits
137
-
138
- `RenderPDFError.code` distinguishes invalid options/source, missing fonts/glyphs,
139
- unsupported CSS/elements, failed resources, source changes/overflow, pagination,
140
- raster and PDF writing failures. Warnings are available through both `onWarning`
141
- and the immutable `result.warnings` collection, without runtime console logging.
142
-
143
- PDF text uses browser word origins and fontkit's shaped glyph advances and offsets;
144
- glyph outlines retain their original proportions. Default OpenType ligatures,
145
- kerning (including `font-kerning: none`) and combining marks retain a selectable
146
- Unicode text layer. Baselines still use Canvas font metrics. Nondefault font
147
- features/variants, stretched faces, font-size adjustment, automatic hyphenation
148
- and soft hyphens fail explicitly. Keep shaped words in one text node; cross-inline
149
- ligatures and zero-width text fragments are unsupported and reject explicitly.
150
- Exact output across shaping engines, arbitrary ligatures, complex scripts, bidi
151
- and variable font instances are not a fidelity promise. There is no arbitrary CSS/stacking
152
- engine, collapsed table borders, repeated table headers or header/footer engine.
153
- Pagination is a measured source-to-page mapping, not full CSS Paged Media: parity
154
- breaks (`left/right/recto/verso`), column breaks, inline/cell break directives,
155
- margin-break rules, widow/orphan line-count policies and cloned page decorations
156
- are unsupported. Nondefault native `widows`/`orphans` and
157
- `box-decoration-break: clone` fail explicitly. Default line-count values are not
158
- enforced. Unsupported visible behavior must fail rather than silently produce an
159
- apparently successful export.
160
- Direct text overlapping later visible paint is also rejected when the simple
161
- ordered paint list cannot reproduce the browser's block/inline paint phases.
190
+ HTML rendering supports static blocks, inline content, nonoverlapping Flexbox and
191
+ Grid layouts, horizontal Latin text (including Czech diacritics), solid backgrounds,
192
+ rectangular borders and native tables with `rowspan` and `colspan`.
193
+
194
+ PNG/JPEG resources can use data, blob, same-origin or CORS-readable URLs. External
195
+ images may be fetched by the browser; documents are never sent to a conversion
196
+ service. JPEG orientation in HTML must be absent or `1`; normalize rotated pixels
197
+ before using them in an `<img>` element.
198
+
199
+ Gradients, rounded corners, inline bitmap backgrounds, inset shadows and opacity
200
+ can use local raster fallback on supported rectangular blocks. `data-raster`
201
+ selects a region explicitly. Regions are captured once in paint order; descendant
202
+ text gets an extraction layer where safe. Raster resources must be inline bitmaps;
203
+ external resources, transforms, backdrop effects and effects outside the border
204
+ box are unsupported. Wrapped inline backgrounds or borders require a containing
205
+ raster region; inline and table-internal raster boundaries are unsupported.
206
+
207
+ ### Pagination and tables
208
+
209
+ The default `pageBreak: 'css'` respects block/table-row `break-before: page`,
210
+ `break-after: page`, their legacy `page-break-*: always` aliases and
211
+ `break-inside: avoid/avoid-page`. Before/after avoidance keeps adjacent complete
212
+ block siblings together when they fit.
213
+
214
+ Text lines, images, raster regions, table rows and spanning cells stay whole.
215
+ Content taller than a page, or a forced break through an indivisible item, fails
216
+ with `PAGINATION_FAILED`. An avoid preference that cannot be honored can be relaxed
217
+ with a `BREAK_AVOID_RELAXED` warning. Parity/column breaks, inline/cell break rules,
218
+ widow/orphan policies and cloned page decorations are unsupported.
219
+
220
+ <details>
221
+ <summary>Table restrictions</summary>
222
+
223
+ Collapsed tables support solid, none and hidden borders on cells and the outer
224
+ table, including different widths, colors and transparency. Shared borders are
225
+ painted once. Row, row-group, column and column-group border contributions, other
226
+ border styles, captions, nested collapsed tables and altered table-part display
227
+ modes are unsupported.
228
+
229
+ Repeated headers require a visible native `thead` followed by body rows. The
230
+ header reserves space on continuation pages and stays with the first body row.
231
+ Headers and rows must fit on a page. Nested tables, raster headers, overlapping
232
+ table ranges, header rowspans and cells spanning the header/body boundary are
233
+ unsupported. Header paint must fit its measured box. Table footers are not repeated.
234
+ Collapsed tables also require matching resolved horizontal borders at every
235
+ possible header/body continuation boundary.
236
+
237
+ </details>
238
+
239
+ ### Text and layout limits
240
+
241
+ Default kerning, ligatures and combining marks are supported. Keep shaped words in
242
+ one text node; cross-inline ligatures and zero-width fragments are unsupported.
243
+ Complex scripts, bidirectional text, custom font features/variants, stretched
244
+ faces, font-size adjustment, automatic hyphenation and soft hyphens are unsupported.
245
+ Exact text appearance can vary between shaping engines.
246
+
247
+ Arbitrary CSS stacking and overlapping paint are unsupported. Pause animations
248
+ and keep source styles, geometry and registered fonts stable during export.
249
+ Changes to the source fail with `SOURCE_CHANGED`.
250
+
251
+ ### Image format limits
252
+
253
+ - **PNG:** static images only; APNG is rejected.
254
+ - **JPEG:** 8-bit baseline, extended and progressive grayscale, RGB or CMYK.
255
+ - **TIFF:** classic stripped TIFF in either byte order; common bilevel/grayscale,
256
+ palette and 8-bit RGB/RGBA profiles. Compression: uncompressed, CCITT RLE/Group
257
+ 3/Group 4, LZW, PackBits and Deflate. Limited to **40 million pixels per frame**
258
+ and **1,000 main frames**.
259
+
260
+ BigTIFF, tiled TIFF, SubIFDs/RAW layouts, planar-separated samples, 16-bit/float
261
+ samples and JPEG-compressed TIFF fail with `UNSUPPORTED_IMAGE`. The TIFF decoder
262
+ loads only when needed. Structural checks reject malformed input, but cannot
263
+ identify every damaged JPEG scan or CCITT bitstream.
162
264
 
163
265
  ## Development
164
266
 
165
- Development requires Node.js 22.18+ on the 22.x release line, 24.11+ on 24.x, or
166
- 26+. The `.node-version` file records the verified 22.22.0 runtime. From a source checkout:
267
+ Use Node.js 22.18+ (22.x), 24.11+ (24.x) or 26+. `.node-version` records the
268
+ verified runtime.
167
269
 
168
270
  ```sh
169
271
  npm ci
170
272
  npm run build
273
+ npm run check
171
274
  ```
172
275
 
173
- Source code lives in `src/`. Browser regression pages live in `tests/fixtures/`;
174
- their measurement helpers in `experiments/` use the same rendering pipeline as
175
- the public API. `npm run dev` serves these fixtures for development, while
176
- `npm run build` builds only the library.
177
-
178
- The independent HTML editor in `playground/` installs the
179
- packed library as an npm dependency. It provides a live preview and PDF download
180
- without importing renderer source files.
181
-
182
- Vite+ provides the development server, builds, Oxlint, Oxfmt and Vitest. Shared
183
- lint, format and unit-test settings live in `vite.config.ts`; the library build
184
- uses `vite.library.config.ts` and TypeScript to generate declarations. The toolchain
185
- is installed locally by `npm ci`; a global Vite+ installation is optional.
186
-
187
- The playground deliberately uses one explicitly stated font family;
188
- the reusable API supports multiple families. Private documents and local system
189
- font programs belong only in ignored `artifacts/`; regression tests use synthetic
190
- documents and do not require them.
191
-
192
- Run `npm run check` before submitting a change. It checks types, lint, unit tests,
193
- browser tests, builds, formatting and package contents. Browser tests require an installed Google
194
- Chrome and use synthetic fixtures, Playwright and PDF.js; those tools never run in
195
- conversion code. Generated PDFs, screenshots and reports stay outside versioned
196
- source files.
197
-
198
- Typography regressions compare tight browser/PDF text crops for Noto Sans regular,
199
- bold and italic plus Noto Serif, including Czech accents, ligatures, kerning and
200
- wrapped mixed-font lines on multiple pages. The [benchmark guide](https://github.com/MichalekJan93/renderPDF.js/blob/main/BENCHMARKS.md)
201
- describes repeatable exports of small, medium, large and hybrid documents.
202
- Run `npm run benchmark` to record timings separately from correctness checks.
203
- GitHub Actions runs the checks and preserves rendered comparisons and timing reports.
204
-
205
- ### Local package
206
-
207
- `npm pack --pack-destination artifacts` builds the library and checks its contents
208
- before creating a local archive. Create the `artifacts/` directory first if it is
209
- absent. The package includes the ESM entry, TypeScript declarations, package metadata,
210
- README, MIT license and README logo. Demos, test fonts, source files and local
211
- documents are excluded by an explicit `files` allowlist.
212
-
213
- `npm run check:package` checks the current build through npm's dry-run inventory;
214
- run `npm run build:library` first when no build exists. The check rejects missing
215
- entry points, empty files and unexpected contents. Packaging does not publish the
216
- library. Maintainers publish a reviewed archive with
217
- `npm publish ./artifacts/renderpdf-js-<version>.tgz --access public` after updating
218
- the package version and verifying the packed consumer.
276
+ `npm run check` runs formatting, lint, types, unit tests, browser tests, build and
277
+ package checks. Google Chrome must be installed for browser tests. Vite+ provides
278
+ the development toolchain; Playwright and PDF.js are used for testing.
279
+
280
+ Run `npm run dev` to serve synthetic test fixtures. The independent
281
+ [playground](https://github.com/MichalekJan93/renderPDF.js/tree/main/playground)
282
+ tests the packaged library. See the
283
+ [benchmark guide](https://github.com/MichalekJan93/renderPDF.js/blob/main/BENCHMARKS.md)
284
+ for `npm run benchmark` and timing reports.
285
+
286
+ To create a local package, create `artifacts/` if needed, then run
287
+ `npm pack --pack-destination artifacts`. Only the library build, declarations,
288
+ package metadata, README, license and logo are packaged. Keep private documents
289
+ and generated files out of Git.
219
290
 
220
291
  ## License
221
292
 
222
- RenderPDF.js is licensed under the [MIT License](LICENSE). Bundled Noto Sans and Noto Serif test
223
- fonts retain their [SIL Open Font License](https://github.com/MichalekJan93/renderPDF.js/blob/main/public/fonts/README.md); they are not
224
- bundled into the library entry. Third-party dependencies retain their own licenses.
293
+ [MIT](https://github.com/MichalekJan93/renderPDF.js/blob/main/LICENSE).
294
+ Third-party dependencies retain their own licenses: `utif2` uses MIT and `pako`
295
+ retains MIT/Zlib notices. Noto Sans and Noto Serif test fonts retain the
296
+ [SIL Open Font License](https://github.com/MichalekJan93/renderPDF.js/blob/main/public/fonts/README.md)
297
+ and are not included in the npm package.
@@ -48,26 +48,85 @@ export interface CustomPageSize {
48
48
  /** Base paper height in CSS pixels, including margins. */
49
49
  height: number;
50
50
  }
51
- /** Small supported API surface; unsupported layout fails explicitly. */
52
- export interface RenderPDFOptions {
53
- /** Explicit registered faces used by the supplied source; no implicit font substitution. */
54
- fonts: readonly RegisteredFont[];
51
+ /** One selectable line repeated in a reserved area above or below each page's content. */
52
+ export interface PageTextOptions {
53
+ /** Plain Latin text; {pageNumber} and {pageCount} expand after pagination. */
54
+ text: string;
55
+ /** Reserved height in CSS pixels, in addition to the page's outer margins. */
56
+ height: number;
57
+ /** Exact face included in fonts; defaults to its first registered face. */
58
+ font?: RegisteredFont;
59
+ /** Text size in CSS pixels, defaulting to 12. */
60
+ fontSize?: number;
61
+ /** Horizontal alignment within the page's content width, defaulting to left. */
62
+ align?: 'left' | 'center' | 'right';
63
+ }
64
+ /** Document properties stored in the PDF, independent of visible page content. */
65
+ export interface PDFMetadata {
66
+ /** Human-readable document title; defaults to RenderPDF.js when omitted. */
67
+ title?: string;
68
+ /** Author displayed in PDF document properties. */
69
+ author?: string;
70
+ /** Short description of the document's subject. */
71
+ subject?: string;
72
+ /** Search keywords copied before export begins. */
73
+ keywords?: readonly string[];
74
+ }
75
+ /** Observable export stages; counters describe work within one stage, never elapsed-time estimates. */
76
+ export type RenderStage = 'preparing' | 'layout' | 'pagination' | 'resources' | 'pages' | 'saving' | 'complete';
77
+ /** Immutable progress notification delivered synchronously to the application's callback. */
78
+ export interface RenderProgress {
79
+ /** Current stage, in pipeline order. */
80
+ readonly stage: RenderStage;
81
+ /** Finished work units within the current stage. */
82
+ readonly completed: number;
83
+ /** Known work units in this stage; page totals are available after pagination. */
84
+ readonly total: number;
85
+ }
86
+ /** Shared physical paper settings for HTML and standalone image conversion. */
87
+ export interface PDFPageOptions {
55
88
  /** Standard page size (default A4) or explicit physical dimensions in CSS pixels. */
56
89
  format?: 'A4' | 'A3' | 'Letter' | CustomPageSize;
57
90
  /** Page orientation, defaulting to portrait. */
58
91
  orientation?: 'portrait' | 'landscape';
59
92
  /** CSS-pixel margins, defaulting to 24 on every edge; content is never implicitly rescaled. */
60
93
  margin?: number | PageMargins;
94
+ }
95
+ /** Small supported API surface; unsupported layout fails explicitly. */
96
+ export interface RenderPDFOptions extends PDFPageOptions {
97
+ /** Explicit registered faces used by the supplied source; no implicit font substitution. */
98
+ fonts: readonly RegisteredFont[];
61
99
  /** Physical pixels per CSS pixel in selected local raster regions, defaulting to 2. */
62
100
  rasterScale?: number;
63
101
  /** Automatically rasterize safe local effects (default auto); strict requires explicit data-raster. */
64
102
  fallback?: 'auto' | 'strict';
65
103
  /** Honor supported CSS page breaks (default css), or use geometry-only automatic cuts. */
66
104
  pageBreak?: 'auto' | 'css';
105
+ /** Repeat native THEAD groups on table continuation pages; defaults to false. */
106
+ repeatTableHeaders?: boolean;
107
+ /** Plain text vertically centered in a reserved area above the source on every page. */
108
+ header?: PageTextOptions;
109
+ /** Plain text vertically centered in a reserved area below the source on every page. */
110
+ footer?: PageTextOptions;
111
+ /** Unicode document properties; all supplied values are copied before asynchronous preparation. */
112
+ metadata?: PDFMetadata;
67
113
  /** Stop preparation and subsequent rendering stages without producing a partial PDF. */
68
114
  signal?: AbortSignal;
69
115
  /** Observe documented approximations, raster choices and relaxed pagination preferences. */
70
116
  onWarning?: (warning: RenderWarning) => void;
117
+ /** Observe stage-local work counts; the renderer yields for browser updates when provided. */
118
+ onProgress?: (progress: RenderProgress) => void;
119
+ }
120
+ /** One caller-owned image file or its complete binary bytes; the signature determines its format. */
121
+ export type ImageSource = Blob | ArrayBuffer | Uint8Array;
122
+ /** Convert one PNG/JPEG or all main image frames of a TIFF, without HTML or font registration. */
123
+ export interface ImagePDFOptions extends PDFPageOptions {
124
+ /** Copied Unicode PDF properties. */
125
+ metadata?: PDFMetadata;
126
+ /** Cancel between input reading, frame decoding, image embedding and page writing stages. */
127
+ signal?: AbortSignal;
128
+ /** Frozen stage-local counters; image conversion omits HTML layout/pagination stages. */
129
+ onProgress?: (progress: RenderProgress) => void;
71
130
  }
72
131
  /** Complete PDF result with independently usable browser output forms. */
73
132
  export interface RenderPDFResult {
@@ -1,5 +1,5 @@
1
1
  /** Machine-readable failure reasons for the supported browser rendering pipeline. */
2
- export type RenderErrorCode = 'INVALID_OPTIONS' | 'INVALID_SOURCE' | 'FONT_LOAD_FAILED' | 'MISSING_FONT' | 'MISSING_GLYPH' | 'UNSUPPORTED_CSS' | 'UNSUPPORTED_ELEMENT' | 'RESOURCE_LOAD_FAILED' | 'SOURCE_CHANGED' | 'SOURCE_OVERFLOW' | 'PAGINATION_FAILED' | 'RASTER_FAILED' | 'PDF_WRITE_FAILED';
2
+ export type RenderErrorCode = 'INVALID_OPTIONS' | 'INVALID_SOURCE' | 'FONT_LOAD_FAILED' | 'MISSING_FONT' | 'MISSING_GLYPH' | 'UNSUPPORTED_CSS' | 'UNSUPPORTED_ELEMENT' | 'UNSUPPORTED_IMAGE' | 'RESOURCE_LOAD_FAILED' | 'SOURCE_CHANGED' | 'SOURCE_OVERFLOW' | 'PAGINATION_FAILED' | 'RASTER_FAILED' | 'PDF_WRITE_FAILED';
3
3
  /** A rendering failure with a stable code and an actionable English explanation. */
4
4
  export declare class RenderPDFError extends Error {
5
5
  /** Stable category that callers can branch on without parsing message text. */
@@ -0,0 +1,12 @@
1
+ import type { PDFMetadata } from './api-types.js';
2
+ import type { PDFDocument } from 'pdf-lib';
3
+ /** Validate and own metadata before the caller can mutate nested settings during an export. */
4
+ export declare function resolveMetadata(
5
+ /** Optional caller-owned PDF document properties. */
6
+ value: PDFMetadata | undefined): Readonly<PDFMetadata> | undefined;
7
+ /** Apply the same document properties for HTML and standalone image exports. */
8
+ export declare function applyPDFMetadata(
9
+ /** PDF document owning these Info dictionary values. */
10
+ pdf: PDFDocument,
11
+ /** Validated, copied properties; omitted fields retain the shared defaults. */
12
+ metadata: Readonly<PDFMetadata> | undefined): void;
@@ -0,0 +1,19 @@
1
+ import type { PageGeometry } from './api-types.js';
2
+ /** Bound per-frame decoding allocations while allowing typical 600-DPI A4 scans. */
3
+ export declare const MAX_IMAGE_PIXELS = 40000000;
4
+ /** Reject invalid and excessive dimensions before a decoder allocates pixel buffers. */
5
+ export declare function assertImageSize(
6
+ /** Encoded pixel width, before orientation. */
7
+ width: number,
8
+ /** Encoded pixel height, before orientation. */
9
+ height: number): void;
10
+ /** Fit and orient an image's unit square inside the page, returning a CSS-pixel PDF matrix. */
11
+ export declare function imagePlacement(
12
+ /** Original encoded image width. */
13
+ width: number,
14
+ /** Original encoded image height. */
15
+ height: number,
16
+ /** TIFF/EXIF orientation, including mirrored variants. */
17
+ orientation: number,
18
+ /** Physical page with validated margins. */
19
+ page: PageGeometry): [number, number, number, number, number, number];