From d5bd3f63bfbd1255f0713021e6b6f354c70dbce3 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Tue, 8 Jun 2021 13:58:52 +0200 Subject: [PATCH] Cache the "raw" standard font data in the worker-thread (PR 12726 follow-up) *This implementation is basically a copy of the pre-existing `builtInCMapCache` implementation.* For some, badly generated, PDF documents it's possible that we'll end up having to fetch the *same* standard font data over and over (which is obviously inefficient). While not common, it's certainly possible that a PDF document uses *custom* font names where the actual font then references one of the standard fonts; see e.g. issue 11399 for one such example. Note that I did suggest adding worker-thread caching of standard font data in PR 12726, however it wasn't deemed necessary at the time. Now that we have a real-world example that benefit from caching, I think that we should simply implement this now. --- src/core/catalog.js | 2 ++ src/core/document.js | 8 ++++++ src/core/evaluator.js | 52 ++++++++++++++++++++++++------------ test/unit/annotation_spec.js | 1 + 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/core/catalog.js b/src/core/catalog.js index 0b3a2bba91e120..6c768e8563857e 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -71,6 +71,7 @@ class Catalog { this.fontCache = new RefSetCache(); this.builtInCMapCache = new Map(); + this.standardFontDataCache = new Map(); this.globalImageCache = new GlobalImageCache(); this.pageKidsCountCache = new RefSetCache(); this.pageIndexCache = new RefSetCache(); @@ -1020,6 +1021,7 @@ class Catalog { } this.fontCache.clear(); this.builtInCMapCache.clear(); + this.standardFontDataCache.clear(); }); } diff --git a/src/core/document.js b/src/core/document.js index 8d782fd61bf798..02d237aae82849 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -79,6 +79,7 @@ class Page { globalIdFactory, fontCache, builtInCMapCache, + standardFontDataCache, globalImageCache, nonBlendModesSet, xfaFactory, @@ -90,6 +91,7 @@ class Page { this.ref = ref; this.fontCache = fontCache; this.builtInCMapCache = builtInCMapCache; + this.standardFontDataCache = standardFontDataCache; this.globalImageCache = globalImageCache; this.nonBlendModesSet = nonBlendModesSet; this.evaluatorOptions = pdfManager.evaluatorOptions; @@ -255,6 +257,7 @@ class Page { idFactory: this._localIdFactory, fontCache: this.fontCache, builtInCMapCache: this.builtInCMapCache, + standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, options: this.evaluatorOptions, }); @@ -321,6 +324,7 @@ class Page { idFactory: this._localIdFactory, fontCache: this.fontCache, builtInCMapCache: this.builtInCMapCache, + standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, options: this.evaluatorOptions, }); @@ -425,6 +429,7 @@ class Page { idFactory: this._localIdFactory, fontCache: this.fontCache, builtInCMapCache: this.builtInCMapCache, + standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, options: this.evaluatorOptions, }); @@ -883,6 +888,7 @@ class PDFDocument { idFactory: this._globalIdFactory, fontCache: this.catalog.fontCache, builtInCMapCache: this.catalog.builtInCMapCache, + standardFontDataCache: this.catalog.standardFontDataCache, }); const operatorList = new OperatorList(); const initialState = { @@ -1141,6 +1147,7 @@ class PDFDocument { globalIdFactory: this._globalIdFactory, fontCache: catalog.fontCache, builtInCMapCache: catalog.builtInCMapCache, + standardFontDataCache: catalog.standardFontDataCache, globalImageCache: catalog.globalImageCache, nonBlendModesSet: catalog.nonBlendModesSet, xfaFactory: this.xfaFactory, @@ -1163,6 +1170,7 @@ class PDFDocument { globalIdFactory: this._globalIdFactory, fontCache: catalog.fontCache, builtInCMapCache: catalog.builtInCMapCache, + standardFontDataCache: catalog.standardFontDataCache, globalImageCache: catalog.globalImageCache, nonBlendModesSet: catalog.nonBlendModesSet, xfaFactory: null, diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 1b1dc1b732c1b7..909ae8e26e05cd 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -205,6 +205,7 @@ class PartialEvaluator { idFactory, fontCache, builtInCMapCache, + standardFontDataCache, globalImageCache, options = null, }) { @@ -214,6 +215,7 @@ class PartialEvaluator { this.idFactory = idFactory; this.fontCache = fontCache; this.builtInCMapCache = builtInCMapCache; + this.standardFontDataCache = standardFontDataCache; this.globalImageCache = globalImageCache; this.options = options || DefaultPartialEvaluatorOptions; this.parsingType3Font = false; @@ -386,8 +388,13 @@ class PartialEvaluator { } async fetchStandardFontData(name) { + const cachedData = this.standardFontDataCache.get(name); + if (cachedData) { + return new Stream(cachedData); + } + // The symbol fonts are not consistent across platforms, always load the - // font data for them. + // standard font data for them. if ( this.options.useSystemFonts && name !== "Symbol" && @@ -395,31 +402,42 @@ class PartialEvaluator { ) { return null; } - const standardFontNameToFileName = getStdFontNameToFileMap(); - const filename = standardFontNameToFileName[name]; + + const standardFontNameToFileName = getStdFontNameToFileMap(), + filename = standardFontNameToFileName[name]; + let data; + if (this.options.standardFontDataUrl !== null) { const url = `${this.options.standardFontDataUrl}${filename}.pfb`; const response = await fetch(url); if (!response.ok) { warn( - `fetchStandardFontData failed to fetch file "${url}" with "${response.statusText}".` + `fetchStandardFontData: failed to fetch file "${url}" with "${response.statusText}".` + ); + } else { + data = await response.arrayBuffer(); + } + } else { + // Get the data on the main-thread instead. + try { + data = await this.handler.sendWithPromise("FetchStandardFontData", { + filename, + }); + } catch (e) { + warn( + `fetchStandardFontData: failed to fetch file "${filename}" with "${e}".` ); - return null; } - return new Stream(await response.arrayBuffer()); } - // Get the data on the main thread instead. - try { - const data = await this.handler.sendWithPromise("FetchStandardFontData", { - filename, - }); - return new Stream(data); - } catch (e) { - warn( - `fetchStandardFontData failed to fetch file "${filename}" with "${e}".` - ); + + if (!data) { + return null; } - return null; + // Cache the "raw" standard font data, to avoid fetching it repeateadly + // (see e.g. issue 11399). + this.standardFontDataCache.set(name, data); + + return new Stream(data); } async buildFormXObject( diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index f4e7149aa6ae74..a6f4a808c733c3 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -125,6 +125,7 @@ describe("annotation", function () { idFactory: createIdFactory(/* pageIndex = */ 0), fontCache: new RefSetCache(), builtInCMapCache, + standardFontDataCache: new Map(), }); });