blitz-pdf 0.1.4 → 0.1.5
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 +29 -0
- package/dist/api.d.ts +9 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +25 -27
- package/dist/api.js.map +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +13 -1
- package/dist/browser.js.map +1 -1
- package/dist/node-worker.d.ts +2 -0
- package/dist/node-worker.d.ts.map +1 -0
- package/dist/node-worker.js +10 -0
- package/dist/node-worker.js.map +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +18 -6
- package/dist/node.js.map +1 -1
- package/dist/render.d.ts +8 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +28 -0
- package/dist/render.js.map +1 -0
- package/dist/wasm.d.ts +3 -0
- package/dist/wasm.d.ts.map +1 -0
- package/dist/wasm.js +18 -0
- package/dist/wasm.js.map +1 -0
- package/dist/worker-host.d.ts +16 -0
- package/dist/worker-host.d.ts.map +1 -0
- package/dist/worker-host.js +31 -0
- package/dist/worker-host.js.map +1 -0
- package/dist/worker-protocol.d.ts +22 -0
- package/dist/worker-protocol.d.ts.map +1 -0
- package/dist/worker-protocol.js +2 -0
- package/dist/worker-protocol.js.map +1 -0
- package/dist/worker-runtime.d.ts +8 -0
- package/dist/worker-runtime.d.ts.map +1 -0
- package/dist/worker-runtime.js +34 -0
- package/dist/worker-runtime.js.map +1 -0
- package/dist/worker.d.ts +2 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +7 -0
- package/dist/worker.js.map +1 -0
- package/package.json +5 -2
- package/src/api.ts +33 -22
- package/src/browser.ts +13 -3
- package/src/node-worker.ts +9 -0
- package/src/node.ts +13 -4
- package/src/render.ts +28 -0
- package/src/wasm.ts +17 -0
- package/src/worker-host.ts +45 -0
- package/src/worker-protocol.ts +10 -0
- package/src/worker-runtime.ts +35 -0
- package/src/worker.ts +12 -0
- package/wasm/blitz_pdf_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -74,6 +74,35 @@ CPU work. Use a Web Worker or server worker thread when the calling thread must
|
|
|
74
74
|
stay responsive. Each worker gets its own WASM instance. Shared memory and
|
|
75
75
|
cross-origin isolation are not required.
|
|
76
76
|
|
|
77
|
+
## Rendering in a worker
|
|
78
|
+
|
|
79
|
+
`renderPdfInWorker` has the same signature as `renderPdf`, but layout and PDF
|
|
80
|
+
serialization run in a new worker: a Web Worker in browsers and a
|
|
81
|
+
`worker_threads` worker in Node.js and Bun. The calling thread stays responsive:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
import { renderPdfInWorker } from "blitz-pdf"; // or "blitz-pdf/browser"
|
|
85
|
+
|
|
86
|
+
const pdfs = await Promise.all(documents.map(html =>
|
|
87
|
+
renderPdfInWorker(html, { format: "A4", assets: async url => (await fetch(url)).arrayBuffer() })));
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The WASM is compiled once into a `WebAssembly.Module` and posted to
|
|
91
|
+
every worker. Workers only instantiate it, so they skip WASM compilation.
|
|
92
|
+
`init({ wasm })` sets the source for this shared module as well. Each call
|
|
93
|
+
starts a separate worker and terminates it after the render, so its memory is
|
|
94
|
+
freed and concurrent calls run in parallel. Limit concurrency yourself, for
|
|
95
|
+
example to `navigator.hardwareConcurrency` or `os.availableParallelism()`.
|
|
96
|
+
|
|
97
|
+
Options are validated on the calling thread. An `assets` function also runs
|
|
98
|
+
there, so it can use closures, credentials, and caches; the worker receives
|
|
99
|
+
only the returned bytes. Errors are reported the same way as for `renderPdf`.
|
|
100
|
+
Bundlers must emit `dist/worker.js` through its `new Worker(new URL(...,
|
|
101
|
+
import.meta.url))` reference; Vite and webpack do this automatically.
|
|
102
|
+
|
|
103
|
+
To manage your own workers, post the shared module from `getWasmModule()` and
|
|
104
|
+
pass it to `init({ wasm: module })` inside the worker.
|
|
105
|
+
|
|
77
106
|
## Options
|
|
78
107
|
|
|
79
108
|
`renderPdf(html: string, options?: PdfOptions): Promise<Uint8Array>`
|
package/dist/api.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { type SpawnWorker } from "./worker-host.js";
|
|
1
2
|
import type { InitOptions, PdfOptions, WasmSource } from "./types.js";
|
|
2
|
-
export
|
|
3
|
+
export interface ApiConfig {
|
|
4
|
+
defaultSource: () => Promise<WasmSource> | WasmSource;
|
|
5
|
+
prepare?: (source: WasmSource) => Promise<WasmSource>;
|
|
6
|
+
spawnWorker: SpawnWorker;
|
|
7
|
+
}
|
|
8
|
+
export declare function createApi({ defaultSource, prepare, spawnWorker }: ApiConfig): {
|
|
3
9
|
init: (options?: InitOptions) => Promise<void>;
|
|
4
10
|
renderPdf: (html: string, options?: PdfOptions) => Promise<Uint8Array>;
|
|
11
|
+
renderPdfInWorker: (html: string, options?: PdfOptions) => Promise<Uint8Array>;
|
|
12
|
+
getWasmModule: () => Promise<WebAssembly.Module>;
|
|
5
13
|
getEngineVersion: () => Promise<string>;
|
|
6
14
|
};
|
|
7
15
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAKA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,SAAS;IACxB,aAAa,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;IACtD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,EAAE,aAAa,EAAE,OAAgC,EAAE,WAAW,EAAE,EAAE,SAAS;qBAY5E,WAAW,KAAQ,OAAO,CAAC,IAAI,CAAC;sBAYxB,MAAM,YAAW,UAAU,KAAQ,OAAO,CAAC,UAAU,CAAC;8BAQ9C,MAAM,YAAW,UAAU,KAAQ,OAAO,CAAC,UAAU,CAAC;yBAXnE,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC;4BAiBlB,OAAO,CAAC,MAAM,CAAC;EAEnD"}
|
package/dist/api.js
CHANGED
|
@@ -1,46 +1,44 @@
|
|
|
1
|
-
import loadWasm, {
|
|
1
|
+
import loadWasm, { engine_version } from "../wasm/blitz_pdf.js";
|
|
2
2
|
import { createAssetLoader } from "./assets.js";
|
|
3
3
|
import { normalizeOptions } from "./options.js";
|
|
4
|
-
|
|
4
|
+
import { render, validateHtml } from "./render.js";
|
|
5
|
+
import { compileWasm } from "./wasm.js";
|
|
6
|
+
import { renderInWorker } from "./worker-host.js";
|
|
7
|
+
export function createApi({ defaultSource, prepare = async (source) => source, spawnWorker }) {
|
|
8
|
+
let compiled;
|
|
5
9
|
let ready;
|
|
10
|
+
function compile(source) {
|
|
11
|
+
if (compiled)
|
|
12
|
+
return compiled;
|
|
13
|
+
compiled = (async () => compileWasm(await prepare(source ?? await defaultSource())))()
|
|
14
|
+
.catch((error) => { compiled = undefined; throw error; });
|
|
15
|
+
return compiled;
|
|
16
|
+
}
|
|
6
17
|
/** Initialize once. Concurrent callers share initialization; failed loads can be retried. */
|
|
7
18
|
function init(options = {}) {
|
|
8
19
|
if (ready)
|
|
9
20
|
return ready;
|
|
10
21
|
ready = (async () => {
|
|
11
|
-
|
|
12
|
-
await loadWasm({ module_or_path: source });
|
|
22
|
+
await loadWasm({ module_or_path: await compile(options.wasm) });
|
|
13
23
|
})().catch((error) => { ready = undefined; throw error; });
|
|
14
24
|
return ready;
|
|
15
25
|
}
|
|
26
|
+
/** The compiled WASM module, compiled once and shared with every worker. It can be posted to your own workers. */
|
|
27
|
+
function getWasmModule() { return compile(); }
|
|
16
28
|
/** Render HTML/CSS to a vector PDF/A-3u with embedded fonts and selectable text. */
|
|
17
29
|
async function renderPdf(html, options = {}) {
|
|
18
|
-
|
|
19
|
-
throw new TypeError("html must be a string");
|
|
20
|
-
if (new TextEncoder().encode(html).byteLength > 16 * 1024 * 1024)
|
|
21
|
-
throw new RangeError("HTML exceeds the 16 MiB limit");
|
|
30
|
+
validateHtml(html);
|
|
22
31
|
const { assetResolver, ...normalized } = normalizeOptions(options);
|
|
23
32
|
await init();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (urls.length === 0)
|
|
32
|
-
return job.finish();
|
|
33
|
-
const data = await load(urls);
|
|
34
|
-
for (let i = 0; i < urls.length; i++)
|
|
35
|
-
job.provide_asset(urls[i], data[i]);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
// In-flight JS loaders never access this job, including after a sibling rejects.
|
|
40
|
-
job.free();
|
|
41
|
-
}
|
|
33
|
+
return render(html, normalized, assetResolver && createAssetLoader(assetResolver));
|
|
34
|
+
}
|
|
35
|
+
/** Like `renderPdf`, but layout and serialization run in a new worker. An `assets` function still runs on the calling thread. */
|
|
36
|
+
async function renderPdfInWorker(html, options = {}) {
|
|
37
|
+
validateHtml(html);
|
|
38
|
+
const { assetResolver, ...normalized } = normalizeOptions(options);
|
|
39
|
+
return renderInWorker(spawnWorker, await compile(), html, normalized, assetResolver);
|
|
42
40
|
}
|
|
43
41
|
async function getEngineVersion() { await init(); return engine_version(); }
|
|
44
|
-
return { init, renderPdf, getEngineVersion };
|
|
42
|
+
return { init, renderPdf, renderPdfInWorker, getWasmModule, getEngineVersion };
|
|
45
43
|
}
|
|
46
44
|
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,EAAE,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,cAAc,EAAoB,MAAM,kBAAkB,CAAC;AASpE,MAAM,UAAU,SAAS,CAAC,EAAE,aAAa,EAAE,OAAO,GAAG,KAAK,EAAC,MAAM,EAAC,EAAE,CAAC,MAAM,EAAE,WAAW,EAAa;IACnG,IAAI,QAAiD,CAAC;IACtD,IAAI,KAAgC,CAAC;IAErC,SAAS,OAAO,CAAC,MAAmB;QAClC,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,QAAQ,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,OAAO,CAAC,MAAM,IAAI,MAAM,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE;aACnF,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACrE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,6FAA6F;IAC7F,SAAS,IAAI,CAAC,UAAuB,EAAE;QACrC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QACxB,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE;YAClB,MAAM,QAAQ,CAAC,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,GAAG,KAAK,GAAG,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,kHAAkH;IAClH,SAAS,aAAa,KAAkC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC;IAE3E,oFAAoF;IACpF,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,UAAsB,EAAE;QAC7D,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,MAAM,EAAE,aAAa,EAAE,GAAG,UAAU,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACnE,MAAM,IAAI,EAAE,CAAC;QACb,OAAO,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,aAAa,IAAI,iBAAiB,CAAC,aAAa,CAAC,CAAC,CAAC;IACrF,CAAC;IAED,iIAAiI;IACjI,KAAK,UAAU,iBAAiB,CAAC,IAAY,EAAE,UAAsB,EAAE;QACrE,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,MAAM,EAAE,aAAa,EAAE,GAAG,UAAU,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACnE,OAAO,cAAc,CAAC,WAAW,EAAE,MAAM,OAAO,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;IACvF,CAAC;IAED,KAAK,UAAU,gBAAgB,KAAsB,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,cAAc,EAAE,CAAC,CAAC,CAAC;IAC7F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC;AACjF,CAAC"}
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { AssetData, AssetMap, AssetResolver, AttachmentRelationship, BinaryData, InitOptions, Length, PageFormat, PageNumberOptions, PdfAttachment, PdfDate, PdfMetadata, PdfOptions, PdfXmpProperty, PdfXmpSchema, WasmSource } from "./types.js";
|
|
2
|
-
export declare const init: (options?: import("./types.js").InitOptions) => Promise<void>, renderPdf: (html: string, options?: import("./types.js").PdfOptions) => Promise<Uint8Array>, getEngineVersion: () => Promise<string>;
|
|
2
|
+
export declare const init: (options?: import("./types.js").InitOptions) => Promise<void>, renderPdf: (html: string, options?: import("./types.js").PdfOptions) => Promise<Uint8Array>, renderPdfInWorker: (html: string, options?: import("./types.js").PdfOptions) => Promise<Uint8Array>, getWasmModule: () => Promise<WebAssembly.Module>, getEngineVersion: () => Promise<string>;
|
|
3
3
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,sBAAsB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxP,eAAO,MAAQ,IAAI,iEAAE,SAAS,oFAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,sBAAsB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxP,eAAO,MAAQ,IAAI,iEAAE,SAAS,oFAAE,iBAAiB,oFAAE,aAAa,qCAAE,gBAAgB,uBAYhF,CAAC"}
|
package/dist/browser.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
import { createApi } from "./api.js";
|
|
2
|
-
export const { init, renderPdf, getEngineVersion } = createApi(
|
|
2
|
+
export const { init, renderPdf, renderPdfInWorker, getWasmModule, getEngineVersion } = createApi({
|
|
3
|
+
defaultSource: () => new URL("../wasm/blitz_pdf_bg.wasm", import.meta.url),
|
|
4
|
+
spawnWorker: events => {
|
|
5
|
+
const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });
|
|
6
|
+
worker.onmessage = event => events.message(event.data);
|
|
7
|
+
worker.onerror = event => {
|
|
8
|
+
event.preventDefault();
|
|
9
|
+
events.error(new Error(`Render worker failed: ${event.message || "unknown error"}`));
|
|
10
|
+
};
|
|
11
|
+
worker.onmessageerror = () => events.error(new Error("Render worker sent a message that could not be deserialized"));
|
|
12
|
+
return { post: (message, transfer) => worker.postMessage(message, transfer), terminate: () => worker.terminate() };
|
|
13
|
+
},
|
|
14
|
+
});
|
|
3
15
|
//# sourceMappingURL=browser.js.map
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAC;IAC/F,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;IAC1E,WAAW,EAAE,MAAM,CAAC,EAAE;QACpB,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvF,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,EAAE;YACvB,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,yBAAyB,KAAK,CAAC,OAAO,IAAI,eAAe,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC,CAAC;QACF,MAAM,CAAC,cAAc,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC,CAAC;QACrH,OAAO,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;IACrH,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker.d.ts","sourceRoot":"","sources":["../src/node-worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { parentPort } from "node:worker_threads";
|
|
2
|
+
import { serve } from "./worker-runtime.js";
|
|
3
|
+
const port = parentPort;
|
|
4
|
+
if (!port)
|
|
5
|
+
throw new Error("blitz-pdf node-worker must run in a worker thread");
|
|
6
|
+
serve({
|
|
7
|
+
post: (message, transfer) => port.postMessage(message, transfer),
|
|
8
|
+
onMessage: handler => { port.on("message", handler); },
|
|
9
|
+
});
|
|
10
|
+
//# sourceMappingURL=node-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker.js","sourceRoot":"","sources":["../src/node-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE5C,MAAM,IAAI,GAAG,UAAU,CAAC;AACxB,IAAI,CAAC,IAAI;IAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;AAChF,KAAK,CAAC;IACJ,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC;IAChE,SAAS,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACvD,CAAC,CAAC"}
|
package/dist/node.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { AssetData, AssetMap, AssetResolver, AttachmentRelationship, BinaryData, InitOptions, Length, PageFormat, PageNumberOptions, PdfAttachment, PdfDate, PdfMetadata, PdfOptions, PdfXmpProperty, PdfXmpSchema, WasmSource } from "./types.js";
|
|
2
|
-
export declare const init: (options?: import("./types.js").InitOptions) => Promise<void>, renderPdf: (html: string, options?: import("./types.js").PdfOptions) => Promise<Uint8Array>, getEngineVersion: () => Promise<string>;
|
|
2
|
+
export declare const init: (options?: import("./types.js").InitOptions) => Promise<void>, renderPdf: (html: string, options?: import("./types.js").PdfOptions) => Promise<Uint8Array>, renderPdfInWorker: (html: string, options?: import("./types.js").PdfOptions) => Promise<Uint8Array>, getWasmModule: () => Promise<WebAssembly.Module>, getEngineVersion: () => Promise<string>;
|
|
3
3
|
//# sourceMappingURL=node.d.ts.map
|
package/dist/node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAIA,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,sBAAsB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxP,eAAO,MAAQ,IAAI,iEAAE,SAAS,oFAAE,iBAAiB,oFAAE,aAAa,qCAAE,gBAAgB,uBAehF,CAAC"}
|
package/dist/node.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { Worker } from "node:worker_threads";
|
|
2
3
|
import { createApi } from "./api.js";
|
|
3
|
-
export const { init, renderPdf, getEngineVersion } = createApi(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export const { init, renderPdf, renderPdfInWorker, getWasmModule, getEngineVersion } = createApi({
|
|
5
|
+
defaultSource: () => readFile(new URL("../wasm/blitz_pdf_bg.wasm", import.meta.url)),
|
|
6
|
+
prepare: async (source) => {
|
|
7
|
+
if (source instanceof URL && source.protocol === "file:")
|
|
8
|
+
return readFile(source);
|
|
9
|
+
if (typeof source === "string" && source.startsWith("file:"))
|
|
10
|
+
return readFile(new URL(source));
|
|
11
|
+
return source;
|
|
12
|
+
},
|
|
13
|
+
spawnWorker: events => {
|
|
14
|
+
const worker = new Worker(new URL("./node-worker.js", import.meta.url));
|
|
15
|
+
worker.on("message", events.message);
|
|
16
|
+
worker.on("error", events.error);
|
|
17
|
+
worker.on("messageerror", events.error);
|
|
18
|
+
worker.on("exit", code => events.error(new Error(`Render worker exited with code ${code}`)));
|
|
19
|
+
return { post: (message, transfer) => worker.postMessage(message, transfer), terminate: () => void worker.terminate() };
|
|
20
|
+
},
|
|
9
21
|
});
|
|
10
22
|
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,SAAS,CAAC;IAC/F,aAAa,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpF,OAAO,EAAE,KAAK,EAAC,MAAM,EAAC,EAAE;QACtB,IAAI,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO;YAAE,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClF,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/F,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,WAAW,EAAE,MAAM,CAAC,EAAE;QACpB,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7F,OAAO,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;IAC1H,CAAC;CACF,CAAC,CAAC"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { normalizeOptions } from "./options.js";
|
|
2
|
+
/** Normalized options without the resolver function, so they can be posted to a worker. */
|
|
3
|
+
export type RenderOptions = Omit<ReturnType<typeof normalizeOptions>, "assetResolver">;
|
|
4
|
+
export type LoadAssets = (urls: string[]) => Promise<Uint8Array[]>;
|
|
5
|
+
export declare function validateHtml(html: unknown): asserts html is string;
|
|
6
|
+
/** Requires an initialized WASM instance in the current thread. */
|
|
7
|
+
export declare function render(html: string, options: RenderOptions, load?: LoadAssets): Promise<Uint8Array>;
|
|
8
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,2FAA2F;AAC3F,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,EAAE,eAAe,CAAC,CAAC;AACvF,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;AAEnE,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAGlE;AAED,mEAAmE;AACnE,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAczG"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { render_pdf, RenderJob } from "../wasm/blitz_pdf.js";
|
|
2
|
+
export function validateHtml(html) {
|
|
3
|
+
if (typeof html !== "string")
|
|
4
|
+
throw new TypeError("html must be a string");
|
|
5
|
+
if (new TextEncoder().encode(html).byteLength > 16 * 1024 * 1024)
|
|
6
|
+
throw new RangeError("HTML exceeds the 16 MiB limit");
|
|
7
|
+
}
|
|
8
|
+
/** Requires an initialized WASM instance in the current thread. */
|
|
9
|
+
export async function render(html, options, load) {
|
|
10
|
+
if (!load)
|
|
11
|
+
return render_pdf(html, options);
|
|
12
|
+
const job = new RenderJob(html, options);
|
|
13
|
+
try {
|
|
14
|
+
for (;;) {
|
|
15
|
+
const urls = job.pending_assets();
|
|
16
|
+
if (urls.length === 0)
|
|
17
|
+
return job.finish();
|
|
18
|
+
const data = await load(urls);
|
|
19
|
+
for (let i = 0; i < urls.length; i++)
|
|
20
|
+
job.provide_asset(urls[i], data[i]);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
// In-flight JS loaders never access this job, including after a sibling rejects.
|
|
25
|
+
job.free();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAO7D,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,MAAM,IAAI,SAAS,CAAC,uBAAuB,CAAC,CAAC;IAC3E,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI;QAAE,MAAM,IAAI,UAAU,CAAC,+BAA+B,CAAC,CAAC;AAC1H,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAY,EAAE,OAAsB,EAAE,IAAiB;IAClF,IAAI,CAAC,IAAI;QAAE,OAAO,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,IAAI,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC;YAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE;gBAAE,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;YAAS,CAAC;QACT,iFAAiF;QACjF,GAAG,CAAC,IAAI,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
|
package/dist/wasm.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm.d.ts","sourceRoot":"","sources":["../src/wasm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAcjF"}
|
package/dist/wasm.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function compileWasm(source) {
|
|
2
|
+
if (source instanceof WebAssembly.Module)
|
|
3
|
+
return source;
|
|
4
|
+
if (typeof source === "string" || source instanceof URL || (typeof Request === "function" && source instanceof Request)) {
|
|
5
|
+
source = await fetch(source);
|
|
6
|
+
}
|
|
7
|
+
if (typeof Response === "function" && source instanceof Response) {
|
|
8
|
+
if (!source.ok)
|
|
9
|
+
throw new Error(`Failed to fetch WASM: ${source.status} ${source.statusText} fetching '${source.url}'`);
|
|
10
|
+
// Streaming compilation rejects other MIME types, so fall back to buffering instead.
|
|
11
|
+
if (typeof WebAssembly.compileStreaming === "function" && source.headers.get("Content-Type") === "application/wasm") {
|
|
12
|
+
return WebAssembly.compileStreaming(source);
|
|
13
|
+
}
|
|
14
|
+
return WebAssembly.compile(await source.arrayBuffer());
|
|
15
|
+
}
|
|
16
|
+
return WebAssembly.compile(source);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=wasm.js.map
|
package/dist/wasm.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wasm.js","sourceRoot":"","sources":["../src/wasm.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAkB;IAClD,IAAI,MAAM,YAAY,WAAW,CAAC,MAAM;QAAE,OAAO,MAAM,CAAC;IACxD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,OAAO,KAAK,UAAU,IAAI,MAAM,YAAY,OAAO,CAAC,EAAE,CAAC;QACxH,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,UAAU,IAAI,MAAM,YAAY,QAAQ,EAAE,CAAC;QACjE,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,UAAU,cAAc,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC;QACxH,qFAAqF;QACrF,IAAI,OAAO,WAAW,CAAC,gBAAgB,KAAK,UAAU,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,kBAAkB,EAAE,CAAC;YACpH,OAAO,WAAW,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,WAAW,CAAC,OAAO,CAAC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,CAAC,MAAsB,CAAC,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RenderOptions } from "./render.js";
|
|
2
|
+
import type { AssetResolver } from "./types.js";
|
|
3
|
+
import type { HostMessage, WorkerMessage } from "./worker-protocol.js";
|
|
4
|
+
export interface WorkerEvents {
|
|
5
|
+
message(message: WorkerMessage): void;
|
|
6
|
+
/** Crashes, unreadable messages, or an exit before the render settled. */
|
|
7
|
+
error(error: unknown): void;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkerHandle {
|
|
10
|
+
post(message: HostMessage, transfer: ArrayBuffer[]): void;
|
|
11
|
+
terminate(): void;
|
|
12
|
+
}
|
|
13
|
+
export type SpawnWorker = (events: WorkerEvents) => WorkerHandle;
|
|
14
|
+
/** Renders in a fresh worker that instantiates the precompiled module, then terminates it. */
|
|
15
|
+
export declare function renderInWorker(spawn: SpawnWorker, module: WebAssembly.Module, html: string, options: RenderOptions, resolver: AssetResolver | undefined): Promise<Uint8Array>;
|
|
16
|
+
//# sourceMappingURL=worker-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-host.d.ts","sourceRoot":"","sources":["../src/worker-host.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEvE,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC,0EAA0E;IAC1E,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAC1D,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,YAAY,KAAK,YAAY,CAAC;AAEjE,8FAA8F;AAC9F,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EACzF,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAwBlF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createAssetLoader } from "./assets.js";
|
|
2
|
+
/** Renders in a fresh worker that instantiates the precompiled module, then terminates it. */
|
|
3
|
+
export function renderInWorker(spawn, module, html, options, resolver) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const load = resolver && createAssetLoader(resolver);
|
|
6
|
+
let settled = false;
|
|
7
|
+
const settle = (callback) => {
|
|
8
|
+
if (settled)
|
|
9
|
+
return;
|
|
10
|
+
settled = true;
|
|
11
|
+
worker.terminate();
|
|
12
|
+
callback();
|
|
13
|
+
};
|
|
14
|
+
const worker = spawn({
|
|
15
|
+
message(message) {
|
|
16
|
+
if (message.type === "done")
|
|
17
|
+
settle(() => resolve(message.pdf));
|
|
18
|
+
else if (message.type === "error")
|
|
19
|
+
settle(() => reject(message.error));
|
|
20
|
+
else if (!load)
|
|
21
|
+
settle(() => reject(new Error("Render worker requested assets without a resolver")));
|
|
22
|
+
else
|
|
23
|
+
load(message.urls).then(data => { if (!settled)
|
|
24
|
+
worker.post({ type: "assets", data }, data.map(bytes => bytes.buffer)); }, (error) => settle(() => reject(error)));
|
|
25
|
+
},
|
|
26
|
+
error(error) { settle(() => reject(error)); },
|
|
27
|
+
});
|
|
28
|
+
worker.post({ type: "render", module, html, options, external: load !== undefined }, []);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=worker-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-host.js","sourceRoot":"","sources":["../src/worker-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAkBhD,8FAA8F;AAC9F,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,MAA0B,EAAE,IAAY,EACzF,OAAsB,EAAE,QAAmC;IAC3D,OAAO,IAAI,OAAO,CAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACjD,MAAM,IAAI,GAAG,QAAQ,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACrD,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,CAAC,QAAoB,EAAE,EAAE;YACtC,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,MAAM,CAAC,SAAS,EAAE,CAAC;YACnB,QAAQ,EAAE,CAAC;QACb,CAAC,CAAC;QACF,MAAM,MAAM,GAAiB,KAAK,CAAC;YACjC,OAAO,CAAC,OAAO;gBACb,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;oBAAE,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;qBAC3D,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;oBAAE,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;qBAClE,IAAI,CAAC,IAAI;oBAAE,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC,CAAC,CAAC;;oBAChG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAC1B,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO;wBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,EAChH,CAAC,KAAc,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAChD,CAAC;YACJ,CAAC;YACD,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;SAC9C,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,KAAK,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;IAC3F,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RenderOptions } from "./render.js";
|
|
2
|
+
export type HostMessage = {
|
|
3
|
+
type: "render";
|
|
4
|
+
module: WebAssembly.Module;
|
|
5
|
+
html: string;
|
|
6
|
+
options: RenderOptions;
|
|
7
|
+
external: boolean;
|
|
8
|
+
} | {
|
|
9
|
+
type: "assets";
|
|
10
|
+
data: Uint8Array[];
|
|
11
|
+
};
|
|
12
|
+
export type WorkerMessage = {
|
|
13
|
+
type: "assets";
|
|
14
|
+
urls: string[];
|
|
15
|
+
} | {
|
|
16
|
+
type: "done";
|
|
17
|
+
pdf: Uint8Array;
|
|
18
|
+
} | {
|
|
19
|
+
type: "error";
|
|
20
|
+
error: unknown;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=worker-protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-protocol.d.ts","sourceRoot":"","sources":["../src/worker-protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,GACvG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAE3C,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-protocol.js","sourceRoot":"","sources":["../src/worker-protocol.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HostMessage, WorkerMessage } from "./worker-protocol.js";
|
|
2
|
+
export interface WorkerEndpoint {
|
|
3
|
+
post(message: WorkerMessage, transfer: ArrayBuffer[]): void;
|
|
4
|
+
onMessage(handler: (message: HostMessage) => void): void;
|
|
5
|
+
}
|
|
6
|
+
/** Worker side of `renderPdfInWorker`: one render per worker; asset bytes come from the host. */
|
|
7
|
+
export declare function serve(endpoint: WorkerEndpoint): void;
|
|
8
|
+
//# sourceMappingURL=worker-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-runtime.d.ts","sourceRoot":"","sources":["../src/worker-runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAC5D,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI,CAAC;CAC1D;AAED,iGAAiG;AACjG,wBAAgB,KAAK,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAwBpD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { initSync } from "../wasm/blitz_pdf.js";
|
|
2
|
+
import { render } from "./render.js";
|
|
3
|
+
/** Worker side of `renderPdfInWorker`: one render per worker; asset bytes come from the host. */
|
|
4
|
+
export function serve(endpoint) {
|
|
5
|
+
let receiveAssets;
|
|
6
|
+
endpoint.onMessage(async (message) => {
|
|
7
|
+
if (message.type === "assets") {
|
|
8
|
+
const receive = receiveAssets;
|
|
9
|
+
receiveAssets = undefined;
|
|
10
|
+
receive?.(message.data);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
initSync({ module: message.module });
|
|
15
|
+
const load = message.external
|
|
16
|
+
? (urls) => new Promise(resolve => {
|
|
17
|
+
receiveAssets = resolve;
|
|
18
|
+
endpoint.post({ type: "assets", urls }, []);
|
|
19
|
+
})
|
|
20
|
+
: undefined;
|
|
21
|
+
const pdf = await render(message.html, message.options, load);
|
|
22
|
+
endpoint.post({ type: "done", pdf }, [pdf.buffer]);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
try {
|
|
26
|
+
endpoint.post({ type: "error", error }, []);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
endpoint.post({ type: "error", error: new Error(String(error)) }, []);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=worker-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-runtime.js","sourceRoot":"","sources":["../src/worker-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC,iGAAiG;AACjG,MAAM,UAAU,KAAK,CAAC,QAAwB;IAC5C,IAAI,aAAyD,CAAC;IAC9D,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAC,OAAO,EAAC,EAAE;QACjC,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,OAAO,GAAG,aAAa,CAAC;YAC9B,aAAa,GAAG,SAAS,CAAC;YAC1B,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACrC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ;gBAC3B,CAAC,CAAC,CAAC,IAAc,EAAE,EAAE,CAAC,IAAI,OAAO,CAAe,OAAO,CAAC,EAAE;oBACxD,aAAa,GAAG,OAAO,CAAC;oBACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;gBAC9C,CAAC,CAAC;gBACF,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAqB,CAAC,CAAC,CAAC;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC;gBAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YAAC,CAAC;YACpD,MAAM,CAAC;gBAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAAC,CAAC;QAClF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":""}
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { serve } from "./worker-runtime.js";
|
|
2
|
+
const scope = globalThis;
|
|
3
|
+
serve({
|
|
4
|
+
post: (message, transfer) => scope.postMessage(message, transfer),
|
|
5
|
+
onMessage: handler => { scope.onmessage = event => handler(event.data); },
|
|
6
|
+
});
|
|
7
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAO5C,MAAM,KAAK,GAAG,UAA6C,CAAC;AAC5D,KAAK,CAAC;IACJ,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC;IACjE,SAAS,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;CAC1E,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitz-pdf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Render HTML and CSS to PDF with Blitz and WebAssembly in Node.js, Bun, and browsers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=20"
|
|
17
17
|
},
|
|
18
|
-
"sideEffects":
|
|
18
|
+
"sideEffects": [
|
|
19
|
+
"./dist/worker.js",
|
|
20
|
+
"./dist/node-worker.js"
|
|
21
|
+
],
|
|
19
22
|
"main": "./dist/node.js",
|
|
20
23
|
"module": "./dist/browser.js",
|
|
21
24
|
"types": "./dist/node.d.ts",
|
package/src/api.ts
CHANGED
|
@@ -1,44 +1,55 @@
|
|
|
1
|
-
import loadWasm, {
|
|
1
|
+
import loadWasm, { engine_version } from "../wasm/blitz_pdf.js";
|
|
2
2
|
import { createAssetLoader } from "./assets.js";
|
|
3
3
|
import { normalizeOptions } from "./options.js";
|
|
4
|
+
import { render, validateHtml } from "./render.js";
|
|
5
|
+
import { compileWasm } from "./wasm.js";
|
|
6
|
+
import { renderInWorker, type SpawnWorker } from "./worker-host.js";
|
|
4
7
|
import type { InitOptions, PdfOptions, WasmSource } from "./types.js";
|
|
5
8
|
|
|
6
|
-
export
|
|
7
|
-
|
|
9
|
+
export interface ApiConfig {
|
|
10
|
+
defaultSource: () => Promise<WasmSource> | WasmSource;
|
|
11
|
+
prepare?: (source: WasmSource) => Promise<WasmSource>;
|
|
12
|
+
spawnWorker: SpawnWorker;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createApi({ defaultSource, prepare = async source => source, spawnWorker }: ApiConfig) {
|
|
16
|
+
let compiled: Promise<WebAssembly.Module> | undefined;
|
|
8
17
|
let ready: Promise<void> | undefined;
|
|
9
18
|
|
|
19
|
+
function compile(source?: WasmSource): Promise<WebAssembly.Module> {
|
|
20
|
+
if (compiled) return compiled;
|
|
21
|
+
compiled = (async () => compileWasm(await prepare(source ?? await defaultSource())))()
|
|
22
|
+
.catch((error: unknown) => { compiled = undefined; throw error; });
|
|
23
|
+
return compiled;
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
/** Initialize once. Concurrent callers share initialization; failed loads can be retried. */
|
|
11
27
|
function init(options: InitOptions = {}): Promise<void> {
|
|
12
28
|
if (ready) return ready;
|
|
13
29
|
ready = (async () => {
|
|
14
|
-
|
|
15
|
-
await loadWasm({ module_or_path: source });
|
|
30
|
+
await loadWasm({ module_or_path: await compile(options.wasm) });
|
|
16
31
|
})().catch((error: unknown) => { ready = undefined; throw error; });
|
|
17
32
|
return ready;
|
|
18
33
|
}
|
|
19
34
|
|
|
35
|
+
/** The compiled WASM module, compiled once and shared with every worker. It can be posted to your own workers. */
|
|
36
|
+
function getWasmModule(): Promise<WebAssembly.Module> { return compile(); }
|
|
37
|
+
|
|
20
38
|
/** Render HTML/CSS to a vector PDF/A-3u with embedded fonts and selectable text. */
|
|
21
39
|
async function renderPdf(html: string, options: PdfOptions = {}): Promise<Uint8Array> {
|
|
22
|
-
|
|
23
|
-
if (new TextEncoder().encode(html).byteLength > 16 * 1024 * 1024) throw new RangeError("HTML exceeds the 16 MiB limit");
|
|
40
|
+
validateHtml(html);
|
|
24
41
|
const { assetResolver, ...normalized } = normalizeOptions(options);
|
|
25
42
|
await init();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
for (let i = 0; i < urls.length; i++) job.provide_asset(urls[i]!, data[i]!);
|
|
35
|
-
}
|
|
36
|
-
} finally {
|
|
37
|
-
// In-flight JS loaders never access this job, including after a sibling rejects.
|
|
38
|
-
job.free();
|
|
39
|
-
}
|
|
43
|
+
return render(html, normalized, assetResolver && createAssetLoader(assetResolver));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Like `renderPdf`, but layout and serialization run in a new worker. An `assets` function still runs on the calling thread. */
|
|
47
|
+
async function renderPdfInWorker(html: string, options: PdfOptions = {}): Promise<Uint8Array> {
|
|
48
|
+
validateHtml(html);
|
|
49
|
+
const { assetResolver, ...normalized } = normalizeOptions(options);
|
|
50
|
+
return renderInWorker(spawnWorker, await compile(), html, normalized, assetResolver);
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
async function getEngineVersion(): Promise<string> { await init(); return engine_version(); }
|
|
43
|
-
return { init, renderPdf, getEngineVersion };
|
|
54
|
+
return { init, renderPdf, renderPdfInWorker, getWasmModule, getEngineVersion };
|
|
44
55
|
}
|
package/src/browser.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { createApi } from "./api.js";
|
|
2
2
|
|
|
3
3
|
export type { AssetData, AssetMap, AssetResolver, AttachmentRelationship, BinaryData, InitOptions, Length, PageFormat, PageNumberOptions, PdfAttachment, PdfDate, PdfMetadata, PdfOptions, PdfXmpProperty, PdfXmpSchema, WasmSource } from "./types.js";
|
|
4
|
-
export const { init, renderPdf, getEngineVersion } = createApi(
|
|
5
|
-
() => new URL("../wasm/blitz_pdf_bg.wasm", import.meta.url),
|
|
6
|
-
|
|
4
|
+
export const { init, renderPdf, renderPdfInWorker, getWasmModule, getEngineVersion } = createApi({
|
|
5
|
+
defaultSource: () => new URL("../wasm/blitz_pdf_bg.wasm", import.meta.url),
|
|
6
|
+
spawnWorker: events => {
|
|
7
|
+
const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });
|
|
8
|
+
worker.onmessage = event => events.message(event.data);
|
|
9
|
+
worker.onerror = event => {
|
|
10
|
+
event.preventDefault();
|
|
11
|
+
events.error(new Error(`Render worker failed: ${event.message || "unknown error"}`));
|
|
12
|
+
};
|
|
13
|
+
worker.onmessageerror = () => events.error(new Error("Render worker sent a message that could not be deserialized"));
|
|
14
|
+
return { post: (message, transfer) => worker.postMessage(message, transfer), terminate: () => worker.terminate() };
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { parentPort } from "node:worker_threads";
|
|
2
|
+
import { serve } from "./worker-runtime.js";
|
|
3
|
+
|
|
4
|
+
const port = parentPort;
|
|
5
|
+
if (!port) throw new Error("blitz-pdf node-worker must run in a worker thread");
|
|
6
|
+
serve({
|
|
7
|
+
post: (message, transfer) => port.postMessage(message, transfer),
|
|
8
|
+
onMessage: handler => { port.on("message", handler); },
|
|
9
|
+
});
|
package/src/node.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { Worker } from "node:worker_threads";
|
|
2
3
|
import { createApi } from "./api.js";
|
|
3
4
|
|
|
4
5
|
export type { AssetData, AssetMap, AssetResolver, AttachmentRelationship, BinaryData, InitOptions, Length, PageFormat, PageNumberOptions, PdfAttachment, PdfDate, PdfMetadata, PdfOptions, PdfXmpProperty, PdfXmpSchema, WasmSource } from "./types.js";
|
|
5
|
-
export const { init, renderPdf, getEngineVersion } = createApi(
|
|
6
|
-
() => readFile(new URL("../wasm/blitz_pdf_bg.wasm", import.meta.url)),
|
|
7
|
-
async source => {
|
|
6
|
+
export const { init, renderPdf, renderPdfInWorker, getWasmModule, getEngineVersion } = createApi({
|
|
7
|
+
defaultSource: () => readFile(new URL("../wasm/blitz_pdf_bg.wasm", import.meta.url)),
|
|
8
|
+
prepare: async source => {
|
|
8
9
|
if (source instanceof URL && source.protocol === "file:") return readFile(source);
|
|
9
10
|
if (typeof source === "string" && source.startsWith("file:")) return readFile(new URL(source));
|
|
10
11
|
return source;
|
|
11
12
|
},
|
|
12
|
-
|
|
13
|
+
spawnWorker: events => {
|
|
14
|
+
const worker = new Worker(new URL("./node-worker.js", import.meta.url));
|
|
15
|
+
worker.on("message", events.message);
|
|
16
|
+
worker.on("error", events.error);
|
|
17
|
+
worker.on("messageerror", events.error);
|
|
18
|
+
worker.on("exit", code => events.error(new Error(`Render worker exited with code ${code}`)));
|
|
19
|
+
return { post: (message, transfer) => worker.postMessage(message, transfer), terminate: () => void worker.terminate() };
|
|
20
|
+
},
|
|
21
|
+
});
|
package/src/render.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { render_pdf, RenderJob } from "../wasm/blitz_pdf.js";
|
|
2
|
+
import type { normalizeOptions } from "./options.js";
|
|
3
|
+
|
|
4
|
+
/** Normalized options without the resolver function, so they can be posted to a worker. */
|
|
5
|
+
export type RenderOptions = Omit<ReturnType<typeof normalizeOptions>, "assetResolver">;
|
|
6
|
+
export type LoadAssets = (urls: string[]) => Promise<Uint8Array[]>;
|
|
7
|
+
|
|
8
|
+
export function validateHtml(html: unknown): asserts html is string {
|
|
9
|
+
if (typeof html !== "string") throw new TypeError("html must be a string");
|
|
10
|
+
if (new TextEncoder().encode(html).byteLength > 16 * 1024 * 1024) throw new RangeError("HTML exceeds the 16 MiB limit");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Requires an initialized WASM instance in the current thread. */
|
|
14
|
+
export async function render(html: string, options: RenderOptions, load?: LoadAssets): Promise<Uint8Array> {
|
|
15
|
+
if (!load) return render_pdf(html, options);
|
|
16
|
+
const job = new RenderJob(html, options);
|
|
17
|
+
try {
|
|
18
|
+
for (;;) {
|
|
19
|
+
const urls = job.pending_assets();
|
|
20
|
+
if (urls.length === 0) return job.finish();
|
|
21
|
+
const data = await load(urls);
|
|
22
|
+
for (let i = 0; i < urls.length; i++) job.provide_asset(urls[i]!, data[i]!);
|
|
23
|
+
}
|
|
24
|
+
} finally {
|
|
25
|
+
// In-flight JS loaders never access this job, including after a sibling rejects.
|
|
26
|
+
job.free();
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/wasm.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WasmSource } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export async function compileWasm(source: WasmSource): Promise<WebAssembly.Module> {
|
|
4
|
+
if (source instanceof WebAssembly.Module) return source;
|
|
5
|
+
if (typeof source === "string" || source instanceof URL || (typeof Request === "function" && source instanceof Request)) {
|
|
6
|
+
source = await fetch(source);
|
|
7
|
+
}
|
|
8
|
+
if (typeof Response === "function" && source instanceof Response) {
|
|
9
|
+
if (!source.ok) throw new Error(`Failed to fetch WASM: ${source.status} ${source.statusText} fetching '${source.url}'`);
|
|
10
|
+
// Streaming compilation rejects other MIME types, so fall back to buffering instead.
|
|
11
|
+
if (typeof WebAssembly.compileStreaming === "function" && source.headers.get("Content-Type") === "application/wasm") {
|
|
12
|
+
return WebAssembly.compileStreaming(source);
|
|
13
|
+
}
|
|
14
|
+
return WebAssembly.compile(await source.arrayBuffer());
|
|
15
|
+
}
|
|
16
|
+
return WebAssembly.compile(source as BufferSource);
|
|
17
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createAssetLoader } from "./assets.js";
|
|
2
|
+
import type { RenderOptions } from "./render.js";
|
|
3
|
+
import type { AssetResolver } from "./types.js";
|
|
4
|
+
import type { HostMessage, WorkerMessage } from "./worker-protocol.js";
|
|
5
|
+
|
|
6
|
+
export interface WorkerEvents {
|
|
7
|
+
message(message: WorkerMessage): void;
|
|
8
|
+
/** Crashes, unreadable messages, or an exit before the render settled. */
|
|
9
|
+
error(error: unknown): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface WorkerHandle {
|
|
13
|
+
post(message: HostMessage, transfer: ArrayBuffer[]): void;
|
|
14
|
+
terminate(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type SpawnWorker = (events: WorkerEvents) => WorkerHandle;
|
|
18
|
+
|
|
19
|
+
/** Renders in a fresh worker that instantiates the precompiled module, then terminates it. */
|
|
20
|
+
export function renderInWorker(spawn: SpawnWorker, module: WebAssembly.Module, html: string,
|
|
21
|
+
options: RenderOptions, resolver: AssetResolver | undefined): Promise<Uint8Array> {
|
|
22
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
23
|
+
const load = resolver && createAssetLoader(resolver);
|
|
24
|
+
let settled = false;
|
|
25
|
+
const settle = (callback: () => void) => {
|
|
26
|
+
if (settled) return;
|
|
27
|
+
settled = true;
|
|
28
|
+
worker.terminate();
|
|
29
|
+
callback();
|
|
30
|
+
};
|
|
31
|
+
const worker: WorkerHandle = spawn({
|
|
32
|
+
message(message) {
|
|
33
|
+
if (message.type === "done") settle(() => resolve(message.pdf));
|
|
34
|
+
else if (message.type === "error") settle(() => reject(message.error));
|
|
35
|
+
else if (!load) settle(() => reject(new Error("Render worker requested assets without a resolver")));
|
|
36
|
+
else load(message.urls).then(
|
|
37
|
+
data => { if (!settled) worker.post({ type: "assets", data }, data.map(bytes => bytes.buffer as ArrayBuffer)); },
|
|
38
|
+
(error: unknown) => settle(() => reject(error)),
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
error(error) { settle(() => reject(error)); },
|
|
42
|
+
});
|
|
43
|
+
worker.post({ type: "render", module, html, options, external: load !== undefined }, []);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RenderOptions } from "./render.js";
|
|
2
|
+
|
|
3
|
+
export type HostMessage =
|
|
4
|
+
| { type: "render"; module: WebAssembly.Module; html: string; options: RenderOptions; external: boolean }
|
|
5
|
+
| { type: "assets"; data: Uint8Array[] };
|
|
6
|
+
|
|
7
|
+
export type WorkerMessage =
|
|
8
|
+
| { type: "assets"; urls: string[] }
|
|
9
|
+
| { type: "done"; pdf: Uint8Array }
|
|
10
|
+
| { type: "error"; error: unknown };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { initSync } from "../wasm/blitz_pdf.js";
|
|
2
|
+
import { render } from "./render.js";
|
|
3
|
+
import type { HostMessage, WorkerMessage } from "./worker-protocol.js";
|
|
4
|
+
|
|
5
|
+
export interface WorkerEndpoint {
|
|
6
|
+
post(message: WorkerMessage, transfer: ArrayBuffer[]): void;
|
|
7
|
+
onMessage(handler: (message: HostMessage) => void): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Worker side of `renderPdfInWorker`: one render per worker; asset bytes come from the host. */
|
|
11
|
+
export function serve(endpoint: WorkerEndpoint): void {
|
|
12
|
+
let receiveAssets: ((data: Uint8Array[]) => void) | undefined;
|
|
13
|
+
endpoint.onMessage(async message => {
|
|
14
|
+
if (message.type === "assets") {
|
|
15
|
+
const receive = receiveAssets;
|
|
16
|
+
receiveAssets = undefined;
|
|
17
|
+
receive?.(message.data);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
initSync({ module: message.module });
|
|
22
|
+
const load = message.external
|
|
23
|
+
? (urls: string[]) => new Promise<Uint8Array[]>(resolve => {
|
|
24
|
+
receiveAssets = resolve;
|
|
25
|
+
endpoint.post({ type: "assets", urls }, []);
|
|
26
|
+
})
|
|
27
|
+
: undefined;
|
|
28
|
+
const pdf = await render(message.html, message.options, load);
|
|
29
|
+
endpoint.post({ type: "done", pdf }, [pdf.buffer as ArrayBuffer]);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
try { endpoint.post({ type: "error", error }, []); }
|
|
32
|
+
catch { endpoint.post({ type: "error", error: new Error(String(error)) }, []); }
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
package/src/worker.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { serve } from "./worker-runtime.js";
|
|
2
|
+
|
|
3
|
+
interface DedicatedWorkerScope {
|
|
4
|
+
postMessage(message: unknown, transfer: Transferable[]): void;
|
|
5
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const scope = globalThis as unknown as DedicatedWorkerScope;
|
|
9
|
+
serve({
|
|
10
|
+
post: (message, transfer) => scope.postMessage(message, transfer),
|
|
11
|
+
onMessage: handler => { scope.onmessage = event => handler(event.data); },
|
|
12
|
+
});
|
package/wasm/blitz_pdf_bg.wasm
CHANGED
|
Binary file
|