From a93a7d1acc5cef0b0917d2f320f0fd713a7ed2d2 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Tue, 12 Nov 2024 16:56:54 +0100 Subject: [PATCH] [api-minor] Add a `getDocument` option to disable `ImageDecoder` usage This allows end-users to forcibly disable `ImageDecoder`, even if the browser appears to support it (similar to the pre-existing option for `OffscreenCanvas`). --- src/core/evaluator.js | 12 ++++-------- src/core/image_resizer.js | 31 ++++++++++++++----------------- src/core/jpeg_stream.js | 14 ++++++++++---- src/core/pdf_manager.js | 6 ++++-- src/display/api.js | 9 +++++++++ src/shared/util.js | 8 ++++++++ 6 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index d6d70bbdbc2d7..0f8d3aedb3e11 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -72,6 +72,7 @@ import { getGlyphsUnicode } from "./glyphlist.js"; import { getMetrics } from "./metrics.js"; import { getUnicodeForGlyph } from "./unicode.js"; import { ImageResizer } from "./image_resizer.js"; +import { JpegStream } from "./jpeg_stream.js"; import { MurmurHash3_64 } from "../shared/murmurhash3.js"; import { OperatorList } from "./operator_list.js"; import { PDFImage } from "./image.js"; @@ -233,14 +234,9 @@ class PartialEvaluator { this._regionalImageCache = new RegionalImageCache(); this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this); - if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { - ImageResizer.setMaxArea(this.options.canvasMaxAreaInBytes); - } else { - ImageResizer.setOptions({ - isChrome: this.options.isChrome, - maxArea: this.options.canvasMaxAreaInBytes, - }); - } + + ImageResizer.setOptions(this.options); + JpegStream.setOptions(this.options); } /** diff --git a/src/core/image_resizer.js b/src/core/image_resizer.js index a21e175ebe6f3..ec80590ba827b 100644 --- a/src/core/image_resizer.js +++ b/src/core/image_resizer.js @@ -34,7 +34,7 @@ const MAX_ERROR = 128; class ImageResizer { static #goodSquareLength = MIN_IMAGE_DIM; - static #isChrome = false; + static #isImageDecoderSupported = FeatureTest.isImageDecoderSupported; constructor(imgData, isMask) { this._imgData = imgData; @@ -42,15 +42,12 @@ class ImageResizer { } static get canUseImageDecoder() { - // TODO: remove the isChrome, once Chrome doesn't crash anymore with - // issue6741.pdf. - // https://issues.chromium.org/issues/374807001. return shadow( this, "canUseImageDecoder", - this.#isChrome || typeof ImageDecoder === "undefined" - ? Promise.resolve(false) - : ImageDecoder.isTypeSupported("image/bmp") + this.#isImageDecoderSupported + ? ImageDecoder.isTypeSupported("image/bmp") + : Promise.resolve(false) ); } @@ -121,19 +118,19 @@ class ImageResizer { } } - static setMaxArea(area) { + static setOptions({ + isChrome = false, + isImageDecoderSupported = false, + maxArea = -1, + }) { if (!this._hasMaxArea) { // Divide by 4 to have the value in pixels. - this.MAX_AREA = area >> 2; + this.MAX_AREA = maxArea >> 2; } - } - - static setOptions(opts) { - if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { - throw new Error("Not implemented: setOptions"); - } - this.setMaxArea(opts.maxArea ?? -1); - this.#isChrome = opts.isChrome ?? false; + // TODO: remove the isChrome, once Chrome doesn't crash anymore with + // issue6741.pdf. + // https://issues.chromium.org/issues/374807001. + this.#isImageDecoderSupported = isImageDecoderSupported && !isChrome; } static _areGoodDims(width, height) { diff --git a/src/core/jpeg_stream.js b/src/core/jpeg_stream.js index f25ae4e40b2b7..70789d543307a 100644 --- a/src/core/jpeg_stream.js +++ b/src/core/jpeg_stream.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { shadow, warn } from "../shared/util.js"; +import { FeatureTest, shadow, warn } from "../shared/util.js"; import { DecodeStream } from "./decode_stream.js"; import { Dict } from "./primitives.js"; import { JpegImage } from "./jpg.js"; @@ -23,6 +23,8 @@ import { JpegImage } from "./jpg.js"; * like all the other DecodeStreams. */ class JpegStream extends DecodeStream { + static #isImageDecoderSupported = FeatureTest.isImageDecoderSupported; + constructor(stream, maybeLength, params) { super(maybeLength); @@ -36,12 +38,16 @@ class JpegStream extends DecodeStream { return shadow( this, "canUseImageDecoder", - typeof ImageDecoder === "undefined" - ? Promise.resolve(false) - : ImageDecoder.isTypeSupported("image/jpeg") + this.#isImageDecoderSupported + ? ImageDecoder.isTypeSupported("image/jpeg") + : Promise.resolve(false) ); } + static setOptions({ isImageDecoderSupported = false }) { + this.#isImageDecoderSupported = isImageDecoderSupported; + } + get bytes() { // If `this.maybeLength` is null, we'll get the entire stream. return shadow(this, "bytes", this.stream.getBytes(this.maybeLength)); diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js index 35d34cb537095..94285f47ec48d 100644 --- a/src/core/pdf_manager.js +++ b/src/core/pdf_manager.js @@ -48,10 +48,12 @@ class BasePdfManager { this._password = args.password; this.enableXfa = args.enableXfa; - // Check `OffscreenCanvas` support once, rather than repeatedly throughout - // the worker-thread code. + // Check `OffscreenCanvas` and `ImageDecoder` support once, + // rather than repeatedly throughout the worker-thread code. args.evaluatorOptions.isOffscreenCanvasSupported &&= FeatureTest.isOffscreenCanvasSupported; + args.evaluatorOptions.isImageDecoderSupported &&= + FeatureTest.isImageDecoderSupported; this.evaluatorOptions = Object.freeze(args.evaluatorOptions); } diff --git a/src/display/api.js b/src/display/api.js index 5749d514da3f7..17b5525a78e04 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -177,6 +177,10 @@ const DefaultStandardFontDataFactory = * `OffscreenCanvas` in the worker. Primarily used to improve performance of * image conversion/rendering. * The default value is `true` in web environments and `false` in Node.js. + * @property {boolean} [isImageDecoderSupported] - Determines if we can use + * `ImageDecoder` in the worker. Primarily used to improve performance of + * image conversion/rendering. + * The default value is `true` in web environments and `false` in Node.js. * @property {boolean} [isChrome] - Determines if we can use bmp ImageDecoder. * NOTE: Temporary option until [https://issues.chromium.org/issues/374807001] * is fixed. @@ -284,6 +288,10 @@ function getDocument(src = {}) { typeof src.isOffscreenCanvasSupported === "boolean" ? src.isOffscreenCanvasSupported : !isNodeJS; + const isImageDecoderSupported = + typeof src.isImageDecoderSupported === "boolean" + ? src.isImageDecoderSupported + : !isNodeJS; const isChrome = typeof src.isChrome === "boolean" ? src.isChrome @@ -395,6 +403,7 @@ function getDocument(src = {}) { ignoreErrors, isEvalSupported, isOffscreenCanvasSupported, + isImageDecoderSupported, isChrome, canvasMaxAreaInBytes, fontExtraProperties, diff --git a/src/shared/util.js b/src/shared/util.js index 341f26c0bfd75..60ce504889c5b 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -623,6 +623,14 @@ class FeatureTest { ); } + static get isImageDecoderSupported() { + return shadow( + this, + "isImageDecoderSupported", + typeof ImageDecoder !== "undefined" + ); + } + static get platform() { if ( (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||