Change PDF rendering
This commit is contained in:
87
src-tauri/assets/viewer/render-worker.js
Normal file
87
src-tauri/assets/viewer/render-worker.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* render-worker.js — Web Worker for off-main-thread PDF rendering.
|
||||
*
|
||||
* Has its own PDF.js instance. Renders pages via OffscreenCanvas and transfers
|
||||
* ImageBitmap objects back to the main thread with zero-copy transfer.
|
||||
*
|
||||
* Message protocol:
|
||||
*
|
||||
* Main → Worker:
|
||||
* { type: "init", pdfData: ArrayBuffer } (transferred, not copied)
|
||||
* { type: "render", pageNum, scale, gen }
|
||||
* { type: "cancel", gen } — renderGen check handles this implicitly
|
||||
* { type: "cleanup" } — pdfDoc.cleanup() (free caches)
|
||||
* { type: "destroy" } — pdfDoc.destroy(); self.close()
|
||||
*
|
||||
* Worker → Main:
|
||||
* { type: "ready", numPages }
|
||||
* { type: "rendered", pageNum, gen, bitmap } (bitmap as transferable)
|
||||
* { type: "error", message }
|
||||
*/
|
||||
|
||||
importScripts("brittle://app/pdfjs/build/pdf.min.js");
|
||||
|
||||
const pdfjsLib = globalThis.pdfjsLib;
|
||||
// Do NOT set workerSrc to a brittle:// URL here. When PDF.js tries to spawn its
|
||||
// own sub-worker with new Worker("brittle://…") and that fails, it falls back to
|
||||
// a "fake worker" that injects a <script> tag — which throws because `document`
|
||||
// does not exist inside a Web Worker.
|
||||
//
|
||||
// Instead, we fetch pdf.worker.min.js in handleInit() and hand PDF.js a blob URL
|
||||
// it can actually use with new Worker(blobUrl). Blob URLs created inside a worker
|
||||
// are same-origin and can be used for nested workers.
|
||||
|
||||
let pdfDoc = null;
|
||||
|
||||
self.onmessage = async function (ev) {
|
||||
const msg = ev.data;
|
||||
switch (msg.type) {
|
||||
case "init": await handleInit(msg); break;
|
||||
case "render": await handleRender(msg); break;
|
||||
case "cleanup":
|
||||
if (pdfDoc) await pdfDoc.cleanup();
|
||||
break;
|
||||
case "destroy":
|
||||
if (pdfDoc) { await pdfDoc.destroy(); pdfDoc = null; }
|
||||
self.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
async function handleInit({ pdfData }) {
|
||||
try {
|
||||
// Fetch pdf.worker.min.js and create a blob URL so PDF.js can spawn its
|
||||
// own sub-worker without relying on brittle:// for new Worker().
|
||||
const resp = await fetch("brittle://app/pdfjs/build/pdf.worker.min.js");
|
||||
const text = await resp.text();
|
||||
const blob = new Blob([text], { type: "application/javascript" });
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = URL.createObjectURL(blob);
|
||||
|
||||
pdfDoc = await pdfjsLib.getDocument({ data: new Uint8Array(pdfData) }).promise;
|
||||
self.postMessage({ type: "ready", numPages: pdfDoc.numPages });
|
||||
} catch (e) {
|
||||
self.postMessage({ type: "error", message: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRender({ pageNum, scale, gen }) {
|
||||
if (!pdfDoc) return;
|
||||
let page = null;
|
||||
try {
|
||||
page = await pdfDoc.getPage(pageNum);
|
||||
const vp = page.getViewport({ scale });
|
||||
const width = Math.round(vp.width);
|
||||
const height = Math.round(vp.height);
|
||||
const canvas = new OffscreenCanvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
||||
const bitmap = canvas.transferToImageBitmap();
|
||||
self.postMessage({ type: "rendered", pageNum, gen, bitmap }, [bitmap]);
|
||||
} catch (e) {
|
||||
if (e?.name !== "RenderingCancelledException") {
|
||||
console.warn("[render-worker] render error page", pageNum, e);
|
||||
}
|
||||
} finally {
|
||||
if (page) page.cleanup();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user