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