From 5830474ef7796c8c16d565a609e7a490d44cf63d Mon Sep 17 00:00:00 2001
From: Julian Viereck <julian.viereck@gmail.com>
Date: Sun, 18 Dec 2011 22:36:36 +0100
Subject: [PATCH 1/5] Add preDraw() functionality to render the next page in
 the background

---
 web/viewer.js | 45 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 43 insertions(+), 2 deletions(-)

diff --git a/web/viewer.js b/web/viewer.js
index 6065a2f68..9646ccd0c 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -33,6 +33,9 @@ var RenderingQueue = (function RenderingQueueClosure() {
 
   RenderingQueue.prototype = {
     enqueueDraw: function RenderingQueueEnqueueDraw(item) {
+      if (!item.drawingRequired())
+        return; // as no redraw required, no need for queueing.
+
       if ('rendering' in item)
         return; // is already in the queue
 
@@ -366,6 +369,7 @@ var PDFView = {
       // when page is painted, using the image as thumbnail base
       pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
         thumbnailView.setImage(pageView.canvas);
+        preDraw();
       };
     }
 
@@ -759,8 +763,12 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
       }, 0);
   };
 
+  this.drawingRequired = function() {
+    return !div.hasChildNodes();
+  };
+
   this.draw = function pageviewDraw(callback) {
-    if (div.hasChildNodes()) {
+    if (!this.drawingRequired()) {
       this.updateStats();
       callback();
       return;
@@ -1004,16 +1012,49 @@ window.addEventListener('unload', function webViewerUnload(evt) {
   window.scrollTo(0, 0);
 }, true);
 
+/**
+ * Render the next not yet visible page already such that it is
+ * hopefully ready once the user scrolls to it.
+ */
+function preDraw() {
+  var pages = PDFView.pages;
+  var visible = PDFView.getVisiblePages();
+  var last = visible[visible.length - 1];
+  // PageView.id is the actual page number, which is + 1 compared
+  // to the index in `pages`. That means, pages[last.id] is the next
+  // PageView instance.
+  if (pages[last.id] && pages[last.id].drawingRequired()) {
+    renderingQueue.enqueueDraw(pages[last.id]);
+    return;
+  }
+  // If there is nothing to draw on the next page, maybe the user
+  // is scrolling up, so, let's try to render the next page *before*
+  // the first visible page
+  if (pages[visible[0].id - 2]) {
+    renderingQueue.enqueueDraw(pages[visible[0].id - 2]);
+  }
+}
+
 function updateViewarea() {
   var visiblePages = PDFView.getVisiblePages();
+  var pageToDraw;
   for (var i = 0; i < visiblePages.length; i++) {
     var page = visiblePages[i];
-    renderingQueue.enqueueDraw(PDFView.pages[page.id - 1]);
+    var pageObj = PDFView.pages[page.id - 1];
+
+    pageToDraw |= pageObj.drawingRequired();
+    renderingQueue.enqueueDraw(pageObj);
   }
 
   if (!visiblePages.length)
     return;
 
+  // If there is no need to draw a page that is currenlty visible, preDraw the
+  // next page the user might scroll to.
+  if (!pageToDraw) {
+    preDraw();
+  }
+
   updateViewarea.inProgress = true; // used in "set page"
   var currentId = PDFView.page;
   var firstPage = visiblePages[0];

From 5bd4d1987a99266fc9c1ef668da91e78f3ef608c Mon Sep 17 00:00:00 2001
From: Brendan Dahl <brendan.dahl@gmail.com>
Date: Wed, 4 Jan 2012 14:43:17 -0800
Subject: [PATCH 2/5] Easier copy/paste error message.

---
 web/viewer.css  | 2 +-
 web/viewer.html | 2 +-
 web/viewer.js   | 7 ++++---
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/web/viewer.css b/web/viewer.css
index 072b2c033..e355f7fc2 100644
--- a/web/viewer.css
+++ b/web/viewer.css
@@ -330,7 +330,7 @@ canvas {
   color: black;
   padding: 3px;
   margin: 3px;
-  white-space: pre;
+  width: 98%;
 }
 
 .clearBoth {
diff --git a/web/viewer.html b/web/viewer.html
index 869bf0f2e..40e99004f 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -114,7 +114,7 @@
         </button>
       </div>
       <div class="clearBoth"></div>
-      <div id="errorMoreInfo" hidden='true'></div>
+      <textarea id="errorMoreInfo" hidden='true' readonly="readonly"></textarea>
     </div>
 
     <div id="sidebar">
diff --git a/web/viewer.js b/web/viewer.js
index 6065a2f68..f089d8c24 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -346,13 +346,14 @@ var PDFView = {
     };
     moreInfoButton.removeAttribute('hidden');
     lessInfoButton.setAttribute('hidden', 'true');
-    errorMoreInfo.innerHTML = 'PDF.JS Build: ' + PDFJS.build + '\n';
+    errorMoreInfo.value = 'PDF.JS Build: ' + PDFJS.build + '\n';
 
     if (moreInfo) {
-      errorMoreInfo.innerHTML += 'Message: ' + moreInfo.message;
+      errorMoreInfo.value += 'Message: ' + moreInfo.message;
       if (moreInfo.stack)
-        errorMoreInfo.innerHTML += '\n' + 'Stack: ' + moreInfo.stack;
+        errorMoreInfo.value += '\n' + 'Stack: ' + moreInfo.stack;
     }
+    errorMoreInfo.rows = errorMoreInfo.value.split('\n').length - 1;
   },
 
   progress: function pdfViewProgress(level) {

From 567dd330e1dea440e0bb5f57706578278609a351 Mon Sep 17 00:00:00 2001
From: Jeff Wagner <jwagner@gmail.com>
Date: Wed, 4 Jan 2012 15:10:25 -0800
Subject: [PATCH 3/5] Fix subarray error for IE9

---
 web/compatibility.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/web/compatibility.js b/web/compatibility.js
index 66e46b51b..26405ad8f 100644
--- a/web/compatibility.js
+++ b/web/compatibility.js
@@ -14,7 +14,7 @@
   }
 
   function subarray(start, end) {
-    return this.slice(start, end);
+    return new TypedArray(this.slice(start, end));
   }
 
   function setArrayOffset(array, offset) {

From dddcb9c91bf00f4ce94a6be281f0de7470e7fe51 Mon Sep 17 00:00:00 2001
From: Brendan Dahl <brendan.dahl@gmail.com>
Date: Wed, 4 Jan 2012 16:13:53 -0800
Subject: [PATCH 4/5] Add error backs for promises.

---
 src/core.js | 30 +++++++++++++++++++-----------
 src/util.js | 30 +++++++++++++++++++++++++++++-
 2 files changed, 48 insertions(+), 12 deletions(-)

diff --git a/src/core.js b/src/core.js
index 8b5fbea75..f262cde45 100644
--- a/src/core.js
+++ b/src/core.js
@@ -398,17 +398,25 @@ var Page = (function PageClosure() {
       }
 
       // Once the IRQueue and fonts are loaded, perform the actual rendering.
-      this.displayReadyPromise.then(function pageDisplayReadyPromise() {
-        var gfx = new CanvasGraphics(ctx, this.objs, textLayer);
-        try {
-          this.display(gfx, callback);
-        } catch (e) {
-          if (self.callback)
-            self.callback(e);
+      this.displayReadyPromise.then(
+        function pageDisplayReadyPromise() {
+          var gfx = new CanvasGraphics(ctx, this.objs, textLayer);
+          try {
+            this.display(gfx, callback);
+          } catch (e) {
+            if (callback)
+              callback(e);
+            else
+              throw e;
+          }
+        }.bind(this),
+        function pageDisplayReadPromiseError(reason) {
+          if (callback)
+            callback(reason);
           else
-            throw e;
+            throw reason;
         }
-      }.bind(this));
+      );
     }
   };
 
@@ -722,8 +730,8 @@ var PDFDoc = (function PDFDocClosure() {
 
       messageHandler.on('page_error', function pdfDocError(data) {
         var page = this.pageCache[data.pageNum];
-        if (page.callback)
-          page.callback(data.error);
+        if (page.displayReadyPromise) {
+          page.displayReadyPromise.reject(data.error);}
         else
           throw data.error;
       }, this);
diff --git a/src/util.js b/src/util.js
index 4d8918e48..99b422296 100644
--- a/src/util.js
+++ b/src/util.js
@@ -206,6 +206,8 @@ var Promise = (function PromiseClosure() {
    */
   function Promise(name, data) {
     this.name = name;
+    this.isRejected = false;
+    this.error = null;
     // If you build a promise and pass in some data it's already resolved.
     if (data != null) {
       this.isResolved = true;
@@ -216,6 +218,7 @@ var Promise = (function PromiseClosure() {
       this._data = EMPTY_PROMISE;
     }
     this.callbacks = [];
+    this.errbacks = [];
   };
   /**
    * Builds a promise that is resolved when all the passed in promises are
@@ -282,6 +285,9 @@ var Promise = (function PromiseClosure() {
       if (this.isResolved) {
         throw 'A Promise can be resolved only once ' + this.name;
       }
+      if (this.isRejected) {
+        throw 'The Promise was already rejected ' + this.name;
+      }
 
       this.isResolved = true;
       this.data = data || null;
@@ -292,7 +298,24 @@ var Promise = (function PromiseClosure() {
       }
     },
 
-    then: function promiseThen(callback) {
+    reject: function proimseReject(reason) {
+      if (this.isRejected) {
+        throw 'A Promise can be rejected only once ' + this.name;
+      }
+      if (this.isResolved) {
+        throw 'The Promise was already resolved ' + this.name;
+      }
+
+      this.isRejected = true;
+      this.error = reason || null;
+      var errbacks = this.errbacks;
+
+      for (var i = 0, ii = errbacks.length; i < ii; i++) {
+        errbacks[i].call(null, reason);
+      }
+    },
+
+    then: function promiseThen(callback, errback) {
       if (!callback) {
         throw 'Requiring callback' + this.name;
       }
@@ -301,8 +324,13 @@ var Promise = (function PromiseClosure() {
       if (this.isResolved) {
         var data = this.data;
         callback.call(null, data);
+      } else if (this.isRejected && errorback) {
+        var error = this.error;
+        errback.call(null, error);
       } else {
         this.callbacks.push(callback);
+        if (errback)
+          this.errbacks.push(errback);
       }
     }
   };

From 3154ec4e38796c38fd27848dbd13447b463f508c Mon Sep 17 00:00:00 2001
From: Brendan Dahl <brendan.dahl@gmail.com>
Date: Wed, 4 Jan 2012 17:22:07 -0800
Subject: [PATCH 5/5] Fix bracket nit.

---
 src/core.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/core.js b/src/core.js
index f262cde45..4cc5984e2 100644
--- a/src/core.js
+++ b/src/core.js
@@ -730,8 +730,8 @@ var PDFDoc = (function PDFDocClosure() {
 
       messageHandler.on('page_error', function pdfDocError(data) {
         var page = this.pageCache[data.pageNum];
-        if (page.displayReadyPromise) {
-          page.displayReadyPromise.reject(data.error);}
+        if (page.displayReadyPromise)
+          page.displayReadyPromise.reject(data.error);
         else
           throw data.error;
       }, this);