From 737ed8417472b9c5084f963de95e3f87779dd7b2 Mon Sep 17 00:00:00 2001 From: Yury Delendik 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 @@ + 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 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 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 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 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 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 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 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 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 @@ + From b312719d7e0cf1a45dda0b4ad871a45162ed6f5b Mon Sep 17 00:00:00 2001 From: Yury Delendik 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 @@ + 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 @@ + 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 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 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 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 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 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 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 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 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 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 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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 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 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 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 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 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 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 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 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 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';