diff --git a/src/core/document.js b/src/core/document.js index 115c8011c..3e0a20299 100644 --- a/src/core/document.js +++ b/src/core/document.js @@ -666,6 +666,10 @@ class PDFDocument { }); } + fontFallback(id, handler) { + return this.catalog.fontFallback(id, handler); + } + cleanup() { return this.catalog.cleanup(); } diff --git a/src/core/evaluator.js b/src/core/evaluator.js index df87c6ba6..cd2841155 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -610,37 +610,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }); }, - handleText: function PartialEvaluator_handleText(chars, state) { - var font = state.font; - var glyphs = font.charsToGlyphs(chars); - var isAddToPathSet = !!(state.textRenderingMode & - TextRenderingMode.ADD_TO_PATH_FLAG); - if (font.data && (isAddToPathSet || this.options.disableFontFace || - state.fillColorSpace.name === 'Pattern')) { - var buildPath = (fontChar) => { - if (!font.renderer.hasBuiltPath(fontChar)) { - var path = font.renderer.getPathJs(fontChar); - this.handler.send('commonobj', [ - font.loadedName + '_path_' + fontChar, - 'FontPath', - path - ]); - } - }; + handleText(chars, state) { + const font = state.font; + const glyphs = font.charsToGlyphs(chars); - for (var i = 0, ii = glyphs.length; i < ii; i++) { - var glyph = glyphs[i]; - buildPath(glyph.fontChar); - - // If the glyph has an accent we need to build a path for its - // fontChar too, otherwise CanvasGraphics_paintChar will fail. - var accent = glyph.accent; - if (accent && accent.fontChar) { - buildPath(accent.fontChar); - } + if (font.data) { + const isAddToPathSet = !!(state.textRenderingMode & + TextRenderingMode.ADD_TO_PATH_FLAG); + if (isAddToPathSet || state.fillColorSpace.name === 'Pattern' || + font.disableFontFace || this.options.disableFontFace) { + PartialEvaluator.buildFontPaths(font, glyphs, this.handler); } } - return glyphs; }, @@ -2623,6 +2604,30 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }, }; + PartialEvaluator.buildFontPaths = function(font, glyphs, handler) { + function buildPath(fontChar) { + if (font.renderer.hasBuiltPath(fontChar)) { + return; + } + handler.send('commonobj', [ + `${font.loadedName}_path_${fontChar}`, + 'FontPath', + font.renderer.getPathJs(fontChar), + ]); + } + + for (const glyph of glyphs) { + buildPath(glyph.fontChar); + + // If the glyph has an accent we need to build a path for its + // fontChar too, otherwise CanvasGraphics_paintChar will fail. + const accent = glyph.accent; + if (accent && accent.fontChar) { + buildPath(accent.fontChar); + } + } + }; + return PartialEvaluator; })(); @@ -2639,14 +2644,31 @@ var TranslatedFont = (function TranslatedFontClosure() { if (this.sent) { return; } - var fontData = this.font.exportData(); + this.sent = true; + handler.send('commonobj', [ this.loadedName, 'Font', - fontData + this.font.exportData(), ]); - this.sent = true; }, + + fallback(handler) { + if (!this.font.data) { + return; + } + // When font loading failed, fall back to the built-in font renderer. + this.font.disableFontFace = true; + // An arbitrary number of text rendering operators could have been + // encountered between the point in time when the 'Font' message was sent + // to the main-thread, and the point in time when the 'FontFallback' + // message was received on the worker-thread. + // To ensure that all 'FontPath's are available on the main-thread, when + // font loading failed, attempt to resend *all* previously parsed glyphs. + const glyphs = this.font.glyphCacheValues; + PartialEvaluator.buildFontPaths(this.font, glyphs, handler); + }, + loadType3Data(evaluator, resources, parentOperatorList, task) { if (!this.font.isType3Font) { throw new Error('Must be a Type3 font.'); diff --git a/src/core/fonts.js b/src/core/fonts.js index 8d618a8c9..a52525127 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -1159,6 +1159,8 @@ var Font = (function FontClosure() { font: null, mimetype: null, encoding: null, + disableFontFace: false, + get renderer() { var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED); return shadow(this, 'renderer', renderer); @@ -2944,6 +2946,10 @@ var Font = (function FontClosure() { // Enter the translated string into the cache return (charsCache[charsCacheKey] = glyphs); }, + + get glyphCacheValues() { + return Object.values(this.glyphCache); + }, }; return Font; diff --git a/src/core/obj.js b/src/core/obj.js index b71a503d0..484460f8d 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -490,6 +490,22 @@ class Catalog { return shadow(this, 'javaScript', javaScript); } + fontFallback(id, handler) { + const promises = []; + this.fontCache.forEach(function(promise) { + promises.push(promise); + }); + + return Promise.all(promises).then((translatedFonts) => { + for (const translatedFont of translatedFonts) { + if (translatedFont.loadedName === id) { + translatedFont.fallback(handler); + return; + } + } + }); + } + cleanup() { this.pageKidsCountCache.clear(); diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js index cdbbc3a58..abe2b7df1 100644 --- a/src/core/pdf_manager.js +++ b/src/core/pdf_manager.js @@ -68,6 +68,10 @@ class BasePdfManager { return this.pdfDocument.getPage(pageIndex); } + fontFallback(id, handler) { + return this.pdfDocument.fontFallback(id, handler); + } + cleanup() { return this.pdfDocument.cleanup(); } diff --git a/src/core/worker.js b/src/core/worker.js index 910d2692f..46daf0a27 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -667,6 +667,10 @@ var WorkerMessageHandler = { }); }); + handler.on('FontFallback', function(data) { + return pdfManager.fontFallback(data.id, handler); + }); + handler.on('Cleanup', function wphCleanup(data) { return pdfManager.cleanup(); }); diff --git a/src/display/api.js b/src/display/api.js index 8c2371fff..271aaead3 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -1676,7 +1676,10 @@ class WorkerTransport { this.messageHandler = messageHandler; this.loadingTask = loadingTask; this.commonObjs = new PDFObjects(); - this.fontLoader = new FontLoader(loadingTask.docId); + this.fontLoader = new FontLoader({ + docId: loadingTask.docId, + onUnsupportedFeature: this._onUnsupportedFeature.bind(this), + }); this._params = params; this.CMapReaderFactory = new params.CMapReaderFactory({ baseUrl: params.cMapUrl, @@ -1947,6 +1950,12 @@ class WorkerTransport { this.fontLoader.bind(font).then(() => { this.commonObjs.resolve(id, font); + }, (reason) => { + messageHandler.sendWithPromise('FontFallback', { + id, + }).finally(() => { + this.commonObjs.resolve(id, font); + }); }); break; case 'FontPath': diff --git a/src/display/font_loader.js b/src/display/font_loader.js index 19e592ad3..8aa8cda55 100644 --- a/src/display/font_loader.js +++ b/src/display/font_loader.js @@ -19,11 +19,12 @@ import { } from '../shared/util'; class BaseFontLoader { - constructor(docId) { + constructor({ docId, onUnsupportedFeature, }) { if (this.constructor === BaseFontLoader) { unreachable('Cannot initialize BaseFontLoader.'); } this.docId = docId; + this._onUnsupportedFeature = onUnsupportedFeature; this.nativeFontFaces = []; this.styleElement = null; @@ -74,9 +75,12 @@ class BaseFontLoader { try { await nativeFontFace.loaded; } catch (ex) { - // Return a promise that is always fulfilled, even when the font - // failed to load. + this._onUnsupportedFeature({ featureId: UNSUPPORTED_FEATURES.font, }); warn(`Failed to load font '${nativeFontFace.family}': '${ex}'.`); + + // When font loading failed, fall back to the built-in font renderer. + font.disableFontFace = true; + throw ex; } } return; // The font was, asynchronously, loaded. diff --git a/src/shared/compatibility.js b/src/shared/compatibility.js index b858ee3fc..c67de876d 100644 --- a/src/shared/compatibility.js +++ b/src/shared/compatibility.js @@ -23,11 +23,6 @@ if ((typeof PDFJSDev === 'undefined' || globalScope._pdfjsCompatibilityChecked = true; -// In the Chrome extension, most of the polyfills are unnecessary. -// We support down to Chrome 49, because it's still commonly used by Windows XP -// users - https://github.com/mozilla/pdf.js/issues/9397 -if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('CHROME')) { - const isNodeJS = require('./is_node'); const hasDOM = typeof window === 'object' && typeof document === 'object'; @@ -199,14 +194,15 @@ const hasDOM = typeof window === 'object' && typeof document === 'object'; Number.isInteger = require('core-js/fn/number/is-integer'); })(); -// Support: IE, Safari<8, Chrome<32 +// Support: IE, Safari<11, Chrome<63 (function checkPromise() { if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('IMAGE_DECODERS')) { // The current image decoders are synchronous, hence `Promise` shouldn't // need to be polyfilled for the IMAGE_DECODERS build target. return; } - if (globalScope.Promise) { + if (globalScope.Promise && (globalScope.Promise.prototype && + globalScope.Promise.prototype.finally)) { return; } globalScope.Promise = require('core-js/fn/promise'); @@ -254,8 +250,6 @@ const hasDOM = typeof window === 'object' && typeof document === 'object'; require('core-js/es6/symbol'); })(); -} // End of !PDFJSDev.test('CHROME') - // Provides support for String.prototype.padStart in legacy browsers. // Support: IE, Chrome<57 (function checkStringPadStart() {