258 lines
7.3 KiB
JavaScript
258 lines
7.3 KiB
JavaScript
import { assert, warn } from "../shared/util.js";
|
|
import { FontFaceObject, FontLoader } from "./font_loader.js";
|
|
import { CanvasGraphics } from "./canvas.js";
|
|
import { DOMFilterFactory } from "./filter_factory.js";
|
|
import { MessageHandler } from "../shared/message_handler.js";
|
|
import { OffscreenCanvasFactory } from "./canvas_factory.js";
|
|
import { PDFObjects } from "./display_utils.js";
|
|
|
|
class RendererMessageHandler {
|
|
static #commonObjs = new PDFObjects();
|
|
|
|
static #objsMap = new Map();
|
|
|
|
static #tasks = new Map();
|
|
|
|
static #fontLoader = new FontLoader({
|
|
ownerDocument: self,
|
|
});
|
|
|
|
static #canvasFactory;
|
|
|
|
static #filterFactory;
|
|
|
|
static #enableHWA = false;
|
|
|
|
static {
|
|
this.initializeFromPort(self);
|
|
}
|
|
|
|
static pageObjs(pageIndex) {
|
|
let objs = this.#objsMap.get(pageIndex);
|
|
if (!objs) {
|
|
objs = new PDFObjects();
|
|
this.#objsMap.set(pageIndex, objs);
|
|
}
|
|
return objs;
|
|
}
|
|
|
|
static initializeFromPort(port) {
|
|
let terminated = false;
|
|
let mainHandler = new MessageHandler("renderer", "main", port);
|
|
mainHandler.send("ready", null);
|
|
mainHandler.on("Ready", function () {
|
|
// DO NOTHING
|
|
});
|
|
|
|
mainHandler.on("configure", ({ channelPort, enableHWA }) => {
|
|
this.#enableHWA = enableHWA;
|
|
const workerHandler = new MessageHandler(
|
|
"renderer-channel",
|
|
"worker-channel",
|
|
channelPort
|
|
);
|
|
this.#canvasFactory = new OffscreenCanvasFactory({
|
|
enableHWA,
|
|
});
|
|
this.#filterFactory = new DOMFilterFactory({});
|
|
|
|
workerHandler.on("commonobj", ([id, type, data]) => {
|
|
if (terminated) {
|
|
throw new Error("Renderer worker has been terminated.");
|
|
}
|
|
this.handleCommonObj(id, type, data, workerHandler);
|
|
});
|
|
|
|
workerHandler.on("obj", ([id, pageIndex, type, data]) => {
|
|
if (terminated) {
|
|
throw new Error("Renderer worker has been terminated.");
|
|
}
|
|
this.handleObj(pageIndex, id, type, data);
|
|
});
|
|
});
|
|
|
|
mainHandler.on(
|
|
"init",
|
|
({
|
|
pageIndex,
|
|
canvas,
|
|
map,
|
|
colors,
|
|
taskID,
|
|
transform,
|
|
viewport,
|
|
transparency,
|
|
background,
|
|
optionalContentConfig,
|
|
dependencyTracker,
|
|
}) => {
|
|
assert(!this.#tasks.has(taskID), "Task already initialized");
|
|
const ctx = canvas.getContext("2d", {
|
|
alpha: false,
|
|
willReadFrequently: this.#enableHWA,
|
|
});
|
|
const objs = this.pageObjs(pageIndex);
|
|
const gfx = new CanvasGraphics(
|
|
ctx,
|
|
this.#commonObjs,
|
|
objs,
|
|
this.#canvasFactory,
|
|
this.#filterFactory,
|
|
{ optionalContentConfig },
|
|
map,
|
|
colors,
|
|
dependencyTracker
|
|
);
|
|
gfx.beginDrawing({ transform, viewport, transparency, background });
|
|
this.#tasks.set(taskID, { canvas, gfx });
|
|
}
|
|
);
|
|
|
|
mainHandler.on(
|
|
"execute",
|
|
async ({ operatorList, operatorListIdx, taskID }) => {
|
|
if (terminated) {
|
|
throw new Error("Renderer worker has been terminated.");
|
|
}
|
|
const task = this.#tasks.get(taskID);
|
|
assert(task !== undefined, "Task not initialized");
|
|
return task.gfx.executeOperatorList(
|
|
operatorList,
|
|
operatorListIdx,
|
|
arg => mainHandler.send("continue", { taskID, arg })
|
|
);
|
|
}
|
|
);
|
|
|
|
mainHandler.on(
|
|
"growOperationsCount",
|
|
({ taskID, newOperatorListLength }) => {
|
|
if (terminated) {
|
|
throw new Error("Renderer worker has been terminated.");
|
|
}
|
|
const task = this.#tasks.get(taskID);
|
|
assert(task !== undefined, "Task not initialized");
|
|
task.gfx.dependencyTracker?.growOperationsCount(newOperatorListLength);
|
|
}
|
|
);
|
|
|
|
mainHandler.on("end", ({ taskID }) => {
|
|
if (terminated) {
|
|
throw new Error("Renderer worker has been terminated.");
|
|
}
|
|
const task = this.#tasks.get(taskID);
|
|
assert(task !== undefined, "Task not initialized");
|
|
task.gfx.endDrawing();
|
|
});
|
|
|
|
mainHandler.on("resetCanvas", ({ taskID }) => {
|
|
if (terminated) {
|
|
throw new Error("Renderer worker has been terminated.");
|
|
}
|
|
const task = this.#tasks.get(taskID);
|
|
assert(task !== undefined, "Task not initialized");
|
|
const canvas = task.canvas;
|
|
canvas.width = canvas.height = 0;
|
|
});
|
|
|
|
mainHandler.on("Terminate", async () => {
|
|
terminated = true;
|
|
this.#commonObjs.clear();
|
|
for (const objs of this.#objsMap.values()) {
|
|
objs.clear();
|
|
}
|
|
this.#objsMap.clear();
|
|
this.#tasks.clear();
|
|
this.#fontLoader.clear();
|
|
mainHandler.destroy();
|
|
mainHandler = null;
|
|
});
|
|
}
|
|
|
|
static handleCommonObj(id, type, exportedData, handler) {
|
|
if (this.#commonObjs.has(id)) {
|
|
return null;
|
|
}
|
|
|
|
switch (type) {
|
|
case "Font":
|
|
if ("error" in exportedData) {
|
|
const exportedError = exportedData.error;
|
|
warn(`Error during font loading: ${exportedError}`);
|
|
this.#commonObjs.resolve(id, exportedError);
|
|
break;
|
|
}
|
|
|
|
// TODO: Make FontInspector work again.
|
|
const inspectFont = null;
|
|
// this._params.pdfBug && globalThis.FontInspector?.enabled
|
|
// ? (font, url) => globalThis.FontInspector.fontAdded(font, url)
|
|
// : null;
|
|
const font = new FontFaceObject(exportedData, inspectFont);
|
|
|
|
this.#fontLoader
|
|
.bind(font)
|
|
.catch(() => handler.sendWithPromise("FontFallback", { id }))
|
|
.finally(() => {
|
|
if (!font.fontExtraProperties && font.data) {
|
|
// Immediately release the `font.data` property once the font
|
|
// has been attached to the DOM, since it's no longer needed,
|
|
// rather than waiting for a `PDFDocumentProxy.cleanup` call.
|
|
// Since `font.data` could be very large, e.g. in some cases
|
|
// multiple megabytes, this will help reduce memory usage.
|
|
font.data = null;
|
|
}
|
|
this.#commonObjs.resolve(id, font);
|
|
});
|
|
break;
|
|
case "CopyLocalImage":
|
|
const { imageRef } = exportedData;
|
|
assert(imageRef, "The imageRef must be defined.");
|
|
|
|
for (const objs of this.#objsMap.values()) {
|
|
for (const [, data] of objs) {
|
|
if (data?.ref !== imageRef) {
|
|
continue;
|
|
}
|
|
if (!data.dataLen) {
|
|
return null;
|
|
}
|
|
this.#commonObjs.resolve(id, structuredClone(data));
|
|
return data.dataLen;
|
|
}
|
|
}
|
|
break;
|
|
case "FontPath":
|
|
case "Image":
|
|
case "Pattern":
|
|
this.#commonObjs.resolve(id, exportedData);
|
|
break;
|
|
default:
|
|
throw new Error(`Got unknown common object type ${type}`);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
static handleObj(pageIndex, id, type, exportedData) {
|
|
const objs = this.pageObjs(pageIndex);
|
|
|
|
if (objs.has(id)) {
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case "Image":
|
|
case "Pattern":
|
|
objs.resolve(id, exportedData);
|
|
break;
|
|
default:
|
|
throw new Error(
|
|
`Got unknown object type ${type} id ${id} for page ${pageIndex} data ${JSON.stringify(exportedData)}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export { RendererMessageHandler };
|