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 &
|
||||
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.');
|
||||
|
@ -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,72 +61,48 @@ 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}`);
|
||||
});
|
||||
};
|
||||
async bind(font) {
|
||||
// Add the font to the DOM only once; skip if the font is already loaded.
|
||||
if (font.attached || font.missingFile) {
|
||||
return;
|
||||
}
|
||||
font.attached = true;
|
||||
|
||||
for (const font of fonts) {
|
||||
// Add the font to the DOM only once; skip if the font is already loaded.
|
||||
if (font.attached || font.missingFile) {
|
||||
continue;
|
||||
}
|
||||
font.attached = true;
|
||||
if (this.isFontLoadingAPISupported) {
|
||||
const nativeFontFace = font.createNativeFontFace();
|
||||
if (nativeFontFace) {
|
||||
this.addNativeFontFace(nativeFontFace);
|
||||
try {
|
||||
await nativeFontFace.loaded;
|
||||
} catch (ex) {
|
||||
this._onUnsupportedFeature({ featureId: UNSUPPORTED_FEATURES.font, });
|
||||
warn(`Failed to load font '${nativeFontFace.family}': '${ex}'.`);
|
||||
|
||||
if (this.isFontLoadingAPISupported) {
|
||||
const nativeFontFace = font.createNativeFontFace();
|
||||
if (nativeFontFace) {
|
||||
this.addNativeFontFace(nativeFontFace);
|
||||
fontLoadPromises.push(getNativeFontPromise(nativeFontFace));
|
||||
}
|
||||
} else {
|
||||
const rule = font.createFontFaceRule();
|
||||
if (rule) {
|
||||
this.insertRule(rule);
|
||||
rules.push(rule);
|
||||
fontsToLoad.push(font);
|
||||
// When font loading failed, fall back to the built-in font renderer.
|
||||
font.disableFontFace = true;
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return; // The font was, asynchronously, loaded.
|
||||
}
|
||||
|
||||
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();
|
||||
// !this.isFontLoadingAPISupported
|
||||
const rule = font.createFontFaceRule();
|
||||
if (rule) {
|
||||
this.insertRule(rule);
|
||||
|
||||
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…
Reference in New Issue
Block a user