Guide
Browser
The pdg-browser package — run the engine as WebAssembly in the browser, with the wasm inlined for zero-config use.
pdg-browser runs the whole engine — parse, layout, paginate, PDF render — as
WebAssembly, client-side. No server, no upload. The output is byte-identical to
the CLI and the other bindings; this very playground uses the same wasm.
The .wasm is inlined into the JavaScript, so the package is a single ES
module with no separate file to serve and no bundler configuration — it drops into
Vite, webpack, esbuild, Rollup, a plain <script type="module">, or a CDN.
// Load on demand so the inlined wasm is code-split out of your main bundle.
const { render } = await import("pdg-browser");
const pdf = await render(xml); // Uint8Array
const url = URL.createObjectURL(new Blob([pdf], { type: "application/pdf" }));
window.open(url);
The XML you render is the document format documented throughout these guides — see Document structure and the element reference.
Installation
npm install pdg-browser
ESM only. No peer dependencies, no build configuration.
Load it lazily
The module is a few MB because the wasm is inlined. Load it with a dynamic
import(), not a static top-level import: bundlers put a dynamically-imported
module — wasm and all — in a separate chunk, so it stays out of your initial
bundle and is fetched only when you first render a PDF.
async function downloadInvoice(xml) {
const { render } = await import("pdg-browser");
const pdf = await render(xml);
triggerDownload(new Blob([pdf], { type: "application/pdf" }));
}
To hide the one-time download and compile, warm it up ahead of time (both the
import and init() are idempotent):
const warm = import("pdg-browser").then((m) => m.init());
// … later, when the user acts:
const { render } = await import("pdg-browser"); // resolved instantly
await warm; // engine already initialized
A static import works too, but pulls the whole inlined wasm into your main
bundle — reserve it for a dedicated Web Worker (where it also keeps rendering off
the main thread).
Rendering
// PDF bytes. Rejects on failure.
const pdf = await render(xml);
// Bytes plus metadata.
const result = await renderWithMeta(xml);
result.pages; // 3
result.bytes; // 48213
result.objects; // 412
result.timings; // { parseMs, layoutMs, pdfMs }
result.warnings; // [Diagnostic, ...]
result.pdf; // Uint8Array
result.blob(); // an application/pdf Blob
// init() / isReady() let you preload and check readiness.
Assets
The engine has no filesystem, so a document's external assets are fetched by the host. By default:
- Relative
<font-face src>,<image src>,<pdf src>are fetched againstbase(the document base URL by default; override withoptions.base). - Absolute
http(s)sources are fetched as-is. - Google fonts (
<google-font>) come from a public CDN, keyless. - Built-in fonts are embedded in the wasm — never fetched.
A missing asset degrades gracefully (the engine warns and falls back).
// Resolve relative paths against a specific base:
await render(xml, { base: "https://cdn.example.com/pdf-assets/" });
Take over fetching entirely — to serve bytes from memory, a cache, or your own
endpoint — with resolve:
await render(xml, {
resolve: async (req) => {
// req: { key, kind: "image" | "font-src" | "pdf" | "google", src?, name?, slot? }
if (req.kind === "image") return myImageBytes(req.src); // Uint8Array | null
return null; // skip — engine warns and falls back
},
});
Errors and diagnostics
A failed render rejects with RenderError (a strict-mode block or a fatal
parse/layout error), carrying diagnostics:
const { RenderError } = await import("pdg-browser");
try {
await render(xml);
} catch (err) {
if (err instanceof RenderError) {
for (const d of err.diagnostics) {
d.severity; // "warning" | "error"
d.code; // "unknown-tag", "content-dropped", …
d.message;
d.line;
d.col;
}
}
}
Warnings don't reject — a recoverable render resolves to a Result; read
result.warnings.
Server-side rendering
For PDFs on the server (in a request handler, a job, a CLI), use the Node, Ruby,
or Python guides — those drive the engine binary directly. pdg-browser is for
in-page, client-side rendering.