For years now, the `Font.exportData` method has (because of its previous implementation) been exporting many properties despite them being completely unused on the main-thread and/or in the API. This is unfortunate, since among those properties there's a number of potentially very large data-structures, containing e.g. Arrays and Objects, which thus have to be first structured cloned and then stored on the main-thread. With the changes in this patch, we'll thus by default save memory for *every* `Font` instance created (there can be a lot in longer documents). The memory savings obviously depends a lot on the actual font data, but some approximate figures are: For non-embedded fonts it can save a couple of kilobytes, for simple embedded fonts a handful of kilobytes, and for composite fonts the size of this auxiliary can even be larger than the actual font program itself. All-in-all, there's no good reason to keep exporting these properties by default when they're unused. However, since we cannot be sure that every property is unused in custom implementations of the PDF.js library, this patch adds a new `getDocument` option (named `fontExtraProperties`) that still allows access to the following properties: - "cMap": An internal data structure, only used with composite fonts and never really intended to be exposed on the main-thread and/or in the API. Note also that the `CMap`/`IdentityCMap` classes are a lot more complex than simple Objects, but only their "internal" properties survive the structured cloning used to send data to the main-thread. Given that CMaps can often be *very* large, not exporting them can also save a fair bit of memory. - "defaultEncoding": An internal property used with simple fonts, and used when building the glyph mapping on the worker-thread. Considering how complex that topic is, and given that not all font types are handled identically, exposing this on the main-thread and/or in the API most likely isn't useful. - "differences": An internal property used with simple fonts, and used when building the glyph mapping on the worker-thread. Considering how complex that topic is, and given that not all font types are handled identically, exposing this on the main-thread and/or in the API most likely isn't useful. - "isSymbolicFont": An internal property, used during font parsing and building of the glyph mapping on the worker-thread. - "seacMap": An internal map, only potentially used with *some* Type1/CFF fonts and never intended to be exposed in the API. The existing `Font.{charToGlyph, charToGlyphs}` functionality already takes this data into account when handling text. - "toFontChar": The glyph map, necessary for mapping characters to glyphs in the font, which is built upon the various encoding information contained in the font dictionary and/or font program. This is not directly used on the main-thread and/or in the API. - "toUnicode": The unicode map, necessary for text-extraction to work correctly, which is built upon the ToUnicode/CMap information contained in the font dictionary, but not directly used on the main-thread and/or in the API. - "vmetrics": An array of width data used with fonts which are composite *and* vertical, but not directly used on the main-thread and/or in the API. - "widths": An array of width data used with most fonts, but not directly used on the main-thread and/or in the API.
318 lines
7.6 KiB
JavaScript
318 lines
7.6 KiB
JavaScript
/* Copyright 2018 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import { apiCompatibilityParams } from "pdfjs-lib";
|
|
import { viewerCompatibilityParams } from "./viewer_compatibility.js";
|
|
|
|
const OptionKind = {
|
|
VIEWER: 0x02,
|
|
API: 0x04,
|
|
WORKER: 0x08,
|
|
PREFERENCE: 0x80,
|
|
};
|
|
|
|
/**
|
|
* PLEASE NOTE: To avoid introducing unnecessary dependencies, we specify the
|
|
* values below *explicitly* rather than relying on imported types.
|
|
*/
|
|
const defaultOptions = {
|
|
cursorToolOnLoad: {
|
|
/** @type {number} */
|
|
value: 0,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
defaultUrl: {
|
|
/** @type {string} */
|
|
value: "compressed.tracemonkey-pldi-09.pdf",
|
|
kind: OptionKind.VIEWER,
|
|
},
|
|
defaultZoomValue: {
|
|
/** @type {string} */
|
|
value: "",
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
disableHistory: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER,
|
|
},
|
|
disablePageLabels: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
/**
|
|
* The `disablePreferences` is, conditionally, defined below.
|
|
*/
|
|
enablePrintAutoRotate: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
enableWebGL: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
externalLinkRel: {
|
|
/** @type {string} */
|
|
value: "noopener noreferrer nofollow",
|
|
kind: OptionKind.VIEWER,
|
|
},
|
|
externalLinkTarget: {
|
|
/** @type {number} */
|
|
value: 0,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
historyUpdateUrl: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
ignoreDestinationZoom: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
imageResourcesPath: {
|
|
/** @type {string} */
|
|
value: "./images/",
|
|
kind: OptionKind.VIEWER,
|
|
},
|
|
/**
|
|
* The `locale` is, conditionally, defined below.
|
|
*/
|
|
maxCanvasPixels: {
|
|
/** @type {number} */
|
|
value: 16777216,
|
|
compatibility: viewerCompatibilityParams.maxCanvasPixels,
|
|
kind: OptionKind.VIEWER,
|
|
},
|
|
pdfBugEnabled: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
/**
|
|
* The `printResolution` is, conditionally, defined below.
|
|
*/
|
|
renderer: {
|
|
/** @type {string} */
|
|
value: "canvas",
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
renderInteractiveForms: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
sidebarViewOnLoad: {
|
|
/** @type {number} */
|
|
value: -1,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
scrollModeOnLoad: {
|
|
/** @type {number} */
|
|
value: -1,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
spreadModeOnLoad: {
|
|
/** @type {number} */
|
|
value: -1,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
textLayerMode: {
|
|
/** @type {number} */
|
|
value: 1,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
useOnlyCssZoom: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
viewOnLoad: {
|
|
/** @type {boolean} */
|
|
value: 0,
|
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
},
|
|
|
|
cMapPacked: {
|
|
/** @type {boolean} */
|
|
value: true,
|
|
kind: OptionKind.API,
|
|
},
|
|
cMapUrl: {
|
|
/** @type {string} */
|
|
value:
|
|
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")
|
|
? "../external/bcmaps/"
|
|
: "../web/cmaps/"),
|
|
kind: OptionKind.API,
|
|
},
|
|
disableAutoFetch: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.API + OptionKind.PREFERENCE,
|
|
},
|
|
disableCreateObjectURL: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
compatibility: apiCompatibilityParams.disableCreateObjectURL,
|
|
kind: OptionKind.API,
|
|
},
|
|
disableFontFace: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.API + OptionKind.PREFERENCE,
|
|
},
|
|
disableRange: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.API + OptionKind.PREFERENCE,
|
|
},
|
|
disableStream: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.API + OptionKind.PREFERENCE,
|
|
},
|
|
docBaseUrl: {
|
|
/** @type {string} */
|
|
value: "",
|
|
kind: OptionKind.API,
|
|
},
|
|
fontExtraProperties: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.API,
|
|
},
|
|
isEvalSupported: {
|
|
/** @type {boolean} */
|
|
value: true,
|
|
kind: OptionKind.API,
|
|
},
|
|
maxImageSize: {
|
|
/** @type {number} */
|
|
value: -1,
|
|
kind: OptionKind.API,
|
|
},
|
|
pdfBug: {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.API,
|
|
},
|
|
verbosity: {
|
|
/** @type {number} */
|
|
value: 1,
|
|
kind: OptionKind.API,
|
|
},
|
|
|
|
workerPort: {
|
|
/** @type {Object} */
|
|
value: null,
|
|
kind: OptionKind.WORKER,
|
|
},
|
|
workerSrc: {
|
|
/** @type {string} */
|
|
value:
|
|
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")
|
|
? "../src/worker_loader.js"
|
|
: "../build/pdf.worker.js"),
|
|
kind: OptionKind.WORKER,
|
|
},
|
|
};
|
|
if (
|
|
typeof PDFJSDev === "undefined" ||
|
|
PDFJSDev.test("!PRODUCTION || (GENERIC && !LIB)")
|
|
) {
|
|
defaultOptions.disablePreferences = {
|
|
/** @type {boolean} */
|
|
value: false,
|
|
kind: OptionKind.VIEWER,
|
|
};
|
|
defaultOptions.locale = {
|
|
/** @type {string} */
|
|
value: (typeof navigator !== "undefined" ? navigator.language : "en-US"),
|
|
kind: OptionKind.VIEWER,
|
|
};
|
|
defaultOptions.printResolution = {
|
|
/** @type {number} */
|
|
value: 150,
|
|
kind: OptionKind.VIEWER,
|
|
};
|
|
}
|
|
|
|
const userOptions = Object.create(null);
|
|
|
|
class AppOptions {
|
|
constructor() {
|
|
throw new Error("Cannot initialize AppOptions.");
|
|
}
|
|
|
|
static get(name) {
|
|
const userOption = userOptions[name];
|
|
if (userOption !== undefined) {
|
|
return userOption;
|
|
}
|
|
const defaultOption = defaultOptions[name];
|
|
if (defaultOption !== undefined) {
|
|
return defaultOption.compatibility || defaultOption.value;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
static getAll(kind = null) {
|
|
const options = Object.create(null);
|
|
for (const name in defaultOptions) {
|
|
const defaultOption = defaultOptions[name];
|
|
if (kind) {
|
|
if ((kind & defaultOption.kind) === 0) {
|
|
continue;
|
|
}
|
|
if (kind === OptionKind.PREFERENCE) {
|
|
const value = defaultOption.value,
|
|
valueType = typeof value;
|
|
|
|
if (
|
|
valueType === "boolean" ||
|
|
valueType === "string" ||
|
|
(valueType === "number" && Number.isInteger(value))
|
|
) {
|
|
options[name] = value;
|
|
continue;
|
|
}
|
|
throw new Error(`Invalid type for preference: ${name}`);
|
|
}
|
|
}
|
|
const userOption = userOptions[name];
|
|
options[name] =
|
|
userOption !== undefined
|
|
? userOption
|
|
: defaultOption.compatibility || defaultOption.value;
|
|
}
|
|
return options;
|
|
}
|
|
|
|
static set(name, value) {
|
|
userOptions[name] = value;
|
|
}
|
|
|
|
static remove(name) {
|
|
delete userOptions[name];
|
|
}
|
|
}
|
|
|
|
export { AppOptions, OptionKind };
|