From 7c9d0d5939bc073d68781850e4349128a6ec8746 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 26 Jul 2020 12:23:28 +0200 Subject: [PATCH 1/3] Improve how Type3-fonts with dependencies are handled While the `CharProcs` streams of Type3-fonts *usually* don't rely on dependencies, such as e.g. images, it does happen in some cases. Currently any dependencies are simply appended to the parent operatorList, which in practice means *only* the operatorList of the *first* page where the Type3-font is being used. However, there's one thing that's slightly unfortunate with that approach: Since fonts are global to the PDF document, we really ought to ensure that any Type3 dependencies are appended to the operatorList of *all* pages where the Type3-font is being used. Otherwise there's a theoretical risk that, if one page has its rendering paused, another page may try to use a Type3-font whose dependencies are not yet fully resolved. In that case there would be errors, since Type3 operatorLists are executed synchronously. Hence this patch, which ensures that all relevant pages will have Type3 dependencies appended to the main operatorList. (Note here that the `OperatorList.addDependencies` method, via `OperatorList.addDependency`, ensures that a dependency is only added *once* to any operatorList.) Finally, these changes also remove the need for the "waiting for the main-thread"-hack that was added to `PartialEvaluator.buildPaintImageXObject` as part of fixing issue 10717. --- src/core/evaluator.js | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index a67ad4ef5..c27eb4c40 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -456,7 +456,7 @@ class PartialEvaluator { const transfers = imgData ? [imgData.data.buffer] : null; if (this.parsingType3Font) { - return this.handler.sendWithPromise( + return this.handler.send( "commonobj", [objId, "FontType3Res", imgData], transfers @@ -581,7 +581,7 @@ class PartialEvaluator { operatorList.addDependency(objId); args = [objId, w, h]; - const imgPromise = PDFImage.buildImage({ + PDFImage.buildImage({ xref: this.xref, res: resources, image, @@ -600,13 +600,6 @@ class PartialEvaluator { return this._sendImgData(objId, /* imgData = */ null, cacheGlobally); }); - if (this.parsingType3Font) { - // In the very rare case where a Type3 image resource is being parsed, - // wait for the image to be both decoded *and* sent to simplify the - // rendering code on the main-thread (see issue10717.pdf). - await imgPromise; - } - operatorList.addOp(OPS.paintImageXObject, args); if (cacheKey) { localImageCache.set(cacheKey, imageRef, { @@ -741,8 +734,12 @@ class PartialEvaluator { return translated; } return translated - .loadType3Data(this, resources, operatorList, task) + .loadType3Data(this, resources, task) .then(function () { + // Add the dependencies to the parent operatorList so they are + // resolved before Type3 operatorLists are executed synchronously. + operatorList.addDependencies(translated.type3Dependencies); + return translated; }) .catch(reason => { @@ -3354,6 +3351,7 @@ class TranslatedFont { this.dict = dict; this._extraProperties = extraProperties; this.type3Loaded = null; + this.type3Dependencies = font.isType3Font ? new Set() : null; this.sent = false; } @@ -3386,7 +3384,7 @@ class TranslatedFont { PartialEvaluator.buildFontPaths(this.font, glyphs, handler); } - loadType3Data(evaluator, resources, parentOperatorList, task) { + loadType3Data(evaluator, resources, task) { if (!this.font.isType3Font) { throw new Error("Must be a Type3 font."); } @@ -3397,24 +3395,19 @@ class TranslatedFont { // When parsing Type3 glyphs, always ignore them if there are errors. // Compared to the parsing of e.g. an entire page, it doesn't really // make sense to only be able to render a Type3 glyph partially. - // - // Also, ensure that any Type3 image resources (which should be very rare - // in practice) are completely decoded on the worker-thread, to simplify - // the rendering code on the main-thread (see issue10717.pdf). var type3Options = Object.create(evaluator.options); type3Options.ignoreErrors = false; var type3Evaluator = evaluator.clone(type3Options); type3Evaluator.parsingType3Font = true; - var translatedFont = this.font; + const translatedFont = this.font, + type3Dependencies = this.type3Dependencies; var loadCharProcsPromise = Promise.resolve(); var charProcs = this.dict.get("CharProcs"); var fontResources = this.dict.get("Resources") || resources; - var charProcKeys = charProcs.getKeys(); var charProcOperatorList = Object.create(null); - for (var i = 0, n = charProcKeys.length; i < n; ++i) { - const key = charProcKeys[i]; + for (const key of charProcs.getKeys()) { loadCharProcsPromise = loadCharProcsPromise.then(function () { var glyphStream = charProcs.get(key); var operatorList = new OperatorList(); @@ -3428,9 +3421,9 @@ class TranslatedFont { .then(function () { charProcOperatorList[key] = operatorList.getIR(); - // Add the dependencies to the parent operator list so they are - // resolved before sub operator list is executed synchronously. - parentOperatorList.addDependencies(operatorList.dependencies); + for (const dependency of operatorList.dependencies) { + type3Dependencies.add(dependency); + } }) .catch(function (reason) { warn(`Type3 font resource "${key}" is not available.`); From f3ff526019278e33c848c23c08fc1fbab5c192db Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 26 Jul 2020 18:05:38 +0200 Subject: [PATCH 2/3] Send/receive Type3 images the same way as other globally-cached images There's quite frankly no particular reason to special-case Type3-fonts with image resources, which are very rare anyway, now that we have a general mechanism for sending/receiving images globally. --- src/core/evaluator.js | 9 +-------- src/display/api.js | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index c27eb4c40..cee0da3d7 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -455,14 +455,7 @@ class PartialEvaluator { _sendImgData(objId, imgData, cacheGlobally = false) { const transfers = imgData ? [imgData.data.buffer] : null; - if (this.parsingType3Font) { - return this.handler.send( - "commonobj", - [objId, "FontType3Res", imgData], - transfers - ); - } - if (cacheGlobally) { + if (this.parsingType3Font || cacheGlobally) { return this.handler.send( "commonobj", [objId, "Image", imgData], diff --git a/src/display/api.js b/src/display/api.js index b1fb4ba61..140dafc49 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -2265,7 +2265,6 @@ class WorkerTransport { }); break; case "FontPath": - case "FontType3Res": case "Image": this.commonObjs.resolve(id, exportedData); break; From 835b5ffddd8a5c0cfa7f1fbf7a7a6c15a48bacaa Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 27 Jul 2020 13:00:24 +0200 Subject: [PATCH 3/3] Only check `isType3Font` the first time that `TranslatedFont.loadType3Data` is called If the `TranslatedFont.type3Loaded` property exists, then you already know that the font must be a Type3 one. --- src/core/evaluator.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index cee0da3d7..dd69ef65e 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -3378,13 +3378,12 @@ class TranslatedFont { } loadType3Data(evaluator, resources, task) { - if (!this.font.isType3Font) { - throw new Error("Must be a Type3 font."); - } - if (this.type3Loaded) { return this.type3Loaded; } + if (!this.font.isType3Font) { + throw new Error("Must be a Type3 font."); + } // When parsing Type3 glyphs, always ignore them if there are errors. // Compared to the parsing of e.g. an entire page, it doesn't really // make sense to only be able to render a Type3 glyph partially.