pdg XML → PDF

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 against base (the document base URL by default; override with options.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.