renderpdf-js 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +389 -2
- package/assets/renderpdf-logo.png +0 -0
- package/dist/api-types.d.ts +143 -0
- package/dist/breaks.d.ts +7 -0
- package/dist/diagnostics.d.ts +26 -0
- package/dist/export-options.d.ts +12 -0
- package/dist/fonts.d.ts +9 -0
- package/dist/image-geometry.d.ts +19 -0
- package/dist/image-input.d.ts +19 -0
- package/dist/image-pdf.d.ts +7 -0
- package/dist/images.d.ts +4 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2388 -0
- package/dist/layout.d.ts +24 -0
- package/dist/page-bands.d.ts +24 -0
- package/dist/page.d.ts +9 -0
- package/dist/pagination.d.ts +21 -0
- package/dist/pdf.d.ts +40 -0
- package/dist/planner.d.ts +9 -0
- package/dist/prepare.d.ts +17 -0
- package/dist/progress.d.ts +11 -0
- package/dist/raster.d.ts +20 -0
- package/dist/render-pdf.d.ts +21 -0
- package/dist/result.d.ts +10 -0
- package/dist/table-borders.d.ts +52 -0
- package/dist/text.d.ts +48 -0
- package/dist/tiff.d.ts +23 -0
- package/dist/types.d.ts +122 -0
- package/package.json +79 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jan Michálek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,390 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/MichalekJan93/renderPDF.js/main/assets/renderpdf-logo.png" alt="RenderPDF.js logo" width="560" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
# RenderPDF.js
|
|
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.
|
|
13
|
+
|
|
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.
|
|
17
|
+
|
|
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
|
+
```
|
|
25
|
+
|
|
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
|
|
32
|
+
|
|
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.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { imageToPDF } from 'renderpdf-js';
|
|
39
|
+
|
|
40
|
+
const file = fileInput.files?.[0];
|
|
41
|
+
if (!file) throw new Error('Select a PNG, JPEG or TIFF file.');
|
|
42
|
+
|
|
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');
|
|
52
|
+
```
|
|
53
|
+
|
|
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.
|
|
86
|
+
|
|
87
|
+
## HTML usage
|
|
88
|
+
|
|
89
|
+
Import the public API from the installed package in your application's browser entry:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { registerFont, renderPDF, RenderPDFError } from 'renderpdf-js';
|
|
93
|
+
|
|
94
|
+
// Fetch a licensed static TTF/OTF program owned by the application.
|
|
95
|
+
const response = await fetch('/fonts/MyFont-Regular.ttf');
|
|
96
|
+
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.
|
|
98
|
+
const regular = await registerFont({
|
|
99
|
+
family: 'Document Sans',
|
|
100
|
+
data: await response.arrayBuffer(),
|
|
101
|
+
});
|
|
102
|
+
// The source stays connected and keeps its existing browser-calculated dimensions.
|
|
103
|
+
const source = document.querySelector<HTMLElement>('#invoice');
|
|
104
|
+
if (!source) throw new Error('Invoice element was not found.');
|
|
105
|
+
source.style.fontFamily = 'Document Sans';
|
|
106
|
+
|
|
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
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
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.
|
|
140
|
+
|
|
141
|
+
### Repeated table headers and page text
|
|
142
|
+
|
|
143
|
+
Repeated table headers and page text are available from version 0.2.0.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const pdf = await renderPDF(source, {
|
|
147
|
+
fonts: [regular],
|
|
148
|
+
margin: 24,
|
|
149
|
+
repeatTableHeaders: true,
|
|
150
|
+
header: { text: 'Account statement', height: 32, fontSize: 12 },
|
|
151
|
+
footer: {
|
|
152
|
+
text: 'Page {pageNumber} of {pageCount}',
|
|
153
|
+
height: 28,
|
|
154
|
+
fontSize: 11,
|
|
155
|
+
align: 'right',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
await pdf.download('statement.pdf');
|
|
159
|
+
```
|
|
160
|
+
|
|
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.
|
|
183
|
+
|
|
184
|
+
### Export progress and document properties
|
|
185
|
+
|
|
186
|
+
Progress reporting and document properties are available from version 0.2.0.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const pdf = await renderPDF(source, {
|
|
190
|
+
fonts: [regular],
|
|
191
|
+
metadata: {
|
|
192
|
+
title: 'Account statement',
|
|
193
|
+
author: 'Example Company',
|
|
194
|
+
subject: 'Quarterly overview',
|
|
195
|
+
keywords: ['statement', 'quarterly'],
|
|
196
|
+
},
|
|
197
|
+
onProgress: ({ stage, completed, total }) => {
|
|
198
|
+
// Keep the status element outside the document being exported.
|
|
199
|
+
statusElement.textContent = `${stage}: ${completed}/${total}`;
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
await pdf.download('statement.pdf');
|
|
203
|
+
```
|
|
204
|
+
|
|
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.
|
|
227
|
+
|
|
228
|
+
## Supported scope
|
|
229
|
+
|
|
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.
|
|
326
|
+
|
|
327
|
+
## Development
|
|
328
|
+
|
|
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:
|
|
331
|
+
|
|
332
|
+
```sh
|
|
333
|
+
npm ci
|
|
334
|
+
npm run build
|
|
335
|
+
```
|
|
336
|
+
|
|
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.
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
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.
|
|
Binary file
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { RenderWarning } from './diagnostics.js';
|
|
2
|
+
/** One explicit static font program registered for browser layout and PDF embedding. */
|
|
3
|
+
export interface RegisteredFont {
|
|
4
|
+
/** CSS family name; provide the same family in the source element's styles. */
|
|
5
|
+
readonly family: string;
|
|
6
|
+
/** Explicit CSS weight of this static face, without browser-synthesized styling. */
|
|
7
|
+
readonly weight: number;
|
|
8
|
+
/** Supported face style. */
|
|
9
|
+
readonly style: 'normal' | 'italic';
|
|
10
|
+
/** Exact font bytes; registration owns its program rather than extracting system fonts. */
|
|
11
|
+
readonly bytes: Uint8Array;
|
|
12
|
+
}
|
|
13
|
+
/** Input for explicit font registration; bytes are supplied by the application. */
|
|
14
|
+
export interface FontSource {
|
|
15
|
+
/** CSS family to register in the current document. */
|
|
16
|
+
family: string;
|
|
17
|
+
/** Complete TTF/OTF program available to the browser and PDF adapter. */
|
|
18
|
+
data: Uint8Array | ArrayBuffer;
|
|
19
|
+
/** Static face weight, defaulting to 400. */
|
|
20
|
+
weight?: number;
|
|
21
|
+
/** Static face style, defaulting to normal. */
|
|
22
|
+
style?: 'normal' | 'italic';
|
|
23
|
+
}
|
|
24
|
+
/** Physical page and source-to-page margins in CSS reference pixels. */
|
|
25
|
+
export interface PageGeometry {
|
|
26
|
+
/** Page width in CSS reference pixels (1px = 0.75pt). */
|
|
27
|
+
width: number;
|
|
28
|
+
/** Page height in CSS reference pixels. */
|
|
29
|
+
height: number;
|
|
30
|
+
/** Empty page edges in CSS pixels. */
|
|
31
|
+
margin: PageMargins;
|
|
32
|
+
}
|
|
33
|
+
/** Per-edge empty page space, measured in CSS pixels. */
|
|
34
|
+
export interface PageMargins {
|
|
35
|
+
/** Top empty edge. */
|
|
36
|
+
top: number;
|
|
37
|
+
/** Right empty edge. */
|
|
38
|
+
right: number;
|
|
39
|
+
/** Bottom empty edge. */
|
|
40
|
+
bottom: number;
|
|
41
|
+
/** Left empty edge. */
|
|
42
|
+
left: number;
|
|
43
|
+
}
|
|
44
|
+
/** Explicit physical paper dimensions in the shared CSS reference pixel units. */
|
|
45
|
+
export interface CustomPageSize {
|
|
46
|
+
/** Base paper width in CSS pixels; landscape orientation swaps the supplied axes. */
|
|
47
|
+
width: number;
|
|
48
|
+
/** Base paper height in CSS pixels, including margins. */
|
|
49
|
+
height: number;
|
|
50
|
+
}
|
|
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 {
|
|
88
|
+
/** Standard page size (default A4) or explicit physical dimensions in CSS pixels. */
|
|
89
|
+
format?: 'A4' | 'A3' | 'Letter' | CustomPageSize;
|
|
90
|
+
/** Page orientation, defaulting to portrait. */
|
|
91
|
+
orientation?: 'portrait' | 'landscape';
|
|
92
|
+
/** CSS-pixel margins, defaulting to 24 on every edge; content is never implicitly rescaled. */
|
|
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[];
|
|
99
|
+
/** Physical pixels per CSS pixel in selected local raster regions, defaulting to 2. */
|
|
100
|
+
rasterScale?: number;
|
|
101
|
+
/** Automatically rasterize safe local effects (default auto); strict requires explicit data-raster. */
|
|
102
|
+
fallback?: 'auto' | 'strict';
|
|
103
|
+
/** Honor supported CSS page breaks (default css), or use geometry-only automatic cuts. */
|
|
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;
|
|
113
|
+
/** Stop preparation and subsequent rendering stages without producing a partial PDF. */
|
|
114
|
+
signal?: AbortSignal;
|
|
115
|
+
/** Observe documented approximations, raster choices and relaxed pagination preferences. */
|
|
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;
|
|
130
|
+
}
|
|
131
|
+
/** Complete PDF result with independently usable browser output forms. */
|
|
132
|
+
export interface RenderPDFResult {
|
|
133
|
+
/** Number of generated pages. */
|
|
134
|
+
readonly pageCount: number;
|
|
135
|
+
/** Frozen diagnostics for this export; there is no production console logging. */
|
|
136
|
+
readonly warnings: readonly RenderWarning[];
|
|
137
|
+
/** Return a fresh byte copy so callers cannot corrupt future downloads. */
|
|
138
|
+
toUint8Array(): Promise<Uint8Array>;
|
|
139
|
+
/** Return a browser PDF Blob without triggering a download. */
|
|
140
|
+
toBlob(): Promise<Blob>;
|
|
141
|
+
/** Download the completed PDF without invoking a print dialog. */
|
|
142
|
+
download(filename?: string): Promise<void>;
|
|
143
|
+
}
|
package/dist/breaks.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LayoutSnapshot } from './types.js';
|
|
2
|
+
/** Collect the supported CSS page-break subset from existing measured browser geometry. */
|
|
3
|
+
export declare function collectPageBreaks(
|
|
4
|
+
/** Connected current-document root; collection leaves its source DOM unchanged. */
|
|
5
|
+
root: HTMLElement,
|
|
6
|
+
/** Ordered display list identifying intrinsic raster boundaries and source extent. */
|
|
7
|
+
snapshot: LayoutSnapshot): Pick<LayoutSnapshot, 'forcedBreaks' | 'avoidBreakBounds'>;
|
|
@@ -0,0 +1,26 @@
|
|
|
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' | 'UNSUPPORTED_IMAGE' | 'RESOURCE_LOAD_FAILED' | 'SOURCE_CHANGED' | 'SOURCE_OVERFLOW' | 'PAGINATION_FAILED' | 'RASTER_FAILED' | 'PDF_WRITE_FAILED';
|
|
3
|
+
/** A rendering failure with a stable code and an actionable English explanation. */
|
|
4
|
+
export declare class RenderPDFError extends Error {
|
|
5
|
+
/** Stable category that callers can branch on without parsing message text. */
|
|
6
|
+
readonly code: RenderErrorCode;
|
|
7
|
+
/** Construct an error without hiding its original lower-level cause. */
|
|
8
|
+
constructor(
|
|
9
|
+
/** Stable category that callers can branch on without parsing message text. */
|
|
10
|
+
code: RenderErrorCode,
|
|
11
|
+
/** Human-readable explanation of the failed requirement. */
|
|
12
|
+
message: string,
|
|
13
|
+
/** Optional original browser or adapter failure. */
|
|
14
|
+
options?: ErrorOptions);
|
|
15
|
+
}
|
|
16
|
+
/** Supported approximations and explicit local raster choices, reported without console noise. */
|
|
17
|
+
export interface RenderWarning {
|
|
18
|
+
/** Stable warning category for application diagnostics. */
|
|
19
|
+
readonly code: 'TEXT_METRICS_APPROXIMATED' | 'RASTER_FALLBACK' | 'RASTER_TEXT_OMITTED' | 'BREAK_AVOID_RELAXED';
|
|
20
|
+
/** Explanation of the behavior and its relevant fidelity limit. */
|
|
21
|
+
readonly message: string;
|
|
22
|
+
}
|
|
23
|
+
/** Stop at asynchronous stage boundaries using the browser's standard AbortError contract. */
|
|
24
|
+
export declare function throwIfAborted(
|
|
25
|
+
/** Optional caller cancellation signal. */
|
|
26
|
+
signal?: AbortSignal): void;
|
|
@@ -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;
|
package/dist/fonts.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FontSource, RegisteredFont } from './api-types.js';
|
|
2
|
+
/** Validate immutable registration handles against the current browser's live font set.
|
|
3
|
+
* @param fonts Explicit handles whose owned programs must still control browser font selection.
|
|
4
|
+
*/
|
|
5
|
+
export declare function validateRegisteredFonts(fonts: readonly RegisteredFont[]): void;
|
|
6
|
+
/** Register explicit static TTF/OTF bytes in the current browser document.
|
|
7
|
+
* @param source Caller-supplied program and its exact CSS family, weight, and style.
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerFont(source: FontSource): Promise<RegisteredFont>;
|
|
@@ -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];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Validated native bitmap geometry; orientation transforms are applied by the PDF adapter. */
|
|
2
|
+
export interface BitmapInfo {
|
|
3
|
+
/** Actual compressed format, independent of the filename and MIME type. */
|
|
4
|
+
format: 'png' | 'jpeg';
|
|
5
|
+
/** Encoded pixel width before EXIF rotation. */
|
|
6
|
+
width: number;
|
|
7
|
+
/** Encoded pixel height before EXIF rotation. */
|
|
8
|
+
height: number;
|
|
9
|
+
/** Standard EXIF orientation, defaulting to one when absent. */
|
|
10
|
+
orientation: number;
|
|
11
|
+
}
|
|
12
|
+
/** Inspect PNG/JPEG bytes without a DOM, mutation, implicit file-type guessing or pixel allocation. */
|
|
13
|
+
export declare function inspectBitmap(bytes: Uint8Array): BitmapInfo;
|
|
14
|
+
/** Validate PNG's complete zlib stream and row layout before pdf-lib's permissive raw inflater. */
|
|
15
|
+
export declare function validatePngData(
|
|
16
|
+
/** Owned PNG bytes; filenames and MIME types never establish their validity. */
|
|
17
|
+
bytes: Uint8Array,
|
|
18
|
+
/** Optional cancellation released together with the native reader. */
|
|
19
|
+
signal?: AbortSignal): Promise<void>;
|