From bdfa96878dc8361ac558cc9b1f639faabd557f4b Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 26 Feb 2025 14:50:50 +0100 Subject: [PATCH] Invoke `TranslatedFont.prototype.loadType3Data` only *once* per font Currently we're first loading the font, and then for Type3 fonts we're invoking `loadType3Data` every time that the font is encountered. That seems completely unnecessary, and it's probably connected to the age of this code, since the `loadType3Data`-method will only run once anyway (note the caching). --- src/core/catalog.js | 4 +- src/core/evaluator.js | 103 +++++++++++++++++++----------------------- 2 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/core/catalog.js b/src/core/catalog.js index c5e481efd..84624b863 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -1177,9 +1177,7 @@ class Catalog { this.pageDictCache.clear(); this.nonBlendModesSet.clear(); - const translatedFonts = await Promise.all(this.fontCache); - - for (const { dict } of translatedFonts) { + for (const { dict } of await Promise.all(this.fontCache)) { delete dict.cacheKey; } this.fontCache.clear(); diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 66024a7d3..47e09c206 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -1046,27 +1046,19 @@ class PartialEvaluator { ) { const fontName = fontArgs?.[0] instanceof Name ? fontArgs[0].name : null; - let translated = await this.loadFont( + const translated = await this.loadFont( fontName, fontRef, resources, + task, fallbackFontDict, cssFontInfo ); if (translated.font.isType3Font) { - try { - await translated.loadType3Data(this, resources, task); - // Add the dependencies to the parent operatorList so they are - // resolved before Type3 operatorLists are executed synchronously. - operatorList.addDependencies(translated.type3Dependencies); - } catch (reason) { - translated = new TranslatedFont({ - loadedName: "g_font_error", - font: new ErrorFont(`Type3 font load error: ${reason}`), - dict: translated.font, - }); - } + // Add the dependencies to the parent operatorList so they are + // resolved before Type3 operatorLists are executed synchronously. + operatorList.addDependencies(translated.type3Dependencies); } state.font = translated.font; @@ -1228,6 +1220,7 @@ class PartialEvaluator { fontName, font, resources, + task, fallbackFontDict = null, cssFontInfo = null ) { @@ -1356,14 +1349,21 @@ class PartialEvaluator { font.loadedName = `${this.idFactory.getDocId()}_${fontID}`; this.translateFont(preEvaluatedFont) - .then(translatedFont => { - resolve( - new TranslatedFont({ - loadedName: font.loadedName, - font: translatedFont, - dict: font, - }) - ); + .then(async translatedFont => { + const translated = new TranslatedFont({ + loadedName: font.loadedName, + font: translatedFont, + dict: font, + }); + + if (translatedFont.isType3Font) { + try { + await translated.loadType3Data(this, resources, task); + } catch (reason) { + throw new Error(`Type3 font load error: ${reason}`); + } + } + resolve(translated); }) .catch(reason => { // TODO reject? @@ -1372,9 +1372,7 @@ class PartialEvaluator { resolve( new TranslatedFont({ loadedName: font.loadedName, - font: new ErrorFont( - reason instanceof Error ? reason.message : reason - ), + font: new ErrorFont(reason?.message), dict: font, }) ); @@ -2616,16 +2614,12 @@ class PartialEvaluator { } async function handleSetFont(fontName, fontRef) { - const translated = await self.loadFont(fontName, fontRef, resources); - - if (translated.font.isType3Font) { - try { - await translated.loadType3Data(self, resources, task); - } catch { - // Ignore Type3-parsing errors, since we only use `loadType3Data` - // here to ensure that we'll always obtain a useful /FontBBox. - } - } + const translated = await self.loadFont( + fontName, + fontRef, + resources, + task + ); textState.loadedName = translated.loadedName; textState.font = translated.font; @@ -4602,20 +4596,22 @@ class PartialEvaluator { } class TranslatedFont { + #sent = false; + + #type3Loaded = null; + constructor({ loadedName, font, dict }) { this.loadedName = loadedName; this.font = font; this.dict = dict; - this.type3Loaded = null; this.type3Dependencies = font.isType3Font ? new Set() : null; - this.sent = false; } send(handler) { - if (this.sent) { + if (this.#sent) { return; } - this.sent = true; + this.#sent = true; handler.send("commonobj", [ this.loadedName, @@ -4645,12 +4641,12 @@ class TranslatedFont { } loadType3Data(evaluator, resources, task) { - if (this.type3Loaded) { - return this.type3Loaded; - } - if (!this.font.isType3Font) { - throw new Error("Must be a Type3 font."); + if (this.#type3Loaded) { + return this.#type3Loaded; } + const { font, type3Dependencies } = this; + assert(font.isType3Font, "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. @@ -4662,14 +4658,12 @@ class TranslatedFont { } type3Evaluator.type3FontRefs = type3FontRefs; - const translatedFont = this.font, - type3Dependencies = this.type3Dependencies; let loadCharProcsPromise = Promise.resolve(); const charProcs = this.dict.get("CharProcs"); const fontResources = this.dict.get("Resources") || resources; const charProcOperatorList = Object.create(null); - const fontBBox = Util.normalizeRect(translatedFont.bbox || [0, 0, 0, 0]), + const fontBBox = Util.normalizeRect(font.bbox || [0, 0, 0, 0]), width = fontBBox[2] - fontBBox[0], height = fontBBox[3] - fontBBox[1]; const fontBBoxSize = Math.hypot(width, height); @@ -4693,7 +4687,7 @@ class TranslatedFont { // colour-related parameters) in the graphics state; // any use of such operators shall be ignored." if (operatorList.fnArray[0] === OPS.setCharWidthAndBounds) { - this._removeType3ColorOperators(operatorList, fontBBoxSize); + this.#removeType3ColorOperators(operatorList, fontBBoxSize); } charProcOperatorList[key] = operatorList.getIR(); @@ -4708,20 +4702,17 @@ class TranslatedFont { }); }); } - this.type3Loaded = loadCharProcsPromise.then(() => { - translatedFont.charProcOperatorList = charProcOperatorList; + this.#type3Loaded = loadCharProcsPromise.then(() => { + font.charProcOperatorList = charProcOperatorList; if (this._bbox) { - translatedFont.isCharBBox = true; - translatedFont.bbox = this._bbox; + font.isCharBBox = true; + font.bbox = this._bbox; } }); - return this.type3Loaded; + return this.#type3Loaded; } - /** - * @private - */ - _removeType3ColorOperators(operatorList, fontBBoxSize = NaN) { + #removeType3ColorOperators(operatorList, fontBBoxSize = NaN) { if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { assert( operatorList.fnArray[0] === OPS.setCharWidthAndBounds,