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.
Files changed (53) hide show
  1. package/README.md +29 -0
  2. package/dist/api.d.ts +9 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +25 -27
  5. package/dist/api.js.map +1 -1
  6. package/dist/browser.d.ts +1 -1
  7. package/dist/browser.d.ts.map +1 -1
  8. package/dist/browser.js +13 -1
  9. package/dist/browser.js.map +1 -1
  10. package/dist/node-worker.d.ts +2 -0
  11. package/dist/node-worker.d.ts.map +1 -0
  12. package/dist/node-worker.js +10 -0
  13. package/dist/node-worker.js.map +1 -0
  14. package/dist/node.d.ts +1 -1
  15. package/dist/node.d.ts.map +1 -1
  16. package/dist/node.js +18 -6
  17. package/dist/node.js.map +1 -1
  18. package/dist/render.d.ts +8 -0
  19. package/dist/render.d.ts.map +1 -0
  20. package/dist/render.js +28 -0
  21. package/dist/render.js.map +1 -0
  22. package/dist/wasm.d.ts +3 -0
  23. package/dist/wasm.d.ts.map +1 -0
  24. package/dist/wasm.js +18 -0
  25. package/dist/wasm.js.map +1 -0
  26. package/dist/worker-host.d.ts +16 -0
  27. package/dist/worker-host.d.ts.map +1 -0
  28. package/dist/worker-host.js +31 -0
  29. package/dist/worker-host.js.map +1 -0
  30. package/dist/worker-protocol.d.ts +22 -0
  31. package/dist/worker-protocol.d.ts.map +1 -0
  32. package/dist/worker-protocol.js +2 -0
  33. package/dist/worker-protocol.js.map +1 -0
  34. package/dist/worker-runtime.d.ts +8 -0
  35. package/dist/worker-runtime.d.ts.map +1 -0
  36. package/dist/worker-runtime.js +34 -0
  37. package/dist/worker-runtime.js.map +1 -0
  38. package/dist/worker.d.ts +2 -0
  39. package/dist/worker.d.ts.map +1 -0
  40. package/dist/worker.js +7 -0
  41. package/dist/worker.js.map +1 -0
  42. package/package.json +5 -2
  43. package/src/api.ts +33 -22
  44. package/src/browser.ts +13 -3
  45. package/src/node-worker.ts +9 -0
  46. package/src/node.ts +13 -4
  47. package/src/render.ts +28 -0
  48. package/src/wasm.ts +17 -0
  49. package/src/worker-host.ts +45 -0
  50. package/src/worker-protocol.ts +10 -0
  51. package/src/worker-runtime.ts +35 -0
  52. package/src/worker.ts +12 -0
  53. 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 declare function createApi(defaultSource: () => Promise<WasmSource> | WasmSource, prepare?: (source: WasmSource) => Promise<WasmSource>): {
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":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtE,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,EAC7E,OAAO,GAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAA0B;qBAItD,WAAW,KAAQ,OAAO,CAAC,IAAI,CAAC;sBAUxB,MAAM,YAAW,UAAU,KAAQ,OAAO,CAAC,UAAU,CAAC;4BAqBlD,OAAO,CAAC,MAAM,CAAC;EAEnD"}
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, { render_pdf, engine_version, RenderJob } from "../wasm/blitz_pdf.js";
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
- export function createApi(defaultSource, prepare = async (source) => source) {
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
- const source = await prepare(options.wasm ?? await defaultSource());
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
- if (typeof html !== "string")
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
- if (!assetResolver)
25
- return render_pdf(html, normalized);
26
- const load = createAssetLoader(assetResolver);
27
- const job = new RenderJob(html, normalized);
28
- try {
29
- for (;;) {
30
- const urls = job.pending_assets();
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,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,UAAU,SAAS,CAAC,aAAqD,EAC7E,UAAuD,KAAK,EAAC,MAAM,EAAC,EAAE,CAAC,MAAM;IAC7E,IAAI,KAAgC,CAAC;IAErC,6FAA6F;IAC7F,SAAS,IAAI,CAAC,UAAuB,EAAE;QACrC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QACxB,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE;YAClB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,MAAM,aAAa,EAAE,CAAC,CAAC;YACpE,MAAM,QAAQ,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7C,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,oFAAoF;IACpF,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,UAAsB,EAAE;QAC7D,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,MAAM,IAAI,SAAS,CAAC,uBAAuB,CAAC,CAAC;QAC3E,IAAI,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI;YAAE,MAAM,IAAI,UAAU,CAAC,+BAA+B,CAAC,CAAC;QACxH,MAAM,EAAE,aAAa,EAAE,GAAG,UAAU,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACnE,MAAM,IAAI,EAAE,CAAC;QACb,IAAI,CAAC,aAAa;YAAE,OAAO,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC;YACH,SAAS,CAAC;gBACR,MAAM,IAAI,GAAG,GAAG,CAAC,cAAc,EAAE,CAAC;gBAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC;gBAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE;oBAAE,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC;YAC9E,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,iFAAiF;YACjF,GAAG,CAAC,IAAI,EAAE,CAAC;QACb,CAAC;IACH,CAAC;IAED,KAAK,UAAU,gBAAgB,KAAsB,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,cAAc,EAAE,CAAC,CAAC,CAAC;IAC7F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;AAC/C,CAAC"}
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
@@ -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,uBAE/C,CAAC"}
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(() => new URL("../wasm/blitz_pdf_bg.wasm", import.meta.url));
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
@@ -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,CAC5D,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAC5D,CAAC"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=node-worker.d.ts.map
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAGA,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,uBAO/C,CAAC"}
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(() => readFile(new URL("../wasm/blitz_pdf_bg.wasm", import.meta.url)), async (source) => {
4
- if (source instanceof URL && source.protocol === "file:")
5
- return readFile(source);
6
- if (typeof source === "string" && source.startsWith("file:"))
7
- return readFile(new URL(source));
8
- return source;
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,CAC5D,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EACrE,KAAK,EAAC,MAAM,EAAC,EAAE;IACb,IAAI,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClF,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/F,OAAO,MAAM,CAAC;AAChB,CAAC,CACF,CAAC"}
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"}
@@ -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,3 @@
1
+ import type { WasmSource } from "./types.js";
2
+ export declare function compileWasm(source: WasmSource): Promise<WebAssembly.Module>;
3
+ //# sourceMappingURL=wasm.d.ts.map
@@ -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
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=worker-protocol.js.map
@@ -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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=worker.d.ts.map
@@ -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.4",
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": false,
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, { render_pdf, engine_version, RenderJob } from "../wasm/blitz_pdf.js";
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 function createApi(defaultSource: () => Promise<WasmSource> | WasmSource,
7
- prepare: (source: WasmSource) => Promise<WasmSource> = async source => source) {
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
- const source = await prepare(options.wasm ?? await defaultSource());
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
- if (typeof html !== "string") throw new TypeError("html must be a string");
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
- if (!assetResolver) return render_pdf(html, normalized);
27
- const load = createAssetLoader(assetResolver);
28
- const job = new RenderJob(html, normalized);
29
- try {
30
- for (;;) {
31
- const urls = job.pending_assets();
32
- if (urls.length === 0) return job.finish();
33
- const data = await load(urls);
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
+ });
Binary file