Merge pull request #11777 from Snuffleupagus/Font-exportData-2

[api-minor] Change `Font.exportData` to, by default, stop exporting properties which are completely unused on the main-thread and/or in the API (PR 11773 follow-up)
This commit is contained in:
Tim van der Meij 2020-04-06 22:54:14 +02:00 committed by GitHub
commit 9871ccc69f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 149 additions and 118 deletions

View File

@ -94,6 +94,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
nativeImageDecoderSupport: NativeImageDecoding.DECODE, nativeImageDecoderSupport: NativeImageDecoding.DECODE,
ignoreErrors: false, ignoreErrors: false,
isEvalSupported: true, isEvalSupported: true,
fontExtraProperties: false,
}; };
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
@ -803,11 +804,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.handler.send("UnsupportedFeature", { this.handler.send("UnsupportedFeature", {
featureId: UNSUPPORTED_FEATURES.font, featureId: UNSUPPORTED_FEATURES.font,
}); });
return new TranslatedFont( return new TranslatedFont({
"g_font_error", loadedName: "g_font_error",
new ErrorFont("Type3 font load error: " + reason), font: new ErrorFont(`Type3 font load error: ${reason}`),
translated.font dict: translated.font,
); extraProperties: this.options.fontExtraProperties,
});
}); });
}) })
.then(translated => { .then(translated => {
@ -956,15 +958,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}, },
loadFont: function PartialEvaluator_loadFont(fontName, font, resources) { loadFont: function PartialEvaluator_loadFont(fontName, font, resources) {
function errorFont() { const errorFont = () => {
return Promise.resolve( return Promise.resolve(
new TranslatedFont( new TranslatedFont({
"g_font_error", loadedName: "g_font_error",
new ErrorFont("Font " + fontName + " is not available"), font: new ErrorFont(`Font "${fontName}" is not available.`),
font dict: font,
) extraProperties: this.options.fontExtraProperties,
})
); );
} };
var fontRef, var fontRef,
xref = this.xref; xref = this.xref;
@ -1096,14 +1099,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} }
translatedPromise translatedPromise
.then(function(translatedFont) { .then(translatedFont => {
if (translatedFont.fontType !== undefined) { if (translatedFont.fontType !== undefined) {
var xrefFontStats = xref.stats.fontTypes; var xrefFontStats = xref.stats.fontTypes;
xrefFontStats[translatedFont.fontType] = true; xrefFontStats[translatedFont.fontType] = true;
} }
fontCapability.resolve( fontCapability.resolve(
new TranslatedFont(font.loadedName, translatedFont, font) new TranslatedFont({
loadedName: font.loadedName,
font: translatedFont,
dict: font,
extraProperties: this.options.fontExtraProperties,
})
); );
}) })
.catch(reason => { .catch(reason => {
@ -1126,11 +1134,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} catch (ex) {} } catch (ex) {}
fontCapability.resolve( fontCapability.resolve(
new TranslatedFont( new TranslatedFont({
font.loadedName, loadedName: font.loadedName,
new ErrorFont(reason instanceof Error ? reason.message : reason), font: new ErrorFont(
font reason instanceof Error ? reason.message : reason
) ),
dict: font,
extraProperties: this.options.fontExtraProperties,
})
); );
}); });
return fontCapability.promise; return fontCapability.promise;
@ -3266,107 +3277,105 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return PartialEvaluator; return PartialEvaluator;
})(); })();
var TranslatedFont = (function TranslatedFontClosure() { class TranslatedFont {
// eslint-disable-next-line no-shadow constructor({ loadedName, font, dict, extraProperties = false }) {
function TranslatedFont(loadedName, font, dict) {
this.loadedName = loadedName; this.loadedName = loadedName;
this.font = font; this.font = font;
this.dict = dict; this.dict = dict;
this._extraProperties = extraProperties;
this.type3Loaded = null; this.type3Loaded = null;
this.sent = false; this.sent = false;
} }
TranslatedFont.prototype = {
send(handler) {
if (this.sent) {
return;
}
this.sent = true;
handler.send("commonobj", [ send(handler) {
this.loadedName, if (this.sent) {
"Font", return;
this.font.exportData(), }
]); this.sent = true;
},
fallback(handler) { handler.send("commonobj", [
if (!this.font.data) { this.loadedName,
return; "Font",
} this.font.exportData(this._extraProperties),
// 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) { fallback(handler) {
if (!this.font.isType3Font) { if (!this.font.data) {
throw new Error("Must be a Type3 font."); 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);
}
if (this.type3Loaded) { loadType3Data(evaluator, resources, parentOperatorList, task) {
return this.type3Loaded; if (!this.font.isType3Font) {
} throw new Error("Must be a Type3 font.");
// When parsing Type3 glyphs, always ignore them if there are errors. }
// Compared to the parsing of e.g. an entire page, it doesn't really
// make sense to only be able to render a Type3 glyph partially.
//
// Also, ensure that any Type3 image resources (which should be very rare
// in practice) are completely decoded on the worker-thread, to simplify
// the rendering code on the main-thread (see issue10717.pdf).
var type3Options = Object.create(evaluator.options);
type3Options.ignoreErrors = false;
type3Options.nativeImageDecoderSupport = NativeImageDecoding.NONE;
var type3Evaluator = evaluator.clone(type3Options);
type3Evaluator.parsingType3Font = true;
var translatedFont = this.font; if (this.type3Loaded) {
var loadCharProcsPromise = Promise.resolve();
var charProcs = this.dict.get("CharProcs");
var fontResources = this.dict.get("Resources") || resources;
var charProcKeys = charProcs.getKeys();
var charProcOperatorList = Object.create(null);
for (var i = 0, n = charProcKeys.length; i < n; ++i) {
const key = charProcKeys[i];
loadCharProcsPromise = loadCharProcsPromise.then(function() {
var glyphStream = charProcs.get(key);
var operatorList = new OperatorList();
return type3Evaluator
.getOperatorList({
stream: glyphStream,
task,
resources: fontResources,
operatorList,
})
.then(function() {
charProcOperatorList[key] = operatorList.getIR();
// Add the dependencies to the parent operator list so they are
// resolved before sub operator list is executed synchronously.
parentOperatorList.addDependencies(operatorList.dependencies);
})
.catch(function(reason) {
warn(`Type3 font resource "${key}" is not available.`);
const dummyOperatorList = new OperatorList();
charProcOperatorList[key] = dummyOperatorList.getIR();
});
});
}
this.type3Loaded = loadCharProcsPromise.then(function() {
translatedFont.charProcOperatorList = charProcOperatorList;
});
return this.type3Loaded; return this.type3Loaded;
}, }
}; // When parsing Type3 glyphs, always ignore them if there are errors.
return TranslatedFont; // Compared to the parsing of e.g. an entire page, it doesn't really
})(); // make sense to only be able to render a Type3 glyph partially.
//
// Also, ensure that any Type3 image resources (which should be very rare
// in practice) are completely decoded on the worker-thread, to simplify
// the rendering code on the main-thread (see issue10717.pdf).
var type3Options = Object.create(evaluator.options);
type3Options.ignoreErrors = false;
type3Options.nativeImageDecoderSupport = NativeImageDecoding.NONE;
var type3Evaluator = evaluator.clone(type3Options);
type3Evaluator.parsingType3Font = true;
var translatedFont = this.font;
var loadCharProcsPromise = Promise.resolve();
var charProcs = this.dict.get("CharProcs");
var fontResources = this.dict.get("Resources") || resources;
var charProcKeys = charProcs.getKeys();
var charProcOperatorList = Object.create(null);
for (var i = 0, n = charProcKeys.length; i < n; ++i) {
const key = charProcKeys[i];
loadCharProcsPromise = loadCharProcsPromise.then(function() {
var glyphStream = charProcs.get(key);
var operatorList = new OperatorList();
return type3Evaluator
.getOperatorList({
stream: glyphStream,
task,
resources: fontResources,
operatorList,
})
.then(function() {
charProcOperatorList[key] = operatorList.getIR();
// Add the dependencies to the parent operator list so they are
// resolved before sub operator list is executed synchronously.
parentOperatorList.addDependencies(operatorList.dependencies);
})
.catch(function(reason) {
warn(`Type3 font resource "${key}" is not available.`);
const dummyOperatorList = new OperatorList();
charProcOperatorList[key] = dummyOperatorList.getIR();
});
});
}
this.type3Loaded = loadCharProcsPromise.then(function() {
translatedFont.charProcOperatorList = charProcOperatorList;
});
return this.type3Loaded;
}
}
var StateManager = (function StateManagerClosure() { var StateManager = (function StateManagerClosure() {
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow

View File

@ -92,21 +92,17 @@ const EXPORT_DATA_PROPERTIES = [
"bbox", "bbox",
"black", "black",
"bold", "bold",
"cMap",
"charProcOperatorList", "charProcOperatorList",
"composite", "composite",
"data", "data",
"defaultEncoding",
"defaultVMetrics", "defaultVMetrics",
"defaultWidth", "defaultWidth",
"descent", "descent",
"differences",
"fallbackName", "fallbackName",
"fontMatrix", "fontMatrix",
"fontType", "fontType",
"isMonospace", "isMonospace",
"isSerifFont", "isSerifFont",
"isSymbolicFont",
"isType3Font", "isType3Font",
"italic", "italic",
"loadedName", "loadedName",
@ -114,12 +110,19 @@ const EXPORT_DATA_PROPERTIES = [
"missingFile", "missingFile",
"name", "name",
"remeasure", "remeasure",
"seacMap",
"subtype", "subtype",
"toFontChar",
"toUnicode",
"type", "type",
"vertical", "vertical",
];
const EXPORT_DATA_EXTRA_PROPERTIES = [
"cMap",
"defaultEncoding",
"differences",
"isSymbolicFont",
"seacMap",
"toFontChar",
"toUnicode",
"vmetrics", "vmetrics",
"widths", "widths",
]; ];
@ -1295,10 +1298,14 @@ var Font = (function FontClosure() {
return shadow(this, "renderer", renderer); return shadow(this, "renderer", renderer);
}, },
exportData() { exportData(extraProperties = false) {
const exportDataProperties = extraProperties
? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES]
: EXPORT_DATA_PROPERTIES;
const data = Object.create(null); const data = Object.create(null);
let property, value; let property, value;
for (property of EXPORT_DATA_PROPERTIES) { for (property of exportDataProperties) {
value = this[property]; value = this[property];
// Ignore properties that haven't been explicitly set. // Ignore properties that haven't been explicitly set.
if (value !== undefined) { if (value !== undefined) {
@ -3352,7 +3359,7 @@ var ErrorFont = (function ErrorFontClosure() {
charsToGlyphs: function ErrorFont_charsToGlyphs() { charsToGlyphs: function ErrorFont_charsToGlyphs() {
return []; return [];
}, },
exportData: function ErrorFont_exportData() { exportData(extraProperties = false) {
return { error: this.error }; return { error: this.error };
}, },
}; };

View File

@ -403,6 +403,7 @@ var WorkerMessageHandler = {
nativeImageDecoderSupport: data.nativeImageDecoderSupport, nativeImageDecoderSupport: data.nativeImageDecoderSupport,
ignoreErrors: data.ignoreErrors, ignoreErrors: data.ignoreErrors,
isEvalSupported: data.isEvalSupported, isEvalSupported: data.isEvalSupported,
fontExtraProperties: data.fontExtraProperties,
}; };
getPdfManager(data, evaluatorOptions) getPdfManager(data, evaluatorOptions)

View File

@ -145,6 +145,11 @@ function setPDFNetworkStreamFactory(pdfNetworkStreamFactory) {
* converted to OpenType fonts and loaded via font face rules. If disabled, * converted to OpenType fonts and loaded via font face rules. If disabled,
* fonts will be rendered using a built-in font renderer that constructs the * fonts will be rendered using a built-in font renderer that constructs the
* glyphs with primitive path commands. The default value is `false`. * glyphs with primitive path commands. The default value is `false`.
* @property {boolean} [fontExtraProperties] - Include additional properties,
* which are unused during rendering of PDF documents, when exporting the
* parsed font data from the worker-thread. This may be useful for debugging
* purposes (and backwards compatibility), but note that it will lead to
* increased memory usage. The default value is `false`.
* @property {boolean} [disableRange] - Disable range request loading * @property {boolean} [disableRange] - Disable range request loading
* of PDF files. When enabled, and if the server supports partial content * of PDF files. When enabled, and if the server supports partial content
* requests, then the PDF will be fetched in chunks. * requests, then the PDF will be fetched in chunks.
@ -251,6 +256,7 @@ function getDocument(src) {
params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE; params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
params.CMapReaderFactory = params.CMapReaderFactory || DOMCMapReaderFactory; params.CMapReaderFactory = params.CMapReaderFactory || DOMCMapReaderFactory;
params.ignoreErrors = params.stopAtErrors !== true; params.ignoreErrors = params.stopAtErrors !== true;
params.fontExtraProperties = params.fontExtraProperties === true;
params.pdfBug = params.pdfBug === true; params.pdfBug = params.pdfBug === true;
const NativeImageDecoderValues = Object.values(NativeImageDecoding); const NativeImageDecoderValues = Object.values(NativeImageDecoding);
@ -403,6 +409,7 @@ function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
nativeImageDecoderSupport: source.nativeImageDecoderSupport, nativeImageDecoderSupport: source.nativeImageDecoderSupport,
ignoreErrors: source.ignoreErrors, ignoreErrors: source.ignoreErrors,
isEvalSupported: source.isEvalSupported, isEvalSupported: source.isEvalSupported,
fontExtraProperties: source.fontExtraProperties,
}) })
.then(function(workerId) { .then(function(workerId) {
if (worker.destroyed) { if (worker.destroyed) {

View File

@ -309,6 +309,8 @@ const PDFViewerApplication = {
} }
if ("pdfbug" in hashParams) { if ("pdfbug" in hashParams) {
AppOptions.set("pdfBug", true); AppOptions.set("pdfBug", true);
AppOptions.set("fontExtraProperties", true);
const enabled = hashParams["pdfbug"].split(","); const enabled = hashParams["pdfbug"].split(",");
waitOn.push(loadAndEnablePDFBug(enabled)); waitOn.push(loadAndEnablePDFBug(enabled));
} }

View File

@ -193,6 +193,11 @@ const defaultOptions = {
value: "", value: "",
kind: OptionKind.API, kind: OptionKind.API,
}, },
fontExtraProperties: {
/** @type {boolean} */
value: false,
kind: OptionKind.API,
},
isEvalSupported: { isEvalSupported: {
/** @type {boolean} */ /** @type {boolean} */
value: true, value: true,