Merge pull request #10539 from Snuffleupagus/fallback-disableFontFace-v2
[api-minor] Fallback to the built-in font renderer when font loading fails
This commit is contained in:
		
						commit
						81f5835cd7
					
				@ -666,6 +666,10 @@ class PDFDocument {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fontFallback(id, handler) {
 | 
			
		||||
    return this.catalog.fontFallback(id, handler);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  cleanup() {
 | 
			
		||||
    return this.catalog.cleanup();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -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 &
 | 
			
		||||
    handleText(chars, state) {
 | 
			
		||||
      const font = state.font;
 | 
			
		||||
      const glyphs = font.charsToGlyphs(chars);
 | 
			
		||||
 | 
			
		||||
      if (font.data) {
 | 
			
		||||
        const 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
 | 
			
		||||
            ]);
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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 (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.');
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
@ -1944,11 +1947,16 @@ class WorkerTransport {
 | 
			
		||||
            onUnsupportedFeature: this._onUnsupportedFeature.bind(this),
 | 
			
		||||
            fontRegistry,
 | 
			
		||||
          });
 | 
			
		||||
          const fontReady = (fontObjs) => {
 | 
			
		||||
            this.commonObjs.resolve(id, font);
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
          this.fontLoader.bind([font], fontReady);
 | 
			
		||||
          this.fontLoader.bind(font).then(() => {
 | 
			
		||||
            this.commonObjs.resolve(id, font);
 | 
			
		||||
          }, (reason) => {
 | 
			
		||||
            messageHandler.sendWithPromise('FontFallback', {
 | 
			
		||||
              id,
 | 
			
		||||
            }).finally(() => {
 | 
			
		||||
              this.commonObjs.resolve(id, font);
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
          break;
 | 
			
		||||
        case 'FontPath':
 | 
			
		||||
          this.commonObjs.resolve(id, exportedData);
 | 
			
		||||
 | 
			
		||||
@ -19,18 +19,15 @@ 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;
 | 
			
		||||
    this.loadingContext = {
 | 
			
		||||
      requests: [],
 | 
			
		||||
      nextRequestId: 0,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addNativeFontFace(nativeFontFace) {
 | 
			
		||||
@ -64,22 +61,10 @@ class BaseFontLoader {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bind(fonts, callback) {
 | 
			
		||||
    const rules = [];
 | 
			
		||||
    const fontsToLoad = [];
 | 
			
		||||
    const fontLoadPromises = [];
 | 
			
		||||
    const getNativeFontPromise = function(nativeFontFace) {
 | 
			
		||||
      // Return a promise that is always fulfilled, even when the font fails to
 | 
			
		||||
      // load.
 | 
			
		||||
      return nativeFontFace.loaded.catch(function(reason) {
 | 
			
		||||
        warn(`Failed to load font "${nativeFontFace.family}": ${reason}`);
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    for (const font of fonts) {
 | 
			
		||||
  async bind(font) {
 | 
			
		||||
    // Add the font to the DOM only once; skip if the font is already loaded.
 | 
			
		||||
    if (font.attached || font.missingFile) {
 | 
			
		||||
        continue;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    font.attached = true;
 | 
			
		||||
 | 
			
		||||
@ -87,49 +72,37 @@ class BaseFontLoader {
 | 
			
		||||
      const nativeFontFace = font.createNativeFontFace();
 | 
			
		||||
      if (nativeFontFace) {
 | 
			
		||||
        this.addNativeFontFace(nativeFontFace);
 | 
			
		||||
          fontLoadPromises.push(getNativeFontPromise(nativeFontFace));
 | 
			
		||||
        try {
 | 
			
		||||
          await nativeFontFace.loaded;
 | 
			
		||||
        } catch (ex) {
 | 
			
		||||
          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;
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
      }
 | 
			
		||||
      return; // The font was, asynchronously, loaded.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // !this.isFontLoadingAPISupported
 | 
			
		||||
    const rule = font.createFontFaceRule();
 | 
			
		||||
    if (rule) {
 | 
			
		||||
      this.insertRule(rule);
 | 
			
		||||
          rules.push(rule);
 | 
			
		||||
          fontsToLoad.push(font);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const request = this._queueLoadingCallback(callback);
 | 
			
		||||
    if (this.isFontLoadingAPISupported) {
 | 
			
		||||
      Promise.all(fontLoadPromises).then(request.complete);
 | 
			
		||||
    } else if (rules.length > 0 && !this.isSyncFontLoadingSupported) {
 | 
			
		||||
      this._prepareFontLoadEvent(rules, fontsToLoad, request);
 | 
			
		||||
    } else {
 | 
			
		||||
      request.complete();
 | 
			
		||||
      if (this.isSyncFontLoadingSupported) {
 | 
			
		||||
        return; // The font was, synchronously, loaded.
 | 
			
		||||
      }
 | 
			
		||||
      return new Promise((resolve) => {
 | 
			
		||||
        const request = this._queueLoadingCallback(resolve);
 | 
			
		||||
        this._prepareFontLoadEvent([rule], [font], request);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _queueLoadingCallback(callback) {
 | 
			
		||||
    function completeRequest() {
 | 
			
		||||
      assert(!request.done, 'completeRequest() cannot be called twice.');
 | 
			
		||||
      request.done = true;
 | 
			
		||||
 | 
			
		||||
      // Sending all completed requests in order of how they were queued.
 | 
			
		||||
      while (context.requests.length > 0 && context.requests[0].done) {
 | 
			
		||||
        const otherRequest = context.requests.shift();
 | 
			
		||||
        setTimeout(otherRequest.callback, 0);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const context = this.loadingContext;
 | 
			
		||||
    const request = {
 | 
			
		||||
      id: `pdfjs-font-loading-${context.nextRequestId++}`,
 | 
			
		||||
      done: false,
 | 
			
		||||
      complete: completeRequest,
 | 
			
		||||
      callback,
 | 
			
		||||
    };
 | 
			
		||||
    context.requests.push(request);
 | 
			
		||||
    return request;
 | 
			
		||||
    unreachable('Abstract method `_queueLoadingCallback`.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get isFontLoadingAPISupported() {
 | 
			
		||||
@ -168,6 +141,10 @@ FontLoader = class MozcentralFontLoader extends BaseFontLoader {
 | 
			
		||||
FontLoader = class GenericFontLoader extends BaseFontLoader {
 | 
			
		||||
  constructor(docId) {
 | 
			
		||||
    super(docId);
 | 
			
		||||
    this.loadingContext = {
 | 
			
		||||
      requests: [],
 | 
			
		||||
      nextRequestId: 0,
 | 
			
		||||
    };
 | 
			
		||||
    this.loadTestFontId = 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -205,6 +182,29 @@ FontLoader = class GenericFontLoader extends BaseFontLoader {
 | 
			
		||||
    return shadow(this, 'isSyncFontLoadingSupported', supported);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _queueLoadingCallback(callback) {
 | 
			
		||||
    function completeRequest() {
 | 
			
		||||
      assert(!request.done, 'completeRequest() cannot be called twice.');
 | 
			
		||||
      request.done = true;
 | 
			
		||||
 | 
			
		||||
      // Sending all completed requests in order of how they were queued.
 | 
			
		||||
      while (context.requests.length > 0 && context.requests[0].done) {
 | 
			
		||||
        const otherRequest = context.requests.shift();
 | 
			
		||||
        setTimeout(otherRequest.callback, 0);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const context = this.loadingContext;
 | 
			
		||||
    const request = {
 | 
			
		||||
      id: `pdfjs-font-loading-${context.nextRequestId++}`,
 | 
			
		||||
      done: false,
 | 
			
		||||
      complete: completeRequest,
 | 
			
		||||
      callback,
 | 
			
		||||
    };
 | 
			
		||||
    context.requests.push(request);
 | 
			
		||||
    return request;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get _loadTestFont() {
 | 
			
		||||
    const getLoadTestFont = function() {
 | 
			
		||||
      // This is a CFF font with 1 glyph for '.' that fills its entire width and
 | 
			
		||||
 | 
			
		||||
@ -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() {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user