move common handler code to a shared file

This commit is contained in:
Ujjwal Sharma 2025-07-16 12:58:54 +02:00
parent aaa7a20874
commit 12d033cabe
6 changed files with 144 additions and 233 deletions

View File

@ -129,11 +129,10 @@ class Page {
};
}
#createPartialEvaluator(handler, rendererHandler) {
#createPartialEvaluator(handler) {
return new PartialEvaluator({
xref: this.xref,
handler,
rendererHandler,
pageIndex: this.pageIndex,
idFactory: this._localIdFactory,
fontCache: this.fontCache,
@ -450,10 +449,7 @@ class Page {
const contentStreamPromise = this.getContentStream();
const resourcesPromise = this.loadResources(RESOURCES_KEYS_OPERATOR_LIST);
const partialEvaluator = this.#createPartialEvaluator(
handler,
rendererHandler
);
const partialEvaluator = this.#createPartialEvaluator(handler);
const newAnnotsByPage = !this.xfaFactory
? getNewAnnotationsMap(annotationStorage)
@ -1336,7 +1332,7 @@ class PDFDocument {
this.xfaFactory.setImages(xfaImages);
}
async #loadXfaFonts(handler, task, rendererHandler) {
async #loadXfaFonts(handler, task) {
const acroForm = await this.pdfManager.ensureCatalog("acroForm");
if (!acroForm) {
return;
@ -1362,7 +1358,6 @@ class PDFDocument {
const partialEvaluator = new PartialEvaluator({
xref: this.xref,
handler,
rendererHandler,
pageIndex: -1,
idFactory: this._globalIdFactory,
fontCache,
@ -1475,9 +1470,9 @@ class PDFDocument {
this.xfaFactory.appendFonts(pdfFonts, reallyMissingFonts);
}
loadXfaResources(handler, task, rendererHandler) {
loadXfaResources(handler, task) {
return Promise.all([
this.#loadXfaFonts(handler, task, rendererHandler).catch(() => {
this.#loadXfaFonts(handler, task).catch(() => {
// Ignore errors, to allow the document to load.
}),
this.#loadXfaImages(),

View File

@ -222,7 +222,6 @@ class PartialEvaluator {
constructor({
xref,
handler,
rendererHandler,
pageIndex,
idFactory,
fontCache,
@ -235,7 +234,6 @@ class PartialEvaluator {
}) {
this.xref = xref;
this.handler = handler;
this.rendererHandler = rendererHandler;
this.pageIndex = pageIndex;
this.idFactory = idFactory;
this.fontCache = fontCache;
@ -555,19 +553,13 @@ class PartialEvaluator {
const transfers = imgData ? [imgData.bitmap || imgData.data.buffer] : null;
if (this.parsingType3Font || cacheGlobally) {
this.handler.send("commonobj", [objId, "Image", imgData], transfers);
return this.rendererHandler.send(
return this.handler.send(
"commonobj",
[objId, "Image", imgData],
transfers
);
}
this.handler.send(
"obj",
[objId, this.pageIndex, "Image", imgData],
transfers
);
return this.rendererHandler.send(
return this.handler.send(
"obj",
[objId, this.pageIndex, "Image", imgData],
transfers
@ -795,10 +787,11 @@ class PartialEvaluator {
// globally, check if the image is still cached locally on the main-thread
// to avoid having to re-parse the image (since that can be slow).
if (w * h > 250000 || hasMask) {
const localLength = await this.rendererHandler.sendWithPromise(
"commonobj",
[objId, "CopyLocalImage", { imageRef }]
);
const localLength = await this.sendWithPromise("commonobj", [
objId,
"CopyLocalImage",
{ imageRef },
]);
if (localLength) {
this.globalImageCache.setData(imageRef, globalCacheData);
@ -1028,7 +1021,6 @@ class PartialEvaluator {
state.font = translated.font;
translated.send(this.handler);
translated.send(this.rendererHandler);
return translated.loadedName;
}
@ -1048,7 +1040,7 @@ class PartialEvaluator {
PartialEvaluator.buildFontPaths(
font,
glyphs,
this.rendererHandler,
this.handler,
this.options
);
}
@ -1526,15 +1518,8 @@ class PartialEvaluator {
if (this.parsingType3Font) {
this.handler.send("commonobj", [id, "Pattern", patternIR]);
this.rendererHandler.send("commonobj", [id, "Pattern", patternIR]);
} else {
this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
this.rendererHandler.send("obj", [
id,
this.pageIndex,
"Pattern",
patternIR,
]);
}
return id;
}

View File

@ -873,6 +873,10 @@ class WorkerMessageHandler {
.then(page => pdfManager.ensure(page, "getStructTree"));
});
handler.on("FontFallback", function (data) {
return pdfManager.fontFallback(data.id, handler);
});
rendererHandler.on("FontFallback", function (data) {
return pdfManager.fontFallback(data.id, rendererHandler);
});

View File

@ -45,7 +45,6 @@ import {
RenderingCancelledException,
StatTimer,
} from "./display_utils.js";
import { FontFaceObject, FontLoader } from "./font_loader.js";
import {
getDataProp,
getFactoryUrlProp,
@ -68,7 +67,7 @@ import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
import { DOMFilterFactory } from "./filter_factory.js";
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
import { DOMWasmFactory } from "display-wasm_factory";
import { FontInfo } from "../shared/obj-bin-transform.js";
import { FontLoader } from "./font_loader.js";
import { GlobalWorkerOptions } from "./worker_options.js";
import { Metadata } from "./metadata.js";
import { OptionalContentConfig } from "./optional_content_config.js";
@ -76,6 +75,7 @@ import { PDFDataTransportStream } from "./transport_stream.js";
import { PDFFetchStream } from "display-fetch_stream";
import { PDFNetworkStream } from "display-network";
import { PDFNodeStream } from "display-node_stream";
import { setupHandler } from "../shared/handle_objs.js";
import { TextLayer } from "./text_layer.js";
import { XfaText } from "./xfa_text.js";
@ -2828,105 +2828,13 @@ class WorkerTransport {
page._startRenderPage(data.transparency, data.cacheKey);
});
messageHandler.on("commonobj", ([id, type, exportedData]) => {
if (this.destroyed) {
return null; // Ignore any pending requests if the worker was terminated.
}
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;
}
const fontData = new FontInfo(exportedData);
const inspectFont =
this._params.pdfBug && globalThis.FontInspector?.enabled
? (font, url) => globalThis.FontInspector.fontAdded(font, url)
: null;
const font = new FontFaceObject(
fontData,
inspectFont,
exportedData.extra,
exportedData.charProcOperatorList
);
this.fontLoader
.bind(font)
.catch(() => messageHandler.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.clearData();
}
this.commonObjs.resolve(id, font);
});
break;
case "CopyLocalImage":
const { imageRef } = exportedData;
assert(imageRef, "The imageRef must be defined.");
for (const pageProxy of this.#pageCache.values()) {
for (const [, data] of pageProxy.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;
});
messageHandler.on("obj", ([id, pageIndex, type, imageData]) => {
if (this.destroyed) {
// Ignore any pending requests if the worker was terminated.
return;
}
const pageProxy = this.#pageCache.get(pageIndex);
if (pageProxy.objs.has(id)) {
return;
}
// Don't store data *after* cleanup has successfully run, see bug 1854145.
if (pageProxy._intentStates.size === 0) {
imageData?.bitmap?.close(); // Release any `ImageBitmap` data.
return;
}
switch (type) {
case "Image":
case "Pattern":
pageProxy.objs.resolve(id, imageData);
break;
default:
throw new Error(`Got unknown object type ${type}`);
}
});
setupHandler(
messageHandler,
this.destroyed,
this.commonObjs,
this.#pageCache,
this.fontLoader
);
messageHandler.on("DocProgress", data => {
if (this.destroyed) {

View File

@ -1,10 +1,11 @@
import { assert, warn } from "../shared/util.js";
import { FontFaceObject, FontLoader } from "./font_loader.js";
import { assert } from "../shared/util.js";
import { CanvasGraphics } from "./canvas.js";
import { DOMFilterFactory } from "./filter_factory.js";
import { FontLoader } from "./font_loader.js";
import { MessageHandler } from "../shared/message_handler.js";
import { OffscreenCanvasFactory } from "./canvas_factory.js";
import { PDFObjects } from "./display_utils.js";
import { setupHandler } from "../shared/handle_objs.js";
class RendererMessageHandler {
static #commonObjs = new PDFObjects();
@ -56,19 +57,13 @@ class RendererMessageHandler {
});
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);
});
setupHandler(
workerHandler,
terminated,
this.#commonObjs,
this.#objsMap,
this.#fontLoader
);
});
mainHandler.on(
@ -168,90 +163,6 @@ class RendererMessageHandler {
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 };

108
src/shared/handle_objs.js Normal file
View File

@ -0,0 +1,108 @@
import { assert, warn } from "../shared/util.js";
import { FontFaceObject } from "../display/font_loader.js";
import { FontInfo } from "../shared/obj-bin-transform.js";
function setupHandler(handler, destroyed, commonObjs, pages, fontLoader) {
handler.on("commonobj", ([id, type, exportedData]) => {
if (destroyed) {
return null; // Ignore any pending requests if the worker was terminated.
}
if (commonObjs.has(id)) {
return null;
}
switch (type) {
case "Font":
if ("error" in exportedData) {
const exportedError = exportedData.error;
warn(`Error during font loading: ${exportedError}`);
commonObjs.resolve(id, exportedError);
break;
}
const fontData = new FontInfo(exportedData);
const inspectFont = // TODO: Fix this
// this._params.pdfBug && globalThis.FontInspector?.enabled
// ? (font, url) => globalThis.FontInspector.fontAdded(font, url)
// : null;
null;
const font = new FontFaceObject(
fontData,
inspectFont,
exportedData.extra,
exportedData.charProcOperatorList
);
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.clearData();
}
commonObjs.resolve(id, font);
});
break;
case "CopyLocalImage":
const { imageRef } = exportedData;
assert(imageRef, "The imageRef must be defined.");
for (const page of pages.values()) {
for (const [, data] of page.objs) {
if (data?.ref !== imageRef) {
continue;
}
if (!data.dataLen) {
return null;
}
commonObjs.resolve(id, structuredClone(data));
return data.dataLen;
}
}
break;
case "FontPath":
case "Image":
case "Pattern":
commonObjs.resolve(id, exportedData);
break;
default:
throw new Error(`Got unknown common object type ${type}`);
}
return null;
});
handler.on("obj", ([id, pageIndex, type, imageData]) => {
if (destroyed) {
// Ignore any pending requests if the worker was terminated.
return;
}
const page = pages.get(pageIndex);
if (page.objs.has(id)) {
return;
}
// Don't store data *after* cleanup has successfully run, see bug 1854145.
if (page._intentStates.size === 0) {
imageData?.bitmap?.close(); // Release any `ImageBitmap` data.
return;
}
switch (type) {
case "Image":
case "Pattern":
page.objs.resolve(id, imageData);
break;
default:
throw new Error(`Got unknown object type ${type}`);
}
});
}
export { setupHandler };