1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 14:48:08 +02:00

Merge pull request #19329 from calixteman/bug1935076_1

[api-major] Add openjpeg.wasm to pdf.js (bug 1935076)
This commit is contained in:
calixteman 2025-01-16 22:32:46 +01:00 committed by GitHub
commit 7a57af12e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 274 additions and 19 deletions

File diff suppressed because one or more lines are too long

BIN
external/openjpeg/openjpeg.wasm vendored Normal file

Binary file not shown.

View file

@ -192,6 +192,7 @@ function createWebpackAlias(defines) {
const libraryAlias = {
"display-cmap_reader_factory": "src/display/stubs.js",
"display-standard_fontdata_factory": "src/display/stubs.js",
"display-wasm_factory": "src/display/stubs.js",
"display-fetch_stream": "src/display/stubs.js",
"display-network": "src/display/stubs.js",
"display-node_stream": "src/display/stubs.js",
@ -224,6 +225,7 @@ function createWebpackAlias(defines) {
"src/display/cmap_reader_factory.js";
libraryAlias["display-standard_fontdata_factory"] =
"src/display/standard_fontdata_factory.js";
libraryAlias["display-wasm_factory"] = "src/display/wasm_factory.js";
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
libraryAlias["display-network"] = "src/display/network.js";
@ -240,6 +242,7 @@ function createWebpackAlias(defines) {
"src/display/cmap_reader_factory.js";
libraryAlias["display-standard_fontdata_factory"] =
"src/display/standard_fontdata_factory.js";
libraryAlias["display-wasm_factory"] = "src/display/wasm_factory.js";
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
libraryAlias["display-network"] = "src/display/network.js";
libraryAlias["display-node_stream"] = "src/display/node_stream.js";
@ -646,6 +649,13 @@ function createStandardFontBundle() {
);
}
function createWasmBundle() {
return gulp.src(["external/openjpeg/openjpeg.wasm"], {
base: "external/openjpeg",
encoding: false,
});
}
function checkFile(filePath) {
try {
const stat = fs.lstatSync(filePath);
@ -1068,6 +1078,7 @@ function buildGeneric(defines, dir) {
.pipe(gulp.dest(dir + "web")),
createCMapBundle().pipe(gulp.dest(dir + "web/cmaps")),
createStandardFontBundle().pipe(gulp.dest(dir + "web/standard_fonts")),
createWasmBundle().pipe(gulp.dest(dir + "web/wasm")),
preprocessHTML("web/viewer.html", defines).pipe(gulp.dest(dir + "web")),
preprocessCSS("web/viewer.css", defines)
@ -1398,6 +1409,7 @@ gulp.task(
createStandardFontBundle().pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/standard_fonts")
),
createWasmBundle().pipe(gulp.dest(MOZCENTRAL_CONTENT_DIR + "web/wasm")),
preprocessHTML("web/viewer.html", defines).pipe(
gulp.dest(MOZCENTRAL_CONTENT_DIR + "web")
@ -1500,6 +1512,9 @@ gulp.task(
createStandardFontBundle().pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "web/standard_fonts")
),
createWasmBundle().pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "web/wasm")
),
preprocessHTML("web/viewer.html", defines).pipe(
gulp.dest(CHROME_BUILD_CONTENT_DIR + "web")
@ -1586,6 +1601,7 @@ function buildLibHelper(bundleDefines, inputStream, outputDir) {
"pdfjs-lib": "../pdf.js",
"display-cmap_reader_factory": "./cmap_reader_factory.js",
"display-standard_fontdata_factory": "./standard_fontdata_factory.js",
"display-wasm_factory": "./wasm_factory.js",
"display-fetch_stream": "./fetch_stream.js",
"display-network": "./network.js",
"display-node_stream": "./node_stream.js",

View file

@ -64,6 +64,10 @@ class BaseStream {
return false;
}
get isAsyncDecoder() {
return false;
}
get canAsyncDecodeImageFromBuffer() {
return false;
}

View file

@ -99,8 +99,11 @@ class DecodeStream extends BaseStream {
return this.buffer.subarray(pos, end);
}
async getImageData(length, decoderOptions = null) {
async getImageData(length, decoderOptions) {
if (!this.canAsyncDecodeImageFromBuffer) {
if (this.isAsyncDecoder) {
return this.decodeImage(null, decoderOptions);
}
return this.getBytes(length, decoderOptions);
}
const data = await this.stream.asyncGetBytes();

View file

@ -72,6 +72,7 @@ import { getMetrics } from "./metrics.js";
import { getUnicodeForGlyph } from "./unicode.js";
import { ImageResizer } from "./image_resizer.js";
import { JpegStream } from "./jpeg_stream.js";
import { JpxImage } from "./jpx.js";
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
import { OperatorList } from "./operator_list.js";
import { PDFImage } from "./image.js";
@ -89,6 +90,7 @@ const DefaultPartialEvaluatorOptions = Object.freeze({
useSystemFonts: true,
cMapUrl: null,
standardFontDataUrl: null,
wasmUrl: null,
});
const PatternType = {
@ -236,6 +238,10 @@ class PartialEvaluator {
ImageResizer.setOptions(this.options);
JpegStream.setOptions(this.options);
JpxImage.setOptions({
wasmUrl: this.options.wasmUrl,
handler,
});
}
/**

View file

@ -24,20 +24,97 @@ class JpxError extends BaseException {
}
class JpxImage {
static #module = null;
static #buffer = null;
static decode(data, decoderOptions) {
decoderOptions ||= {};
this.#module ||= OpenJPEG({ warn });
const imageData = this.#module.decode(data, decoderOptions);
if (typeof imageData === "string") {
throw new JpxError(imageData);
static #handler = null;
static #instantiationFailed = false;
static #modulePromise = null;
static #wasmUrl = null;
static setOptions({ handler, wasmUrl }) {
if (!this.#buffer) {
this.#wasmUrl = wasmUrl || null;
if (wasmUrl === null) {
this.#handler = handler;
}
}
}
static async #instantiateWasm(imports, successCallback) {
try {
if (!this.#buffer) {
if (this.#wasmUrl !== null) {
const response = await fetch(`${this.#wasmUrl}openjpeg.wasm`);
this.#buffer = await response.arrayBuffer();
} else {
this.#buffer = await this.#handler.sendWithPromise("FetchWasm", {
filename: "openjpeg.wasm",
});
}
}
const results = await WebAssembly.instantiate(this.#buffer, imports);
return successCallback(results.instance);
} catch (e) {
this.#instantiationFailed = true;
warn(`Cannot load openjpeg.wasm: "${e}".`);
return false;
} finally {
this.#handler = null;
this.#wasmUrl = null;
}
}
static async decode(
bytes,
{ numComponents = 4, isIndexedColormap = false, smaskInData = false } = {}
) {
if (this.#instantiationFailed) {
throw new JpxError("OpenJPEG failed to instantiate.");
}
this.#modulePromise ||= OpenJPEG({
warn,
instantiateWasm: this.#instantiateWasm.bind(this),
});
const module = await this.#modulePromise;
let ptr;
try {
const size = bytes.length;
ptr = module._malloc(size);
module.HEAPU8.set(bytes, ptr);
const ret = module._jp2_decode(
ptr,
size,
numComponents > 0 ? numComponents : 0,
!!isIndexedColormap,
!!smaskInData
);
if (ret) {
const { errorMessages } = module;
if (errorMessages) {
delete module.errorMessages;
throw new JpxError(errorMessages);
}
throw new JpxError("Unknown error");
}
const { imageData } = module;
module.imageData = null;
return imageData;
} finally {
if (ptr) {
module._free(ptr);
}
}
return imageData;
}
static cleanup() {
this.#module = null;
this.#modulePromise = null;
}
static parseImageProperties(stream) {

View file

@ -13,9 +13,9 @@
* limitations under the License.
*/
import { shadow, unreachable } from "../shared/util.js";
import { DecodeStream } from "./decode_stream.js";
import { JpxImage } from "./jpx.js";
import { shadow } from "../shared/util.js";
/**
* For JPEG 2000's we use a library to decode these images and
@ -42,15 +42,19 @@ class JpxStream extends DecodeStream {
}
readBlock(decoderOptions) {
this.decodeImage(null, decoderOptions);
unreachable("JpxStream.readBlock");
}
decodeImage(bytes, decoderOptions) {
get isAsyncDecoder() {
return true;
}
async decodeImage(bytes, decoderOptions) {
if (this.eof) {
return this.buffer;
}
bytes ||= this.bytes;
this.buffer = JpxImage.decode(bytes, decoderOptions);
this.buffer = await JpxImage.decode(bytes, decoderOptions);
this.bufferLength = this.buffer.length;
this.eof = true;

View file

@ -52,12 +52,14 @@ import {
NodeCMapReaderFactory,
NodeFilterFactory,
NodeStandardFontDataFactory,
NodeWasmFactory,
} from "display-node_utils";
import { CanvasGraphics } from "./canvas.js";
import { DOMCanvasFactory } from "./canvas_factory.js";
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 { GlobalWorkerOptions } from "./worker_options.js";
import { Metadata } from "./metadata.js";
import { OptionalContentConfig } from "./optional_content_config.js";
@ -88,6 +90,10 @@ const DefaultStandardFontDataFactory =
typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
? NodeStandardFontDataFactory
: DOMStandardFontDataFactory;
const DefaultWasmFactory =
typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
? NodeWasmFactory
: DOMWasmFactory;
/**
* @typedef { Int8Array | Uint8Array | Uint8ClampedArray |
@ -153,6 +159,12 @@ const DefaultStandardFontDataFactory =
* when reading the standard font files. Providing a custom factory is useful
* for environments without Fetch API or `XMLHttpRequest` support, such as
* Node.js. The default value is {DOMStandardFontDataFactory}.
* @property {string} [wasmUrl] - The URL where the wasm files are located.
* Include the trailing slash.
* @property {Object} [WasmFactory] - The factory that will be used
* when reading the wasm files. Providing a custom factory is useful
* for environments without Fetch API or `XMLHttpRequest` support, such as
* Node.js. The default value is {DOMWasmFactory}.
* @property {boolean} [useWorkerFetch] - Enable using the Fetch API in the
* worker-thread when reading CMap and standard font files. When `true`,
* the `CMapReaderFactory` and `StandardFontDataFactory` options are ignored.
@ -280,6 +292,8 @@ function getDocument(src = {}) {
: null;
const StandardFontDataFactory =
src.StandardFontDataFactory || DefaultStandardFontDataFactory;
const wasmUrl = typeof src.wasmUrl === "string" ? src.wasmUrl : null;
const WasmFactory = src.WasmFactory || DefaultWasmFactory;
const ignoreErrors = src.stopAtErrors !== true;
const maxImageSize =
Number.isInteger(src.maxImageSize) && src.maxImageSize > -1
@ -328,10 +342,13 @@ function getDocument(src = {}) {
: (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
(CMapReaderFactory === DOMCMapReaderFactory &&
StandardFontDataFactory === DOMStandardFontDataFactory &&
WasmFactory === DOMWasmFactory &&
cMapUrl &&
standardFontDataUrl &&
wasmUrl &&
isValidFetchUrl(cMapUrl, document.baseURI) &&
isValidFetchUrl(standardFontDataUrl, document.baseURI));
isValidFetchUrl(standardFontDataUrl, document.baseURI) &&
isValidFetchUrl(wasmUrl, document.baseURI));
// Parameters only intended for development/testing purposes.
const styleElement =
@ -357,6 +374,11 @@ function getDocument(src = {}) {
useWorkerFetch
? null
: new StandardFontDataFactory({ baseUrl: standardFontDataUrl }),
wasmFactory:
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
useWorkerFetch
? null
: new WasmFactory({ baseUrl: wasmUrl }),
};
if (!worker) {
@ -397,6 +419,7 @@ function getDocument(src = {}) {
useSystemFonts,
cMapUrl: useWorkerFetch ? cMapUrl : null,
standardFontDataUrl: useWorkerFetch ? standardFontDataUrl : null,
wasmUrl: useWorkerFetch ? wasmUrl : null,
},
};
const transportParams = {
@ -2423,6 +2446,7 @@ class WorkerTransport {
this.filterFactory = factory.filterFactory;
this.cMapReaderFactory = factory.cMapReaderFactory;
this.standardFontDataFactory = factory.standardFontDataFactory;
this.wasmFactory = factory.wasmFactory;
this.destroyed = false;
this.destroyCapability = null;
@ -2918,6 +2942,21 @@ class WorkerTransport {
}
return this.standardFontDataFactory.fetch(data);
});
messageHandler.on("FetchWasm", async data => {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
throw new Error("Not implemented: FetchWasm");
}
if (this.destroyed) {
throw new Error("Worker was destroyed.");
}
if (!this.wasmFactory) {
throw new Error(
"WasmFactory not initialized, see the `useWorkerFetch` parameter."
);
}
return this.wasmFactory.fetch(data);
});
}
getData() {
@ -3501,6 +3540,7 @@ export {
DefaultCMapReaderFactory,
DefaultFilterFactory,
DefaultStandardFontDataFactory,
DefaultWasmFactory,
getDocument,
LoopbackPort,
PDFDataRangeTransport,

View file

@ -19,6 +19,7 @@ import { BaseCanvasFactory } from "./canvas_factory.js";
import { BaseCMapReaderFactory } from "./cmap_reader_factory.js";
import { BaseFilterFactory } from "./filter_factory.js";
import { BaseStandardFontDataFactory } from "./standard_fontdata_factory.js";
import { BaseWasmFactory } from "./wasm_factory.js";
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
throw new Error(
@ -108,10 +109,20 @@ class NodeStandardFontDataFactory extends BaseStandardFontDataFactory {
}
}
class NodeWasmFactory extends BaseWasmFactory {
/**
* @ignore
*/
async _fetch(url) {
return fetchData(url);
}
}
export {
fetchData,
NodeCanvasFactory,
NodeCMapReaderFactory,
NodeFilterFactory,
NodeStandardFontDataFactory,
NodeWasmFactory,
};

View file

@ -14,10 +14,12 @@
*/
const DOMCMapReaderFactory = null;
const DOMWasmFactory = null;
const DOMStandardFontDataFactory = null;
const NodeCanvasFactory = null;
const NodeCMapReaderFactory = null;
const NodeFilterFactory = null;
const NodeWasmFactory = null;
const NodeStandardFontDataFactory = null;
const PDFFetchStream = null;
const PDFNetworkStream = null;
@ -26,10 +28,12 @@ const PDFNodeStream = null;
export {
DOMCMapReaderFactory,
DOMStandardFontDataFactory,
DOMWasmFactory,
NodeCanvasFactory,
NodeCMapReaderFactory,
NodeFilterFactory,
NodeStandardFontDataFactory,
NodeWasmFactory,
PDFFetchStream,
PDFNetworkStream,
PDFNodeStream,

View file

@ -0,0 +1,63 @@
/* Copyright 2015 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 { fetchData } from "./display_utils.js";
import { unreachable } from "../shared/util.js";
class BaseWasmFactory {
constructor({ baseUrl = null }) {
if (
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
this.constructor === BaseWasmFactory
) {
unreachable("Cannot initialize BaseWasmFactory.");
}
this.baseUrl = baseUrl;
}
async fetch({ filename }) {
if (!this.baseUrl) {
throw new Error("Ensure that the `wasmUrl` API parameter is provided.");
}
if (!filename) {
throw new Error("Wasm filename must be specified.");
}
const url = `${this.baseUrl}${filename}`;
return this._fetch(url).catch(reason => {
throw new Error(`Unable to load wasm data at: ${url}`);
});
}
/**
* @ignore
* @returns {Promise<Uint8Array>}
*/
async _fetch(url) {
unreachable("Abstract method `_fetch` called.");
}
}
class DOMWasmFactory extends BaseWasmFactory {
/**
* @ignore
*/
async _fetch(url) {
const data = await fetchData(url, /* type = */ "arraybuffer");
return new Uint8Array(data);
}
}
export { BaseWasmFactory, DOMWasmFactory };

View file

@ -31,6 +31,7 @@ const { GenericL10n, parseQueryString, SimpleLinkService } = pdfjsViewer;
const WAITING_TIME = 100; // ms
const CMAP_URL = "/build/generic/web/cmaps/";
const STANDARD_FONT_DATA_URL = "/build/generic/web/standard_fonts/";
const WASM_URL = "/build/generic/web/wasm/";
const IMAGE_RESOURCES_PATH = "/web/images/";
const VIEWER_CSS = "../build/components/pdf_viewer.css";
const VIEWER_LOCALE = "en-US";
@ -631,6 +632,7 @@ class Driver {
password: task.password,
cMapUrl: CMAP_URL,
standardFontDataUrl: STANDARD_FONT_DATA_URL,
wasmUrl: WASM_URL,
disableAutoFetch: !task.enableAutoFetch,
pdfBug: true,
useSystemFonts: task.useSystemFonts,

View file

@ -6371,6 +6371,14 @@
"rounds": 1,
"type": "eq"
},
{
"id": "issue19326_main_thread_fetch",
"file": "pdfs/issue19326.pdf",
"md5": "b4d937017daf439a6318501428e0c6ba",
"rounds": 1,
"type": "eq",
"useWorkerFetch": false
},
{
"id": "bug1140761",
"file": "pdfs/bug1140761.pdf",

View file

@ -28,6 +28,8 @@ const STANDARD_FONT_DATA_URL = isNodeJS
? "./external/standard_fonts/"
: "../../external/standard_fonts/";
const WASM_URL = isNodeJS ? "./external/openjpeg/" : "../../external/openjpeg/";
class DefaultFileReaderFactory {
static async fetch(params) {
if (isNodeJS) {
@ -44,6 +46,7 @@ function buildGetDocumentParams(filename, options) {
? TEST_PDFS_PATH + filename
: new URL(TEST_PDFS_PATH + filename, window.location).href;
params.standardFontDataUrl = STANDARD_FONT_DATA_URL;
params.wasmUrl = WASM_URL;
for (const option in options) {
params[option] = options[option];

View file

@ -22,6 +22,7 @@
"display-cmap_reader_factory": "../../src/display/cmap_reader_factory.js",
"display-standard_fontdata_factory": "../../src/display/standard_fontdata_factory.js",
"display-wasm_factory": "../../src/display/wasm_factory.js",
"display-fetch_stream": "../../src/display/fetch_stream.js",
"display-network": "../../src/display/network.js",
"display-node_stream": "../../src/display/stubs.js",

View file

@ -38,6 +38,7 @@ const MIME_TYPES = {
".log": "text/plain",
".bcmap": "application/octet-stream",
".ftl": "text/plain",
".wasm": "application/wasm",
};
const DEFAULT_MIME_TYPE = "application/octet-stream";

View file

@ -14,6 +14,7 @@
"display-standard_fontdata_factory": [
"./src/display/standard_fontdata_factory"
],
"display-wasm_factory": ["./src/display/wasm_factory"],
"display-fetch_stream": ["./src/display/fetch_stream"],
"display-network": ["./src/display/network"],
"display-node_stream": ["./src/display/node_stream"],

View file

@ -431,7 +431,17 @@ const defaultOptions = {
value: 1,
kind: OptionKind.API,
},
wasmUrl: {
/** @type {string} */
value:
// eslint-disable-next-line no-nested-ternary
typeof PDFJSDev === "undefined"
? "../external/openjpeg/"
: PDFJSDev.test("MOZCENTRAL")
? "resource://pdf.js/web/wasm/"
: "../web/wasm/",
kind: OptionKind.API,
},
workerPort: {
/** @type {Object} */
value: null,

View file

@ -61,6 +61,7 @@ See https://github.com/adobe-type-tools/cmap-resources
"display-cmap_reader_factory": "../src/display/cmap_reader_factory.js",
"display-standard_fontdata_factory": "../src/display/standard_fontdata_factory.js",
"display-wasm_factory": "../src/display/wasm_factory.js",
"display-fetch_stream": "../src/display/fetch_stream.js",
"display-network": "../src/display/network.js",
"display-node_stream": "../src/display/stubs.js",

View file

@ -64,6 +64,7 @@ See https://github.com/adobe-type-tools/cmap-resources
"display-cmap_reader_factory": "../src/display/cmap_reader_factory.js",
"display-standard_fontdata_factory": "../src/display/standard_fontdata_factory.js",
"display-wasm_factory": "../src/display/wasm_factory.js",
"display-fetch_stream": "../src/display/fetch_stream.js",
"display-network": "../src/display/network.js",
"display-node_stream": "../src/display/stubs.js",