renderpdf-js 0.2.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.
Files changed (2) hide show
  1. package/README.md +221 -314
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -4,150 +4,136 @@
4
4
 
5
5
  # RenderPDF.js
6
6
 
7
- An open-source TypeScript library for converting HTML/CSS and standalone images
8
- to PDF in the browser. HTML exports retain selectable text, local raster fallbacks
9
- and automatic pagination. The browser calculates HTML layout; RenderPDF.js uses
10
- its measured text, box, image and table coordinates to write the PDF. Standalone
11
- PNG/JPEG and multipage TIFF files use a separate image API. Conversion runs
12
- entirely on the client.
7
+ **Convert HTML, PNG, JPEG and multipage TIFF to PDF in the browser.**
13
8
 
14
- The renderer is experimental and supports the bounded subset described below.
15
- Chromium is the verified browser; Firefox/Safari compatibility has not been
16
- 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)
17
12
 
18
- ## Getting started
19
-
20
- Install the library in a browser application with a bundler, such as Vite:
21
-
22
- ```sh
23
- npm install renderpdf-js
24
- ```
13
+ HTML exports use the browser's layout, keep text selectable and split content
14
+ across pages. Conversion runs locally, without a conversion server.
25
15
 
26
- The package provides an ESM entry and TypeScript declarations. Version 0.x is
27
- experimental; review the supported scope and limits before using it for documents.
28
- To try HTML preview and PDF download from a source checkout, run the independent
29
- [playground](https://github.com/MichalekJan93/renderPDF.js/tree/main/playground).
30
-
31
- ## Standalone image files
16
+ **Experimental.** Chromium is the verified browser. Firefox and Safari have not
17
+ been verified. HTML rendering supports the [subset described below](#supported-scope).
32
18
 
33
- `imageToPDF` is available from version 0.2.0. It accepts one `File`/`Blob`, `ArrayBuffer` or
34
- `Uint8Array`; actual file signatures determine the format, not filenames or MIME
35
- labels. No HTML element or registered fonts are needed.
19
+ ## Features
36
20
 
37
- ```ts
38
- import { imageToPDF } from 'renderpdf-js';
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.
39
26
 
40
- const file = fileInput.files?.[0];
41
- if (!file) throw new Error('Select a PNG, JPEG or TIFF file.');
27
+ ## Getting started
42
28
 
43
- const pdf = await imageToPDF(file, {
44
- format: 'A4',
45
- margin: 24,
46
- metadata: { title: 'Scanned document' },
47
- onProgress: ({ stage, completed, total }) => {
48
- statusElement.textContent = `${stage}: ${completed}/${total}`;
49
- },
50
- });
51
- await pdf.download('document.pdf');
29
+ ```sh
30
+ npm install renderpdf-js
52
31
  ```
53
32
 
54
- PNG/JPEG produce one PDF page. Every main linked TIFF frame produces its own page
55
- in file order. Images fit completely inside the selected paper's margins, retain
56
- their aspect ratio and are centered; fitting can enlarge a small image. The
57
- defaults are A4 portrait and 24 CSS-pixel margins. A3, Letter, landscape,
58
- asymmetric margins and custom paper sizes use the same settings as HTML exports.
59
- TIFF/EXIF orientations 1–8, including mirrored orientations and PNG `eXIf`, are
60
- applied when painting the page. JPEG compressed bytes remain unchanged.
61
-
62
- The result has the same `pageCount`, `download()`, `toBlob()` and
63
- `toUint8Array()` methods as HTML exports. Image text remains part of the bitmap;
64
- this API does not perform OCR. Metadata and mutable input bytes are copied before
65
- the first asynchronous stage. Cancellation uses `signal`. Progress stages are
66
- `preparing`, `resources`, `pages`, `saving` and `complete`; image conversion does
67
- not emit HTML layout/pagination stages. Resource/page totals equal the number of
68
- image frames. A failed or canceled conversion returns no partial PDF.
69
-
70
- Supported TIFF input is classic TIFF with strips and either byte order, using
71
- uncompressed, CCITT RLE/Group 3/Group 4, LZW, PackBits or Deflate compression.
72
- The bounded decoder supports common bilevel/grayscale, palette and 8-bit RGB/RGBA
73
- profiles, including associated alpha normalization. Other modes, including
74
- BigTIFF, tiles, SubIFDs/RAW layouts, planar-separated samples, 16-bit/float samples
75
- and JPEG-compressed TIFF, fail with `UNSUPPORTED_IMAGE`. Frames are limited to
76
- 40 million pixels each and TIFF files to 1,000 main frames. PNG must be static;
77
- APNG is rejected explicitly. JPEG supports 8-bit baseline/extended/progressive
78
- grayscale, RGB and CMYK frames.
79
-
80
- Malformed headers, checksums and out-of-file offsets are rejected. JPEG entropy
81
- validation and detection of every corrupt CCITT bitstream remain decoder-limited;
82
- structural checks do not prove that every damaged scan is recoverable. TIFF
83
- pixels are decoded one frame at a time with the MIT-licensed `utif2` dependency,
84
- which loads only when TIFF decoding is requested. The HTML `<img>` pipeline still
85
- accepts native PNG/JPEG resources; standalone TIFF support does not change it.
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.
86
36
 
87
37
  ## HTML usage
88
38
 
89
- 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:
90
40
 
91
41
  ```ts
92
- import { registerFont, renderPDF, RenderPDFError } from 'renderpdf-js';
42
+ import { registerFont, renderPDF } from 'renderpdf-js';
93
43
 
94
- // Fetch a licensed static TTF/OTF program owned by the application.
44
+ // Supply a licensed static TTF/OTF file from your application.
95
45
  const response = await fetch('/fonts/MyFont-Regular.ttf');
96
46
  if (!response.ok) throw new Error('The document font could not be loaded.');
97
- // Register before displaying/measuring the source; use a family owned by this registration.
47
+
98
48
  const regular = await registerFont({
99
49
  family: 'Document Sans',
100
50
  data: await response.arrayBuffer(),
101
51
  });
102
- // The source stays connected and keeps its existing browser-calculated dimensions.
52
+
103
53
  const source = document.querySelector<HTMLElement>('#invoice');
104
54
  if (!source) throw new Error('Invoice element was not found.');
105
55
  source.style.fontFamily = 'Document Sans';
106
56
 
107
- try {
108
- // At the default A4 margins the source must fit within approximately 745.7 CSS pixels.
109
- const pdf = await renderPDF(source, {
110
- fonts: [regular],
111
- format: 'A4',
112
- orientation: 'portrait',
113
- margin: 24,
114
- rasterScale: 2,
115
- fallback: 'auto',
116
- pageBreak: 'css',
117
- onWarning: (warning) => console.info(warning.code, warning.message),
118
- });
119
- await pdf.download('invoice.pdf');
120
- // Independent byte and Blob forms do not render the source again.
121
- const bytes = await pdf.toUint8Array();
122
- // Applications can preview, store or upload this Blob themselves if required.
123
- const blob = await pdf.toBlob();
124
- console.info(pdf.pageCount, bytes.byteLength, blob.type);
125
- } catch (error) {
126
- if (error instanceof RenderPDFError) console.error(error.code, error.message);
127
- else throw error;
128
- }
57
+ const pdf = await renderPDF(source, {
58
+ fonts: [regular],
59
+ format: 'A4',
60
+ margin: 24,
61
+ });
62
+
63
+ await pdf.download('invoice.pdf');
129
64
  ```
130
65
 
131
- Register each used family, weight and style explicitly, including bold headings.
132
- `registerFont({ family, data, weight: 700, style: 'normal' })` registers a bold
133
- program; italic uses `style: 'italic'`. The first computed CSS family must match a
134
- provided handle. System fonts cannot be recovered through browser layout APIs.
135
- Registration copies bytes, rejects conflicting browser faces and reuses identical
136
- registrations. Importing the module is safe without a DOM; conversion and font
137
- registration require a browser document.
138
- Variable fonts, WOFF/WOFF2 and font collections are rejected; supply static
139
- 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.
140
71
 
141
- ### Repeated table headers and page text
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
142
78
 
143
- Repeated table headers and page text are available from version 0.2.0.
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
144
131
 
145
132
  ```ts
146
133
  const pdf = await renderPDF(source, {
147
134
  fonts: [regular],
148
- margin: 24,
149
135
  repeatTableHeaders: true,
150
- header: { text: 'Account statement', height: 32, fontSize: 12 },
136
+ header: { text: 'Account statement', height: 32 },
151
137
  footer: {
152
138
  text: 'Page {pageNumber} of {pageCount}',
153
139
  height: 28,
@@ -158,233 +144,154 @@ const pdf = await renderPDF(source, {
158
144
  await pdf.download('statement.pdf');
159
145
  ```
160
146
 
161
- `repeatTableHeaders` defaults to `false`, preserving existing exports. Enabling it
162
- repeats native `thead` paint and selectable text on continuation pages of a table.
163
- The header's measured height and spacing reduce the available body space on those
164
- pages. The original header stays together with the first body row. Body rows and
165
- spanning cells remain indivisible; a header and row that cannot fit fail with
166
- `PAGINATION_FAILED`. Repeated headers require nonnested native tables with a
167
- visible `thead` followed by body rows; raster headers, overlapping table ranges,
168
- header rowspans and cells spanning the header/body boundary are unsupported. Table footers are
169
- not repeated.
170
- Header paint must fit inside the measured `thead` box; text overflowing into its
171
- surrounding spacing fails explicitly instead of being clipped on continuation pages.
172
-
173
- `header` and `footer` each reserve their required `height` in CSS pixels **in
174
- addition to** the page's outer margins. They accept one plain Latin text line,
175
- including Czech diacritics, with `{pageNumber}` and `{pageCount}` placeholders.
176
- The text is black, vertically centered, and remains selectable. `fontSize`
177
- defaults to 12 CSS pixels and `align` to `'left'`; `'center'` and `'right'` are
178
- also supported. `font` can select a registered handle included in `fonts`, and
179
- otherwise defaults to the first supplied face. Band text never wraps or shrinks
180
- silently: text that exceeds its reserved height or available width fails with
181
- `SOURCE_OVERFLOW`. Invalid settings or bands leaving no content area fail with
182
- `INVALID_OPTIONS`. HTML templates and per-page callbacks are not supported.
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.
183
153
 
184
154
  ### Export progress and document properties
185
155
 
186
- Progress reporting and document properties are available from version 0.2.0.
187
-
188
156
  ```ts
189
157
  const pdf = await renderPDF(source, {
190
158
  fonts: [regular],
191
- metadata: {
192
- title: 'Account statement',
193
- author: 'Example Company',
194
- subject: 'Quarterly overview',
195
- keywords: ['statement', 'quarterly'],
196
- },
159
+ metadata: { title: 'Account statement', author: 'Example Company' },
197
160
  onProgress: ({ stage, completed, total }) => {
198
- // Keep the status element outside the document being exported.
199
- statusElement.textContent = `${stage}: ${completed}/${total}`;
161
+ console.log(stage, completed, total);
200
162
  },
201
163
  });
202
- await pdf.download('statement.pdf');
203
164
  ```
204
165
 
205
- Progress events are frozen records with `stage`, `completed` and `total` fields.
206
- Stages are `preparing`, `layout`, `pagination`, `resources`, `pages`, `saving`
207
- and `complete`, in that order. Counters describe work **within the current stage**,
208
- not elapsed-time percentages. Preparation, layout, pagination and saving use one
209
- work unit; resources count font programs and processed native image/raster
210
- references, including reused image references. The page stage counts finished
211
- physical pages; its total is known after pagination. A successful export ends
212
- with `{ stage: 'complete', completed: 1, total: 1 }` after source validation.
213
-
214
- Providing `onProgress` allows periodic browser task yields so status updates and
215
- cancellation can run during export. Without it, conversion adds no progress
216
- timers. Callbacks are synchronous notifications; thrown callback errors reject
217
- the export with the original error. Keep the source static during callbacks as
218
- well as asynchronous rendering. Cancellation continues to use `signal` and the
219
- standard `AbortError`, with no partial PDF returned.
220
-
221
- `metadata` accepts optional Unicode `title`, `author`, `subject` and `keywords`
222
- properties. The keywords value is an array of strings. Settings and keyword
223
- entries are copied before the first asynchronous stage; later caller edits do
224
- not alter the current PDF. Invalid runtime values fail with `INVALID_OPTIONS`.
225
- The default title remains `RenderPDF.js`. Metadata does not add visible page
226
- content or change font requirements.
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.
227
187
 
228
188
  ## Supported scope
229
189
 
230
- - Static block, inline, nonoverlapping Flexbox and Grid geometry with horizontal
231
- Latin text, including Czech diacritics.
232
- - Solid backgrounds, uniform solid rectangular borders and browser-measured
233
- padding, margins and alignment.
234
- - Native PNG/JPEG images loaded from data, blob, same-origin or CORS-readable URLs.
235
- Other image formats fail explicitly. External images can require a browser
236
- fetch at export time; document data is never submitted to a conversion service.
237
- JPEG EXIF orientation must be absent or 1; normalize rotated image pixels first.
238
- - Legacy `FONT`, `CENTER` and native tables with separate or collapsed borders.
239
- Collapsed borders are available from version 0.2.0. Table rows and rowspan cells remain atomic; oversized
240
- atomic content fails.
241
- - A4, A3 and Letter, portrait/landscape, a numeric margin or four explicit edges.
242
- Custom paper uses `format: { width: 500, height: 400 }`; dimensions include
243
- margins. Landscape swaps the supplied base axes, including custom dimensions.
244
- Page dimensions and margins use CSS pixels; `1 CSS px = 0.75 PDF pt`. The renderer preserves
245
- source width and wrapping and fails if it exceeds the page's content width.
246
- - Automatic local fallback for gradients, inline bitmap backgrounds, rounded
247
- corners, inset shadows and opacity on independently capturable rectangular
248
- blocks. Plain parents stay native; a selected parent owns all nested effects
249
- and is captured once in paint order. Descendant text adds extraction-only PDF
250
- objects where safe. Layout/order/overlap guards also apply inside automatic
251
- regions. Inline fragments and table-internal raster boundaries are rejected.
252
- Raster resources must be inline bitmaps; external resources, backdrop effects,
253
- transforms and effects outside the border box are rejected.
254
- Wrapped native inline backgrounds/borders need an explicit containing raster;
255
- a single union rectangle cannot reproduce their separate line fragments.
256
- - Explicit `data-raster` remains available. `fallback: 'strict'` disables automatic
257
- selection while honoring these caller-selected boundaries. Neither mode silently
258
- substitutes a whole-document screenshot when a local feature cannot be rendered.
259
- - `pageBreak: 'css'` (default) respects block/table-row `break-before: page` and
260
- `break-after: page`, their legacy `page-break-*: always` aliases, and
261
- `break-inside: avoid/avoid-page`. Before/after avoidance conservatively keeps
262
- adjacent complete block siblings together when they fit. `pageBreak: 'auto'`
263
- ignores authored fragmentation hints and uses only measured atomic geometry.
264
-
265
- Collapsed tables support cell and outer-table borders with `solid`, `none` and
266
- `hidden` styles, including different widths/colors on each side, transparency,
267
- `rowspan` and `colspan`. Shared edges resolve conflicts before native painting:
268
- hidden suppresses a border, otherwise the wider solid candidate wins; cells
269
- outrank the table on equal widths, followed by the upper/left cell in LTR layout.
270
- Crossing fills are partitioned so transparency is not applied twice. Original
271
- browser column widths and selectable text are retained; no table screenshot is
272
- used. Outer borders stay with the first/last complete rows during pagination.
273
-
274
- Collapsed row, row-group, column and column-group border contributions, other
275
- border styles, nested collapsed tables, captions and altered native table-part
276
- display modes fail explicitly. Keep these contributions on cells or the table
277
- itself. Repeated headers on collapsed tables additionally require the same
278
- resolved horizontal border segments, widths and colors at every possible body
279
- continuation boundary; incompatible combinations fail with `UNSUPPORTED_CSS`.
280
- Repeated headers on tables with separate borders retain their existing scope.
281
-
282
- Page mapping keeps consecutive source intervals: native backgrounds and boxes may
283
- span pages, while measured text lines, images, raster regions, table rows and
284
- rowspan cells remain atomic. Fitting avoid blocks move intact to the next page.
285
- An oversized or incompatible avoid preference relaxes with `BREAK_AVOID_RELAXED`;
286
- it never authorizes cutting an atomic item. Forced breaks take precedence over
287
- soft avoidance, and coincident/root-edge rules create no empty pages. A forced
288
- break through an atomic object, including a raster descendant or spanning cell,
289
- fails with `PAGINATION_FAILED`. Atomic content taller than a page also fails.
290
-
291
- Keep the source static while exporting. The renderer waits for fonts and image
292
- decoding, observes source and stylesheet mutations, and checks computed styles and
293
- geometry across asynchronous stages. A changed source fails with `SOURCE_CHANGED`.
294
- Pause animations before export. Cancellation uses `signal: controller.signal` and
295
- rejects with the standard `AbortError`; it is cooperative between rendering
296
- stages. No partial PDF is returned. Temporary observers and download URLs are
297
- released.
298
-
299
- ## Diagnostics and limits
300
-
301
- `RenderPDFError.code` distinguishes invalid options/source, missing fonts/glyphs,
302
- unsupported CSS/elements, failed resources, source changes/overflow, pagination,
303
- raster and PDF writing failures. Warnings are available through both `onWarning`
304
- and the immutable `result.warnings` collection, without runtime console logging.
305
-
306
- PDF text uses browser word origins and fontkit's shaped glyph advances and offsets;
307
- glyph outlines retain their original proportions. Default OpenType ligatures,
308
- kerning (including `font-kerning: none`) and combining marks retain a selectable
309
- Unicode text layer. Baselines still use Canvas font metrics. Nondefault font
310
- features/variants, stretched faces, font-size adjustment, automatic hyphenation
311
- and soft hyphens fail explicitly. Keep shaped words in one text node; cross-inline
312
- ligatures and zero-width text fragments are unsupported and reject explicitly.
313
- Exact output across shaping engines, arbitrary ligatures, complex scripts, bidi
314
- and variable font instances are not a fidelity promise. There is no arbitrary CSS/stacking
315
- engine. Collapsed tables, repeated table headers and plain page text
316
- use the explicit options described above; arbitrary HTML page templates are unsupported.
317
- Pagination is a measured source-to-page mapping, not full CSS Paged Media: parity
318
- breaks (`left/right/recto/verso`), column breaks, inline/cell break directives,
319
- margin-break rules, widow/orphan line-count policies and cloned page decorations
320
- are unsupported. Nondefault native `widows`/`orphans` and
321
- `box-decoration-break: clone` fail explicitly. Default line-count values are not
322
- enforced. Unsupported visible behavior must fail rather than silently produce an
323
- apparently successful export.
324
- Direct text overlapping later visible paint is also rejected when the simple
325
- 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.
326
264
 
327
265
  ## Development
328
266
 
329
- Development requires Node.js 22.18+ on the 22.x release line, 24.11+ on 24.x, or
330
- 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.
331
269
 
332
270
  ```sh
333
271
  npm ci
334
272
  npm run build
273
+ npm run check
335
274
  ```
336
275
 
337
- Source code lives in `src/`. Browser regression pages live in `tests/fixtures/`;
338
- their measurement helpers in `experiments/` use the same rendering pipeline as
339
- the public API. `npm run dev` serves these fixtures for development, while
340
- `npm run build` builds only the library.
341
-
342
- The independent HTML editor in `playground/` installs the
343
- packed library as an npm dependency. It provides a live preview and PDF download
344
- without importing renderer source files.
345
-
346
- Vite+ provides the development server, builds, Oxlint, Oxfmt and Vitest. Shared
347
- lint, format and unit-test settings live in `vite.config.ts`; the library build
348
- uses `vite.library.config.ts` and TypeScript to generate declarations. The toolchain
349
- is installed locally by `npm ci`; a global Vite+ installation is optional.
350
-
351
- The playground deliberately uses one explicitly stated font family;
352
- the reusable API supports multiple families. Private documents and local system
353
- font programs belong only in ignored `artifacts/`; regression tests use synthetic
354
- documents and do not require them.
355
-
356
- Run `npm run check` before submitting a change. It checks types, lint, unit tests,
357
- browser tests, builds, formatting and package contents. Browser tests require an installed Google
358
- Chrome and use synthetic fixtures, Playwright and PDF.js; those tools never run in
359
- conversion code. Generated PDFs, screenshots and reports stay outside versioned
360
- source files.
361
-
362
- Typography regressions compare tight browser/PDF text crops for Noto Sans regular,
363
- bold and italic plus Noto Serif, including Czech accents, ligatures, kerning and
364
- wrapped mixed-font lines on multiple pages. The [benchmark guide](https://github.com/MichalekJan93/renderPDF.js/blob/main/BENCHMARKS.md)
365
- describes repeatable exports of small, medium, large and hybrid documents.
366
- Run `npm run benchmark` to record timings separately from correctness checks.
367
- GitHub Actions runs the checks and preserves rendered comparisons and timing reports.
368
-
369
- ### Local package
370
-
371
- `npm pack --pack-destination artifacts` builds the library and checks its contents
372
- before creating a local archive. Create the `artifacts/` directory first if it is
373
- absent. The package includes the ESM entry, TypeScript declarations, package metadata,
374
- README, MIT license and README logo. Demos, test fonts, source files and local
375
- documents are excluded by an explicit `files` allowlist.
376
-
377
- `npm run check:package` checks the current build through npm's dry-run inventory;
378
- run `npm run build:library` first when no build exists. The check rejects missing
379
- entry points, empty files and unexpected contents. Packaging does not publish the
380
- library. Maintainers publish a reviewed archive with
381
- `npm publish ./artifacts/renderpdf-js-<version>.tgz --access public` after updating
382
- 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.
383
290
 
384
291
  ## License
385
292
 
386
- RenderPDF.js is licensed under the [MIT License](LICENSE). The TIFF decoder
387
- `utif2` retains its MIT license; its `pako` dependency retains its MIT/Zlib notices.
388
- Runtime dependencies remain external to the library bundle. Bundled Noto Sans and Noto Serif test
389
- fonts retain their [SIL Open Font License](https://github.com/MichalekJan93/renderPDF.js/blob/main/public/fonts/README.md); they are not
390
- 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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "renderpdf-js",
3
- "version": "0.2.0",
4
- "description": "Browser-side HTML and image to PDF renderer with explicit fonts, local rasters and multipage TIFF",
3
+ "version": "0.2.1",
4
+ "description": "Convert HTML, PNG, JPEG and multipage TIFF to PDF in the browser.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",