diff --git a/examples/acroforms/index.html b/examples/acroforms/index.html
index 858ad649f..193d16ee6 100644
--- a/examples/acroforms/index.html
+++ b/examples/acroforms/index.html
@@ -25,6 +25,7 @@
+
+
+
diff --git a/web/compatibility.js b/web/compatibility.js
index 528841bb6..4b7119c63 100644
--- a/web/compatibility.js
+++ b/web/compatibility.js
@@ -6,6 +6,16 @@
// Checking if the typed arrays are supported
(function checkTypedArrayCompatibility() {
if (typeof Uint8Array !== 'undefined') {
+ // some mobile versions do not support subarray (e.g. safari 5 / iOS)
+ if (typeof Uint8Array.prototype.subarray === 'undefined') {
+ Uint8Array.prototype.subarray = function subarray(start, end) {
+ return new Uint8Array(this.slice(start, end));
+ };
+ Float32Array.prototype.subarray = function subarray(start, end) {
+ return new Float32Array(this.slice(start, end));
+ };
+ }
+
// some mobile version might not support Float64Array
if (typeof Float64Array === 'undefined')
window.Float64Array = Float32Array;
@@ -69,8 +79,17 @@
// Object.defineProperty() ?
(function checkObjectDefinePropertyCompatibility() {
- if (typeof Object.defineProperty !== 'undefined')
- return;
+ if (typeof Object.defineProperty !== 'undefined') {
+ // some browsers (e.g. safari) cannot use defineProperty() on DOM objects
+ // and thus the native version is not sufficient
+ var definePropertyPossible = true;
+ try {
+ Object.defineProperty(new Image(), 'id', { value: 'test' });
+ } catch (e) {
+ definePropertyPossible = false;
+ }
+ if (definePropertyPossible) return;
+ }
Object.defineProperty = function objectDefineProperty(obj, name, def) {
delete obj[name];
diff --git a/web/viewer.html b/web/viewer.html
index 4edf6ce1a..c59d9fcf3 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -35,6 +35,7 @@
+
diff --git a/web/viewer.js b/web/viewer.js
index d371c3242..516081f6f 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -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,16 +234,48 @@ 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;
},
- setScale: function pdfViewSetScale(val, resetAutoSettings) {
+ // 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, noScroll) {
if (val == this.currentScale)
return;
@@ -275,7 +283,7 @@ var PDFView = {
for (var i = 0; i < pages.length; i++)
pages[i].update(val * kCssUnits);
- if (this.currentScale != val)
+ if (!noScroll && this.currentScale != val)
this.pages[this.page - 1].scrollIntoView();
this.currentScale = val;
@@ -286,14 +294,14 @@ var PDFView = {
window.dispatchEvent(event);
},
- parseScale: function pdfViewParseScale(value, resetAutoSettings) {
+ parseScale: function pdfViewParseScale(value, resetAutoSettings, noScroll) {
if ('custom' == value)
return;
var scale = parseFloat(value);
this.currentScaleValue = value;
if (scale) {
- this.setScale(scale, true);
+ this.setScale(scale, true, noScroll);
return;
}
@@ -305,22 +313,22 @@ var PDFView = {
currentPage.height * currentPage.scale / kCssUnits;
switch (value) {
case 'page-actual':
- this.setScale(1, resetAutoSettings);
+ scale = 1;
break;
case 'page-width':
- this.setScale(pageWidthScale, resetAutoSettings);
+ scale = pageWidthScale;
break;
case 'page-height':
- this.setScale(pageHeightScale, resetAutoSettings);
+ scale = pageHeightScale;
break;
case 'page-fit':
- this.setScale(
- Math.min(pageWidthScale, pageHeightScale), resetAutoSettings);
+ scale = Math.min(pageWidthScale, pageHeightScale);
break;
case 'auto':
- this.setScale(Math.min(1.0, pageWidthScale), resetAutoSettings);
+ scale = Math.min(1.0, pageWidthScale);
break;
}
+ this.setScale(scale, resetAutoSettings, noScroll);
selectScaleOption(value);
},
@@ -606,7 +614,6 @@ var PDFView = {
// when page is painted, using the image as thumbnail base
pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
thumbnailView.setImage(pageView.canvas);
- preDraw();
};
}
@@ -733,6 +740,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 ms.
var SEARCH_TIMEOUT = 250;
@@ -856,7 +945,7 @@ var PDFView = {
outlineView.classList.add('hidden');
searchView.classList.add('hidden');
- updateThumbViewArea();
+ PDFView.renderHighestPriority();
break;
case 'outline':
@@ -906,63 +995,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 = [];
+ return this.getVisibleElements(this.thumbnailContainer,
+ this.thumbnails);
+ },
- var view = document.getElementById('thumbnailView');
- var currentHeight = kBottomMargin;
+ // 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;
- 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)
+ for (var i = 1; i <= views.length; ++i) {
+ view = views[i - 1];
+ currentHeight = view.el.offsetTop;
+ if (currentHeight + view.el.clientHeight > top)
break;
-
- currentHeight += thumbHeight;
+ 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=...).
@@ -987,10 +1052,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';
@@ -1003,6 +1071,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);
@@ -1176,9 +1247,9 @@ var PageView = function pageView(container, pdfPage, id, scale,
}
if (scale && scale !== PDFView.currentScale)
- PDFView.parseScale(scale, true);
+ PDFView.parseScale(scale, true, true);
else if (PDFView.currentScale === kUnknownScale)
- PDFView.parseScale(kDefaultScale, true);
+ PDFView.parseScale(kDefaultScale, true, true);
var boundingRect = [
this.viewport.convertToViewportPoint(x, y),
@@ -1205,16 +1276,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;
@@ -1244,6 +1310,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;
@@ -1266,7 +1334,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() {
@@ -1309,7 +1388,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';
@@ -1317,6 +1396,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');
@@ -1349,22 +1429,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();
}
);
@@ -1374,7 +1472,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);
@@ -1616,9 +1714,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) {
@@ -1635,56 +1730,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;
@@ -1715,29 +1773,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 ||