Merge 12d033cabebab63a6da97517ae2c743b74e82c25 into 745e42701ff2a2c442dd2e524f2be6d05f4378c9
This commit is contained in:
commit
0ff4a8b028
20
gulpfile.mjs
20
gulpfile.mjs
@ -589,6 +589,18 @@ function createWorkerBundle(defines) {
|
|||||||
.pipe(webpack2Stream(workerFileConfig));
|
.pipe(webpack2Stream(workerFileConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createRendererWorkerBundle(defines) {
|
||||||
|
const rendererWorkerFileConfig = createWebpackConfig(defines, {
|
||||||
|
filename: defines.MINIFIED ? "pdf.renderer.min.mjs" : "pdf.renderer.mjs",
|
||||||
|
library: {
|
||||||
|
type: "module",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return gulp
|
||||||
|
.src("./src/pdf.renderer.js", { encoding: false })
|
||||||
|
.pipe(webpack2Stream(rendererWorkerFileConfig));
|
||||||
|
}
|
||||||
|
|
||||||
function createWebBundle(defines, options) {
|
function createWebBundle(defines, options) {
|
||||||
const viewerFileConfig = createWebpackConfig(
|
const viewerFileConfig = createWebpackConfig(
|
||||||
defines,
|
defines,
|
||||||
@ -1103,6 +1115,7 @@ function buildGeneric(defines, dir) {
|
|||||||
return ordered([
|
return ordered([
|
||||||
createMainBundle(defines).pipe(gulp.dest(dir + "build")),
|
createMainBundle(defines).pipe(gulp.dest(dir + "build")),
|
||||||
createWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
|
createWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
|
||||||
|
createRendererWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
|
||||||
createSandboxBundle(defines).pipe(gulp.dest(dir + "build")),
|
createSandboxBundle(defines).pipe(gulp.dest(dir + "build")),
|
||||||
createWebBundle(defines, {
|
createWebBundle(defines, {
|
||||||
defaultPreferencesDir: defines.SKIP_BABEL
|
defaultPreferencesDir: defines.SKIP_BABEL
|
||||||
@ -1294,6 +1307,7 @@ function buildMinified(defines, dir) {
|
|||||||
return ordered([
|
return ordered([
|
||||||
createMainBundle(defines).pipe(gulp.dest(dir + "build")),
|
createMainBundle(defines).pipe(gulp.dest(dir + "build")),
|
||||||
createWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
|
createWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
|
||||||
|
createRendererWorkerBundle(defines).pipe(gulp.dest(dir + "build")),
|
||||||
createSandboxBundle(defines).pipe(gulp.dest(dir + "build")),
|
createSandboxBundle(defines).pipe(gulp.dest(dir + "build")),
|
||||||
createImageDecodersBundle({ ...defines, IMAGE_DECODERS: true }).pipe(
|
createImageDecodersBundle({ ...defines, IMAGE_DECODERS: true }).pipe(
|
||||||
gulp.dest(dir + "image_decoders")
|
gulp.dest(dir + "image_decoders")
|
||||||
@ -1439,6 +1453,9 @@ gulp.task(
|
|||||||
createWorkerBundle(defines).pipe(
|
createWorkerBundle(defines).pipe(
|
||||||
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
|
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
|
||||||
),
|
),
|
||||||
|
createRendererWorkerBundle(defines).pipe(
|
||||||
|
gulp.dest(MOZCENTRAL_CONTENT_DIR + "build")
|
||||||
|
),
|
||||||
createWebBundle(defines, { defaultPreferencesDir: "mozcentral/" }).pipe(
|
createWebBundle(defines, { defaultPreferencesDir: "mozcentral/" }).pipe(
|
||||||
gulp.dest(MOZCENTRAL_CONTENT_DIR + "web")
|
gulp.dest(MOZCENTRAL_CONTENT_DIR + "web")
|
||||||
),
|
),
|
||||||
@ -1536,6 +1553,9 @@ gulp.task(
|
|||||||
createWorkerBundle(defines).pipe(
|
createWorkerBundle(defines).pipe(
|
||||||
gulp.dest(CHROME_BUILD_CONTENT_DIR + "build")
|
gulp.dest(CHROME_BUILD_CONTENT_DIR + "build")
|
||||||
),
|
),
|
||||||
|
createRendererWorkerBundle(defines).pipe(
|
||||||
|
gulp.dest(CHROME_BUILD_CONTENT_DIR + "build")
|
||||||
|
),
|
||||||
createSandboxBundle(defines).pipe(
|
createSandboxBundle(defines).pipe(
|
||||||
gulp.dest(CHROME_BUILD_CONTENT_DIR + "build")
|
gulp.dest(CHROME_BUILD_CONTENT_DIR + "build")
|
||||||
),
|
),
|
||||||
|
|||||||
@ -438,6 +438,7 @@ class Page {
|
|||||||
|
|
||||||
async getOperatorList({
|
async getOperatorList({
|
||||||
handler,
|
handler,
|
||||||
|
rendererHandler,
|
||||||
sink,
|
sink,
|
||||||
task,
|
task,
|
||||||
intent,
|
intent,
|
||||||
|
|||||||
@ -787,7 +787,7 @@ class PartialEvaluator {
|
|||||||
// globally, check if the image is still cached locally on the main-thread
|
// 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).
|
// to avoid having to re-parse the image (since that can be slow).
|
||||||
if (w * h > 250000 || hasMask) {
|
if (w * h > 250000 || hasMask) {
|
||||||
const localLength = await this.handler.sendWithPromise("commonobj", [
|
const localLength = await this.sendWithPromise("commonobj", [
|
||||||
objId,
|
objId,
|
||||||
"CopyLocalImage",
|
"CopyLocalImage",
|
||||||
{ imageRef },
|
{ imageRef },
|
||||||
|
|||||||
@ -82,6 +82,8 @@ class WorkerMessageHandler {
|
|||||||
|
|
||||||
static setup(handler, port) {
|
static setup(handler, port) {
|
||||||
let testMessageProcessed = false;
|
let testMessageProcessed = false;
|
||||||
|
let rendererHandler = null;
|
||||||
|
|
||||||
handler.on("test", data => {
|
handler.on("test", data => {
|
||||||
if (testMessageProcessed) {
|
if (testMessageProcessed) {
|
||||||
return; // we already processed 'test' message once
|
return; // we already processed 'test' message once
|
||||||
@ -94,12 +96,19 @@ class WorkerMessageHandler {
|
|||||||
|
|
||||||
handler.on("configure", data => {
|
handler.on("configure", data => {
|
||||||
setVerbosityLevel(data.verbosity);
|
setVerbosityLevel(data.verbosity);
|
||||||
|
rendererHandler = new MessageHandler(
|
||||||
|
"worker-channel",
|
||||||
|
"renderer-channel",
|
||||||
|
data.channelPort
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
handler.on("GetDocRequest", data => this.createDocumentHandler(data, port));
|
handler.on("GetDocRequest", data =>
|
||||||
|
this.createDocumentHandler(data, port, rendererHandler)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static createDocumentHandler(docParams, port) {
|
static createDocumentHandler(docParams, port, rendererHandler) {
|
||||||
// This context is actually holds references on pdfManager and handler,
|
// This context is actually holds references on pdfManager and handler,
|
||||||
// until the latter is destroyed.
|
// until the latter is destroyed.
|
||||||
let pdfManager;
|
let pdfManager;
|
||||||
@ -173,7 +182,11 @@ class WorkerMessageHandler {
|
|||||||
const task = new WorkerTask("loadXfaResources");
|
const task = new WorkerTask("loadXfaResources");
|
||||||
startWorkerTask(task);
|
startWorkerTask(task);
|
||||||
|
|
||||||
await pdfManager.ensureDoc("loadXfaResources", [handler, task]);
|
await pdfManager.ensureDoc("loadXfaResources", [
|
||||||
|
handler,
|
||||||
|
task,
|
||||||
|
rendererHandler,
|
||||||
|
]);
|
||||||
finishWorkerTask(task);
|
finishWorkerTask(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,6 +785,7 @@ class WorkerMessageHandler {
|
|||||||
page
|
page
|
||||||
.getOperatorList({
|
.getOperatorList({
|
||||||
handler,
|
handler,
|
||||||
|
rendererHandler,
|
||||||
sink,
|
sink,
|
||||||
task,
|
task,
|
||||||
intent: data.intent,
|
intent: data.intent,
|
||||||
@ -859,6 +873,10 @@ class WorkerMessageHandler {
|
|||||||
return pdfManager.fontFallback(data.id, handler);
|
return pdfManager.fontFallback(data.id, handler);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rendererHandler.on("FontFallback", function (data) {
|
||||||
|
return pdfManager.fontFallback(data.id, rendererHandler);
|
||||||
|
});
|
||||||
|
|
||||||
handler.on("Cleanup", function (data) {
|
handler.on("Cleanup", function (data) {
|
||||||
return pdfManager.cleanup(/* manuallyTriggered = */ true);
|
return pdfManager.cleanup(/* manuallyTriggered = */ true);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -41,10 +41,10 @@ import {
|
|||||||
isDataScheme,
|
isDataScheme,
|
||||||
isValidFetchUrl,
|
isValidFetchUrl,
|
||||||
PageViewport,
|
PageViewport,
|
||||||
|
PDFObjects,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
StatTimer,
|
StatTimer,
|
||||||
} from "./display_utils.js";
|
} from "./display_utils.js";
|
||||||
import { FontFaceObject, FontLoader } from "./font_loader.js";
|
|
||||||
import {
|
import {
|
||||||
getDataProp,
|
getDataProp,
|
||||||
getFactoryUrlProp,
|
getFactoryUrlProp,
|
||||||
@ -67,7 +67,7 @@ import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
|
|||||||
import { DOMFilterFactory } from "./filter_factory.js";
|
import { DOMFilterFactory } from "./filter_factory.js";
|
||||||
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
|
import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
|
||||||
import { DOMWasmFactory } from "display-wasm_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 { GlobalWorkerOptions } from "./worker_options.js";
|
||||||
import { Metadata } from "./metadata.js";
|
import { Metadata } from "./metadata.js";
|
||||||
import { OptionalContentConfig } from "./optional_content_config.js";
|
import { OptionalContentConfig } from "./optional_content_config.js";
|
||||||
@ -75,7 +75,7 @@ import { PDFDataTransportStream } from "./transport_stream.js";
|
|||||||
import { PDFFetchStream } from "display-fetch_stream";
|
import { PDFFetchStream } from "display-fetch_stream";
|
||||||
import { PDFNetworkStream } from "display-network";
|
import { PDFNetworkStream } from "display-network";
|
||||||
import { PDFNodeStream } from "display-node_stream";
|
import { PDFNodeStream } from "display-node_stream";
|
||||||
import { PDFObjects } from "./pdf_objects.js";
|
import { setupHandler } from "../shared/handle_objs.js";
|
||||||
import { TextLayer } from "./text_layer.js";
|
import { TextLayer } from "./text_layer.js";
|
||||||
import { XfaText } from "./xfa_text.js";
|
import { XfaText } from "./xfa_text.js";
|
||||||
|
|
||||||
@ -390,16 +390,23 @@ function getDocument(src = {}) {
|
|||||||
: new WasmFactory({ baseUrl: wasmUrl }),
|
: new WasmFactory({ baseUrl: wasmUrl }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const workerChannel = new MessageChannel();
|
||||||
|
|
||||||
if (!worker) {
|
if (!worker) {
|
||||||
// Worker was not provided -- creating and owning our own. If message port
|
// Worker was not provided -- creating and owning our own. If message port
|
||||||
// is specified in global worker options, using it.
|
// is specified in global worker options, using it.
|
||||||
worker = PDFWorker.create({
|
worker = PDFWorker.create({
|
||||||
verbosity,
|
verbosity,
|
||||||
port: GlobalWorkerOptions.workerPort,
|
port: GlobalWorkerOptions.workerPort,
|
||||||
|
channelPort: workerChannel.port1,
|
||||||
});
|
});
|
||||||
task._worker = worker;
|
task._worker = worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderer = new RendererWorker(workerChannel.port2, enableHWA);
|
||||||
|
const rendererHandler = renderer.handler;
|
||||||
|
task.renderer = renderer;
|
||||||
|
|
||||||
const docParams = {
|
const docParams = {
|
||||||
docId,
|
docId,
|
||||||
apiVersion:
|
apiVersion:
|
||||||
@ -504,10 +511,12 @@ function getDocument(src = {}) {
|
|||||||
networkStream,
|
networkStream,
|
||||||
transportParams,
|
transportParams,
|
||||||
transportFactory,
|
transportFactory,
|
||||||
enableHWA
|
enableHWA,
|
||||||
|
rendererHandler
|
||||||
);
|
);
|
||||||
task._transport = transport;
|
task._transport = transport;
|
||||||
messageHandler.send("Ready", null);
|
messageHandler.send("Ready", null);
|
||||||
|
rendererHandler.send("Ready", null);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(task._capability.reject);
|
.catch(task._capability.reject);
|
||||||
@ -544,6 +553,8 @@ class PDFDocumentLoadingTask {
|
|||||||
*/
|
*/
|
||||||
_worker = null;
|
_worker = null;
|
||||||
|
|
||||||
|
#renderer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique identifier for the document loading task.
|
* Unique identifier for the document loading task.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -603,6 +614,8 @@ class PDFDocumentLoadingTask {
|
|||||||
|
|
||||||
this._worker?.destroy();
|
this._worker?.destroy();
|
||||||
this._worker = null;
|
this._worker = null;
|
||||||
|
this.#renderer.destroy();
|
||||||
|
this.#renderer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -614,6 +627,17 @@ class PDFDocumentLoadingTask {
|
|||||||
async getData() {
|
async getData() {
|
||||||
return this._transport.getData();
|
return this._transport.getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get renderer() {
|
||||||
|
return this.#renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
set renderer(renderer) {
|
||||||
|
if (this.#renderer) {
|
||||||
|
throw new Error("PDFDocumentLoadingTask.renderer: already set.");
|
||||||
|
}
|
||||||
|
this.#renderer = renderer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1571,7 +1595,7 @@ class PDFPageProxy {
|
|||||||
useRequestAnimationFrame: !intentPrint,
|
useRequestAnimationFrame: !intentPrint,
|
||||||
pdfBug: this._pdfBug,
|
pdfBug: this._pdfBug,
|
||||||
pageColors,
|
pageColors,
|
||||||
enableHWA: this._transport.enableHWA,
|
rendererHandler: this._transport.rendererHandler,
|
||||||
operationsFilter,
|
operationsFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1989,6 +2013,12 @@ class PDFPageProxy {
|
|||||||
get stats() {
|
get stats() {
|
||||||
return this._stats;
|
return this._stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetCanvas(taskID) {
|
||||||
|
this._transport.rendererHandler.send("resetCanvas", {
|
||||||
|
taskID,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1997,6 +2027,8 @@ class PDFPageProxy {
|
|||||||
* @property {Worker} [port] - The `workerPort` object.
|
* @property {Worker} [port] - The `workerPort` object.
|
||||||
* @property {number} [verbosity] - Controls the logging level;
|
* @property {number} [verbosity] - Controls the logging level;
|
||||||
* the constants from {@link VerbosityLevel} should be used.
|
* the constants from {@link VerbosityLevel} should be used.
|
||||||
|
* @property {MessagePort} [channelPort] - The channel port to use for
|
||||||
|
* communication with the renderer thread.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2078,10 +2110,12 @@ class PDFWorker {
|
|||||||
name = null,
|
name = null,
|
||||||
port = null,
|
port = null,
|
||||||
verbosity = getVerbosityLevel(),
|
verbosity = getVerbosityLevel(),
|
||||||
|
channelPort = null,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
this.verbosity = verbosity;
|
this.verbosity = verbosity;
|
||||||
|
this.channelPort = channelPort;
|
||||||
|
|
||||||
if (port) {
|
if (port) {
|
||||||
if (PDFWorker.#workerPorts.has(port)) {
|
if (PDFWorker.#workerPorts.has(port)) {
|
||||||
@ -2114,9 +2148,14 @@ class PDFWorker {
|
|||||||
#resolve() {
|
#resolve() {
|
||||||
this.#capability.resolve();
|
this.#capability.resolve();
|
||||||
// Send global setting, e.g. verbosity level.
|
// Send global setting, e.g. verbosity level.
|
||||||
this.#messageHandler.send("configure", {
|
this.#messageHandler.send(
|
||||||
|
"configure",
|
||||||
|
{
|
||||||
verbosity: this.verbosity,
|
verbosity: this.verbosity,
|
||||||
});
|
channelPort: this.channelPort,
|
||||||
|
},
|
||||||
|
[this.channelPort]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2352,6 +2391,39 @@ class PDFWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RendererWorker {
|
||||||
|
#worker;
|
||||||
|
|
||||||
|
#handler;
|
||||||
|
|
||||||
|
constructor(channelPort, enableHWA) {
|
||||||
|
const src =
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
typeof PDFJSDev === "undefined"
|
||||||
|
? "../src/pdf.renderer.js"
|
||||||
|
: PDFJSDev.test("MOZCENTRAL")
|
||||||
|
? "resource://pdf.js/build/pdf.renderer.mjs"
|
||||||
|
: "../build/pdf.renderer.mjs";
|
||||||
|
this.#worker = new Worker(src, { type: "module" });
|
||||||
|
this.#handler = new MessageHandler("main", "renderer", this.#worker);
|
||||||
|
this.#handler.send("configure", { channelPort, enableHWA }, [channelPort]);
|
||||||
|
this.#handler.on("ready", () => {
|
||||||
|
// DO NOTHING
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get handler() {
|
||||||
|
return this.#handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.#worker.terminate();
|
||||||
|
this.#worker = null;
|
||||||
|
this.#handler.destroy();
|
||||||
|
this.#handler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For internal use only.
|
* For internal use only.
|
||||||
* @ignore
|
* @ignore
|
||||||
@ -2373,9 +2445,11 @@ class WorkerTransport {
|
|||||||
networkStream,
|
networkStream,
|
||||||
params,
|
params,
|
||||||
factory,
|
factory,
|
||||||
enableHWA
|
enableHWA,
|
||||||
|
rendererHandler
|
||||||
) {
|
) {
|
||||||
this.messageHandler = messageHandler;
|
this.messageHandler = messageHandler;
|
||||||
|
this.rendererHandler = rendererHandler;
|
||||||
this.loadingTask = loadingTask;
|
this.loadingTask = loadingTask;
|
||||||
this.commonObjs = new PDFObjects();
|
this.commonObjs = new PDFObjects();
|
||||||
this.fontLoader = new FontLoader({
|
this.fontLoader = new FontLoader({
|
||||||
@ -2541,8 +2615,13 @@ class WorkerTransport {
|
|||||||
const terminated = this.messageHandler.sendWithPromise("Terminate", null);
|
const terminated = this.messageHandler.sendWithPromise("Terminate", null);
|
||||||
waitOn.push(terminated);
|
waitOn.push(terminated);
|
||||||
|
|
||||||
|
const terminatedRenderer = this.rendererHandler.sendWithPromise(
|
||||||
|
"Terminate",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
waitOn.push(terminatedRenderer);
|
||||||
|
|
||||||
Promise.all(waitOn).then(() => {
|
Promise.all(waitOn).then(() => {
|
||||||
this.commonObjs.clear();
|
|
||||||
this.fontLoader.clear();
|
this.fontLoader.clear();
|
||||||
this.#methodPromises.clear();
|
this.#methodPromises.clear();
|
||||||
this.filterFactory.destroy();
|
this.filterFactory.destroy();
|
||||||
@ -2561,7 +2640,13 @@ class WorkerTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupMessageHandler() {
|
setupMessageHandler() {
|
||||||
const { messageHandler, loadingTask } = this;
|
const { messageHandler, loadingTask, rendererHandler } = this;
|
||||||
|
|
||||||
|
rendererHandler.on("continue", ({ taskID, arg }) => {
|
||||||
|
const continueFn = InternalRenderTask.continueFnMap.get(taskID);
|
||||||
|
assert(continueFn, `No continue function for taskID: ${taskID}`);
|
||||||
|
continueFn.call(arg);
|
||||||
|
});
|
||||||
|
|
||||||
messageHandler.on("GetReader", (data, sink) => {
|
messageHandler.on("GetReader", (data, sink) => {
|
||||||
assert(
|
assert(
|
||||||
@ -2740,105 +2825,13 @@ class WorkerTransport {
|
|||||||
page._startRenderPage(data.transparency, data.cacheKey);
|
page._startRenderPage(data.transparency, data.cacheKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
messageHandler.on("commonobj", ([id, type, exportedData]) => {
|
setupHandler(
|
||||||
if (this.destroyed) {
|
messageHandler,
|
||||||
return null; // Ignore any pending requests if the worker was terminated.
|
this.destroyed,
|
||||||
}
|
this.commonObjs,
|
||||||
|
this.#pageCache,
|
||||||
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
|
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}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
messageHandler.on("DocProgress", data => {
|
messageHandler.on("DocProgress", data => {
|
||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
@ -3163,6 +3156,10 @@ class RenderTask {
|
|||||||
(separateAnnots.canvas && annotationCanvasMap?.size > 0)
|
(separateAnnots.canvas && annotationCanvasMap?.size > 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get taskID() {
|
||||||
|
return this.#internalRenderTask.taskID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3174,6 +3171,10 @@ class InternalRenderTask {
|
|||||||
|
|
||||||
static #canvasInUse = new WeakSet();
|
static #canvasInUse = new WeakSet();
|
||||||
|
|
||||||
|
static #taskCounter = 0n;
|
||||||
|
|
||||||
|
static continueFnMap = new Map();
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
callback,
|
callback,
|
||||||
params,
|
params,
|
||||||
@ -3187,9 +3188,10 @@ class InternalRenderTask {
|
|||||||
useRequestAnimationFrame = false,
|
useRequestAnimationFrame = false,
|
||||||
pdfBug = false,
|
pdfBug = false,
|
||||||
pageColors = null,
|
pageColors = null,
|
||||||
enableHWA = false,
|
rendererHandler,
|
||||||
operationsFilter = null,
|
operationsFilter = null,
|
||||||
}) {
|
}) {
|
||||||
|
this.taskID = InternalRenderTask.#taskCounter++;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.objs = objs;
|
this.objs = objs;
|
||||||
@ -3218,7 +3220,9 @@ class InternalRenderTask {
|
|||||||
this._nextBound = this._next.bind(this);
|
this._nextBound = this._next.bind(this);
|
||||||
this._canvas = params.canvas;
|
this._canvas = params.canvas;
|
||||||
this._canvasContext = params.canvas ? null : params.canvasContext;
|
this._canvasContext = params.canvas ? null : params.canvasContext;
|
||||||
this._enableHWA = enableHWA;
|
this._renderInWorker = this._canvasContext === null;
|
||||||
|
this.rendererHandler = rendererHandler;
|
||||||
|
InternalRenderTask.continueFnMap.set(this.taskID, this._continueBound);
|
||||||
this._dependencyTracker = params.dependencyTracker;
|
this._dependencyTracker = params.dependencyTracker;
|
||||||
this._operationsFilter = operationsFilter;
|
this._operationsFilter = operationsFilter;
|
||||||
}
|
}
|
||||||
@ -3253,16 +3257,30 @@ class InternalRenderTask {
|
|||||||
const { viewport, transform, background, dependencyTracker } = this.params;
|
const { viewport, transform, background, dependencyTracker } = this.params;
|
||||||
|
|
||||||
// When printing in Firefox, we get a specific context in mozPrintCallback
|
// When printing in Firefox, we get a specific context in mozPrintCallback
|
||||||
// which cannot be created from the canvas itself.
|
// which cannot be created from the canvas itself. In this case, we don't
|
||||||
const canvasContext =
|
// render in the worker and use the context directly.
|
||||||
this._canvasContext ||
|
if (this._renderInWorker) {
|
||||||
this._canvas.getContext("2d", {
|
const offscreen = this._canvas.transferControlToOffscreen();
|
||||||
alpha: false,
|
this.rendererHandler.send(
|
||||||
willReadFrequently: !this._enableHWA,
|
"init",
|
||||||
});
|
{
|
||||||
|
pageIndex: this._pageIndex,
|
||||||
|
canvas: offscreen,
|
||||||
|
map: this.annotationCanvasMap,
|
||||||
|
colors: this.pageColors,
|
||||||
|
taskID: this.taskID,
|
||||||
|
transform,
|
||||||
|
viewport,
|
||||||
|
transparency,
|
||||||
|
background,
|
||||||
|
optionalContentConfig,
|
||||||
|
dependencyTracker,
|
||||||
|
},
|
||||||
|
[offscreen]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
this.gfx = new CanvasGraphics(
|
this.gfx = new CanvasGraphics(
|
||||||
canvasContext,
|
this._canvasContext,
|
||||||
this.commonObjs,
|
this.commonObjs,
|
||||||
this.objs,
|
this.objs,
|
||||||
this.canvasFactory,
|
this.canvasFactory,
|
||||||
@ -3278,6 +3296,8 @@ class InternalRenderTask {
|
|||||||
transparency,
|
transparency,
|
||||||
background,
|
background,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.operatorListIdx = 0;
|
this.operatorListIdx = 0;
|
||||||
this.graphicsReady = true;
|
this.graphicsReady = true;
|
||||||
this.graphicsReadyCallback?.();
|
this.graphicsReadyCallback?.();
|
||||||
@ -3286,7 +3306,12 @@ class InternalRenderTask {
|
|||||||
cancel(error = null, extraDelay = 0) {
|
cancel(error = null, extraDelay = 0) {
|
||||||
this.running = false;
|
this.running = false;
|
||||||
this.cancelled = true;
|
this.cancelled = true;
|
||||||
this.gfx?.endDrawing();
|
if (this._renderInWorker) {
|
||||||
|
this.rendererHandler.send("end", { taskID: this.taskID });
|
||||||
|
} else {
|
||||||
|
this.gfx.endDrawing();
|
||||||
|
}
|
||||||
|
InternalRenderTask.continueFnMap.delete(this.taskID);
|
||||||
if (this.#rAF) {
|
if (this.#rAF) {
|
||||||
window.cancelAnimationFrame(this.#rAF);
|
window.cancelAnimationFrame(this.#rAF);
|
||||||
this.#rAF = null;
|
this.#rAF = null;
|
||||||
@ -3307,9 +3332,16 @@ class InternalRenderTask {
|
|||||||
this.graphicsReadyCallback ||= this._continueBound;
|
this.graphicsReadyCallback ||= this._continueBound;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this._renderInWorker) {
|
||||||
this.gfx.dependencyTracker?.growOperationsCount(
|
this.gfx.dependencyTracker?.growOperationsCount(
|
||||||
this.operatorList.fnArray.length
|
this.operatorList.fnArray.length
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
this.rendererHandler.send("growOperationsCount", {
|
||||||
|
taskID: this.taskID,
|
||||||
|
newOperatorListLength: this.operatorList.fnArray.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
this.stepper?.updateOperatorList(this.operatorList);
|
this.stepper?.updateOperatorList(this.operatorList);
|
||||||
|
|
||||||
if (this.running) {
|
if (this.running) {
|
||||||
@ -3345,6 +3377,18 @@ class InternalRenderTask {
|
|||||||
if (this.cancelled) {
|
if (this.cancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { operatorList, operatorListIdx, taskID } = this;
|
||||||
|
if (this._renderInWorker) {
|
||||||
|
this.operatorListIdx = await this.rendererHandler.sendWithPromise(
|
||||||
|
"execute",
|
||||||
|
{
|
||||||
|
operatorList,
|
||||||
|
operatorListIdx,
|
||||||
|
taskID,
|
||||||
|
operationsFilter: this._operationsFilter,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
this.operatorListIdx = this.gfx.executeOperatorList(
|
this.operatorListIdx = this.gfx.executeOperatorList(
|
||||||
this.operatorList,
|
this.operatorList,
|
||||||
this.operatorListIdx,
|
this.operatorListIdx,
|
||||||
@ -3352,12 +3396,16 @@ class InternalRenderTask {
|
|||||||
this.stepper,
|
this.stepper,
|
||||||
this._operationsFilter
|
this._operationsFilter
|
||||||
);
|
);
|
||||||
|
}
|
||||||
if (this.operatorListIdx === this.operatorList.argsArray.length) {
|
if (this.operatorListIdx === this.operatorList.argsArray.length) {
|
||||||
this.running = false;
|
this.running = false;
|
||||||
if (this.operatorList.lastChunk) {
|
if (this.operatorList.lastChunk) {
|
||||||
|
if (this._renderInWorker) {
|
||||||
|
this.rendererHandler.send("end", { taskID });
|
||||||
|
} else {
|
||||||
this.gfx.endDrawing();
|
this.gfx.endDrawing();
|
||||||
|
}
|
||||||
InternalRenderTask.#canvasInUse.delete(this._canvas);
|
InternalRenderTask.#canvasInUse.delete(this._canvas);
|
||||||
|
|
||||||
this.callback();
|
this.callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,4 +89,14 @@ class DOMCanvasFactory extends BaseCanvasFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { BaseCanvasFactory, DOMCanvasFactory };
|
class OffscreenCanvasFactory extends BaseCanvasFactory {
|
||||||
|
constructor({ enableHWA = false }) {
|
||||||
|
super({ enableHWA });
|
||||||
|
}
|
||||||
|
|
||||||
|
_createCanvas(width, height) {
|
||||||
|
return new OffscreenCanvas(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BaseCanvasFactory, DOMCanvasFactory, OffscreenCanvasFactory };
|
||||||
|
|||||||
@ -995,6 +995,115 @@ function renderRichText({ html, dir, className }, container) {
|
|||||||
container.append(fragment);
|
container.append(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INITIAL_DATA = Symbol("INITIAL_DATA");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A PDF document and page is built of many objects. E.g. there are objects for
|
||||||
|
* fonts, images, rendering code, etc. These objects may get processed inside of
|
||||||
|
* a worker. This class implements some basic methods to manage these objects.
|
||||||
|
*/
|
||||||
|
class PDFObjects {
|
||||||
|
#objs = Object.create(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures there is an object defined for `objId`.
|
||||||
|
*
|
||||||
|
* @param {string} objId
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
#ensureObj(objId) {
|
||||||
|
return (this.#objs[objId] ||= {
|
||||||
|
...Promise.withResolvers(),
|
||||||
|
data: INITIAL_DATA,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If called *without* callback, this returns the data of `objId` but the
|
||||||
|
* object needs to be resolved. If it isn't, this method throws.
|
||||||
|
*
|
||||||
|
* If called *with* a callback, the callback is called with the data of the
|
||||||
|
* object once the object is resolved. That means, if you call this method
|
||||||
|
* and the object is already resolved, the callback gets called right away.
|
||||||
|
*
|
||||||
|
* @param {string} objId
|
||||||
|
* @param {function} [callback]
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
get(objId, callback = null) {
|
||||||
|
// If there is a callback, then the get can be async and the object is
|
||||||
|
// not required to be resolved right now.
|
||||||
|
if (callback) {
|
||||||
|
const obj = this.#ensureObj(objId);
|
||||||
|
obj.promise.then(() => callback(obj.data));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// If there isn't a callback, the user expects to get the resolved data
|
||||||
|
// directly.
|
||||||
|
const obj = this.#objs[objId];
|
||||||
|
// If there isn't an object yet or the object isn't resolved, then the
|
||||||
|
// data isn't ready yet!
|
||||||
|
if (!obj || obj.data === INITIAL_DATA) {
|
||||||
|
throw new Error(`Requesting object that isn't resolved yet ${objId}.`);
|
||||||
|
}
|
||||||
|
return obj.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} objId
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
has(objId) {
|
||||||
|
const obj = this.#objs[objId];
|
||||||
|
return !!obj && obj.data !== INITIAL_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} objId
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
delete(objId) {
|
||||||
|
const obj = this.#objs[objId];
|
||||||
|
if (!obj || obj.data === INITIAL_DATA) {
|
||||||
|
// Only allow removing the object *after* it's been resolved.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete this.#objs[objId];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the object `objId` with optional `data`.
|
||||||
|
*
|
||||||
|
* @param {string} objId
|
||||||
|
* @param {any} [data]
|
||||||
|
*/
|
||||||
|
resolve(objId, data = null) {
|
||||||
|
const obj = this.#ensureObj(objId);
|
||||||
|
obj.data = data;
|
||||||
|
obj.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
for (const objId in this.#objs) {
|
||||||
|
const { data } = this.#objs[objId];
|
||||||
|
data?.bitmap?.close(); // Release any `ImageBitmap` data.
|
||||||
|
}
|
||||||
|
this.#objs = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
*[Symbol.iterator]() {
|
||||||
|
for (const objId in this.#objs) {
|
||||||
|
const { data } = this.#objs[objId];
|
||||||
|
|
||||||
|
if (data === INITIAL_DATA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
yield [objId, data];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
ColorScheme,
|
ColorScheme,
|
||||||
@ -1016,6 +1125,7 @@ export {
|
|||||||
OutputScale,
|
OutputScale,
|
||||||
PageViewport,
|
PageViewport,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
|
PDFObjects,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
renderRichText,
|
renderRichText,
|
||||||
|
|||||||
168
src/display/renderer_worker.js
Normal file
168
src/display/renderer_worker.js
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
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();
|
||||||
|
|
||||||
|
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({});
|
||||||
|
|
||||||
|
setupHandler(
|
||||||
|
workerHandler,
|
||||||
|
terminated,
|
||||||
|
this.#commonObjs,
|
||||||
|
this.#objsMap,
|
||||||
|
this.#fontLoader
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { RendererMessageHandler };
|
||||||
18
src/pdf.renderer.js
Normal file
18
src/pdf.renderer.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/* Copyright 2025 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RendererMessageHandler } from "./display/renderer_worker.js";
|
||||||
|
|
||||||
|
export { RendererMessageHandler };
|
||||||
108
src/shared/handle_objs.js
Normal file
108
src/shared/handle_objs.js
Normal 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 };
|
||||||
@ -90,6 +90,7 @@ class MessageHandler {
|
|||||||
comObj.addEventListener("message", this.#onMessage.bind(this), {
|
comObj.addEventListener("message", this.#onMessage.bind(this), {
|
||||||
signal: this.#messageAC.signal,
|
signal: this.#messageAC.signal,
|
||||||
});
|
});
|
||||||
|
comObj.start?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
#onMessage({ data }) {
|
#onMessage({ data }) {
|
||||||
|
|||||||
@ -50,6 +50,8 @@ class BasePDFPageView {
|
|||||||
|
|
||||||
renderTask = null;
|
renderTask = null;
|
||||||
|
|
||||||
|
renderTaskID = null;
|
||||||
|
|
||||||
resume = null;
|
resume = null;
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
@ -112,7 +114,8 @@ class BasePDFPageView {
|
|||||||
// In HCM, a final filter is applied on the canvas which means that
|
// In HCM, a final filter is applied on the canvas which means that
|
||||||
// before it's applied we've normal colors. Consequently, to avoid to
|
// before it's applied we've normal colors. Consequently, to avoid to
|
||||||
// have a final flash we just display it once all the drawing is done.
|
// have a final flash we just display it once all the drawing is done.
|
||||||
const updateOnFirstShow = !prevCanvas && !hasHCM && !hideUntilComplete;
|
// const updateOnFirstShow = !prevCanvas && !hasHCM && !hideUntilComplete;
|
||||||
|
const updateOnFirstShow = false;
|
||||||
|
|
||||||
let canvas = (this.canvas = document.createElement("canvas"));
|
let canvas = (this.canvas = document.createElement("canvas"));
|
||||||
|
|
||||||
@ -162,7 +165,7 @@ class BasePDFPageView {
|
|||||||
|
|
||||||
if (prevCanvas) {
|
if (prevCanvas) {
|
||||||
prevCanvas.replaceWith(canvas);
|
prevCanvas.replaceWith(canvas);
|
||||||
prevCanvas.width = prevCanvas.height = 0;
|
this.pdfPage.resetCanvas(this.renderTaskID);
|
||||||
} else {
|
} else {
|
||||||
onShow(canvas);
|
onShow(canvas);
|
||||||
}
|
}
|
||||||
@ -190,7 +193,7 @@ class BasePDFPageView {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
canvas.width = canvas.height = 0;
|
this.pdfPage.resetCanvas(this.renderTaskID);
|
||||||
this.canvas = null;
|
this.canvas = null;
|
||||||
this.#resetTempCanvas();
|
this.#resetTempCanvas();
|
||||||
}
|
}
|
||||||
@ -212,6 +215,8 @@ class BasePDFPageView {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.renderTaskID = renderTask.taskID;
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
try {
|
try {
|
||||||
await renderTask.promise;
|
await renderTask.promise;
|
||||||
|
|||||||
@ -303,7 +303,7 @@ class PDFThumbnailView {
|
|||||||
await renderTask.promise;
|
await renderTask.promise;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof RenderingCancelledException) {
|
if (e instanceof RenderingCancelledException) {
|
||||||
zeroCanvas(canvas);
|
pdfPage.resetCanvas(renderTask.taskID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
error = e;
|
error = e;
|
||||||
@ -318,7 +318,7 @@ class PDFThumbnailView {
|
|||||||
this.renderingState = RenderingStates.FINISHED;
|
this.renderingState = RenderingStates.FINISHED;
|
||||||
|
|
||||||
this.#convertCanvasToImage(canvas);
|
this.#convertCanvasToImage(canvas);
|
||||||
zeroCanvas(canvas);
|
pdfPage.resetCanvas(renderTask.taskID);
|
||||||
|
|
||||||
this.eventBus.dispatch("thumbnailrendered", {
|
this.eventBus.dispatch("thumbnailrendered", {
|
||||||
source: this,
|
source: this,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user