From 737ed8417472b9c5084f963de95e3f87779dd7b2 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Mon, 9 Apr 2012 22:20:57 -0700 Subject: [PATCH] Initial API implementation --- src/api.js | 141 +++++++++++++++++++++++++++++++ src/util.js | 17 +++- web/viewer.html | 1 + web/viewer.js | 220 +++++++++++++++++++++++++++--------------------- 4 files changed, 281 insertions(+), 98 deletions(-) create mode 100644 src/api.js diff --git a/src/api.js b/src/api.js new file mode 100644 index 000000000..a8bb5fb65 --- /dev/null +++ b/src/api.js @@ -0,0 +1,141 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +(function pdfApiWrapper() { + function PdfPageWrapper(page) { + this.page = page; + } + PdfPageWrapper.prototype = { + get width() { + return this.page.width; + }, + get height() { + return this.page.height; + }, + get stats() { + return this.page.stats; + }, + get ref() { + return this.page.ref; + }, + get view() { + return this.page.view; + }, + rotatePoint: function(x, y) { + return this.page.rotatePoint(x, y); + }, + getAnnotations: function() { + var promise = new PDFJS.Promise(); + var annotations = this.page.getAnnotations(); + promise.resolve(annotations); + return promise; + }, + render: function(renderContext) { + var promise = new PDFJS.Promise(); + this.page.startRendering(renderContext.canvasContext, + function complete(error) { + if (error) + promise.reject(error); + else + promise.resolve(); + }, + renderContext.textLayer); + return promise; + }, + getTextContent: function() { + var promise = new PDFJS.Promise(); + var textContent = 'page text'; // not implemented + promise.resolve(textContent); + return promise; + }, + getOperationList: function() { + var promise = new PDFJS.Promise(); + var operationList = { // not implemented + dependencyFontsID: null, + operatorList: null + }; + promise.resolve(operationList); + return promise; + } + }; + + function PdfDocumentWrapper(pdf) { + this.pdf = pdf; + } + PdfDocumentWrapper.prototype = { + get numPages() { + return this.pdf.numPages; + }, + get fingerprint() { + return this.pdf.fingerPrint; + }, + getPage: function(number) { + var promise = new PDFJS.Promise(); + var page = this.pdf.getPage(number); + promise.resolve(new PdfPageWrapper(page)); + return promise; + }, + getDestinations: function() { + var promise = new PDFJS.Promise(); + var destinations = this.pdf.catalog.destinations; + promise.resolve(destinations); + return promise; + }, + getOutline: function() { + var promise = new PDFJS.Promise(); + var outline = this.pdf.catalog.documentOutline; + promise.resolve(outline); + return promise; + }, + getMetadata: function() { + var promise = new PDFJS.Promise(); + var info = this.pdf.info; + var metadata = this.pdf.catalog.metadata; + promise.resolve(info, metadata ? new PDFJS.Metadata(metadata) : null); + return promise; + } + }; + + PDFJS.getDocument = function getDocument(source) { + var promise = new PDFJS.Promise(); + if (typeof source === 'string') { + // fetch url + PDFJS.getPdf( + { + url: source, + progress: function getPdfProgress(evt) { + if (evt.lengthComputable) + promise.progress({ + loaded: evt.loaded, + total: evt.total + }); + }, + error: function getPdfError(e) { + promise.reject('Unexpected server response of ' + + e.target.status + '.'); + } + }, + function getPdfLoad(data) { + var pdf = null; + try { + pdf = new PDFJS.PDFDoc(data); + } catch (e) { + promise.reject('An error occurred while reading the PDF.', e); + } + if (pdf) + promise.resolve(new PdfDocumentWrapper(pdf)); + }); + } else { + // assuming the source is array, instantiating directly from it + var pdf = null; + try { + pdf = new PDFJS.PDFDoc(source); + } catch (e) { + promise.reject('An error occurred while reading the PDF.', e); + } + if (pdf) + promise.resolve(new PdfDocumentWrapper(pdf)); + } + return promise; + }; +})(); diff --git a/src/util.js b/src/util.js index de7f3c1d5..30fc799f9 100644 --- a/src/util.js +++ b/src/util.js @@ -275,7 +275,7 @@ function isPDFFunction(v) { * can be set. If any of these happens twice or the data is required before * it was set, an exception is throw. */ -var Promise = (function PromiseClosure() { +var Promise = PDFJS.Promise = (function PromiseClosure() { var EMPTY_PROMISE = {}; /** @@ -297,6 +297,7 @@ var Promise = (function PromiseClosure() { } this.callbacks = []; this.errbacks = []; + this.progressbacks = []; }; /** * Builds a promise that is resolved when all the passed in promises are @@ -312,7 +313,7 @@ var Promise = (function PromiseClosure() { deferred.resolve(results); return deferred; } - for (var i = 0; i < unresolved; ++i) { + for (var i = 0, ii = promises.length; i < ii; ++i) { var promise = promises[i]; promise.then((function(i) { return function(value) { @@ -376,6 +377,13 @@ var Promise = (function PromiseClosure() { } }, + progress: function Promise_progress(data) { + var callbacks = this.progressbacks; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callbacks[i].call(null, data); + } + }, + reject: function Promise_reject(reason) { if (this.isRejected) { error('A Promise can be rejected only once ' + this.name); @@ -393,7 +401,7 @@ var Promise = (function PromiseClosure() { } }, - then: function Promise_then(callback, errback) { + then: function Promise_then(callback, errback, progressback) { if (!callback) { error('Requiring callback' + this.name); } @@ -410,6 +418,9 @@ var Promise = (function PromiseClosure() { if (errback) this.errbacks.push(errback); } + + if (progressback) + this.progressbacks.push(progressback); } }; diff --git a/web/viewer.html b/web/viewer.html index d275f77c1..ef61ce697 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -11,6 +11,7 @@ <!-- PDFJSSCRIPT_INCLUDE_BUILD --> <script type="text/javascript" src="../src/core.js"></script> <!-- PDFJSSCRIPT_REMOVE_CORE --> <script type="text/javascript" src="../src/util.js"></script> <!-- PDFJSSCRIPT_REMOVE_CORE --> + <script type="text/javascript" src="../src/api.js"></script> <!-- PDFJSSCRIPT_REMOVE_CORE --> <script type="text/javascript" src="../src/metadata.js"></script> <!-- PDFJSSCRIPT_REMOVE_CORE --> <script type="text/javascript" src="../src/canvas.js"></script> <!-- PDFJSSCRIPT_REMOVE_CORE --> <script type="text/javascript" src="../src/obj.js"></script> <!-- PDFJSSCRIPT_REMOVE_CORE --> diff --git a/web/viewer.js b/web/viewer.js index 3587c96bd..3ca4f805f 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -318,27 +318,25 @@ var PDFView = { } var self = this; - PDFJS.getPdf( - { - url: url, - progress: function getPdfProgress(evt) { - if (evt.lengthComputable) - self.progress(evt.loaded / evt.total); - }, - error: function getPdfError(e) { - var loadingIndicator = document.getElementById('loading'); - loadingIndicator.textContent = 'Error'; - var moreInfo = { - message: 'Unexpected server response of ' + e.target.status + '.' - }; - self.error('An error occurred while loading the PDF.', moreInfo); - } - }, - function getPdfLoad(data) { - self.loading = true; - self.load(data, scale); + self.loading = true; + PDFJS.getDocument(url).then( + function getDocumentCallback(pdfDocument) { + self.load(pdfDocument, scale); self.loading = false; - }); + }, + function getDocumentError(message, exception) { + var loadingIndicator = document.getElementById('loading'); + loadingIndicator.textContent = 'Error'; + var moreInfo = { + message: message + }; + self.error('An error occurred while loading the PDF.', moreInfo); + self.loading = false; + }, + function getDocumentProgress(progressData) { + self.progress(progressData.loaded / progressData.total); + } + ); }, download: function pdfViewDownload() { @@ -461,7 +459,7 @@ var PDFView = { PDFView.loadingBar.percent = percent; }, - load: function pdfViewLoad(data, scale) { + load: function pdfViewLoad(pdfDocument, scale) { function bindOnAfterDraw(pageView, thumbnailView) { // when page is painted, using the image as thumbnail base pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() { @@ -489,14 +487,8 @@ var PDFView = { while (container.hasChildNodes()) container.removeChild(container.lastChild); - var pdf; - try { - pdf = new PDFJS.PDFDoc(data); - } catch (e) { - this.error('An error occurred while reading the PDF.', e); - } - var pagesCount = pdf.numPages; - var id = pdf.fingerprint; + var pagesCount = pdfDocument.numPages; + var id = pdfDocument.fingerprint; var storedHash = null; document.getElementById('numPages').textContent = pagesCount; document.getElementById('pageNumber').max = pagesCount; @@ -514,30 +506,68 @@ var PDFView = { var pages = this.pages = []; var pagesRefMap = {}; var thumbnails = this.thumbnails = []; - for (var i = 1; i <= pagesCount; i++) { - var page = pdf.getPage(i); - var pageView = new PageView(container, page, i, page.width, page.height, - page.stats, this.navigateTo.bind(this)); - var thumbnailView = new ThumbnailView(sidebar, page, i, - page.width / page.height); - bindOnAfterDraw(pageView, thumbnailView); + var pagePromises = []; + for (var i = 1; i <= pagesCount; i++) + pagePromises.push(pdfDocument.getPage(i)); + var self = this; + var pagesPromise = PDFJS.Promise.all(pagePromises); + pagesPromise.then(function(promisedPages) { + for (var i = 1; i <= pagesCount; i++) { + var page = promisedPages[i - 1]; + var pageView = new PageView(container, page, i, page.width, page.height, + page.stats, self.navigateTo.bind(self)); + var thumbnailView = new ThumbnailView(sidebar, page, i, + page.width / page.height); + bindOnAfterDraw(pageView, thumbnailView); - pages.push(pageView); - thumbnails.push(thumbnailView); - var pageRef = page.ref; - pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; - } + pages.push(pageView); + thumbnails.push(thumbnailView); + var pageRef = page.ref; + pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; + } - this.pagesRefMap = pagesRefMap; - this.destinations = pdf.catalog.destinations; + self.pagesRefMap = pagesRefMap; + }); - if (pdf.catalog.documentOutline) { - this.outline = new DocumentOutlineView(pdf.catalog.documentOutline); - var outlineSwitchButton = document.getElementById('outlineSwitch'); - outlineSwitchButton.removeAttribute('disabled'); - this.switchSidebarView('outline'); - } + var destinationsPromise = pdfDocument.getDestinations(); + destinationsPromise.then(function(destinations) { + self.destinations = destinations; + }); + // outline and initial view depends on destinations and pagesRefMap + PDFJS.Promise.all([pagesPromise, destinationsPromise]).then(function() { + pdfDocument.getOutline().then(function(outline) { + if (!outline) + return; + + self.outline = new DocumentOutlineView(outline); + var outlineSwitchButton = document.getElementById('outlineSwitch'); + outlineSwitchButton.removeAttribute('disabled'); + self.switchSidebarView('outline'); + }); + + self.setInitialView(storedHash, scale); + }); + + pdfDocument.getMetadata().then(function(info, metadata) { + self.documentInfo = info; + self.metadata = metadata; + + var pdfTitle; + if (metadata) { + if (metadata.has('dc:title')) + pdfTitle = metadata.get('dc:title'); + } + + if (!pdfTitle && info && info['Title']) + pdfTitle = info['Title']; + + if (pdfTitle) + document.title = pdfTitle + ' - ' + document.title; + }); + }, + + setInitialView: function pdfViewSetInitialView(storedHash, scale) { // Reset the current scale, as otherwise the page's scale might not get // updated if the zoom level stayed the same. this.currentScale = 0; @@ -558,24 +588,6 @@ var PDFView = { // Setting the default one. this.parseScale(kDefaultScale, true); } - - this.metadata = null; - var metadata = pdf.catalog.metadata; - var info = this.documentInfo = pdf.info; - var pdfTitle; - - if (metadata) { - this.metadata = metadata = new PDFJS.Metadata(metadata); - - if (metadata.has('dc:title')) - pdfTitle = metadata.get('dc:title'); - } - - if (!pdfTitle && info && info['Title']) - pdfTitle = info['Title']; - - if (pdfTitle) - document.title = pdfTitle + ' - ' + document.title; }, setHash: function pdfViewSetHash(hash) { @@ -711,12 +723,12 @@ var PDFView = { } }; -var PageView = function pageView(container, content, id, pageWidth, pageHeight, +var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, stats, navigateTo) { this.id = id; - this.content = content; + this.pdfPage = pdfPage; - var view = this.content.view; + var view = pdfPage.view; this.x = view.x; this.y = view.y; this.width = view.width; @@ -748,7 +760,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, div.appendChild(this.loadingIconDiv); }; - function setupAnnotations(content, scale) { + function setupAnnotations(pdfPage, scale) { function bindLink(link, dest) { link.href = PDFView.getDestinationHash(dest); link.onclick = function pageViewSetupLinksOnclick() { @@ -809,29 +821,30 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, return container; } - var items = content.getAnnotations(); - for (var i = 0; i < items.length; i++) { - var item = items[i]; - switch (item.type) { - case 'Link': - var link = createElementWithStyle('a', item); - link.href = item.url || ''; - if (!item.url) - bindLink(link, ('dest' in item) ? item.dest : null); - div.appendChild(link); - break; - case 'Text': - var comment = createCommentAnnotation(item.name, item); - if (comment) - div.appendChild(comment); - break; + pdfPage.getAnnotations().then(function(items) { + for (var i = 0; i < items.length; i++) { + var item = items[i]; + switch (item.type) { + case 'Link': + var link = createElementWithStyle('a', item); + link.href = item.url || ''; + if (!item.url) + bindLink(link, ('dest' in item) ? item.dest : null); + div.appendChild(link); + break; + case 'Text': + var comment = createCommentAnnotation(item.name, item); + if (comment) + div.appendChild(comment); + break; + } } - } + }); } this.getPagePoint = function pageViewGetPagePoint(x, y) { var scale = PDFView.currentScale; - return this.content.rotatePoint(x / scale, y / scale); + return this.pdfPage.rotatePoint(x / scale, y / scale); }; this.scrollIntoView = function pageViewScrollIntoView(dest) { @@ -879,8 +892,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, } var boundingRect = [ - this.content.rotatePoint(x, y), - this.content.rotatePoint(x + width, y + height) + this.pdfPage.rotatePoint(x, y), + this.pdfPage.rotatePoint(x + width, y + height) ]; if (scale && scale !== PDFView.currentScale) @@ -948,7 +961,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, // Rendering area var self = this; - this.content.startRendering(ctx, function pageViewDrawCallback(error) { + function pageViewDrawCallback(error) { if (self.loadingIconDiv) { div.removeChild(self.loadingIconDiv); delete self.loadingIconDiv; @@ -964,9 +977,22 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, cache.push(self); callback(); - }, textLayer); + } - setupAnnotations(this.content, this.scale); + var renderContext = { + canvasContext: ctx, + textLayer: textLayer + }; + this.pdfPage.render(renderContext).then( + function pdfPageRenderCallback() { + pageViewDrawCallback(null); + }, + function pdfPageRenderError(error) { + pageViewDrawCallback(error); + } + ); + + setupAnnotations(this.pdfPage, this.scale); div.setAttribute('data-loaded', true); }; @@ -1397,7 +1423,11 @@ window.addEventListener('change', function webViewerChange(evt) { for (var i = 0; i < data.length; i++) uint8Array[i] = data.charCodeAt(i); - PDFView.load(uint8Array); + + // TODO using blob instead? + PDFJS.getDocument(uint8Array).then(function(pdfDocument) { + PDFView.load(pdfDocument); + }); }; // Read as a binary string since "readAsArrayBuffer" is not yet