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() {
|
cleanup() {
|
||||||
return this.catalog.cleanup();
|
return this.catalog.cleanup();
|
||||||
}
|
}
|
||||||
|
@ -610,37 +610,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
handleText: function PartialEvaluator_handleText(chars, state) {
|
handleText(chars, state) {
|
||||||
var font = state.font;
|
const font = state.font;
|
||||||
var glyphs = font.charsToGlyphs(chars);
|
const glyphs = font.charsToGlyphs(chars);
|
||||||
var isAddToPathSet = !!(state.textRenderingMode &
|
|
||||||
|
if (font.data) {
|
||||||
|
const isAddToPathSet = !!(state.textRenderingMode &
|
||||||
TextRenderingMode.ADD_TO_PATH_FLAG);
|
TextRenderingMode.ADD_TO_PATH_FLAG);
|
||||||
if (font.data && (isAddToPathSet || this.options.disableFontFace ||
|
if (isAddToPathSet || state.fillColorSpace.name === 'Pattern' ||
|
||||||
state.fillColorSpace.name === 'Pattern')) {
|
font.disableFontFace || this.options.disableFontFace) {
|
||||||
var buildPath = (fontChar) => {
|
PartialEvaluator.buildFontPaths(font, glyphs, this.handler);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return glyphs;
|
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;
|
return PartialEvaluator;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -2639,14 +2644,31 @@ var TranslatedFont = (function TranslatedFontClosure() {
|
|||||||
if (this.sent) {
|
if (this.sent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var fontData = this.font.exportData();
|
this.sent = true;
|
||||||
|
|
||||||
handler.send('commonobj', [
|
handler.send('commonobj', [
|
||||||
this.loadedName,
|
this.loadedName,
|
||||||
'Font',
|
'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) {
|
loadType3Data(evaluator, resources, parentOperatorList, task) {
|
||||||
if (!this.font.isType3Font) {
|
if (!this.font.isType3Font) {
|
||||||
throw new Error('Must be a Type3 font.');
|
throw new Error('Must be a Type3 font.');
|
||||||
|
@ -1159,6 +1159,8 @@ var Font = (function FontClosure() {
|
|||||||
font: null,
|
font: null,
|
||||||
mimetype: null,
|
mimetype: null,
|
||||||
encoding: null,
|
encoding: null,
|
||||||
|
disableFontFace: false,
|
||||||
|
|
||||||
get renderer() {
|
get renderer() {
|
||||||
var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
|
var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
|
||||||
return shadow(this, 'renderer', renderer);
|
return shadow(this, 'renderer', renderer);
|
||||||
@ -2944,6 +2946,10 @@ var Font = (function FontClosure() {
|
|||||||
// Enter the translated string into the cache
|
// Enter the translated string into the cache
|
||||||
return (charsCache[charsCacheKey] = glyphs);
|
return (charsCache[charsCacheKey] = glyphs);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get glyphCacheValues() {
|
||||||
|
return Object.values(this.glyphCache);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return Font;
|
return Font;
|
||||||
|
@ -490,6 +490,22 @@ class Catalog {
|
|||||||
return shadow(this, 'javaScript', javaScript);
|
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() {
|
cleanup() {
|
||||||
this.pageKidsCountCache.clear();
|
this.pageKidsCountCache.clear();
|
||||||
|
|
||||||
|
@ -68,6 +68,10 @@ class BasePdfManager {
|
|||||||
return this.pdfDocument.getPage(pageIndex);
|
return this.pdfDocument.getPage(pageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fontFallback(id, handler) {
|
||||||
|
return this.pdfDocument.fontFallback(id, handler);
|
||||||
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
return this.pdfDocument.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) {
|
handler.on('Cleanup', function wphCleanup(data) {
|
||||||
return pdfManager.cleanup();
|
return pdfManager.cleanup();
|
||||||
});
|
});
|
||||||
|
@ -1676,7 +1676,10 @@ class WorkerTransport {
|
|||||||
this.messageHandler = messageHandler;
|
this.messageHandler = messageHandler;
|
||||||
this.loadingTask = loadingTask;
|
this.loadingTask = loadingTask;
|
||||||
this.commonObjs = new PDFObjects();
|
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._params = params;
|
||||||
this.CMapReaderFactory = new params.CMapReaderFactory({
|
this.CMapReaderFactory = new params.CMapReaderFactory({
|
||||||
baseUrl: params.cMapUrl,
|
baseUrl: params.cMapUrl,
|
||||||
@ -1944,11 +1947,16 @@ class WorkerTransport {
|
|||||||
onUnsupportedFeature: this._onUnsupportedFeature.bind(this),
|
onUnsupportedFeature: this._onUnsupportedFeature.bind(this),
|
||||||
fontRegistry,
|
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;
|
break;
|
||||||
case 'FontPath':
|
case 'FontPath':
|
||||||
this.commonObjs.resolve(id, exportedData);
|
this.commonObjs.resolve(id, exportedData);
|
||||||
|
@ -19,18 +19,15 @@ import {
|
|||||||
} from '../shared/util';
|
} from '../shared/util';
|
||||||
|
|
||||||
class BaseFontLoader {
|
class BaseFontLoader {
|
||||||
constructor(docId) {
|
constructor({ docId, onUnsupportedFeature, }) {
|
||||||
if (this.constructor === BaseFontLoader) {
|
if (this.constructor === BaseFontLoader) {
|
||||||
unreachable('Cannot initialize BaseFontLoader.');
|
unreachable('Cannot initialize BaseFontLoader.');
|
||||||
}
|
}
|
||||||
this.docId = docId;
|
this.docId = docId;
|
||||||
|
this._onUnsupportedFeature = onUnsupportedFeature;
|
||||||
|
|
||||||
this.nativeFontFaces = [];
|
this.nativeFontFaces = [];
|
||||||
this.styleElement = null;
|
this.styleElement = null;
|
||||||
this.loadingContext = {
|
|
||||||
requests: [],
|
|
||||||
nextRequestId: 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addNativeFontFace(nativeFontFace) {
|
addNativeFontFace(nativeFontFace) {
|
||||||
@ -64,22 +61,10 @@ class BaseFontLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bind(fonts, callback) {
|
async bind(font) {
|
||||||
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) {
|
|
||||||
// Add the font to the DOM only once; skip if the font is already loaded.
|
// Add the font to the DOM only once; skip if the font is already loaded.
|
||||||
if (font.attached || font.missingFile) {
|
if (font.attached || font.missingFile) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
font.attached = true;
|
font.attached = true;
|
||||||
|
|
||||||
@ -87,49 +72,37 @@ class BaseFontLoader {
|
|||||||
const nativeFontFace = font.createNativeFontFace();
|
const nativeFontFace = font.createNativeFontFace();
|
||||||
if (nativeFontFace) {
|
if (nativeFontFace) {
|
||||||
this.addNativeFontFace(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();
|
const rule = font.createFontFaceRule();
|
||||||
if (rule) {
|
if (rule) {
|
||||||
this.insertRule(rule);
|
this.insertRule(rule);
|
||||||
rules.push(rule);
|
|
||||||
fontsToLoad.push(font);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = this._queueLoadingCallback(callback);
|
if (this.isSyncFontLoadingSupported) {
|
||||||
if (this.isFontLoadingAPISupported) {
|
return; // The font was, synchronously, loaded.
|
||||||
Promise.all(fontLoadPromises).then(request.complete);
|
}
|
||||||
} else if (rules.length > 0 && !this.isSyncFontLoadingSupported) {
|
return new Promise((resolve) => {
|
||||||
this._prepareFontLoadEvent(rules, fontsToLoad, request);
|
const request = this._queueLoadingCallback(resolve);
|
||||||
} else {
|
this._prepareFontLoadEvent([rule], [font], request);
|
||||||
request.complete();
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_queueLoadingCallback(callback) {
|
_queueLoadingCallback(callback) {
|
||||||
function completeRequest() {
|
unreachable('Abstract method `_queueLoadingCallback`.');
|
||||||
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 isFontLoadingAPISupported() {
|
get isFontLoadingAPISupported() {
|
||||||
@ -168,6 +141,10 @@ FontLoader = class MozcentralFontLoader extends BaseFontLoader {
|
|||||||
FontLoader = class GenericFontLoader extends BaseFontLoader {
|
FontLoader = class GenericFontLoader extends BaseFontLoader {
|
||||||
constructor(docId) {
|
constructor(docId) {
|
||||||
super(docId);
|
super(docId);
|
||||||
|
this.loadingContext = {
|
||||||
|
requests: [],
|
||||||
|
nextRequestId: 0,
|
||||||
|
};
|
||||||
this.loadTestFontId = 0;
|
this.loadTestFontId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +182,29 @@ FontLoader = class GenericFontLoader extends BaseFontLoader {
|
|||||||
return shadow(this, 'isSyncFontLoadingSupported', supported);
|
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() {
|
get _loadTestFont() {
|
||||||
const getLoadTestFont = function() {
|
const getLoadTestFont = function() {
|
||||||
// This is a CFF font with 1 glyph for '.' that fills its entire width and
|
// 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;
|
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 isNodeJS = require('./is_node');
|
||||||
|
|
||||||
const hasDOM = typeof window === 'object' && typeof document === 'object';
|
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');
|
Number.isInteger = require('core-js/fn/number/is-integer');
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Support: IE, Safari<8, Chrome<32
|
// Support: IE, Safari<11, Chrome<63
|
||||||
(function checkPromise() {
|
(function checkPromise() {
|
||||||
if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('IMAGE_DECODERS')) {
|
if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('IMAGE_DECODERS')) {
|
||||||
// The current image decoders are synchronous, hence `Promise` shouldn't
|
// The current image decoders are synchronous, hence `Promise` shouldn't
|
||||||
// need to be polyfilled for the IMAGE_DECODERS build target.
|
// need to be polyfilled for the IMAGE_DECODERS build target.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (globalScope.Promise) {
|
if (globalScope.Promise && (globalScope.Promise.prototype &&
|
||||||
|
globalScope.Promise.prototype.finally)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
globalScope.Promise = require('core-js/fn/promise');
|
globalScope.Promise = require('core-js/fn/promise');
|
||||||
@ -254,8 +250,6 @@ const hasDOM = typeof window === 'object' && typeof document === 'object';
|
|||||||
require('core-js/es6/symbol');
|
require('core-js/es6/symbol');
|
||||||
})();
|
})();
|
||||||
|
|
||||||
} // End of !PDFJSDev.test('CHROME')
|
|
||||||
|
|
||||||
// Provides support for String.prototype.padStart in legacy browsers.
|
// Provides support for String.prototype.padStart in legacy browsers.
|
||||||
// Support: IE, Chrome<57
|
// Support: IE, Chrome<57
|
||||||
(function checkStringPadStart() {
|
(function checkStringPadStart() {
|
||||||
|
Loading…
Reference in New Issue
Block a user