Save in a Web Worker
You’ll save a PDF in a Web Worker, reporting progress as it goes and stopping when the user presses Cancel. You need this in a browser app once files get large, because a save on the main thread freezes the page until it ends.
saveInWorker starts a Web Worker, opens the bytes there and saves them as a full rewrite, handing your onProgress and signal to each call:
// Saving a PDF in a Web Worker, so a large file never blocks the page: `@laminapdf/sdk/worker`// runs the same API there, and each method returns a Promise. The save reports its progress step// by step, and aborting the signal (a Cancel button, say) stops the work.import { type CallOptions, connect } from "@laminapdf/sdk/worker";
/** What the save reports to and listens to. */export type SaveInWorkerOptions = Pick<CallOptions, "onProgress" | "signal">;
/** * Opens `bytes` in a Web Worker and saves them there as a full rewrite. * * @param bytes - any PDF; it is copied to the worker (`transfer: true` would move it). * @param options - `onProgress` is called after each step of the save. Aborting `signal` stops * the work wherever it is (starting the Web Worker, opening, or at the save's next step), and * the promise rejects with a `cancelled` error. * @returns the saved file. */export async function saveInWorker(bytes: Uint8Array, options: SaveInWorkerOptions = {}): Promise<Uint8Array> { const client = await connect(options); try { await using doc = await client.open(bytes, options); return await doc.save({ mode: "fullRewrite" }, options); } finally { client.terminate(); }}The promise resolves to the saved file, or rejects with cancelled once the signal aborts. Lamina PDF’s tests run it in Chromium, Firefox and WebKit.
How it works
Section titled “How it works”connectstarts the Web Worker file that ships with@laminapdf/sdkand checks that it comes from the same build as your page.client.opencopies the bytes into the Web Worker and returns a remote document, whose methods return promises.await usingcloses it there when the block ends.- The Web Worker saves in steps of 20,000 units of work, about one per object in a full rewrite. Between steps it calls
onProgresswithdone,totalandphase, at most every 50 ms, so a small file saves in one step and reports none. - Aborting the signal rejects the call at once with
cancelled, anderror.causeholds the signal’s reason. A save stops at its next step. client.terminate()infinallystops the Web Worker however the save ended, which also stops an open that was still running.
What to change
Section titled “What to change”- A progress bar: in
onProgress, set a<progress>element’svaluetodoneand, when the save knows it, itsmaxtototal. - Large files: pass
transfer: truetoclient.opento move the buffer instead of copying it, which leaves your copy empty. See Keep the page responsive. - Several files: call
connectonce, reuse the client for each file, and terminate it when the page no longer needs it. - Edits before the save: apply them with
await doc.apply(ops), then save in the defaultautomode. It appends them as an incremental save when the file allows one. See Save a PDF.
Next: a Cloudflare Worker that generates a PDF per request.