diff --git a/src/api.js b/src/api.js index f1baefade..18644ebe6 100644 --- a/src/api.js +++ b/src/api.js @@ -1,6 +1,16 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/** + * This is the main entry point for loading a PDF and interacting with it. + * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR) + * is used, which means it must follow the same origin rules that any XHR does + * e.g. No cross domain requests without CORS. + * + * @param {string|TypedAray} source Either a url to a PDF is located or a + * typed array already populated with data. + * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object. + */ PDFJS.getDocument = function getDocument(source) { var promise = new PDFJS.Promise(); var transport = new WorkerTransport(promise); @@ -31,33 +41,74 @@ PDFJS.getDocument = function getDocument(source) { return promise; }; +/** + * Proxy to a PDFDocument in the worker thread. Also, contains commonly used + * properties that can be read synchronously. + */ var PDFDocumentProxy = (function() { function PDFDocumentProxy(pdfInfo, transport) { this.pdfInfo = pdfInfo; this.transport = transport; } PDFDocumentProxy.prototype = { + /** + * @return {number} Total number of pages the PDF contains. + */ get numPages() { return this.pdfInfo.numPages; }, + /** + * @return {string} A unique ID to identify a PDF. Not guaranteed to be + * unique. + */ get fingerprint() { return this.pdfInfo.fingerprint; }, + /** + * @param {number} The page number to get. The first page is 1. + * @return {Promise} A promise that is resolved with a {PDFPageProxy} + * object. + */ getPage: function(number) { return this.transport.getPage(number); }, + /** + * @return {Promise} A promise that is resolved with a lookup table for + * mapping named destinations to reference numbers. + */ getDestinations: function() { var promise = new PDFJS.Promise(); var destinations = this.pdfInfo.destinations; promise.resolve(destinations); 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: + * [ + * { + * title: string, + * bold: boolean, + * italic: boolean, + * color: rgb array, + * dest: dest obj, + * items: array of more items like this + * }, + * ... + * ]. + */ getOutline: function() { var promise = new PDFJS.Promise(); var outline = this.pdfInfo.outline; promise.resolve(outline); return promise; }, + /** + * @return {Promise} A promise that is resolved with an {object} that has + * info and metadata properties. Info is an {object} filled with anything + * available in the information dictionary and similarly metadata is a + * {Metadata} object with information from the metadata section of the PDF. + */ getMetadata: function() { var promise = new PDFJS.Promise(); var info = this.pdfInfo.info; @@ -84,30 +135,69 @@ var PDFPageProxy = (function PDFPageProxyClosure() { this.objs = transport.objs; } PDFPageProxy.prototype = { + /** + * @return {number} Page number of the page. First page is 1. + */ get pageNumber() { return this.pageInfo.pageIndex + 1; }, + /** + * @return {number} The number of degrees the page is rotated clockwise. + */ get rotate() { return this.pageInfo.rotate; }, + /** + * @return {object} The reference that points to this page. It has 'num' and + * 'gen' properties. + */ get ref() { return this.pageInfo.ref; }, + /** + * @return {array} An array of the visible portion of the PDF page in the + * user space units - [x1, y1, x2, y2]. + */ get view() { return this.pageInfo.view; }, + /** + * @param {number} scale The desired scale of the viewport. + * @param {number} rotate Degrees to rotate the viewport. If omitted this + * defaults to the page rotation. + * @return {PageViewport} Contains 'width' and 'height' properties along + * with transforms required for rendering. + */ getViewport: function(scale, rotate) { if (arguments.length < 2) rotate = this.rotate; return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); }, + /** + * @return {Promise} A promise that is resolved with an {array} of the + * annotation objects. + */ getAnnotations: function() { + if (this.annotationsPromise) + return this.annotationsPromise; + var promise = new PDFJS.Promise(); - var annotations = this.pageInfo.annotations; - promise.resolve(annotations); + this.annotationsPromise = promise; + this.transport.getAnnotations(this.pageInfo.pageIndex); return promise; }, - render: function(renderContext) { + /** + * Begins the process of rendering a page to the desired context. + * @param {object} params A parameter object that supports: + * { + * canvasContext(required): A 2D context of a DOM Canvas object., + * textLayer(optional): An object that has beginLayout, endLayout, and + * appendText functions. + * }. + * @return {Promise} A promise that is resolved when the page finishes + * rendering. + */ + render: function(params) { var promise = new Promise(); var stats = this.stats; stats.time('Overall'); @@ -132,10 +222,10 @@ var PDFPageProxy = (function PDFPageProxyClosure() { // Once the operatorList and fonts are loaded, do the actual rendering. this.displayReadyPromise.then( function pageDisplayReadyPromise() { - var gfx = new CanvasGraphics(renderContext.canvasContext, - this.objs, renderContext.textLayer); + var gfx = new CanvasGraphics(params.canvasContext, + this.objs, params.textLayer); try { - this.display(gfx, renderContext.viewport, complete); + this.display(gfx, params.viewport, complete); } catch (e) { complete(e); } @@ -147,7 +237,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { return promise; }, - + /** + * For internal use only. + */ startRenderingFromOperatorList: function PDFPageWrapper_startRenderingFromOperatorList(operatorList, fonts) { @@ -168,7 +260,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { } ); }, - + /** + * For internal use only. + */ ensureFonts: function PDFPageWrapper_ensureFonts(fonts, callback) { this.stats.time('Font Loading'); // Convert the font names to the corresponding font obj. @@ -186,7 +280,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { }.bind(this) ); }, - + /** + * For internal use only. + */ display: function PDFPageWrapper_display(gfx, viewport, callback) { var stats = this.stats; stats.time('Rendering'); @@ -209,6 +305,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { gfx.executeOperatorList(operatorList, startIdx, next, stepper); if (startIdx == length) { gfx.endDrawing(); + delete this.operatorList; stats.timeEnd('Rendering'); stats.timeEnd('Overall'); if (callback) callback(); @@ -216,13 +313,18 @@ var PDFPageProxy = (function PDFPageProxyClosure() { } next(); }, - + /** + * Stub for future feature. + */ getTextContent: function() { var promise = new PDFJS.Promise(); var textContent = 'page text'; // not implemented promise.resolve(textContent); return promise; }, + /** + * Stub for future feature. + */ getOperationList: function() { var promise = new PDFJS.Promise(); var operationList = { // not implemented @@ -235,7 +337,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { }; return PDFPageProxy; })(); - +/** + * For internal use only. + */ var WorkerTransport = (function WorkerTransportClosure() { function WorkerTransport(promise) { this.workerReadyPromise = promise; @@ -342,6 +446,12 @@ var WorkerTransport = (function WorkerTransportClosure() { promise.resolve(page); }, this); + messageHandler.on('GetAnnotations', function transportAnnotations(data) { + var annotations = data.annotations; + var promise = this.pageCache[data.pageIndex].annotationsPromise; + promise.resolve(annotations); + }, this); + messageHandler.on('RenderPage', function transportRender(data) { var page = this.pageCache[data.pageIndex]; var depFonts = data.depFonts; @@ -440,6 +550,11 @@ var WorkerTransport = (function WorkerTransportClosure() { this.pagePromises[pageIndex] = promise; this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex }); return promise; + }, + + getAnnotations: function WorkerTransport_getAnnotations(pageIndex) { + this.messageHandler.send('GetAnnotationsRequest', + { pageIndex: pageIndex }); } }; return WorkerTransport; diff --git a/src/evaluator.js b/src/evaluator.js index 350ab20b2..c57e291c0 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -466,7 +466,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { args = []; } else if (obj != null) { assertWellFormed(args.length <= 33, 'Too many arguments'); - args.push(obj); + args.push(obj instanceof Dict ? obj.getAll() : obj); } } @@ -862,7 +862,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { properties.coded = true; var charProcs = dict.get('CharProcs').getAll(); var fontResources = dict.get('Resources') || resources; - properties.resources = fontResources; properties.charProcOperatorList = {}; for (var key in charProcs) { var glyphStream = charProcs[key]; diff --git a/src/fonts.js b/src/fonts.js index 7fdab8fbb..7693f5839 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -766,7 +766,6 @@ var Font = (function FontClosure() { this.name = name; this.coded = properties.coded; this.charProcOperatorList = properties.charProcOperatorList; - this.resources = properties.resources; this.sizes = []; var names = name.split('+'); diff --git a/src/obj.js b/src/obj.js index 200b40a7f..c905a7dc5 100644 --- a/src/obj.js +++ b/src/obj.js @@ -37,51 +37,55 @@ var Dict = (function DictClosure() { // xref is optional function Dict(xref) { // Map should only be used internally, use functions below to access. - this.map = Object.create(null); - this.xref = xref; - } + var map = Object.create(null); + + this.assignXref = function Dict_assingXref(newXref) { + xref = newXref; + }; - Dict.prototype = { // automatically dereferences Ref objects - get: function Dict_get(key1, key2, key3) { + this.get = function Dict_get(key1, key2, key3) { var value; - var xref = this.xref; - if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map || + if (typeof (value = map[key1]) != 'undefined' || key1 in map || typeof key2 == 'undefined') { - return xref ? this.xref.fetchIfRef(value) : value; + return xref ? xref.fetchIfRef(value) : value; } - if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map || + if (typeof (value = map[key2]) != 'undefined' || key2 in map || typeof key3 == 'undefined') { - return xref ? this.xref.fetchIfRef(value) : value; + return xref ? xref.fetchIfRef(value) : value; } - value = this.map[key3] || null; - return xref ? this.xref.fetchIfRef(value) : value; - }, + value = map[key3] || null; + return xref ? xref.fetchIfRef(value) : value; + }; + // no dereferencing - getRaw: function Dict_getRaw(key) { - return this.map[key]; - }, + this.getRaw = function Dict_getRaw(key) { + return map[key]; + }; + // creates new map and dereferences all Refs - getAll: function Dict_getAll() { + this.getAll = function Dict_getAll() { var all = {}; - for (var key in this.map) - all[key] = this.get(key); + for (var key in map) { + var obj = this.get(key); + all[key] = obj instanceof Dict ? obj.getAll() : obj; + } return all; - }, + }; - set: function Dict_set(key, value) { - this.map[key] = value; - }, + this.set = function Dict_set(key, value) { + map[key] = value; + }; - has: function Dict_has(key) { - return key in this.map; - }, + this.has = function Dict_has(key) { + return key in map; + }; - forEach: function Dict_forEach(callback) { - for (var key in this.map) { + this.forEach = function Dict_forEach(callback) { + for (var key in map) { callback(key, this.get(key)); } - } + }; }; return Dict; @@ -299,7 +303,7 @@ var XRef = (function XRefClosure() { this.entries = []; this.xrefstms = {}; var trailerDict = this.readXRef(startXRef); - trailerDict.xref = this; + trailerDict.assignXref(this); this.trailer = trailerDict; // prepare the XRef cache this.cache = []; diff --git a/src/worker.js b/src/worker.js index 5cecc6cf2..25f3f52cd 100644 --- a/src/worker.js +++ b/src/worker.js @@ -100,20 +100,27 @@ var WorkerMessageHandler = { handler.send('GetDoc', {pdfInfo: doc}); }); - handler.on('GetPageRequest', function wphSetupTest(data) { + handler.on('GetPageRequest', function wphSetupGetPage(data) { var pageNumber = data.pageIndex + 1; var pdfPage = pdfModel.getPage(pageNumber); var page = { pageIndex: data.pageIndex, rotate: pdfPage.rotate, ref: pdfPage.ref, - view: pdfPage.view, - annotations: pdfPage.getAnnotations() + view: pdfPage.view }; handler.send('GetPage', {pageInfo: page}); }); - handler.on('RenderPageRequest', function wphSetupPageRequest(data) { + handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) { + var pdfPage = pdfModel.getPage(data.pageIndex + 1); + handler.send('GetAnnotations', { + pageIndex: data.pageIndex, + annotations: pdfPage.getAnnotations() + }); + }); + + handler.on('RenderPageRequest', function wphSetupRenderPage(data) { var pageNum = data.pageIndex + 1; @@ -170,7 +177,6 @@ var WorkerMessageHandler = { fonts[dep] = true; } } - handler.send('RenderPage', { pageIndex: data.pageIndex, operatorList: operatorList, diff --git a/test/test_manifest.json b/test/test_manifest.json index 6a083bdf7..207c41c22 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -29,6 +29,7 @@ "file": "pdfs/pdf.pdf", "md5": "dbdb23c939d2be09b43126c3c56060c7", "link": true, + "pageLimit": 500, "rounds": 1, "type": "load" }, diff --git a/web/viewer.js b/web/viewer.js index 5b43ee7e1..b1876c814 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -1284,7 +1284,6 @@ window.addEventListener('load', function webViewerLoad(evt) { var file = PDFJS.isFirefoxExtension ? window.location.toString() : params.file || kDefaultURL; - PDFView.open(file, 0); if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader || !window.FileList || !window.Blob) { @@ -1316,6 +1315,7 @@ window.addEventListener('load', function webViewerLoad(evt) { var sidebarScrollView = document.getElementById('sidebarScrollView'); sidebarScrollView.addEventListener('scroll', updateThumbViewArea, true); + PDFView.open(file, 0); }, true); /**