From 80441346a3fe91b52a08331ff278da6a43cf0971 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 1 Feb 2018 16:43:10 +0100 Subject: [PATCH] Fallback to the built-in JPEG decoder if 'JpegStream', in `src/display/api.js`, fails to load the image This works by making `PartialEvaluator.buildPaintImageXObject` wait for the success/failure of `loadJpegStream` on the API side *before* parsing continues. Please note that in practice, it should be quite rare for the browser to fail loading/decoding of a JPEG image. In the general case, it should thus not be completely surprising if even `src/core/jpg.js` will fail to decode the image. --- src/core/evaluator.js | 52 +++++++++++++++++++++++++++++++------------ src/display/api.js | 10 ++++----- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 2ad04dbca..302ed26a5 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -349,7 +349,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }, buildPaintImageXObject({ resources, image, isInline = false, operatorList, - cacheKey, imageCache, }) { + cacheKey, imageCache, + forceDisableNativeImageDecoder = false, }) { var dict = image.dict; var w = dict.get('Width', 'W'); var h = dict.get('Height', 'H'); @@ -419,28 +420,47 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return Promise.resolve(); } - var nativeImageDecoderSupport = this.options.nativeImageDecoderSupport; + const nativeImageDecoderSupport = forceDisableNativeImageDecoder ? + NativeImageDecoding.NONE : this.options.nativeImageDecoderSupport; // If there is no imageMask, create the PDFImage and a lot // of image processing can be done here. var objId = 'img_' + this.idFactory.createObjId(); - operatorList.addDependency(objId); - args = [objId, w, h]; if (nativeImageDecoderSupport !== NativeImageDecoding.NONE && !softMask && !mask && image instanceof JpegStream && NativeImageDecoder.isSupported(image, this.xref, resources, this.pdfFunctionFactory)) { // These JPEGs don't need any more processing so we can just send it. - operatorList.addOp(OPS.paintJpegXObject, args); - this.handler.send('obj', [objId, this.pageIndex, 'JpegStream', - image.getIR(this.options.forceDataSchema)]); - if (cacheKey) { - imageCache[cacheKey] = { - fn: OPS.paintJpegXObject, - args, - }; - } - return Promise.resolve(); + return this.handler.sendWithPromise('obj', [ + objId, this.pageIndex, 'JpegStream', + image.getIR(this.options.forceDataSchema) + ]).then(function() { + // Only add the dependency once we know that the native JPEG decoding + // succeeded, to ensure that rendering will always complete. + operatorList.addDependency(objId); + args = [objId, w, h]; + + operatorList.addOp(OPS.paintJpegXObject, args); + if (cacheKey) { + imageCache[cacheKey] = { + fn: OPS.paintJpegXObject, + args, + }; + } + }, (reason) => { + warn('Native JPEG decoding failed -- trying to recover: ' + + (reason && reason.message)); + // Try to decode the JPEG image with the built-in decoder instead. + return this.buildPaintImageXObject({ + resources, + image, + isInline, + operatorList, + cacheKey, + imageCache, + forceDisableNativeImageDecoder: true, + }); + }); } // Creates native image decoder only if a JPEG image or mask is present. @@ -457,6 +477,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }); } + // Ensure that the dependency is added before the image is decoded. + operatorList.addDependency(objId); + args = [objId, w, h]; + PDFImage.buildImage({ handler: this.handler, xref: this.xref, diff --git a/src/display/api.js b/src/display/api.js index d50651c42..0819b9736 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -1817,22 +1817,22 @@ var WorkerTransport = (function WorkerTransportClosure() { switch (type) { case 'JpegStream': imageData = data[3]; - new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const img = new Image(); img.onload = function() { resolve(img); }; img.onerror = function() { reject(new Error('Error during JPEG image loading')); + // Note that when the browser image loading/decoding fails, + // we'll fallback to the built-in PDF.js JPEG decoder; see + // `PartialEvaluator.buildPaintImageXObject` in the + // `src/core/evaluator.js` file. }; img.src = imageData; }).then((img) => { pageProxy.objs.resolve(id, img); - }, (reason) => { - warn(reason); - pageProxy.objs.resolve(id, null); }); - break; case 'Image': imageData = data[3]; pageProxy.objs.resolve(id, imageData);