Merge pull request #9990 from timvandermeij/catalog

Convert the `Catalog` class, in `src/core/obj.js`, to ES6 syntax
This commit is contained in:
Tim van der Meij 2018-08-25 17:11:07 +02:00 committed by GitHub
commit 7b7cd6dc95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -32,13 +32,14 @@ function fetchDestination(dest) {
return isDict(dest) ? dest.get('D') : dest;
}
var Catalog = (function CatalogClosure() {
function Catalog(pdfManager, xref) {
class Catalog {
constructor(pdfManager, xref) {
this.pdfManager = pdfManager;
this.xref = xref;
this.catDict = xref.getCatalogObj();
if (!isDict(this.catDict)) {
throw new FormatError('catalog object is not a dictionary');
throw new FormatError('Catalog object is not a dictionary.');
}
this.fontCache = new RefSetCache();
@ -46,21 +47,20 @@ var Catalog = (function CatalogClosure() {
this.pageKidsCountCache = new RefSetCache();
}
Catalog.prototype = {
get metadata() {
var streamRef = this.catDict.getRaw('Metadata');
const streamRef = this.catDict.getRaw('Metadata');
if (!isRef(streamRef)) {
return shadow(this, 'metadata', null);
}
var encryptMetadata = (!this.xref.encrypt ? false :
const suppressEncryption = !(this.xref.encrypt &&
this.xref.encrypt.encryptMetadata);
const stream = this.xref.fetch(streamRef, suppressEncryption);
let metadata;
var stream = this.xref.fetch(streamRef, !encryptMetadata);
var metadata;
if (stream && isDict(stream.dict)) {
var type = stream.dict.get('Type');
var subtype = stream.dict.get('Subtype');
const type = stream.dict.get('Type');
const subtype = stream.dict.get('Subtype');
if (isName(type, 'Metadata') && isName(subtype, 'XML')) {
// XXX: This should examine the charset the XML document defines,
@ -78,31 +78,35 @@ var Catalog = (function CatalogClosure() {
}
}
}
return shadow(this, 'metadata', metadata);
},
get toplevelPagesDict() {
var pagesObj = this.catDict.get('Pages');
if (!isDict(pagesObj)) {
throw new FormatError('invalid top-level pages dictionary');
}
// shadow the prototype getter
get toplevelPagesDict() {
const pagesObj = this.catDict.get('Pages');
if (!isDict(pagesObj)) {
throw new FormatError('Invalid top-level pages dictionary.');
}
return shadow(this, 'toplevelPagesDict', pagesObj);
},
}
get documentOutline() {
var obj = null;
let obj = null;
try {
obj = this.readDocumentOutline();
obj = this._readDocumentOutline();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn('Unable to read document outline');
warn('Unable to read document outline.');
}
return shadow(this, 'documentOutline', obj);
},
readDocumentOutline: function Catalog_readDocumentOutline() {
var obj = this.catDict.get('Outlines');
}
/**
* @private
*/
_readDocumentOutline() {
let obj = this.catDict.get('Outlines');
if (!isDict(obj)) {
return null;
}
@ -110,39 +114,42 @@ var Catalog = (function CatalogClosure() {
if (!isRef(obj)) {
return null;
}
var root = { items: [], };
var queue = [{ obj, parent: root, }];
const root = { items: [], };
const queue = [{ obj, parent: root, }];
// To avoid recursion, keep track of the already processed items.
var processed = new RefSet();
const processed = new RefSet();
processed.put(obj);
var xref = this.xref, blackColor = new Uint8ClampedArray(3);
const xref = this.xref, blackColor = new Uint8ClampedArray(3);
while (queue.length > 0) {
var i = queue.shift();
var outlineDict = xref.fetchIfRef(i.obj);
const i = queue.shift();
const outlineDict = xref.fetchIfRef(i.obj);
if (outlineDict === null) {
continue;
}
if (!outlineDict.has('Title')) {
throw new FormatError('Invalid outline item');
throw new FormatError('Invalid outline item encountered.');
}
var data = { url: null, dest: null, };
const data = { url: null, dest: null, };
Catalog.parseDestDictionary({
destDict: outlineDict,
resultObj: data,
docBaseUrl: this.pdfManager.docBaseUrl,
});
var title = outlineDict.get('Title');
var flags = outlineDict.get('F') || 0;
const title = outlineDict.get('Title');
const flags = outlineDict.get('F') || 0;
const color = outlineDict.getArray('C');
let rgbColor = blackColor;
var color = outlineDict.getArray('C'), rgbColor = blackColor;
// We only need to parse the color when it's valid, and non-default.
if (Array.isArray(color) && color.length === 3 &&
(color[0] !== 0 || color[1] !== 0 || color[2] !== 0)) {
rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
}
var outlineItem = {
const outlineItem = {
dest: data.dest,
url: data.url,
unsafeUrl: data.unsafeUrl,
@ -154,6 +161,7 @@ var Catalog = (function CatalogClosure() {
italic: !!(flags & 1),
items: [],
};
i.parent.items.push(outlineItem);
obj = outlineDict.getRaw('First');
if (isRef(obj) && !processed.has(obj)) {
@ -167,16 +175,16 @@ var Catalog = (function CatalogClosure() {
}
}
return (root.items.length > 0 ? root.items : null);
},
}
get numPages() {
var obj = this.toplevelPagesDict.get('Count');
const obj = this.toplevelPagesDict.get('Count');
if (!Number.isInteger(obj)) {
throw new FormatError(
'page count in top level pages object is not an integer');
'Page count in top-level pages dictionary is not an integer.');
}
// shadow the prototype getter
return shadow(this, 'numPages', obj);
},
}
get destinations() {
const obj = this._readDests(), dests = Object.create(null);
@ -193,14 +201,19 @@ var Catalog = (function CatalogClosure() {
});
}
return shadow(this, 'destinations', dests);
},
}
getDestination(destinationId) {
const obj = this._readDests();
if (obj instanceof NameTree || obj instanceof Dict) {
return fetchDestination(obj.get(destinationId) || null);
}
return null;
},
}
/**
* @private
*/
_readDests() {
const obj = this.catDict.get('Names');
if (obj && obj.has('Dests')) {
@ -208,12 +221,12 @@ var Catalog = (function CatalogClosure() {
} else if (this.catDict.has('Dests')) { // Simple destination dictionary.
return this.catDict.get('Dests');
}
},
}
get pageLabels() {
var obj = null;
let obj = null;
try {
obj = this.readPageLabels();
obj = this._readPageLabels();
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
@ -221,25 +234,29 @@ var Catalog = (function CatalogClosure() {
warn('Unable to read page labels.');
}
return shadow(this, 'pageLabels', obj);
},
readPageLabels: function Catalog_readPageLabels() {
var obj = this.catDict.getRaw('PageLabels');
}
/**
* @private
*/
_readPageLabels() {
const obj = this.catDict.getRaw('PageLabels');
if (!obj) {
return null;
}
var pageLabels = new Array(this.numPages);
var style = null;
var prefix = '';
var numberTree = new NumberTree(obj, this.xref);
var nums = numberTree.getAll();
var currentLabel = '', currentIndex = 1;
const pageLabels = new Array(this.numPages);
let style = null, prefix = '';
for (var i = 0, ii = this.numPages; i < ii; i++) {
const numberTree = new NumberTree(obj, this.xref);
const nums = numberTree.getAll();
let currentLabel = '', currentIndex = 1;
for (let i = 0, ii = this.numPages; i < ii; i++) {
if (i in nums) {
const labelDict = nums[i];
if (!isDict(labelDict)) {
throw new FormatError('The PageLabel is not a dictionary.');
throw new FormatError('PageLabel is not a dictionary.');
}
if (labelDict.has('Type') &&
@ -288,15 +305,15 @@ var Catalog = (function CatalogClosure() {
break;
case 'A':
case 'a':
var LIMIT = 26; // Use only the characters A--Z, or a--z.
var A_UPPER_CASE = 0x41, A_LOWER_CASE = 0x61;
const LIMIT = 26; // Use only the characters A-Z, or a-z.
const A_UPPER_CASE = 0x41, A_LOWER_CASE = 0x61;
var baseCharCode = (style === 'a' ? A_LOWER_CASE : A_UPPER_CASE);
var letterIndex = currentIndex - 1;
var character = String.fromCharCode(baseCharCode +
const baseCharCode = (style === 'a' ? A_LOWER_CASE : A_UPPER_CASE);
const letterIndex = currentIndex - 1;
const character = String.fromCharCode(baseCharCode +
(letterIndex % LIMIT));
var charBuf = [];
for (var j = 0, jj = (letterIndex / LIMIT) | 0; j <= jj; j++) {
const charBuf = [];
for (let j = 0, jj = (letterIndex / LIMIT) | 0; j <= jj; j++) {
charBuf.push(character);
}
currentLabel = charBuf.join('');
@ -313,10 +330,10 @@ var Catalog = (function CatalogClosure() {
currentIndex++;
}
return pageLabels;
},
}
get pageMode() {
let obj = this.catDict.get('PageMode');
const obj = this.catDict.get('PageMode');
let pageMode = 'UseNone'; // Default value.
if (isName(obj)) {
@ -331,21 +348,17 @@ var Catalog = (function CatalogClosure() {
}
}
return shadow(this, 'pageMode', pageMode);
},
get attachments() {
var xref = this.xref;
var attachments = null, nameTreeRef;
var obj = this.catDict.get('Names');
if (obj) {
nameTreeRef = obj.getRaw('EmbeddedFiles');
}
if (nameTreeRef) {
var nameTree = new NameTree(nameTreeRef, xref);
var names = nameTree.getAll();
for (var name in names) {
var fs = new FileSpec(names[name], xref);
get attachments() {
const obj = this.catDict.get('Names');
let attachments = null;
if (obj && obj.has('EmbeddedFiles')) {
const nameTree = new NameTree(obj.getRaw('EmbeddedFiles'), this.xref);
const names = nameTree.getAll();
for (const name in names) {
const fs = new FileSpec(names[name], this.xref);
if (!attachments) {
attachments = Object.create(null);
}
@ -353,49 +366,52 @@ var Catalog = (function CatalogClosure() {
}
}
return shadow(this, 'attachments', attachments);
},
}
get javaScript() {
var xref = this.xref;
var obj = this.catDict.get('Names');
const obj = this.catDict.get('Names');
let javaScript = null;
function appendIfJavaScriptDict(jsDict) {
var type = jsDict.get('S');
const type = jsDict.get('S');
if (!isName(type, 'JavaScript')) {
return;
}
var js = jsDict.get('JS');
let js = jsDict.get('JS');
if (isStream(js)) {
js = bytesToString(js.getBytes());
} else if (!isString(js)) {
return;
}
if (!javaScript) {
javaScript = [];
}
javaScript.push(stringToPDFString(js));
}
if (obj && obj.has('JavaScript')) {
var nameTree = new NameTree(obj.getRaw('JavaScript'), xref);
var names = nameTree.getAll();
for (var name in names) {
// We don't really use the JavaScript right now. This code is
const nameTree = new NameTree(obj.getRaw('JavaScript'), this.xref);
const names = nameTree.getAll();
for (const name in names) {
// We don't use most JavaScript in PDF documents. This code is
// defensive so we don't cause errors on document load.
var jsDict = names[name];
const jsDict = names[name];
if (isDict(jsDict)) {
appendIfJavaScriptDict(jsDict);
}
}
}
// Append OpenAction actions to javaScript array
var openactionDict = this.catDict.get('OpenAction');
if (isDict(openactionDict, 'Action')) {
var actionType = openactionDict.get('S');
// Append OpenAction actions to the JavaScript array.
const openActionDict = this.catDict.get('OpenAction');
if (isDict(openActionDict, 'Action')) {
const actionType = openActionDict.get('S');
if (isName(actionType, 'Named')) {
// The named Print action is not a part of the PDF 1.7 specification,
// but is supported by many PDF readers/writers (including Adobe's).
var action = openactionDict.get('N');
const action = openActionDict.get('N');
if (isName(action, 'Print')) {
if (!javaScript) {
javaScript = [];
@ -403,39 +419,40 @@ var Catalog = (function CatalogClosure() {
javaScript.push('print({});');
}
} else {
appendIfJavaScriptDict(openactionDict);
appendIfJavaScriptDict(openActionDict);
}
}
return shadow(this, 'javaScript', javaScript);
},
}
cleanup: function Catalog_cleanup() {
cleanup() {
this.pageKidsCountCache.clear();
var promises = [];
this.fontCache.forEach(function (promise) {
const promises = [];
this.fontCache.forEach(function(promise) {
promises.push(promise);
});
return Promise.all(promises).then((translatedFonts) => {
for (var i = 0, ii = translatedFonts.length; i < ii; i++) {
var font = translatedFonts[i].dict;
for (let i = 0, ii = translatedFonts.length; i < ii; i++) {
const font = translatedFonts[i].dict;
delete font.translated;
}
this.fontCache.clear();
this.builtInCMapCache.clear();
});
},
}
getPageDict: function Catalog_getPageDict(pageIndex) {
var capability = createPromiseCapability();
var nodesToVisit = [this.catDict.getRaw('Pages')];
var count, currentPageIndex = 0;
var xref = this.xref, pageKidsCountCache = this.pageKidsCountCache;
getPageDict(pageIndex) {
const capability = createPromiseCapability();
const nodesToVisit = [this.catDict.getRaw('Pages')];
const xref = this.xref, pageKidsCountCache = this.pageKidsCountCache;
let count, currentPageIndex = 0;
function next() {
while (nodesToVisit.length) {
var currentNode = nodesToVisit.pop();
const currentNode = nodesToVisit.pop();
if (isRef(currentNode)) {
count = pageKidsCountCache.get(currentNode);
@ -445,7 +462,7 @@ var Catalog = (function CatalogClosure() {
continue;
}
xref.fetchAsync(currentNode).then(function (obj) {
xref.fetchAsync(currentNode).then(function(obj) {
if (isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids'))) {
if (pageIndex === currentPageIndex) {
// Cache the Page reference, since it can *greatly* improve
@ -470,7 +487,7 @@ var Catalog = (function CatalogClosure() {
// Must be a child page dictionary.
if (!isDict(currentNode)) {
capability.reject(new FormatError(
'page dictionary kid reference points to wrong type of object'));
'Page dictionary kid reference points to wrong type of object.'));
return;
}
@ -478,7 +495,7 @@ var Catalog = (function CatalogClosure() {
if (Number.isInteger(count) && count >= 0) {
// Cache the Kids count, since it can reduce redundant lookups in
// documents where all nodes are found at *one* level of the tree.
var objId = currentNode.objId;
const objId = currentNode.objId;
if (objId && !pageKidsCountCache.has(objId)) {
pageKidsCountCache.put(objId, count);
}
@ -489,7 +506,7 @@ var Catalog = (function CatalogClosure() {
}
}
var kids = currentNode.get('Kids');
const kids = currentNode.get('Kids');
if (!Array.isArray(kids)) {
// Prevent errors in corrupt PDF documents that violate the
// specification by *inlining* Page dicts directly in the Kids
@ -505,105 +522,104 @@ var Catalog = (function CatalogClosure() {
}
capability.reject(new FormatError(
'page dictionary kids object is not an array'));
'Page dictionary kids object is not an array.'));
return;
}
// Always check all `Kids` nodes, to avoid getting stuck in an empty
// node further down in the tree (see issue5644.pdf, issue8088.pdf),
// and to ensure that we actually find the correct `Page` dict.
for (var last = kids.length - 1; last >= 0; last--) {
for (let last = kids.length - 1; last >= 0; last--) {
nodesToVisit.push(kids[last]);
}
}
capability.reject(new Error('Page index ' + pageIndex + ' not found.'));
capability.reject(new Error(`Page index ${pageIndex} not found.`));
}
next();
return capability.promise;
},
}
getPageIndex: function Catalog_getPageIndex(pageRef) {
getPageIndex(pageRef) {
// The page tree nodes have the count of all the leaves below them. To get
// how many pages are before we just have to walk up the tree and keep
// adding the count of siblings to the left of the node.
var xref = this.xref;
const xref = this.xref;
function pagesBeforeRef(kidRef) {
var total = 0;
var parentRef;
return xref.fetchAsync(kidRef).then(function (node) {
let total = 0, parentRef;
return xref.fetchAsync(kidRef).then(function(node) {
if (isRefsEqual(kidRef, pageRef) && !isDict(node, 'Page') &&
!(isDict(node) && !node.has('Type') && node.has('Contents'))) {
throw new FormatError(
'The reference does not point to a /Page Dict.');
'The reference does not point to a /Page dictionary.');
}
if (!node) {
return null;
}
if (!isDict(node)) {
throw new FormatError('node must be a Dict.');
throw new FormatError('Node must be a dictionary.');
}
parentRef = node.getRaw('Parent');
return node.getAsync('Parent');
}).then(function (parent) {
}).then(function(parent) {
if (!parent) {
return null;
}
if (!isDict(parent)) {
throw new FormatError('parent must be a Dict.');
throw new FormatError('Parent must be a dictionary.');
}
return parent.getAsync('Kids');
}).then(function (kids) {
}).then(function(kids) {
if (!kids) {
return null;
}
var kidPromises = [];
var found = false;
for (var i = 0; i < kids.length; i++) {
var kid = kids[i];
const kidPromises = [];
let found = false;
for (let i = 0, ii = kids.length; i < ii; i++) {
const kid = kids[i];
if (!isRef(kid)) {
throw new FormatError('kid must be a Ref.');
throw new FormatError('Kid must be a reference.');
}
if (isRefsEqual(kid, kidRef)) {
found = true;
break;
}
kidPromises.push(xref.fetchAsync(kid).then(function (kid) {
kidPromises.push(xref.fetchAsync(kid).then(function(kid) {
if (!isDict(kid)) {
throw new FormatError('kid node must be a Dict.');
throw new FormatError('Kid node must be a dictionary.');
}
if (kid.has('Count')) {
var count = kid.get('Count');
total += count;
} else { // page leaf node
total += kid.get('Count');
} else { // Page leaf node.
total++;
}
}));
}
if (!found) {
throw new FormatError('kid ref not found in parents kids');
throw new FormatError('Kid reference not found in parent\'s kids.');
}
return Promise.all(kidPromises).then(function () {
return Promise.all(kidPromises).then(function() {
return [total, parentRef];
});
});
}
var total = 0;
let total = 0;
function next(ref) {
return pagesBeforeRef(ref).then(function (args) {
return pagesBeforeRef(ref).then(function(args) {
if (!args) {
return total;
}
var count = args[0];
var parentRef = args[1];
const [count, parentRef] = args;
total += count;
return next(parentRef);
});
}
return next(pageRef);
},
};
}
/**
* @typedef ParseDestDictionaryParameters
@ -618,16 +634,17 @@ var Catalog = (function CatalogClosure() {
* Helper function used to parse the contents of destination dictionaries.
* @param {ParseDestDictionaryParameters} params
*/
Catalog.parseDestDictionary = function Catalog_parseDestDictionary(params) {
static parseDestDictionary(params) {
// Lets URLs beginning with 'www.' default to using the 'http://' protocol.
function addDefaultProtocolToUrl(url) {
if (url.indexOf('www.') === 0) {
return ('http://' + url);
return `http://${url}`;
}
return url;
}
// According to ISO 32000-1:2008, section 12.6.4.7, URIs should be encoded
// in 7-bit ASCII. Some bad PDFs use UTF-8 encoding, see Bugzilla 1122280.
// in 7-bit ASCII. Some bad PDFs use UTF-8 encoding; see Bugzilla 1122280.
function tryConvertUrlEncoding(url) {
try {
return stringToUTF8String(url);
@ -636,19 +653,19 @@ var Catalog = (function CatalogClosure() {
}
}
var destDict = params.destDict;
const destDict = params.destDict;
if (!isDict(destDict)) {
warn('parseDestDictionary: "destDict" must be a dictionary.');
warn('parseDestDictionary: `destDict` must be a dictionary.');
return;
}
var resultObj = params.resultObj;
const resultObj = params.resultObj;
if (typeof resultObj !== 'object') {
warn('parseDestDictionary: "resultObj" must be an object.');
warn('parseDestDictionary: `resultObj` must be an object.');
return;
}
var docBaseUrl = params.docBaseUrl || null;
const docBaseUrl = params.docBaseUrl || null;
var action = destDict.get('A'), url, dest;
let action = destDict.get('A'), url, dest;
if (!isDict(action) && destDict.has('Dest')) {
// A /Dest entry should *only* contain a Name or an Array, but some bad
// PDF generators ignore that and treat it as an /A entry.
@ -656,12 +673,12 @@ var Catalog = (function CatalogClosure() {
}
if (isDict(action)) {
let actionType = action.get('S');
const actionType = action.get('S');
if (!isName(actionType)) {
warn('parseDestDictionary: Invalid type in Action dictionary.');
return;
}
let actionName = actionType.name;
const actionName = actionType.name;
switch (actionName) {
case 'URI':
@ -687,7 +704,7 @@ var Catalog = (function CatalogClosure() {
/* falls through */
case 'GoToR':
var urlDict = action.get('F');
const urlDict = action.get('F');
if (isDict(urlDict)) {
// We assume that we found a FileSpec dictionary
// and fetch the URL without checking any further.
@ -697,13 +714,13 @@ var Catalog = (function CatalogClosure() {
}
// NOTE: the destination is relative to the *remote* document.
var remoteDest = action.get('D');
let remoteDest = action.get('D');
if (remoteDest) {
if (isName(remoteDest)) {
remoteDest = remoteDest.name;
}
if (isString(url)) {
let baseUrl = url.split('#')[0];
const baseUrl = url.split('#')[0];
if (isString(remoteDest)) {
url = baseUrl + '#' + remoteDest;
} else if (Array.isArray(remoteDest)) {
@ -712,21 +729,23 @@ var Catalog = (function CatalogClosure() {
}
}
// The 'NewWindow' property, equal to `LinkTarget.BLANK`.
var newWindow = action.get('NewWindow');
const newWindow = action.get('NewWindow');
if (isBool(newWindow)) {
resultObj.newWindow = newWindow;
}
break;
case 'Named':
var namedAction = action.get('N');
const namedAction = action.get('N');
if (isName(namedAction)) {
resultObj.action = namedAction.name;
}
break;
case 'JavaScript':
var jsAction = action.get('JS'), js;
const jsAction = action.get('JS');
let js;
if (isStream(jsAction)) {
js = bytesToString(jsAction.getBytes());
} else if (isString(jsAction)) {
@ -734,19 +753,19 @@ var Catalog = (function CatalogClosure() {
}
if (js) {
// Attempt to recover valid URLs from 'JS' entries with certain
// white-listed formats, e.g.
// Attempt to recover valid URLs from `JS` entries with certain
// white-listed formats:
// - window.open('http://example.com')
// - app.launchURL('http://example.com', true)
var URL_OPEN_METHODS = [
const URL_OPEN_METHODS = [
'app.launchURL',
'window.open'
];
var regex = new RegExp(
const regex = new RegExp(
'^\\s*(' + URL_OPEN_METHODS.join('|').split('.').join('\\.') +
')\\((?:\'|\")([^\'\"]*)(?:\'|\")(?:,\\s*(\\w+)\\)|\\))', 'i');
var jsUrl = regex.exec(stringToPDFString(js));
const jsUrl = regex.exec(stringToPDFString(js));
if (jsUrl && jsUrl[2]) {
url = jsUrl[2];
@ -758,7 +777,7 @@ var Catalog = (function CatalogClosure() {
}
/* falls through */
default:
warn(`parseDestDictionary: Unsupported Action type "${actionName}".`);
warn(`parseDestDictionary: unsupported action type "${actionName}".`);
break;
}
} else if (destDict.has('Dest')) { // Simple destination.
@ -767,7 +786,7 @@ var Catalog = (function CatalogClosure() {
if (isString(url)) {
url = tryConvertUrlEncoding(url);
var absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl);
const absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl);
if (absoluteUrl) {
resultObj.url = absoluteUrl.href;
}
@ -781,10 +800,8 @@ var Catalog = (function CatalogClosure() {
resultObj.dest = dest;
}
}
};
return Catalog;
})();
}
}
var XRef = (function XRefClosure() {
function XRef(stream, pdfManager) {