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.
This commit is contained in:
Calixte Denizet 2023-05-10 15:31:07 +02:00
parent 2d2f7b315e
commit cfb908c999
6 changed files with 38 additions and 2 deletions

View File

@ -81,6 +81,7 @@ class Catalog {
this.pageKidsCountCache = new RefSetCache(); this.pageKidsCountCache = new RefSetCache();
this.pageIndexCache = new RefSetCache(); this.pageIndexCache = new RefSetCache();
this.nonBlendModesSet = new RefSet(); this.nonBlendModesSet = new RefSet();
this.systemFontCache = new Map();
} }
get version() { get version() {
@ -1062,6 +1063,7 @@ class Catalog {
this.fontCache.clear(); this.fontCache.clear();
this.builtInCMapCache.clear(); this.builtInCMapCache.clear();
this.standardFontDataCache.clear(); this.standardFontDataCache.clear();
this.systemFontCache.clear();
} }
async getPageDict(pageIndex) { async getPageDict(pageIndex) {

View File

@ -74,6 +74,7 @@ class Page {
builtInCMapCache, builtInCMapCache,
standardFontDataCache, standardFontDataCache,
globalImageCache, globalImageCache,
systemFontCache,
nonBlendModesSet, nonBlendModesSet,
xfaFactory, xfaFactory,
}) { }) {
@ -86,6 +87,7 @@ class Page {
this.builtInCMapCache = builtInCMapCache; this.builtInCMapCache = builtInCMapCache;
this.standardFontDataCache = standardFontDataCache; this.standardFontDataCache = standardFontDataCache;
this.globalImageCache = globalImageCache; this.globalImageCache = globalImageCache;
this.systemFontCache = systemFontCache;
this.nonBlendModesSet = nonBlendModesSet; this.nonBlendModesSet = nonBlendModesSet;
this.evaluatorOptions = pdfManager.evaluatorOptions; this.evaluatorOptions = pdfManager.evaluatorOptions;
this.resourcesPromise = null; this.resourcesPromise = null;
@ -270,6 +272,7 @@ class Page {
builtInCMapCache: this.builtInCMapCache, builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache, standardFontDataCache: this.standardFontDataCache,
globalImageCache: this.globalImageCache, globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions, options: this.evaluatorOptions,
}); });
@ -321,6 +324,7 @@ class Page {
builtInCMapCache: this.builtInCMapCache, builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache, standardFontDataCache: this.standardFontDataCache,
globalImageCache: this.globalImageCache, globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions, options: this.evaluatorOptions,
}); });
@ -390,6 +394,7 @@ class Page {
builtInCMapCache: this.builtInCMapCache, builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache, standardFontDataCache: this.standardFontDataCache,
globalImageCache: this.globalImageCache, globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions, options: this.evaluatorOptions,
}); });
@ -533,6 +538,7 @@ class Page {
builtInCMapCache: this.builtInCMapCache, builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache, standardFontDataCache: this.standardFontDataCache,
globalImageCache: this.globalImageCache, globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions, options: this.evaluatorOptions,
}); });
@ -602,6 +608,7 @@ class Page {
builtInCMapCache: this.builtInCMapCache, builtInCMapCache: this.builtInCMapCache,
standardFontDataCache: this.standardFontDataCache, standardFontDataCache: this.standardFontDataCache,
globalImageCache: this.globalImageCache, globalImageCache: this.globalImageCache,
systemFontCache: this.systemFontCache,
options: this.evaluatorOptions, options: this.evaluatorOptions,
}); });
@ -1476,6 +1483,7 @@ class PDFDocument {
builtInCMapCache: catalog.builtInCMapCache, builtInCMapCache: catalog.builtInCMapCache,
standardFontDataCache: catalog.standardFontDataCache, standardFontDataCache: catalog.standardFontDataCache,
globalImageCache: catalog.globalImageCache, globalImageCache: catalog.globalImageCache,
systemFontCache: catalog.systemFontCache,
nonBlendModesSet: catalog.nonBlendModesSet, nonBlendModesSet: catalog.nonBlendModesSet,
xfaFactory, xfaFactory,
}); });
@ -1574,6 +1582,7 @@ class PDFDocument {
builtInCMapCache: catalog.builtInCMapCache, builtInCMapCache: catalog.builtInCMapCache,
standardFontDataCache: catalog.standardFontDataCache, standardFontDataCache: catalog.standardFontDataCache,
globalImageCache: catalog.globalImageCache, globalImageCache: catalog.globalImageCache,
systemFontCache: catalog.systemFontCache,
nonBlendModesSet: catalog.nonBlendModesSet, nonBlendModesSet: catalog.nonBlendModesSet,
xfaFactory: null, xfaFactory: null,
}) })

View File

