]> git.parisson.com Git - pdf.js.git/commitdiff
Change to priority rendering.
authorBrendan Dahl <brendan.dahl@gmail.com>
Mon, 18 Jun 2012 16:48:47 +0000 (09:48 -0700)
committerBrendan Dahl <brendan.dahl@gmail.com>
Mon, 18 Jun 2012 16:48:47 +0000 (09:48 -0700)
src/api.js
web/viewer.js

index 7d65f96b4e2d3698e95769144d434b5290c71263..2ab23bdde62dadf84ff0bbbbd7bddd06526dc912 100644 (file)
@@ -234,7 +234,11 @@ 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.
+     *                        appendText functions.,
+     *   continueCallback(optional): A function that will be called each time
+     *                               the rendering is paused.  To continue
+     *                               rendering call the function that is the
+     *                               first argument to the callback.
      * }.
      * @return {Promise} A promise that is resolved when the page finishes
      * rendering.
@@ -270,6 +274,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
         else
           promise.resolve();
       };
+      var continueCallback = params.continueCallback;
 
       // Once the operatorList and fonts are loaded, do the actual rendering.
       this.displayReadyPromise.then(
@@ -282,7 +287,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
           var gfx = new CanvasGraphics(params.canvasContext,
             this.objs, params.textLayer);
           try {
-            this.display(gfx, params.viewport, complete);
+            this.display(gfx, params.viewport, complete, continueCallback);
           } catch (e) {
             complete(e);
           }
@@ -340,7 +345,8 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
     /**
      * For internal use only.
      */
-    display: function PDFPageProxy_display(gfx, viewport, callback) {
+    display: function PDFPageProxy_display(gfx, viewport, callback,
+                                           continueCallback) {
       var stats = this.stats;
       stats.time('Rendering');
 
@@ -356,10 +362,16 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
         stepper.nextBreakPoint = stepper.getNextBreakPoint();
       }
 
+      var continueWrapper;
+      if (continueCallback)
+        continueWrapper = function() { continueCallback(next); }
+      else
+        continueWrapper = next;
+
       var self = this;
       function next() {
-        startIdx =
-          gfx.executeOperatorList(operatorList, startIdx, next, stepper);
+        startIdx = gfx.executeOperatorList(operatorList, startIdx,
+                                           continueWrapper, stepper);
         if (startIdx == length) {
           gfx.endDrawing();
           stats.timeEnd('Rendering');
@@ -367,7 +379,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
           if (callback) callback();
         }
       }
-      next();
+      continueWrapper();
     },
     /**
      * @return {Promise} That is resolved with the a {string} that is the text
index 29b57a9484a4041eb03b59b40e76486745a0b9a0..040bf91b8a9468a677fc5b354461621a10df3554 100644 (file)
@@ -14,6 +14,13 @@ var kMinScale = 0.25;
 var kMaxScale = 4.0;
 var kImageDirectory = './images/';
 var kSettingsMemory = 20;
+var RenderingStates = {
+  INITIAL: 0,
+  RUNNING: 1,
+  PAUSED: 2,
+  FINISHED: 3
+};
+
 
 var mozL10n = document.mozL10n || document.webL10n;
 
@@ -83,36 +90,6 @@ var ProgressBar = (function ProgressBarClosure() {
   return ProgressBar;
 })();
 
-var RenderingQueue = (function RenderingQueueClosure() {
-  function RenderingQueue() {
-    this.items = [];
-  }
-
-  RenderingQueue.prototype = {
-    enqueueDraw: function RenderingQueueEnqueueDraw(item) {
-      if (!item.drawingRequired())
-        return; // as no redraw required, no need for queueing.
-
-      this.items.push(item);
-      if (this.items.length > 1)
-        return; // not first item
-
-      item.draw(this.continueExecution.bind(this));
-    },
-    continueExecution: function RenderingQueueContinueExecution() {
-      var item = this.items.shift();
-
-      if (this.items.length == 0)
-        return; // queue is empty
-
-      item = this.items[0];
-      item.draw(this.continueExecution.bind(this));
-    }
-  };
-
-  return RenderingQueue;
-})();
-
 var FirefoxCom = (function FirefoxComClosure() {
   return {
     /**
@@ -246,7 +223,6 @@ var Settings = (function SettingsClosure() {
 })();
 
 var cache = new Cache(kCacheSize);
-var renderingQueue = new RenderingQueue();
 var currentPageNumber = 1;
 
 var PDFView = {
@@ -258,15 +234,47 @@ var PDFView = {
   startedTextExtraction: false,
   pageText: [],
   container: null,
+  thumbnailContainer: null,
   initialized: false,
   fellback: false,
   pdfDocument: null,
+  sidebarOpen: false,
+  pageViewScroll: null,
+  thumbnailViewScroll: null,
+
   // called once when the document is loaded
   initialize: function pdfViewInitialize() {
-    this.container = document.getElementById('viewerContainer');
+    var container = this.container = document.getElementById('viewerContainer');
+    this.pageViewScroll = {};
+    this.watchScroll(container, this.pageViewScroll, updateViewarea);
+
+    var thumbnailContainer = this.thumbnailContainer =
+                             document.getElementById('thumbnailView');
+    this.thumbnailViewScroll = {};
+    this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
+                     this.renderHighestPriority.bind(this));
+
     this.initialized = true;
   },
 
+  // Helper function to keep track whether a div was scrolled up or down and
+  // then call a callback.
+  watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
+    state.down = true;
+    state.lastY = viewAreaElement.scrollTop;
+    viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) {
+      var currentY = viewAreaElement.scrollTop;
+      var lastY = state.lastY;
+      if (currentY > lastY)
+        state.down = true;
+      else if (currentY < lastY)
+        state.down = false;
+      // else do nothing and use previous value
+      state.lastY = currentY;
+      callback();
+    }, true);
+  },
+
   setScale: function pdfViewSetScale(val, resetAutoSettings) {
     if (val == this.currentScale)
       return;
@@ -610,7 +618,6 @@ var PDFView = {
       // when page is painted, using the image as thumbnail base
       pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
         thumbnailView.setImage(pageView.canvas);
-        preDraw();
       };
     }
 
@@ -735,6 +742,88 @@ var PDFView = {
     }
   },
 
+  renderHighestPriority: function pdfViewRenderHighestPriority() {
+    // Pages have a higher priority than thumbnails, so check them first.
+    var visiblePages = this.getVisiblePages();
+    var pageView = this.getHighestPriority(visiblePages, this.pages,
+                                           this.pageViewScroll.down);
+    if (pageView) {
+      this.renderView(pageView, 'page');
+      return;
+    }
+    // No pages needed rendering so check thumbnails.
+    if (this.sidebarOpen) {
+      var visibleThumbs = this.getVisibleThumbs();
+      var thumbView = this.getHighestPriority(visibleThumbs,
+                                              this.thumbnails,
+                                              this.thumbnailViewScroll.down);
+      if (thumbView)
+        this.renderView(thumbView, 'thumbnail');
+    }
+  },
+
+  getHighestPriority: function pdfViewGetHighestPriority(visibleViews, views,
+                                                         scrolledDown) {
+    // The state has changed figure out which page has the highest priority to
+    // render next (if any).
+    // Priority:
+    // 1 visible pages
+    // 2 if last scrolled down page after the visible pages
+    // 2 if last scrolled up page before the visible pages
+    var numVisible = visibleViews.length;
+    if (numVisible === 0) {
+      info('No visible views.');
+      return false;
+    }
+    for (var i = 0; i < numVisible; ++i) {
+      var view = visibleViews[i].view;
+      if (!this.isViewFinshed(view))
+        return view;
+    }
+
+    // All the visible views have rendered, try to render next/previous pages.
+    if (scrolledDown) {
+      var lastVisible = visibleViews[visibleViews.length - 1];
+      var nextPageIndex = lastVisible.id;
+      // ID's start at 1 so no need to add 1.
+      if (views[nextPageIndex] && !this.isViewFinshed(views[nextPageIndex]))
+        return views[nextPageIndex];
+    } else {
+      var previousPageIndex = visibleViews[0].id - 2;
+      if (views[previousPageIndex] &&
+          !this.isViewFinshed(views[previousPageIndex]))
+        return views[previousPageIndex];
+    }
+    // Everything that needs to be rendered has been.
+    return false;
+  },
+
+  isViewFinshed: function pdfViewNeedsRendering(view) {
+    return view.renderingState === RenderingStates.FINISHED;
+  },
+
+  // Render a page or thumbnail view. This calls the appropriate function based
+  // on the views state. If the view is already rendered it will return false.
+  renderView: function pdfViewRender(view, type) {
+    var state = view.renderingState;
+    switch (state) {
+      case RenderingStates.FINISHED:
+        return false;
+      case RenderingStates.PAUSED:
+        PDFView.highestPriorityPage = type + view.id;
+        view.resume();
+        break;
+      case RenderingStates.RUNNING:
+        PDFView.highestPriorityPage = type + view.id;
+        break;
+      case RenderingStates.INITIAL:
+        PDFView.highestPriorityPage = type + view.id;
+        view.draw(this.renderHighestPriority.bind(this));
+        break;
+    }
+    return true;
+  },
+
   search: function pdfViewStartSearch() {
     // Limit this function to run every <SEARCH_TIMEOUT>ms.
     var SEARCH_TIMEOUT = 250;
@@ -854,7 +943,7 @@ var PDFView = {
         outlineView.classList.add('hidden');
         searchView.classList.add('hidden');
 
-        updateThumbViewArea();
+        PDFView.renderHighestPriority();
         break;
 
       case 'outline':
@@ -904,63 +993,39 @@ var PDFView = {
   },
 
   getVisiblePages: function pdfViewGetVisiblePages() {
-    var pages = this.pages;
-    var kBottomMargin = 10;
-    var kTopPadding = 30;
-    var visiblePages = [];
-
-    var currentHeight = kTopPadding + kBottomMargin;
-    var container = this.container;
-    // Add 1px to the scrolltop to give a little wiggle room if the math is off,
-    // this won't be needed if we calc current page number based off the middle
-    // of the screen instead of the top.
-    var containerTop = container.scrollTop + 1;
-    for (var i = 1; i <= pages.length; ++i) {
-      var page = pages[i - 1];
-      var pageHeight = page.height + kBottomMargin;
-      if (currentHeight + pageHeight > containerTop)
-        break;
-
-      currentHeight += pageHeight;
-    }
-
-    var containerBottom = containerTop + container.clientHeight;
-    for (; i <= pages.length && currentHeight < containerBottom; ++i) {
-      var singlePage = pages[i - 1];
-      visiblePages.push({ id: singlePage.id, y: currentHeight,
-                          view: singlePage });
-      currentHeight += page.height + kBottomMargin;
-    }
-    return visiblePages;
+    return this.getVisibleElements(this.container,
+                                   this.pages);
   },
 
   getVisibleThumbs: function pdfViewGetVisibleThumbs() {
-    var thumbs = this.thumbnails;
-    var kBottomMargin = 15;
-    var visibleThumbs = [];
-
-    var view = document.getElementById('thumbnailView');
-    var currentHeight = kBottomMargin;
-
-    var top = view.scrollTop;
-    for (var i = 1; i <= thumbs.length; ++i) {
-      var thumb = thumbs[i - 1];
-      var thumbHeight = thumb.height * thumb.scaleY + kBottomMargin;
-      if (currentHeight + thumbHeight > top)
-        break;
+    return this.getVisibleElements(this.thumbnailContainer,
+                                   this.thumbnails);
+  },
 
-      currentHeight += thumbHeight;
+  // Generic helper to find out what elements are visible within a scroll pane.
+  getVisibleElements: function pdfViewGetVisibleElements(scrollEl, views) {
+    var currentHeight = 0, view;
+    var top = scrollEl.scrollTop;
+
+    for (var i = 1; i <= views.length; ++i) {
+      view = views[i - 1];
+      currentHeight = view.el.offsetTop;
+      if (currentHeight + view.el.clientHeight > top)
+        break;
+      currentHeight += view.el.clientHeight;
     }
 
-    var bottom = top + view.clientHeight;
-    for (; i <= thumbs.length && currentHeight < bottom; ++i) {
-      var singleThumb = thumbs[i - 1];
-      visibleThumbs.push({ id: singleThumb.id, y: currentHeight,
-                          view: singleThumb });
-      currentHeight += singleThumb.height * singleThumb.scaleY + kBottomMargin;
+    var visible = [];
+    var bottom = top + scrollEl.clientHeight;
+    for (; i <= views.length && currentHeight < bottom; ++i) {
+      view = views[i - 1];
+      currentHeight = view.el.offsetTop;
+      visible.push({ id: view.id, y: currentHeight,
+                     view: view });
+      currentHeight += view.el.clientHeight;
     }
 
-    return visibleThumbs;
+    return visible;
   },
 
   // Helper function to parse query string (e.g. ?param1=value&parm2=...).
@@ -985,10 +1050,13 @@ var PageView = function pageView(container, pdfPage, id, scale,
   this.scale = scale || 1.0;
   this.viewport = this.pdfPage.getViewport(this.scale);
 
+  this.renderingState = RenderingStates.INITIAL;
+  this.resume = null;
+
   var anchor = document.createElement('a');
   anchor.name = '' + this.id;
 
-  var div = document.createElement('div');
+  var div = this.el = document.createElement('div');
   div.id = 'pageContainer' + this.id;
   div.className = 'page';
 
@@ -1001,6 +1069,9 @@ var PageView = function pageView(container, pdfPage, id, scale,
   };
 
   this.update = function pageViewUpdate(scale) {
+    this.renderingState = RenderingStates.INITIAL;
+    this.resume = null;
+
     this.scale = scale || this.scale;
     var viewport = this.pdfPage.getViewport(this.scale);
 
@@ -1203,16 +1274,11 @@ var PageView = function pageView(container, pdfPage, id, scale,
       }, 0);
   };
 
-  this.drawingRequired = function() {
-    return !div.querySelector('canvas');
-  };
-
   this.draw = function pageviewDraw(callback) {
-    if (!this.drawingRequired()) {
-      this.updateStats();
-      callback();
-      return;
-    }
+    if (this.renderingState !== RenderingStates.INITIAL)
+      error('Must be in new state before drawing');
+
+    this.renderingState = RenderingStates.RUNNING;
 
     var canvas = document.createElement('canvas');
     canvas.id = 'page' + this.id;
@@ -1242,6 +1308,8 @@ var PageView = function pageView(container, pdfPage, id, scale,
 
     var self = this;
     function pageViewDrawCallback(error) {
+      self.renderingState = RenderingStates.FINISHED;
+
       if (self.loadingIconDiv) {
         div.removeChild(self.loadingIconDiv);
         delete self.loadingIconDiv;
@@ -1264,7 +1332,18 @@ var PageView = function pageView(container, pdfPage, id, scale,
     var renderContext = {
       canvasContext: ctx,
       viewport: this.viewport,
-      textLayer: textLayer
+      textLayer: textLayer,
+      continueCallback: function pdfViewcContinueCallback(cont) {
+        if (PDFView.highestPriorityPage !== 'page' + self.id) {
+          self.renderingState = RenderingStates.PAUSED;
+          self.resume = function resumeCallback() {
+            self.renderingState = RenderingStates.RUNNING;
+            cont();
+          };
+          return;
+        }
+        cont();
+      }
     };
     this.pdfPage.render(renderContext).then(
       function pdfPageRenderCallback() {
@@ -1307,7 +1386,7 @@ var ThumbnailView = function thumbnailView(container, pdfPage, id) {
   var scaleX = this.scaleX = (canvasWidth / pageWidth);
   var scaleY = this.scaleY = (canvasHeight / pageHeight);
 
-  var div = document.createElement('div');
+  var div = this.el = document.createElement('div');
   div.id = 'thumbnailContainer' + id;
   div.className = 'thumbnail';
 
@@ -1315,6 +1394,7 @@ var ThumbnailView = function thumbnailView(container, pdfPage, id) {
   container.appendChild(anchor);
 
   this.hasImage = false;
+  this.renderingState = RenderingStates.INITIAL;
 
   function getPageDrawContext() {
     var canvas = document.createElement('canvas');
@@ -1347,22 +1427,40 @@ var ThumbnailView = function thumbnailView(container, pdfPage, id) {
   };
 
   this.draw = function thumbnailViewDraw(callback) {
+    if (this.renderingState !== RenderingStates.INITIAL)
+      error('Must be in new state before drawing');
+
+    this.renderingState = RenderingStates.RUNNING;
     if (this.hasImage) {
       callback();
       return;
     }
 
+    var self = this;
     var ctx = getPageDrawContext();
     var drawViewport = pdfPage.getViewport(scaleX);
     var renderContext = {
       canvasContext: ctx,
-      viewport: drawViewport
+      viewport: drawViewport,
+      continueCallback: function(cont) {
+        if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) {
+          self.renderingState = RenderingStates.PAUSED;
+          self.resume = function() {
+            self.renderingState = RenderingStates.RUNNING;
+            cont();
+          };
+          return;
+        }
+        cont();
+      }
     };
     pdfPage.render(renderContext).then(
       function pdfPageRenderCallback() {
+        self.renderingState = RenderingStates.FINISHED;
         callback();
       },
       function pdfPageRenderError(error) {
+        self.renderingState = RenderingStates.FINISHED;
         callback();
       }
     );
@@ -1372,7 +1470,7 @@ var ThumbnailView = function thumbnailView(container, pdfPage, id) {
   this.setImage = function thumbnailViewSetImage(img) {
     if (this.hasImage || !img)
       return;
-
+    this.renderingState = RenderingStates.FINISHED;
     var ctx = getPageDrawContext();
     ctx.drawImage(img, 0, 0, img.width, img.height,
                   0, 0, ctx.canvas.width, ctx.canvas.height);
@@ -1614,9 +1712,6 @@ window.addEventListener('load', function webViewerLoad(evt) {
     }
   });
 
-  var thumbsView = document.getElementById('thumbnailView');
-  thumbsView.addEventListener('scroll', updateThumbViewArea, true);
-
   var mainContainer = document.getElementById('mainContainer');
   var outerContainer = document.getElementById('outerContainer');
   mainContainer.addEventListener('transitionend', function(e) {
@@ -1633,56 +1728,19 @@ window.addEventListener('load', function webViewerLoad(evt) {
       this.classList.toggle('toggled');
       outerContainer.classList.add('sidebarMoving');
       outerContainer.classList.toggle('sidebarOpen');
-      updateThumbViewArea();
+      PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen');
+      PDFView.renderHighestPriority();
     });
 
   PDFView.open(file, 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() {
   if (!PDFView.initialized)
     return;
   var visiblePages = PDFView.getVisiblePages();
-  var pageToDraw;
-  for (var i = 0; i < visiblePages.length; i++) {
-    var page = visiblePages[i];
-    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();
-  }
+  PDFView.renderHighestPriority();
 
   updateViewarea.inProgress = true; // used in "set page"
   var currentId = PDFView.page;
@@ -1713,29 +1771,6 @@ function updateViewarea() {
   document.getElementById('viewBookmark').href = href;
 }
 
-window.addEventListener('scroll', function webViewerScroll(evt) {
-  updateViewarea();
-}, true);
-
-var thumbnailTimer;
-
-function updateThumbViewArea() {
-  // Only render thumbs after pausing scrolling for this amount of time
-  // (makes UI more responsive)
-  var delay = 50; // in ms
-
-  if (thumbnailTimer)
-    clearTimeout(thumbnailTimer);
-
-  thumbnailTimer = setTimeout(function() {
-    var visibleThumbs = PDFView.getVisibleThumbs();
-    for (var i = 0; i < visibleThumbs.length; i++) {
-      var thumb = visibleThumbs[i];
-      renderingQueue.enqueueDraw(PDFView.thumbnails[thumb.id - 1]);
-    }
-  }, delay);
-}
-
 window.addEventListener('resize', function webViewerResize(evt) {
   if (PDFView.initialized &&
       (document.getElementById('pageWidthOption').selected ||