From cfb908c999359e32ac87a8e3aa3d391fd4635dd4 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Wed, 10 May 2023 15:31:07 +0200 Subject: [PATCH] Add a cache to avoid to load several times a local font On my computer, it takes few tenths of a second to load a local font. Since a font can be used several times in a document, the cache will improve performances. --- src/core/catalog.js | 2 ++ src/core/document.js | 9 +++++++++ src/core/evaluator.js | 4 ++++ src/core/font_substitutions.js | 17 +++++++++++++++-- src/display/font_loader.js | 7 +++++++ test/unit/annotation_spec.js | 1 + 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/core/catalog.js b/src/core/catalog.js index 92811ebe5..da4ba665e 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -81,6 +81,7 @@ class Catalog { this.pageKidsCountCache = new RefSetCache(); this.pageIndexCache = new RefSetCache(); this.nonBlendModesSet = new RefSet(); + this.systemFontCache = new Map(); } get version() { @@ -1062,6 +1063,7 @@ class Catalog { this.fontCache.clear(); this.builtInCMapCache.clear(); this.standardFontDataCache.clear(); + this.systemFontCache.clear(); } async getPageDict(pageIndex) { diff --git a/src/core/document.js b/src/core/document.js index be6f0d728..221488e73 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -74,6 +74,7 @@ class Page { builtInCMapCache, standardFontDataCache, globalImageCache, + systemFontCache, nonBlendModesSet, xfaFactory, }) { @@ -86,6 +87,7 @@ class Page { this.builtInCMapCache = builtInCMapCache; this.standardFontDataCache = standardFontDataCache; this.globalImageCache = globalImageCache; + this.systemFontCache = systemFontCache; this.nonBlendModesSet = nonBlendModesSet; this.evaluatorOptions = pdfManager.evaluatorOptions; this.resourcesPromise = null; @@ -270,6 +272,7 @@ class Page { builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, options: this.evaluatorOptions, }); @@ -321,6 +324,7 @@ class Page { builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, options: this.evaluatorOptions, }); @@ -390,6 +394,7 @@ class Page { builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, options: this.evaluatorOptions, }); @@ -533,6 +538,7 @@ class Page { builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, options: this.evaluatorOptions, }); @@ -602,6 +608,7 @@ class Page { builtInCMapCache: this.builtInCMapCache, standardFontDataCache: this.standardFontDataCache, globalImageCache: this.globalImageCache, + systemFontCache: this.systemFontCache, options: this.evaluatorOptions, }); @@ -1476,6 +1483,7 @@ class PDFDocument { builtInCMapCache: catalog.builtInCMapCache, standardFontDataCache: catalog.standardFontDataCache, globalImageCache: catalog.globalImageCache, + systemFontCache: catalog.systemFontCache, nonBlendModesSet: catalog.nonBlendModesSet, xfaFactory, }); @@ -1574,6 +1582,7 @@ class PDFDocument { builtInCMapCache: catalog.builtInCMapCache, standardFontDataCache: catalog.standardFontDataCache, globalImageCache: catalog.globalImageCache, + systemFontCache: catalog.systemFontCache, nonBlendModesSet: catalog.nonBlendModesSet, xfaFactory: null, }) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index c2c7f1e0d..4baf3ffe2 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -215,6 +215,7 @@ class PartialEvaluator { builtInCMapCache, standardFontDataCache, globalImageCache, + systemFontCache, options = null, }) { this.xref = xref; @@ -225,6 +226,7 @@ class PartialEvaluator { this.builtInCMapCache = builtInCMapCache; this.standardFontDataCache = standardFontDataCache; this.globalImageCache = globalImageCache; + this.systemFontCache = systemFontCache; this.options = options || DefaultPartialEvaluatorOptions; this.parsingType3Font = false; @@ -4197,6 +4199,7 @@ class PartialEvaluator { properties.isInternalFont = !!file; if (!properties.isInternalFont && this.options.useSystemFonts) { properties.systemFontInfo = getFontSubstitution( + this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, baseFontName, @@ -4309,6 +4312,7 @@ class PartialEvaluator { isInternalFont = !!fontFile; if (!isInternalFont && this.options.useSystemFonts) { systemFontInfo = getFontSubstitution( + this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, fontName.name, diff --git a/src/core/font_substitutions.js b/src/core/font_substitutions.js index 584bef6d3..bc576aa6d 100644 --- a/src/core/font_substitutions.js +++ b/src/core/font_substitutions.js @@ -374,6 +374,7 @@ function makeLocal(prepend, local) { * } * or use the FontFace API. * + * @param {Map} systemFontCache The cache of local fonts. * @param {Object} idFactory The ids factory. * @param {String} localFontPath Path to the fonts directory. * @param {String} baseFontName The font name to be substituted. @@ -382,6 +383,7 @@ function makeLocal(prepend, local) { * @returns an Object with the CSS, the loaded name, the src and the style. */ function getFontSubstitution( + systemFontCache, idFactory, localFontPath, baseFontName, @@ -393,6 +395,12 @@ function getFontSubstitution( // just replace them by a dash. baseFontName = normalizeFontName(baseFontName); + const key = baseFontName; + let substitutionInfo = systemFontCache.get(key); + if (substitutionInfo) { + return substitutionInfo; + } + // First, check if we've a substitution for the base font. let substitution = substitutionMap.get(baseFontName); if (!substitution) { @@ -416,6 +424,7 @@ function getFontSubstitution( const loadedName = `${idFactory.getDocId()}_sf_${idFactory.createFontId()}`; if (!substitution) { if (!validateFontName(baseFontName)) { + systemFontCache.set(key, null); // If the baseFontName is not valid we don't want to use it. return null; } @@ -427,12 +436,14 @@ function getFontSubstitution( (bold && BOLD) || (italic && ITALIC) || NORMAL; - return { + substitutionInfo = { css: `${loadedName},sans-serif`, loadedName, src: `local(${baseFontName})`, style, }; + systemFontCache.set(key, substitutionInfo); + return substitutionInfo; } while (substitution.alias) { @@ -467,12 +478,14 @@ function getFontSubstitution( src = `local(${baseFontName}),${src}`; } - return { + substitutionInfo = { css: `${loadedName},${ultimate}`, loadedName, src, style, }; + systemFontCache.set(key, substitutionInfo); + return substitutionInfo; } export { getFontSubstitution }; diff --git a/src/display/font_loader.js b/src/display/font_loader.js index 515c8fc33..dce51fafb 100644 --- a/src/display/font_loader.js +++ b/src/display/font_loader.js @@ -25,6 +25,8 @@ import { import { isNodeJS } from "../shared/is_node.js"; class FontLoader { + #systemFonts = new Set(); + constructor({ ownerDocument = globalThis.document, styleElement = null, // For testing only. @@ -69,6 +71,7 @@ class FontLoader { this._document.fonts.delete(nativeFontFace); } this.nativeFontFaces.clear(); + this.#systemFonts.clear(); if (this.styleElement) { // Note: ChildNode.remove doesn't throw if the parentNode is undefined. @@ -78,6 +81,9 @@ class FontLoader { } async loadSystemFont(info) { + if (!info || this.#systemFonts.has(info.loadedName)) { + return; + } assert( !this.disableFontFace, "loadSystemFont shouldn't be called when `disableFontFace` is set." @@ -89,6 +95,7 @@ class FontLoader { this.addNativeFontFace(fontFace); try { await fontFace.load(); + this.#systemFonts.add(loadedName); } catch { warn( `Cannot load system font: ${loadedName} for style ${style.style} and weight ${style.weight}.` diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index 615de6ddd..5fbc477d4 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -130,6 +130,7 @@ describe("annotation", function () { fontCache: new RefSetCache(), builtInCMapCache, standardFontDataCache: new Map(), + systemFontCache: new Map(), }); });