diff --git a/src/api.js b/src/api.js index aa3356ee7..a5ea5b4cd 100644 --- a/src/api.js +++ b/src/api.js @@ -119,6 +119,16 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { promise.resolve(destinations); return promise; }, + /** + * @return {Promise} A promise that is resolved with an array of all the + * JavaScript strings in the name tree. + */ + getJavaScript: function PDFDocumentProxy_getDestinations() { + var promise = new PDFJS.Promise(); + var js = this.pdfInfo.javaScript; + promise.resolve(js); + return promise; + }, /** * @return {Promise} A promise that is resolved with an {array} that is a * tree outline (if it has one) of the PDF. The tree is in the format of: diff --git a/src/obj.js b/src/obj.js index 3b0e16f3f..794efced3 100644 --- a/src/obj.js +++ b/src/obj.js @@ -17,7 +17,7 @@ /* globals assertWellFormed, bytesToString, CipherTransformFactory, error, info, InvalidPDFException, isArray, isCmd, isDict, isInt, isName, isRef, isStream, JpegStream, Lexer, log, Page, Parser, Promise, shadow, - stringToPDFString, stringToUTF8String, warn */ + stringToPDFString, stringToUTF8String, warn, isString */ 'use strict'; @@ -292,34 +292,51 @@ var Catalog = (function CatalogClosure() { }); } if (nameTreeRef) { - // reading name tree - var processed = new RefSet(); - processed.put(nameTreeRef); - var queue = [nameTreeRef]; - while (queue.length > 0) { - var i, n; - obj = xref.fetch(queue.shift()); - if (obj.has('Kids')) { - var kids = obj.get('Kids'); - for (i = 0, n = kids.length; i < n; i++) { - var kid = kids[i]; - if (processed.has(kid)) - error('invalid destinations'); - queue.push(kid); - processed.put(kid); - } + var nameTree = new NameTree(nameTreeRef, xref); + var names = nameTree.getAll(); + for (var name in names) { + if (!names.hasOwnProperty(name)) { continue; } - var names = obj.get('Names'); - if (names) { - for (i = 0, n = names.length; i < n; i += 2) { - dests[names[i]] = fetchDestination(xref.fetchIfRef(names[i + 1])); - } - } + dests[name] = fetchDestination(names[name]); } } return shadow(this, 'destinations', dests); }, + get javaScript() { + var xref = this.xref; + var obj = this.catDict.get('Names'); + + var javaScript = []; + if (obj && obj.has('JavaScript')) { + var nameTree = new NameTree(obj.getRaw('JavaScript'), xref); + var names = nameTree.getAll(); + for (var name in names) { + if (!names.hasOwnProperty(name)) { + continue; + } + // We don't really use the JavaScript right now so this code is + // defensive so we don't cause errors on document load. + var jsDict = names[name]; + if (!isDict(jsDict)) { + continue; + } + var type = jsDict.get('S'); + if (!isName(type) || type.name !== 'JavaScript') { + continue; + } + var js = jsDict.get('JS'); + if (!isString(js) && !isStream(js)) { + continue; + } + if (isStream(js)) { + js = bytesToString(js.getBytes()); + } + javaScript.push(js); + } + } + return shadow(this, 'javaScript', javaScript); + }, getPage: function Catalog_getPage(n) { var pageCache = this.pageCache; if (!pageCache) { @@ -755,6 +772,55 @@ var XRef = (function XRefClosure() { return XRef; })(); +/** + * A NameTree is like a Dict but has some adventagous properties, see the spec + * (7.9.6) for more details. + * TODO: implement all the Dict functions and make this more efficent. + */ +var NameTree = (function NameTreeClosure() { + function NameTree(root, xref) { + this.root = root; + this.xref = xref; + } + + NameTree.prototype = { + getAll: function NameTree_getAll() { + var dict = {}; + if (!this.root) { + return dict; + } + var xref = this.xref; + // reading name tree + var processed = new RefSet(); + processed.put(this.root); + var queue = [this.root]; + while (queue.length > 0) { + var i, n; + var obj = xref.fetch(queue.shift()); + if (obj.has('Kids')) { + var kids = obj.get('Kids'); + for (i = 0, n = kids.length; i < n; i++) { + var kid = kids[i]; + if (processed.has(kid)) + error('invalid destinations'); + queue.push(kid); + processed.put(kid); + } + continue; + } + var names = obj.get('Names'); + if (names) { + for (i = 0, n = names.length; i < n; i += 2) { + dict[names[i]] = xref.fetchIfRef(names[i + 1]); + } + } + } + return dict; + } + }; + return NameTree; +})(); + /** * A PDF document and page is built of many objects. E.g. there are objects * for fonts, images, rendering code and such. These objects might get processed diff --git a/src/worker.js b/src/worker.js index 0677f7c86..a3a2e61a6 100644 --- a/src/worker.js +++ b/src/worker.js @@ -152,6 +152,7 @@ var WorkerMessageHandler = { numPages: pdfModel.numPages, fingerprint: pdfModel.getFingerprint(), destinations: pdfModel.catalog.destinations, + javaScript: pdfModel.catalog.javaScript, outline: pdfModel.catalog.documentOutline, info: pdfModel.getDocumentInfo(), metadata: pdfModel.catalog.metadata, diff --git a/web/viewer.js b/web/viewer.js index 02d547786..749117472 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -1310,6 +1310,26 @@ var PDFView = { } self.pagesRefMap = pagesRefMap; + + // Wait to do this here so all the canvases are setup. + if (PDFView.supportsPrinting) { + pdfDocument.getJavaScript().then(function(javaScript) { + if (javaScript.length) { + PDFView.fallback(); + } + // Hack to support auto printing. + var regex = /\bprint\s*\(/g; + for (var i = 0, ii = javaScript.length; i < ii; i++) { + var js = javaScript[i]; + if (js && regex.test(js)) { + setTimeout(function() { + window.print(); + }); + return; + } + } + }); + } }); var destinationsPromise = pdfDocument.getDestinations();