mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-05-31 15:21:00 +02:00
Add an abstract WasmImage class, that JBig2CCITTFaxImage and JpxImage inherit from
Given that these classes are, with the exception of their `decode` methods, virtually identical this helps reduce code duplication and simplifies maintenance. These changes reduce the size of the `gulp mozcentral` build-target by `1292` bytes, which obviously isn't a lot but still cannot hurt.
This commit is contained in:
parent
e8d3d19f67
commit
6ff0f8690f
@ -64,7 +64,7 @@ class CCITTFaxStream extends DecodeStream {
|
||||
: this.bytes;
|
||||
}
|
||||
|
||||
this.buffer = await JBig2CCITTFaxImage.decode(
|
||||
this.buffer = await JBig2CCITTFaxImage.instance.decode(
|
||||
bytes,
|
||||
this.dict.get("W", "Width"),
|
||||
this.dict.get("H", "Height"),
|
||||
|
||||
@ -16,18 +16,16 @@
|
||||
import { clearPatternCaches } from "./pattern.js";
|
||||
import { clearPrimitiveCaches } from "./primitives.js";
|
||||
import { clearUnicodeCaches } from "./unicode.js";
|
||||
import { JBig2CCITTFaxImage } from "./jbig2_ccittFax.js";
|
||||
import { JpxImage } from "./jpx.js";
|
||||
import { WasmImage } from "./wasm_image.js";
|
||||
|
||||
function clearGlobalCaches() {
|
||||
clearPatternCaches();
|
||||
clearPrimitiveCaches();
|
||||
clearUnicodeCaches();
|
||||
|
||||
// Remove the global `JBig2CCITTFaxImage`/`JpxImage` instances,
|
||||
// Remove the global `WasmImage` instances,
|
||||
// since they may hold references to the WebAssembly modules.
|
||||
JBig2CCITTFaxImage.cleanup();
|
||||
JpxImage.cleanup();
|
||||
WasmImage.cleanup();
|
||||
}
|
||||
|
||||
export { clearGlobalCaches };
|
||||
|
||||
@ -13,9 +13,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BaseException, warn } from "../shared/util.js";
|
||||
import { fetchBinaryData } from "./core_utils.js";
|
||||
import { BaseException, shadow } from "../shared/util.js";
|
||||
import JBig2 from "../../external/jbig2/jbig2.js";
|
||||
import { WasmImage } from "./wasm_image.js";
|
||||
|
||||
class Jbig2Error extends BaseException {
|
||||
constructor(msg) {
|
||||
@ -23,92 +23,17 @@ class Jbig2Error extends BaseException {
|
||||
}
|
||||
}
|
||||
|
||||
class JBig2CCITTFaxImage {
|
||||
static #buffer = null;
|
||||
class JBig2CCITTFaxImage extends WasmImage {
|
||||
_filename = "jbig2.wasm";
|
||||
|
||||
static #handler = null;
|
||||
_noWasmFilename = "jbig2_nowasm_fallback.js";
|
||||
|
||||
static #modulePromise = null;
|
||||
|
||||
static #useWasm = true;
|
||||
|
||||
static #useWorkerFetch = true;
|
||||
|
||||
static #wasmUrl = null;
|
||||
|
||||
static setOptions({ handler, useWasm, useWorkerFetch, wasmUrl }) {
|
||||
this.#useWasm = useWasm;
|
||||
this.#useWorkerFetch = useWorkerFetch;
|
||||
this.#wasmUrl = wasmUrl;
|
||||
|
||||
if (!useWorkerFetch) {
|
||||
this.#handler = handler;
|
||||
}
|
||||
static get instance() {
|
||||
return shadow(this, "instance", new JBig2CCITTFaxImage());
|
||||
}
|
||||
|
||||
static async #getJsModule(fallbackCallback) {
|
||||
const path =
|
||||
typeof PDFJSDev === "undefined"
|
||||
? `../${this.#wasmUrl}jbig2_nowasm_fallback.js`
|
||||
: `${this.#wasmUrl}jbig2_nowasm_fallback.js`;
|
||||
|
||||
let instance = null;
|
||||
try {
|
||||
const mod = await (typeof PDFJSDev === "undefined"
|
||||
? import(path) // eslint-disable-line no-unsanitized/method
|
||||
: __raw_import__(path));
|
||||
instance = mod.default();
|
||||
} catch (e) {
|
||||
warn(`JBig2CCITTFaxImage#getJsModule: ${e}`);
|
||||
}
|
||||
fallbackCallback(instance);
|
||||
}
|
||||
|
||||
static async #instantiateWasm(fallbackCallback, imports, successCallback) {
|
||||
const filename = "jbig2.wasm";
|
||||
try {
|
||||
if (!this.#buffer) {
|
||||
if (this.#useWorkerFetch) {
|
||||
this.#buffer = await fetchBinaryData(`${this.#wasmUrl}${filename}`);
|
||||
} else {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
throw new Error("Only worker-thread fetching supported.");
|
||||
}
|
||||
this.#buffer = await this.#handler.sendWithPromise(
|
||||
"FetchBinaryData",
|
||||
{ kind: "wasmUrl", filename }
|
||||
);
|
||||
}
|
||||
}
|
||||
const results = await WebAssembly.instantiate(this.#buffer, imports);
|
||||
return successCallback(results.instance);
|
||||
} catch (reason) {
|
||||
warn(`JBig2CCITTFaxImage#instantiateWasm: ${reason}`);
|
||||
|
||||
this.#getJsModule(fallbackCallback);
|
||||
return null;
|
||||
} finally {
|
||||
this.#handler = null;
|
||||
}
|
||||
}
|
||||
|
||||
static async decode(bytes, width, height, globals, CCITTOptions) {
|
||||
if (!this.#modulePromise) {
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
const promises = [promise];
|
||||
if (!this.#useWasm) {
|
||||
this.#getJsModule(resolve);
|
||||
} else {
|
||||
promises.push(
|
||||
JBig2({
|
||||
warn,
|
||||
instantiateWasm: this.#instantiateWasm.bind(this, resolve),
|
||||
})
|
||||
);
|
||||
}
|
||||
this.#modulePromise = Promise.race(promises);
|
||||
}
|
||||
const module = await this.#modulePromise;
|
||||
async decode(bytes, width, height, globals, CCITTOptions) {
|
||||
const module = await this._getModule(JBig2);
|
||||
|
||||
if (!module) {
|
||||
throw new Jbig2Error("JBig2 failed to initialize");
|
||||
@ -157,10 +82,6 @@ class JBig2CCITTFaxImage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static cleanup() {
|
||||
this.#modulePromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
export { JBig2CCITTFaxImage, Jbig2Error };
|
||||
|
||||
@ -64,7 +64,7 @@ class Jbig2Stream extends DecodeStream {
|
||||
globals = globalsStream.getBytes();
|
||||
}
|
||||
}
|
||||
this.buffer = await JBig2CCITTFaxImage.decode(
|
||||
this.buffer = await JBig2CCITTFaxImage.instance.decode(
|
||||
bytes,
|
||||
this.dict.get("Width"),
|
||||
this.dict.get("Height"),
|
||||
|
||||
@ -13,10 +13,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BaseException, warn } from "../shared/util.js";
|
||||
import { fetchBinaryData } from "./core_utils.js";
|
||||
import { BaseException, shadow } from "../shared/util.js";
|
||||
import OpenJPEG from "../../external/openjpeg/openjpeg.js";
|
||||
import { Stream } from "./stream.js";
|
||||
import { WasmImage } from "./wasm_image.js";
|
||||
|
||||
class JpxError extends BaseException {
|
||||
constructor(msg) {
|
||||
@ -24,76 +24,16 @@ class JpxError extends BaseException {
|
||||
}
|
||||
}
|
||||
|
||||
class JpxImage {
|
||||
static #buffer = null;
|
||||
class JpxImage extends WasmImage {
|
||||
_filename = "openjpeg.wasm";
|
||||
|
||||
static #handler = null;
|
||||
_noWasmFilename = "openjpeg_nowasm_fallback.js";
|
||||
|
||||
static #modulePromise = null;
|
||||
|
||||
static #useWasm = true;
|
||||
|
||||
static #useWorkerFetch = true;
|
||||
|
||||
static #wasmUrl = null;
|
||||
|
||||
static setOptions({ handler, useWasm, useWorkerFetch, wasmUrl }) {
|
||||
this.#useWasm = useWasm;
|
||||
this.#useWorkerFetch = useWorkerFetch;
|
||||
this.#wasmUrl = wasmUrl;
|
||||
|
||||
if (!useWorkerFetch) {
|
||||
this.#handler = handler;
|
||||
}
|
||||
static get instance() {
|
||||
return shadow(this, "instance", new JpxImage());
|
||||
}
|
||||
|
||||
static async #getJsModule(fallbackCallback) {
|
||||
const path =
|
||||
typeof PDFJSDev === "undefined"
|
||||
? `../${this.#wasmUrl}openjpeg_nowasm_fallback.js`
|
||||
: `${this.#wasmUrl}openjpeg_nowasm_fallback.js`;
|
||||
|
||||
let instance = null;
|
||||
try {
|
||||
const mod = await (typeof PDFJSDev === "undefined"
|
||||
? import(path) // eslint-disable-line no-unsanitized/method
|
||||
: __raw_import__(path));
|
||||
instance = mod.default();
|
||||
} catch (e) {
|
||||
warn(`JpxImage#getJsModule: ${e}`);
|
||||
}
|
||||
fallbackCallback(instance);
|
||||
}
|
||||
|
||||
static async #instantiateWasm(fallbackCallback, imports, successCallback) {
|
||||
const filename = "openjpeg.wasm";
|
||||
try {
|
||||
if (!this.#buffer) {
|
||||
if (this.#useWorkerFetch) {
|
||||
this.#buffer = await fetchBinaryData(`${this.#wasmUrl}${filename}`);
|
||||
} else {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
throw new Error("Only worker-thread fetching supported.");
|
||||
}
|
||||
this.#buffer = await this.#handler.sendWithPromise(
|
||||
"FetchBinaryData",
|
||||
{ kind: "wasmUrl", filename }
|
||||
);
|
||||
}
|
||||
}
|
||||
const results = await WebAssembly.instantiate(this.#buffer, imports);
|
||||
return successCallback(results.instance);
|
||||
} catch (reason) {
|
||||
warn(`JpxImage#instantiateWasm: ${reason}`);
|
||||
|
||||
this.#getJsModule(fallbackCallback);
|
||||
return null;
|
||||
} finally {
|
||||
this.#handler = null;
|
||||
}
|
||||
}
|
||||
|
||||
static async decode(
|
||||
async decode(
|
||||
bytes,
|
||||
{
|
||||
numComponents = 4,
|
||||
@ -102,22 +42,7 @@ class JpxImage {
|
||||
reducePower = 0,
|
||||
} = {}
|
||||
) {
|
||||
if (!this.#modulePromise) {
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
const promises = [promise];
|
||||
if (!this.#useWasm) {
|
||||
this.#getJsModule(resolve);
|
||||
} else {
|
||||
promises.push(
|
||||
OpenJPEG({
|
||||
warn,
|
||||
instantiateWasm: this.#instantiateWasm.bind(this, resolve),
|
||||
})
|
||||
);
|
||||
}
|
||||
this.#modulePromise = Promise.race(promises);
|
||||
}
|
||||
const module = await this.#modulePromise;
|
||||
const module = await this._getModule(OpenJPEG);
|
||||
|
||||
if (!module) {
|
||||
throw new JpxError("OpenJPEG failed to initialize");
|
||||
@ -155,10 +80,6 @@ class JpxImage {
|
||||
}
|
||||
}
|
||||
|
||||
static cleanup() {
|
||||
this.#modulePromise = null;
|
||||
}
|
||||
|
||||
static parseImageProperties(stream) {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("IMAGE_DECODERS")) {
|
||||
if (stream instanceof ArrayBuffer || ArrayBuffer.isView(stream)) {
|
||||
|
||||
@ -49,7 +49,7 @@ class JpxStream extends DecodeStream {
|
||||
return this.buffer;
|
||||
}
|
||||
bytes ||= this.bytes;
|
||||
this.buffer = await JpxImage.decode(bytes, decoderOptions);
|
||||
this.buffer = await JpxImage.instance.decode(bytes, decoderOptions);
|
||||
this.bufferLength = this.buffer.length;
|
||||
this.eof = true;
|
||||
|
||||
|
||||
@ -22,15 +22,14 @@ import {
|
||||
} from "../shared/util.js";
|
||||
import { ChunkedStreamManager } from "./chunked_stream.js";
|
||||
import { ImageResizer } from "./image_resizer.js";
|
||||
import { JBig2CCITTFaxImage } from "./jbig2_ccittFax.js";
|
||||
import { JpegStream } from "./jpeg_stream.js";
|
||||
import { JpxImage } from "./jpx.js";
|
||||
import { MissingDataException } from "./core_utils.js";
|
||||
import { OperatorList } from "./operator_list.js";
|
||||
import { Pattern } from "./pattern.js";
|
||||
import { PDFDocument } from "./document.js";
|
||||
import { PDFFunctionFactory } from "./function.js";
|
||||
import { Stream } from "./stream.js";
|
||||
import { WasmImage } from "./wasm_image.js";
|
||||
|
||||
function parseDocBaseUrl(url) {
|
||||
if (url) {
|
||||
@ -82,12 +81,11 @@ class BasePdfManager {
|
||||
OperatorList.setOptions(evaluatorOptions);
|
||||
|
||||
const options = { ...evaluatorOptions, handler };
|
||||
JpxImage.setOptions(options);
|
||||
IccColorSpace.setOptions(options);
|
||||
CmykICCBasedCS.setOptions(options);
|
||||
JBig2CCITTFaxImage.setOptions(options);
|
||||
PDFFunctionFactory.setOptions(options);
|
||||
Pattern.setOptions(options);
|
||||
WasmImage.setOptions(options);
|
||||
}
|
||||
|
||||
get docId() {
|
||||
|
||||
135
src/core/wasm_image.js
Normal file
135
src/core/wasm_image.js
Normal file
@ -0,0 +1,135 @@
|
||||
/* Copyright 2026 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 { unreachable, warn } from "../shared/util.js";
|
||||
import { fetchBinaryData } from "./core_utils.js";
|
||||
|
||||
class WasmImage {
|
||||
static #handler = null;
|
||||
|
||||
static #instances = new Set();
|
||||
|
||||
static #useWasm = true;
|
||||
|
||||
static #useWorkerFetch = true;
|
||||
|
||||
static #wasmUrl = null;
|
||||
|
||||
_buffer = null;
|
||||
|
||||
_filename = "";
|
||||
|
||||
_noWasmFilename = "";
|
||||
|
||||
_modulePromise = null;
|
||||
|
||||
static setOptions({ handler, useWasm, useWorkerFetch, wasmUrl }) {
|
||||
WasmImage.#useWasm = useWasm;
|
||||
WasmImage.#useWorkerFetch = useWorkerFetch;
|
||||
WasmImage.#wasmUrl = wasmUrl;
|
||||
|
||||
if (!useWorkerFetch) {
|
||||
WasmImage.#handler = handler;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line getter-return
|
||||
static get instance() {
|
||||
unreachable("Abstract getter `instance` accessed");
|
||||
}
|
||||
|
||||
static cleanup() {
|
||||
for (const instance of WasmImage.#instances) {
|
||||
instance._modulePromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||
this.constructor === WasmImage
|
||||
) {
|
||||
unreachable("Cannot initialize WasmImage.");
|
||||
}
|
||||
// Keep track of the instances for `cleanup` purposes.
|
||||
WasmImage.#instances.add(this);
|
||||
}
|
||||
|
||||
async #getJsModule(fallbackCallback) {
|
||||
let instance = null;
|
||||
try {
|
||||
const mod = await (typeof PDFJSDev === "undefined"
|
||||
? // eslint-disable-next-line no-unsanitized/method
|
||||
import(`../${WasmImage.#wasmUrl}${this._noWasmFilename}`)
|
||||
: __raw_import__(`${WasmImage.#wasmUrl}${this._noWasmFilename}`));
|
||||
instance = mod.default();
|
||||
} catch (ex) {
|
||||
warn(`#getJsModule: ${ex}`);
|
||||
}
|
||||
fallbackCallback(instance);
|
||||
}
|
||||
|
||||
async #instantiateWasm(fallbackCallback, imports, successCallback) {
|
||||
try {
|
||||
if (!this._buffer) {
|
||||
if (WasmImage.#useWorkerFetch) {
|
||||
this._buffer = await fetchBinaryData(
|
||||
`${WasmImage.#wasmUrl}${this._filename}`
|
||||
);
|
||||
} else {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
throw new Error("Only worker-thread fetching supported.");
|
||||
}
|
||||
this._buffer = await WasmImage.#handler.sendWithPromise(
|
||||
"FetchBinaryData",
|
||||
{ kind: "wasmUrl", filename: this._filename }
|
||||
);
|
||||
}
|
||||
}
|
||||
const results = await WebAssembly.instantiate(this._buffer, imports);
|
||||
return successCallback(results.instance);
|
||||
} catch (ex) {
|
||||
warn(`#instantiateWasm: ${ex}`);
|
||||
|
||||
this.#getJsModule(fallbackCallback);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_getModule(ImageDecoder) {
|
||||
if (!this._modulePromise) {
|
||||
const { promise, resolve } = Promise.withResolvers();
|
||||
const promises = [promise];
|
||||
if (!WasmImage.#useWasm) {
|
||||
this.#getJsModule(resolve);
|
||||
} else {
|
||||
promises.push(
|
||||
ImageDecoder({
|
||||
warn,
|
||||
instantiateWasm: this.#instantiateWasm.bind(this, resolve),
|
||||
})
|
||||
);
|
||||
}
|
||||
this._modulePromise = Promise.race(promises);
|
||||
}
|
||||
return this._modulePromise;
|
||||
}
|
||||
|
||||
async decode(bytes, _params) {
|
||||
unreachable("Abstract method `decode` called");
|
||||
}
|
||||
}
|
||||
|
||||
export { WasmImage };
|
||||
Loading…
x
Reference in New Issue
Block a user