@inkoxide/wasm
v1.0.0
Published
The Inkoxide PDF engine as WebAssembly, for browsers: declarative document tree in, PDF bytes out.
Readme
@inkoxide/wasm
The Inkoxide PDF engine built as WebAssembly: a declarative document tree
(JSON) in, PDF bytes out, in browsers. The same engine, limits and error codes
as the native inkoxide package,
with the same functions, plus init, threadCount, loadHyphenation and
clearCaches, and one more render option, deferredFonts.
This page is for using the engine directly. To render templates in a browser,
in a Web Worker, use the browser entry of your framework's package
(@inkoxide/react/browser, @inkoxide/vue/browser, …), which runs this
package for you: see
In the browser.
npm install @inkoxide/wasmimport { init, registerFont, renderSync, InkoxideError } from '@inkoxide/wasm';
// Once: compiles `inkoxide.wasm`, which bundlers resolve next to this module.
await init();
registerFont('Inter-Regular', new Uint8Array(await (await fetch('/fonts/Inter-Regular.ttf')).arrayBuffer()));
const doc = {
version: 1,
fonts: [{ id: 'Inter-Regular', family: 'Inter' }],
pages: [{ size: { width: 595.28, height: 841.89 },
children: [{ type: 'text', runs: [{ text: 'Hello', font: 'Inter-Regular', size: 24 }] }] }],
};
try {
const pdf = renderSync(JSON.stringify(doc));
const url = URL.createObjectURL(new Blob([pdf], { type: 'application/pdf' }));
} catch (e) {
if (!(e instanceof InkoxideError)) throw e;
console.error(e.code, e.path, e.message);
}How it differs from inkoxide:
- One thread, or a pool. Renders run on the calling thread,
renderincluded: in a page, render in a Web Worker.threadsmay only be1, unlessinitstarted a pool (below). - JSON only.
format: 'msgpack'is aPARSE_ERROR. - Downloads on demand. The module (about 1.4 MB with brotli) leaves out the built-in
hyphenation patterns:
renderfetches a language's fromhyphenation/next to the module the first time a document hyphenates text in it, and the fonts of itsdeferredFontsoption (id → URL) that a document draws with, or needs as a fallback.renderSynccannot fetch, and fails withNOT_LOADED: callloadHyphenation(['de'])first, or userender. init(source?, options?)takes a URL, aResponse, the bytes or a compiledWebAssembly.Module, of either module. On one thread it needs no cross-origin isolation; a Content Security Policy needsscript-src 'wasm-unsafe-eval'. A module that cannot be fetched or compiled rejects it withNOT_LOADED, as does any call before it has resolved.- A bug in the engine (
INTERNAL) ends its instance: the next call runs in a new one, with the fonts and hyphenation patterns registered so far, which the package keeps a copy of. - Memory grows and is not given back.
clearCaches()empties the caches renders share, for later renders to reuse the memory. - Fonts. A WOFF2 font gives other PDF bytes than its TrueType file.
Threads. In a worker of a page that is cross-origin isolated
(Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy:
require-corp, on every response), or in Node, init can start a pool of
threads: it then loads inkoxide-threads.wasm, which shares its memory with
that many Web Workers (thread.js; worker threads in Node), and renders spread their pages over them,
for the same bytes:
// A channel reaches every tab of the origin: a name of this engine's own.
const trapChannel = `inkoxide-trap-${crypto.randomUUID()}`;
await init(undefined, { threads: Math.min(navigator.hardwareConcurrency, 8), trapChannel });
threadCount(); // the pool's size, or 1 where memory cannot be sharedA render's threads: 1 keeps to the calling thread. A bug on a pool thread
cannot end the call that waits for it: the thread posts { message } on the
BroadcastChannel named trapChannel, and the page, listening, ends the
worker. @inkoxide/render/browser, under each front end's browser entry, does all of this with its threads option.
From a CDN. A page cannot start a worker from a script of another origin. When this package
is served from one, the pool's threads start as workers of the page's origin from a blob: URL
that imports thread.js. The CDN must serve thread.js, the modules and the other assets with
CORS (Access-Control-Allow-Origin); on a page that is cross-origin isolated, its files also
need CORS or Cross-Origin-Resource-Policy: cross-origin. A Content Security Policy needs
worker-src blob:, and script-src and connect-src that allow the CDN. A script of the
page's own origin starts its workers as before, and worker-src 'self' is enough.
From a CDN, give init the module with its
Subresource Integrity
hash, so that the browser refuses any other bytes, and the folder of the
hyphenation patterns:
const base = 'https://cdn.jsdelivr.net/npm/@inkoxide/[email protected]/';
await init(fetch(`${base}inkoxide.wasm`, { integrity: 'sha384-0tq2eWJQXL9wv3Om/Swz1rFtRoOKdRznjAmlz/o+dGAWzu6iriBTXM+uf/iUWRri' }), {
hyphenation: `${base}hyphenation/`,
});With threads, each module has its hash. Fetch only the one the page will use.
A hash covers the module alone: the pool's thread.js, which its workers
import, and the hyphenation patterns load without one.
const threads = crossOriginIsolated ? 4 : 1;
const module = threads > 1
? fetch(`${base}inkoxide-threads.wasm`, { integrity: 'sha384-Er0NyqOHjYzPSJFnwOpb2fjzNH2F2F+ntDgcT//1uw3thvxnWPsF5Kfvq7ty4lTB' })
: fetch(`${base}inkoxide.wasm`, { integrity: 'sha384-0tq2eWJQXL9wv3Om/Swz1rFtRoOKdRznjAmlz/o+dGAWzu6iriBTXM+uf/iUWRri' });
await init(module, { threads, hyphenation: `${base}hyphenation/` });Options, defaults and error codes are typed in index.d.ts and described in
the API reference.