@ -215,6 +215,7 @@ class PartialEvaluator {
builtInCMapCache, builtInCMapCache,
standardFontDataCache, standardFontDataCache,
globalImageCache, globalImageCache,
systemFontCache,
options = null, options = null,
}) { }) {
this.xref = xref; this.xref = xref;
@ -225,6 +226,7 @@ class PartialEvaluator {
this.builtInCMapCache = builtInCMapCache; this.builtInCMapCache = builtInCMapCache;
this.standardFontDataCache = standardFontDataCache; this.standardFontDataCache = standardFontDataCache;
this.globalImageCache = globalImageCache; this.globalImageCache = globalImageCache;
this.systemFontCache = systemFontCache;
this.options = options || DefaultPartialEvaluatorOptions; this.options = options || DefaultPartialEvaluatorOptions;
this.parsingType3Font = false; this.parsingType3Font = false;
@ -4197,6 +4199,7 @@ class PartialEvaluator {
properties.isInternalFont = !!file; properties.isInternalFont = !!file;
if (!properties.isInternalFont && this.options.useSystemFonts) { if (!properties.isInternalFont && this.options.useSystemFonts) {
properties.systemFontInfo = getFontSubstitution( properties.systemFontInfo = getFontSubstitution(
this.systemFontCache,
this.idFactory, this.idFactory,
this.options.standardFontDataUrl, this.options.standardFontDataUrl,
baseFontName, baseFontName,
@ -4309,6 +4312,7 @@ class PartialEvaluator {
isInternalFont = !!fontFile; isInternalFont = !!fontFile;
if (!isInternalFont && this.options.useSystemFonts) { if (!isInternalFont && this.options.useSystemFonts) {
systemFontInfo = getFontSubstitution( systemFontInfo = getFontSubstitution(
this.systemFontCache,
this.idFactory, this.idFactory,
this.options.standardFontDataUrl, this.options.standardFontDataUrl,
fontName.name, fontName.name,

View File

@ -374,6 +374,7 @@ function makeLocal(prepend, local) {
* } * }
* or use the FontFace API. * or use the FontFace API.
* *
* @param {Map} systemFontCache The cache of local fonts.
* @param {Object} idFactory The ids factory. * @param {Object} idFactory The ids factory.
* @param {String} localFontPath Path to the fonts directory. * @param {String} localFontPath Path to the fonts directory.
* @param {String} baseFontName The font name to be substituted. * @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. * @returns an Object with the CSS, the loaded name, the src and the style.
*/ */
function getFontSubstitution( function getFontSubstitution(
systemFontCache,
idFactory, idFactory,
localFontPath, localFontPath,
baseFontName, baseFontName,
@ -393,6 +395,12 @@ function getFontSubstitution(
// just replace them by a dash. // just replace them by a dash.
baseFontName = normalizeFontName(baseFontName); 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. // First, check if we've a substitution for the base font.
let substitution = substitutionMap.get(baseFontName); let substitution = substitutionMap.get(baseFontName);
if (!substitution) { if (!substitution) {
@ -416,6 +424,7 @@ function getFontSubstitution(
const loadedName = `${idFactory.getDocId()}_sf_${idFactory.createFontId()}`; const loadedName = `${idFactory.getDocId()}_sf_${idFactory.createFontId()}`;
if (!substitution) { if (!substitution) {
if (!validateFontName(baseFontName)) { if (!validateFontName(baseFontName)) {
systemFontCache.set(key, null);
// If the baseFontName is not valid we don't want to use it. // If the baseFontName is not valid we don't want to use it.
return null; return null;
} }
@ -427,12 +436,14 @@ function getFontSubstitution(
(bold && BOLD) || (bold && BOLD) ||
(italic && ITALIC) || (italic && ITALIC) ||
NORMAL; NORMAL;
return { substitutionInfo = {
css: `${loadedName},sans-serif`, css: `${loadedName},sans-serif`,
loadedName, loadedName,
src: `local(${baseFontName})`, src: `local(${baseFontName})`,
style, style,
}; };
systemFontCache.set(key, substitutionInfo);
return substitutionInfo;
} }
while (substitution.alias) { while (substitution.alias) {
@ -467,12 +478,14 @@ function getFontSubstitution(
src = `local(${baseFontName}),${src}`; src = `local(${baseFontName}),${src}`;
} }
return { substitutionInfo = {
css: `${loadedName},${ultimate}`, css: `${loadedName},${ultimate}`,
loadedName, loadedName,
src, src,
style, style,
}; };
systemFontCache.set(key, substitutionInfo);
return substitutionInfo;
} }
export { getFontSubstitution }; export { getFontSubstitution };

View File

@ -25,6 +25,8 @@ import {
import { isNodeJS } from "../shared/is_node.js"; import { isNodeJS } from "../shared/is_node.js";
class FontLoader { class FontLoader {
#systemFonts = new Set();
constructor({ constructor({
ownerDocument = globalThis.document, ownerDocument = globalThis.document,
styleElement = null, // For testing only. styleElement = null, // For testing only.
@ -69,6 +71,7 @@ class FontLoader {
this._document.fonts.delete(nativeFontFace); this._document.fonts.delete(nativeFontFace);
} }
this.nativeFontFaces.clear(); this.nativeFontFaces.clear();
this.#systemFonts.clear();
if (this.styleElement) { if (this.styleElement) {
// Note: ChildNode.remove doesn't throw if the parentNode is undefined. // Note: ChildNode.remove doesn't throw if the parentNode is undefined.
@ -78,6 +81,9 @@ class FontLoader {
} }
async loadSystemFont(info) { async loadSystemFont(info) {
if (!info || this.#systemFonts.has(info.loadedName)) {
return;
}
assert( assert(
!this.disableFontFace, !this.disableFontFace,
"loadSystemFont shouldn't be called when `disableFontFace` is set." "loadSystemFont shouldn't be called when `disableFontFace` is set."
@ -89,6 +95,7 @@ class FontLoader {
this.addNativeFontFace(fontFace); this.addNativeFontFace(fontFace);
try { try {
await fontFace.load(); await fontFace.load();
this.#systemFonts.add(loadedName);
} catch { } catch {
warn( warn(
`Cannot load system font: ${loadedName} for style ${style.style} and weight ${style.weight}.` `Cannot load system font: ${loadedName} for style ${style.style} and weight ${style.weight}.`

View File

@ -130,6 +130,7 @@ describe("annotation", function () {
fontCache: new RefSetCache(), fontCache: new RefSetCache(),
builtInCMapCache, builtInCMapCache,
standardFontDataCache: new Map(), standardFontDataCache: new Map(),
systemFontCache: new Map(),
}); });
}); });