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 01/29] 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 From fbd9fcd8fbfa14688e60c58cf062b55d04c2bfe0 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Tue, 10 Apr 2012 12:17:43 -0700 Subject: [PATCH 02/29] Fix fingerprint name. --- src/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api.js b/src/api.js index a8bb5fb65..220b738a0 100644 --- a/src/api.js +++ b/src/api.js @@ -67,7 +67,7 @@ return this.pdf.numPages; }, get fingerprint() { - return this.pdf.fingerPrint; + return this.pdf.fingerprint; }, getPage: function(number) { var promise = new PDFJS.Promise(); From 47c43b5779451f3011cde8ffb9bb242492315a5a Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Wed, 11 Apr 2012 08:29:44 -0700 Subject: [PATCH 03/29] Removing the rotatePoint, width, height from the API --- src/api.js | 14 +++---- src/canvas.js | 23 ++--------- src/core.js | 81 ++++++--------------------------------- src/util.js | 88 +++++++++++++++++++++++++++++++++++++++++- web/viewer.js | 103 +++++++++++++++++++++++++------------------------- 5 files changed, 160 insertions(+), 149 deletions(-) diff --git a/src/api.js b/src/api.js index a8bb5fb65..d6d0bc5c6 100644 --- a/src/api.js +++ b/src/api.js @@ -6,11 +6,8 @@ this.page = page; } PdfPageWrapper.prototype = { - get width() { - return this.page.width; - }, - get height() { - return this.page.height; + get rotate() { + return this.page.rotate; }, get stats() { return this.page.stats; @@ -21,8 +18,10 @@ get view() { return this.page.view; }, - rotatePoint: function(x, y) { - return this.page.rotatePoint(x, y); + getViewport: function(scale, rotate) { + if (arguments < 2) + rotate = this.rotate; + return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); }, getAnnotations: function() { var promise = new PDFJS.Promise(); @@ -33,6 +32,7 @@ render: function(renderContext) { var promise = new PDFJS.Promise(); this.page.startRendering(renderContext.canvasContext, + renderContext.viewport, function complete(error) { if (error) promise.reject(error); diff --git a/src/canvas.js b/src/canvas.js index 8f29051fd..9d470fbec 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -241,27 +241,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { 'shadingFill': true }, - beginDrawing: function CanvasGraphics_beginDrawing(mediaBox) { - var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; + beginDrawing: function CanvasGraphics_beginDrawing(viewport) { + var transform = viewport.transform; this.ctx.save(); - switch (mediaBox.rotate) { - case 0: - this.ctx.transform(1, 0, 0, -1, 0, ch); - break; - case 90: - this.ctx.transform(0, 1, 1, 0, 0, 0); - break; - case 180: - this.ctx.transform(-1, 0, 0, 1, cw, 0); - break; - case 270: - this.ctx.transform(0, -1, -1, 0, cw, ch); - break; - } - // Scale so that canvas units are the same as PDF user space units - this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); - // Move the media left-top corner to the (0,0) canvas position - this.ctx.translate(-mediaBox.x, -mediaBox.y); + this.ctx.transform.apply(this.ctx, transform); if (this.textLayer) this.textLayer.beginLayout(); diff --git a/src/core.js b/src/core.js index 15cd147e2..f71880852 100644 --- a/src/core.js +++ b/src/core.js @@ -100,18 +100,10 @@ var Page = (function PageClosure() { return shadow(this, 'mediaBox', obj); }, get view() { - var cropBox = this.inheritPageProp('CropBox'); - var view = { - x: 0, - y: 0, - width: this.width, - height: this.height - }; - if (!isArray(cropBox) || cropBox.length !== 4) - return shadow(this, 'view', view); - var mediaBox = this.mediaBox; - var offsetX = mediaBox[0], offsetY = mediaBox[1]; + var cropBox = this.inheritPageProp('CropBox'); + if (!isArray(cropBox) || cropBox.length !== 4) + return shadow(this, 'view', mediaBox); // From the spec, 6th ed., p.963: // "The crop, bleed, trim, and art boxes should not ordinarily @@ -119,42 +111,13 @@ var Page = (function PageClosure() { // effectively reduced to their intersection with the media box." cropBox = Util.intersect(cropBox, mediaBox); if (!cropBox) - return shadow(this, 'view', view); + return shadow(this, 'view', mediaBox); - var tl = this.rotatePoint(cropBox[0] - offsetX, cropBox[1] - offsetY); - var br = this.rotatePoint(cropBox[2] - offsetX, cropBox[3] - offsetY); - view.x = Math.min(tl.x, br.x); - view.y = Math.min(tl.y, br.y); - view.width = Math.abs(tl.x - br.x); - view.height = Math.abs(tl.y - br.y); - - return shadow(this, 'view', view); + return shadow(this, 'view', cropBox); }, get annotations() { return shadow(this, 'annotations', this.inheritPageProp('Annots')); }, - get width() { - var mediaBox = this.mediaBox; - var rotate = this.rotate; - var width; - if (rotate == 0 || rotate == 180) { - width = (mediaBox[2] - mediaBox[0]); - } else { - width = (mediaBox[3] - mediaBox[1]); - } - return shadow(this, 'width', width); - }, - get height() { - var mediaBox = this.mediaBox; - var rotate = this.rotate; - var height; - if (rotate == 0 || rotate == 180) { - height = (mediaBox[3] - mediaBox[1]); - } else { - height = (mediaBox[2] - mediaBox[0]); - } - return shadow(this, 'height', height); - }, get rotate() { var rotate = this.inheritPageProp('Rotate') || 0; // Normalize rotation so it's a multiple of 90 and between 0 and 270 @@ -238,7 +201,7 @@ var Page = (function PageClosure() { ); }, - display: function Page_display(gfx, callback) { + display: function Page_display(gfx, viewport, callback) { var stats = this.stats; stats.time('Rendering'); var xref = this.xref; @@ -248,10 +211,7 @@ var Page = (function PageClosure() { gfx.xref = xref; gfx.res = resources; - gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], - width: this.width, - height: this.height, - rotate: this.rotate }); + gfx.beginDrawing(viewport); var startIdx = 0; var length = this.operatorList.fnArray.length; @@ -276,21 +236,6 @@ var Page = (function PageClosure() { } next(); }, - rotatePoint: function Page_rotatePoint(x, y, reverse) { - var rotate = reverse ? (360 - this.rotate) : this.rotate; - switch (rotate) { - case 180: - return {x: this.width - x, y: y}; - case 90: - return {x: this.width - y, y: this.height - x}; - case 270: - return {x: y, y: x}; - case 360: - case 0: - default: - return {x: x, y: this.height - y}; - } - }, getLinks: function Page_getLinks() { var links = []; var annotations = pageGetAnnotations(); @@ -342,15 +287,10 @@ var Page = (function PageClosure() { if (!isName(subtype)) continue; var rect = annotation.get('Rect'); - var topLeftCorner = this.rotatePoint(rect[0], rect[1]); - var bottomRightCorner = this.rotatePoint(rect[2], rect[3]); var item = {}; item.type = subtype.name; - item.x = Math.min(topLeftCorner.x, bottomRightCorner.x); - item.y = Math.min(topLeftCorner.y, bottomRightCorner.y); - item.width = Math.abs(topLeftCorner.x - bottomRightCorner.x); - item.height = Math.abs(topLeftCorner.y - bottomRightCorner.y); + item.rect = rect; switch (subtype.name) { case 'Link': var a = annotation.get('A'); @@ -434,7 +374,8 @@ var Page = (function PageClosure() { } return items; }, - startRendering: function Page_startRendering(ctx, callback, textLayer) { + startRendering: function Page_startRendering(ctx, viewport, + callback, textLayer) { var stats = this.stats; stats.time('Overall'); // If there is no displayReadyPromise yet, then the operatorList was never @@ -449,7 +390,7 @@ var Page = (function PageClosure() { function pageDisplayReadyPromise() { var gfx = new CanvasGraphics(ctx, this.objs, textLayer); try { - this.display(gfx, callback); + this.display(gfx, viewport, callback); } catch (e) { if (callback) callback(e); diff --git a/src/util.js b/src/util.js index 30fc799f9..9989d9c74 100644 --- a/src/util.js +++ b/src/util.js @@ -97,6 +97,19 @@ var Util = (function UtilClosure() { return [xt, yt]; }; + Util.applyInverseTransform = function Util_applyTransform(p, m) { + var d = m[0] * m[3] - m[1] * m[2]; + var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d; + var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d; + return [xt, yt]; + }; + + Util.inverseTransform = function Util_inverseTransform(m) { + var d = m[0] * m[3] - m[1] * m[2]; + return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, + (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d]; + }; + // Apply a generic 3d matrix M on a 3-vector v: // | a b c | | X | // | d e f | x | Y | @@ -165,7 +178,7 @@ var Util = (function UtilClosure() { } return result; - } + }; Util.sign = function Util_sign(num) { return num < 0 ? -1 : 1; @@ -174,6 +187,79 @@ var Util = (function UtilClosure() { return Util; })(); +var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { + function PageViewport(viewBox, scale, rotate, offsetX, offsetY) { + // creating transform to convert pdf coordinate system to the normal + // canvas like coordinates taking in account scale and rotation + var centerX = (viewBox[2] + viewBox[0]) / 2; + var centerY = (viewBox[3] + viewBox[1]) / 2; + var rotateA, rotateB, rotateC, rotateD; + switch (rotate) { + case -180: + case 180: + rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1; + break; + case -270: + case 90: + rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0; + break; + case -90: + case 270: + rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0; + break; + case 360: + case 0: + default: + rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1; + break; + } + var offsetCanvasX, offsetCanvasY; + var width, height; + if (rotateA == 0) { + offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; + offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; + width = Math.abs(viewBox[3] - viewBox[1]) * scale; + height = Math.abs(viewBox[2] - viewBox[0]) * scale; + } else { + offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; + offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; + width = Math.abs(viewBox[2] - viewBox[0]) * scale; + height = Math.abs(viewBox[3] - viewBox[1]) * scale; + } + // creating transform for the following operations: + // translate(-centerX, -centerY), rotate and flip vertically, + // scale, and translate(offsetCanvasX, offsetCanvasY) + this.transform = [ + rotateA * scale, + rotateB * scale, + rotateC * scale, + rotateD * scale, + offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, + offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY + ]; + + this.offsetX = offsetX; + this.offsetY = offsetY; + this.width = width; + this.height = height; + } + PageViewport.prototype = { + convertPointToViewport: function PageViewport_convertPointToViewport(x, y) { + return Util.applyTransform([x, y], this.transform); + }, + convertRectangleToViewport: + function PageViewport_convertRectangeToViewport(rect) { + var tl = Util.applyTransform([rect[0], rect[1]], this.transform); + var br = Util.applyTransform([rect[2], rect[3]], this.transform); + return [tl[0], tl[1], br[0], br[1]]; + }, + convertViewportToPoint: function PageViewport_convertViewportToPoint(x, y) { + return Util.applyInverseTransform([x, y], this.transform); + } + }; + return PageViewport; +})(); + var PDFStringTranslateTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, diff --git a/web/viewer.js b/web/viewer.js index 3ca4f805f..9fe9a1714 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -358,6 +358,8 @@ var PDFView = { var destRef = dest[0]; var pageNumber = destRef instanceof Object ? this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1); + if (pageNumber > this.pages.length) + pageNumber = this.pages.length; if (pageNumber) { this.page = pageNumber; var currentPage = this.pages[pageNumber - 1]; @@ -514,10 +516,9 @@ var PDFView = { 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, + var pageView = new PageView(container, page, i, scale, page.stats, self.navigateTo.bind(self)); - var thumbnailView = new ThumbnailView(sidebar, page, i, - page.width / page.height); + var thumbnailView = new ThumbnailView(sidebar, page, i); bindOnAfterDraw(pageView, thumbnailView); pages.push(pageView); @@ -664,7 +665,7 @@ var PDFView = { var windowTop = window.pageYOffset; for (var i = 1; i <= pages.length; ++i) { var page = pages[i - 1]; - var pageHeight = page.height * page.scale + kBottomMargin; + var pageHeight = page.height + kBottomMargin; if (currentHeight + pageHeight > windowTop) break; @@ -723,16 +724,13 @@ var PDFView = { } }; -var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, +var PageView = function pageView(container, pdfPage, id, scale, stats, navigateTo) { this.id = id; this.pdfPage = pdfPage; - var view = pdfPage.view; - this.x = view.x; - this.y = view.y; - this.width = view.width; - this.height = view.height; + this.scale = scale || 1.0; + this.viewport = this.pdfPage.getViewport(scale); var anchor = document.createElement('a'); anchor.name = '' + this.id; @@ -746,8 +744,13 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, this.update = function pageViewUpdate(scale) { this.scale = scale || this.scale; - div.style.width = (this.width * this.scale) + 'px'; - div.style.height = (this.height * this.scale) + 'px'; + var viewport = this.pdfPage.getViewport(this.scale); + + this.viewport = viewport; + this.width = viewport.width; + this.height = viewport.height; + div.style.width = viewport.width + 'px'; + div.style.height = viewport.height + 'px'; while (div.hasChildNodes()) div.removeChild(div.lastChild); @@ -760,7 +763,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, div.appendChild(this.loadingIconDiv); }; - function setupAnnotations(pdfPage, scale) { + function setupAnnotations(pdfPage, viewport) { function bindLink(link, dest) { link.href = PDFView.getDestinationHash(dest); link.onclick = function pageViewSetupLinksOnclick() { @@ -770,11 +773,13 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, }; } function createElementWithStyle(tagName, item) { + var rect = viewport.convertRectangleToViewport(item.rect); + rect = Util.normalizeRect(rect); var element = document.createElement(tagName); - element.style.left = (Math.floor(item.x - view.x) * scale) + 'px'; - element.style.top = (Math.floor(item.y - view.y) * scale) + 'px'; - element.style.width = Math.ceil(item.width * scale) + 'px'; - element.style.height = Math.ceil(item.height * scale) + 'px'; + element.style.left = Math.floor(rect[0]) + 'px'; + element.style.top = Math.floor(rect[1]) + 'px'; + element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'; + element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'; return element; } function createCommentAnnotation(type, item) { @@ -844,7 +849,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, this.getPagePoint = function pageViewGetPagePoint(x, y) { var scale = PDFView.currentScale; - return this.pdfPage.rotatePoint(x / scale, y / scale); + return this.viewport.convertPointToViewport(x, y); }; this.scrollIntoView = function pageViewScrollIntoView(dest) { @@ -892,8 +897,8 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, } var boundingRect = [ - this.pdfPage.rotatePoint(x, y), - this.pdfPage.rotatePoint(x + width, y + height) + this.viewport.convertPointToViewport(x, y), + this.viewport.convertPointToViewport(x + width, y + height) ]; if (scale && scale !== PDFView.currentScale) @@ -904,18 +909,18 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, setTimeout(function pageViewScrollIntoViewRelayout() { // letting page to re-layout before scrolling var scale = PDFView.currentScale; - var x = Math.min(boundingRect[0].x, boundingRect[1].x); - var y = Math.min(boundingRect[0].y, boundingRect[1].y); - var width = Math.abs(boundingRect[0].x - boundingRect[1].x); - var height = Math.abs(boundingRect[0].y - boundingRect[1].y); + var x = Math.min(boundingRect[0][0], boundingRect[1][0]); + var y = Math.min(boundingRect[0][1], boundingRect[1][1]); + var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]); + var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]); // using temporary div to scroll it into view var tempDiv = document.createElement('div'); tempDiv.style.position = 'absolute'; - tempDiv.style.left = Math.floor(x * scale) + 'px'; - tempDiv.style.top = Math.floor(y * scale) + 'px'; - tempDiv.style.width = Math.ceil(width * scale) + 'px'; - tempDiv.style.height = Math.ceil(height * scale) + 'px'; + tempDiv.style.left = Math.floor(x) + 'px'; + tempDiv.style.top = Math.floor(y) + 'px'; + tempDiv.style.width = Math.ceil(width) + 'px'; + tempDiv.style.height = Math.ceil(height) + 'px'; div.appendChild(tempDiv); tempDiv.scrollIntoView(true); div.removeChild(tempDiv); @@ -947,16 +952,15 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, } var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null; - var scale = this.scale; - canvas.width = pageWidth * scale; - canvas.height = pageHeight * scale; + var scale = this.scale, viewport = this.viewport; + canvas.width = viewport.width; + canvas.height = viewport.height; var ctx = canvas.getContext('2d'); ctx.save(); ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); - ctx.translate(-this.x * scale, -this.y * scale); // Rendering area @@ -981,6 +985,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, var renderContext = { canvasContext: ctx, + viewport: this.viewport, textLayer: textLayer }; this.pdfPage.render(renderContext).then( @@ -992,7 +997,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, } ); - setupAnnotations(this.pdfPage, this.scale); + setupAnnotations(this.pdfPage, this.viewport); div.setAttribute('data-loaded', true); }; @@ -1004,7 +1009,7 @@ var PageView = function pageView(container, pdfPage, id, pageWidth, pageHeight, }; }; -var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { +var ThumbnailView = function thumbnailView(container, pdfPage, id) { var anchor = document.createElement('a'); anchor.href = PDFView.getAnchorUrl('#page=' + id); anchor.onclick = function stopNivigation() { @@ -1012,9 +1017,10 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { return false; }; - var view = page.view; - this.width = view.width; - this.height = view.height; + var viewport = pdfPage.getViewport(1); + var pageWidth = viewport.width; + var pageHeight = viewport.height; + var pageRatio = pageWidth / pageHeight; this.id = id; var maxThumbSize = 134; @@ -1022,8 +1028,8 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { maxThumbSize * pageRatio; var canvasHeight = pageRatio <= 1 ? maxThumbSize : maxThumbSize / pageRatio; - var scaleX = this.scaleX = (canvasWidth / this.width); - var scaleY = this.scaleY = (canvasHeight / this.height); + var scaleX = this.scaleX = (canvasWidth / pageWidth); + var scaleY = this.scaleY = (canvasHeight / pageHeight); var div = document.createElement('div'); div.id = 'thumbnailContainer' + id; @@ -1048,15 +1054,8 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { var ctx = canvas.getContext('2d'); ctx.save(); ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.restore(); - - var view = page.view; - ctx.translate(-view.x * scaleX, -view.y * scaleY); - div.style.width = (view.width * scaleX) + 'px'; - div.style.height = (view.height * scaleY) + 'px'; - div.style.lineHeight = (view.height * scaleY) + 'px'; - return ctx; } @@ -1071,9 +1070,11 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { } var ctx = getPageDrawContext(); - page.startRendering(ctx, function thumbnailViewDrawStartRendering() { - callback(); - }); + var drawViewport = pdfPage.getViewport(scaleX); + page.startRendering(ctx, drawViewport, + function thumbnailViewDrawStartRendering() { + callback(); + }); this.hasImage = true; }; @@ -1359,7 +1360,7 @@ function updateViewarea() { var currentPage = PDFView.pages[pageNumber - 1]; var topLeft = currentPage.getPagePoint(window.pageXOffset, window.pageYOffset - firstPage.y - kViewerTopMargin); - pdfOpenParams += ',' + Math.round(topLeft.x) + ',' + Math.round(topLeft.y); + pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]); var store = PDFView.store; store.set('exists', true); From 2f4423cffb8efc2fdddc3bcbfd250627e49dcd34 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Wed, 11 Apr 2012 09:42:41 -0700 Subject: [PATCH 04/29] Fixing zoom and rotate issues --- src/api.js | 2 +- web/viewer.js | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/api.js b/src/api.js index 3d97f53c9..cccd4b152 100644 --- a/src/api.js +++ b/src/api.js @@ -19,7 +19,7 @@ return this.page.view; }, getViewport: function(scale, rotate) { - if (arguments < 2) + if (arguments.length < 2) rotate = this.rotate; return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); }, diff --git a/web/viewer.js b/web/viewer.js index 9fe9a1714..3c7279fb4 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -747,8 +747,6 @@ var PageView = function pageView(container, pdfPage, id, scale, var viewport = this.pdfPage.getViewport(this.scale); this.viewport = viewport; - this.width = viewport.width; - this.height = viewport.height; div.style.width = viewport.width + 'px'; div.style.height = viewport.height + 'px'; @@ -763,6 +761,20 @@ var PageView = function pageView(container, pdfPage, id, scale, div.appendChild(this.loadingIconDiv); }; + Object.defineProperty(this, 'width', { + get: function PageView_getWidth() { + return this.viewport.width; + }, + enumerable: true + }); + + Object.defineProperty(this, 'height', { + get: function PageView_getHeight() { + return this.viewport.height; + }, + enumerable: true + }); + function setupAnnotations(pdfPage, viewport) { function bindLink(link, dest) { link.href = PDFView.getDestinationHash(dest); From d61c4f07f8c3d3869f3932fe1dd42c1993e1a087 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Wed, 11 Apr 2012 10:18:29 -0700 Subject: [PATCH 05/29] Initial view bug and rename viewport function --- src/util.js | 8 ++++---- web/viewer.js | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/util.js b/src/util.js index 9989d9c74..390b08427 100644 --- a/src/util.js +++ b/src/util.js @@ -244,16 +244,16 @@ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { this.height = height; } PageViewport.prototype = { - convertPointToViewport: function PageViewport_convertPointToViewport(x, y) { + convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) { return Util.applyTransform([x, y], this.transform); }, - convertRectangleToViewport: - function PageViewport_convertRectangeToViewport(rect) { + convertToViewportRectangle: + function PageViewport_convertToViewportRectangle(rect) { var tl = Util.applyTransform([rect[0], rect[1]], this.transform); var br = Util.applyTransform([rect[2], rect[3]], this.transform); return [tl[0], tl[1], br[0], br[1]]; }, - convertViewportToPoint: function PageViewport_convertViewportToPoint(x, y) { + convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) { return Util.applyInverseTransform([x, y], this.transform); } }; diff --git a/web/viewer.js b/web/viewer.js index 3c7279fb4..51d4df711 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -730,7 +730,7 @@ var PageView = function pageView(container, pdfPage, id, scale, this.pdfPage = pdfPage; this.scale = scale || 1.0; - this.viewport = this.pdfPage.getViewport(scale); + this.viewport = this.pdfPage.getViewport(this.scale); var anchor = document.createElement('a'); anchor.name = '' + this.id; @@ -785,7 +785,7 @@ var PageView = function pageView(container, pdfPage, id, scale, }; } function createElementWithStyle(tagName, item) { - var rect = viewport.convertRectangleToViewport(item.rect); + var rect = viewport.convertToViewportRectangle(item.rect); rect = Util.normalizeRect(rect); var element = document.createElement(tagName); element.style.left = Math.floor(rect[0]) + 'px'; @@ -861,7 +861,7 @@ var PageView = function pageView(container, pdfPage, id, scale, this.getPagePoint = function pageViewGetPagePoint(x, y) { var scale = PDFView.currentScale; - return this.viewport.convertPointToViewport(x, y); + return this.viewport.convertToPdfPoint(x, y); }; this.scrollIntoView = function pageViewScrollIntoView(dest) { @@ -909,8 +909,8 @@ var PageView = function pageView(container, pdfPage, id, scale, } var boundingRect = [ - this.viewport.convertPointToViewport(x, y), - this.viewport.convertPointToViewport(x + width, y + height) + this.viewport.convertToViewportPoint(x, y), + this.viewport.convertToViewportPoint(x + width, y + height) ]; if (scale && scale !== PDFView.currentScale) @@ -1378,8 +1378,8 @@ function updateViewarea() { store.set('exists', true); store.set('page', pageNumber); store.set('zoom', normalizedScaleValue); - store.set('scrollLeft', Math.round(topLeft.x)); - store.set('scrollTop', Math.round(topLeft.y)); + store.set('scrollLeft', Math.round(topLeft[0])); + store.set('scrollTop', Math.round(topLeft[1])); var href = PDFView.getAnchorUrl(pdfOpenParams); document.getElementById('viewBookmark').href = href; } From 73cab9c30220711e7204a62c92f49f76ae4242de Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Wed, 11 Apr 2012 15:52:15 -0700 Subject: [PATCH 06/29] Initial API refectoring --- src/api.js | 409 +++++++++++++++++++++++++++++++++++++++++++++----- src/core.js | 344 +----------------------------------------- src/worker.js | 21 +++ 3 files changed, 391 insertions(+), 383 deletions(-) diff --git a/src/api.js b/src/api.js index cccd4b152..c8f908e7b 100644 --- a/src/api.js +++ b/src/api.js @@ -1,22 +1,250 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* + PDFDoc.prototype = { + destroy: function PDFDoc_destroy() { + if (this.worker) + this.worker.terminate(); + + if (this.fontWorker) + this.fontWorker.terminate(); + + for (var n in this.pageCache) + delete this.pageCache[n]; + + delete this.data; + delete this.stream; + delete this.pdf; + delete this.catalog; + } + }; +*/ + (function pdfApiWrapper() { - function PdfPageWrapper(page) { - this.page = page; + function WorkerTransport(promise) { + this.workerReadyPromise = promise; + this.objs = new PDFObjects(); + + this.pageCache = []; + this.pagePromises = []; + this.fontsLoading = {}; + + // If worker support isn't disabled explicit and the browser has worker + // support, create a new web worker and test if it/the browser fullfills + // all requirements to run parts of pdf.js in a web worker. + // Right now, the requirement is, that an Uint8Array is still an Uint8Array + // as it arrives on the worker. Chrome added this with version 15. + if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { + var workerSrc = PDFJS.workerSrc; + if (typeof workerSrc === 'undefined') { + error('No PDFJS.workerSrc specified'); + } + + try { + var worker; + if (PDFJS.isFirefoxExtension) { + // The firefox extension can't load the worker from the resource:// + // url so we have to inline the script and then use the blob loader. + var bb = new MozBlobBuilder(); + bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent); + var blobUrl = window.URL.createObjectURL(bb.getBlob()); + worker = new Worker(blobUrl); + } else { + // Some versions of FF can't create a worker on localhost, see: + // https://bugzilla.mozilla.org/show_bug.cgi?id=683280 + worker = new Worker(workerSrc); + } + + var messageHandler = new MessageHandler('main', worker); + + messageHandler.on('test', function pdfDocTest(supportTypedArray) { + if (supportTypedArray) { + this.worker = worker; + this.setupMessageHandler(messageHandler); + } else { + globalScope.PDFJS.disableWorker = true; + this.setupFakeWorker(); + } + }.bind(this)); + + var testObj = new Uint8Array(1); + // Some versions of Opera throw a DATA_CLONE_ERR on + // serializing the typed array. + messageHandler.send('test', testObj); + return; + } catch (e) { + warn('The worker has been disabled.'); + } + } + // Either workers are disabled, not supported or have thrown an exception. + // Thus, we fallback to a faked worker. + globalScope.PDFJS.disableWorker = true; + this.setupFakeWorker(); + } + WorkerTransport.prototype = { + setupFakeWorker: function WorkerTransport_setupFakeWorker() { + // If we don't use a worker, just post/sendMessage to the main thread. + var fakeWorker = { + postMessage: function WorkerTransport_postMessage(obj) { + fakeWorker.onmessage({data: obj}); + }, + terminate: function WorkerTransport_terminate() {} + }; + + var messageHandler = new MessageHandler('main', fakeWorker); + this.setupMessageHandler(messageHandler); + + // If the main thread is our worker, setup the handling for the messages + // the main thread sends to it self. + WorkerMessageHandler.setup(messageHandler); + }, + + setupMessageHandler: function WorkerTransport_setupMessageHandler(messageHandler) { + this.messageHandler = messageHandler; + + messageHandler.on('doc', function pdfDocPage(data) { + var pdfInfo = data.pdfInfo; + var pdfDocument = new PdfDocumentWrapper(pdfInfo, this); + this.pdfDocument = pdfDocument; + this.workerReadyPromise.resolve(pdfDocument) + }, this); + + messageHandler.on('getpage', function pdfDocPage(data) { + var pageInfo = data.pageInfo; + var page = new PdfPageWrapper(pageInfo, transport); + this.pageCache[pageInfo.pageNumber] = pageInfo; + var promises = this.pagePromises[pageInfo.pageNumber]; + delete this.pagePromises[pageInfo.pageNumber]; + for (var i = 0, ii = promises.length; i < ii; ++i) + promises.resolve(page); + }, this); + + messageHandler.on('page', function pdfDocPage(data) { + var pageNum = data.pageNum; + var page = this.pageCache[pageNum]; + var depFonts = data.depFonts; + + page.stats.timeEnd('Page Request'); + page.startRenderingFromOperatorList(data.operatorList, depFonts); + }, this); + + messageHandler.on('obj', function pdfDocObj(data) { + var id = data[0]; + var type = data[1]; + + switch (type) { + case 'JpegStream': + var imageData = data[2]; + loadJpegStream(id, imageData, this.objs); + break; + case 'Image': + var imageData = data[2]; + this.objs.resolve(id, imageData); + break; + case 'Font': + var name = data[2]; + var file = data[3]; + var properties = data[4]; + + if (file) { + // Rewrap the ArrayBuffer in a stream. + var fontFileDict = new Dict(); + file = new Stream(file, 0, file.length, fontFileDict); + } + + // At this point, only the font object is created but the font is + // not yet attached to the DOM. This is done in `FontLoader.bind`. + var font = new Font(name, file, properties); + this.objs.resolve(id, font); + break; + default: + error('Got unkown object type ' + type); + } + }, this); + + messageHandler.on('page_error', function pdfDocError(data) { + var page = this.pageCache[data.pageNum]; + if (page.displayReadyPromise) + page.displayReadyPromise.reject(data.error); + else + error(data.error); + }, this); + + messageHandler.on('jpeg_decode', function(data, promise) { + var imageData = data[0]; + var components = data[1]; + if (components != 3 && components != 1) + error('Only 3 component or 1 component can be returned'); + + var img = new Image(); + img.onload = (function messageHandler_onloadClosure() { + var width = img.width; + var height = img.height; + var size = width * height; + var rgbaLength = size * 4; + var buf = new Uint8Array(size * components); + var tmpCanvas = createScratchCanvas(width, height); + var tmpCtx = tmpCanvas.getContext('2d'); + tmpCtx.drawImage(img, 0, 0); + var data = tmpCtx.getImageData(0, 0, width, height).data; + + if (components == 3) { + for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) { + buf[j] = data[i]; + buf[j + 1] = data[i + 1]; + buf[j + 2] = data[i + 2]; + } + } else if (components == 1) { + for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) { + buf[j] = data[i]; + } + } + promise.resolve({ data: buf, width: width, height: height}); + }).bind(this); + var src = 'data:image/jpeg;base64,' + window.btoa(imageData); + img.src = src; + }); + }, + + sendData: function WorkerTransport_sendData(data) { + this.messageHandler.send('doc', data); + }, + + getPage: function WorkerTransport_getPage(n, promise) { + if (this.pageCache[n]) { + promise.resolve(pageCache[n]); + return; + } + if (n in this.pagePromises) { + this.pagePromises[n].push(promise); + return; + } + this.pagePromises[n] = [promise]; + this.messageHandler.send('getpage', {page: n}); + } + }; + function PdfPageWrapper(page, transport) { + this.pageInfo = pageInfo; + this.transport = transport; + this.stats = new StatTimer(); + this.objs = transport.objs; } PdfPageWrapper.prototype = { + get pageNumber() { + return this.pageInfo.pageNumber; + }, get rotate() { - return this.page.rotate; + return this.pageInfo.rotate; }, get stats() { - return this.page.stats; + return this.stats; }, get ref() { - return this.page.ref; + return this.pageInfo.ref; }, get view() { - return this.page.view; + return this.pageInfo.view; }, getViewport: function(scale, rotate) { if (arguments.length < 2) @@ -25,23 +253,133 @@ }, getAnnotations: function() { var promise = new PDFJS.Promise(); - var annotations = this.page.getAnnotations(); + var annotations = this.pageInfo.annotations; promise.resolve(annotations); return promise; }, render: function(renderContext) { - var promise = new PDFJS.Promise(); - this.page.startRendering(renderContext.canvasContext, - renderContext.viewport, - function complete(error) { + var promise; + var stats = this.stats; + stats.time('Overall'); + // If there is no displayReadyPromise yet, then the operatorList was never + // requested before. Make the request and create the promise. + if (!this.displayReadyPromise) { + this.displayReadyPromise = promise = new Promise(); + + this.stats.time('Page Request'); + this.messageHandler.send('page_request', this.pageNumber + 1); + } else + promise = this.displayReadyPromise; + + var callback = (function complete(error) { if (error) promise.reject(error); else promise.resolve(); - }, - renderContext.textLayer); + }); + + // 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); + try { + this.display(gfx, renderContext.viewport, callback); + } catch (e) { + if (callback) + callback(e); + else + error(e); + } + }.bind(this), + function pageDisplayReadPromiseError(reason) { + if (callback) + callback(reason); + else + error(reason); + } + ); + return promise; }, + + startRenderingFromOperatorList: + function Page_startRenderingFromOperatorList(operatorList, fonts) { + var self = this; + this.operatorList = operatorList; + + var displayContinuation = function pageDisplayContinuation() { + // Always defer call to display() to work around bug in + // Firefox error reporting from XHR callbacks. + setTimeout(function pageSetTimeout() { + self.displayReadyPromise.resolve(); + }); + }; + + this.ensureFonts(fonts, + function pageStartRenderingFromOperatorListEnsureFonts() { + displayContinuation(); + } + ); + }, + + ensureFonts: function Page_ensureFonts(fonts, callback) { + this.stats.time('Font Loading'); + // Convert the font names to the corresponding font obj. + for (var i = 0, ii = fonts.length; i < ii; i++) { + fonts[i] = this.objs.objs[fonts[i]].data; + } + + // Load all the fonts + FontLoader.bind( + fonts, + function pageEnsureFontsFontObjs(fontObjs) { + this.stats.timeEnd('Font Loading'); + + callback.call(this); + }.bind(this) + ); + }, + + display: function Page_display(gfx, viewport, callback) { + var stats = this.stats; + stats.time('Rendering'); + +/* REMOVE ??? */ + var xref = this.xref; + var resources = this.resources; + assertWellFormed(isDict(resources), 'invalid page resources'); + + gfx.xref = xref; + gfx.res = resources; +/* REMOVE END */ + + gfx.beginDrawing(viewport); + + var startIdx = 0; + var length = this.operatorList.fnArray.length; + var operatorList = this.operatorList; + var stepper = null; + if (PDFJS.pdfBug && StepperManager.enabled) { + stepper = StepperManager.create(this.pageNumber); + stepper.init(operatorList); + stepper.nextBreakPoint = stepper.getNextBreakPoint(); + } + + var self = this; + function next() { + startIdx = + gfx.executeOperatorList(operatorList, startIdx, next, stepper); + if (startIdx == length) { + gfx.endDrawing(); + stats.timeEnd('Rendering'); + stats.timeEnd('Overall'); + if (callback) callback(); + } + } + next(); + }, + getTextContent: function() { var promise = new PDFJS.Promise(); var textContent = 'page text'; // not implemented @@ -59,45 +397,49 @@ } }; - function PdfDocumentWrapper(pdf) { - this.pdf = pdf; + function PdfDocumentWrapper(pdfInfo, transport) { + this.pdfInfo = pdfInfo; + this.transport = transport; } PdfDocumentWrapper.prototype = { get numPages() { - return this.pdf.numPages; + return this.pdfInfo.numPages; }, get fingerprint() { - return this.pdf.fingerprint; + return this.pdfInfo.fingerprint; }, getPage: function(number) { var promise = new PDFJS.Promise(); - var page = this.pdf.getPage(number); - promise.resolve(new PdfPageWrapper(page)); + this.transport.getPage(number, promise); return promise; }, getDestinations: function() { var promise = new PDFJS.Promise(); - var destinations = this.pdf.catalog.destinations; + var destinations = this.pdfInfo.destinations; promise.resolve(destinations); return promise; }, getOutline: function() { var promise = new PDFJS.Promise(); - var outline = this.pdf.catalog.documentOutline; + var outline = this.pdfInfo.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); + var info = this.pdfInfo.info; + var metadata = this.pdfInfo.metadata; + promise.resolve({ + info: info, + metadata: metadata ? new PDFJS.Metadata(metadata) : null + }); return promise; } }; PDFJS.getDocument = function getDocument(source) { var promise = new PDFJS.Promise(); + var transport = new WorkerTransport(promise); if (typeof source === 'string') { // fetch url PDFJS.getPdf( @@ -116,26 +458,13 @@ } }, 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)); + transport.sendData(data); }); } 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)); + transport.sendData(source); } return promise; }; })(); + diff --git a/src/core.js b/src/core.js index f71880852..1aa560a46 100644 --- a/src/core.js +++ b/src/core.js @@ -133,26 +133,6 @@ var Page = (function PageClosure() { return shadow(this, 'rotate', rotate); }, - startRenderingFromOperatorList: - function Page_startRenderingFromOperatorList(operatorList, fonts) { - var self = this; - this.operatorList = operatorList; - - var displayContinuation = function pageDisplayContinuation() { - // Always defer call to display() to work around bug in - // Firefox error reporting from XHR callbacks. - setTimeout(function pageSetTimeout() { - self.displayReadyPromise.resolve(); - }); - }; - - this.ensureFonts(fonts, - function pageStartRenderingFromOperatorListEnsureFonts() { - displayContinuation(); - } - ); - }, - getOperatorList: function Page_getOperatorList(handler, dependency) { if (this.operatorList) { // content was compiled @@ -183,59 +163,6 @@ var Page = (function PageClosure() { return this.operatorList; }, - ensureFonts: function Page_ensureFonts(fonts, callback) { - this.stats.time('Font Loading'); - // Convert the font names to the corresponding font obj. - for (var i = 0, ii = fonts.length; i < ii; i++) { - fonts[i] = this.objs.objs[fonts[i]].data; - } - - // Load all the fonts - FontLoader.bind( - fonts, - function pageEnsureFontsFontObjs(fontObjs) { - this.stats.timeEnd('Font Loading'); - - callback.call(this); - }.bind(this) - ); - }, - - display: function Page_display(gfx, viewport, callback) { - var stats = this.stats; - stats.time('Rendering'); - var xref = this.xref; - var resources = this.resources; - var mediaBox = this.mediaBox; - assertWellFormed(isDict(resources), 'invalid page resources'); - - gfx.xref = xref; - gfx.res = resources; - gfx.beginDrawing(viewport); - - var startIdx = 0; - var length = this.operatorList.fnArray.length; - var operatorList = this.operatorList; - var stepper = null; - if (PDFJS.pdfBug && StepperManager.enabled) { - stepper = StepperManager.create(this.pageNumber); - stepper.init(operatorList); - stepper.nextBreakPoint = stepper.getNextBreakPoint(); - } - - var self = this; - function next() { - startIdx = - gfx.executeOperatorList(operatorList, startIdx, next, stepper); - if (startIdx == length) { - gfx.endDrawing(); - stats.timeEnd('Rendering'); - stats.timeEnd('Overall'); - if (callback) callback(); - } - } - next(); - }, getLinks: function Page_getLinks() { var links = []; var annotations = pageGetAnnotations(); @@ -376,35 +303,7 @@ var Page = (function PageClosure() { }, startRendering: function Page_startRendering(ctx, viewport, callback, textLayer) { - var stats = this.stats; - stats.time('Overall'); - // If there is no displayReadyPromise yet, then the operatorList was never - // requested before. Make the request and create the promise. - if (!this.displayReadyPromise) { - this.pdf.startRendering(this); - this.displayReadyPromise = new Promise(); - } - - // Once the operatorList and fonts are loaded, do the actual rendering. - this.displayReadyPromise.then( - function pageDisplayReadyPromise() { - var gfx = new CanvasGraphics(ctx, this.objs, textLayer); - try { - this.display(gfx, viewport, callback); - } catch (e) { - if (callback) - callback(e); - else - error(e); - } - }.bind(this), - function pageDisplayReadPromiseError(reason) { - if (callback) - callback(reason); - else - error(reason); - } - ); +/// DELETE } }; @@ -578,244 +477,3 @@ var PDFDocModel = (function PDFDocModelClosure() { return PDFDocModel; })(); - -var PDFDoc = (function PDFDocClosure() { - function PDFDoc(arg, callback) { - var stream = null; - var data = null; - - if (isStream(arg)) { - stream = arg; - data = arg.bytes; - } else if (isArrayBuffer(arg)) { - stream = new Stream(arg); - data = arg; - } else { - error('PDFDoc: Unknown argument type'); - } - - this.data = data; - this.stream = stream; - this.pdfModel = new PDFDocModel(stream); - this.fingerprint = this.pdfModel.getFingerprint(); - this.info = this.pdfModel.getDocumentInfo(); - this.catalog = this.pdfModel.catalog; - this.objs = new PDFObjects(); - - this.pageCache = []; - this.fontsLoading = {}; - this.workerReadyPromise = new Promise('workerReady'); - - // If worker support isn't disabled explicit and the browser has worker - // support, create a new web worker and test if it/the browser fullfills - // all requirements to run parts of pdf.js in a web worker. - // Right now, the requirement is, that an Uint8Array is still an Uint8Array - // as it arrives on the worker. Chrome added this with version 15. - if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { - var workerSrc = PDFJS.workerSrc; - if (typeof workerSrc === 'undefined') { - error('No PDFJS.workerSrc specified'); - } - - try { - var worker; - if (PDFJS.isFirefoxExtension) { - // The firefox extension can't load the worker from the resource:// - // url so we have to inline the script and then use the blob loader. - var bb = new MozBlobBuilder(); - bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent); - var blobUrl = window.URL.createObjectURL(bb.getBlob()); - worker = new Worker(blobUrl); - } else { - // Some versions of FF can't create a worker on localhost, see: - // https://bugzilla.mozilla.org/show_bug.cgi?id=683280 - worker = new Worker(workerSrc); - } - - var messageHandler = new MessageHandler('main', worker); - - messageHandler.on('test', function pdfDocTest(supportTypedArray) { - if (supportTypedArray) { - this.worker = worker; - this.setupMessageHandler(messageHandler); - } else { - globalScope.PDFJS.disableWorker = true; - this.setupFakeWorker(); - } - }.bind(this)); - - var testObj = new Uint8Array(1); - // Some versions of Opera throw a DATA_CLONE_ERR on - // serializing the typed array. - messageHandler.send('test', testObj); - return; - } catch (e) { - warn('The worker has been disabled.'); - } - } - // Either workers are disabled, not supported or have thrown an exception. - // Thus, we fallback to a faked worker. - globalScope.PDFJS.disableWorker = true; - this.setupFakeWorker(); - } - - PDFDoc.prototype = { - setupFakeWorker: function PDFDoc_setupFakeWorker() { - // If we don't use a worker, just post/sendMessage to the main thread. - var fakeWorker = { - postMessage: function PDFDoc_postMessage(obj) { - fakeWorker.onmessage({data: obj}); - }, - terminate: function PDFDoc_terminate() {} - }; - - var messageHandler = new MessageHandler('main', fakeWorker); - this.setupMessageHandler(messageHandler); - - // If the main thread is our worker, setup the handling for the messages - // the main thread sends to it self. - WorkerMessageHandler.setup(messageHandler); - }, - - - setupMessageHandler: function PDFDoc_setupMessageHandler(messageHandler) { - this.messageHandler = messageHandler; - - messageHandler.on('page', function pdfDocPage(data) { - var pageNum = data.pageNum; - var page = this.pageCache[pageNum]; - var depFonts = data.depFonts; - - page.stats.timeEnd('Page Request'); - page.startRenderingFromOperatorList(data.operatorList, depFonts); - }, this); - - messageHandler.on('obj', function pdfDocObj(data) { - var id = data[0]; - var type = data[1]; - - switch (type) { - case 'JpegStream': - var imageData = data[2]; - loadJpegStream(id, imageData, this.objs); - break; - case 'Image': - var imageData = data[2]; - this.objs.resolve(id, imageData); - break; - case 'Font': - var name = data[2]; - var file = data[3]; - var properties = data[4]; - - if (file) { - // Rewrap the ArrayBuffer in a stream. - var fontFileDict = new Dict(); - file = new Stream(file, 0, file.length, fontFileDict); - } - - // At this point, only the font object is created but the font is - // not yet attached to the DOM. This is done in `FontLoader.bind`. - var font = new Font(name, file, properties); - this.objs.resolve(id, font); - break; - default: - error('Got unkown object type ' + type); - } - }, this); - - messageHandler.on('page_error', function pdfDocError(data) { - var page = this.pageCache[data.pageNum]; - if (page.displayReadyPromise) - page.displayReadyPromise.reject(data.error); - else - error(data.error); - }, this); - - messageHandler.on('jpeg_decode', function(data, promise) { - var imageData = data[0]; - var components = data[1]; - if (components != 3 && components != 1) - error('Only 3 component or 1 component can be returned'); - - var img = new Image(); - img.onload = (function messageHandler_onloadClosure() { - var width = img.width; - var height = img.height; - var size = width * height; - var rgbaLength = size * 4; - var buf = new Uint8Array(size * components); - var tmpCanvas = createScratchCanvas(width, height); - var tmpCtx = tmpCanvas.getContext('2d'); - tmpCtx.drawImage(img, 0, 0); - var data = tmpCtx.getImageData(0, 0, width, height).data; - - if (components == 3) { - for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) { - buf[j] = data[i]; - buf[j + 1] = data[i + 1]; - buf[j + 2] = data[i + 2]; - } - } else if (components == 1) { - for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) { - buf[j] = data[i]; - } - } - promise.resolve({ data: buf, width: width, height: height}); - }).bind(this); - var src = 'data:image/jpeg;base64,' + window.btoa(imageData); - img.src = src; - }); - - setTimeout(function pdfDocFontReadySetTimeout() { - messageHandler.send('doc', this.data); - this.workerReadyPromise.resolve(true); - }.bind(this)); - }, - - get numPages() { - return this.pdfModel.numPages; - }, - - startRendering: function PDFDoc_startRendering(page) { - // The worker might not be ready to receive the page request yet. - this.workerReadyPromise.then(function pdfDocStartRenderingThen() { - page.stats.time('Page Request'); - this.messageHandler.send('page_request', page.pageNumber + 1); - }.bind(this)); - }, - - getPage: function PDFDoc_getPage(n) { - if (this.pageCache[n]) - return this.pageCache[n]; - - var page = this.pdfModel.getPage(n); - // Add a reference to the objects such that Page can forward the reference - // to the CanvasGraphics and so on. - page.objs = this.objs; - page.pdf = this; - return (this.pageCache[n] = page); - }, - - destroy: function PDFDoc_destroy() { - if (this.worker) - this.worker.terminate(); - - if (this.fontWorker) - this.fontWorker.terminate(); - - for (var n in this.pageCache) - delete this.pageCache[n]; - - delete this.data; - delete this.stream; - delete this.pdf; - delete this.catalog; - } - }; - - return PDFDoc; -})(); - -globalScope.PDFJS.PDFDoc = PDFDoc; - diff --git a/src/worker.js b/src/worker.js index 42bd61050..69409ed79 100644 --- a/src/worker.js +++ b/src/worker.js @@ -89,6 +89,27 @@ var WorkerMessageHandler = { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. pdfModel = new PDFDocModel(new Stream(data)); + var doc = { + numPages: pdfModel.numPages, + fingerprint: pdfModel.fingerprint, + destinations: pdfModel.catalog.destinations, + outline: pdfModel.catalog.documentOutline, + info: pdfModel.info, + metadata: pdfModel.catalog.metadata + }; + handler.send('doc', {pdfInfo: doc}); + }); + + handler.on('getpage', function wphSetupTest(data) { + var pdfPage = pdfModel.getPage(data.pageNumber); + var page = { + pageNumber: data.pageNumber, + rotate: pdfPage.rotate, + ref: pdfPage.ref, + view: pdfPage.view, + annotations: pdfPage.getAnnotations(), // REMOVE + }; + handler.send('getpage', {pageInfo: page}); }); handler.on('page_request', function wphSetupPageRequest(pageNum) { From fd58f041179816115ca6f8d31434d61f63ae6159 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Wed, 11 Apr 2012 16:47:42 -0700 Subject: [PATCH 07/29] Refactor API to be async. --- src/api.js | 47 ++++++++++++++++++++++++----------------------- src/worker.js | 6 +++--- web/viewer.js | 2 +- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/api.js b/src/api.js index c8f908e7b..4a3f001b1 100644 --- a/src/api.js +++ b/src/api.js @@ -35,6 +35,7 @@ // all requirements to run parts of pdf.js in a web worker. // Right now, the requirement is, that an Uint8Array is still an Uint8Array // as it arrives on the worker. Chrome added this with version 15. + globalScope.PDFJS.disableWorker = true; if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { var workerSrc = PDFJS.workerSrc; if (typeof workerSrc === 'undefined') { @@ -57,6 +58,7 @@ } var messageHandler = new MessageHandler('main', worker); + this.messageHandler = messageHandler; messageHandler.on('test', function pdfDocTest(supportTypedArray) { if (supportTypedArray) { @@ -112,20 +114,20 @@ messageHandler.on('getpage', function pdfDocPage(data) { var pageInfo = data.pageInfo; - var page = new PdfPageWrapper(pageInfo, transport); - this.pageCache[pageInfo.pageNumber] = pageInfo; + var page = new PdfPageWrapper(pageInfo, this); + this.pageCache[pageInfo.pageNumber] = page; var promises = this.pagePromises[pageInfo.pageNumber]; delete this.pagePromises[pageInfo.pageNumber]; for (var i = 0, ii = promises.length; i < ii; ++i) - promises.resolve(page); + promises[i].resolve(page); }, this); messageHandler.on('page', function pdfDocPage(data) { var pageNum = data.pageNum; - var page = this.pageCache[pageNum]; + var page = this.pageCache[pageNum - 1]; var depFonts = data.depFonts; - page.stats.timeEnd('Page Request'); + //page.stats.timeEnd('Page Request'); page.startRenderingFromOperatorList(data.operatorList, depFonts); }, this); @@ -208,26 +210,26 @@ }, sendData: function WorkerTransport_sendData(data) { - this.messageHandler.send('doc', data); + this.messageHandler.send('doc_request', data); }, getPage: function WorkerTransport_getPage(n, promise) { - if (this.pageCache[n]) { - promise.resolve(pageCache[n]); + if (this.pageCache[n - 1]) { + promise.resolve(pageCache[n - 1]); return; } - if (n in this.pagePromises) { - this.pagePromises[n].push(promise); + if ((n - 1) in this.pagePromises) { + this.pagePromises[n - 1].push(promise); return; } - this.pagePromises[n] = [promise]; - this.messageHandler.send('getpage', {page: n}); + this.pagePromises[n - 1] = [promise]; + this.messageHandler.send('getpage_request', {pageNumber: n - 1}); } }; - function PdfPageWrapper(page, transport) { + function PdfPageWrapper(pageInfo, transport) { this.pageInfo = pageInfo; this.transport = transport; - this.stats = new StatTimer(); + this._stats = new StatTimer(); this.objs = transport.objs; } PdfPageWrapper.prototype = { @@ -238,7 +240,7 @@ return this.pageInfo.rotate; }, get stats() { - return this.stats; + return this._stats; }, get ref() { return this.pageInfo.ref; @@ -258,18 +260,17 @@ return promise; }, render: function(renderContext) { - var promise; + var promise = new Promise(); var stats = this.stats; stats.time('Overall'); // If there is no displayReadyPromise yet, then the operatorList was never // requested before. Make the request and create the promise. if (!this.displayReadyPromise) { - this.displayReadyPromise = promise = new Promise(); + this.displayReadyPromise = new Promise(); - this.stats.time('Page Request'); - this.messageHandler.send('page_request', this.pageNumber + 1); - } else - promise = this.displayReadyPromise; + //this.stats.time('Page Request'); + this.transport.messageHandler.send('page_request', this.pageNumber + 1); + } var callback = (function complete(error) { if (error) @@ -345,14 +346,14 @@ var stats = this.stats; stats.time('Rendering'); -/* REMOVE ??? */ +/* REMOVE ??? var xref = this.xref; var resources = this.resources; assertWellFormed(isDict(resources), 'invalid page resources'); gfx.xref = xref; gfx.res = resources; -/* REMOVE END */ + REMOVE END */ gfx.beginDrawing(viewport); diff --git a/src/worker.js b/src/worker.js index 69409ed79..cc9a8a590 100644 --- a/src/worker.js +++ b/src/worker.js @@ -85,7 +85,7 @@ var WorkerMessageHandler = { handler.send('test', data instanceof Uint8Array); }); - handler.on('doc', function wphSetupDoc(data) { + handler.on('doc_request', function wphSetupDoc(data) { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. pdfModel = new PDFDocModel(new Stream(data)); @@ -100,8 +100,8 @@ var WorkerMessageHandler = { handler.send('doc', {pdfInfo: doc}); }); - handler.on('getpage', function wphSetupTest(data) { - var pdfPage = pdfModel.getPage(data.pageNumber); + handler.on('getpage_request', function wphSetupTest(data) { + var pdfPage = pdfModel.getPage(data.pageNumber + 1); var page = { pageNumber: data.pageNumber, rotate: pdfPage.rotate, diff --git a/web/viewer.js b/web/viewer.js index 51d4df711..bda342aa7 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -986,7 +986,7 @@ var PageView = function pageView(container, pdfPage, id, scale, if (error) PDFView.error('An error occurred while rendering the page.', error); - self.stats = content.stats; + self.stats = pdfPage.stats; self.updateStats(); if (self.onAfterDraw) self.onAfterDraw(); From 3b83a42a9122c1b7546535eaf215fe64d2b51f92 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Wed, 11 Apr 2012 17:09:55 -0700 Subject: [PATCH 08/29] Outline fix, destroy, and linting --- src/api.js | 65 +++++++++++++++++++-------------------------------- src/worker.js | 2 +- 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/src/api.js b/src/api.js index 4a3f001b1..82e85681c 100644 --- a/src/api.js +++ b/src/api.js @@ -1,26 +1,6 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -/* - PDFDoc.prototype = { - destroy: function PDFDoc_destroy() { - if (this.worker) - this.worker.terminate(); - - if (this.fontWorker) - this.fontWorker.terminate(); - - for (var n in this.pageCache) - delete this.pageCache[n]; - - delete this.data; - delete this.stream; - delete this.pdf; - delete this.catalog; - } - }; -*/ - (function pdfApiWrapper() { function WorkerTransport(promise) { this.workerReadyPromise = promise; @@ -60,7 +40,7 @@ var messageHandler = new MessageHandler('main', worker); this.messageHandler = messageHandler; - messageHandler.on('test', function pdfDocTest(supportTypedArray) { + messageHandler.on('test', function transportTest(supportTypedArray) { if (supportTypedArray) { this.worker = worker; this.setupMessageHandler(messageHandler); @@ -85,6 +65,13 @@ this.setupFakeWorker(); } WorkerTransport.prototype = { + destroy: function WorkerTransport_destroy() { + if (this.worker) + this.worker.terminate(); + + for (var n in this.pageCache) + delete this.pageCache[n]; + }, setupFakeWorker: function WorkerTransport_setupFakeWorker() { // If we don't use a worker, just post/sendMessage to the main thread. var fakeWorker = { @@ -102,17 +89,18 @@ WorkerMessageHandler.setup(messageHandler); }, - setupMessageHandler: function WorkerTransport_setupMessageHandler(messageHandler) { + setupMessageHandler: + function WorkerTransport_setupMessageHandler(messageHandler) { this.messageHandler = messageHandler; - messageHandler.on('doc', function pdfDocPage(data) { + messageHandler.on('doc', function transportPage(data) { var pdfInfo = data.pdfInfo; var pdfDocument = new PdfDocumentWrapper(pdfInfo, this); this.pdfDocument = pdfDocument; - this.workerReadyPromise.resolve(pdfDocument) + this.workerReadyPromise.resolve(pdfDocument); }, this); - messageHandler.on('getpage', function pdfDocPage(data) { + messageHandler.on('getpage', function transportPage(data) { var pageInfo = data.pageInfo; var page = new PdfPageWrapper(pageInfo, this); this.pageCache[pageInfo.pageNumber] = page; @@ -122,7 +110,7 @@ promises[i].resolve(page); }, this); - messageHandler.on('page', function pdfDocPage(data) { + messageHandler.on('page', function transportPage(data) { var pageNum = data.pageNum; var page = this.pageCache[pageNum - 1]; var depFonts = data.depFonts; @@ -131,7 +119,7 @@ page.startRenderingFromOperatorList(data.operatorList, depFonts); }, this); - messageHandler.on('obj', function pdfDocObj(data) { + messageHandler.on('obj', function transportObj(data) { var id = data[0]; var type = data[1]; @@ -165,7 +153,7 @@ } }, this); - messageHandler.on('page_error', function pdfDocError(data) { + messageHandler.on('page_error', function transportError(data) { var page = this.pageCache[data.pageNum]; if (page.displayReadyPromise) page.displayReadyPromise.reject(data.error); @@ -305,7 +293,8 @@ }, startRenderingFromOperatorList: - function Page_startRenderingFromOperatorList(operatorList, fonts) { + function PdfPageWrapper_startRenderingFromOperatorList(operatorList, + fonts) { var self = this; this.operatorList = operatorList; @@ -324,7 +313,7 @@ ); }, - ensureFonts: function Page_ensureFonts(fonts, callback) { + ensureFonts: function PdfPageWrapper_ensureFonts(fonts, callback) { this.stats.time('Font Loading'); // Convert the font names to the corresponding font obj. for (var i = 0, ii = fonts.length; i < ii; i++) { @@ -342,19 +331,10 @@ ); }, - display: function Page_display(gfx, viewport, callback) { + display: function PdfPageWrapper_display(gfx, viewport, callback) { var stats = this.stats; stats.time('Rendering'); -/* REMOVE ??? - var xref = this.xref; - var resources = this.resources; - assertWellFormed(isDict(resources), 'invalid page resources'); - - gfx.xref = xref; - gfx.res = resources; - REMOVE END */ - gfx.beginDrawing(viewport); var startIdx = 0; @@ -422,7 +402,7 @@ }, getOutline: function() { var promise = new PDFJS.Promise(); - var outline = this.pdfInfo.documentOutline; + var outline = this.pdfInfo.outline; promise.resolve(outline); return promise; }, @@ -435,6 +415,9 @@ metadata: metadata ? new PDFJS.Metadata(metadata) : null }); return promise; + }, + destroy: function() { + this.transport.destroy(); } }; diff --git a/src/worker.js b/src/worker.js index cc9a8a590..fcde756b9 100644 --- a/src/worker.js +++ b/src/worker.js @@ -107,7 +107,7 @@ var WorkerMessageHandler = { rotate: pdfPage.rotate, ref: pdfPage.ref, view: pdfPage.view, - annotations: pdfPage.getAnnotations(), // REMOVE + annotations: pdfPage.getAnnotations() }; handler.send('getpage', {pageInfo: page}); }); From 5608f8e445ce16aadd4f7a9e92bb61d4554ec104 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Wed, 11 Apr 2012 18:05:43 -0700 Subject: [PATCH 09/29] Test refactoring for async api. --- src/api.js | 3 +- test/driver.js | 81 ++++++++++++++++++++++++-------------------- test/test_slave.html | 1 + 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/api.js b/src/api.js index 82e85681c..3d683af07 100644 --- a/src/api.js +++ b/src/api.js @@ -15,7 +15,6 @@ // all requirements to run parts of pdf.js in a web worker. // Right now, the requirement is, that an Uint8Array is still an Uint8Array // as it arrives on the worker. Chrome added this with version 15. - globalScope.PDFJS.disableWorker = true; if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { var workerSrc = PDFJS.workerSrc; if (typeof workerSrc === 'undefined') { @@ -203,7 +202,7 @@ getPage: function WorkerTransport_getPage(n, promise) { if (this.pageCache[n - 1]) { - promise.resolve(pageCache[n - 1]); + promise.resolve(this.pageCache[n - 1]); return; } if ((n - 1) in this.pagePromises) { diff --git a/test/driver.js b/test/driver.js index a1dc4b242..600f53c9c 100644 --- a/test/driver.js +++ b/test/driver.js @@ -100,13 +100,24 @@ function nextTask() { getPdf(task.file, function nextTaskGetPdf(data) { var failure; + function continuation() { + task.pageNum = task.firstPage || 1; + nextPage(task, failure); + } try { - task.pdfDoc = new PDFJS.PDFDoc(data); + var promise = PDFJS.getDocument(data); + promise.then(function(doc) { + task.pdfDoc = doc; + continuation(); + }, function(e) { + failure = 'load PDF doc : ' + e; + continuation(); + }); + return; } catch (e) { failure = 'load PDF doc : ' + exceptionToString(e); } - task.pageNum = task.firstPage || 1; - nextPage(task, failure); + continuation(); }); } @@ -163,45 +174,43 @@ function nextPage(task, loadError) { log(' loading page ' + task.pageNum + '/' + task.pdfDoc.numPages + '... '); var ctx = canvas.getContext('2d'); - page = task.pdfDoc.getPage(task.pageNum); + task.pdfDoc.getPage(task.pageNum).then(function(page) { + var pdfToCssUnitsCoef = 96.0 / 72.0; + // using mediaBox for the canvas size + var pageWidth = page.width; + var pageHeight = page.height; + canvas.width = pageWidth * pdfToCssUnitsCoef; + canvas.height = pageHeight * pdfToCssUnitsCoef; + clear(ctx); - var pdfToCssUnitsCoef = 96.0 / 72.0; - // using mediaBox for the canvas size - var pageWidth = page.width; - var pageHeight = page.height; - canvas.width = pageWidth * pdfToCssUnitsCoef; - canvas.height = pageHeight * pdfToCssUnitsCoef; - clear(ctx); - - // using the text layer builder that does nothing to test - // text layer creation operations - var textLayerBuilder = { - beginLayout: function nullTextLayerBuilderBeginLayout() {}, - endLayout: function nullTextLayerBuilderEndLayout() {}, - appendText: function nullTextLayerBuilderAppendText(text, fontName, - fontSize) {} - }; - - page.startRendering( - ctx, - function nextPageStartRendering(error) { - var failureMessage = false; - if (error) - failureMessage = 'render : ' + error.message; - snapshotCurrentPage(task, failureMessage); + // using the text layer builder that does nothing to test + // text layer creation operations + var textLayerBuilder = { + beginLayout: function nullTextLayerBuilderBeginLayout() {}, + endLayout: function nullTextLayerBuilderEndLayout() {}, + appendText: function nullTextLayerBuilderAppendText(text, fontName, + fontSize) {} + }; + var renderContext = { + canvasContext: ctx, + textLayer: textLayerBuilder, + viewport: page.getViewport(1) + }; + page.render(renderContext).then(function() { + snapshotCurrentPage(task, false); }, - textLayerBuilder - ); + function(error) { + snapshotCurrentPage(task, 'render : ' + error); + }); + }, + function(error) { + snapshotCurrentPage(task, 'render : ' + error); + }); } catch (e) { failure = 'page setup : ' + exceptionToString(e); + snapshotCurrentPage(task, failure); } } - - if (failure) { - // Skip right to snapshotting if there was a failure, since the - // fonts might be in an inconsistent state. - snapshotCurrentPage(task, failure); - } } function snapshotCurrentPage(task, failure) { diff --git a/test/test_slave.html b/test/test_slave.html index 50bb067e1..7c2097110 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -5,6 +5,7 @@ <style type="text/css"></style> <script type="text/javascript" src="/src/core.js"></script> <script type="text/javascript" src="/src/util.js"></script> + <script type="text/javascript" src="/src/api.js"></script> <script type="text/javascript" src="/src/canvas.js"></script> <script type="text/javascript" src="/src/obj.js"></script> <script type="text/javascript" src="/src/function.js"></script> From b312719d7e0cf1a45dda0b4ad871a45162ed6f5b Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Thu, 12 Apr 2012 08:23:38 -0700 Subject: [PATCH 10/29] Fixes test driver and examples --- examples/acroforms/forms.js | 149 +++++++++++++++++---------------- examples/acroforms/index.html | 1 + examples/helloworld/hello.js | 42 ++++++---- examples/helloworld/index.html | 1 + src/util.js | 1 + test/driver.js | 8 +- 6 files changed, 109 insertions(+), 93 deletions(-) diff --git a/examples/acroforms/forms.js b/examples/acroforms/forms.js index 6ec92766d..868825fc7 100644 --- a/examples/acroforms/forms.js +++ b/examples/acroforms/forms.js @@ -9,7 +9,7 @@ var formFields = {}; -function setupForm(div, content, scale) { +function setupForm(div, content, viewport) { function bindInputItem(input, item) { if (input.name in formFields) { var value = formFields[input.name]; @@ -27,16 +27,20 @@ function setupForm(div, content, scale) { } function createElementWithStyle(tagName, item) { var element = document.createElement(tagName); - element.style.left = (item.x * scale) + 'px'; - element.style.top = (item.y * scale) + 'px'; - element.style.width = Math.ceil(item.width * scale) + 'px'; - element.style.height = Math.ceil(item.height * scale) + 'px'; + var rect = Util.normalizeRect( + viewport.convertToViewportRectangle(item.rect)); + element.style.left = Math.floor(rect[0]) + 'px'; + element.style.top = Math.floor(rect[1]) + 'px'; + element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'; + element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'; return element; } function assignFontStyle(element, item) { var fontStyles = ''; - if ('fontSize' in item) - fontStyles += 'font-size: ' + Math.round(item.fontSize * scale) + 'px;'; + if ('fontSize' in item) { + fontStyles += 'font-size: ' + Math.round(item.fontSize * + viewport.fontScale) + 'px;'; + } switch (item.textAlignment) { case 0: fontStyles += 'text-align: left;'; @@ -51,83 +55,88 @@ function setupForm(div, content, scale) { element.setAttribute('style', element.getAttribute('style') + fontStyles); } - var items = content.getAnnotations(); - for (var i = 0; i < items.length; i++) { - var item = items[i]; - switch (item.type) { - case 'Widget': - if (item.fieldType != 'Tx' && item.fieldType != 'Btn' && - item.fieldType != 'Ch') - break; - var inputDiv = createElementWithStyle('div', item); - inputDiv.className = 'inputHint'; - div.appendChild(inputDiv); - var input; - if (item.fieldType == 'Tx') { - input = createElementWithStyle('input', item); - } - if (item.fieldType == 'Btn') { - input = createElementWithStyle('input', item); - if (item.flags & 32768) { - input.type = 'radio'; - // radio button is not supported - } else if (item.flags & 65536) { - input.type = 'button'; - // pushbutton is not supported - } else { - input.type = 'checkbox'; + content.getAnnotations().then(function(items) { + for (var i = 0; i < items.length; i++) { + var item = items[i]; + switch (item.type) { + case 'Widget': + if (item.fieldType != 'Tx' && item.fieldType != 'Btn' && + item.fieldType != 'Ch') + break; + var inputDiv = createElementWithStyle('div', item); + inputDiv.className = 'inputHint'; + div.appendChild(inputDiv); + var input; + if (item.fieldType == 'Tx') { + input = createElementWithStyle('input', item); } - } - if (item.fieldType == 'Ch') { - input = createElementWithStyle('select', item); - // select box is not supported - } - input.className = 'inputControl'; - input.name = item.fullName; - input.title = item.alternativeText; - assignFontStyle(input, item); - bindInputItem(input, item); - div.appendChild(input); - break; + if (item.fieldType == 'Btn') { + input = createElementWithStyle('input', item); + if (item.flags & 32768) { + input.type = 'radio'; + // radio button is not supported + } else if (item.flags & 65536) { + input.type = 'button'; + // pushbutton is not supported + } else { + input.type = 'checkbox'; + } + } + if (item.fieldType == 'Ch') { + input = createElementWithStyle('select', item); + // select box is not supported + } + input.className = 'inputControl'; + input.name = item.fullName; + input.title = item.alternativeText; + assignFontStyle(input, item); + bindInputItem(input, item); + div.appendChild(input); + break; + } } - } + }); } function renderPage(div, pdf, pageNumber, callback) { - var page = pdf.getPage(pageNumber); - var scale = 1.5; + pdf.getPage(pageNumber).then(function(page) { + var scale = 1.5; + var viewport = page.getViewport(scale); - var pageDisplayWidth = page.width * scale; - var pageDisplayHeight = page.height * scale; + var pageDisplayWidth = viewport.width; + var pageDisplayHeight = viewport.height; - var pageDivHolder = document.createElement('div'); - pageDivHolder.className = 'pdfpage'; - pageDivHolder.style.width = pageDisplayWidth + 'px'; - pageDivHolder.style.height = pageDisplayHeight + 'px'; - div.appendChild(pageDivHolder); + var pageDivHolder = document.createElement('div'); + pageDivHolder.className = 'pdfpage'; + pageDivHolder.style.width = pageDisplayWidth + 'px'; + pageDivHolder.style.height = pageDisplayHeight + 'px'; + div.appendChild(pageDivHolder); - // Prepare canvas using PDF page dimensions - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - canvas.width = pageDisplayWidth; - canvas.height = pageDisplayHeight; - pageDivHolder.appendChild(canvas); + // Prepare canvas using PDF page dimensions + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + canvas.width = pageDisplayWidth; + canvas.height = pageDisplayHeight; + pageDivHolder.appendChild(canvas); - // Render PDF page into canvas context - page.startRendering(context, callback); + // Render PDF page into canvas context + var renderContext = { + canvasContext: context, + viewport: viewport + }; + page.render(renderContext).then(callback); - // Prepare and populate form elements layer - var formDiv = document.createElement('div'); - pageDivHolder.appendChild(formDiv); + // Prepare and populate form elements layer + var formDiv = document.createElement('div'); + pageDivHolder.appendChild(formDiv); - setupForm(formDiv, page, scale); + setupForm(formDiv, page, viewport); + }); } -PDFJS.getPdf(pdfWithFormsPath, function getPdfForm(data) { - // Instantiate PDFDoc with PDF data - var pdf = new PDFJS.PDFDoc(data); - +// Fetch the PDF document from the URL using promices +PDFJS.getDocument(pdfWithFormsPath).then(function getPdfForm(pdf) { // Rendering all pages starting from first var viewer = document.getElementById('viewer'); var pageNumber = 1; diff --git a/examples/acroforms/index.html b/examples/acroforms/index.html index 8a9053f78..858ad649f 100644 --- a/examples/acroforms/index.html +++ b/examples/acroforms/index.html @@ -6,6 +6,7 @@ <!-- In production, change the content of PDFJS.workerSrc below --> <script type="text/javascript" src="../../src/core.js"></script> <script type="text/javascript" src="../../src/util.js"></script> + <script type="text/javascript" src="../../src/api.js"></script> <script type="text/javascript" src="../../src/canvas.js"></script> <script type="text/javascript" src="../../src/obj.js"></script> <script type="text/javascript" src="../../src/function.js"></script> diff --git a/examples/helloworld/hello.js b/examples/helloworld/hello.js index 45e61eb6f..7bf18d65b 100644 --- a/examples/helloworld/hello.js +++ b/examples/helloworld/hello.js @@ -7,25 +7,31 @@ 'use strict'; -PDFJS.getPdf('helloworld.pdf', function getPdfHelloWorld(data) { - // - // Instantiate PDFDoc with PDF data - // - var pdf = new PDFJS.PDFDoc(data); - var page = pdf.getPage(1); - var scale = 1.5; +// +// Fetch the PDF document from the URL using promices +// +PDFJS.getDocument('helloworld.pdf').then(function(pdf) { + // Using promise to fetch the page + pdf.getPage(1).then(function(page) { + var scale = 1.5; + var viewport = page.getViewport(scale); - // - // Prepare canvas using PDF page dimensions - // - var canvas = document.getElementById('the-canvas'); - var context = canvas.getContext('2d'); - canvas.height = page.height * scale; - canvas.width = page.width * scale; + // + // Prepare canvas using PDF page dimensions + // + var canvas = document.getElementById('the-canvas'); + var context = canvas.getContext('2d'); + canvas.height = viewport.height; + canvas.width = viewport.width; - // - // Render PDF page into canvas context - // - page.startRendering(context); + // + // Render PDF page into canvas context + // + var renderContext = { + canvasContext: context, + viewport: viewport + }; + page.render(renderContext); + }); }); diff --git a/examples/helloworld/index.html b/examples/helloworld/index.html index c6af616e6..c9df98809 100644 --- a/examples/helloworld/index.html +++ b/examples/helloworld/index.html @@ -6,6 +6,7 @@ <!-- In production, change the content of PDFJS.workerSrc below --> <script type="text/javascript" src="../../src/core.js"></script> <script type="text/javascript" src="../../src/util.js"></script> + <script type="text/javascript" src="../../src/api.js"></script> <script type="text/javascript" src="../../src/canvas.js"></script> <script type="text/javascript" src="../../src/obj.js"></script> <script type="text/javascript" src="../../src/function.js"></script> diff --git a/src/util.js b/src/util.js index 390b08427..6ec4bc9cb 100644 --- a/src/util.js +++ b/src/util.js @@ -242,6 +242,7 @@ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { this.offsetY = offsetY; this.width = width; this.height = height; + this.fontScale = scale; } PageViewport.prototype = { convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) { diff --git a/test/driver.js b/test/driver.js index 600f53c9c..993a31349 100644 --- a/test/driver.js +++ b/test/driver.js @@ -176,11 +176,9 @@ function nextPage(task, loadError) { var ctx = canvas.getContext('2d'); task.pdfDoc.getPage(task.pageNum).then(function(page) { var pdfToCssUnitsCoef = 96.0 / 72.0; - // using mediaBox for the canvas size - var pageWidth = page.width; - var pageHeight = page.height; - canvas.width = pageWidth * pdfToCssUnitsCoef; - canvas.height = pageHeight * pdfToCssUnitsCoef; + var viewport = page.getViewport(pdfToCssUnitsCoef); + canvas.width = viewport.width; + canvas.height = viewport.height; clear(ctx); // using the text layer builder that does nothing to test From b6c587bb9801fba9dcacfe3e3c82a6f967425a67 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Thu, 12 Apr 2012 09:59:17 -0700 Subject: [PATCH 11/29] Fix driver viewport. --- test/driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/driver.js b/test/driver.js index 993a31349..be66aa216 100644 --- a/test/driver.js +++ b/test/driver.js @@ -192,7 +192,7 @@ function nextPage(task, loadError) { var renderContext = { canvasContext: ctx, textLayer: textLayerBuilder, - viewport: page.getViewport(1) + viewport: viewport }; page.render(renderContext).then(function() { snapshotCurrentPage(task, false); From f0687c4d50f4f674620b5cada5120421d2ba60c8 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Thu, 12 Apr 2012 10:01:07 -0700 Subject: [PATCH 12/29] Refactor pageNumber/pageIndex concept --- src/api.js | 54 +++++++++++++++++++++++---------------------------- src/worker.js | 17 ++++++++-------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/api.js b/src/api.js index 3d683af07..e3969563a 100644 --- a/src/api.js +++ b/src/api.js @@ -68,8 +68,8 @@ if (this.worker) this.worker.terminate(); - for (var n in this.pageCache) - delete this.pageCache[n]; + this.pageCache = []; + this.pagePromises = []; }, setupFakeWorker: function WorkerTransport_setupFakeWorker() { // If we don't use a worker, just post/sendMessage to the main thread. @@ -92,7 +92,7 @@ function WorkerTransport_setupMessageHandler(messageHandler) { this.messageHandler = messageHandler; - messageHandler.on('doc', function transportPage(data) { + messageHandler.on('getdoc', function transportDoc(data) { var pdfInfo = data.pdfInfo; var pdfDocument = new PdfDocumentWrapper(pdfInfo, this); this.pdfDocument = pdfDocument; @@ -102,19 +102,16 @@ messageHandler.on('getpage', function transportPage(data) { var pageInfo = data.pageInfo; var page = new PdfPageWrapper(pageInfo, this); - this.pageCache[pageInfo.pageNumber] = page; - var promises = this.pagePromises[pageInfo.pageNumber]; - delete this.pagePromises[pageInfo.pageNumber]; - for (var i = 0, ii = promises.length; i < ii; ++i) - promises[i].resolve(page); + this.pageCache[pageInfo.pageIndex] = page; + var promise = this.pagePromises[pageInfo.pageIndex]; + promise.resolve(page); }, this); - messageHandler.on('page', function transportPage(data) { - var pageNum = data.pageNum; - var page = this.pageCache[pageNum - 1]; + messageHandler.on('renderpage', function transportRender(data) { + var page = this.pageCache[data.pageIndex]; var depFonts = data.depFonts; - //page.stats.timeEnd('Page Request'); + page.stats.timeEnd('Page Request'); page.startRenderingFromOperatorList(data.operatorList, depFonts); }, this); @@ -197,20 +194,17 @@ }, sendData: function WorkerTransport_sendData(data) { - this.messageHandler.send('doc_request', data); + this.messageHandler.send('getdoc_request', data); }, - getPage: function WorkerTransport_getPage(n, promise) { - if (this.pageCache[n - 1]) { - promise.resolve(this.pageCache[n - 1]); - return; - } - if ((n - 1) in this.pagePromises) { - this.pagePromises[n - 1].push(promise); - return; - } - this.pagePromises[n - 1] = [promise]; - this.messageHandler.send('getpage_request', {pageNumber: n - 1}); + getPage: function WorkerTransport_getPage(pageNumber, promise) { + var pageIndex = pageNumber - 1; + if (pageIndex in this.pagePromises) + return this.pagePromises[pageIndex]; + var promise = new PDFJS.Promise('Page ' + pageNumber); + this.pagePromises[pageIndex] = promise; + this.messageHandler.send('getpage_request', { pageIndex: pageIndex }); + return promise; } }; function PdfPageWrapper(pageInfo, transport) { @@ -221,7 +215,7 @@ } PdfPageWrapper.prototype = { get pageNumber() { - return this.pageInfo.pageNumber; + return this.pageInfo.pageIndex + 1; }, get rotate() { return this.pageInfo.rotate; @@ -255,8 +249,10 @@ if (!this.displayReadyPromise) { this.displayReadyPromise = new Promise(); - //this.stats.time('Page Request'); - this.transport.messageHandler.send('page_request', this.pageNumber + 1); + this.stats.time('Page Request'); + this.transport.messageHandler.send('renderpage_request', { + pageIndex: this.pageNumber - 1 + }); } var callback = (function complete(error) { @@ -389,9 +385,7 @@ return this.pdfInfo.fingerprint; }, getPage: function(number) { - var promise = new PDFJS.Promise(); - this.transport.getPage(number, promise); - return promise; + return this.transport.getPage(number); }, getDestinations: function() { var promise = new PDFJS.Promise(); diff --git a/src/worker.js b/src/worker.js index fcde756b9..f61e4b509 100644 --- a/src/worker.js +++ b/src/worker.js @@ -85,7 +85,7 @@ var WorkerMessageHandler = { handler.send('test', data instanceof Uint8Array); }); - handler.on('doc_request', function wphSetupDoc(data) { + handler.on('getdoc_request', function wphSetupDoc(data) { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. pdfModel = new PDFDocModel(new Stream(data)); @@ -97,13 +97,14 @@ var WorkerMessageHandler = { info: pdfModel.info, metadata: pdfModel.catalog.metadata }; - handler.send('doc', {pdfInfo: doc}); + handler.send('getdoc', {pdfInfo: doc}); }); handler.on('getpage_request', function wphSetupTest(data) { - var pdfPage = pdfModel.getPage(data.pageNumber + 1); + var pageNumber = data.pageIndex + 1; + var pdfPage = pdfModel.getPage(pageNumber); var page = { - pageNumber: data.pageNumber, + pageIndex: data.pageIndex, rotate: pdfPage.rotate, ref: pdfPage.ref, view: pdfPage.view, @@ -112,8 +113,8 @@ var WorkerMessageHandler = { handler.send('getpage', {pageInfo: page}); }); - handler.on('page_request', function wphSetupPageRequest(pageNum) { - pageNum = parseInt(pageNum); + handler.on('renderpage_request', function wphSetupPageRequest(data) { + var pageNum = data.pageIndex + 1; // The following code does quite the same as @@ -170,8 +171,8 @@ var WorkerMessageHandler = { } } - handler.send('page', { - pageNum: pageNum, + handler.send('renderpage', { + pageIndex: data.pageIndex, operatorList: operatorList, depFonts: Object.keys(fonts) }); From 2c49cab3a111e541e4ea1acc1907fd742ff2c57d Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Thu, 12 Apr 2012 12:11:22 -0700 Subject: [PATCH 13/29] Fixing names. --- src/api.js | 500 +++++++++++++++++++++++++------------------------- src/core.js | 26 +-- src/image.js | 2 +- src/worker.js | 16 +- 4 files changed, 276 insertions(+), 268 deletions(-) diff --git a/src/api.js b/src/api.js index e3969563a..72d8cae66 100644 --- a/src/api.js +++ b/src/api.js @@ -1,7 +1,250 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -(function pdfApiWrapper() { +PDFJS.getDocument = function getDocument(source) { + var promise = new PDFJS.Promise(); + var transport = new WorkerTransport(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) { + transport.sendData(data); + }); + } else { + // assuming the source is array, instantiating directly from it + transport.sendData(source); + } + return promise; +}; + +var PDFDocumentProxy = (function() { + function PDFDocumentProxy(pdfInfo, transport) { + this.pdfInfo = pdfInfo; + this.transport = transport; + } + PDFDocumentProxy.prototype = { + get numPages() { + return this.pdfInfo.numPages; + }, + get fingerprint() { + return this.pdfInfo.fingerprint; + }, + getPage: function(number) { + return this.transport.getPage(number); + }, + getDestinations: function() { + var promise = new PDFJS.Promise(); + var destinations = this.pdfInfo.destinations; + promise.resolve(destinations); + return promise; + }, + getOutline: function() { + var promise = new PDFJS.Promise(); + var outline = this.pdfInfo.outline; + promise.resolve(outline); + return promise; + }, + getMetadata: function() { + var promise = new PDFJS.Promise(); + var info = this.pdfInfo.info; + var metadata = this.pdfInfo.metadata; + promise.resolve({ + info: info, + metadata: metadata ? new PDFJS.Metadata(metadata) : null + }); + return promise; + }, + destroy: function() { + this.transport.destroy(); + } + }; + return PDFDocumentProxy; +})(); + +var PDFPageProxy = (function PDFPageProxyClosure() { + function PDFPageProxy(pageInfo, transport) { + this.pageInfo = pageInfo; + this.transport = transport; + this._stats = new StatTimer(); + this.objs = transport.objs; + } + PDFPageProxy.prototype = { + get pageNumber() { + return this.pageInfo.pageIndex + 1; + }, + get rotate() { + return this.pageInfo.rotate; + }, + get stats() { + return this._stats; + }, + get ref() { + return this.pageInfo.ref; + }, + get view() { + return this.pageInfo.view; + }, + getViewport: function(scale, rotate) { + if (arguments.length < 2) + rotate = this.rotate; + return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); + }, + getAnnotations: function() { + var promise = new PDFJS.Promise(); + var annotations = this.pageInfo.annotations; + promise.resolve(annotations); + return promise; + }, + render: function(renderContext) { + var promise = new Promise(); + var stats = this.stats; + stats.time('Overall'); + // If there is no displayReadyPromise yet, then the operatorList was never + // requested before. Make the request and create the promise. + if (!this.displayReadyPromise) { + this.displayReadyPromise = new Promise(); + + this.stats.time('Page Request'); + this.transport.messageHandler.send('RenderPageRequest', { + pageIndex: this.pageNumber - 1 + }); + } + + var callback = (function complete(error) { + if (error) + promise.reject(error); + else + promise.resolve(); + }); + + // 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); + try { + this.display(gfx, renderContext.viewport, callback); + } catch (e) { + if (callback) + callback(e); + else + error(e); + } + }.bind(this), + function pageDisplayReadPromiseError(reason) { + if (callback) + callback(reason); + else + error(reason); + } + ); + + return promise; + }, + + startRenderingFromOperatorList: + function PDFPageWrapper_startRenderingFromOperatorList(operatorList, + fonts) { + var self = this; + this.operatorList = operatorList; + + var displayContinuation = function pageDisplayContinuation() { + // Always defer call to display() to work around bug in + // Firefox error reporting from XHR callbacks. + setTimeout(function pageSetTimeout() { + self.displayReadyPromise.resolve(); + }); + }; + + this.ensureFonts(fonts, + function pageStartRenderingFromOperatorListEnsureFonts() { + displayContinuation(); + } + ); + }, + + ensureFonts: function PDFPageWrapper_ensureFonts(fonts, callback) { + this.stats.time('Font Loading'); + // Convert the font names to the corresponding font obj. + for (var i = 0, ii = fonts.length; i < ii; i++) { + fonts[i] = this.objs.objs[fonts[i]].data; + } + + // Load all the fonts + FontLoader.bind( + fonts, + function pageEnsureFontsFontObjs(fontObjs) { + this.stats.timeEnd('Font Loading'); + + callback.call(this); + }.bind(this) + ); + }, + + display: function PDFPageWrapper_display(gfx, viewport, callback) { + var stats = this.stats; + stats.time('Rendering'); + + gfx.beginDrawing(viewport); + + var startIdx = 0; + var length = this.operatorList.fnArray.length; + var operatorList = this.operatorList; + var stepper = null; + if (PDFJS.pdfBug && StepperManager.enabled) { + stepper = StepperManager.create(this.pageNumber); + stepper.init(operatorList); + stepper.nextBreakPoint = stepper.getNextBreakPoint(); + } + + var self = this; + function next() { + startIdx = + gfx.executeOperatorList(operatorList, startIdx, next, stepper); + if (startIdx == length) { + gfx.endDrawing(); + stats.timeEnd('Rendering'); + stats.timeEnd('Overall'); + if (callback) callback(); + } + } + next(); + }, + + 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; + } + }; + return PDFPageProxy; +})(); + +var WorkerTransport = (function WorkerTransportClosure() { function WorkerTransport(promise) { this.workerReadyPromise = promise; this.objs = new PDFObjects(); @@ -92,22 +335,22 @@ function WorkerTransport_setupMessageHandler(messageHandler) { this.messageHandler = messageHandler; - messageHandler.on('getdoc', function transportDoc(data) { + messageHandler.on('GetDoc', function transportDoc(data) { var pdfInfo = data.pdfInfo; - var pdfDocument = new PdfDocumentWrapper(pdfInfo, this); + var pdfDocument = new PDFDocumentProxy(pdfInfo, this); this.pdfDocument = pdfDocument; this.workerReadyPromise.resolve(pdfDocument); }, this); - messageHandler.on('getpage', function transportPage(data) { + messageHandler.on('GetPage', function transportPage(data) { var pageInfo = data.pageInfo; - var page = new PdfPageWrapper(pageInfo, this); + var page = new PDFPageProxy(pageInfo, this); this.pageCache[pageInfo.pageIndex] = page; var promise = this.pagePromises[pageInfo.pageIndex]; promise.resolve(page); }, this); - messageHandler.on('renderpage', function transportRender(data) { + messageHandler.on('RenderPage', function transportRender(data) { var page = this.pageCache[data.pageIndex]; var depFonts = data.depFonts; @@ -149,7 +392,7 @@ } }, this); - messageHandler.on('page_error', function transportError(data) { + messageHandler.on('PageError', function transportError(data) { var page = this.pageCache[data.pageNum]; if (page.displayReadyPromise) page.displayReadyPromise.reject(data.error); @@ -157,7 +400,7 @@ error(data.error); }, this); - messageHandler.on('jpeg_decode', function(data, promise) { + messageHandler.on('JpegDecode', function(data, promise) { var imageData = data[0]; var components = data[1]; if (components != 3 && components != 1) @@ -194,7 +437,7 @@ }, sendData: function WorkerTransport_sendData(data) { - this.messageHandler.send('getdoc_request', data); + this.messageHandler.send('GetDocRequest', data); }, getPage: function WorkerTransport_getPage(pageNumber, promise) { @@ -203,245 +446,10 @@ return this.pagePromises[pageIndex]; var promise = new PDFJS.Promise('Page ' + pageNumber); this.pagePromises[pageIndex] = promise; - this.messageHandler.send('getpage_request', { pageIndex: pageIndex }); + this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex }); return promise; } }; - function PdfPageWrapper(pageInfo, transport) { - this.pageInfo = pageInfo; - this.transport = transport; - this._stats = new StatTimer(); - this.objs = transport.objs; - } - PdfPageWrapper.prototype = { - get pageNumber() { - return this.pageInfo.pageIndex + 1; - }, - get rotate() { - return this.pageInfo.rotate; - }, - get stats() { - return this._stats; - }, - get ref() { - return this.pageInfo.ref; - }, - get view() { - return this.pageInfo.view; - }, - getViewport: function(scale, rotate) { - if (arguments.length < 2) - rotate = this.rotate; - return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); - }, - getAnnotations: function() { - var promise = new PDFJS.Promise(); - var annotations = this.pageInfo.annotations; - promise.resolve(annotations); - return promise; - }, - render: function(renderContext) { - var promise = new Promise(); - var stats = this.stats; - stats.time('Overall'); - // If there is no displayReadyPromise yet, then the operatorList was never - // requested before. Make the request and create the promise. - if (!this.displayReadyPromise) { - this.displayReadyPromise = new Promise(); + return WorkerTransport; - this.stats.time('Page Request'); - this.transport.messageHandler.send('renderpage_request', { - pageIndex: this.pageNumber - 1 - }); - } - - var callback = (function complete(error) { - if (error) - promise.reject(error); - else - promise.resolve(); - }); - - // 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); - try { - this.display(gfx, renderContext.viewport, callback); - } catch (e) { - if (callback) - callback(e); - else - error(e); - } - }.bind(this), - function pageDisplayReadPromiseError(reason) { - if (callback) - callback(reason); - else - error(reason); - } - ); - - return promise; - }, - - startRenderingFromOperatorList: - function PdfPageWrapper_startRenderingFromOperatorList(operatorList, - fonts) { - var self = this; - this.operatorList = operatorList; - - var displayContinuation = function pageDisplayContinuation() { - // Always defer call to display() to work around bug in - // Firefox error reporting from XHR callbacks. - setTimeout(function pageSetTimeout() { - self.displayReadyPromise.resolve(); - }); - }; - - this.ensureFonts(fonts, - function pageStartRenderingFromOperatorListEnsureFonts() { - displayContinuation(); - } - ); - }, - - ensureFonts: function PdfPageWrapper_ensureFonts(fonts, callback) { - this.stats.time('Font Loading'); - // Convert the font names to the corresponding font obj. - for (var i = 0, ii = fonts.length; i < ii; i++) { - fonts[i] = this.objs.objs[fonts[i]].data; - } - - // Load all the fonts - FontLoader.bind( - fonts, - function pageEnsureFontsFontObjs(fontObjs) { - this.stats.timeEnd('Font Loading'); - - callback.call(this); - }.bind(this) - ); - }, - - display: function PdfPageWrapper_display(gfx, viewport, callback) { - var stats = this.stats; - stats.time('Rendering'); - - gfx.beginDrawing(viewport); - - var startIdx = 0; - var length = this.operatorList.fnArray.length; - var operatorList = this.operatorList; - var stepper = null; - if (PDFJS.pdfBug && StepperManager.enabled) { - stepper = StepperManager.create(this.pageNumber); - stepper.init(operatorList); - stepper.nextBreakPoint = stepper.getNextBreakPoint(); - } - - var self = this; - function next() { - startIdx = - gfx.executeOperatorList(operatorList, startIdx, next, stepper); - if (startIdx == length) { - gfx.endDrawing(); - stats.timeEnd('Rendering'); - stats.timeEnd('Overall'); - if (callback) callback(); - } - } - next(); - }, - - 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(pdfInfo, transport) { - this.pdfInfo = pdfInfo; - this.transport = transport; - } - PdfDocumentWrapper.prototype = { - get numPages() { - return this.pdfInfo.numPages; - }, - get fingerprint() { - return this.pdfInfo.fingerprint; - }, - getPage: function(number) { - return this.transport.getPage(number); - }, - getDestinations: function() { - var promise = new PDFJS.Promise(); - var destinations = this.pdfInfo.destinations; - promise.resolve(destinations); - return promise; - }, - getOutline: function() { - var promise = new PDFJS.Promise(); - var outline = this.pdfInfo.outline; - promise.resolve(outline); - return promise; - }, - getMetadata: function() { - var promise = new PDFJS.Promise(); - var info = this.pdfInfo.info; - var metadata = this.pdfInfo.metadata; - promise.resolve({ - info: info, - metadata: metadata ? new PDFJS.Metadata(metadata) : null - }); - return promise; - }, - destroy: function() { - this.transport.destroy(); - } - }; - - PDFJS.getDocument = function getDocument(source) { - var promise = new PDFJS.Promise(); - var transport = new WorkerTransport(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) { - transport.sendData(data); - }); - } else { - // assuming the source is array, instantiating directly from it - transport.sendData(source); - } - return promise; - }; })(); - diff --git a/src/core.js b/src/core.js index 1aa560a46..ed35ce49e 100644 --- a/src/core.js +++ b/src/core.js @@ -311,20 +311,20 @@ var Page = (function PageClosure() { })(); /** - * The `PDFDocModel` holds all the data of the PDF file. Compared to the + * The `PDFDocument` holds all the data of the PDF file. Compared to the * `PDFDoc`, this one doesn't have any job management code. - * Right now there exists one PDFDocModel on the main thread + one object + * Right now there exists one PDFDocument on the main thread + one object * for each worker. If there is no worker support enabled, there are two - * `PDFDocModel` objects on the main thread created. + * `PDFDocument` objects on the main thread created. */ -var PDFDocModel = (function PDFDocModelClosure() { - function PDFDocModel(arg, callback) { +var PDFDocument = (function PDFDocumentClosure() { + function PDFDocument(arg, callback) { if (isStream(arg)) init.call(this, arg); else if (isArrayBuffer(arg)) init.call(this, new Stream(arg)); else - error('PDFDocModel: Unknown argument type'); + error('PDFDocument: Unknown argument type'); } function init(stream) { @@ -350,7 +350,7 @@ var PDFDocModel = (function PDFDocModelClosure() { return true; /* found */ } - PDFDocModel.prototype = { + PDFDocument.prototype = { get linearization() { var length = this.stream.length; var linearization = false; @@ -411,7 +411,7 @@ var PDFDocModel = (function PDFDocModelClosure() { }, // Find the header, remove leading garbage and setup the stream // starting from the header. - checkHeader: function PDFDocModel_checkHeader() { + checkHeader: function PDFDocument_checkHeader() { var stream = this.stream; stream.reset(); if (find(stream, '%PDF-', 1024)) { @@ -421,7 +421,7 @@ var PDFDocModel = (function PDFDocModelClosure() { } // May not be a PDF file, continue anyway. }, - setup: function PDFDocModel_setup(ownerPassword, userPassword) { + setup: function PDFDocument_setup(ownerPassword, userPassword) { this.checkHeader(); var xref = new XRef(this.stream, this.startXRef, @@ -435,7 +435,7 @@ var PDFDocModel = (function PDFDocModelClosure() { // shadow the prototype getter return shadow(this, 'numPages', num); }, - getDocumentInfo: function PDFDocModel_getDocumentInfo() { + getDocumentInfo: function PDFDocument_getDocumentInfo() { var info; if (this.xref.trailer.has('Info')) { var infoDict = this.xref.trailer.get('Info'); @@ -449,7 +449,7 @@ var PDFDocModel = (function PDFDocModelClosure() { return shadow(this, 'getDocumentInfo', info); }, - getFingerprint: function PDFDocModel_getFingerprint() { + getFingerprint: function PDFDocument_getFingerprint() { var xref = this.xref, fileID; if (xref.trailer.has('ID')) { fileID = ''; @@ -470,10 +470,10 @@ var PDFDocModel = (function PDFDocModelClosure() { return shadow(this, 'getFingerprint', fileID); }, - getPage: function PDFDocModel_getPage(n) { + getPage: function PDFDocument_getPage(n) { return this.catalog.getPage(n); } }; - return PDFDocModel; + return PDFDocument; })(); diff --git a/src/image.js b/src/image.js index 035e2f754..c8c19f9e5 100644 --- a/src/image.js +++ b/src/image.js @@ -15,7 +15,7 @@ var PDFImage = (function PDFImageClosure() { var colorSpace = dict.get('ColorSpace', 'CS'); colorSpace = ColorSpace.parse(colorSpace, xref, res); var numComps = colorSpace.numComps; - handler.send('jpeg_decode', [image.getIR(), numComps], function(message) { + handler.send('JpegDecode', [image.getIR(), numComps], function(message) { var data = message.data; var stream = new Stream(data, 0, data.length, image.dict); promise.resolve(stream); diff --git a/src/worker.js b/src/worker.js index f61e4b509..2075ff0eb 100644 --- a/src/worker.js +++ b/src/worker.js @@ -85,10 +85,10 @@ var WorkerMessageHandler = { handler.send('test', data instanceof Uint8Array); }); - handler.on('getdoc_request', function wphSetupDoc(data) { + handler.on('GetDocRequest', function wphSetupDoc(data) { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. - pdfModel = new PDFDocModel(new Stream(data)); + pdfModel = new PDFDocument(new Stream(data)); var doc = { numPages: pdfModel.numPages, fingerprint: pdfModel.fingerprint, @@ -97,10 +97,10 @@ var WorkerMessageHandler = { info: pdfModel.info, metadata: pdfModel.catalog.metadata }; - handler.send('getdoc', {pdfInfo: doc}); + handler.send('GetDoc', {pdfInfo: doc}); }); - handler.on('getpage_request', function wphSetupTest(data) { + handler.on('GetPageRequest', function wphSetupTest(data) { var pageNumber = data.pageIndex + 1; var pdfPage = pdfModel.getPage(pageNumber); var page = { @@ -110,10 +110,10 @@ var WorkerMessageHandler = { view: pdfPage.view, annotations: pdfPage.getAnnotations() }; - handler.send('getpage', {pageInfo: page}); + handler.send('GetPage', {pageInfo: page}); }); - handler.on('renderpage_request', function wphSetupPageRequest(data) { + handler.on('RenderPageRequest', function wphSetupPageRequest(data) { var pageNum = data.pageIndex + 1; @@ -152,7 +152,7 @@ var WorkerMessageHandler = { }; } - handler.send('page_error', { + handler.send('PageError', { pageNum: pageNum, error: e }); @@ -171,7 +171,7 @@ var WorkerMessageHandler = { } } - handler.send('renderpage', { + handler.send('RenderPage', { pageIndex: data.pageIndex, operatorList: operatorList, depFonts: Object.keys(fonts) From 7c35f10af84776b709bdd155a3cd794519f142e1 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Thu, 12 Apr 2012 13:04:03 -0700 Subject: [PATCH 14/29] Fix thumbnail view. --- src/core.js | 4 ---- web/viewer.js | 19 +++++++++++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/core.js b/src/core.js index ed35ce49e..bba95ab06 100644 --- a/src/core.js +++ b/src/core.js @@ -300,10 +300,6 @@ var Page = (function PageClosure() { items.push(item); } return items; - }, - startRendering: function Page_startRendering(ctx, viewport, - callback, textLayer) { -/// DELETE } }; diff --git a/web/viewer.js b/web/viewer.js index bda342aa7..4a6a91e7a 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -1036,9 +1036,9 @@ var ThumbnailView = function thumbnailView(container, pdfPage, id) { this.id = id; var maxThumbSize = 134; - var canvasWidth = pageRatio >= 1 ? maxThumbSize : + var canvasWidth = this.width = pageRatio >= 1 ? maxThumbSize : maxThumbSize * pageRatio; - var canvasHeight = pageRatio <= 1 ? maxThumbSize : + var canvasHeight = this.height = pageRatio <= 1 ? maxThumbSize : maxThumbSize / pageRatio; var scaleX = this.scaleX = (canvasWidth / pageWidth); var scaleY = this.scaleY = (canvasHeight / pageHeight); @@ -1083,11 +1083,18 @@ var ThumbnailView = function thumbnailView(container, pdfPage, id) { var ctx = getPageDrawContext(); var drawViewport = pdfPage.getViewport(scaleX); - page.startRendering(ctx, drawViewport, - function thumbnailViewDrawStartRendering() { + var renderContext = { + canvasContext: ctx, + viewport: drawViewport + }; + pdfPage.render(renderContext).then( + function pdfPageRenderCallback() { callback(); - }); - + }, + function pdfPageRenderError(error) { + callback(); + } + ); this.hasImage = true; }; From 494fd1ccf9eff089f1176bf8199ce15e9fe0488f Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Thu, 12 Apr 2012 14:02:47 -0700 Subject: [PATCH 15/29] Fixes make files, removes stats from backend, stepper --- Makefile | 1 + make.js | 1 + src/api.js | 3 ++- src/core.js | 5 ----- web/debugger.js | 32 ++++++++++++++++---------------- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 3cc423350..d4457e08f 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ all: bundle PDF_JS_FILES = \ core.js \ util.js \ + api.js \ canvas.js \ obj.js \ function.js \ diff --git a/make.js b/make.js index e0975fec8..2ee0d4cbd 100755 --- a/make.js +++ b/make.js @@ -79,6 +79,7 @@ target.bundle = function() { var SRC_FILES = ['core.js', 'util.js', + 'api.js', 'canvas.js', 'obj.js', 'function.js', diff --git a/src/api.js b/src/api.js index 72d8cae66..b14ea138d 100644 --- a/src/api.js +++ b/src/api.js @@ -80,6 +80,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { this.pageInfo = pageInfo; this.transport = transport; this._stats = new StatTimer(); + this._stats.enabled = !!globalScope.PDFJS.enableStats; this.objs = transport.objs; } PDFPageProxy.prototype = { @@ -206,7 +207,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { var operatorList = this.operatorList; var stepper = null; if (PDFJS.pdfBug && StepperManager.enabled) { - stepper = StepperManager.create(this.pageNumber); + stepper = StepperManager.create(this.pageNumber - 1); stepper.init(operatorList); stepper.nextBreakPoint = stepper.getNextBreakPoint(); } diff --git a/src/core.js b/src/core.js index bba95ab06..41f9a9c61 100644 --- a/src/core.js +++ b/src/core.js @@ -63,8 +63,6 @@ var Page = (function PageClosure() { function Page(xref, pageNumber, pageDict, ref) { this.pageNumber = pageNumber; this.pageDict = pageDict; - this.stats = new StatTimer(); - this.stats.enabled = !!globalScope.PDFJS.enableStats; this.xref = xref; this.ref = ref; @@ -139,8 +137,6 @@ var Page = (function PageClosure() { return this.operatorList; } - this.stats.time('Build IR Queue'); - var xref = this.xref; var content = this.content; var resources = this.resources; @@ -159,7 +155,6 @@ var Page = (function PageClosure() { xref, handler, 'p' + this.pageNumber + '_'); this.operatorList = pe.getOperatorList(content, resources, dependency); - this.stats.timeEnd('Build IR Queue'); return this.operatorList; }, diff --git a/web/debugger.js b/web/debugger.js index 00f5f6fd4..610a63854 100644 --- a/web/debugger.js +++ b/web/debugger.js @@ -163,29 +163,29 @@ var StepperManager = (function StepperManagerClosure() { enabled: false, active: false, // Stepper specific functions. - create: function create(pageNumber) { + create: function create(pageIndex) { var debug = document.createElement('div'); - debug.id = 'stepper' + pageNumber; + debug.id = 'stepper' + pageIndex; debug.setAttribute('hidden', true); debug.className = 'stepper'; stepperDiv.appendChild(debug); var b = document.createElement('option'); - b.textContent = 'Page ' + (pageNumber + 1); - b.value = pageNumber; + b.textContent = 'Page ' + (pageIndex + 1); + b.value = pageIndex; stepperChooser.appendChild(b); - var initBreakPoints = breakPoints[pageNumber] || []; - var stepper = new Stepper(debug, pageNumber, initBreakPoints); + var initBreakPoints = breakPoints[pageIndex] || []; + var stepper = new Stepper(debug, pageIndex, initBreakPoints); steppers.push(stepper); if (steppers.length === 1) - this.selectStepper(pageNumber, false); + this.selectStepper(pageIndex, false); return stepper; }, - selectStepper: function selectStepper(pageNumber, selectPanel) { + selectStepper: function selectStepper(pageIndex, selectPanel) { if (selectPanel) this.manager.selectPanel(1); for (var i = 0; i < steppers.length; ++i) { var stepper = steppers[i]; - if (stepper.pageNumber == pageNumber) + if (stepper.pageIndex == pageIndex) stepper.panel.removeAttribute('hidden'); else stepper.panel.setAttribute('hidden', true); @@ -193,11 +193,11 @@ var StepperManager = (function StepperManagerClosure() { var options = stepperChooser.options; for (var i = 0; i < options.length; ++i) { var option = options[i]; - option.selected = option.value == pageNumber; + option.selected = option.value == pageIndex; } }, - saveBreakPoints: function saveBreakPoints(pageNumber, bps) { - breakPoints[pageNumber] = bps; + saveBreakPoints: function saveBreakPoints(pageIndex, bps) { + breakPoints[pageIndex] = bps; sessionStorage.setItem('pdfjsBreakPoints', JSON.stringify(breakPoints)); } }; @@ -205,12 +205,12 @@ var StepperManager = (function StepperManagerClosure() { // The stepper for each page's IRQueue. var Stepper = (function StepperClosure() { - function Stepper(panel, pageNumber, initialBreakPoints) { + function Stepper(panel, pageIndex, initialBreakPoints) { this.panel = panel; this.len; this.breakPoint = 0; this.nextBreakPoint = null; - this.pageNumber = pageNumber; + this.pageIndex = pageIndex; this.breakPoints = initialBreakPoints; this.currentIdx = -1; } @@ -256,7 +256,7 @@ var Stepper = (function StepperClosure() { self.breakPoints.push(x); else self.breakPoints.splice(self.breakPoints.indexOf(x), 1); - StepperManager.saveBreakPoints(self.pageNumber, self.breakPoints); + StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints); } })(i); @@ -278,7 +278,7 @@ var Stepper = (function StepperClosure() { return null; }, breakIt: function breakIt(idx, callback) { - StepperManager.selectStepper(this.pageNumber, true); + StepperManager.selectStepper(this.pageIndex, true); var self = this; var dom = document; self.currentIdx = idx; From 07fc34551d7785433a6d2d0d6ca79d2e32220b17 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Thu, 12 Apr 2012 14:07:11 -0700 Subject: [PATCH 16/29] Minor clean up. --- src/api.js | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/api.js b/src/api.js index 72d8cae66..841f3fe00 100644 --- a/src/api.js +++ b/src/api.js @@ -79,7 +79,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { function PDFPageProxy(pageInfo, transport) { this.pageInfo = pageInfo; this.transport = transport; - this._stats = new StatTimer(); + this.stats = new StatTimer(); this.objs = transport.objs; } PDFPageProxy.prototype = { @@ -89,9 +89,6 @@ var PDFPageProxy = (function PDFPageProxyClosure() { get rotate() { return this.pageInfo.rotate; }, - get stats() { - return this._stats; - }, get ref() { return this.pageInfo.ref; }, @@ -124,12 +121,12 @@ var PDFPageProxy = (function PDFPageProxyClosure() { }); } - var callback = (function complete(error) { - if (error) - promise.reject(error); - else - promise.resolve(); - }); + function complete(error) { + if (error) + promise.reject(error); + else + promise.resolve(); + }; // Once the operatorList and fonts are loaded, do the actual rendering. this.displayReadyPromise.then( @@ -137,19 +134,13 @@ var PDFPageProxy = (function PDFPageProxyClosure() { var gfx = new CanvasGraphics(renderContext.canvasContext, this.objs, renderContext.textLayer); try { - this.display(gfx, renderContext.viewport, callback); + this.display(gfx, renderContext.viewport, complete); } catch (e) { - if (callback) - callback(e); - else - error(e); + complete(e); } }.bind(this), function pageDisplayReadPromiseError(reason) { - if (callback) - callback(reason); - else - error(reason); + complete(reason); } ); From 23df48bf0e540277348121603fcd0c368fe87ff1 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Thu, 12 Apr 2012 15:14:18 -0700 Subject: [PATCH 17/29] Fix page error handling. --- src/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api.js b/src/api.js index 05e8a952f..f1baefade 100644 --- a/src/api.js +++ b/src/api.js @@ -385,7 +385,7 @@ var WorkerTransport = (function WorkerTransportClosure() { }, this); messageHandler.on('PageError', function transportError(data) { - var page = this.pageCache[data.pageNum]; + var page = this.pageCache[data.pageNum - 1]; if (page.displayReadyPromise) page.displayReadyPromise.reject(data.error); else From eba8f5a22cc8997a73947ba5cc023edf8c50cc9d Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Thu, 12 Apr 2012 15:14:18 -0700 Subject: [PATCH 18/29] Fix finger print, remove unused code --- src/worker.js | 2 +- web/viewer.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/worker.js b/src/worker.js index 2075ff0eb..d7b9b5718 100644 --- a/src/worker.js +++ b/src/worker.js @@ -91,7 +91,7 @@ var WorkerMessageHandler = { pdfModel = new PDFDocument(new Stream(data)); var doc = { numPages: pdfModel.numPages, - fingerprint: pdfModel.fingerprint, + fingerprint: pdfModel.getFingerprint(), destinations: pdfModel.catalog.destinations, outline: pdfModel.catalog.documentOutline, info: pdfModel.info, diff --git a/web/viewer.js b/web/viewer.js index 4a6a91e7a..35ee38e42 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -860,7 +860,6 @@ var PageView = function pageView(container, pdfPage, id, scale, } this.getPagePoint = function pageViewGetPagePoint(x, y) { - var scale = PDFView.currentScale; return this.viewport.convertToPdfPoint(x, y); }; From dee158d80c926e02c7a7573bc3d87659b2e64408 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Thu, 12 Apr 2012 15:56:17 -0700 Subject: [PATCH 19/29] Fix title info for PDF document --- src/worker.js | 2 +- web/viewer.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/worker.js b/src/worker.js index d7b9b5718..5cecc6cf2 100644 --- a/src/worker.js +++ b/src/worker.js @@ -94,7 +94,7 @@ var WorkerMessageHandler = { fingerprint: pdfModel.getFingerprint(), destinations: pdfModel.catalog.destinations, outline: pdfModel.catalog.documentOutline, - info: pdfModel.info, + info: pdfModel.getDocumentInfo(), metadata: pdfModel.catalog.metadata }; handler.send('GetDoc', {pdfInfo: doc}); diff --git a/web/viewer.js b/web/viewer.js index 35ee38e42..5b43ee7e1 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -550,7 +550,8 @@ var PDFView = { self.setInitialView(storedHash, scale); }); - pdfDocument.getMetadata().then(function(info, metadata) { + pdfDocument.getMetadata().then(function(data) { + var info = data.info, metadata = data.metadata; self.documentInfo = info; self.metadata = metadata; From 3925e374177d9e5b8d470c92b8b23c31fdf11b4e Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Thu, 12 Apr 2012 17:59:30 -0700 Subject: [PATCH 20/29] Add basic api unit testing. --- test/pdfs/.gitignore | 2 +- test/pdfs/basicapi.pdf | Bin 0 -> 105779 bytes test/unit/api_spec.js | 107 ++++++++++++++++++++++++++++++++++++ test/unit/jsTestDriver.conf | 60 ++++++++++---------- 4 files changed, 139 insertions(+), 30 deletions(-) create mode 100644 test/pdfs/basicapi.pdf create mode 100644 test/unit/api_spec.js diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 2a7c27875..e4b9fb10c 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -31,4 +31,4 @@ !issue1002.pdf !issue925.pdf !gradientfill.pdf - +!basicapi.pdf diff --git a/test/pdfs/basicapi.pdf b/test/pdfs/basicapi.pdf new file mode 100644 index 0000000000000000000000000000000000000000..31ffcfe9feb708602cb42c25534eaa6ef12b2c25 GIT binary patch literal 105779 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 BznTC5 literal 0 HcmV?d00001 diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js new file mode 100644 index 000000000..72c6434b1 --- /dev/null +++ b/test/unit/api_spec.js @@ -0,0 +1,107 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +describe('api', function() { + var basicApiUrl = '/basicapi.pdf'; + function waitsForPromise(promise) { + waitsFor(function() { + return promise.isResolved || promise.isRejected; + }, 250); + } + function expectAfterPromise(promise, successCallback) { + waitsForPromise(promise); + runs(function() { + promise.then(successCallback, + function(error, e) { + // Shouldn't get here. + expect(false).toEqual(true); + }); + }); + } + describe('PDFJS', function() { + describe('getDocument', function() { + it('creates pdf doc from URL', function() { + console.log('what is'); + debugger; + var promise = PDFJS.getDocument(basicApiUrl); + expectAfterPromise(promise, function(data) { + expect(true).toEqual(true); + }); + }); + /* + it('creates pdf doc from typed array', function() { + // TODO + }); + */ + }); + }); + describe('PDFDocument', function() { + var promise = PDFJS.getDocument(basicApiUrl); + waitsForPromise(promise); + var doc; + runs(function() { + promise.then(function(data) { doc = data; }); + }); + it('gets number of pages', function() { + expect(doc.numPages).toEqual(3); + }); + it('gets fingerprint', function() { + expect(typeof doc.fingerprint).toEqual('string'); + }); + it('gets page', function() { + var promise = doc.getPage(1); + expectAfterPromise(promise, function(data) { + expect(true).toEqual(true); + }); + }); + it('gets destinations', function() { + var promise = doc.getDestinations(); + expectAfterPromise(promise, function(data) { + // TODO this seems to be broken for the test pdf + }); + }); + it('gets outline', function() { + var promise = doc.getOutline(); + expectAfterPromise(promise, function(outline) { + // Two top level entries. + expect(outline.length).toEqual(2); + // Make sure some basic attributes are set. + expect(outline[1].title).toEqual('Chapter 1'); + expect(outline[1].items.length).toEqual(1); + expect(outline[1].items[0].title).toEqual('Paragraph 1.1'); + }); + }); + it('gets metadata', function() { + var promise = doc.getMetadata(); + expectAfterPromise(promise, function(metadata) { + expect(metadata.info['Title']).toEqual('Basic API Test'); + expect(metadata.metadata.get('dc:title')).toEqual('Basic API Test'); + }); + }); + }); + describe('Page', function() { + var promise = new Promise(); + PDFJS.getDocument(basicApiUrl).then(function(doc) { + doc.getPage(1).then(function(data) { + promise.resolve(data); + }); + }); + waitsForPromise(promise); + var page; + runs(function() { + promise.then(function(data) { + page = data; + }); + }); + it('gets ref', function() { + expect(page.ref).toEqual({num: 15, gen: 0}); + }); + // TODO rotate + // TODO viewport + // TODO annotaions + // TOOD text content + // TODO operation list + }); +}); diff --git a/test/unit/jsTestDriver.conf b/test/unit/jsTestDriver.conf index 9a26df6a4..8ff218093 100644 --- a/test/unit/jsTestDriver.conf +++ b/test/unit/jsTestDriver.conf @@ -1,32 +1,34 @@ server: http://localhost:4224 -load: - - ../../external/jasmine/jasmine.js - - ../../external/jasmineAdapter/JasmineAdapter.js - - ../../src/obj.js - - ../../src/core.js - - ../../src/util.js - - ../../src/canvas.js - - ../../src/obj.js - - ../../src/function.js - - ../../src/charsets.js - - ../../src/cidmaps.js - - ../../src/colorspace.js - - ../../src/crypto.js - - ../../src/evaluator.js - - ../../src/fonts.js - - ../../src/glyphlist.js - - ../../src/image.js - - ../../src/metrics.js - - ../../src/parser.js - - ../../src/pattern.js - - ../../src/stream.js - - ../../src/worker.js - - ../../src/bidi.js - - ../../external/jpgjs/jpg.js - - ../unit/obj_spec.js - - ../unit/font_spec.js - - ../unit/function_spec.js - - ../unit/crypto_spec.js - - ../unit/stream_spec.js +basepath: .. +load: + - ../external/jasmine/jasmine.js + - ../external/jasmineAdapter/JasmineAdapter.js + - ../src/obj.js + - ../src/core.js + - ../src/util.js + - ../src/api.js + - ../src/canvas.js + - ../src/obj.js + - ../src/function.js + - ../src/charsets.js + - ../src/cidmaps.js + - ../src/colorspace.js + - ../src/crypto.js + - ../src/evaluator.js + - ../src/fonts.js + - ../src/glyphlist.js + - ../src/image.js + - ../src/metrics.js + - ../src/parser.js + - ../src/pattern.js + - ../src/stream.js + - ../src/worker.js + - ../src/bidi.js + - ../src/metadata.js + - ../external/jpgjs/jpg.js + - unit/api_spec.js + +gateway: + - {matcher: "*.pdf", server: "http://localhost:8888/test/pdfs/"} From 19c0c6a983562294251bfa2fa02a1b2a71bc876c Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Thu, 12 Apr 2012 18:09:25 -0700 Subject: [PATCH 21/29] Add back other unit tests. Disable worker. --- test/unit/api_spec.js | 2 ++ test/unit/jsTestDriver.conf | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 72c6434b1..318dbb42a 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -4,6 +4,8 @@ 'use strict'; describe('api', function() { + // TODO run with worker enabled + PDFJS.disableWorker = true; var basicApiUrl = '/basicapi.pdf'; function waitsForPromise(promise) { waitsFor(function() { diff --git a/test/unit/jsTestDriver.conf b/test/unit/jsTestDriver.conf index 8ff218093..b0f917b66 100644 --- a/test/unit/jsTestDriver.conf +++ b/test/unit/jsTestDriver.conf @@ -28,6 +28,11 @@ load: - ../src/bidi.js - ../src/metadata.js - ../external/jpgjs/jpg.js + - unit/obj_spec.js + - unit/font_spec.js + - unit/function_spec.js + - unit/crypto_spec.js + - unit/stream_spec.js - unit/api_spec.js gateway: From c207d4a7d6e383de4c9b1b1eedbda6f56860f1b7 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Fri, 13 Apr 2012 09:25:08 -0700 Subject: [PATCH 22/29] Add docs to API. --- src/api.js | 118 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 9 deletions(-) diff --git a/src/api.js b/src/api.js index f1baefade..74c58a61d 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,66 @@ 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() { var promise = new PDFJS.Promise(); var annotations = this.pageInfo.annotations; promise.resolve(annotations); 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 +219,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 +234,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { return promise; }, - + /** + * For internal use only. + */ startRenderingFromOperatorList: function PDFPageWrapper_startRenderingFromOperatorList(operatorList, fonts) { @@ -168,7 +257,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 +277,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'); @@ -216,13 +309,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 +333,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { }; return PDFPageProxy; })(); - +/** + * For internal use only. + */ var WorkerTransport = (function WorkerTransportClosure() { function WorkerTransport(promise) { this.workerReadyPromise = promise; From f679f0dfe8bc0f6d2f54a8f645576011ffcbd9ab Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Fri, 13 Apr 2012 09:33:36 -0700 Subject: [PATCH 23/29] Move open after everything is initialized. --- web/viewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); /** From e1b4fc5ac73c7466ef6b12b80e93119a9520531e Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Fri, 13 Apr 2012 16:04:57 -0500 Subject: [PATCH 24/29] Enabled workers during testing --- test/driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/driver.js b/test/driver.js index be66aa216..26c5a156a 100644 --- a/test/driver.js +++ b/test/driver.js @@ -10,7 +10,7 @@ // Disable worker support for running test as // https://github.com/mozilla/pdf.js/pull/764#issuecomment-2638944 // "firefox-bin: Fatal IO error 12 (Cannot allocate memory) on X server :1." -PDFJS.disableWorker = true; +// PDFJS.disableWorker = true; var appPath, browser, canvas, currentTaskIdx, manifest, stdout; var inFlightRequests = 0; From 42911f1fc975b6f87a39ddb7bf66dc44dfeae37d Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Sat, 14 Apr 2012 13:54:31 -0700 Subject: [PATCH 25/29] Async getAnnotations(); hide map and xref for Dict --- src/api.js | 19 ++++++++++++-- src/evaluator.js | 2 +- src/obj.js | 64 +++++++++++++++++++++++++----------------------- src/worker.js | 16 ++++++++---- 4 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/api.js b/src/api.js index f1baefade..479134bd2 100644 --- a/src/api.js +++ b/src/api.js @@ -102,9 +102,12 @@ var PDFPageProxy = (function PDFPageProxyClosure() { return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); }, 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) { @@ -209,6 +212,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(); @@ -342,6 +346,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 +450,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..50274a9ed 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); } } 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, From 6bf640260fec1ee16226613e50d96f58fd4bfc08 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Sat, 14 Apr 2012 17:52:49 -0700 Subject: [PATCH 26/29] Fix jsdoc comment; remove resources dict from type3 properties --- src/api.js | 7 ++++++- src/evaluator.js | 1 - src/fonts.js | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api.js b/src/api.js index 996dd1b42..0e27d2369 100644 --- a/src/api.js +++ b/src/api.js @@ -193,7 +193,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { * 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. */ @@ -415,6 +415,11 @@ var WorkerTransport = (function WorkerTransportClosure() { var fakeWorker = { postMessage: function WorkerTransport_postMessage(obj) { fakeWorker.onmessage({data: obj}); + try { + testF.contentWindow.postMessage(obj, "*"); + } catch(e) { + debugger; + } }, terminate: function WorkerTransport_terminate() {} }; diff --git a/src/evaluator.js b/src/evaluator.js index 50274a9ed..c57e291c0 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -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('+'); From 12b0282836d5fb9ee3d72106e7a7897150c05993 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Sat, 14 Apr 2012 17:57:14 -0500 Subject: [PATCH 27/29] Remove debug code --- src/api.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/api.js b/src/api.js index 0e27d2369..18644ebe6 100644 --- a/src/api.js +++ b/src/api.js @@ -415,11 +415,6 @@ var WorkerTransport = (function WorkerTransportClosure() { var fakeWorker = { postMessage: function WorkerTransport_postMessage(obj) { fakeWorker.onmessage({data: obj}); - try { - testF.contentWindow.postMessage(obj, "*"); - } catch(e) { - debugger; - } }, terminate: function WorkerTransport_terminate() {} }; From 50349658affcb3bfa18705c952563289fff14537 Mon Sep 17 00:00:00 2001 From: Yury Delendik <ydelendik@mozilla.com> Date: Sun, 15 Apr 2012 21:12:00 -0500 Subject: [PATCH 28/29] Temporary avoiding chrome on linux failures --- test/test_manifest.json | 1 + 1 file changed, 1 insertion(+) 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" }, From 04c8d1454d9fa30c376b08b667184c6bf449819e Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Mon, 16 Apr 2012 09:45:49 -0700 Subject: [PATCH 29/29] Add Util functions to PDFJS. --- src/util.js | 2 +- web/viewer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.js b/src/util.js index 6ec4bc9cb..63f6115a7 100644 --- a/src/util.js +++ b/src/util.js @@ -76,7 +76,7 @@ function stringToBytes(str) { var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; -var Util = (function UtilClosure() { +var Util = PDFJS.Util = (function UtilClosure() { function Util() {} Util.makeCssRgb = function Util_makeCssRgb(r, g, b) { diff --git a/web/viewer.js b/web/viewer.js index b1876c814..68f0a6a33 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -787,7 +787,7 @@ var PageView = function pageView(container, pdfPage, id, scale, } function createElementWithStyle(tagName, item) { var rect = viewport.convertToViewportRectangle(item.rect); - rect = Util.normalizeRect(rect); + rect = PDFJS.Util.normalizeRect(rect); var element = document.createElement(tagName); element.style.left = Math.floor(rect[0]) + 'px'; element.style.top = Math.floor(rect[1]) + 'px';