app/assets/javascripts/pdfjs_viewer/viewer.js in pdfjs_viewer-rails-0.0.6 vs app/assets/javascripts/pdfjs_viewer/viewer.js in pdfjs_viewer-rails-0.0.7
- old
+ new
@@ -1,7 +1,5 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@@ -13,97 +11,54 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar,
- DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL,
- PDFHistory, Preferences, SidebarView, ViewHistory, PageView,
+ DownloadManager, getFileName, getPDFFileNameFromURL,
+ PDFHistory, Preferences, SidebarView, ViewHistory, Stats,
PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
- PasswordPrompt, PresentationMode, HandTool, Promise,
- DocumentProperties, DocumentOutlineView, DocumentAttachmentsView,
- OverlayManager, PDFFindController, PDFFindBar, getVisibleElements,
- watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState,
- RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE,
+ PasswordPrompt, PDFPresentationMode, PDFDocumentProperties, HandTool,
+ Promise, PDFLinkService, PDFOutlineView, PDFAttachmentView,
+ OverlayManager, PDFFindController, PDFFindBar, PDFViewer,
+ PDFRenderingQueue, PresentationModeState, parseQueryString,
+ RenderingStates, UNKNOWN_SCALE, DEFAULT_SCALE_VALUE,
IGNORE_CURRENT_POSITION_ON_ZOOM: true */
'use strict';
var DEFAULT_URL = window.resourceURL;
var DEFAULT_SCALE_DELTA = 1.1;
var MIN_SCALE = 0.25;
var MAX_SCALE = 10.0;
-var VIEW_HISTORY_MEMORY = 20;
var SCALE_SELECT_CONTAINER_PADDING = 8;
var SCALE_SELECT_PADDING = 22;
var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading';
+var DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000;
PDFJS.imageResourcesPath = '/pdfjs/web/images/';
-PDFJS.cMapUrl = '/pdfjs/web/cmaps/';
-PDFJS.cMapPacked = true;
+ // PDFJS.workerSrc = '../build/pdf.worker.js';
+ PDFJS.cMapUrl = '/pdfjs/web/cmaps/';
+ PDFJS.cMapPacked = true;
+ PDFJS.externalLinkTarget = 2;
var mozL10n = document.mozL10n || document.webL10n;
var CSS_UNITS = 96.0 / 72.0;
-var DEFAULT_SCALE = 'auto';
+var DEFAULT_SCALE_VALUE = 'auto';
+var DEFAULT_SCALE = 1.0;
var UNKNOWN_SCALE = 0;
var MAX_AUTO_SCALE = 1.25;
var SCROLLBAR_PADDING = 40;
-var VERTICAL_PADDING = 0;
-var DEFAULT_CACHE_SIZE = 10;
+var VERTICAL_PADDING = 5;
-// optimised CSS custom property getter/setter
-var CustomStyle = (function CustomStyleClosure() {
+var NullCharactersRegExp = /\x00/g;
- // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
- // animate-css-transforms-firefox-webkit.html
- // in some versions of IE9 it is critical that ms appear in this list
- // before Moz
- var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
- var _cache = {};
+function removeNullCharacters(str) {
+ return str.replace(NullCharactersRegExp, '');
+}
- function CustomStyle() {}
-
- CustomStyle.getProp = function get(propName, element) {
- // check cache only when no element is given
- if (arguments.length === 1 && typeof _cache[propName] === 'string') {
- return _cache[propName];
- }
-
- element = element || document.documentElement;
- var style = element.style, prefixed, uPropName;
-
- // test standard property first
- if (typeof style[propName] === 'string') {
- return (_cache[propName] = propName);
- }
-
- // capitalize
- uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
-
- // test vendor specific properties
- for (var i = 0, l = prefixes.length; i < l; i++) {
- prefixed = prefixes[i] + uPropName;
- if (typeof style[prefixed] === 'string') {
- return (_cache[propName] = prefixed);
- }
- }
-
- //if all fails then set to undefined
- return (_cache[propName] = 'undefined');
- };
-
- CustomStyle.setProp = function set(propName, element, str) {
- var prop = this.getProp(propName);
- if (prop !== 'undefined') {
- element.style[prop] = str;
- }
- };
-
- return CustomStyle;
-})();
-
function getFileName(url) {
var anchor = url.indexOf('#');
var query = url.indexOf('?');
var end = Math.min(
anchor > 0 ? anchor : url.length,
@@ -132,26 +87,30 @@
};
}
/**
* Scrolls specified element into view of its parent.
- * element {Object} The element to be visible.
- * spot {Object} An object with optional top and left properties,
- * specifying the offset from the top left edge.
+ * @param {Object} element - The element to be visible.
+ * @param {Object} spot - An object with optional top and left properties,
+ * specifying the offset from the top left edge.
+ * @param {boolean} skipOverflowHiddenElements - Ignore elements that have
+ * the CSS rule `overflow: hidden;` set. The default is false.
*/
-function scrollIntoView(element, spot) {
+function scrollIntoView(element, spot, skipOverflowHiddenElements) {
// Assuming offsetParent is available (it's not available when viewer is in
// hidden iframe or object). We have to scroll: if the offsetParent is not set
// producing the error. See also animationStartedClosure.
var parent = element.offsetParent;
- var offsetY = element.offsetTop + element.clientTop;
- var offsetX = element.offsetLeft + element.clientLeft;
if (!parent) {
console.error('offsetParent is not set -- cannot scroll');
return;
}
- while (parent.clientHeight === parent.scrollHeight) {
+ var checkOverflow = skipOverflowHiddenElements || false;
+ var offsetY = element.offsetTop + element.clientTop;
+ var offsetX = element.offsetLeft + element.clientLeft;
+ while (parent.clientHeight === parent.scrollHeight ||
+ (checkOverflow && getComputedStyle(parent).overflow === 'hidden')) {
if (parent.dataset._scaleY) {
offsetY /= parent.dataset._scaleY;
offsetX /= parent.dataset._scaleX;
}
offsetY += parent.offsetTop;
@@ -186,17 +145,14 @@
rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
rAF = null;
var currentY = viewAreaElement.scrollTop;
var lastY = state.lastY;
- if (currentY > lastY) {
- state.down = true;
- } else if (currentY < lastY) {
- state.down = false;
+ if (currentY !== lastY) {
+ state.down = currentY > lastY;
}
state.lastY = currentY;
- // else do nothing and use previous value
callback(state);
});
};
var state = {
@@ -209,40 +165,151 @@
viewAreaElement.addEventListener('scroll', debounceScroll, true);
return state;
}
/**
+ * Helper function to parse query string (e.g. ?param1=value&parm2=...).
+ */
+function parseQueryString(query) {
+ var parts = query.split('&');
+ var params = {};
+ for (var i = 0, ii = parts.length; i < ii; ++i) {
+ var param = parts[i].split('=');
+ var key = param[0].toLowerCase();
+ var value = param.length > 1 ? param[1] : null;
+ params[decodeURIComponent(key)] = decodeURIComponent(value);
+ }
+ return params;
+}
+
+/**
+ * Use binary search to find the index of the first item in a given array which
+ * passes a given condition. The items are expected to be sorted in the sense
+ * that if the condition is true for one item in the array, then it is also true
+ * for all following items.
+ *
+ * @returns {Number} Index of the first array element to pass the test,
+ * or |items.length| if no such element exists.
+ */
+function binarySearchFirstItem(items, condition) {
+ var minIndex = 0;
+ var maxIndex = items.length - 1;
+
+ if (items.length === 0 || !condition(items[maxIndex])) {
+ return items.length;
+ }
+ if (condition(items[minIndex])) {
+ return minIndex;
+ }
+
+ while (minIndex < maxIndex) {
+ var currentIndex = (minIndex + maxIndex) >> 1;
+ var currentItem = items[currentIndex];
+ if (condition(currentItem)) {
+ maxIndex = currentIndex;
+ } else {
+ minIndex = currentIndex + 1;
+ }
+ }
+ return minIndex; /* === maxIndex */
+}
+
+/**
+ * Approximates float number as a fraction using Farey sequence (max order
+ * of 8).
+ * @param {number} x - Positive float number.
+ * @returns {Array} Estimated fraction: the first array item is a numerator,
+ * the second one is a denominator.
+ */
+function approximateFraction(x) {
+ // Fast paths for int numbers or their inversions.
+ if (Math.floor(x) === x) {
+ return [x, 1];
+ }
+ var xinv = 1 / x;
+ var limit = 8;
+ if (xinv > limit) {
+ return [1, limit];
+ } else if (Math.floor(xinv) === xinv) {
+ return [1, xinv];
+ }
+
+ var x_ = x > 1 ? xinv : x;
+ // a/b and c/d are neighbours in Farey sequence.
+ var a = 0, b = 1, c = 1, d = 1;
+ // Limiting search to order 8.
+ while (true) {
+ // Generating next term in sequence (order of q).
+ var p = a + c, q = b + d;
+ if (q > limit) {
+ break;
+ }
+ if (x_ <= p / q) {
+ c = p; d = q;
+ } else {
+ a = p; b = q;
+ }
+ }
+ // Select closest of the neighbours to x.
+ if (x_ - a / b < c / d - x_) {
+ return x_ === x ? [a, b] : [b, a];
+ } else {
+ return x_ === x ? [c, d] : [d, c];
+ }
+}
+
+function roundToDivide(x, div) {
+ var r = x % div;
+ return r === 0 ? x : Math.round(x - r + div);
+}
+
+/**
* Generic helper to find out what elements are visible within a scroll pane.
*/
function getVisibleElements(scrollEl, views, sortByVisibility) {
var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
- var visible = [], view;
+ function isElementBottomBelowViewTop(view) {
+ var element = view.div;
+ var elementBottom =
+ element.offsetTop + element.clientTop + element.clientHeight;
+ return elementBottom > top;
+ }
+
+ var visible = [], view, element;
var currentHeight, viewHeight, hiddenHeight, percentHeight;
var currentWidth, viewWidth;
- for (var i = 0, ii = views.length; i < ii; ++i) {
+ var firstVisibleElementInd = (views.length === 0) ? 0 :
+ binarySearchFirstItem(views, isElementBottomBelowViewTop);
+
+ for (var i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
view = views[i];
- currentHeight = view.el.offsetTop + view.el.clientTop;
- viewHeight = view.el.clientHeight;
- if ((currentHeight + viewHeight) < top) {
- continue;
- }
+ element = view.div;
+ currentHeight = element.offsetTop + element.clientTop;
+ viewHeight = element.clientHeight;
+
if (currentHeight > bottom) {
break;
}
- currentWidth = view.el.offsetLeft + view.el.clientLeft;
- viewWidth = view.el.clientWidth;
- if ((currentWidth + viewWidth) < left || currentWidth > right) {
+
+ currentWidth = element.offsetLeft + element.clientLeft;
+ viewWidth = element.clientWidth;
+ if (currentWidth + viewWidth < left || currentWidth > right) {
continue;
}
hiddenHeight = Math.max(0, top - currentHeight) +
Math.max(0, currentHeight + viewHeight - bottom);
percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
- visible.push({ id: view.id, x: currentWidth, y: currentHeight,
- view: view, percent: percentHeight });
+ visible.push({
+ id: view.id,
+ x: currentWidth,
+ y: currentHeight,
+ view: view,
+ percent: percentHeight
+ });
}
var first = visible[0];
var last = visible[visible.length - 1];
@@ -300,10 +367,11 @@
function clamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}
function ProgressBar(id, opts) {
+ this.visible = true;
// Fetch the sub-elements for later.
this.div = document.querySelector(id + ' .progress');
// Get the loading bar element, so it can be resized to fit the viewer.
@@ -353,40 +421,33 @@
}
}
},
hide: function ProgressBar_hide() {
+ if (!this.visible) {
+ return;
+ }
+ this.visible = false;
this.bar.classList.add('hidden');
- this.bar.removeAttribute('style');
+ document.body.classList.remove('loadingInProgress');
+ },
+
+ show: function ProgressBar_show() {
+ if (this.visible) {
+ return;
+ }
+ this.visible = true;
+ document.body.classList.add('loadingInProgress');
+ this.bar.classList.remove('hidden');
}
};
return ProgressBar;
})();
-var Cache = function cacheCache(size) {
- var data = [];
- this.push = function cachePush(view) {
- var i = data.indexOf(view);
- if (i >= 0) {
- data.splice(i, 1);
- }
- data.push(view);
- if (data.length > size) {
- data.shift().destroy();
- }
- };
- this.resize = function (newSize) {
- size = newSize;
- while (data.length > size) {
- data.shift().destroy();
- }
- };
-};
-
var DEFAULT_PREFERENCES = {
showPreviousViewOnLoad: true,
defaultZoomValue: '',
sidebarViewOnLoad: 0,
enableHandToolOnLoad: false,
@@ -395,11 +456,12 @@
disableRange: false,
disableStream: false,
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,
- useOnlyCssZoom: false
+ useOnlyCssZoom: false,
+ externalLinkTarget: 0,
};
var SidebarView = {
NONE: 0,
@@ -540,11 +602,10 @@
}.bind(this));
}
};
-
Preferences._writeToStorage = function (prefObj) {
return new Promise(function (resolve) {
localStorage.setItem('pdfjs.preferences', JSON.stringify(prefObj));
resolve();
});
@@ -621,11 +682,11 @@
}
}
function renderProgress() {
var progressContainer = document.getElementById('mozPrintCallback-shim');
- if (canvases) {
+ if (canvases && canvases.length) {
var progress = Math.round(100 * index / canvases.length);
var progressBar = progressContainer.querySelector('progress');
var progressPerc = progressContainer.querySelector('.relative-progress');
progressBar.value = progress;
progressPerc.textContent = progress + '%';
@@ -771,33 +832,35 @@
+var DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
+
/**
* View History - This is a utility for saving various view parameters for
* recently opened files.
*
* The way that the view parameters are stored depends on how PDF.js is built,
* for 'node make <flag>' the following cases exist:
* - FIREFOX or MOZCENTRAL - uses sessionStorage.
- * - B2G - uses asyncStorage.
* - GENERIC or CHROME - uses localStorage, if it is available.
*/
var ViewHistory = (function ViewHistoryClosure() {
- function ViewHistory(fingerprint) {
+ function ViewHistory(fingerprint, cacheSize) {
this.fingerprint = fingerprint;
+ this.cacheSize = cacheSize || DEFAULT_VIEW_HISTORY_CACHE_SIZE;
this.isInitializedPromiseResolved = false;
this.initializedPromise =
this._readFromStorage().then(function (databaseStr) {
this.isInitializedPromiseResolved = true;
var database = JSON.parse(databaseStr || '{}');
if (!('files' in database)) {
database.files = [];
}
- if (database.files.length >= VIEW_HISTORY_MEMORY) {
+ if (database.files.length >= this.cacheSize) {
database.files.shift();
}
var index;
for (var i = 0, length = database.files.length; i < length; i++) {
var branch = database.files[i];
@@ -818,20 +881,18 @@
_writeToStorage: function ViewHistory_writeToStorage() {
return new Promise(function (resolve) {
var databaseStr = JSON.stringify(this.database);
-
localStorage.setItem('database', databaseStr);
resolve();
}.bind(this));
},
_readFromStorage: function ViewHistory_readFromStorage() {
return new Promise(function (resolve) {
-
resolve(localStorage.getItem('database'));
});
},
set: function ViewHistory_set(name, val) {
@@ -877,10 +938,11 @@
this.toggleButton = options.toggleButton || null;
this.findField = options.findField || null;
this.highlightAll = options.highlightAllCheckbox || null;
this.caseSensitive = options.caseSensitiveCheckbox || null;
this.findMsg = options.findMsg || null;
+ this.findResultsCount = options.findResultsCount || null;
this.findStatusIcon = options.findStatusIcon || null;
this.findPreviousButton = options.findPreviousButton || null;
this.findNextButton = options.findNextButton || null;
this.findController = options.findController || null;
@@ -939,11 +1001,12 @@
findPrevious: findPrev
});
return window.dispatchEvent(event);
},
- updateUIState: function PDFFindBar_updateUIState(state, previous) {
+ updateUIState:
+ function PDFFindBar_updateUIState(state, previous, matchCount) {
var notFound = false;
var findMsg = '';
var status = '';
switch (state) {
@@ -976,12 +1039,32 @@
this.findField.classList.remove('notFound');
}
this.findField.setAttribute('data-status', status);
this.findMsg.textContent = findMsg;
+
+ this.updateResultsCount(matchCount);
},
+ updateResultsCount: function(matchCount) {
+ if (!this.findResultsCount) {
+ return; // no UI control is provided
+ }
+
+ // If there are no matches, hide the counter
+ if (!matchCount) {
+ this.findResultsCount.classList.add('hidden');
+ return;
+ }
+
+ // Create the match counter
+ this.findResultsCount.textContent = matchCount.toLocaleString();
+
+ // Show the counter
+ this.findResultsCount.classList.remove('hidden');
+ },
+
open: function PDFFindBar_open() {
if (!this.opened) {
this.opened = true;
this.toggleButton.classList.add('toggled');
this.bar.classList.remove('hidden');
@@ -1010,18 +1093,20 @@
};
return PDFFindBar;
})();
-
var FindStates = {
FIND_FOUND: 0,
FIND_NOTFOUND: 1,
FIND_WRAPPED: 2,
FIND_PENDING: 3
};
+var FIND_SCROLL_OFFSET_TOP = -50;
+var FIND_SCROLL_OFFSET_LEFT = -400;
+
/**
* Provides "search" or "find" functionality for the PDF.
* This object actually performs the search for a given string.
*/
var PDFFindController = (function PDFFindControllerClosure() {
@@ -1030,10 +1115,11 @@
this.extractTextPromises = [];
this.pendingFindMatches = {};
this.active = false; // If active, find results will be highlighted.
this.pageContents = []; // Stores the text for each page.
this.pageMatches = [];
+ this.matchCount = 0;
this.selected = { // Currently selected match.
pageIdx: -1,
matchIdx: -1
};
this.offset = { // Where the find algorithm currently is in the document.
@@ -1056,11 +1142,11 @@
'\u201D': '"', // Right double quotation mark
'\u201E': '"', // Double low-9 quotation mark
'\u201F': '"', // Double high-reversed-9 quotation mark
'\u00BC': '1/4', // Vulgar fraction one quarter
'\u00BD': '1/2', // Vulgar fraction one half
- '\u00BE': '3/4' // Vulgar fraction three quarters
+ '\u00BE': '3/4', // Vulgar fraction three quarters
};
this.findBar = options.findBar || null;
// Compile the regular expression for text normalization once
var replace = Object.keys(this.charactersToNormalize).join('');
@@ -1106,11 +1192,12 @@
var query = this.normalize(this.state.query);
var caseSensitive = this.state.caseSensitive;
var queryLen = query.length;
if (queryLen === 0) {
- return; // Do nothing: the matches should be wiped out already.
+ // Do nothing: the matches should be wiped out already.
+ return;
}
if (!caseSensitive) {
pageContent = pageContent.toLowerCase();
query = query.toLowerCase();
@@ -1129,10 +1216,16 @@
this.updatePage(pageIndex);
if (this.resumePageIdx === pageIndex) {
this.resumePageIdx = null;
this.nextPageMatch();
}
+
+ // Update the matches count
+ if (matches.length > 0) {
+ this.matchCount += matches.length;
+ this.updateUIResultsCount();
+ }
},
extractText: function PDFFindController_extractText() {
if (this.startedTextExtraction) {
return;
@@ -1191,19 +1284,18 @@
}
}.bind(this));
},
updatePage: function PDFFindController_updatePage(index) {
- var page = this.pdfViewer.getPageView(index);
-
if (this.selected.pageIdx === index) {
// If the page is selected, scroll the page into view, which triggers
// rendering the page, which adds the textLayer. Once the textLayer is
// build, it will scroll onto the selected match.
this.pdfViewer.scrollPageIntoView(index + 1);
}
+ var page = this.pdfViewer.getPageView(index);
if (page.textLayer) {
page.textLayer.updateMatches();
}
},
@@ -1221,10 +1313,11 @@
this.offset.pageIdx = currentPageIndex;
this.offset.matchIdx = null;
this.hadMatch = false;
this.resumePageIdx = null;
this.pageMatches = [];
+ this.matchCount = 0;
var self = this;
for (var i = 0; i < numPages; i++) {
// Wipe out any previous highlighted matches.
this.updatePage(i);
@@ -1303,10 +1396,32 @@
// Matches were not found (and searching is not done).
return false;
}
},
+ /**
+ * The method is called back from the text layer when match presentation
+ * is updated.
+ * @param {number} pageIndex - page index.
+ * @param {number} index - match index.
+ * @param {Array} elements - text layer div elements array.
+ * @param {number} beginIdx - start index of the div array for the match.
+ * @param {number} endIdx - end index of the div array for the match.
+ */
+ updateMatchPosition: function PDFFindController_updateMatchPosition(
+ pageIndex, index, elements, beginIdx, endIdx) {
+ if (this.selected.matchIdx === index &&
+ this.selected.pageIdx === pageIndex) {
+ var spot = {
+ top: FIND_SCROLL_OFFSET_TOP,
+ left: FIND_SCROLL_OFFSET_LEFT
+ };
+ scrollIntoView(elements[beginIdx], spot,
+ /* skipOverflowHiddenElements = */ true);
+ }
+ },
+
nextPageMatch: function PDFFindController_nextPageMatch() {
if (this.resumePageIdx !== null) {
console.error('There can only be one pending page.');
}
do {
@@ -1355,384 +1470,720 @@
if (this.selected.pageIdx !== -1) {
this.updatePage(this.selected.pageIdx);
}
},
+ updateUIResultsCount:
+ function PDFFindController_updateUIResultsCount() {
+ if (this.findBar === null) {
+ throw new Error('PDFFindController is not initialized with a ' +
+ 'PDFFindBar instance.');
+ }
+ this.findBar.updateResultsCount(this.matchCount);
+ },
+
updateUIState: function PDFFindController_updateUIState(state, previous) {
if (this.integratedFind) {
FirefoxCom.request('updateFindControlState',
{ result: state, findPrevious: previous });
return;
}
if (this.findBar === null) {
throw new Error('PDFFindController is not initialized with a ' +
'PDFFindBar instance.');
}
- this.findBar.updateUIState(state, previous);
+ this.findBar.updateUIState(state, previous, this.matchCount);
}
};
return PDFFindController;
})();
-
-var PDFHistory = {
- initialized: false,
- initialDestination: null,
-
+/**
+ * Performs navigation functions inside PDF, such as opening specified page,
+ * or destination.
+ * @class
+ * @implements {IPDFLinkService}
+ */
+var PDFLinkService = (function () {
/**
- * @param {string} fingerprint
- * @param {IPDFLinkService} linkService
+ * @constructs PDFLinkService
*/
- initialize: function pdfHistoryInitialize(fingerprint, linkService) {
- this.initialized = true;
- this.reInitialized = false;
- this.allowHashChange = true;
- this.historyUnlocked = true;
+ function PDFLinkService() {
+ this.baseUrl = null;
+ this.pdfDocument = null;
+ this.pdfViewer = null;
+ this.pdfHistory = null;
- this.previousHash = window.location.hash.substring(1);
- this.currentBookmark = '';
- this.currentPage = 0;
- this.updatePreviousBookmark = false;
- this.previousBookmark = '';
- this.previousPage = 0;
- this.nextHashParam = '';
+ this._pagesRefCache = null;
+ }
- this.fingerprint = fingerprint;
- this.linkService = linkService;
- this.currentUid = this.uid = 0;
- this.current = {};
+ PDFLinkService.prototype = {
+ setDocument: function PDFLinkService_setDocument(pdfDocument, baseUrl) {
+ this.baseUrl = baseUrl;
+ this.pdfDocument = pdfDocument;
+ this._pagesRefCache = Object.create(null);
+ },
- var state = window.history.state;
- if (this._isStateObjectDefined(state)) {
- // This corresponds to navigating back to the document
- // from another page in the browser history.
- if (state.target.dest) {
- this.initialDestination = state.target.dest;
+ setViewer: function PDFLinkService_setViewer(pdfViewer) {
+ this.pdfViewer = pdfViewer;
+ },
+
+ setHistory: function PDFLinkService_setHistory(pdfHistory) {
+ this.pdfHistory = pdfHistory;
+ },
+
+ /**
+ * @returns {number}
+ */
+ get pagesCount() {
+ return this.pdfDocument.numPages;
+ },
+
+ /**
+ * @returns {number}
+ */
+ get page() {
+ return this.pdfViewer.currentPageNumber;
+ },
+
+ /**
+ * @param {number} value
+ */
+ set page(value) {
+ this.pdfViewer.currentPageNumber = value;
+ },
+
+ /**
+ * @param dest - The PDF destination object.
+ */
+ navigateTo: function PDFLinkService_navigateTo(dest) {
+ var destString = '';
+ var self = this;
+
+ var goToDestination = function(destRef) {
+ // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
+ var pageNumber = destRef instanceof Object ?
+ self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
+ (destRef + 1);
+ if (pageNumber) {
+ if (pageNumber > self.pagesCount) {
+ pageNumber = self.pagesCount;
+ }
+ self.pdfViewer.scrollPageIntoView(pageNumber, dest);
+
+ if (self.pdfHistory) {
+ // Update the browsing history.
+ self.pdfHistory.push({
+ dest: dest,
+ hash: destString,
+ page: pageNumber
+ });
+ }
+ } else {
+ self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
+ var pageNum = pageIndex + 1;
+ var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
+ self._pagesRefCache[cacheKey] = pageNum;
+ goToDestination(destRef);
+ });
+ }
+ };
+
+ var destinationPromise;
+ if (typeof dest === 'string') {
+ destString = dest;
+ destinationPromise = this.pdfDocument.getDestination(dest);
} else {
- linkService.setHash(state.target.hash);
+ destinationPromise = Promise.resolve(dest);
}
- this.currentUid = state.uid;
- this.uid = state.uid + 1;
- this.current = state.target;
- } else {
- // This corresponds to the loading of a new document.
- if (state && state.fingerprint &&
- this.fingerprint !== state.fingerprint) {
- // Reinitialize the browsing history when a new document
- // is opened in the web viewer.
- this.reInitialized = true;
+ destinationPromise.then(function(destination) {
+ dest = destination;
+ if (!(destination instanceof Array)) {
+ return; // invalid destination
+ }
+ goToDestination(destination[0]);
+ });
+ },
+
+ /**
+ * @param dest - The PDF destination object.
+ * @returns {string} The hyperlink to the PDF object.
+ */
+ getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
+ if (typeof dest === 'string') {
+ return this.getAnchorUrl('#' + escape(dest));
}
- this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
- }
+ if (dest instanceof Array) {
+ var destRef = dest[0]; // see navigateTo method for dest format
+ var pageNumber = destRef instanceof Object ?
+ this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
+ (destRef + 1);
+ if (pageNumber) {
+ var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
+ var destKind = dest[1];
+ if (typeof destKind === 'object' && 'name' in destKind &&
+ destKind.name === 'XYZ') {
+ var scale = (dest[4] || this.pdfViewer.currentScaleValue);
+ var scaleNumber = parseFloat(scale);
+ if (scaleNumber) {
+ scale = scaleNumber * 100;
+ }
+ pdfOpenParams += '&zoom=' + scale;
+ if (dest[2] || dest[3]) {
+ pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
+ }
+ }
+ return pdfOpenParams;
+ }
+ }
+ return this.getAnchorUrl('');
+ },
- var self = this;
- window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
- evt.preventDefault();
- evt.stopPropagation();
+ /**
+ * Prefix the full url on anchor links to make sure that links are resolved
+ * relative to the current URL instead of the one defined in <base href>.
+ * @param {String} anchor The anchor hash, including the #.
+ * @returns {string} The hyperlink to the PDF object.
+ */
+ getAnchorUrl: function PDFLinkService_getAnchorUrl(anchor) {
+ return (this.baseUrl || '') + anchor;
+ },
- if (!self.historyUnlocked) {
- return;
+ /**
+ * @param {string} hash
+ */
+ setHash: function PDFLinkService_setHash(hash) {
+ if (hash.indexOf('=') >= 0) {
+ var params = parseQueryString(hash);
+ // borrowing syntax from "Parameters for Opening PDF Files"
+ if ('nameddest' in params) {
+ if (this.pdfHistory) {
+ this.pdfHistory.updateNextHashParam(params.nameddest);
+ }
+ this.navigateTo(params.nameddest);
+ return;
+ }
+ var pageNumber, dest;
+ if ('page' in params) {
+ pageNumber = (params.page | 0) || 1;
+ }
+ if ('zoom' in params) {
+ // Build the destination array.
+ var zoomArgs = params.zoom.split(','); // scale,left,top
+ var zoomArg = zoomArgs[0];
+ var zoomArgNumber = parseFloat(zoomArg);
+
+ if (zoomArg.indexOf('Fit') === -1) {
+ // If the zoomArg is a number, it has to get divided by 100. If it's
+ // a string, it should stay as it is.
+ dest = [null, { name: 'XYZ' },
+ zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
+ zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
+ (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
+ } else {
+ if (zoomArg === 'Fit' || zoomArg === 'FitB') {
+ dest = [null, { name: zoomArg }];
+ } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
+ (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
+ dest = [null, { name: zoomArg },
+ zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
+ } else if (zoomArg === 'FitR') {
+ if (zoomArgs.length !== 5) {
+ console.error('PDFLinkService_setHash: ' +
+ 'Not enough parameters for \'FitR\'.');
+ } else {
+ dest = [null, { name: zoomArg },
+ (zoomArgs[1] | 0), (zoomArgs[2] | 0),
+ (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
+ }
+ } else {
+ console.error('PDFLinkService_setHash: \'' + zoomArg +
+ '\' is not a valid zoom value.');
+ }
+ }
+ }
+ if (dest) {
+ this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
+ } else if (pageNumber) {
+ this.page = pageNumber; // simple page
+ }
+ if ('pagemode' in params) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagemode', true, true, {
+ mode: params.pagemode,
+ });
+ this.pdfViewer.container.dispatchEvent(event);
+ }
+ } else if (/^\d+$/.test(hash)) { // page number
+ this.page = hash;
+ } else { // named destination
+ if (this.pdfHistory) {
+ this.pdfHistory.updateNextHashParam(unescape(hash));
+ }
+ this.navigateTo(unescape(hash));
}
- if (evt.state) {
- // Move back/forward in the history.
- self._goTo(evt.state);
+ },
+
+ /**
+ * @param {string} action
+ */
+ executeNamedAction: function PDFLinkService_executeNamedAction(action) {
+ // See PDF reference, table 8.45 - Named action
+ switch (action) {
+ case 'GoBack':
+ if (this.pdfHistory) {
+ this.pdfHistory.back();
+ }
+ break;
+
+ case 'GoForward':
+ if (this.pdfHistory) {
+ this.pdfHistory.forward();
+ }
+ break;
+
+ case 'NextPage':
+ this.page++;
+ break;
+
+ case 'PrevPage':
+ this.page--;
+ break;
+
+ case 'LastPage':
+ this.page = this.pagesCount;
+ break;
+
+ case 'FirstPage':
+ this.page = 1;
+ break;
+
+ default:
+ break; // No action according to spec
+ }
+
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('namedaction', true, true, {
+ action: action
+ });
+ this.pdfViewer.container.dispatchEvent(event);
+ },
+
+ /**
+ * @param {number} pageNum - page number.
+ * @param {Object} pageRef - reference to the page.
+ */
+ cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
+ var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
+ this._pagesRefCache[refStr] = pageNum;
+ }
+ };
+
+ return PDFLinkService;
+})();
+
+
+var PDFHistory = (function () {
+ function PDFHistory(options) {
+ this.linkService = options.linkService;
+
+ this.initialized = false;
+ this.initialDestination = null;
+ this.initialBookmark = null;
+ }
+
+ PDFHistory.prototype = {
+ /**
+ * @param {string} fingerprint
+ * @param {IPDFLinkService} linkService
+ */
+ initialize: function pdfHistoryInitialize(fingerprint) {
+ this.initialized = true;
+ this.reInitialized = false;
+ this.allowHashChange = true;
+ this.historyUnlocked = true;
+ this.isViewerInPresentationMode = false;
+
+ this.previousHash = window.location.hash.substring(1);
+ this.currentBookmark = '';
+ this.currentPage = 0;
+ this.updatePreviousBookmark = false;
+ this.previousBookmark = '';
+ this.previousPage = 0;
+ this.nextHashParam = '';
+
+ this.fingerprint = fingerprint;
+ this.currentUid = this.uid = 0;
+ this.current = {};
+
+ var state = window.history.state;
+ if (this._isStateObjectDefined(state)) {
+ // This corresponds to navigating back to the document
+ // from another page in the browser history.
+ if (state.target.dest) {
+ this.initialDestination = state.target.dest;
+ } else {
+ this.initialBookmark = state.target.hash;
+ }
+ this.currentUid = state.uid;
+ this.uid = state.uid + 1;
+ this.current = state.target;
} else {
- // Handle the user modifying the hash of a loaded document.
- self.previousHash = window.location.hash.substring(1);
+ // This corresponds to the loading of a new document.
+ if (state && state.fingerprint &&
+ this.fingerprint !== state.fingerprint) {
+ // Reinitialize the browsing history when a new document
+ // is opened in the web viewer.
+ this.reInitialized = true;
+ }
+ this._pushOrReplaceState({fingerprint: this.fingerprint}, true);
+ }
- // If the history is empty when the hash changes,
- // update the previous entry in the browser history.
+ var self = this;
+ window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
+ if (!self.historyUnlocked) {
+ return;
+ }
+ if (evt.state) {
+ // Move back/forward in the history.
+ self._goTo(evt.state);
+ return;
+ }
+
+ // If the state is not set, then the user tried to navigate to a
+ // different hash by manually editing the URL and pressing Enter, or by
+ // clicking on an in-page link (e.g. the "current view" link).
+ // Save the current view state to the browser history.
+
+ // Note: In Firefox, history.null could also be null after an in-page
+ // navigation to the same URL, and without dispatching the popstate
+ // event: https://bugzilla.mozilla.org/show_bug.cgi?id=1183881
+
if (self.uid === 0) {
+ // Replace the previous state if it was not explicitly set.
var previousParams = (self.previousHash && self.currentBookmark &&
- self.previousHash !== self.currentBookmark) ?
- { hash: self.currentBookmark, page: self.currentPage } :
- { page: 1 };
- self.historyUnlocked = false;
- self.allowHashChange = false;
- window.history.back();
- self._pushToHistory(previousParams, false, true);
- window.history.forward();
- self.historyUnlocked = true;
+ self.previousHash !== self.currentBookmark) ?
+ {hash: self.currentBookmark, page: self.currentPage} :
+ {page: 1};
+ replacePreviousHistoryState(previousParams, function() {
+ updateHistoryWithCurrentHash();
+ });
+ } else {
+ updateHistoryWithCurrentHash();
}
- self._pushToHistory({ hash: self.previousHash }, false, true);
+ }, false);
+
+
+ function updateHistoryWithCurrentHash() {
+ self.previousHash = window.location.hash.slice(1);
+ self._pushToHistory({hash: self.previousHash}, false, true);
self._updatePreviousBookmark();
}
- }, false);
- function pdfHistoryBeforeUnload() {
- var previousParams = self._getPreviousParams(null, true);
- if (previousParams) {
- var replacePrevious = (!self.current.dest &&
- self.current.hash !== self.previousHash);
- self._pushToHistory(previousParams, false, replacePrevious);
- self._updatePreviousBookmark();
+ function replacePreviousHistoryState(params, callback) {
+ // To modify the previous history entry, the following happens:
+ // 1. history.back()
+ // 2. _pushToHistory, which calls history.replaceState( ... )
+ // 3. history.forward()
+ // Because a navigation via the history API does not immediately update
+ // the history state, the popstate event is used for synchronization.
+ self.historyUnlocked = false;
+
+ // Suppress the hashchange event to avoid side effects caused by
+ // navigating back and forward.
+ self.allowHashChange = false;
+ window.addEventListener('popstate', rewriteHistoryAfterBack);
+ history.back();
+
+ function rewriteHistoryAfterBack() {
+ window.removeEventListener('popstate', rewriteHistoryAfterBack);
+ window.addEventListener('popstate', rewriteHistoryAfterForward);
+ self._pushToHistory(params, false, true);
+ history.forward();
+ }
+ function rewriteHistoryAfterForward() {
+ window.removeEventListener('popstate', rewriteHistoryAfterForward);
+ self.allowHashChange = true;
+ self.historyUnlocked = true;
+ callback();
+ }
}
- // Remove the event listener when navigating away from the document,
- // since 'beforeunload' prevents Firefox from caching the document.
- window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false);
- }
- window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
- window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
- // If the entire viewer (including the PDF file) is cached in the browser,
- // we need to reattach the 'beforeunload' event listener since
- // the 'DOMContentLoaded' event is not fired on 'pageshow'.
+ function pdfHistoryBeforeUnload() {
+ var previousParams = self._getPreviousParams(null, true);
+ if (previousParams) {
+ var replacePrevious = (!self.current.dest &&
+ self.current.hash !== self.previousHash);
+ self._pushToHistory(previousParams, false, replacePrevious);
+ self._updatePreviousBookmark();
+ }
+ // Remove the event listener when navigating away from the document,
+ // since 'beforeunload' prevents Firefox from caching the document.
+ window.removeEventListener('beforeunload', pdfHistoryBeforeUnload,
+ false);
+ }
+
window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
- }, false);
- },
- _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
- return (state && state.uid >= 0 &&
- state.fingerprint && this.fingerprint === state.fingerprint &&
- state.target && state.target.hash) ? true : false;
- },
+ window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
+ // If the entire viewer (including the PDF file) is cached in
+ // the browser, we need to reattach the 'beforeunload' event listener
+ // since the 'DOMContentLoaded' event is not fired on 'pageshow'.
+ window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+ }, false);
- _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
- replace) {
- if (replace) {
- window.history.replaceState(stateObj, '', document.URL);
- } else {
- window.history.pushState(stateObj, '', document.URL);
- }
- },
+ window.addEventListener('presentationmodechanged', function(e) {
+ self.isViewerInPresentationMode = !!e.detail.active;
+ });
+ },
- get isHashChangeUnlocked() {
- if (!this.initialized) {
- return true;
- }
- // If the current hash changes when moving back/forward in the history,
- // this will trigger a 'popstate' event *as well* as a 'hashchange' event.
- // Since the hash generally won't correspond to the exact the position
- // stored in the history's state object, triggering the 'hashchange' event
- // can thus corrupt the browser history.
- //
- // When the hash changes during a 'popstate' event, we *only* prevent the
- // first 'hashchange' event and immediately reset allowHashChange.
- // If it is not reset, the user would not be able to change the hash.
+ clearHistoryState: function pdfHistory_clearHistoryState() {
+ this._pushOrReplaceState(null, true);
+ },
- var temp = this.allowHashChange;
- this.allowHashChange = true;
- return temp;
- },
+ _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
+ return (state && state.uid >= 0 &&
+ state.fingerprint && this.fingerprint === state.fingerprint &&
+ state.target && state.target.hash) ? true : false;
+ },
- _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
- if (this.updatePreviousBookmark &&
+ _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
+ replace) {
+ if (replace) {
+ window.history.replaceState(stateObj, '', document.URL);
+ } else {
+ window.history.pushState(stateObj, '', document.URL);
+ }
+ },
+
+ get isHashChangeUnlocked() {
+ if (!this.initialized) {
+ return true;
+ }
+ return this.allowHashChange;
+ },
+
+ _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
+ if (this.updatePreviousBookmark &&
this.currentBookmark && this.currentPage) {
- this.previousBookmark = this.currentBookmark;
- this.previousPage = this.currentPage;
- this.updatePreviousBookmark = false;
- }
- },
+ this.previousBookmark = this.currentBookmark;
+ this.previousPage = this.currentPage;
+ this.updatePreviousBookmark = false;
+ }
+ },
- updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
- pageNum) {
- if (this.initialized) {
- this.currentBookmark = bookmark.substring(1);
- this.currentPage = pageNum | 0;
- this._updatePreviousBookmark();
- }
- },
+ updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
+ pageNum) {
+ if (this.initialized) {
+ this.currentBookmark = bookmark.substring(1);
+ this.currentPage = pageNum | 0;
+ this._updatePreviousBookmark();
+ }
+ },
- updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
- if (this.initialized) {
- this.nextHashParam = param;
- }
- },
+ updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
+ if (this.initialized) {
+ this.nextHashParam = param;
+ }
+ },
- push: function pdfHistoryPush(params, isInitialBookmark) {
- if (!(this.initialized && this.historyUnlocked)) {
- return;
- }
- if (params.dest && !params.hash) {
- params.hash = (this.current.hash && this.current.dest &&
- this.current.dest === params.dest) ?
- this.current.hash :
- this.linkService.getDestinationHash(params.dest).split('#')[1];
- }
- if (params.page) {
- params.page |= 0;
- }
- if (isInitialBookmark) {
- var target = window.history.state.target;
- if (!target) {
- // Invoked when the user specifies an initial bookmark,
- // thus setting initialBookmark, when the document is loaded.
- this._pushToHistory(params, false);
- this.previousHash = window.location.hash.substring(1);
+ push: function pdfHistoryPush(params, isInitialBookmark) {
+ if (!(this.initialized && this.historyUnlocked)) {
+ return;
}
- this.updatePreviousBookmark = this.nextHashParam ? false : true;
- if (target) {
- // If the current document is reloaded,
- // avoid creating duplicate entries in the history.
- this._updatePreviousBookmark();
+ if (params.dest && !params.hash) {
+ params.hash = (this.current.hash && this.current.dest &&
+ this.current.dest === params.dest) ?
+ this.current.hash :
+ this.linkService.getDestinationHash(params.dest).split('#')[1];
}
- return;
- }
- if (this.nextHashParam) {
- if (this.nextHashParam === params.hash) {
- this.nextHashParam = null;
- this.updatePreviousBookmark = true;
+ if (params.page) {
+ params.page |= 0;
+ }
+ if (isInitialBookmark) {
+ var target = window.history.state.target;
+ if (!target) {
+ // Invoked when the user specifies an initial bookmark,
+ // thus setting initialBookmark, when the document is loaded.
+ this._pushToHistory(params, false);
+ this.previousHash = window.location.hash.substring(1);
+ }
+ this.updatePreviousBookmark = this.nextHashParam ? false : true;
+ if (target) {
+ // If the current document is reloaded,
+ // avoid creating duplicate entries in the history.
+ this._updatePreviousBookmark();
+ }
return;
- } else {
- this.nextHashParam = null;
}
- }
-
- if (params.hash) {
- if (this.current.hash) {
- if (this.current.hash !== params.hash) {
- this._pushToHistory(params, true);
+ if (this.nextHashParam) {
+ if (this.nextHashParam === params.hash) {
+ this.nextHashParam = null;
+ this.updatePreviousBookmark = true;
+ return;
} else {
- if (!this.current.page && params.page) {
- this._pushToHistory(params, false, true);
+ this.nextHashParam = null;
+ }
+ }
+
+ if (params.hash) {
+ if (this.current.hash) {
+ if (this.current.hash !== params.hash) {
+ this._pushToHistory(params, true);
+ } else {
+ if (!this.current.page && params.page) {
+ this._pushToHistory(params, false, true);
+ }
+ this.updatePreviousBookmark = true;
}
- this.updatePreviousBookmark = true;
+ } else {
+ this._pushToHistory(params, true);
}
- } else {
+ } else if (this.current.page && params.page &&
+ this.current.page !== params.page) {
this._pushToHistory(params, true);
}
- } else if (this.current.page && params.page &&
- this.current.page !== params.page) {
- this._pushToHistory(params, true);
- }
- },
+ },
- _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
- beforeUnload) {
- if (!(this.currentBookmark && this.currentPage)) {
- return null;
- } else if (this.updatePreviousBookmark) {
- this.updatePreviousBookmark = false;
- }
- if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
- // Prevent the history from getting stuck in the current state,
- // effectively preventing the user from going back/forward in the history.
- //
- // This happens if the current position in the document didn't change when
- // the history was previously updated. The reasons for this are either:
- // 1. The current zoom value is such that the document does not need to,
- // or cannot, be scrolled to display the destination.
- // 2. The previous destination is broken, and doesn't actally point to a
- // position within the document.
- // (This is either due to a bad PDF generator, or the user making a
- // mistake when entering a destination in the hash parameters.)
- return null;
- }
- if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
- if (this.previousBookmark === this.currentBookmark) {
+ _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
+ beforeUnload) {
+ if (!(this.currentBookmark && this.currentPage)) {
return null;
+ } else if (this.updatePreviousBookmark) {
+ this.updatePreviousBookmark = false;
}
- } else if (this.current.page || onlyCheckPage) {
- if (this.previousPage === this.currentPage) {
+ if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
+ // Prevent the history from getting stuck in the current state,
+ // effectively preventing the user from going back/forward in
+ // the history.
+ //
+ // This happens if the current position in the document didn't change
+ // when the history was previously updated. The reasons for this are
+ // either:
+ // 1. The current zoom value is such that the document does not need to,
+ // or cannot, be scrolled to display the destination.
+ // 2. The previous destination is broken, and doesn't actally point to a
+ // position within the document.
+ // (This is either due to a bad PDF generator, or the user making a
+ // mistake when entering a destination in the hash parameters.)
return null;
}
- } else {
- return null;
- }
- var params = { hash: this.currentBookmark, page: this.currentPage };
- if (PresentationMode.active) {
- params.hash = null;
- }
- return params;
- },
+ if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
+ if (this.previousBookmark === this.currentBookmark) {
+ return null;
+ }
+ } else if (this.current.page || onlyCheckPage) {
+ if (this.previousPage === this.currentPage) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ var params = {hash: this.currentBookmark, page: this.currentPage};
+ if (this.isViewerInPresentationMode) {
+ params.hash = null;
+ }
+ return params;
+ },
- _stateObj: function pdfHistory_stateObj(params) {
- return { fingerprint: this.fingerprint, uid: this.uid, target: params };
- },
+ _stateObj: function pdfHistory_stateObj(params) {
+ return {fingerprint: this.fingerprint, uid: this.uid, target: params};
+ },
- _pushToHistory: function pdfHistory_pushToHistory(params,
- addPrevious, overwrite) {
- if (!this.initialized) {
- return;
- }
- if (!params.hash && params.page) {
- params.hash = ('page=' + params.page);
- }
- if (addPrevious && !overwrite) {
- var previousParams = this._getPreviousParams();
- if (previousParams) {
- var replacePrevious = (!this.current.dest &&
- this.current.hash !== this.previousHash);
- this._pushToHistory(previousParams, false, replacePrevious);
+ _pushToHistory: function pdfHistory_pushToHistory(params,
+ addPrevious, overwrite) {
+ if (!this.initialized) {
+ return;
}
- }
- this._pushOrReplaceState(this._stateObj(params),
- (overwrite || this.uid === 0));
- this.currentUid = this.uid++;
- this.current = params;
- this.updatePreviousBookmark = true;
- },
+ if (!params.hash && params.page) {
+ params.hash = ('page=' + params.page);
+ }
+ if (addPrevious && !overwrite) {
+ var previousParams = this._getPreviousParams();
+ if (previousParams) {
+ var replacePrevious = (!this.current.dest &&
+ this.current.hash !== this.previousHash);
+ this._pushToHistory(previousParams, false, replacePrevious);
+ }
+ }
+ this._pushOrReplaceState(this._stateObj(params),
+ (overwrite || this.uid === 0));
+ this.currentUid = this.uid++;
+ this.current = params;
+ this.updatePreviousBookmark = true;
+ },
- _goTo: function pdfHistory_goTo(state) {
- if (!(this.initialized && this.historyUnlocked &&
- this._isStateObjectDefined(state))) {
- return;
- }
- if (!this.reInitialized && state.uid < this.currentUid) {
- var previousParams = this._getPreviousParams(true);
- if (previousParams) {
- this._pushToHistory(this.current, false);
- this._pushToHistory(previousParams, false);
- this.currentUid = state.uid;
- window.history.back();
+ _goTo: function pdfHistory_goTo(state) {
+ if (!(this.initialized && this.historyUnlocked &&
+ this._isStateObjectDefined(state))) {
return;
}
- }
- this.historyUnlocked = false;
+ if (!this.reInitialized && state.uid < this.currentUid) {
+ var previousParams = this._getPreviousParams(true);
+ if (previousParams) {
+ this._pushToHistory(this.current, false);
+ this._pushToHistory(previousParams, false);
+ this.currentUid = state.uid;
+ window.history.back();
+ return;
+ }
+ }
+ this.historyUnlocked = false;
- if (state.target.dest) {
- this.linkService.navigateTo(state.target.dest);
- } else {
- this.linkService.setHash(state.target.hash);
- }
- this.currentUid = state.uid;
- if (state.uid > this.uid) {
- this.uid = state.uid;
- }
- this.current = state.target;
- this.updatePreviousBookmark = true;
+ if (state.target.dest) {
+ this.linkService.navigateTo(state.target.dest);
+ } else {
+ this.linkService.setHash(state.target.hash);
+ }
+ this.currentUid = state.uid;
+ if (state.uid > this.uid) {
+ this.uid = state.uid;
+ }
+ this.current = state.target;
+ this.updatePreviousBookmark = true;
- var currentHash = window.location.hash.substring(1);
- if (this.previousHash !== currentHash) {
- this.allowHashChange = false;
- }
- this.previousHash = currentHash;
+ var currentHash = window.location.hash.substring(1);
+ if (this.previousHash !== currentHash) {
+ this.allowHashChange = false;
+ }
+ this.previousHash = currentHash;
- this.historyUnlocked = true;
- },
+ this.historyUnlocked = true;
+ },
- back: function pdfHistoryBack() {
- this.go(-1);
- },
+ back: function pdfHistoryBack() {
+ this.go(-1);
+ },
- forward: function pdfHistoryForward() {
- this.go(1);
- },
+ forward: function pdfHistoryForward() {
+ this.go(1);
+ },
- go: function pdfHistoryGo(direction) {
- if (this.initialized && this.historyUnlocked) {
- var state = window.history.state;
- if (direction === -1 && state && state.uid > 0) {
- window.history.back();
- } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
- window.history.forward();
+ go: function pdfHistoryGo(direction) {
+ if (this.initialized && this.historyUnlocked) {
+ var state = window.history.state;
+ if (direction === -1 && state && state.uid > 0) {
+ window.history.back();
+ } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
+ window.history.forward();
+ }
}
}
- }
-};
+ };
+ return PDFHistory;
+})();
+
var SecondaryToolbar = {
opened: false,
previousContainerHeight: null,
newContainerHeight: null,
initialize: function secondaryToolbarInitialize(options) {
this.toolbar = options.toolbar;
- this.presentationMode = options.presentationMode;
- this.documentProperties = options.documentProperties;
this.buttonContainer = this.toolbar.firstElementChild;
// Define the toolbar buttons.
this.toggleButton = options.toggleButton;
this.presentationModeButton = options.presentationModeButton;
@@ -1774,11 +2225,11 @@
}
},
// Event handling functions.
presentationModeClick: function secondaryToolbarPresentationModeClick(evt) {
- this.presentationMode.request();
+ PDFViewerApplication.requestPresentationMode();
this.close();
},
openFileClick: function secondaryToolbarOpenFileClick(evt) {
document.getElementById('fileInput').click();
@@ -1818,11 +2269,11 @@
pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
PDFViewerApplication.rotatePages(-90);
},
documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) {
- this.documentProperties.open();
+ PDFViewerApplication.pdfDocumentProperties.open();
this.close();
},
// Misc. functions for interacting with the toolbar.
setMaxHeight: function secondaryToolbarSetMaxHeight(container) {
@@ -1866,269 +2317,390 @@
}
}
};
+var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1500; // in ms
var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms
-var SELECTOR = 'presentationControls';
-var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1000; // in ms
+var ACTIVE_SELECTOR = 'pdfPresentationMode';
+var CONTROLS_SELECTOR = 'pdfPresentationModeControls';
-var PresentationMode = {
- active: false,
- args: null,
- contextMenuOpen: false,
- prevCoords: { x: null, y: null },
+/**
+ * @typedef {Object} PDFPresentationModeOptions
+ * @property {HTMLDivElement} container - The container for the viewer element.
+ * @property {HTMLDivElement} viewer - (optional) The viewer element.
+ * @property {PDFViewer} pdfViewer - The document viewer.
+ * @property {PDFThumbnailViewer} pdfThumbnailViewer - (optional) The thumbnail
+ * viewer.
+ * @property {Array} contextMenuItems - (optional) The menuitems that are added
+ * to the context menu in Presentation Mode.
+ */
- initialize: function presentationModeInitialize(options) {
+/**
+ * @class
+ */
+var PDFPresentationMode = (function PDFPresentationModeClosure() {
+ /**
+ * @constructs PDFPresentationMode
+ * @param {PDFPresentationModeOptions} options
+ */
+ function PDFPresentationMode(options) {
this.container = options.container;
- this.secondaryToolbar = options.secondaryToolbar;
+ this.viewer = options.viewer || options.container.firstElementChild;
+ this.pdfViewer = options.pdfViewer;
+ this.pdfThumbnailViewer = options.pdfThumbnailViewer || null;
+ var contextMenuItems = options.contextMenuItems || null;
- this.viewer = this.container.firstElementChild;
+ this.active = false;
+ this.args = null;
+ this.contextMenuOpen = false;
+ this.mouseScrollTimeStamp = 0;
+ this.mouseScrollDelta = 0;
- this.firstPage = options.firstPage;
- this.lastPage = options.lastPage;
- this.pageRotateCw = options.pageRotateCw;
- this.pageRotateCcw = options.pageRotateCcw;
+ if (contextMenuItems) {
+ for (var i = 0, ii = contextMenuItems.length; i < ii; i++) {
+ var item = contextMenuItems[i];
+ item.element.addEventListener('click', function (handler) {
+ this.contextMenuOpen = false;
+ handler();
+ }.bind(this, item.handler));
+ }
+ }
+ }
- this.firstPage.addEventListener('click', function() {
- this.contextMenuOpen = false;
- this.secondaryToolbar.firstPageClick();
- }.bind(this));
- this.lastPage.addEventListener('click', function() {
- this.contextMenuOpen = false;
- this.secondaryToolbar.lastPageClick();
- }.bind(this));
+ PDFPresentationMode.prototype = {
+ /**
+ * Request the browser to enter fullscreen mode.
+ * @returns {boolean} Indicating if the request was successful.
+ */
+ request: function PDFPresentationMode_request() {
+ if (this.switchInProgress || this.active ||
+ !this.viewer.hasChildNodes()) {
+ return false;
+ }
+ this._addFullscreenChangeListeners();
+ this._setSwitchInProgress();
+ this._notifyStateChange();
- this.pageRotateCw.addEventListener('click', function() {
- this.contextMenuOpen = false;
- this.secondaryToolbar.pageRotateCwClick();
- }.bind(this));
- this.pageRotateCcw.addEventListener('click', function() {
- this.contextMenuOpen = false;
- this.secondaryToolbar.pageRotateCcwClick();
- }.bind(this));
- },
+ if (this.container.requestFullscreen) {
+ this.container.requestFullscreen();
+ } else if (this.container.mozRequestFullScreen) {
+ this.container.mozRequestFullScreen();
+ } else if (this.container.webkitRequestFullscreen) {
+ this.container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+ } else if (this.container.msRequestFullscreen) {
+ this.container.msRequestFullscreen();
+ } else {
+ return false;
+ }
- get isFullscreen() {
- return (document.fullscreenElement ||
- document.mozFullScreen ||
- document.webkitIsFullScreen ||
- document.msFullscreenElement);
- },
+ this.args = {
+ page: this.pdfViewer.currentPageNumber,
+ previousScale: this.pdfViewer.currentScaleValue,
+ };
- /**
- * Initialize a timeout that is used to specify switchInProgress when the
- * browser transitions to fullscreen mode. Since resize events are triggered
- * multiple times during the switch to fullscreen mode, this is necessary in
- * order to prevent the page from being scrolled partially, or completely,
- * out of view when Presentation Mode is enabled.
- * Note: This is only an issue at certain zoom levels, e.g. 'page-width'.
- */
- _setSwitchInProgress: function presentationMode_setSwitchInProgress() {
- if (this.switchInProgress) {
- clearTimeout(this.switchInProgress);
- }
- this.switchInProgress = setTimeout(function switchInProgressTimeout() {
- delete this.switchInProgress;
- this._notifyStateChange();
- }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
- },
+ return true;
+ },
- _resetSwitchInProgress: function presentationMode_resetSwitchInProgress() {
- if (this.switchInProgress) {
- clearTimeout(this.switchInProgress);
- delete this.switchInProgress;
- }
- },
+ /**
+ * Switches page when the user scrolls (using a scroll wheel or a touchpad)
+ * with large enough motion, to prevent accidental page switches.
+ * @param {number} delta - The delta value from the mouse event.
+ */
+ mouseScroll: function PDFPresentationMode_mouseScroll(delta) {
+ if (!this.active) {
+ return;
+ }
+ var MOUSE_SCROLL_COOLDOWN_TIME = 50;
+ var PAGE_SWITCH_THRESHOLD = 120;
+ var PageSwitchDirection = {
+ UP: -1,
+ DOWN: 1
+ };
- request: function presentationModeRequest() {
- if (!PDFViewerApplication.supportsFullscreen || this.isFullscreen ||
- !this.viewer.hasChildNodes()) {
- return false;
- }
- this._setSwitchInProgress();
- this._notifyStateChange();
+ var currentTime = (new Date()).getTime();
+ var storedTime = this.mouseScrollTimeStamp;
- if (this.container.requestFullscreen) {
- this.container.requestFullscreen();
- } else if (this.container.mozRequestFullScreen) {
- this.container.mozRequestFullScreen();
- } else if (this.container.webkitRequestFullScreen) {
- this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
- } else if (this.container.msRequestFullscreen) {
- this.container.msRequestFullscreen();
- } else {
- return false;
- }
+ // If we've already switched page, avoid accidentally switching again.
+ if (currentTime > storedTime &&
+ currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
+ return;
+ }
+ // If the scroll direction changed, reset the accumulated scroll delta.
+ if ((this.mouseScrollDelta > 0 && delta < 0) ||
+ (this.mouseScrollDelta < 0 && delta > 0)) {
+ this._resetMouseScrollState();
+ }
+ this.mouseScrollDelta += delta;
- this.args = {
- page: PDFViewerApplication.page,
- previousScale: PDFViewerApplication.currentScaleValue
- };
+ if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) {
+ var pageSwitchDirection = (this.mouseScrollDelta > 0) ?
+ PageSwitchDirection.UP : PageSwitchDirection.DOWN;
+ var page = this.pdfViewer.currentPageNumber;
+ this._resetMouseScrollState();
- return true;
- },
+ // If we're at the first/last page, we don't need to do anything.
+ if ((page === 1 && pageSwitchDirection === PageSwitchDirection.UP) ||
+ (page === this.pdfViewer.pagesCount &&
+ pageSwitchDirection === PageSwitchDirection.DOWN)) {
+ return;
+ }
+ this.pdfViewer.currentPageNumber = (page + pageSwitchDirection);
+ this.mouseScrollTimeStamp = currentTime;
+ }
+ },
- _notifyStateChange: function presentationModeNotifyStateChange() {
- var event = document.createEvent('CustomEvent');
- event.initCustomEvent('presentationmodechanged', true, true, {
- active: PresentationMode.active,
- switchInProgress: !!PresentationMode.switchInProgress
- });
- window.dispatchEvent(event);
- },
+ get isFullscreen() {
+ return !!(document.fullscreenElement ||
+ document.mozFullScreen ||
+ document.webkitIsFullScreen ||
+ document.msFullscreenElement);
+ },
- enter: function presentationModeEnter() {
- this.active = true;
- this._resetSwitchInProgress();
- this._notifyStateChange();
+ /**
+ * @private
+ */
+ _notifyStateChange: function PDFPresentationMode_notifyStateChange() {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('presentationmodechanged', true, true, {
+ active: this.active,
+ switchInProgress: !!this.switchInProgress
+ });
+ window.dispatchEvent(event);
+ },
- // Ensure that the correct page is scrolled into view when entering
- // Presentation Mode, by waiting until fullscreen mode in enabled.
- // Note: This is only necessary in non-Mozilla browsers.
- setTimeout(function enterPresentationModeTimeout() {
- PDFViewerApplication.page = this.args.page;
- PDFViewerApplication.setScale('page-fit', true);
- }.bind(this), 0);
+ /**
+ * Used to initialize a timeout when requesting Presentation Mode,
+ * i.e. when the browser is requested to enter fullscreen mode.
+ * This timeout is used to prevent the current page from being scrolled
+ * partially, or completely, out of view when entering Presentation Mode.
+ * NOTE: This issue seems limited to certain zoom levels (e.g. page-width).
+ * @private
+ */
+ _setSwitchInProgress: function PDFPresentationMode_setSwitchInProgress() {
+ if (this.switchInProgress) {
+ clearTimeout(this.switchInProgress);
+ }
+ this.switchInProgress = setTimeout(function switchInProgressTimeout() {
+ this._removeFullscreenChangeListeners();
+ delete this.switchInProgress;
+ this._notifyStateChange();
+ }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
+ },
- window.addEventListener('mousemove', this.mouseMove, false);
- window.addEventListener('mousedown', this.mouseDown, false);
- window.addEventListener('contextmenu', this.contextMenu, false);
+ /**
+ * @private
+ */
+ _resetSwitchInProgress:
+ function PDFPresentationMode_resetSwitchInProgress() {
+ if (this.switchInProgress) {
+ clearTimeout(this.switchInProgress);
+ delete this.switchInProgress;
+ }
+ },
- this.showControls();
- HandTool.enterPresentationMode();
- this.contextMenuOpen = false;
- this.container.setAttribute('contextmenu', 'viewerContextMenu');
- },
+ /**
+ * @private
+ */
+ _enter: function PDFPresentationMode_enter() {
+ this.active = true;
+ this._resetSwitchInProgress();
+ this._notifyStateChange();
+ this.container.classList.add(ACTIVE_SELECTOR);
- exit: function presentationModeExit() {
- var page = PDFViewerApplication.page;
+ // Ensure that the correct page is scrolled into view when entering
+ // Presentation Mode, by waiting until fullscreen mode in enabled.
+ setTimeout(function enterPresentationModeTimeout() {
+ this.pdfViewer.currentPageNumber = this.args.page;
+ this.pdfViewer.currentScaleValue = 'page-fit';
+ }.bind(this), 0);
- // Ensure that the correct page is scrolled into view when exiting
- // Presentation Mode, by waiting until fullscreen mode is disabled.
- // Note: This is only necessary in non-Mozilla browsers.
- setTimeout(function exitPresentationModeTimeout() {
- this.active = false;
- this._notifyStateChange();
+ this._addWindowListeners();
+ this._showControls();
+ this.contextMenuOpen = false;
+ this.container.setAttribute('contextmenu', 'viewerContextMenu');
- PDFViewerApplication.setScale(this.args.previousScale, true);
- PDFViewerApplication.page = page;
- this.args = null;
- }.bind(this), 0);
+ // Text selection is disabled in Presentation Mode, thus it's not possible
+ // for the user to deselect text that is selected (e.g. with "Select all")
+ // when entering Presentation Mode, hence we remove any active selection.
+ window.getSelection().removeAllRanges();
+ },
- window.removeEventListener('mousemove', this.mouseMove, false);
- window.removeEventListener('mousedown', this.mouseDown, false);
- window.removeEventListener('contextmenu', this.contextMenu, false);
+ /**
+ * @private
+ */
+ _exit: function PDFPresentationMode_exit() {
+ var page = this.pdfViewer.currentPageNumber;
+ this.container.classList.remove(ACTIVE_SELECTOR);
- this.hideControls();
- PDFViewerApplication.clearMouseScrollState();
- HandTool.exitPresentationMode();
- this.container.removeAttribute('contextmenu');
- this.contextMenuOpen = false;
+ // Ensure that the correct page is scrolled into view when exiting
+ // Presentation Mode, by waiting until fullscreen mode is disabled.
+ setTimeout(function exitPresentationModeTimeout() {
+ this.active = false;
+ this._removeFullscreenChangeListeners();
+ this._notifyStateChange();
- // Ensure that the thumbnail of the current page is visible
- // when exiting presentation mode.
- scrollIntoView(document.getElementById('thumbnailContainer' + page));
- },
+ this.pdfViewer.currentScaleValue = this.args.previousScale;
+ this.pdfViewer.currentPageNumber = page;
+ this.args = null;
+ }.bind(this), 0);
- showControls: function presentationModeShowControls() {
- if (this.controlsTimeout) {
+ this._removeWindowListeners();
+ this._hideControls();
+ this._resetMouseScrollState();
+ this.container.removeAttribute('contextmenu');
+ this.contextMenuOpen = false;
+
+ if (this.pdfThumbnailViewer) {
+ this.pdfThumbnailViewer.ensureThumbnailVisible(page);
+ }
+ },
+
+ /**
+ * @private
+ */
+ _mouseDown: function PDFPresentationMode_mouseDown(evt) {
+ if (this.contextMenuOpen) {
+ this.contextMenuOpen = false;
+ evt.preventDefault();
+ return;
+ }
+ if (evt.button === 0) {
+ // Enable clicking of links in presentation mode. Please note:
+ // Only links pointing to destinations in the current PDF document work.
+ var isInternalLink = (evt.target.href &&
+ evt.target.classList.contains('internalLink'));
+ if (!isInternalLink) {
+ // Unless an internal link was clicked, advance one page.
+ evt.preventDefault();
+ this.pdfViewer.currentPageNumber += (evt.shiftKey ? -1 : 1);
+ }
+ }
+ },
+
+ /**
+ * @private
+ */
+ _contextMenu: function PDFPresentationMode_contextMenu() {
+ this.contextMenuOpen = true;
+ },
+
+ /**
+ * @private
+ */
+ _showControls: function PDFPresentationMode_showControls() {
+ if (this.controlsTimeout) {
+ clearTimeout(this.controlsTimeout);
+ } else {
+ this.container.classList.add(CONTROLS_SELECTOR);
+ }
+ this.controlsTimeout = setTimeout(function showControlsTimeout() {
+ this.container.classList.remove(CONTROLS_SELECTOR);
+ delete this.controlsTimeout;
+ }.bind(this), DELAY_BEFORE_HIDING_CONTROLS);
+ },
+
+ /**
+ * @private
+ */
+ _hideControls: function PDFPresentationMode_hideControls() {
+ if (!this.controlsTimeout) {
+ return;
+ }
clearTimeout(this.controlsTimeout);
- } else {
- this.container.classList.add(SELECTOR);
- }
- this.controlsTimeout = setTimeout(function hideControlsTimeout() {
- this.container.classList.remove(SELECTOR);
+ this.container.classList.remove(CONTROLS_SELECTOR);
delete this.controlsTimeout;
- }.bind(this), DELAY_BEFORE_HIDING_CONTROLS);
- },
+ },
- hideControls: function presentationModeHideControls() {
- if (!this.controlsTimeout) {
- return;
- }
- this.container.classList.remove(SELECTOR);
- clearTimeout(this.controlsTimeout);
- delete this.controlsTimeout;
- },
+ /**
+ * Resets the properties used for tracking mouse scrolling events.
+ * @private
+ */
+ _resetMouseScrollState:
+ function PDFPresentationMode_resetMouseScrollState() {
+ this.mouseScrollTimeStamp = 0;
+ this.mouseScrollDelta = 0;
+ },
- mouseMove: function presentationModeMouseMove(evt) {
- // Workaround for a bug in WebKit browsers that causes the 'mousemove' event
- // to be fired when the cursor is changed. For details, see:
- // http://code.google.com/p/chromium/issues/detail?id=103041.
+ /**
+ * @private
+ */
+ _addWindowListeners: function PDFPresentationMode_addWindowListeners() {
+ this.showControlsBind = this._showControls.bind(this);
+ this.mouseDownBind = this._mouseDown.bind(this);
+ this.resetMouseScrollStateBind = this._resetMouseScrollState.bind(this);
+ this.contextMenuBind = this._contextMenu.bind(this);
- var currCoords = { x: evt.clientX, y: evt.clientY };
- var prevCoords = PresentationMode.prevCoords;
- PresentationMode.prevCoords = currCoords;
+ window.addEventListener('mousemove', this.showControlsBind);
+ window.addEventListener('mousedown', this.mouseDownBind);
+ window.addEventListener('keydown', this.resetMouseScrollStateBind);
+ window.addEventListener('contextmenu', this.contextMenuBind);
+ },
- if (currCoords.x === prevCoords.x && currCoords.y === prevCoords.y) {
- return;
- }
- PresentationMode.showControls();
- },
+ /**
+ * @private
+ */
+ _removeWindowListeners:
+ function PDFPresentationMode_removeWindowListeners() {
+ window.removeEventListener('mousemove', this.showControlsBind);
+ window.removeEventListener('mousedown', this.mouseDownBind);
+ window.removeEventListener('keydown', this.resetMouseScrollStateBind);
+ window.removeEventListener('contextmenu', this.contextMenuBind);
- mouseDown: function presentationModeMouseDown(evt) {
- var self = PresentationMode;
- if (self.contextMenuOpen) {
- self.contextMenuOpen = false;
- evt.preventDefault();
- return;
- }
+ delete this.showControlsBind;
+ delete this.mouseDownBind;
+ delete this.resetMouseScrollStateBind;
+ delete this.contextMenuBind;
+ },
- if (evt.button === 0) {
- // Enable clicking of links in presentation mode. Please note:
- // Only links pointing to destinations in the current PDF document work.
- var isInternalLink = (evt.target.href &&
- evt.target.classList.contains('internalLink'));
- if (!isInternalLink) {
- // Unless an internal link was clicked, advance one page.
- evt.preventDefault();
- PDFViewerApplication.page += (evt.shiftKey ? -1 : 1);
+ /**
+ * @private
+ */
+ _fullscreenChange: function PDFPresentationMode_fullscreenChange() {
+ if (this.isFullscreen) {
+ this._enter();
+ } else {
+ this._exit();
}
- }
- },
+ },
- contextMenu: function presentationModeContextMenu(evt) {
- PresentationMode.contextMenuOpen = true;
- }
-};
+ /**
+ * @private
+ */
+ _addFullscreenChangeListeners:
+ function PDFPresentationMode_addFullscreenChangeListeners() {
+ this.fullscreenChangeBind = this._fullscreenChange.bind(this);
-(function presentationModeClosure() {
- function presentationModeChange(e) {
- if (PresentationMode.isFullscreen) {
- PresentationMode.enter();
- } else {
- PresentationMode.exit();
+ window.addEventListener('fullscreenchange', this.fullscreenChangeBind);
+ window.addEventListener('mozfullscreenchange', this.fullscreenChangeBind);
+ window.addEventListener('webkitfullscreenchange',
+ this.fullscreenChangeBind);
+ window.addEventListener('MSFullscreenChange', this.fullscreenChangeBind);
+ },
+
+ /**
+ * @private
+ */
+ _removeFullscreenChangeListeners:
+ function PDFPresentationMode_removeFullscreenChangeListeners() {
+ window.removeEventListener('fullscreenchange', this.fullscreenChangeBind);
+ window.removeEventListener('mozfullscreenchange',
+ this.fullscreenChangeBind);
+ window.removeEventListener('webkitfullscreenchange',
+ this.fullscreenChangeBind);
+ window.removeEventListener('MSFullscreenChange',
+ this.fullscreenChangeBind);
+
+ delete this.fullscreenChangeBind;
}
- }
+ };
- window.addEventListener('fullscreenchange', presentationModeChange, false);
- window.addEventListener('mozfullscreenchange', presentationModeChange, false);
- window.addEventListener('webkitfullscreenchange', presentationModeChange,
- false);
- window.addEventListener('MSFullscreenChange', presentationModeChange, false);
+ return PDFPresentationMode;
})();
-/* Copyright 2013 Rob Wu <gwnRob@gmail.com>
- * https://github.com/Rob--W/grab-to-pan.js
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-'use strict';
-
var GrabToPan = (function GrabToPanClosure() {
/**
* Construct a GrabToPan instance for a given HTML element.
* @param options.element {Element}
* @param options.ignoreTarget {function} optional. See `ignoreTarget(node)`
@@ -2361,10 +2933,21 @@
if (value) {
this.handTool.activate();
}
}.bind(this), function rejected(reason) {});
}.bind(this));
+
+ window.addEventListener('presentationmodechanged', function (evt) {
+ if (evt.detail.switchInProgress) {
+ return;
+ }
+ if (evt.detail.active) {
+ this.enterPresentationMode();
+ } else {
+ this.exitPresentationMode();
+ }
+ }.bind(this));
}
},
toggle: function handToolToggle() {
this.handTool.toggle();
@@ -2576,46 +3159,32 @@
}
}
};
-var DocumentProperties = {
- overlayName: null,
- rawFileSize: 0,
+/**
+ * @typedef {Object} PDFDocumentPropertiesOptions
+ * @property {string} overlayName - Name/identifier for the overlay.
+ * @property {Object} fields - Names and elements of the overlay's fields.
+ * @property {HTMLButtonElement} closeButton - Button for closing the overlay.
+ */
- // Document property fields (in the viewer).
- fileNameField: null,
- fileSizeField: null,
- titleField: null,
- authorField: null,
- subjectField: null,
- keywordsField: null,
- creationDateField: null,
- modificationDateField: null,
- creatorField: null,
- producerField: null,
- versionField: null,
- pageCountField: null,
- url: null,
- pdfDocument: null,
-
- initialize: function documentPropertiesInitialize(options) {
+/**
+ * @class
+ */
+var PDFDocumentProperties = (function PDFDocumentPropertiesClosure() {
+ /**
+ * @constructs PDFDocumentProperties
+ * @param {PDFDocumentPropertiesOptions} options
+ */
+ function PDFDocumentProperties(options) {
+ this.fields = options.fields;
this.overlayName = options.overlayName;
- // Set the document property fields.
- this.fileNameField = options.fileNameField;
- this.fileSizeField = options.fileSizeField;
- this.titleField = options.titleField;
- this.authorField = options.authorField;
- this.subjectField = options.subjectField;
- this.keywordsField = options.keywordsField;
- this.creationDateField = options.creationDateField;
- this.modificationDateField = options.modificationDateField;
- this.creatorField = options.creatorField;
- this.producerField = options.producerField;
- this.versionField = options.versionField;
- this.pageCountField = options.pageCountField;
+ this.rawFileSize = 0;
+ this.url = null;
+ this.pdfDocument = null;
// Bind the event listener for the Close button.
if (options.closeButton) {
options.closeButton.addEventListener('click', this.close.bind(this));
}
@@ -2623,153 +3192,194 @@
this.dataAvailablePromise = new Promise(function (resolve) {
this.resolveDataAvailable = resolve;
}.bind(this));
OverlayManager.register(this.overlayName, this.close.bind(this));
- },
+ }
- getProperties: function documentPropertiesGetProperties() {
- if (!OverlayManager.active) {
- // If the dialog was closed before dataAvailablePromise was resolved,
- // don't bother updating the properties.
- return;
- }
- // Get the file size (if it hasn't already been set).
- this.pdfDocument.getDownloadInfo().then(function(data) {
- if (data.length === this.rawFileSize) {
- return;
+ PDFDocumentProperties.prototype = {
+ /**
+ * Open the document properties overlay.
+ */
+ open: function PDFDocumentProperties_open() {
+ Promise.all([OverlayManager.open(this.overlayName),
+ this.dataAvailablePromise]).then(function () {
+ this._getProperties();
+ }.bind(this));
+ },
+
+ /**
+ * Close the document properties overlay.
+ */
+ close: function PDFDocumentProperties_close() {
+ OverlayManager.close(this.overlayName);
+ },
+
+ /**
+ * Set the file size of the PDF document. This method is used to
+ * update the file size in the document properties overlay once it
+ * is known so we do not have to wait until the entire file is loaded.
+ *
+ * @param {number} fileSize - The file size of the PDF document.
+ */
+ setFileSize: function PDFDocumentProperties_setFileSize(fileSize) {
+ if (fileSize > 0) {
+ this.rawFileSize = fileSize;
}
- this.setFileSize(data.length);
- this.updateUI(this.fileSizeField, this.parseFileSize());
- }.bind(this));
+ },
- // Get the document properties.
- this.pdfDocument.getMetadata().then(function(data) {
- var fields = [
- { field: this.fileNameField,
- content: getPDFFileNameFromURL(this.url) },
- { field: this.fileSizeField, content: this.parseFileSize() },
- { field: this.titleField, content: data.info.Title },
- { field: this.authorField, content: data.info.Author },
- { field: this.subjectField, content: data.info.Subject },
- { field: this.keywordsField, content: data.info.Keywords },
- { field: this.creationDateField,
- content: this.parseDate(data.info.CreationDate) },
- { field: this.modificationDateField,
- content: this.parseDate(data.info.ModDate) },
- { field: this.creatorField, content: data.info.Creator },
- { field: this.producerField, content: data.info.Producer },
- { field: this.versionField, content: data.info.PDFFormatVersion },
- { field: this.pageCountField, content: this.pdfDocument.numPages }
- ];
+ /**
+ * Set a reference to the PDF document and the URL in order
+ * to populate the overlay fields with the document properties.
+ * Note that the overlay will contain no information if this method
+ * is not called.
+ *
+ * @param {Object} pdfDocument - A reference to the PDF document.
+ * @param {string} url - The URL of the document.
+ */
+ setDocumentAndUrl:
+ function PDFDocumentProperties_setDocumentAndUrl(pdfDocument, url) {
+ this.pdfDocument = pdfDocument;
+ this.url = url;
+ this.resolveDataAvailable();
+ },
- // Show the properties in the dialog.
- for (var item in fields) {
- var element = fields[item];
- this.updateUI(element.field, element.content);
+ /**
+ * @private
+ */
+ _getProperties: function PDFDocumentProperties_getProperties() {
+ if (!OverlayManager.active) {
+ // If the dialog was closed before dataAvailablePromise was resolved,
+ // don't bother updating the properties.
+ return;
}
- }.bind(this));
- },
+ // Get the file size (if it hasn't already been set).
+ this.pdfDocument.getDownloadInfo().then(function(data) {
+ if (data.length === this.rawFileSize) {
+ return;
+ }
+ this.setFileSize(data.length);
+ this._updateUI(this.fields['fileSize'], this._parseFileSize());
+ }.bind(this));
- updateUI: function documentPropertiesUpdateUI(field, content) {
- if (field && content !== undefined && content !== '') {
- field.textContent = content;
- }
- },
+ // Get the document properties.
+ this.pdfDocument.getMetadata().then(function(data) {
+ var content = {
+ 'fileName': getPDFFileNameFromURL(this.url),
+ 'fileSize': this._parseFileSize(),
+ 'title': data.info.Title,
+ 'author': data.info.Author,
+ 'subject': data.info.Subject,
+ 'keywords': data.info.Keywords,
+ 'creationDate': this._parseDate(data.info.CreationDate),
+ 'modificationDate': this._parseDate(data.info.ModDate),
+ 'creator': data.info.Creator,
+ 'producer': data.info.Producer,
+ 'version': data.info.PDFFormatVersion,
+ 'pageCount': this.pdfDocument.numPages
+ };
- setFileSize: function documentPropertiesSetFileSize(fileSize) {
- if (fileSize > 0) {
- this.rawFileSize = fileSize;
- }
- },
+ // Show the properties in the dialog.
+ for (var identifier in content) {
+ this._updateUI(this.fields[identifier], content[identifier]);
+ }
+ }.bind(this));
+ },
- parseFileSize: function documentPropertiesParseFileSize() {
- var fileSize = this.rawFileSize, kb = fileSize / 1024;
- if (!kb) {
- return;
- } else if (kb < 1024) {
- return mozL10n.get('document_properties_kb', {
- size_kb: (+kb.toPrecision(3)).toLocaleString(),
- size_b: fileSize.toLocaleString()
- }, '{{size_kb}} KB ({{size_b}} bytes)');
- } else {
- return mozL10n.get('document_properties_mb', {
- size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
- size_b: fileSize.toLocaleString()
- }, '{{size_mb}} MB ({{size_b}} bytes)');
- }
- },
+ /**
+ * @private
+ */
+ _updateUI: function PDFDocumentProperties_updateUI(field, content) {
+ if (field && content !== undefined && content !== '') {
+ field.textContent = content;
+ }
+ },
- open: function documentPropertiesOpen() {
- Promise.all([OverlayManager.open(this.overlayName),
- this.dataAvailablePromise]).then(function () {
- this.getProperties();
- }.bind(this));
- },
+ /**
+ * @private
+ */
+ _parseFileSize: function PDFDocumentProperties_parseFileSize() {
+ var fileSize = this.rawFileSize, kb = fileSize / 1024;
+ if (!kb) {
+ return;
+ } else if (kb < 1024) {
+ return mozL10n.get('document_properties_kb', {
+ size_kb: (+kb.toPrecision(3)).toLocaleString(),
+ size_b: fileSize.toLocaleString()
+ }, '{{size_kb}} KB ({{size_b}} bytes)');
+ } else {
+ return mozL10n.get('document_properties_mb', {
+ size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
+ size_b: fileSize.toLocaleString()
+ }, '{{size_mb}} MB ({{size_b}} bytes)');
+ }
+ },
- close: function documentPropertiesClose() {
- OverlayManager.close(this.overlayName);
- },
+ /**
+ * @private
+ */
+ _parseDate: function PDFDocumentProperties_parseDate(inputDate) {
+ // This is implemented according to the PDF specification, but note that
+ // Adobe Reader doesn't handle changing the date to universal time
+ // and doesn't use the user's time zone (they're effectively ignoring
+ // the HH' and mm' parts of the date string).
+ var dateToParse = inputDate;
+ if (dateToParse === undefined) {
+ return '';
+ }
- parseDate: function documentPropertiesParseDate(inputDate) {
- // This is implemented according to the PDF specification (see
- // http://www.gnupdf.org/Date for an overview), but note that
- // Adobe Reader doesn't handle changing the date to universal time
- // and doesn't use the user's time zone (they're effectively ignoring
- // the HH' and mm' parts of the date string).
- var dateToParse = inputDate;
- if (dateToParse === undefined) {
- return '';
- }
+ // Remove the D: prefix if it is available.
+ if (dateToParse.substring(0,2) === 'D:') {
+ dateToParse = dateToParse.substring(2);
+ }
- // Remove the D: prefix if it is available.
- if (dateToParse.substring(0,2) === 'D:') {
- dateToParse = dateToParse.substring(2);
- }
+ // Get all elements from the PDF date string.
+ // JavaScript's Date object expects the month to be between
+ // 0 and 11 instead of 1 and 12, so we're correcting for this.
+ var year = parseInt(dateToParse.substring(0,4), 10);
+ var month = parseInt(dateToParse.substring(4,6), 10) - 1;
+ var day = parseInt(dateToParse.substring(6,8), 10);
+ var hours = parseInt(dateToParse.substring(8,10), 10);
+ var minutes = parseInt(dateToParse.substring(10,12), 10);
+ var seconds = parseInt(dateToParse.substring(12,14), 10);
+ var utRel = dateToParse.substring(14,15);
+ var offsetHours = parseInt(dateToParse.substring(15,17), 10);
+ var offsetMinutes = parseInt(dateToParse.substring(18,20), 10);
- // Get all elements from the PDF date string.
- // JavaScript's Date object expects the month to be between
- // 0 and 11 instead of 1 and 12, so we're correcting for this.
- var year = parseInt(dateToParse.substring(0,4), 10);
- var month = parseInt(dateToParse.substring(4,6), 10) - 1;
- var day = parseInt(dateToParse.substring(6,8), 10);
- var hours = parseInt(dateToParse.substring(8,10), 10);
- var minutes = parseInt(dateToParse.substring(10,12), 10);
- var seconds = parseInt(dateToParse.substring(12,14), 10);
- var utRel = dateToParse.substring(14,15);
- var offsetHours = parseInt(dateToParse.substring(15,17), 10);
- var offsetMinutes = parseInt(dateToParse.substring(18,20), 10);
+ // As per spec, utRel = 'Z' means equal to universal time.
+ // The other cases ('-' and '+') have to be handled here.
+ if (utRel === '-') {
+ hours += offsetHours;
+ minutes += offsetMinutes;
+ } else if (utRel === '+') {
+ hours -= offsetHours;
+ minutes -= offsetMinutes;
+ }
- // As per spec, utRel = 'Z' means equal to universal time.
- // The other cases ('-' and '+') have to be handled here.
- if (utRel === '-') {
- hours += offsetHours;
- minutes += offsetMinutes;
- } else if (utRel === '+') {
- hours -= offsetHours;
- minutes -= offsetMinutes;
+ // Return the new date format from the user's locale.
+ var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
+ var dateString = date.toLocaleDateString();
+ var timeString = date.toLocaleTimeString();
+ return mozL10n.get('document_properties_date_string',
+ {date: dateString, time: timeString},
+ '{{date}}, {{time}}');
}
+ };
- // Return the new date format from the user's locale.
- var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
- var dateString = date.toLocaleDateString();
- var timeString = date.toLocaleTimeString();
- return mozL10n.get('document_properties_date_string',
- {date: dateString, time: timeString},
- '{{date}}, {{time}}');
- }
-};
+ return PDFDocumentProperties;
+})();
var PresentationModeState = {
UNKNOWN: 0,
NORMAL: 1,
CHANGING: 2,
FULLSCREEN: 3,
};
var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
+var DEFAULT_CACHE_SIZE = 10;
var CLEANUP_TIMEOUT = 30000;
var RenderingStates = {
@@ -2916,633 +3526,592 @@
case RenderingStates.RUNNING:
this.highestPriorityPage = view.renderingId;
break;
case RenderingStates.INITIAL:
this.highestPriorityPage = view.renderingId;
- view.draw(this.renderHighestPriority.bind(this));
+ var continueRendering = function () {
+ this.renderHighestPriority();
+ }.bind(this);
+ view.draw().then(continueRendering, continueRendering);
break;
}
return true;
},
};
return PDFRenderingQueue;
})();
+var TEXT_LAYER_RENDER_DELAY = 200; // ms
+
/**
- * @constructor
- * @param {HTMLDivElement} container - The viewer element.
- * @param {number} id - The page unique ID (normally its number).
- * @param {number} scale - The page scale display.
- * @param {PageViewport} defaultViewport - The page viewport.
- * @param {IPDFLinkService} linkService - The navigation/linking service.
- * @param {PDFRenderingQueue} renderingQueue - The rendering queue object.
- * @param {Cache} cache - The page cache.
- * @param {PDFPageSource} pageSource
- * @param {PDFViewer} viewer
- *
+ * @typedef {Object} PDFPageViewOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {number} id - The page unique ID (normally its number).
+ * @property {number} scale - The page scale display.
+ * @property {PageViewport} defaultViewport - The page viewport.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ * @property {IPDFTextLayerFactory} textLayerFactory
+ * @property {IPDFAnnotationsLayerFactory} annotationsLayerFactory
+ */
+
+/**
+ * @class
* @implements {IRenderableView}
*/
-var PageView = function pageView(container, id, scale, defaultViewport,
- linkService, renderingQueue, cache,
- pageSource, viewer) {
- this.id = id;
- this.renderingId = 'page' + id;
+var PDFPageView = (function PDFPageViewClosure() {
+ var CustomStyle = PDFJS.CustomStyle;
- this.rotation = 0;
- this.scale = scale || 1.0;
- this.viewport = defaultViewport;
- this.pdfPageRotate = defaultViewport.rotation;
- this.hasRestrictedScaling = false;
+ /**
+ * @constructs PDFPageView
+ * @param {PDFPageViewOptions} options
+ */
+ function PDFPageView(options) {
+ var container = options.container;
+ var id = options.id;
+ var scale = options.scale;
+ var defaultViewport = options.defaultViewport;
+ var renderingQueue = options.renderingQueue;
+ var textLayerFactory = options.textLayerFactory;
+ var annotationsLayerFactory = options.annotationsLayerFactory;
- this.linkService = linkService;
- this.renderingQueue = renderingQueue;
- this.cache = cache;
- this.pageSource = pageSource;
- this.viewer = viewer;
+ this.id = id;
+ this.renderingId = 'page' + id;
- this.renderingState = RenderingStates.INITIAL;
- this.resume = null;
+ this.rotation = 0;
+ this.scale = scale || DEFAULT_SCALE;
+ this.viewport = defaultViewport;
+ this.pdfPageRotate = defaultViewport.rotation;
+ this.hasRestrictedScaling = false;
- this.textLayer = null;
+ this.renderingQueue = renderingQueue;
+ this.textLayerFactory = textLayerFactory;
+ this.annotationsLayerFactory = annotationsLayerFactory;
- this.zoomLayer = null;
+ this.renderingState = RenderingStates.INITIAL;
+ this.resume = null;
- this.annotationLayer = null;
+ this.onBeforeDraw = null;
+ this.onAfterDraw = null;
- var anchor = document.createElement('a');
- anchor.name = '' + this.id;
+ this.textLayer = null;
- var div = this.el = document.createElement('div');
- div.id = 'pageContainer' + this.id;
- div.className = 'page';
- div.style.width = Math.floor(this.viewport.width) + 'px';
- div.style.height = Math.floor(this.viewport.height) + 'px';
-
- container.appendChild(anchor);
- container.appendChild(div);
-
- this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
- this.pdfPage = pdfPage;
- this.pdfPageRotate = pdfPage.rotate;
- var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
- this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
- this.stats = pdfPage.stats;
- this.reset();
- };
-
- this.destroy = function pageViewDestroy() {
this.zoomLayer = null;
- this.reset();
- if (this.pdfPage) {
- this.pdfPage.destroy();
- }
- };
- this.reset = function pageViewReset(keepAnnotations) {
- if (this.renderTask) {
- this.renderTask.cancel();
- }
- this.resume = null;
- this.renderingState = RenderingStates.INITIAL;
+ this.annotationLayer = null;
+ var div = document.createElement('div');
+ div.id = 'pageContainer' + this.id;
+ div.className = 'page';
div.style.width = Math.floor(this.viewport.width) + 'px';
div.style.height = Math.floor(this.viewport.height) + 'px';
+ div.setAttribute('data-page-number', this.id);
+ this.div = div;
- var childNodes = div.childNodes;
- for (var i = div.childNodes.length - 1; i >= 0; i--) {
- var node = childNodes[i];
- if ((this.zoomLayer && this.zoomLayer === node) ||
- (keepAnnotations && this.annotationLayer === node)) {
- continue;
+ container.appendChild(div);
+ }
+
+ PDFPageView.prototype = {
+ setPdfPage: function PDFPageView_setPdfPage(pdfPage) {
+ this.pdfPage = pdfPage;
+ this.pdfPageRotate = pdfPage.rotate;
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS,
+ totalRotation);
+ this.stats = pdfPage.stats;
+ this.reset();
+ },
+
+ destroy: function PDFPageView_destroy() {
+ this.zoomLayer = null;
+ this.reset();
+ if (this.pdfPage) {
+ this.pdfPage.cleanup();
}
- div.removeChild(node);
- }
- div.removeAttribute('data-loaded');
+ },
- if (keepAnnotations) {
- if (this.annotationLayer) {
+ reset: function PDFPageView_reset(keepZoomLayer, keepAnnotations) {
+ if (this.renderTask) {
+ this.renderTask.cancel();
+ }
+ this.resume = null;
+ this.renderingState = RenderingStates.INITIAL;
+
+ var div = this.div;
+ div.style.width = Math.floor(this.viewport.width) + 'px';
+ div.style.height = Math.floor(this.viewport.height) + 'px';
+
+ var childNodes = div.childNodes;
+ var currentZoomLayerNode = (keepZoomLayer && this.zoomLayer) || null;
+ var currentAnnotationNode = (keepAnnotations && this.annotationLayer &&
+ this.annotationLayer.div) || null;
+ for (var i = childNodes.length - 1; i >= 0; i--) {
+ var node = childNodes[i];
+ if (currentZoomLayerNode === node || currentAnnotationNode === node) {
+ continue;
+ }
+ div.removeChild(node);
+ }
+ div.removeAttribute('data-loaded');
+
+ if (currentAnnotationNode) {
// Hide annotationLayer until all elements are resized
// so they are not displayed on the already-resized page
- this.annotationLayer.setAttribute('hidden', 'true');
+ this.annotationLayer.hide();
+ } else {
+ this.annotationLayer = null;
}
- } else {
- this.annotationLayer = null;
- }
- if (this.canvas) {
- // Zeroing the width and height causes Firefox to release graphics
- // resources immediately, which can greatly reduce memory consumption.
- this.canvas.width = 0;
- this.canvas.height = 0;
- delete this.canvas;
- }
+ if (this.canvas && !currentZoomLayerNode) {
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ this.canvas.width = 0;
+ this.canvas.height = 0;
+ delete this.canvas;
+ }
- this.loadingIconDiv = document.createElement('div');
- this.loadingIconDiv.className = 'loadingIcon';
- div.appendChild(this.loadingIconDiv);
- };
+ this.loadingIconDiv = document.createElement('div');
+ this.loadingIconDiv.className = 'loadingIcon';
+ div.appendChild(this.loadingIconDiv);
+ },
- this.update = function pageViewUpdate(scale, rotation) {
- this.scale = scale || this.scale;
+ update: function PDFPageView_update(scale, rotation) {
+ this.scale = scale || this.scale;
- if (typeof rotation !== 'undefined') {
- this.rotation = rotation;
- }
+ if (typeof rotation !== 'undefined') {
+ this.rotation = rotation;
+ }
- var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
- this.viewport = this.viewport.clone({
- scale: this.scale * CSS_UNITS,
- rotation: totalRotation
- });
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = this.viewport.clone({
+ scale: this.scale * CSS_UNITS,
+ rotation: totalRotation
+ });
- var isScalingRestricted = false;
- if (this.canvas && PDFJS.maxCanvasPixels > 0) {
- var ctx = this.canvas.getContext('2d');
- var outputScale = getOutputScale(ctx);
- var pixelsInViewport = this.viewport.width * this.viewport.height;
- var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
- if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
- ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
- PDFJS.maxCanvasPixels) {
- isScalingRestricted = true;
+ var isScalingRestricted = false;
+ if (this.canvas && PDFJS.maxCanvasPixels > 0) {
+ var outputScale = this.outputScale;
+ var pixelsInViewport = this.viewport.width * this.viewport.height;
+ var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
+ if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
+ ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
+ PDFJS.maxCanvasPixels) {
+ isScalingRestricted = true;
+ }
}
- }
- if (this.canvas &&
- (PDFJS.useOnlyCssZoom ||
- (this.hasRestrictedScaling && isScalingRestricted))) {
- this.cssTransform(this.canvas, true);
- return;
- } else if (this.canvas && !this.zoomLayer) {
- this.zoomLayer = this.canvas.parentNode;
- this.zoomLayer.style.position = 'absolute';
- }
- if (this.zoomLayer) {
- this.cssTransform(this.zoomLayer.firstChild);
- }
- this.reset(true);
- };
+ if (this.canvas) {
+ if (PDFJS.useOnlyCssZoom ||
+ (this.hasRestrictedScaling && isScalingRestricted)) {
+ this.cssTransform(this.canvas, true);
- this.cssTransform = function pageCssTransform(canvas, redrawAnnotations) {
- // Scale canvas, canvas wrapper, and page container.
- var width = this.viewport.width;
- var height = this.viewport.height;
- canvas.style.width = canvas.parentNode.style.width = div.style.width =
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagerendered', true, true, {
+ pageNumber: this.id,
+ cssTransform: true,
+ });
+ this.div.dispatchEvent(event);
+
+ return;
+ }
+ if (!this.zoomLayer) {
+ this.zoomLayer = this.canvas.parentNode;
+ this.zoomLayer.style.position = 'absolute';
+ }
+ }
+ if (this.zoomLayer) {
+ this.cssTransform(this.zoomLayer.firstChild);
+ }
+ this.reset(/* keepZoomLayer = */ true, /* keepAnnotations = */ true);
+ },
+
+ /**
+ * Called when moved in the parent's container.
+ */
+ updatePosition: function PDFPageView_updatePosition() {
+ if (this.textLayer) {
+ this.textLayer.render(TEXT_LAYER_RENDER_DELAY);
+ }
+ },
+
+ cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) {
+ // Scale canvas, canvas wrapper, and page container.
+ var width = this.viewport.width;
+ var height = this.viewport.height;
+ var div = this.div;
+ canvas.style.width = canvas.parentNode.style.width = div.style.width =
Math.floor(width) + 'px';
- canvas.style.height = canvas.parentNode.style.height = div.style.height =
+ canvas.style.height = canvas.parentNode.style.height = div.style.height =
Math.floor(height) + 'px';
- // The canvas may have been originally rotated, so rotate relative to that.
- var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
- var absRotation = Math.abs(relativeRotation);
- var scaleX = 1, scaleY = 1;
- if (absRotation === 90 || absRotation === 270) {
- // Scale x and y because of the rotation.
- scaleX = height / width;
- scaleY = width / height;
- }
- var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
- 'scale(' + scaleX + ',' + scaleY + ')';
- CustomStyle.setProp('transform', canvas, cssTransform);
+ // The canvas may have been originally rotated, rotate relative to that.
+ var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
+ var absRotation = Math.abs(relativeRotation);
+ var scaleX = 1, scaleY = 1;
+ if (absRotation === 90 || absRotation === 270) {
+ // Scale x and y because of the rotation.
+ scaleX = height / width;
+ scaleY = width / height;
+ }
+ var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
+ 'scale(' + scaleX + ',' + scaleY + ')';
+ CustomStyle.setProp('transform', canvas, cssTransform);
- if (this.textLayer) {
- // Rotating the text layer is more complicated since the divs inside the
- // the text layer are rotated.
- // TODO: This could probably be simplified by drawing the text layer in
- // one orientation then rotating overall.
- var textLayerViewport = this.textLayer.viewport;
- var textRelativeRotation = this.viewport.rotation -
- textLayerViewport.rotation;
- var textAbsRotation = Math.abs(textRelativeRotation);
- var scale = width / textLayerViewport.width;
- if (textAbsRotation === 90 || textAbsRotation === 270) {
- scale = width / textLayerViewport.height;
+ if (this.textLayer) {
+ // Rotating the text layer is more complicated since the divs inside the
+ // the text layer are rotated.
+ // TODO: This could probably be simplified by drawing the text layer in
+ // one orientation then rotating overall.
+ var textLayerViewport = this.textLayer.viewport;
+ var textRelativeRotation = this.viewport.rotation -
+ textLayerViewport.rotation;
+ var textAbsRotation = Math.abs(textRelativeRotation);
+ var scale = width / textLayerViewport.width;
+ if (textAbsRotation === 90 || textAbsRotation === 270) {
+ scale = width / textLayerViewport.height;
+ }
+ var textLayerDiv = this.textLayer.textLayerDiv;
+ var transX, transY;
+ switch (textAbsRotation) {
+ case 0:
+ transX = transY = 0;
+ break;
+ case 90:
+ transX = 0;
+ transY = '-' + textLayerDiv.style.height;
+ break;
+ case 180:
+ transX = '-' + textLayerDiv.style.width;
+ transY = '-' + textLayerDiv.style.height;
+ break;
+ case 270:
+ transX = '-' + textLayerDiv.style.width;
+ transY = 0;
+ break;
+ default:
+ console.error('Bad rotation value.');
+ break;
+ }
+ CustomStyle.setProp('transform', textLayerDiv,
+ 'rotate(' + textAbsRotation + 'deg) ' +
+ 'scale(' + scale + ', ' + scale + ') ' +
+ 'translate(' + transX + ', ' + transY + ')');
+ CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
}
- var textLayerDiv = this.textLayer.textLayerDiv;
- var transX, transY;
- switch (textAbsRotation) {
- case 0:
- transX = transY = 0;
- break;
- case 90:
- transX = 0;
- transY = '-' + textLayerDiv.style.height;
- break;
- case 180:
- transX = '-' + textLayerDiv.style.width;
- transY = '-' + textLayerDiv.style.height;
- break;
- case 270:
- transX = '-' + textLayerDiv.style.width;
- transY = 0;
- break;
- default:
- console.error('Bad rotation value.');
- break;
+
+ if (redrawAnnotations && this.annotationLayer) {
+ this.annotationLayer.render(this.viewport, 'display');
}
- CustomStyle.setProp('transform', textLayerDiv,
- 'rotate(' + textAbsRotation + 'deg) ' +
- 'scale(' + scale + ', ' + scale + ') ' +
- 'translate(' + transX + ', ' + transY + ')');
- CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
- }
+ },
- if (redrawAnnotations && this.annotationLayer) {
- setupAnnotations(div, this.pdfPage, this.viewport);
- }
- };
-
- Object.defineProperty(this, 'width', {
- get: function PageView_getWidth() {
+ get width() {
return this.viewport.width;
},
- enumerable: true
- });
- Object.defineProperty(this, 'height', {
- get: function PageView_getHeight() {
+ get height() {
return this.viewport.height;
},
- enumerable: true
- });
- var self = this;
+ getPagePoint: function PDFPageView_getPagePoint(x, y) {
+ return this.viewport.convertToPdfPoint(x, y);
+ },
- function setupAnnotations(pageDiv, pdfPage, viewport) {
-
- function bindLink(link, dest) {
- link.href = linkService.getDestinationHash(dest);
- link.onclick = function pageViewSetupLinksOnclick() {
- if (dest) {
- linkService.navigateTo(dest);
- }
- return false;
- };
- if (dest) {
- link.removeAttribute("target");
- link.className = 'internalLink';
+ draw: function PDFPageView_draw() {
+ if (this.renderingState !== RenderingStates.INITIAL) {
+ console.error('Must be in new state before drawing');
}
- }
- function bindNamedAction(link, action) {
- link.href = linkService.getAnchorUrl('');
- link.onclick = function pageViewSetupNamedActionOnClick() {
- linkService.executeNamedAction(action);
- return false;
- };
- link.removeAttribute("target");
- link.className = 'internalLink';
- }
+ this.renderingState = RenderingStates.RUNNING;
- pdfPage.getAnnotations().then(function(annotationsData) {
- viewport = viewport.clone({ dontFlip: true });
- var transform = viewport.transform;
- var transformStr = 'matrix(' + transform.join(',') + ')';
- var data, element, i, ii;
+ var pdfPage = this.pdfPage;
+ var viewport = this.viewport;
+ var div = this.div;
+ // Wrap the canvas so if it has a css transform for highdpi the overflow
+ // will be hidden in FF.
+ var canvasWrapper = document.createElement('div');
+ canvasWrapper.style.width = div.style.width;
+ canvasWrapper.style.height = div.style.height;
+ canvasWrapper.classList.add('canvasWrapper');
- if (self.annotationLayer) {
- // If an annotationLayer already exists, refresh its children's
- // transformation matrices
- for (i = 0, ii = annotationsData.length; i < ii; i++) {
- data = annotationsData[i];
- element = self.annotationLayer.querySelector(
- '[data-annotation-id="' + data.id + '"]');
- if (element) {
- CustomStyle.setProp('transform', element, transformStr);
- }
- }
- // See this.reset()
- self.annotationLayer.removeAttribute('hidden');
+ var canvas = document.createElement('canvas');
+ canvas.id = 'page' + this.id;
+ // Keep the canvas hidden until the first draw callback, or until drawing
+ // is complete when `!this.renderingQueue`, to prevent black flickering.
+ canvas.setAttribute('hidden', 'hidden');
+ var isCanvasHidden = true;
+
+ canvasWrapper.appendChild(canvas);
+ if (this.annotationLayer && this.annotationLayer.div) {
+ // annotationLayer needs to stay on top
+ div.insertBefore(canvasWrapper, this.annotationLayer.div);
} else {
- for (i = 0, ii = annotationsData.length; i < ii; i++) {
- data = annotationsData[i];
- if (!data || !data.hasHtml) {
- continue;
- }
+ div.appendChild(canvasWrapper);
+ }
+ this.canvas = canvas;
- element = PDFJS.AnnotationUtils.getHtmlElement(data,
- pdfPage.commonObjs);
- element.setAttribute('data-annotation-id', data.id);
- mozL10n.translate(element);
+ canvas.mozOpaque = true;
+ var ctx = canvas.getContext('2d', {alpha: false});
+ var outputScale = getOutputScale(ctx);
+ this.outputScale = outputScale;
- var rect = data.rect;
- var view = pdfPage.view;
- rect = PDFJS.Util.normalizeRect([
- rect[0],
- view[3] - rect[1] + view[1],
- rect[2],
- view[3] - rect[3] + view[1]
- ]);
- element.style.left = rect[0] + 'px';
- element.style.top = rect[1] + 'px';
- element.style.position = 'absolute';
+ if (PDFJS.useOnlyCssZoom) {
+ var actualSizeViewport = viewport.clone({scale: CSS_UNITS});
+ // Use a scale that will make the canvas be the original intended size
+ // of the page.
+ outputScale.sx *= actualSizeViewport.width / viewport.width;
+ outputScale.sy *= actualSizeViewport.height / viewport.height;
+ outputScale.scaled = true;
+ }
- CustomStyle.setProp('transform', element, transformStr);
- var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
- CustomStyle.setProp('transformOrigin', element, transformOriginStr);
-
- if (data.subtype === 'Link' && !data.url) {
- var link = element.getElementsByTagName('a')[0];
- if (link) {
- if (data.action) {
- bindNamedAction(link, data.action);
- } else {
- bindLink(link, ('dest' in data) ? data.dest : null);
- }
- }
- }
-
- if (!self.annotationLayer) {
- var annotationLayerDiv = document.createElement('div');
- annotationLayerDiv.className = 'annotationLayer';
- pageDiv.appendChild(annotationLayerDiv);
- self.annotationLayer = annotationLayerDiv;
- }
-
- self.annotationLayer.appendChild(element);
+ if (PDFJS.maxCanvasPixels > 0) {
+ var pixelsInViewport = viewport.width * viewport.height;
+ var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
+ if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
+ outputScale.sx = maxScale;
+ outputScale.sy = maxScale;
+ outputScale.scaled = true;
+ this.hasRestrictedScaling = true;
+ } else {
+ this.hasRestrictedScaling = false;
}
}
- });
- }
- this.getPagePoint = function pageViewGetPagePoint(x, y) {
- return this.viewport.convertToPdfPoint(x, y);
- };
+ var sfx = approximateFraction(outputScale.sx);
+ var sfy = approximateFraction(outputScale.sy);
+ canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
+ canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
+ canvas.style.width = roundToDivide(viewport.width, sfx[1]) + 'px';
+ canvas.style.height = roundToDivide(viewport.height, sfy[1]) + 'px';
+ // Add the viewport so it's known what it was originally drawn with.
+ canvas._viewport = viewport;
- this.draw = function pageviewDraw(callback) {
- var pdfPage = this.pdfPage;
+ var textLayerDiv = null;
+ var textLayer = null;
+ if (this.textLayerFactory) {
+ textLayerDiv = document.createElement('div');
+ textLayerDiv.className = 'textLayer';
+ textLayerDiv.style.width = canvasWrapper.style.width;
+ textLayerDiv.style.height = canvasWrapper.style.height;
+ if (this.annotationLayer && this.annotationLayer.div) {
+ // annotationLayer needs to stay on top
+ div.insertBefore(textLayerDiv, this.annotationLayer.div);
+ } else {
+ div.appendChild(textLayerDiv);
+ }
- if (this.pagePdfPromise) {
- return;
- }
- if (!pdfPage) {
- var promise = this.pageSource.getPage();
- promise.then(function(pdfPage) {
- delete this.pagePdfPromise;
- this.setPdfPage(pdfPage);
- this.draw(callback);
- }.bind(this));
- this.pagePdfPromise = promise;
- return;
- }
+ textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv,
+ this.id - 1,
+ this.viewport);
+ }
+ this.textLayer = textLayer;
- if (this.renderingState !== RenderingStates.INITIAL) {
- console.error('Must be in new state before drawing');
- }
+ var resolveRenderPromise, rejectRenderPromise;
+ var promise = new Promise(function (resolve, reject) {
+ resolveRenderPromise = resolve;
+ rejectRenderPromise = reject;
+ });
- this.renderingState = RenderingStates.RUNNING;
+ // Rendering area
- var viewport = this.viewport;
- // Wrap the canvas so if it has a css transform for highdpi the overflow
- // will be hidden in FF.
- var canvasWrapper = document.createElement('div');
- canvasWrapper.style.width = div.style.width;
- canvasWrapper.style.height = div.style.height;
- canvasWrapper.classList.add('canvasWrapper');
+ var self = this;
+ function pageViewDrawCallback(error) {
+ // The renderTask may have been replaced by a new one, so only remove
+ // the reference to the renderTask if it matches the one that is
+ // triggering this callback.
+ if (renderTask === self.renderTask) {
+ self.renderTask = null;
+ }
- var canvas = document.createElement('canvas');
- canvas.id = 'page' + this.id;
- canvasWrapper.appendChild(canvas);
- if (this.annotationLayer) {
- // annotationLayer needs to stay on top
- div.insertBefore(canvasWrapper, this.annotationLayer);
- } else {
- div.appendChild(canvasWrapper);
- }
- this.canvas = canvas;
+ if (error === 'cancelled') {
+ rejectRenderPromise(error);
+ return;
+ }
- var ctx = canvas.getContext('2d');
- var outputScale = getOutputScale(ctx);
+ self.renderingState = RenderingStates.FINISHED;
- if (PDFJS.useOnlyCssZoom) {
- var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
- // Use a scale that will make the canvas be the original intended size
- // of the page.
- outputScale.sx *= actualSizeViewport.width / viewport.width;
- outputScale.sy *= actualSizeViewport.height / viewport.height;
- outputScale.scaled = true;
- }
+ if (isCanvasHidden) {
+ self.canvas.removeAttribute('hidden');
+ isCanvasHidden = false;
+ }
- if (PDFJS.maxCanvasPixels > 0) {
- var pixelsInViewport = viewport.width * viewport.height;
- var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
- if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
- outputScale.sx = maxScale;
- outputScale.sy = maxScale;
- outputScale.scaled = true;
- this.hasRestrictedScaling = true;
- } else {
- this.hasRestrictedScaling = false;
- }
- }
+ if (self.loadingIconDiv) {
+ div.removeChild(self.loadingIconDiv);
+ delete self.loadingIconDiv;
+ }
- canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
- canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
- canvas.style.width = Math.floor(viewport.width) + 'px';
- canvas.style.height = Math.floor(viewport.height) + 'px';
- // Add the viewport so it's known what it was originally drawn with.
- canvas._viewport = viewport;
+ if (self.zoomLayer) {
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ var zoomLayerCanvas = self.zoomLayer.firstChild;
+ zoomLayerCanvas.width = 0;
+ zoomLayerCanvas.height = 0;
- var textLayerDiv = null;
- var textLayer = null;
- if (!PDFJS.disableTextLayer) {
- textLayerDiv = document.createElement('div');
- textLayerDiv.className = 'textLayer';
- textLayerDiv.style.width = canvas.style.width;
- textLayerDiv.style.height = canvas.style.height;
- if (this.annotationLayer) {
- // annotationLayer needs to stay on top
- div.insertBefore(textLayerDiv, this.annotationLayer);
- } else {
- div.appendChild(textLayerDiv);
- }
+ div.removeChild(self.zoomLayer);
+ self.zoomLayer = null;
+ }
- textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1,
- this.viewport);
- }
- this.textLayer = textLayer;
+ self.error = error;
+ self.stats = pdfPage.stats;
+ if (self.onAfterDraw) {
+ self.onAfterDraw();
+ }
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagerendered', true, true, {
+ pageNumber: self.id,
+ cssTransform: false,
+ });
+ div.dispatchEvent(event);
+ // This custom event is deprecated, and will be removed in the future,
+ // please use the |pagerendered| event instead.
+ var deprecatedEvent = document.createEvent('CustomEvent');
+ deprecatedEvent.initCustomEvent('pagerender', true, true, {
+ pageNumber: pdfPage.pageNumber
+ });
+ div.dispatchEvent(deprecatedEvent);
- // TODO(mack): use data attributes to store these
- ctx._scaleX = outputScale.sx;
- ctx._scaleY = outputScale.sy;
- if (outputScale.scaled) {
- ctx.scale(outputScale.sx, outputScale.sy);
- }
-
- // Rendering area
-
- var self = this;
- function pageViewDrawCallback(error) {
- // The renderTask may have been replaced by a new one, so only remove the
- // reference to the renderTask if it matches the one that is triggering
- // this callback.
- if (renderTask === self.renderTask) {
- self.renderTask = null;
+ if (!error) {
+ resolveRenderPromise(undefined);
+ } else {
+ rejectRenderPromise(error);
+ }
}
- if (error === 'cancelled') {
- return;
+ var renderContinueCallback = null;
+ if (this.renderingQueue) {
+ renderContinueCallback = function renderContinueCallback(cont) {
+ if (!self.renderingQueue.isHighestPriority(self)) {
+ self.renderingState = RenderingStates.PAUSED;
+ self.resume = function resumeCallback() {
+ self.renderingState = RenderingStates.RUNNING;
+ cont();
+ };
+ return;
+ }
+ if (isCanvasHidden) {
+ self.canvas.removeAttribute('hidden');
+ isCanvasHidden = false;
+ }
+ cont();
+ };
}
- self.renderingState = RenderingStates.FINISHED;
+ var transform = !outputScale.scaled ? null :
+ [outputScale.sx, 0, 0, outputScale.sy, 0, 0];
+ var renderContext = {
+ canvasContext: ctx,
+ transform: transform,
+ viewport: this.viewport,
+ // intent: 'default', // === 'display'
+ };
+ var renderTask = this.renderTask = this.pdfPage.render(renderContext);
+ renderTask.onContinue = renderContinueCallback;
- if (self.loadingIconDiv) {
- div.removeChild(self.loadingIconDiv);
- delete self.loadingIconDiv;
- }
+ this.renderTask.promise.then(
+ function pdfPageRenderCallback() {
+ pageViewDrawCallback(null);
+ if (textLayer) {
+ self.pdfPage.getTextContent({ normalizeWhitespace: true }).then(
+ function textContentResolved(textContent) {
+ textLayer.setTextContent(textContent);
+ textLayer.render(TEXT_LAYER_RENDER_DELAY);
+ }
+ );
+ }
+ },
+ function pdfPageRenderError(error) {
+ pageViewDrawCallback(error);
+ }
+ );
- if (self.zoomLayer) {
- div.removeChild(self.zoomLayer);
- self.zoomLayer = null;
- }
-
- self.error = error;
- self.stats = pdfPage.stats;
- self.updateStats();
- if (self.onAfterDraw) {
- self.onAfterDraw();
- }
-
- var event = document.createEvent('CustomEvent');
- event.initCustomEvent('pagerender', true, true, {
- pageNumber: pdfPage.pageNumber
- });
- div.dispatchEvent(event);
-
- callback();
- }
-
- var renderContext = {
- canvasContext: ctx,
- viewport: this.viewport,
- // intent: 'default', // === 'display'
- continueCallback: function pdfViewcContinueCallback(cont) {
- if (!self.renderingQueue.isHighestPriority(self)) {
- self.renderingState = RenderingStates.PAUSED;
- self.resume = function resumeCallback() {
- self.renderingState = RenderingStates.RUNNING;
- cont();
- };
- return;
+ if (this.annotationsLayerFactory) {
+ if (!this.annotationLayer) {
+ this.annotationLayer = this.annotationsLayerFactory.
+ createAnnotationsLayerBuilder(div, this.pdfPage);
}
- cont();
+ this.annotationLayer.render(this.viewport, 'display');
}
- };
- var renderTask = this.renderTask = this.pdfPage.render(renderContext);
+ div.setAttribute('data-loaded', true);
- this.renderTask.promise.then(
- function pdfPageRenderCallback() {
- pageViewDrawCallback(null);
- if (textLayer) {
- self.pdfPage.getTextContent().then(
- function textContentResolved(textContent) {
- textLayer.setTextContent(textContent);
- }
- );
- }
- },
- function pdfPageRenderError(error) {
- pageViewDrawCallback(error);
+ if (self.onBeforeDraw) {
+ self.onBeforeDraw();
}
- );
+ return promise;
+ },
- setupAnnotations(div, pdfPage, this.viewport);
- div.setAttribute('data-loaded', true);
+ beforePrint: function PDFPageView_beforePrint() {
+ var pdfPage = this.pdfPage;
- // Add the page to the cache at the start of drawing. That way it can be
- // evicted from the cache and destroyed even if we pause its rendering.
- cache.push(this);
- };
+ var viewport = pdfPage.getViewport(1);
+ // Use the same hack we use for high dpi displays for printing to get
+ // better output until bug 811002 is fixed in FF.
+ var PRINT_OUTPUT_SCALE = 2;
+ var canvas = document.createElement('canvas');
- this.beforePrint = function pageViewBeforePrint() {
- var pdfPage = this.pdfPage;
+ // The logical size of the canvas.
+ canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
+ canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
- var viewport = pdfPage.getViewport(1);
- // Use the same hack we use for high dpi displays for printing to get better
- // output until bug 811002 is fixed in FF.
- var PRINT_OUTPUT_SCALE = 2;
- var canvas = document.createElement('canvas');
- canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
- canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
- canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';
- canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt';
- var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
- (1 / PRINT_OUTPUT_SCALE) + ')';
- CustomStyle.setProp('transform' , canvas, cssScale);
- CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
+ // The rendered size of the canvas, relative to the size of canvasWrapper.
+ canvas.style.width = (PRINT_OUTPUT_SCALE * 100) + '%';
+ canvas.style.height = (PRINT_OUTPUT_SCALE * 100) + '%';
- var printContainer = document.getElementById('printContainer');
- var canvasWrapper = document.createElement('div');
- canvasWrapper.style.width = viewport.width + 'pt';
- canvasWrapper.style.height = viewport.height + 'pt';
- canvasWrapper.appendChild(canvas);
- printContainer.appendChild(canvasWrapper);
+ var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
+ (1 / PRINT_OUTPUT_SCALE) + ')';
+ CustomStyle.setProp('transform' , canvas, cssScale);
+ CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
- canvas.mozPrintCallback = function(obj) {
- var ctx = obj.context;
+ var printContainer = document.getElementById('printContainer');
+ var canvasWrapper = document.createElement('div');
+ canvasWrapper.style.width = viewport.width + 'pt';
+ canvasWrapper.style.height = viewport.height + 'pt';
+ canvasWrapper.appendChild(canvas);
+ printContainer.appendChild(canvasWrapper);
- ctx.save();
- ctx.fillStyle = 'rgb(255, 255, 255)';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- ctx.restore();
- ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
+ canvas.mozPrintCallback = function(obj) {
+ var ctx = obj.context;
- var renderContext = {
- canvasContext: ctx,
- viewport: viewport,
- intent: 'print'
- };
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.restore();
+ // Used by the mozCurrentTransform polyfill in src/display/canvas.js.
+ ctx._transformMatrix =
+ [PRINT_OUTPUT_SCALE, 0, 0, PRINT_OUTPUT_SCALE, 0, 0];
+ ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
- pdfPage.render(renderContext).promise.then(function() {
- // Tell the printEngine that rendering this canvas/page has finished.
- obj.done();
- }, function(error) {
- console.error(error);
- // Tell the printEngine that rendering this canvas/page has failed.
- // This will make the print proces stop.
- if ('abort' in obj) {
- obj.abort();
- } else {
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: viewport,
+ intent: 'print'
+ };
+
+ pdfPage.render(renderContext).promise.then(function() {
+ // Tell the printEngine that rendering this canvas/page has finished.
obj.done();
- }
- });
- };
+ }, function(error) {
+ console.error(error);
+ // Tell the printEngine that rendering this canvas/page has failed.
+ // This will make the print proces stop.
+ if ('abort' in obj) {
+ obj.abort();
+ } else {
+ obj.done();
+ }
+ });
+ };
+ },
};
- this.updateStats = function pageViewUpdateStats() {
- if (!this.stats) {
- return;
- }
+ return PDFPageView;
+})();
- if (PDFJS.pdfBug && Stats.enabled) {
- var stats = this.stats;
- Stats.add(this.id, stats);
- }
- };
-};
-
-var FIND_SCROLL_OFFSET_TOP = -50;
-var FIND_SCROLL_OFFSET_LEFT = -400;
-var MAX_TEXT_DIVS_TO_RENDER = 100000;
-var RENDER_DELAY = 200; // ms
-
-var NonWhitespaceRegexp = /\S/;
-
-function isAllWhitespace(str) {
- return !NonWhitespaceRegexp.test(str);
-}
-
/**
* @typedef {Object} TextLayerBuilderOptions
* @property {HTMLDivElement} textLayerDiv - The text layer container.
* @property {number} pageIndex - The page index.
* @property {PageViewport} viewport - The viewport of the text layer.
- * @property {ILastScrollSource} lastScrollSource - The object that records when
- * last time scroll happened.
- * @property {boolean} isViewerInPresentationMode
* @property {PDFFindController} findController
*/
/**
* TextLayerBuilder provides text-selection functionality for the PDF.
@@ -3552,166 +4121,77 @@
* @class
*/
var TextLayerBuilder = (function TextLayerBuilderClosure() {
function TextLayerBuilder(options) {
this.textLayerDiv = options.textLayerDiv;
- this.layoutDone = false;
+ this.renderingDone = false;
this.divContentDone = false;
this.pageIdx = options.pageIndex;
+ this.pageNumber = this.pageIdx + 1;
this.matches = [];
- this.lastScrollSource = options.lastScrollSource || null;
this.viewport = options.viewport;
- this.isViewerInPresentationMode = options.isViewerInPresentationMode;
this.textDivs = [];
this.findController = options.findController || null;
+ this.textLayerRenderTask = null;
+ this._bindMouse();
}
TextLayerBuilder.prototype = {
- renderLayer: function TextLayerBuilder_renderLayer() {
- var textLayerFrag = document.createDocumentFragment();
- var textDivs = this.textDivs;
- var textDivsLength = textDivs.length;
- var canvas = document.createElement('canvas');
- var ctx = canvas.getContext('2d');
-
- // No point in rendering many divs as it would make the browser
- // unusable even after the divs are rendered.
- if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
- return;
- }
-
- var lastFontSize;
- var lastFontFamily;
- for (var i = 0; i < textDivsLength; i++) {
- var textDiv = textDivs[i];
- if (textDiv.dataset.isWhitespace !== undefined) {
- continue;
- }
-
- var fontSize = textDiv.style.fontSize;
- var fontFamily = textDiv.style.fontFamily;
-
- // Only build font string and set to context if different from last.
- if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
- ctx.font = fontSize + ' ' + fontFamily;
- lastFontSize = fontSize;
- lastFontFamily = fontFamily;
- }
-
- var width = ctx.measureText(textDiv.textContent).width;
- if (width > 0) {
- textLayerFrag.appendChild(textDiv);
- var transform;
- if (textDiv.dataset.canvasWidth !== undefined) {
- // Dataset values come of type string.
- var textScale = textDiv.dataset.canvasWidth / width;
- transform = 'scaleX(' + textScale + ')';
- } else {
- transform = '';
- }
- var rotation = textDiv.dataset.angle;
- if (rotation) {
- transform = 'rotate(' + rotation + 'deg) ' + transform;
- }
- if (transform) {
- CustomStyle.setProp('transform' , textDiv, transform);
- }
- }
- }
-
- this.textLayerDiv.appendChild(textLayerFrag);
+ _finishRendering: function TextLayerBuilder_finishRendering() {
this.renderingDone = true;
- this.updateMatches();
- },
- setupRenderLayoutTimer:
- function TextLayerBuilder_setupRenderLayoutTimer() {
- // Schedule renderLayout() if the user has been scrolling,
- // otherwise run it right away.
- var self = this;
- var lastScroll = (this.lastScrollSource === null ?
- 0 : this.lastScrollSource.lastScroll);
+ var endOfContent = document.createElement('div');
+ endOfContent.className = 'endOfContent';
+ this.textLayerDiv.appendChild(endOfContent);
- if (Date.now() - lastScroll > RENDER_DELAY) { // Render right away
- this.renderLayer();
- } else { // Schedule
- if (this.renderTimer) {
- clearTimeout(this.renderTimer);
- }
- this.renderTimer = setTimeout(function() {
- self.setupRenderLayoutTimer();
- }, RENDER_DELAY);
- }
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('textlayerrendered', true, true, {
+ pageNumber: this.pageNumber
+ });
+ this.textLayerDiv.dispatchEvent(event);
},
- appendText: function TextLayerBuilder_appendText(geom, styles) {
- var style = styles[geom.fontName];
- var textDiv = document.createElement('div');
- this.textDivs.push(textDiv);
- if (isAllWhitespace(geom.str)) {
- textDiv.dataset.isWhitespace = true;
+ /**
+ * Renders the text layer.
+ * @param {number} timeout (optional) if specified, the rendering waits
+ * for specified amount of ms.
+ */
+ render: function TextLayerBuilder_render(timeout) {
+ if (!this.divContentDone || this.renderingDone) {
return;
}
- var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
- var angle = Math.atan2(tx[1], tx[0]);
- if (style.vertical) {
- angle += Math.PI / 2;
- }
- var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
- var fontAscent = fontHeight;
- if (style.ascent) {
- fontAscent = style.ascent * fontAscent;
- } else if (style.descent) {
- fontAscent = (1 + style.descent) * fontAscent;
- }
- var left;
- var top;
- if (angle === 0) {
- left = tx[4];
- top = tx[5] - fontAscent;
- } else {
- left = tx[4] + (fontAscent * Math.sin(angle));
- top = tx[5] - (fontAscent * Math.cos(angle));
+ if (this.textLayerRenderTask) {
+ this.textLayerRenderTask.cancel();
+ this.textLayerRenderTask = null;
}
- textDiv.style.left = left + 'px';
- textDiv.style.top = top + 'px';
- textDiv.style.fontSize = fontHeight + 'px';
- textDiv.style.fontFamily = style.fontFamily;
- textDiv.textContent = geom.str;
- // |fontName| is only used by the Font Inspector. This test will succeed
- // when e.g. the Font Inspector is off but the Stepper is on, but it's
- // not worth the effort to do a more accurate test.
- if (PDFJS.pdfBug) {
- textDiv.dataset.fontName = geom.fontName;
- }
- // Storing into dataset will convert number into string.
- if (angle !== 0) {
- textDiv.dataset.angle = angle * (180 / Math.PI);
- }
- // We don't bother scaling single-char text divs, because it has very
- // little effect on text highlighting. This makes scrolling on docs with
- // lots of such divs a lot faster.
- if (textDiv.textContent.length > 1) {
- if (style.vertical) {
- textDiv.dataset.canvasWidth = geom.height * this.viewport.scale;
- } else {
- textDiv.dataset.canvasWidth = geom.width * this.viewport.scale;
- }
- }
+ this.textDivs = [];
+ var textLayerFrag = document.createDocumentFragment();
+ this.textLayerRenderTask = PDFJS.renderTextLayer({
+ textContent: this.textContent,
+ container: textLayerFrag,
+ viewport: this.viewport,
+ textDivs: this.textDivs,
+ timeout: timeout
+ });
+ this.textLayerRenderTask.promise.then(function () {
+ this.textLayerDiv.appendChild(textLayerFrag);
+ this._finishRendering();
+ this.updateMatches();
+ }.bind(this), function (reason) {
+ // canceled or failed to render text layer -- skipping errors
+ });
},
setTextContent: function TextLayerBuilder_setTextContent(textContent) {
- this.textContent = textContent;
-
- var textItems = textContent.items;
- for (var i = 0, len = textItems.length; i < len; i++) {
- this.appendText(textItems[i], textContent.styles);
+ if (this.textLayerRenderTask) {
+ this.textLayerRenderTask.cancel();
+ this.textLayerRenderTask = null;
}
+ this.textContent = textContent;
this.divContentDone = true;
- this.setupRenderLayoutTimer();
},
convertMatches: function TextLayerBuilder_convertMatches(matches) {
var i = 0;
var iIndex = 0;
@@ -3769,12 +4249,13 @@
}
var bidiTexts = this.textContent.items;
var textDivs = this.textDivs;
var prevEnd = null;
+ var pageIdx = this.pageIdx;
var isSelectedPage = (this.findController === null ?
- false : (this.pageIdx === this.findController.selected.pageIdx));
+ false : (pageIdx === this.findController.selected.pageIdx));
var selectedMatchIdx = (this.findController === null ?
-1 : this.findController.selected.matchIdx);
var highlightAll = (this.findController === null ?
false : this.findController.state.highlightAll);
var infinity = {
@@ -3816,14 +4297,13 @@
var begin = match.begin;
var end = match.end;
var isSelected = (isSelectedPage && i === selectedMatchIdx);
var highlightSuffix = (isSelected ? ' selected' : '');
- if (isSelected && !this.isViewerInPresentationMode) {
- scrollIntoView(textDivs[begin.divIdx],
- { top: FIND_SCROLL_OFFSET_TOP,
- left: FIND_SCROLL_OFFSET_LEFT });
+ if (this.findController) {
+ this.findController.updateMatchPosition(pageIdx, i, textDivs,
+ begin.divIdx, end.divIdx);
}
// Match inside new div.
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
// If there was a previous div, then add the text at the end.
@@ -3886,40 +4366,226 @@
// Convert the matches on the page controller into the match format
// used for the textLayer.
this.matches = this.convertMatches(this.findController === null ?
[] : (this.findController.pageMatches[this.pageIdx] || []));
this.renderMatches(this.matches);
- }
+ },
+
+ /**
+ * Fixes text selection: adds additional div where mouse was clicked.
+ * This reduces flickering of the content if mouse slowly dragged down/up.
+ * @private
+ */
+ _bindMouse: function TextLayerBuilder_bindMouse() {
+ var div = this.textLayerDiv;
+ div.addEventListener('mousedown', function (e) {
+ var end = div.querySelector('.endOfContent');
+ if (!end) {
+ return;
+ }
+ // On non-Firefox browsers, the selection will feel better if the height
+ // of the endOfContent div will be adjusted to start at mouse click
+ // location -- this will avoid flickering when selections moves up.
+ // However it does not work when selection started on empty space.
+ var adjustTop = e.target !== div;
+ adjustTop = adjustTop && window.getComputedStyle(end).
+ getPropertyValue('-moz-user-select') !== 'none';
+ if (adjustTop) {
+ var divBounds = div.getBoundingClientRect();
+ var r = Math.max(0, (e.pageY - divBounds.top) / divBounds.height);
+ end.style.top = (r * 100).toFixed(2) + '%';
+ }
+ end.classList.add('active');
+ });
+ div.addEventListener('mouseup', function (e) {
+ var end = div.querySelector('.endOfContent');
+ if (!end) {
+ return;
+ }
+ end.style.top = '';
+ end.classList.remove('active');
+ });
+ },
};
return TextLayerBuilder;
})();
+/**
+ * @constructor
+ * @implements IPDFTextLayerFactory
+ */
+function DefaultTextLayerFactory() {}
+DefaultTextLayerFactory.prototype = {
+ /**
+ * @param {HTMLDivElement} textLayerDiv
+ * @param {number} pageIndex
+ * @param {PageViewport} viewport
+ * @returns {TextLayerBuilder}
+ */
+ createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
+ return new TextLayerBuilder({
+ textLayerDiv: textLayerDiv,
+ pageIndex: pageIndex,
+ viewport: viewport
+ });
+ }
+};
+
/**
+ * @typedef {Object} AnnotationsLayerBuilderOptions
+ * @property {HTMLDivElement} pageDiv
+ * @property {PDFPage} pdfPage
+ * @property {IPDFLinkService} linkService
+ */
+
+/**
+ * @class
+ */
+var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() {
+ /**
+ * @param {AnnotationsLayerBuilderOptions} options
+ * @constructs AnnotationsLayerBuilder
+ */
+ function AnnotationsLayerBuilder(options) {
+ this.pageDiv = options.pageDiv;
+ this.pdfPage = options.pdfPage;
+ this.linkService = options.linkService;
+
+ this.div = null;
+ }
+
+ AnnotationsLayerBuilder.prototype =
+ /** @lends AnnotationsLayerBuilder.prototype */ {
+
+ /**
+ * @param {PageViewport} viewport
+ * @param {string} intent (default value is 'display')
+ */
+ render: function AnnotationsLayerBuilder_render(viewport, intent) {
+ var self = this;
+ var parameters = {
+ intent: (intent === undefined ? 'display' : intent),
+ };
+
+ this.pdfPage.getAnnotations(parameters).then(function (annotations) {
+ viewport = viewport.clone({ dontFlip: true });
+
+ if (self.div) {
+ // If an annotationLayer already exists, refresh its children's
+ // transformation matrices.
+ PDFJS.AnnotationLayer.update(viewport, self.div, annotations);
+ } else {
+ // Create an annotation layer div and render the annotations
+ // if there is at least one annotation.
+ if (annotations.length === 0) {
+ return;
+ }
+
+ self.div = document.createElement('div');
+ self.div.className = 'annotationLayer';
+ self.pageDiv.appendChild(self.div);
+
+ PDFJS.AnnotationLayer.render(viewport, self.div, annotations,
+ self.pdfPage, self.linkService);
+ if (typeof mozL10n !== 'undefined') {
+ mozL10n.translate(self.div);
+ }
+ }
+ });
+ },
+
+ hide: function AnnotationsLayerBuilder_hide() {
+ if (!this.div) {
+ return;
+ }
+ this.div.setAttribute('hidden', 'true');
+ }
+ };
+
+ return AnnotationsLayerBuilder;
+})();
+
+/**
+ * @constructor
+ * @implements IPDFAnnotationsLayerFactory
+ */
+function DefaultAnnotationsLayerFactory() {}
+DefaultAnnotationsLayerFactory.prototype = {
+ /**
+ * @param {HTMLDivElement} pageDiv
+ * @param {PDFPage} pdfPage
+ * @returns {AnnotationsLayerBuilder}
+ */
+ createAnnotationsLayerBuilder: function (pageDiv, pdfPage) {
+ return new AnnotationsLayerBuilder({
+ pageDiv: pageDiv,
+ pdfPage: pdfPage,
+ linkService: new SimpleLinkService(),
+ });
+ }
+};
+
+
+/**
* @typedef {Object} PDFViewerOptions
* @property {HTMLDivElement} container - The container for the viewer element.
* @property {HTMLDivElement} viewer - (optional) The viewer element.
* @property {IPDFLinkService} linkService - The navigation/linking service.
* @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
* queue object.
+ * @property {boolean} removePageBorders - (optional) Removes the border shadow
+ * around the pages. The default is false.
*/
/**
* Simple viewer control to display PDF content/pages.
* @class
- * @implements {ILastScrollSource}
* @implements {IRenderableView}
*/
var PDFViewer = (function pdfViewer() {
+ function PDFPageViewBuffer(size) {
+ var data = [];
+ this.push = function cachePush(view) {
+ var i = data.indexOf(view);
+ if (i >= 0) {
+ data.splice(i, 1);
+ }
+ data.push(view);
+ if (data.length > size) {
+ data.shift().destroy();
+ }
+ };
+ this.resize = function (newSize) {
+ size = newSize;
+ while (data.length > size) {
+ data.shift().destroy();
+ }
+ };
+ }
+
+ function isSameScale(oldScale, newScale) {
+ if (newScale === oldScale) {
+ return true;
+ }
+ if (Math.abs(newScale - oldScale) < 1e-15) {
+ // Prevent unnecessary re-rendering of all pages when the scale
+ // changes only because of limited numerical precision.
+ return true;
+ }
+ return false;
+ }
+
/**
* @constructs PDFViewer
* @param {PDFViewerOptions} options
*/
function PDFViewer(options) {
this.container = options.container;
this.viewer = options.viewer || options.container.firstElementChild;
- this.linkService = options.linkService || new SimpleLinkService(this);
+ this.linkService = options.linkService || new SimpleLinkService();
+ this.removePageBorders = options.removePageBorders || false;
this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) {
// Custom rendering queue is not specified, using default one
this.renderingQueue = new PDFRenderingQueue();
@@ -3927,23 +4593,26 @@
} else {
this.renderingQueue = options.renderingQueue;
}
this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
- this.lastScroll = 0;
this.updateInProgress = false;
this.presentationModeState = PresentationModeState.UNKNOWN;
this._resetView();
+
+ if (this.removePageBorders) {
+ this.viewer.classList.add('removePageBorders');
+ }
}
PDFViewer.prototype = /** @lends PDFViewer.prototype */{
get pagesCount() {
- return this.pages.length;
+ return this._pages.length;
},
getPageView: function (index) {
- return this.pages[index];
+ return this._pages[index];
},
get currentPageNumber() {
return this._currentPageNumber;
},
@@ -3963,22 +4632,28 @@
event.previousPageNumber = val;
this.container.dispatchEvent(event);
return;
}
- this.pages[val - 1].updateStats();
event.previousPageNumber = this._currentPageNumber;
this._currentPageNumber = val;
event.pageNumber = val;
this.container.dispatchEvent(event);
+
+ // Check if the caller is `PDFViewer_update`, to avoid breaking scrolling.
+ if (this.updateInProgress) {
+ return;
+ }
+ this.scrollPageIntoView(val);
},
/**
* @returns {number}
*/
get currentScale() {
- return this._currentScale;
+ return this._currentScale !== UNKNOWN_SCALE ? this._currentScale :
+ DEFAULT_SCALE;
},
/**
* @param {number} val - Scale of the pages in percents.
*/
@@ -3986,11 +4661,11 @@
if (isNaN(val)) {
throw new Error('Invalid numeric scale');
}
if (!this.pdfDocument) {
this._currentScale = val;
- this._currentScaleValue = val.toString();
+ this._currentScaleValue = val !== UNKNOWN_SCALE ? val.toString() : null;
return;
}
this._setScale(val, false);
},
@@ -4024,16 +4699,20 @@
* @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
*/
set pagesRotation(rotation) {
this._pagesRotation = rotation;
- for (var i = 0, l = this.pages.length; i < l; i++) {
- var page = this.pages[i];
- page.update(page.scale, rotation);
+ for (var i = 0, l = this._pages.length; i < l; i++) {
+ var pageView = this._pages[i];
+ pageView.update(pageView.scale, rotation);
}
this._setScale(this._currentScaleValue, true);
+
+ if (this.defaultRenderingQueue) {
+ this.update();
+ }
},
/**
* @param pdfDocument {PDFDocument}
*/
@@ -4046,11 +4725,10 @@
if (!pdfDocument) {
return;
}
var pagesCount = pdfDocument.numPages;
- var pagesRefMap = this.pagesRefMap = {};
var self = this;
var resolvePagesPromise;
var pagesPromise = new Promise(function (resolve) {
resolvePagesPromise = resolve;
@@ -4069,57 +4747,67 @@
var onePageRendered = new Promise(function (resolve) {
resolveOnePageRendered = resolve;
});
this.onePageRendered = onePageRendered;
- var bindOnAfterDraw = function (pageView) {
+ var bindOnAfterAndBeforeDraw = function (pageView) {
+ pageView.onBeforeDraw = function pdfViewLoadOnBeforeDraw() {
+ // Add the page to the buffer at the start of drawing. That way it can
+ // be evicted from the buffer and destroyed even if we pause its
+ // rendering.
+ self._buffer.push(this);
+ };
// when page is painted, using the image as thumbnail base
pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
if (!isOnePageRenderedResolved) {
isOnePageRenderedResolved = true;
resolveOnePageRendered();
}
- var event = document.createEvent('CustomEvent');
- event.initCustomEvent('pagerendered', true, true, {
- pageNumber: pageView.id
- });
- self.container.dispatchEvent(event);
};
};
var firstPagePromise = pdfDocument.getPage(1);
this.firstPagePromise = firstPagePromise;
// Fetch a single page so we can get a viewport that will be the default
// viewport for all pages
return firstPagePromise.then(function(pdfPage) {
- var scale = this._currentScale || 1.0;
+ var scale = this.currentScale;
var viewport = pdfPage.getViewport(scale * CSS_UNITS);
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
- var pageSource = new PDFPageSource(pdfDocument, pageNum);
- var pageView = new PageView(this.viewer, pageNum, scale,
- viewport.clone(), this.linkService,
- this.renderingQueue, this.cache,
- pageSource, this);
- bindOnAfterDraw(pageView);
- this.pages.push(pageView);
+ var textLayerFactory = null;
+ if (!PDFJS.disableTextLayer) {
+ textLayerFactory = this;
+ }
+ var pageView = new PDFPageView({
+ container: this.viewer,
+ id: pageNum,
+ scale: scale,
+ defaultViewport: viewport.clone(),
+ renderingQueue: this.renderingQueue,
+ textLayerFactory: textLayerFactory,
+ annotationsLayerFactory: this
+ });
+ bindOnAfterAndBeforeDraw(pageView);
+ this._pages.push(pageView);
}
+ var linkService = this.linkService;
+
// Fetch all the pages since the viewport is needed before printing
// starts to create the correct size canvas. Wait until one page is
// rendered so we don't tie up too many resources early on.
onePageRendered.then(function () {
if (!PDFJS.disableAutoFetch) {
var getPagesLeft = pagesCount;
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
- var pageView = self.pages[pageNum - 1];
+ var pageView = self._pages[pageNum - 1];
if (!pageView.pdfPage) {
pageView.setPdfPage(pdfPage);
}
- var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
- pagesRefMap[refStr] = pageNum;
+ linkService.cachePageRef(pageNum, pdfPage.ref);
getPagesLeft--;
if (!getPagesLeft) {
resolvePagesPromise();
}
}.bind(null, pageNum));
@@ -4135,88 +4823,102 @@
self.container.dispatchEvent(event);
if (this.defaultRenderingQueue) {
this.update();
}
+
+ if (this.findController) {
+ this.findController.resolveFirstPage();
+ }
}.bind(this));
},
_resetView: function () {
- this.cache = new Cache(DEFAULT_CACHE_SIZE);
- this.pages = [];
+ this._pages = [];
this._currentPageNumber = 1;
this._currentScale = UNKNOWN_SCALE;
this._currentScaleValue = null;
- this.location = null;
+ this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
+ this._location = null;
this._pagesRotation = 0;
+ this._pagesRequests = [];
var container = this.viewer;
while (container.hasChildNodes()) {
container.removeChild(container.lastChild);
}
},
- _scrollUpdate: function () {
- this.lastScroll = Date.now();
-
+ _scrollUpdate: function PDFViewer_scrollUpdate() {
if (this.pagesCount === 0) {
return;
}
this.update();
+ for (var i = 0, ii = this._pages.length; i < ii; i++) {
+ this._pages[i].updatePosition();
+ }
},
+ _setScaleDispatchEvent: function pdfViewer_setScaleDispatchEvent(
+ newScale, newValue, preset) {
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('scalechange', true, true, window, 0);
+ event.scale = newScale;
+ if (preset) {
+ event.presetValue = newValue;
+ }
+ this.container.dispatchEvent(event);
+ },
+
_setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
newScale, newValue, noScroll, preset) {
this._currentScaleValue = newValue;
- if (newScale === this._currentScale) {
+
+ if (isSameScale(this._currentScale, newScale)) {
+ if (preset) {
+ this._setScaleDispatchEvent(newScale, newValue, true);
+ }
return;
}
- for (var i = 0, ii = this.pages.length; i < ii; i++) {
- this.pages[i].update(newScale);
+
+ for (var i = 0, ii = this._pages.length; i < ii; i++) {
+ this._pages[i].update(newScale);
}
this._currentScale = newScale;
if (!noScroll) {
var page = this._currentPageNumber, dest;
- var inPresentationMode =
- this.presentationModeState === PresentationModeState.CHANGING ||
- this.presentationModeState === PresentationModeState.FULLSCREEN;
- if (this.location && !inPresentationMode &&
- !IGNORE_CURRENT_POSITION_ON_ZOOM) {
- page = this.location.pageNumber;
- dest = [null, { name: 'XYZ' }, this.location.left,
- this.location.top, null];
+ if (this._location && !IGNORE_CURRENT_POSITION_ON_ZOOM &&
+ !(this.isInPresentationMode || this.isChangingPresentationMode)) {
+ page = this._location.pageNumber;
+ dest = [null, { name: 'XYZ' }, this._location.left,
+ this._location.top, null];
}
this.scrollPageIntoView(page, dest);
}
- var event = document.createEvent('UIEvents');
- event.initUIEvent('scalechange', true, true, window, 0);
- event.scale = newScale;
- if (preset) {
- event.presetValue = newValue;
+ this._setScaleDispatchEvent(newScale, newValue, preset);
+
+ if (this.defaultRenderingQueue) {
+ this.update();
}
- this.container.dispatchEvent(event);
},
_setScale: function pdfViewer_setScale(value, noScroll) {
- if (value === 'custom') {
- return;
- }
var scale = parseFloat(value);
if (scale > 0) {
this._setScaleUpdatePages(scale, value, noScroll, false);
} else {
- var currentPage = this.pages[this._currentPageNumber - 1];
+ var currentPage = this._pages[this._currentPageNumber - 1];
if (!currentPage) {
return;
}
- var inPresentationMode =
- this.presentationModeState === PresentationModeState.FULLSCREEN;
- var hPadding = inPresentationMode ? 0 : SCROLLBAR_PADDING;
- var vPadding = inPresentationMode ? 0 : VERTICAL_PADDING;
+ var hPadding = (this.isInPresentationMode || this.removePageBorders) ?
+ 0 : SCROLLBAR_PADDING;
+ var vPadding = (this.isInPresentationMode || this.removePageBorders) ?
+ 0 : VERTICAL_PADDING;
var pageWidthScale = (this.container.clientWidth - hPadding) /
currentPage.width * currentPage.scale;
var pageHeightScale = (this.container.clientHeight - vPadding) /
currentPage.height * currentPage.scale;
switch (value) {
@@ -4255,26 +4957,28 @@
* @param {Array} dest - (optional) original PDF destination array:
* <page-ref> </XYZ|FitXXX> <args..>
*/
scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
dest) {
- var pageView = this.pages[pageNumber - 1];
- var pageViewDiv = pageView.el;
+ if (!this.pdfDocument) {
+ return;
+ }
- if (this.presentationModeState ===
- PresentationModeState.FULLSCREEN) {
- if (this.linkService.page !== pageView.id) {
+ var pageView = this._pages[pageNumber - 1];
+
+ if (this.isInPresentationMode) {
+ if (this._currentPageNumber !== pageView.id) {
// Avoid breaking getVisiblePages in presentation mode.
- this.linkService.page = pageView.id;
+ this.currentPageNumber = pageView.id;
return;
}
dest = null;
// Fixes the case when PDF has different page sizes.
- this._setScale(this.currentScaleValue, true);
+ this._setScale(this._currentScaleValue, true);
}
if (!dest) {
- scrollIntoView(pageViewDiv);
+ scrollIntoView(pageView.div);
return;
}
var x = 0, y = 0;
var width = 0, height = 0, widthScale, heightScale;
@@ -4302,10 +5006,16 @@
break;
case 'FitH':
case 'FitBH':
y = dest[2];
scale = 'page-width';
+ // According to the PDF spec, section 12.3.2.2, a `null` value in the
+ // parameter should maintain the position relative to the new page.
+ if (y === null && this._location) {
+ x = this._location.left;
+ y = this._location.top;
+ }
break;
case 'FitV':
case 'FitBV':
x = dest[2];
width = pageWidth;
@@ -4315,40 +5025,42 @@
case 'FitR':
x = dest[2];
y = dest[3];
width = dest[4] - x;
height = dest[5] - y;
- var viewerContainer = this.container;
- widthScale = (viewerContainer.clientWidth - SCROLLBAR_PADDING) /
+ var hPadding = this.removePageBorders ? 0 : SCROLLBAR_PADDING;
+ var vPadding = this.removePageBorders ? 0 : VERTICAL_PADDING;
+
+ widthScale = (this.container.clientWidth - hPadding) /
width / CSS_UNITS;
- heightScale = (viewerContainer.clientHeight - SCROLLBAR_PADDING) /
+ heightScale = (this.container.clientHeight - vPadding) /
height / CSS_UNITS;
scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
break;
default:
return;
}
- if (scale && scale !== this.currentScale) {
+ if (scale && scale !== this._currentScale) {
this.currentScaleValue = scale;
- } else if (this.currentScale === UNKNOWN_SCALE) {
- this.currentScaleValue = DEFAULT_SCALE;
+ } else if (this._currentScale === UNKNOWN_SCALE) {
+ this.currentScaleValue = DEFAULT_SCALE_VALUE;
}
if (scale === 'page-fit' && !dest[4]) {
- scrollIntoView(pageViewDiv);
+ scrollIntoView(pageView.div);
return;
}
var boundingRect = [
pageView.viewport.convertToViewportPoint(x, y),
pageView.viewport.convertToViewportPoint(x + width, y + height)
];
var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
- scrollIntoView(pageViewDiv, { left: left, top: top });
+ scrollIntoView(pageView.div, { left: left, top: top });
},
_updateLocation: function (firstPage) {
var currentScale = this._currentScale;
var currentScaleValue = this._currentScaleValue;
@@ -4357,44 +5069,44 @@
Math.round(currentScale * 10000) / 100 : currentScaleValue;
var pageNumber = firstPage.id;
var pdfOpenParams = '#page=' + pageNumber;
pdfOpenParams += '&zoom=' + normalizedScaleValue;
- var currentPageView = this.pages[pageNumber - 1];
+ var currentPageView = this._pages[pageNumber - 1];
var container = this.container;
var topLeft = currentPageView.getPagePoint(
(container.scrollLeft - firstPage.x),
(container.scrollTop - firstPage.y));
var intLeft = Math.round(topLeft[0]);
var intTop = Math.round(topLeft[1]);
pdfOpenParams += ',' + intLeft + ',' + intTop;
- this.location = {
+ this._location = {
pageNumber: pageNumber,
scale: normalizedScaleValue,
top: intTop,
left: intLeft,
pdfOpenParams: pdfOpenParams
};
},
- update: function () {
+ update: function PDFViewer_update() {
var visible = this._getVisiblePages();
var visiblePages = visible.views;
if (visiblePages.length === 0) {
return;
}
this.updateInProgress = true;
var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
2 * visiblePages.length + 1);
- this.cache.resize(suggestedCacheSize);
+ this._buffer.resize(suggestedCacheSize);
this.renderingQueue.renderHighestPriority(visible);
- var currentId = this.currentPageNumber;
+ var currentId = this._currentPageNumber;
var firstPage = visible.first;
for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
i < ii; ++i) {
var page = visiblePages[i];
@@ -4410,20 +5122,21 @@
if (!stillFullyVisible) {
currentId = visiblePages[0].id;
}
- if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
+ if (!this.isInPresentationMode) {
this.currentPageNumber = currentId;
}
this._updateLocation(firstPage);
this.updateInProgress = false;
var event = document.createEvent('UIEvents');
event.initUIEvent('updateviewarea', true, true, window, 0);
+ event.location = this._location;
this.container.dispatchEvent(event);
},
containsElement: function (element) {
return this.container.contains(element);
@@ -4431,103 +5144,138 @@
focus: function () {
this.container.focus();
},
- blur: function () {
- this.container.blur();
+ get isInPresentationMode() {
+ return this.presentationModeState === PresentationModeState.FULLSCREEN;
},
+ get isChangingPresentationMode() {
+ return this.presentationModeState === PresentationModeState.CHANGING;
+ },
+
get isHorizontalScrollbarEnabled() {
- return (this.presentationModeState === PresentationModeState.FULLSCREEN ?
+ return (this.isInPresentationMode ?
false : (this.container.scrollWidth > this.container.clientWidth));
},
_getVisiblePages: function () {
- if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
- return getVisibleElements(this.container, this.pages, true);
+ if (!this.isInPresentationMode) {
+ return getVisibleElements(this.container, this._pages, true);
} else {
// The algorithm in getVisibleElements doesn't work in all browsers and
// configurations when presentation mode is active.
var visible = [];
- var currentPage = this.pages[this._currentPageNumber - 1];
+ var currentPage = this._pages[this._currentPageNumber - 1];
visible.push({ id: currentPage.id, view: currentPage });
return { first: currentPage, last: currentPage, views: visible };
}
},
cleanup: function () {
- for (var i = 0, ii = this.pages.length; i < ii; i++) {
- if (this.pages[i] &&
- this.pages[i].renderingState !== RenderingStates.FINISHED) {
- this.pages[i].reset();
+ for (var i = 0, ii = this._pages.length; i < ii; i++) {
+ if (this._pages[i] &&
+ this._pages[i].renderingState !== RenderingStates.FINISHED) {
+ this._pages[i].reset();
}
}
},
+ /**
+ * @param {PDFPageView} pageView
+ * @returns {PDFPage}
+ * @private
+ */
+ _ensurePdfPageLoaded: function (pageView) {
+ if (pageView.pdfPage) {
+ return Promise.resolve(pageView.pdfPage);
+ }
+ var pageNumber = pageView.id;
+ if (this._pagesRequests[pageNumber]) {
+ return this._pagesRequests[pageNumber];
+ }
+ var promise = this.pdfDocument.getPage(pageNumber).then(
+ function (pdfPage) {
+ pageView.setPdfPage(pdfPage);
+ this._pagesRequests[pageNumber] = null;
+ return pdfPage;
+ }.bind(this));
+ this._pagesRequests[pageNumber] = promise;
+ return promise;
+ },
+
forceRendering: function (currentlyVisiblePages) {
var visiblePages = currentlyVisiblePages || this._getVisiblePages();
var pageView = this.renderingQueue.getHighestPriority(visiblePages,
- this.pages,
+ this._pages,
this.scroll.down);
if (pageView) {
- this.renderingQueue.renderView(pageView);
+ this._ensurePdfPageLoaded(pageView).then(function () {
+ this.renderingQueue.renderView(pageView);
+ }.bind(this));
return true;
}
return false;
},
getPageTextContent: function (pageIndex) {
return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
- return page.getTextContent();
+ return page.getTextContent({ normalizeWhitespace: true });
});
},
/**
- * @param textLayerDiv {HTMLDivElement}
- * @param pageIndex {number}
- * @param viewport {PageViewport}
+ * @param {HTMLDivElement} textLayerDiv
+ * @param {number} pageIndex
+ * @param {PageViewport} viewport
* @returns {TextLayerBuilder}
*/
createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
- var isViewerInPresentationMode =
- this.presentationModeState === PresentationModeState.FULLSCREEN;
return new TextLayerBuilder({
textLayerDiv: textLayerDiv,
pageIndex: pageIndex,
viewport: viewport,
- lastScrollSource: this,
- isViewerInPresentationMode: isViewerInPresentationMode,
- findController: this.findController
+ findController: this.isInPresentationMode ? null : this.findController
});
},
+ /**
+ * @param {HTMLDivElement} pageDiv
+ * @param {PDFPage} pdfPage
+ * @returns {AnnotationsLayerBuilder}
+ */
+ createAnnotationsLayerBuilder: function (pageDiv, pdfPage) {
+ return new AnnotationsLayerBuilder({
+ pageDiv: pageDiv,
+ pdfPage: pdfPage,
+ linkService: this.linkService
+ });
+ },
+
setFindController: function (findController) {
this.findController = findController;
},
};
return PDFViewer;
})();
var SimpleLinkService = (function SimpleLinkServiceClosure() {
- function SimpleLinkService(pdfViewer) {
- this.pdfViewer = pdfViewer;
- }
+ function SimpleLinkService() {}
+
SimpleLinkService.prototype = {
/**
* @returns {number}
*/
get page() {
- return this.pdfViewer.currentPageNumber;
+ return 0;
},
/**
* @param {number} value
*/
- set page(value) {
- this.pdfViewer.currentPageNumber = value;
- },
+ set page(value) {},
/**
* @param dest - The PDF destination object.
*/
navigateTo: function (dest) {},
/**
@@ -4550,93 +5298,849 @@
setHash: function (hash) {},
/**
* @param {string} action
*/
executeNamedAction: function (action) {},
+ /**
+ * @param {number} pageNum - page number.
+ * @param {Object} pageRef - reference to the page.
+ */
+ cachePageRef: function (pageNum, pageRef) {}
};
return SimpleLinkService;
})();
+
+var THUMBNAIL_SCROLL_MARGIN = -19;
+
+
+var THUMBNAIL_WIDTH = 98; // px
+var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px
+
/**
- * PDFPage object source.
+ * @typedef {Object} PDFThumbnailViewOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {number} id - The thumbnail's unique ID (normally its number).
+ * @property {PageViewport} defaultViewport - The page viewport.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ */
+
+/**
* @class
+ * @implements {IRenderableView}
*/
-var PDFPageSource = (function PDFPageSourceClosure() {
+var PDFThumbnailView = (function PDFThumbnailViewClosure() {
+ function getTempCanvas(width, height) {
+ var tempCanvas = PDFThumbnailView.tempImageCache;
+ if (!tempCanvas) {
+ tempCanvas = document.createElement('canvas');
+ PDFThumbnailView.tempImageCache = tempCanvas;
+ }
+ tempCanvas.width = width;
+ tempCanvas.height = height;
+
+ // Since this is a temporary canvas, we need to fill the canvas with a white
+ // background ourselves. |_getPageDrawContext| uses CSS rules for this.
+ tempCanvas.mozOpaque = true;
+ var ctx = tempCanvas.getContext('2d', {alpha: false});
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, width, height);
+ ctx.restore();
+ return tempCanvas;
+ }
+
/**
- * @constructs
- * @param {PDFDocument} pdfDocument
- * @param {number} pageNumber
- * @constructor
+ * @constructs PDFThumbnailView
+ * @param {PDFThumbnailViewOptions} options
*/
- function PDFPageSource(pdfDocument, pageNumber) {
- this.pdfDocument = pdfDocument;
- this.pageNumber = pageNumber;
+ function PDFThumbnailView(options) {
+ var container = options.container;
+ var id = options.id;
+ var defaultViewport = options.defaultViewport;
+ var linkService = options.linkService;
+ var renderingQueue = options.renderingQueue;
+
+ this.id = id;
+ this.renderingId = 'thumbnail' + id;
+
+ this.pdfPage = null;
+ this.rotation = 0;
+ this.viewport = defaultViewport;
+ this.pdfPageRotate = defaultViewport.rotation;
+
+ this.linkService = linkService;
+ this.renderingQueue = renderingQueue;
+
+ this.hasImage = false;
+ this.resume = null;
+ this.renderingState = RenderingStates.INITIAL;
+
+ this.pageWidth = this.viewport.width;
+ this.pageHeight = this.viewport.height;
+ this.pageRatio = this.pageWidth / this.pageHeight;
+
+ this.canvasWidth = THUMBNAIL_WIDTH;
+ this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0;
+ this.scale = this.canvasWidth / this.pageWidth;
+
+ var anchor = document.createElement('a');
+ anchor.href = linkService.getAnchorUrl('#page=' + id);
+ anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
+ anchor.onclick = function stopNavigation() {
+ linkService.page = id;
+ return false;
+ };
+
+ var div = document.createElement('div');
+ div.id = 'thumbnailContainer' + id;
+ div.className = 'thumbnail';
+ this.div = div;
+
+ if (id === 1) {
+ // Highlight the thumbnail of the first page when no page number is
+ // specified (or exists in cache) when the document is loaded.
+ div.classList.add('selected');
+ }
+
+ var ring = document.createElement('div');
+ ring.className = 'thumbnailSelectionRing';
+ var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
+ ring.style.width = this.canvasWidth + borderAdjustment + 'px';
+ ring.style.height = this.canvasHeight + borderAdjustment + 'px';
+ this.ring = ring;
+
+ div.appendChild(ring);
+ anchor.appendChild(div);
+ container.appendChild(anchor);
}
- PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ {
+ PDFThumbnailView.prototype = {
+ setPdfPage: function PDFThumbnailView_setPdfPage(pdfPage) {
+ this.pdfPage = pdfPage;
+ this.pdfPageRotate = pdfPage.rotate;
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = pdfPage.getViewport(1, totalRotation);
+ this.reset();
+ },
+
+ reset: function PDFThumbnailView_reset() {
+ if (this.renderTask) {
+ this.renderTask.cancel();
+ }
+ this.hasImage = false;
+ this.resume = null;
+ this.renderingState = RenderingStates.INITIAL;
+
+ this.pageWidth = this.viewport.width;
+ this.pageHeight = this.viewport.height;
+ this.pageRatio = this.pageWidth / this.pageHeight;
+
+ this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0;
+ this.scale = (this.canvasWidth / this.pageWidth);
+
+ this.div.removeAttribute('data-loaded');
+ var ring = this.ring;
+ var childNodes = ring.childNodes;
+ for (var i = childNodes.length - 1; i >= 0; i--) {
+ ring.removeChild(childNodes[i]);
+ }
+ var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
+ ring.style.width = this.canvasWidth + borderAdjustment + 'px';
+ ring.style.height = this.canvasHeight + borderAdjustment + 'px';
+
+ if (this.canvas) {
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ this.canvas.width = 0;
+ this.canvas.height = 0;
+ delete this.canvas;
+ }
+ if (this.image) {
+ this.image.removeAttribute('src');
+ delete this.image;
+ }
+ },
+
+ update: function PDFThumbnailView_update(rotation) {
+ if (typeof rotation !== 'undefined') {
+ this.rotation = rotation;
+ }
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = this.viewport.clone({
+ scale: 1,
+ rotation: totalRotation
+ });
+ this.reset();
+ },
+
/**
- * @returns {Promise<PDFPage>}
+ * @private
*/
- getPage: function () {
- return this.pdfDocument.getPage(this.pageNumber);
+ _getPageDrawContext:
+ function PDFThumbnailView_getPageDrawContext(noCtxScale) {
+ var canvas = document.createElement('canvas');
+ this.canvas = canvas;
+
+ canvas.mozOpaque = true;
+ var ctx = canvas.getContext('2d', {alpha: false});
+ var outputScale = getOutputScale(ctx);
+
+ canvas.width = (this.canvasWidth * outputScale.sx) | 0;
+ canvas.height = (this.canvasHeight * outputScale.sy) | 0;
+ canvas.style.width = this.canvasWidth + 'px';
+ canvas.style.height = this.canvasHeight + 'px';
+
+ if (!noCtxScale && outputScale.scaled) {
+ ctx.scale(outputScale.sx, outputScale.sy);
+ }
+
+ var image = document.createElement('img');
+ this.image = image;
+
+ image.id = this.renderingId;
+ image.className = 'thumbnailImage';
+ image.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
+ { page: this.id }, 'Thumbnail of Page {{page}}'));
+
+ image.style.width = canvas.style.width;
+ image.style.height = canvas.style.height;
+
+ return ctx;
+ },
+
+ /**
+ * @private
+ */
+ _convertCanvasToImage: function PDFThumbnailView_convertCanvasToImage() {
+ if (!this.canvas) {
+ return;
+ }
+ this.image.src = this.canvas.toDataURL();
+
+ this.div.setAttribute('data-loaded', true);
+ this.ring.appendChild(this.image);
+
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ this.canvas.width = 0;
+ this.canvas.height = 0;
+ delete this.canvas;
+ },
+
+ draw: function PDFThumbnailView_draw() {
+ if (this.renderingState !== RenderingStates.INITIAL) {
+ console.error('Must be in new state before drawing');
+ }
+ if (this.hasImage) {
+ return Promise.resolve(undefined);
+ }
+ this.hasImage = true;
+ this.renderingState = RenderingStates.RUNNING;
+
+ var resolveRenderPromise, rejectRenderPromise;
+ var promise = new Promise(function (resolve, reject) {
+ resolveRenderPromise = resolve;
+ rejectRenderPromise = reject;
+ });
+
+ var self = this;
+ function thumbnailDrawCallback(error) {
+ // The renderTask may have been replaced by a new one, so only remove
+ // the reference to the renderTask if it matches the one that is
+ // triggering this callback.
+ if (renderTask === self.renderTask) {
+ self.renderTask = null;
+ }
+ if (error === 'cancelled') {
+ rejectRenderPromise(error);
+ return;
+ }
+ self.renderingState = RenderingStates.FINISHED;
+ self._convertCanvasToImage();
+
+ if (!error) {
+ resolveRenderPromise(undefined);
+ } else {
+ rejectRenderPromise(error);
+ }
+ }
+
+ var ctx = this._getPageDrawContext();
+ var drawViewport = this.viewport.clone({ scale: this.scale });
+ var renderContinueCallback = function renderContinueCallback(cont) {
+ if (!self.renderingQueue.isHighestPriority(self)) {
+ self.renderingState = RenderingStates.PAUSED;
+ self.resume = function resumeCallback() {
+ self.renderingState = RenderingStates.RUNNING;
+ cont();
+ };
+ return;
+ }
+ cont();
+ };
+
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: drawViewport
+ };
+ var renderTask = this.renderTask = this.pdfPage.render(renderContext);
+ renderTask.onContinue = renderContinueCallback;
+
+ renderTask.promise.then(
+ function pdfPageRenderCallback() {
+ thumbnailDrawCallback(null);
+ },
+ function pdfPageRenderError(error) {
+ thumbnailDrawCallback(error);
+ }
+ );
+ return promise;
+ },
+
+ setImage: function PDFThumbnailView_setImage(pageView) {
+ var img = pageView.canvas;
+ if (this.hasImage || !img) {
+ return;
+ }
+ if (!this.pdfPage) {
+ this.setPdfPage(pageView.pdfPage);
+ }
+ this.hasImage = true;
+ this.renderingState = RenderingStates.FINISHED;
+
+ var ctx = this._getPageDrawContext(true);
+ var canvas = ctx.canvas;
+
+ if (img.width <= 2 * canvas.width) {
+ ctx.drawImage(img, 0, 0, img.width, img.height,
+ 0, 0, canvas.width, canvas.height);
+ this._convertCanvasToImage();
+ return;
+ }
+ // drawImage does an awful job of rescaling the image, doing it gradually.
+ var MAX_NUM_SCALING_STEPS = 3;
+ var reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS;
+ var reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS;
+ var reducedImage = getTempCanvas(reducedWidth, reducedHeight);
+ var reducedImageCtx = reducedImage.getContext('2d');
+
+ while (reducedWidth > img.width || reducedHeight > img.height) {
+ reducedWidth >>= 1;
+ reducedHeight >>= 1;
+ }
+ reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
+ 0, 0, reducedWidth, reducedHeight);
+ while (reducedWidth > 2 * canvas.width) {
+ reducedImageCtx.drawImage(reducedImage,
+ 0, 0, reducedWidth, reducedHeight,
+ 0, 0, reducedWidth >> 1, reducedHeight >> 1);
+ reducedWidth >>= 1;
+ reducedHeight >>= 1;
+ }
+ ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
+ 0, 0, canvas.width, canvas.height);
+ this._convertCanvasToImage();
}
};
- return PDFPageSource;
+ return PDFThumbnailView;
})();
+PDFThumbnailView.tempImageCache = null;
+
+/**
+ * @typedef {Object} PDFThumbnailViewerOptions
+ * @property {HTMLDivElement} container - The container for the thumbnail
+ * elements.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ */
+
+/**
+ * Simple viewer control to display thumbnails for pages.
+ * @class
+ * @implements {IRenderableView}
+ */
+var PDFThumbnailViewer = (function PDFThumbnailViewerClosure() {
+ /**
+ * @constructs PDFThumbnailViewer
+ * @param {PDFThumbnailViewerOptions} options
+ */
+ function PDFThumbnailViewer(options) {
+ this.container = options.container;
+ this.renderingQueue = options.renderingQueue;
+ this.linkService = options.linkService;
+
+ this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this));
+ this._resetView();
+ }
+
+ PDFThumbnailViewer.prototype = {
+ /**
+ * @private
+ */
+ _scrollUpdated: function PDFThumbnailViewer_scrollUpdated() {
+ this.renderingQueue.renderHighestPriority();
+ },
+
+ getThumbnail: function PDFThumbnailViewer_getThumbnail(index) {
+ return this.thumbnails[index];
+ },
+
+ /**
+ * @private
+ */
+ _getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() {
+ return getVisibleElements(this.container, this.thumbnails);
+ },
+
+ scrollThumbnailIntoView:
+ function PDFThumbnailViewer_scrollThumbnailIntoView(page) {
+ var selected = document.querySelector('.thumbnail.selected');
+ if (selected) {
+ selected.classList.remove('selected');
+ }
+ var thumbnail = document.getElementById('thumbnailContainer' + page);
+ if (thumbnail) {
+ thumbnail.classList.add('selected');
+ }
+ var visibleThumbs = this._getVisibleThumbs();
+ var numVisibleThumbs = visibleThumbs.views.length;
+
+ // If the thumbnail isn't currently visible, scroll it into view.
+ if (numVisibleThumbs > 0) {
+ var first = visibleThumbs.first.id;
+ // Account for only one thumbnail being visible.
+ var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
+ if (page <= first || page >= last) {
+ scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
+ }
+ }
+ },
+
+ get pagesRotation() {
+ return this._pagesRotation;
+ },
+
+ set pagesRotation(rotation) {
+ this._pagesRotation = rotation;
+ for (var i = 0, l = this.thumbnails.length; i < l; i++) {
+ var thumb = this.thumbnails[i];
+ thumb.update(rotation);
+ }
+ },
+
+ cleanup: function PDFThumbnailViewer_cleanup() {
+ var tempCanvas = PDFThumbnailView.tempImageCache;
+ if (tempCanvas) {
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ tempCanvas.width = 0;
+ tempCanvas.height = 0;
+ }
+ PDFThumbnailView.tempImageCache = null;
+ },
+
+ /**
+ * @private
+ */
+ _resetView: function PDFThumbnailViewer_resetView() {
+ this.thumbnails = [];
+ this._pagesRotation = 0;
+ this._pagesRequests = [];
+ },
+
+ setDocument: function PDFThumbnailViewer_setDocument(pdfDocument) {
+ if (this.pdfDocument) {
+ // cleanup of the elements and views
+ var thumbsView = this.container;
+ while (thumbsView.hasChildNodes()) {
+ thumbsView.removeChild(thumbsView.lastChild);
+ }
+ this._resetView();
+ }
+
+ this.pdfDocument = pdfDocument;
+ if (!pdfDocument) {
+ return Promise.resolve();
+ }
+
+ return pdfDocument.getPage(1).then(function (firstPage) {
+ var pagesCount = pdfDocument.numPages;
+ var viewport = firstPage.getViewport(1.0);
+ for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+ var thumbnail = new PDFThumbnailView({
+ container: this.container,
+ id: pageNum,
+ defaultViewport: viewport.clone(),
+ linkService: this.linkService,
+ renderingQueue: this.renderingQueue
+ });
+ this.thumbnails.push(thumbnail);
+ }
+ }.bind(this));
+ },
+
+ /**
+ * @param {PDFPageView} pageView
+ * @returns {PDFPage}
+ * @private
+ */
+ _ensurePdfPageLoaded:
+ function PDFThumbnailViewer_ensurePdfPageLoaded(thumbView) {
+ if (thumbView.pdfPage) {
+ return Promise.resolve(thumbView.pdfPage);
+ }
+ var pageNumber = thumbView.id;
+ if (this._pagesRequests[pageNumber]) {
+ return this._pagesRequests[pageNumber];
+ }
+ var promise = this.pdfDocument.getPage(pageNumber).then(
+ function (pdfPage) {
+ thumbView.setPdfPage(pdfPage);
+ this._pagesRequests[pageNumber] = null;
+ return pdfPage;
+ }.bind(this));
+ this._pagesRequests[pageNumber] = promise;
+ return promise;
+ },
+
+ ensureThumbnailVisible:
+ function PDFThumbnailViewer_ensureThumbnailVisible(page) {
+ // Ensure that the thumbnail of the current page is visible
+ // when switching from another view.
+ scrollIntoView(document.getElementById('thumbnailContainer' + page));
+ },
+
+ forceRendering: function () {
+ var visibleThumbs = this._getVisibleThumbs();
+ var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs,
+ this.thumbnails,
+ this.scroll.down);
+ if (thumbView) {
+ this._ensurePdfPageLoaded(thumbView).then(function () {
+ this.renderingQueue.renderView(thumbView);
+ }.bind(this));
+ return true;
+ }
+ return false;
+ }
+ };
+
+ return PDFThumbnailViewer;
+})();
+
+
+/**
+ * @typedef {Object} PDFOutlineViewOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {Array} outline - An array of outline objects.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ */
+
+/**
+ * @class
+ */
+var PDFOutlineView = (function PDFOutlineViewClosure() {
+ /**
+ * @constructs PDFOutlineView
+ * @param {PDFOutlineViewOptions} options
+ */
+ function PDFOutlineView(options) {
+ this.container = options.container;
+ this.outline = options.outline;
+ this.linkService = options.linkService;
+ this.lastToggleIsShow = true;
+ }
+
+ PDFOutlineView.prototype = {
+ reset: function PDFOutlineView_reset() {
+ var container = this.container;
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+ this.lastToggleIsShow = true;
+ },
+
+ /**
+ * @private
+ */
+ _dispatchEvent: function PDFOutlineView_dispatchEvent(outlineCount) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('outlineloaded', true, true, {
+ outlineCount: outlineCount
+ });
+ this.container.dispatchEvent(event);
+ },
+
+ /**
+ * @private
+ */
+ _bindLink: function PDFOutlineView_bindLink(element, item) {
+ var linkService = this.linkService;
+ element.href = linkService.getDestinationHash(item.dest);
+ element.onclick = function goToDestination(e) {
+ linkService.navigateTo(item.dest);
+ return false;
+ };
+ },
+
+ /**
+ * Prepend a button before an outline item which allows the user to toggle
+ * the visibility of all outline items at that level.
+ *
+ * @private
+ */
+ _addToggleButton: function PDFOutlineView_addToggleButton(div) {
+ var toggler = document.createElement('div');
+ toggler.className = 'outlineItemToggler';
+ toggler.onclick = function(event) {
+ event.stopPropagation();
+ toggler.classList.toggle('outlineItemsHidden');
+
+ if (event.shiftKey) {
+ var shouldShowAll = !toggler.classList.contains('outlineItemsHidden');
+ this._toggleOutlineItem(div, shouldShowAll);
+ }
+ }.bind(this);
+ div.insertBefore(toggler, div.firstChild);
+ },
+
+ /**
+ * Toggle the visibility of the subtree of an outline item.
+ *
+ * @param {Element} root - the root of the outline (sub)tree.
+ * @param {boolean} state - whether to show the outline (sub)tree. If false,
+ * the outline subtree rooted at |root| will be collapsed.
+ *
+ * @private
+ */
+ _toggleOutlineItem: function PDFOutlineView_toggleOutlineItem(root, show) {
+ this.lastToggleIsShow = show;
+ var togglers = root.querySelectorAll('.outlineItemToggler');
+ for (var i = 0, ii = togglers.length; i < ii; ++i) {
+ togglers[i].classList[show ? 'remove' : 'add']('outlineItemsHidden');
+ }
+ },
+
+ /**
+ * Collapse or expand all subtrees of the outline.
+ */
+ toggleOutlineTree: function PDFOutlineView_toggleOutlineTree() {
+ this._toggleOutlineItem(this.container, !this.lastToggleIsShow);
+ },
+
+ render: function PDFOutlineView_render() {
+ var outline = this.outline;
+ var outlineCount = 0;
+
+ this.reset();
+
+ if (!outline) {
+ this._dispatchEvent(outlineCount);
+ return;
+ }
+
+ var fragment = document.createDocumentFragment();
+ var queue = [{ parent: fragment, items: this.outline }];
+ var hasAnyNesting = false;
+ while (queue.length > 0) {
+ var levelData = queue.shift();
+ for (var i = 0, len = levelData.items.length; i < len; i++) {
+ var item = levelData.items[i];
+ var div = document.createElement('div');
+ div.className = 'outlineItem';
+ var element = document.createElement('a');
+ this._bindLink(element, item);
+ element.textContent = removeNullCharacters(item.title);
+ div.appendChild(element);
+
+ if (item.items.length > 0) {
+ hasAnyNesting = true;
+ this._addToggleButton(div);
+
+ var itemsDiv = document.createElement('div');
+ itemsDiv.className = 'outlineItems';
+ div.appendChild(itemsDiv);
+ queue.push({ parent: itemsDiv, items: item.items });
+ }
+
+ levelData.parent.appendChild(div);
+ outlineCount++;
+ }
+ }
+ if (hasAnyNesting) {
+ this.container.classList.add('outlineWithDeepNesting');
+ }
+
+ this.container.appendChild(fragment);
+
+ this._dispatchEvent(outlineCount);
+ }
+ };
+
+ return PDFOutlineView;
+})();
+
+
+/**
+ * @typedef {Object} PDFAttachmentViewOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {Array} attachments - An array of attachment objects.
+ * @property {DownloadManager} downloadManager - The download manager.
+ */
+
+/**
+ * @class
+ */
+var PDFAttachmentView = (function PDFAttachmentViewClosure() {
+ /**
+ * @constructs PDFAttachmentView
+ * @param {PDFAttachmentViewOptions} options
+ */
+ function PDFAttachmentView(options) {
+ this.container = options.container;
+ this.attachments = options.attachments;
+ this.downloadManager = options.downloadManager;
+ }
+
+ PDFAttachmentView.prototype = {
+ reset: function PDFAttachmentView_reset() {
+ var container = this.container;
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+ },
+
+ /**
+ * @private
+ */
+ _dispatchEvent: function PDFAttachmentView_dispatchEvent(attachmentsCount) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('attachmentsloaded', true, true, {
+ attachmentsCount: attachmentsCount
+ });
+ this.container.dispatchEvent(event);
+ },
+
+ /**
+ * @private
+ */
+ _bindLink: function PDFAttachmentView_bindLink(button, content, filename) {
+ button.onclick = function downloadFile(e) {
+ this.downloadManager.downloadData(content, filename, '');
+ return false;
+ }.bind(this);
+ },
+
+ render: function PDFAttachmentView_render() {
+ var attachments = this.attachments;
+ var attachmentsCount = 0;
+
+ this.reset();
+
+ if (!attachments) {
+ this._dispatchEvent(attachmentsCount);
+ return;
+ }
+
+ var names = Object.keys(attachments).sort(function(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ });
+ attachmentsCount = names.length;
+
+ for (var i = 0; i < attachmentsCount; i++) {
+ var item = attachments[names[i]];
+ var filename = getFileName(item.filename);
+ var div = document.createElement('div');
+ div.className = 'attachmentsItem';
+ var button = document.createElement('button');
+ this._bindLink(button, item.content, filename);
+ button.textContent = removeNullCharacters(filename);
+ div.appendChild(button);
+ this.container.appendChild(div);
+ }
+
+ this._dispatchEvent(attachmentsCount);
+ }
+ };
+
+ return PDFAttachmentView;
+})();
+
+
var PDFViewerApplication = {
initialBookmark: document.location.hash.substring(1),
+ initialDestination: null,
initialized: false,
fellback: false,
pdfDocument: null,
+ pdfLoadingTask: null,
sidebarOpen: false,
printing: false,
/** @type {PDFViewer} */
pdfViewer: null,
/** @type {PDFThumbnailViewer} */
pdfThumbnailViewer: null,
/** @type {PDFRenderingQueue} */
pdfRenderingQueue: null,
+ /** @type {PDFPresentationMode} */
+ pdfPresentationMode: null,
+ /** @type {PDFDocumentProperties} */
+ pdfDocumentProperties: null,
+ /** @type {PDFLinkService} */
+ pdfLinkService: null,
+ /** @type {PDFHistory} */
+ pdfHistory: null,
pageRotation: 0,
- updateScaleControls: true,
isInitialViewSet: false,
animationStartedPromise: null,
- mouseScrollTimeStamp: 0,
- mouseScrollDelta: 0,
preferenceSidebarViewOnLoad: SidebarView.NONE,
preferencePdfBugEnabled: false,
+ preferenceShowPreviousViewOnLoad: true,
+ preferenceDefaultZoomValue: '',
isViewerEmbedded: (window.parent !== window),
url: '',
// called once when the document is loaded
initialize: function pdfViewInitialize() {
var pdfRenderingQueue = new PDFRenderingQueue();
pdfRenderingQueue.onIdle = this.cleanup.bind(this);
this.pdfRenderingQueue = pdfRenderingQueue;
+ var pdfLinkService = new PDFLinkService();
+ this.pdfLinkService = pdfLinkService;
+
var container = document.getElementById('viewerContainer');
var viewer = document.getElementById('viewer');
this.pdfViewer = new PDFViewer({
container: container,
viewer: viewer,
renderingQueue: pdfRenderingQueue,
- linkService: this
+ linkService: pdfLinkService
});
pdfRenderingQueue.setViewer(this.pdfViewer);
+ pdfLinkService.setViewer(this.pdfViewer);
var thumbnailContainer = document.getElementById('thumbnailView');
this.pdfThumbnailViewer = new PDFThumbnailViewer({
container: thumbnailContainer,
renderingQueue: pdfRenderingQueue,
- linkService: this
+ linkService: pdfLinkService
});
pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
Preferences.initialize();
+ this.pdfHistory = new PDFHistory({
+ linkService: pdfLinkService
+ });
+ pdfLinkService.setHistory(this.pdfHistory);
+
this.findController = new PDFFindController({
pdfViewer: this.pdfViewer,
integratedFind: this.supportsIntegratedFind
});
this.pdfViewer.setFindController(this.findController);
@@ -4646,10 +6150,11 @@
toggleButton: document.getElementById('viewFind'),
findField: document.getElementById('findInput'),
highlightAllCheckbox: document.getElementById('findHighlightAll'),
caseSensitiveCheckbox: document.getElementById('findMatchCase'),
findMsg: document.getElementById('findMsg'),
+ findResultsCount: document.getElementById('findResultsCount'),
findStatusIcon: document.getElementById('findStatusIcon'),
findPreviousButton: document.getElementById('findPrevious'),
findNextButton: document.getElementById('findNext'),
findController: this.findController
});
@@ -4659,13 +6164,31 @@
HandTool.initialize({
container: container,
toggleHandTool: document.getElementById('toggleHandTool')
});
+ this.pdfDocumentProperties = new PDFDocumentProperties({
+ overlayName: 'documentPropertiesOverlay',
+ closeButton: document.getElementById('documentPropertiesClose'),
+ fields: {
+ 'fileName': document.getElementById('fileNameField'),
+ 'fileSize': document.getElementById('fileSizeField'),
+ 'title': document.getElementById('titleField'),
+ 'author': document.getElementById('authorField'),
+ 'subject': document.getElementById('subjectField'),
+ 'keywords': document.getElementById('keywordsField'),
+ 'creationDate': document.getElementById('creationDateField'),
+ 'modificationDate': document.getElementById('modificationDateField'),
+ 'creator': document.getElementById('creatorField'),
+ 'producer': document.getElementById('producerField'),
+ 'version': document.getElementById('versionField'),
+ 'pageCount': document.getElementById('pageCountField')
+ }
+ });
+
SecondaryToolbar.initialize({
toolbar: document.getElementById('secondaryToolbar'),
- presentationMode: PresentationMode,
toggleButton: document.getElementById('secondaryToolbarToggle'),
presentationModeButton:
document.getElementById('secondaryPresentationMode'),
openFile: document.getElementById('secondaryOpenFile'),
print: document.getElementById('secondaryPrint'),
@@ -4673,48 +6196,41 @@
viewBookmark: document.getElementById('secondaryViewBookmark'),
firstPage: document.getElementById('firstPage'),
lastPage: document.getElementById('lastPage'),
pageRotateCw: document.getElementById('pageRotateCw'),
pageRotateCcw: document.getElementById('pageRotateCcw'),
- documentProperties: DocumentProperties,
documentPropertiesButton: document.getElementById('documentProperties')
});
- PresentationMode.initialize({
- container: container,
- secondaryToolbar: SecondaryToolbar,
- firstPage: document.getElementById('contextFirstPage'),
- lastPage: document.getElementById('contextLastPage'),
- pageRotateCw: document.getElementById('contextPageRotateCw'),
- pageRotateCcw: document.getElementById('contextPageRotateCcw')
- });
+ if (this.supportsFullscreen) {
+ var toolbar = SecondaryToolbar;
+ this.pdfPresentationMode = new PDFPresentationMode({
+ container: container,
+ viewer: viewer,
+ pdfViewer: this.pdfViewer,
+ pdfThumbnailViewer: this.pdfThumbnailViewer,
+ contextMenuItems: [
+ { element: document.getElementById('contextFirstPage'),
+ handler: toolbar.firstPageClick.bind(toolbar) },
+ { element: document.getElementById('contextLastPage'),
+ handler: toolbar.lastPageClick.bind(toolbar) },
+ { element: document.getElementById('contextPageRotateCw'),
+ handler: toolbar.pageRotateCwClick.bind(toolbar) },
+ { element: document.getElementById('contextPageRotateCcw'),
+ handler: toolbar.pageRotateCcwClick.bind(toolbar) }
+ ]
+ });
+ }
PasswordPrompt.initialize({
overlayName: 'passwordOverlay',
passwordField: document.getElementById('password'),
passwordText: document.getElementById('passwordText'),
passwordSubmit: document.getElementById('passwordSubmit'),
passwordCancel: document.getElementById('passwordCancel')
});
- DocumentProperties.initialize({
- overlayName: 'documentPropertiesOverlay',
- closeButton: document.getElementById('documentPropertiesClose'),
- fileNameField: document.getElementById('fileNameField'),
- fileSizeField: document.getElementById('fileSizeField'),
- titleField: document.getElementById('titleField'),
- authorField: document.getElementById('authorField'),
- subjectField: document.getElementById('subjectField'),
- keywordsField: document.getElementById('keywordsField'),
- creationDateField: document.getElementById('creationDateField'),
- modificationDateField: document.getElementById('modificationDateField'),
- creatorField: document.getElementById('creatorField'),
- producerField: document.getElementById('producerField'),
- versionField: document.getElementById('versionField'),
- pageCountField: document.getElementById('pageCountField')
- });
-
var self = this;
var initializedPromise = Promise.all([
Preferences.get('enableWebGL').then(function resolved(value) {
PDFJS.disableWebGL = !value;
}),
@@ -4722,10 +6238,16 @@
self.preferenceSidebarViewOnLoad = value;
}),
Preferences.get('pdfBugEnabled').then(function resolved(value) {
self.preferencePdfBugEnabled = value;
}),
+ Preferences.get('showPreviousViewOnLoad').then(function resolved(value) {
+ self.preferenceShowPreviousViewOnLoad = value;
+ }),
+ Preferences.get('defaultZoomValue').then(function resolved(value) {
+ self.preferenceDefaultZoomValue = value;
+ }),
Preferences.get('disableTextLayer').then(function resolved(value) {
if (PDFJS.disableTextLayer === true) {
return;
}
PDFJS.disableTextLayer = value;
@@ -4734,10 +6256,16 @@
if (PDFJS.disableRange === true) {
return;
}
PDFJS.disableRange = value;
}),
+ Preferences.get('disableStream').then(function resolved(value) {
+ if (PDFJS.disableStream === true) {
+ return;
+ }
+ PDFJS.disableStream = value;
+ }),
Preferences.get('disableAutoFetch').then(function resolved(value) {
PDFJS.disableAutoFetch = value;
}),
Preferences.get('disableFontFace').then(function resolved(value) {
if (PDFJS.disableFontFace === true) {
@@ -4745,95 +6273,122 @@
}
PDFJS.disableFontFace = value;
}),
Preferences.get('useOnlyCssZoom').then(function resolved(value) {
PDFJS.useOnlyCssZoom = value;
- })
+ }),
+ Preferences.get('externalLinkTarget').then(function resolved(value) {
+ if (PDFJS.isExternalLinkTargetSet()) {
+ return;
+ }
+ PDFJS.externalLinkTarget = value;
+ }),
// TODO move more preferences and other async stuff here
]).catch(function (reason) { });
return initializedPromise.then(function () {
- PDFViewerApplication.initialized = true;
+ if (self.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) {
+ // Prevent external links from "replacing" the viewer,
+ // when it's embedded in e.g. an iframe or an object.
+ PDFJS.externalLinkTarget = PDFJS.LinkTarget.TOP;
+ }
+
+ self.initialized = true;
});
},
zoomIn: function pdfViewZoomIn(ticks) {
var newScale = this.pdfViewer.currentScale;
do {
newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.ceil(newScale * 10) / 10;
newScale = Math.min(MAX_SCALE, newScale);
- } while (--ticks && newScale < MAX_SCALE);
- this.setScale(newScale, true);
+ } while (--ticks > 0 && newScale < MAX_SCALE);
+ this.pdfViewer.currentScaleValue = newScale;
},
zoomOut: function pdfViewZoomOut(ticks) {
var newScale = this.pdfViewer.currentScale;
do {
newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.floor(newScale * 10) / 10;
newScale = Math.max(MIN_SCALE, newScale);
- } while (--ticks && newScale > MIN_SCALE);
- this.setScale(newScale, true);
+ } while (--ticks > 0 && newScale > MIN_SCALE);
+ this.pdfViewer.currentScaleValue = newScale;
},
- get currentScaleValue() {
- return this.pdfViewer.currentScaleValue;
- },
-
get pagesCount() {
return this.pdfDocument.numPages;
},
set page(val) {
- this.pdfViewer.currentPageNumber = val;
+ this.pdfLinkService.page = val;
},
- get page() {
- return this.pdfViewer.currentPageNumber;
+ get page() { // TODO remove
+ return this.pdfLinkService.page;
},
get supportsPrinting() {
- return false;
+ var canvas = document.createElement('canvas');
+ var value = 'mozPrintCallback' in canvas;
+
+ return PDFJS.shadow(this, 'supportsPrinting', value);
},
get supportsFullscreen() {
- return false;
+ var doc = document.documentElement;
+ var support = !!(doc.requestFullscreen || doc.mozRequestFullScreen ||
+ doc.webkitRequestFullScreen || doc.msRequestFullscreen);
+
+ if (document.fullscreenEnabled === false ||
+ document.mozFullScreenEnabled === false ||
+ document.webkitFullscreenEnabled === false ||
+ document.msFullscreenEnabled === false) {
+ support = false;
+ }
+ if (support && PDFJS.disableFullscreen === true) {
+ support = false;
+ }
+
+ return PDFJS.shadow(this, 'supportsFullscreen', support);
},
get supportsIntegratedFind() {
- return false;
+ var support = false;
+
+ return PDFJS.shadow(this, 'supportsIntegratedFind', support);
},
get supportsDocumentFonts() {
var support = true;
- Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
- enumerable: true,
- configurable: true,
- writable: false });
- return support;
+
+ return PDFJS.shadow(this, 'supportsDocumentFonts', support);
},
get supportsDocumentColors() {
var support = true;
- Object.defineProperty(this, 'supportsDocumentColors', { value: support,
- enumerable: true,
- configurable: true,
- writable: false });
- return support;
+
+ return PDFJS.shadow(this, 'supportsDocumentColors', support);
},
get loadingBar() {
var bar = new ProgressBar('#loadingBar', {});
- Object.defineProperty(this, 'loadingBar', { value: bar,
- enumerable: true,
- configurable: true,
- writable: false });
- return bar;
+
+ return PDFJS.shadow(this, 'loadingBar', bar);
},
+ get supportedMouseWheelZoomModifierKeys() {
+ var support = {
+ ctrlKey: true,
+ metaKey: true,
+ };
+ return PDFJS.shadow(this, 'supportedMouseWheelZoomModifierKeys', support);
+ },
+
+
setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
this.url = url;
try {
this.setTitle(decodeURIComponent(getFileName(url)) || url);
} catch (e) {
@@ -4842,42 +6397,87 @@
this.setTitle(url);
}
},
setTitle: function pdfViewSetTitle(title) {
+ if (this.isViewerEmbedded) {
+ // Embedded PDF viewers should not be changing their parent page's title.
+ return;
+ }
document.title = title;
},
+ /**
+ * Closes opened PDF document.
+ * @returns {Promise} - Returns the promise, which is resolved when all
+ * destruction is completed.
+ */
close: function pdfViewClose() {
var errorWrapper = document.getElementById('errorWrapper');
errorWrapper.setAttribute('hidden', 'true');
- if (!this.pdfDocument) {
- return;
+ if (!this.pdfLoadingTask) {
+ return Promise.resolve();
}
- this.pdfDocument.destroy();
- this.pdfDocument = null;
+ var promise = this.pdfLoadingTask.destroy();
+ this.pdfLoadingTask = null;
- this.pdfThumbnailViewer.setDocument(null);
- this.pdfViewer.setDocument(null);
+ if (this.pdfDocument) {
+ this.pdfDocument = null;
+ this.pdfThumbnailViewer.setDocument(null);
+ this.pdfViewer.setDocument(null);
+ this.pdfLinkService.setDocument(null, null);
+ }
+
if (typeof PDFBug !== 'undefined') {
PDFBug.cleanup();
}
+ return promise;
},
- // TODO(mack): This function signature should really be pdfViewOpen(url, args)
- open: function pdfViewOpen(file, scale, password,
- pdfDataRangeTransport, args) {
- if (this.pdfDocument) {
- // Reload the preferences if a document was previously opened.
- Preferences.reload();
+ /**
+ * Opens PDF document specified by URL or array with additional arguments.
+ * @param {string|TypedArray|ArrayBuffer} file - PDF location or binary data.
+ * @param {Object} args - (optional) Additional arguments for the getDocument
+ * call, e.g. HTTP headers ('httpHeaders') or
+ * alternative data transport ('range').
+ * @returns {Promise} - Returns the promise, which is resolved when document
+ * is opened.
+ */
+ open: function pdfViewOpen(file, args) {
+ var scale = 0;
+ if (arguments.length > 2 || typeof args === 'number') {
+ console.warn('Call of open() with obsolete signature.');
+ if (typeof args === 'number') {
+ scale = args; // scale argument was found
+ }
+ args = arguments[4] || null;
+ if (arguments[3] && typeof arguments[3] === 'object') {
+ // The pdfDataRangeTransport argument is present.
+ args = Object.create(args);
+ args.range = arguments[3];
+ }
+ if (typeof arguments[2] === 'string') {
+ // The password argument is present.
+ args = Object.create(args);
+ args.password = arguments[2];
+ }
}
- this.close();
- var parameters = {password: password};
+ if (this.pdfLoadingTask) {
+ // We need to destroy already opened document.
+ return this.close().then(function () {
+ // Reload the preferences if a document was previously opened.
+ Preferences.reload();
+ // ... and repeat the open() call.
+ return this.open(file, args);
+ }.bind(this));
+ }
+
+ var parameters = Object.create(null);
if (typeof file === 'string') { // URL
this.setTitleUsingUrl(file);
parameters.url = file;
} else if (file && 'byteLength' in file) { // ArrayBuffer
parameters.data = file;
@@ -4890,28 +6490,31 @@
parameters[prop] = args[prop];
}
}
var self = this;
- self.loading = true;
self.downloadComplete = false;
- var passwordNeeded = function passwordNeeded(updatePassword, reason) {
+ var loadingTask = PDFJS.getDocument(parameters);
+ this.pdfLoadingTask = loadingTask;
+
+ loadingTask.onPassword = function passwordNeeded(updatePassword, reason) {
PasswordPrompt.updatePassword = updatePassword;
PasswordPrompt.reason = reason;
PasswordPrompt.open();
};
- function getDocumentProgress(progressData) {
+ loadingTask.onProgress = function getDocumentProgress(progressData) {
self.progress(progressData.loaded / progressData.total);
- }
+ };
- PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
- getDocumentProgress).then(
+ // Listen for unsupported features to trigger the fallback UI.
+ loadingTask.onUnsupportedFeature = this.fallback.bind(this);
+
+ var result = loadingTask.promise.then(
function getDocumentCallback(pdfDocument) {
self.load(pdfDocument, scale);
- self.loading = false;
},
function getDocumentError(exception) {
var message = exception && exception.message;
var loadingErrorMessage = mozL10n.get('loading_error', null,
'An error occurred while loading the PDF.');
@@ -4931,17 +6534,19 @@
var moreInfo = {
message: message
};
self.error(loadingErrorMessage, moreInfo);
- self.loading = false;
+
+ throw new Error(loadingErrorMessage);
}
);
if (args && args.length) {
- DocumentProperties.setFileSize(args.length);
+ PDFViewerApplication.pdfDocumentProperties.setFileSize(args.length);
}
+ return result;
},
download: function pdfViewDownload() {
function downloadByUrl() {
downloadManager.downloadUrl(url, filename);
@@ -4974,138 +6579,13 @@
downloadByUrl // Error occurred try downloading with just the url.
).then(null, downloadByUrl);
},
fallback: function pdfViewFallback(featureId) {
- return;
},
- navigateTo: function pdfViewNavigateTo(dest) {
- var destString = '';
- var self = this;
-
- var goToDestination = function(destRef) {
- self.pendingRefStr = null;
- // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
- var pageNumber = destRef instanceof Object ?
- self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
- (destRef + 1);
- if (pageNumber) {
- if (pageNumber > self.pagesCount) {
- pageNumber = self.pagesCount;
- }
- self.pdfViewer.scrollPageIntoView(pageNumber, dest);
-
- // Update the browsing history.
- PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
- } else {
- self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
- var pageNum = pageIndex + 1;
- self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
- goToDestination(destRef);
- });
- }
- };
-
- var destinationPromise;
- if (typeof dest === 'string') {
- destString = dest;
- destinationPromise = this.pdfDocument.getDestination(dest);
- } else {
- destinationPromise = Promise.resolve(dest);
- }
- destinationPromise.then(function(destination) {
- dest = destination;
- if (!(destination instanceof Array)) {
- return; // invalid destination
- }
- goToDestination(destination[0]);
- });
- },
-
- executeNamedAction: function pdfViewExecuteNamedAction(action) {
- // See PDF reference, table 8.45 - Named action
- switch (action) {
- case 'GoToPage':
- document.getElementById('pageNumber').focus();
- break;
-
- case 'GoBack':
- PDFHistory.back();
- break;
-
- case 'GoForward':
- PDFHistory.forward();
- break;
-
- case 'Find':
- if (!this.supportsIntegratedFind) {
- this.findBar.toggle();
- }
- break;
-
- case 'NextPage':
- this.page++;
- break;
-
- case 'PrevPage':
- this.page--;
- break;
-
- case 'LastPage':
- this.page = this.pagesCount;
- break;
-
- case 'FirstPage':
- this.page = 1;
- break;
-
- default:
- break; // No action according to spec
- }
- },
-
- getDestinationHash: function pdfViewGetDestinationHash(dest) {
- if (typeof dest === 'string') {
- return this.getAnchorUrl('#' + escape(dest));
- }
- if (dest instanceof Array) {
- var destRef = dest[0]; // see navigateTo method for dest format
- var pageNumber = destRef instanceof Object ?
- this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
- (destRef + 1);
- if (pageNumber) {
- var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
- var destKind = dest[1];
- if (typeof destKind === 'object' && 'name' in destKind &&
- destKind.name === 'XYZ') {
- var scale = (dest[4] || this.currentScaleValue);
- var scaleNumber = parseFloat(scale);
- if (scaleNumber) {
- scale = scaleNumber * 100;
- }
- pdfOpenParams += '&zoom=' + scale;
- if (dest[2] || dest[3]) {
- pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
- }
- }
- return pdfOpenParams;
- }
- }
- return '';
- },
-
/**
- * Prefix the full url on anchor links to make sure that links are resolved
- * relative to the current URL instead of the one defined in <base href>.
- * @param {String} anchor The anchor hash, including the #.
- */
- getAnchorUrl: function getAnchorUrl(anchor) {
- return anchor;
- },
-
- /**
* Show the error box.
* @param {String} message A message that is human readable.
* @param {Object} moreInfo (optional) Further information about the error
* that is more technical. Should have a 'message'
* and optionally a 'stack' property.
@@ -5175,10 +6655,28 @@
// that we discard some of the loaded data. This can cause the loading
// bar to move backwards. So prevent this by only updating the bar if it
// increases.
if (percent > this.loadingBar.percent || isNaN(percent)) {
this.loadingBar.percent = percent;
+
+ // When disableAutoFetch is enabled, it's not uncommon for the entire file
+ // to never be fetched (depends on e.g. the file structure). In this case
+ // the loading bar will not be completely filled, nor will it be hidden.
+ // To prevent displaying a partially filled loading bar permanently, we
+ // hide it when no data has been loaded during a certain amount of time.
+ if (PDFJS.disableAutoFetch && percent) {
+ if (this.disableAutoFetchLoadingBarTimeout) {
+ clearTimeout(this.disableAutoFetchLoadingBarTimeout);
+ this.disableAutoFetchLoadingBarTimeout = null;
+ }
+ this.loadingBar.show();
+
+ this.disableAutoFetchLoadingBarTimeout = setTimeout(function () {
+ this.loadingBar.hide();
+ this.disableAutoFetchLoadingBarTimeout = null;
+ }.bind(this), DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT);
+ }
}
},
load: function pdfViewLoad(pdfDocument, scale) {
var self = this;
@@ -5186,39 +6684,37 @@
this.findController.reset();
this.pdfDocument = pdfDocument;
- DocumentProperties.url = this.url;
- DocumentProperties.pdfDocument = pdfDocument;
- DocumentProperties.resolveDataAvailable();
+ this.pdfDocumentProperties.setDocumentAndUrl(pdfDocument, this.url);
var downloadedPromise = pdfDocument.getDownloadInfo().then(function() {
self.downloadComplete = true;
self.loadingBar.hide();
- var outerContainer = document.getElementById('outerContainer');
- outerContainer.classList.remove('loadingInProgress');
});
var pagesCount = pdfDocument.numPages;
document.getElementById('numPages').textContent =
mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
document.getElementById('pageNumber').max = pagesCount;
var id = this.documentFingerprint = pdfDocument.fingerprint;
var store = this.store = new ViewHistory(id);
+ var baseDocumentUrl = null;
+ this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
+
var pdfViewer = this.pdfViewer;
pdfViewer.currentScale = scale;
pdfViewer.setDocument(pdfDocument);
var firstPagePromise = pdfViewer.firstPagePromise;
var pagesPromise = pdfViewer.pagesPromise;
var onePageRendered = pdfViewer.onePageRendered;
this.pageRotation = 0;
this.isInitialViewSet = false;
- this.pagesRefMap = pdfViewer.pagesRefMap;
this.pdfThumbnailViewer.setDocument(pdfDocument);
firstPagePromise.then(function(pdfPage) {
downloadedPromise.then(function () {
@@ -5227,71 +6723,87 @@
window.dispatchEvent(event);
});
self.loadingBar.setWidth(document.getElementById('viewer'));
- self.findController.resolveFirstPage();
-
if (!PDFJS.disableHistory && !self.isViewerEmbedded) {
// The browsing history is only enabled when the viewer is standalone,
// i.e. not when it is embedded in a web page.
- PDFHistory.initialize(self.documentFingerprint, self);
+ if (!self.preferenceShowPreviousViewOnLoad) {
+ self.pdfHistory.clearHistoryState();
+ }
+ self.pdfHistory.initialize(self.documentFingerprint);
+
+ if (self.pdfHistory.initialDestination) {
+ self.initialDestination = self.pdfHistory.initialDestination;
+ } else if (self.pdfHistory.initialBookmark) {
+ self.initialBookmark = self.pdfHistory.initialBookmark;
+ }
}
- });
- // Fetch the necessary preference values.
- var showPreviousViewOnLoad;
- var showPreviousViewOnLoadPromise =
- Preferences.get('showPreviousViewOnLoad').then(function (prefValue) {
- showPreviousViewOnLoad = prefValue;
- });
- var defaultZoomValue;
- var defaultZoomValuePromise =
- Preferences.get('defaultZoomValue').then(function (prefValue) {
- defaultZoomValue = prefValue;
- });
+ var initialParams = {
+ destination: self.initialDestination,
+ bookmark: self.initialBookmark,
+ hash: null,
+ };
- var storePromise = store.initializedPromise;
- Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise,
- defaultZoomValuePromise]).then(function resolved() {
- var storedHash = null;
- if (showPreviousViewOnLoad && store.get('exists', false)) {
- var pageNum = store.get('page', '1');
- var zoom = defaultZoomValue ||
- store.get('zoom', self.pdfViewer.currentScale);
- var left = store.get('scrollLeft', '0');
- var top = store.get('scrollTop', '0');
+ store.initializedPromise.then(function resolved() {
+ var storedHash = null;
+ if (self.preferenceShowPreviousViewOnLoad &&
+ store.get('exists', false)) {
+ var pageNum = store.get('page', '1');
+ var zoom = self.preferenceDefaultZoomValue ||
+ store.get('zoom', DEFAULT_SCALE_VALUE);
+ var left = store.get('scrollLeft', '0');
+ var top = store.get('scrollTop', '0');
- storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
- left + ',' + top;
- } else if (defaultZoomValue) {
- storedHash = 'page=1&zoom=' + defaultZoomValue;
- }
- self.setInitialView(storedHash, scale);
+ storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
+ left + ',' + top;
+ } else if (self.preferenceDefaultZoomValue) {
+ storedHash = 'page=1&zoom=' + self.preferenceDefaultZoomValue;
+ }
+ self.setInitialView(storedHash, scale);
- // Make all navigation keys work on document load,
- // unless the viewer is embedded in a web page.
- if (!self.isViewerEmbedded) {
- self.pdfViewer.focus();
- }
- }, function rejected(reason) {
- console.error(reason);
+ initialParams.hash = storedHash;
- firstPagePromise.then(function () {
+ // Make all navigation keys work on document load,
+ // unless the viewer is embedded in a web page.
+ if (!self.isViewerEmbedded) {
+ self.pdfViewer.focus();
+ }
+ }, function rejected(reason) {
+ console.error(reason);
self.setInitialView(null, scale);
});
+
+ // For documents with different page sizes,
+ // ensure that the correct location becomes visible on load.
+ pagesPromise.then(function resolved() {
+ if (!initialParams.destination && !initialParams.bookmark &&
+ !initialParams.hash) {
+ return;
+ }
+ if (self.hasEqualPageSizes) {
+ return;
+ }
+ self.initialDestination = initialParams.destination;
+ self.initialBookmark = initialParams.bookmark;
+
+ self.pdfViewer.currentScaleValue = self.pdfViewer.currentScaleValue;
+ self.setInitialView(initialParams.hash, scale);
+ });
});
pagesPromise.then(function() {
if (self.supportsPrinting) {
pdfDocument.getJavaScript().then(function(javaScript) {
if (javaScript.length) {
console.warn('Warning: JavaScript is not supported');
self.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript);
}
// Hack to support auto printing.
- var regex = /\bprint\s*\(/g;
+ var regex = /\bprint\s*\(/;
for (var i = 0, ii = javaScript.length; i < ii; i++) {
var js = javaScript[i];
if (js && regex.test(js)) {
setTimeout(function() {
window.print();
@@ -5305,35 +6817,38 @@
// outline depends on pagesRefMap
var promises = [pagesPromise, this.animationStartedPromise];
Promise.all(promises).then(function() {
pdfDocument.getOutline().then(function(outline) {
- var outlineView = document.getElementById('outlineView');
- self.outline = new DocumentOutlineView({
+ var container = document.getElementById('outlineView');
+ self.outline = new PDFOutlineView({
+ container: container,
outline: outline,
- outlineView: outlineView,
- linkService: self
+ linkService: self.pdfLinkService
});
+ self.outline.render();
document.getElementById('viewOutline').disabled = !outline;
- if (!outline && !outlineView.classList.contains('hidden')) {
+ if (!outline && !container.classList.contains('hidden')) {
self.switchSidebarView('thumbs');
}
if (outline &&
self.preferenceSidebarViewOnLoad === SidebarView.OUTLINE) {
self.switchSidebarView('outline', true);
}
});
pdfDocument.getAttachments().then(function(attachments) {
- var attachmentsView = document.getElementById('attachmentsView');
- self.attachments = new DocumentAttachmentsView({
+ var container = document.getElementById('attachmentsView');
+ self.attachments = new PDFAttachmentView({
+ container: container,
attachments: attachments,
- attachmentsView: attachmentsView
+ downloadManager: new DownloadManager()
});
+ self.attachments.render();
document.getElementById('viewAttachments').disabled = !attachments;
- if (!attachments && !attachmentsView.classList.contains('hidden')) {
+ if (!attachments && !container.classList.contains('hidden')) {
self.switchSidebarView('thumbs');
}
if (attachments &&
self.preferenceSidebarViewOnLoad === SidebarView.ATTACHMENTS) {
self.switchSidebarView('attachments', true);
@@ -5344,10 +6859,11 @@
if (self.preferenceSidebarViewOnLoad === SidebarView.THUMBS) {
Promise.all([firstPagePromise, onePageRendered]).then(function () {
self.switchSidebarView('thumbs', true);
});
}
+
pdfDocument.getMetadata().then(function(data) {
var info = data.info, metadata = data.metadata;
self.documentInfo = info;
self.metadata = metadata;
@@ -5384,38 +6900,40 @@
},
setInitialView: function pdfViewSetInitialView(storedHash, scale) {
this.isInitialViewSet = true;
- // When opening a new file (when one is already loaded in the viewer):
- // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
- // if 'currentPageNumber' is larger than the number of pages in the file.
+ // When opening a new file, when one is already loaded in the viewer,
+ // ensure that the 'pageNumber' element displays the correct value.
document.getElementById('pageNumber').value =
- this.pdfViewer.currentPageNumber = 1;
+ this.pdfViewer.currentPageNumber;
- if (PDFHistory.initialDestination) {
- this.navigateTo(PDFHistory.initialDestination);
- PDFHistory.initialDestination = null;
+ if (this.initialDestination) {
+ this.pdfLinkService.navigateTo(this.initialDestination);
+ this.initialDestination = null;
} else if (this.initialBookmark) {
- this.setHash(this.initialBookmark);
- PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
+ this.pdfLinkService.setHash(this.initialBookmark);
+ this.pdfHistory.push({ hash: this.initialBookmark }, true);
this.initialBookmark = null;
} else if (storedHash) {
- this.setHash(storedHash);
+ this.pdfLinkService.setHash(storedHash);
} else if (scale) {
- this.setScale(scale, true);
+ this.pdfViewer.currentScaleValue = scale;
this.page = 1;
}
- if (this.pdfViewer.currentScale === UNKNOWN_SCALE) {
+ if (!this.pdfViewer.currentScaleValue) {
// Scale was not initialized: invalid bookmark or scale was not specified.
// Setting the default one.
- this.setScale(DEFAULT_SCALE, true);
+ this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
}
},
cleanup: function pdfViewCleanup() {
+ if (!this.pdfDocument) {
+ return; // run cleanup when document is loaded
+ }
this.pdfViewer.cleanup();
this.pdfThumbnailViewer.cleanup();
this.pdfDocument.cleanup();
},
@@ -5423,74 +6941,25 @@
this.pdfRenderingQueue.printing = this.printing;
this.pdfRenderingQueue.isThumbnailViewEnabled = this.sidebarOpen;
this.pdfRenderingQueue.renderHighestPriority();
},
- setHash: function pdfViewSetHash(hash) {
- if (!this.isInitialViewSet) {
- this.initialBookmark = hash;
- return;
- }
+ refreshThumbnailViewer: function pdfViewRefreshThumbnailViewer() {
+ var pdfViewer = this.pdfViewer;
+ var thumbnailViewer = this.pdfThumbnailViewer;
- var validFitZoomValues = ['Fit','FitB','FitH','FitBH',
- 'FitV','FitBV','FitR'];
-
- if (!hash) {
- return;
- }
-
- if (hash.indexOf('=') >= 0) {
- var params = this.parseQueryString(hash);
- // borrowing syntax from "Parameters for Opening PDF Files"
- if ('nameddest' in params) {
- PDFHistory.updateNextHashParam(params.nameddest);
- this.navigateTo(params.nameddest);
- return;
+ // set thumbnail images of rendered pages
+ var pagesCount = pdfViewer.pagesCount;
+ for (var pageIndex = 0; pageIndex < pagesCount; pageIndex++) {
+ var pageView = pdfViewer.getPageView(pageIndex);
+ if (pageView && pageView.renderingState === RenderingStates.FINISHED) {
+ var thumbnailView = thumbnailViewer.getThumbnail(pageIndex);
+ thumbnailView.setImage(pageView);
}
- var pageNumber, dest;
- if ('page' in params) {
- pageNumber = (params.page | 0) || 1;
- }
- if ('zoom' in params) {
- var zoomArgs = params.zoom.split(','); // scale,left,top
- // building destination array
-
- // If the zoom value, it has to get divided by 100. If it is a string,
- // it should stay as it is.
- var zoomArg = zoomArgs[0];
- var zoomArgNumber = parseFloat(zoomArg);
- var destName = 'XYZ';
- if (zoomArgNumber) {
- zoomArg = zoomArgNumber / 100;
- } else if (validFitZoomValues.indexOf(zoomArg) >= 0) {
- destName = zoomArg;
- }
- dest = [null, { name: destName },
- zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
- zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
- zoomArg];
- }
- if (dest) {
- this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
- } else if (pageNumber) {
- this.page = pageNumber; // simple page
- }
- if ('pagemode' in params) {
- if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
- params.pagemode === 'attachments') {
- this.switchSidebarView((params.pagemode === 'bookmarks' ?
- 'outline' : params.pagemode), true);
- } else if (params.pagemode === 'none' && this.sidebarOpen) {
- document.getElementById('sidebarToggle').click();
- }
- }
- } else if (/^\d+$/.test(hash)) { // page number
- this.page = hash;
- } else { // named destination
- PDFHistory.updateNextHashParam(unescape(hash));
- this.navigateTo(unescape(hash));
}
+
+ thumbnailViewer.scrollThumbnailIntoView(this.page);
},
switchSidebarView: function pdfViewSwitchSidebarView(view, openSidebar) {
if (openSidebar && !this.sidebarOpen) {
document.getElementById('sidebarToggle').click();
@@ -5520,61 +6989,46 @@
this.pdfThumbnailViewer.ensureThumbnailVisible(this.page);
}
break;
case 'outline':
+ if (outlineButton.disabled) {
+ return;
+ }
thumbsButton.classList.remove('toggled');
outlineButton.classList.add('toggled');
attachmentsButton.classList.remove('toggled');
thumbsView.classList.add('hidden');
outlineView.classList.remove('hidden');
attachmentsView.classList.add('hidden');
-
- if (outlineButton.getAttribute('disabled')) {
- return;
- }
break;
case 'attachments':
+ if (attachmentsButton.disabled) {
+ return;
+ }
thumbsButton.classList.remove('toggled');
outlineButton.classList.remove('toggled');
attachmentsButton.classList.add('toggled');
thumbsView.classList.add('hidden');
outlineView.classList.add('hidden');
attachmentsView.classList.remove('hidden');
-
- if (attachmentsButton.getAttribute('disabled')) {
- return;
- }
break;
}
},
- // Helper function to parse query string (e.g. ?param1=value&parm2=...).
- parseQueryString: function pdfViewParseQueryString(query) {
- var parts = query.split('&');
- var params = {};
- for (var i = 0, ii = parts.length; i < ii; ++i) {
- var param = parts[i].split('=');
- var key = param[0].toLowerCase();
- var value = param.length > 1 ? param[1] : null;
- params[decodeURIComponent(key)] = decodeURIComponent(value);
- }
- return params;
- },
-
beforePrint: function pdfViewSetupBeforePrint() {
if (!this.supportsPrinting) {
var printMessage = mozL10n.get('printing_not_supported', null,
'Warning: Printing is not fully supported by this browser.');
this.error(printMessage);
return;
}
var alertNotReady = false;
var i, ii;
- if (!this.pagesCount) {
+ if (!this.pdfDocument || !this.pagesCount) {
alertNotReady = true;
} else {
for (i = 0, ii = this.pagesCount; i < ii; ++i) {
if (!this.pdfViewer.getPageView(i).pdfPage) {
alertNotReady = true;
@@ -5592,572 +7046,142 @@
this.printing = true;
this.forceRendering();
var body = document.querySelector('body');
body.setAttribute('data-mozPrintCallback', true);
+
+ if (!this.hasEqualPageSizes) {
+ console.warn('Not all pages have the same size. The printed result ' +
+ 'may be incorrect!');
+ }
+
+ // Insert a @page + size rule to make sure that the page size is correctly
+ // set. Note that we assume that all pages have the same size, because
+ // variable-size pages are not supported yet (at least in Chrome & Firefox).
+ // TODO(robwu): Use named pages when size calculation bugs get resolved
+ // (e.g. https://crbug.com/355116) AND when support for named pages is
+ // added (http://www.w3.org/TR/css3-page/#using-named-pages).
+ // In browsers where @page + size is not supported (such as Firefox,
+ // https://bugzil.la/851441), the next stylesheet will be ignored and the
+ // user has to select the correct paper size in the UI if wanted.
+ this.pageStyleSheet = document.createElement('style');
+ var pageSize = this.pdfViewer.getPageView(0).pdfPage.getViewport(1);
+ this.pageStyleSheet.textContent =
+ // "size:<width> <height>" is what we need. But also add "A4" because
+ // Firefox incorrectly reports support for the other value.
+ '@supports ((size:A4) and (size:1pt 1pt)) {' +
+ '@page { size: ' + pageSize.width + 'pt ' + pageSize.height + 'pt;}' +
+ // The canvas and each ancestor node must have a height of 100% to make
+ // sure that each canvas is printed on exactly one page.
+ '#printContainer {height:100%}' +
+ '#printContainer > div {width:100% !important;height:100% !important;}' +
+ '}';
+ body.appendChild(this.pageStyleSheet);
+
for (i = 0, ii = this.pagesCount; i < ii; ++i) {
this.pdfViewer.getPageView(i).beforePrint();
}
},
+ // Whether all pages of the PDF have the same width and height.
+ get hasEqualPageSizes() {
+ var firstPage = this.pdfViewer.getPageView(0);
+ for (var i = 1, ii = this.pagesCount; i < ii; ++i) {
+ var pageView = this.pdfViewer.getPageView(i);
+ if (pageView.width !== firstPage.width ||
+ pageView.height !== firstPage.height) {
+ return false;
+ }
+ }
+ return true;
+ },
+
afterPrint: function pdfViewSetupAfterPrint() {
var div = document.getElementById('printContainer');
while (div.hasChildNodes()) {
div.removeChild(div.lastChild);
}
+ if (this.pageStyleSheet && this.pageStyleSheet.parentNode) {
+ this.pageStyleSheet.parentNode.removeChild(this.pageStyleSheet);
+ this.pageStyleSheet = null;
+ }
+
this.printing = false;
this.forceRendering();
},
- setScale: function (value, resetAutoSettings) {
- this.updateScaleControls = !!resetAutoSettings;
- this.pdfViewer.currentScaleValue = value;
- this.updateScaleControls = true;
- },
-
rotatePages: function pdfViewRotatePages(delta) {
var pageNumber = this.page;
-
this.pageRotation = (this.pageRotation + 360 + delta) % 360;
this.pdfViewer.pagesRotation = this.pageRotation;
this.pdfThumbnailViewer.pagesRotation = this.pageRotation;
this.forceRendering();
this.pdfViewer.scrollPageIntoView(pageNumber);
},
- /**
- * This function flips the page in presentation mode if the user scrolls up
- * or down with large enough motion and prevents page flipping too often.
- *
- * @this {PDFView}
- * @param {number} mouseScrollDelta The delta value from the mouse event.
- */
- mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
- var MOUSE_SCROLL_COOLDOWN_TIME = 50;
-
- var currentTime = (new Date()).getTime();
- var storedTime = this.mouseScrollTimeStamp;
-
- // In case one page has already been flipped there is a cooldown time
- // which has to expire before next page can be scrolled on to.
- if (currentTime > storedTime &&
- currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
+ requestPresentationMode: function pdfViewRequestPresentationMode() {
+ if (!this.pdfPresentationMode) {
return;
}
-
- // In case the user decides to scroll to the opposite direction than before
- // clear the accumulated delta.
- if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
- (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) {
- this.clearMouseScrollState();
- }
-
- this.mouseScrollDelta += mouseScrollDelta;
-
- var PAGE_FLIP_THRESHOLD = 120;
- if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
-
- var PageFlipDirection = {
- UP: -1,
- DOWN: 1
- };
-
- // In presentation mode scroll one page at a time.
- var pageFlipDirection = (this.mouseScrollDelta > 0) ?
- PageFlipDirection.UP :
- PageFlipDirection.DOWN;
- this.clearMouseScrollState();
- var currentPage = this.page;
-
- // In case we are already on the first or the last page there is no need
- // to do anything.
- if ((currentPage === 1 && pageFlipDirection === PageFlipDirection.UP) ||
- (currentPage === this.pagesCount &&
- pageFlipDirection === PageFlipDirection.DOWN)) {
- return;
- }
-
- this.page += pageFlipDirection;
- this.mouseScrollTimeStamp = currentTime;
- }
+ this.pdfPresentationMode.request();
},
/**
- * This function clears the member attributes used with mouse scrolling in
- * presentation mode.
- *
- * @this {PDFView}
+ * @param {number} delta - The delta value from the mouse event.
*/
- clearMouseScrollState: function pdfViewClearMouseScrollState() {
- this.mouseScrollTimeStamp = 0;
- this.mouseScrollDelta = 0;
+ scrollPresentationMode: function pdfViewScrollPresentationMode(delta) {
+ if (!this.pdfPresentationMode) {
+ return;
+ }
+ this.pdfPresentationMode.mouseScroll(delta);
}
};
window.PDFView = PDFViewerApplication; // obsolete name, using it as an alias
-var THUMBNAIL_SCROLL_MARGIN = -19;
-
-/**
- * @constructor
- * @param container
- * @param id
- * @param defaultViewport
- * @param linkService
- * @param renderingQueue
- * @param pageSource
- *
- * @implements {IRenderableView}
- */
-var ThumbnailView = function thumbnailView(container, id, defaultViewport,
- linkService, renderingQueue,
- pageSource) {
- var anchor = document.createElement('a');
- anchor.href = linkService.getAnchorUrl('#page=' + id);
- anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
- anchor.onclick = function stopNavigation() {
- linkService.page = id;
- return false;
- };
-
- this.pdfPage = undefined;
- this.viewport = defaultViewport;
- this.pdfPageRotate = defaultViewport.rotation;
-
- this.rotation = 0;
- this.pageWidth = this.viewport.width;
- this.pageHeight = this.viewport.height;
- this.pageRatio = this.pageWidth / this.pageHeight;
- this.id = id;
- this.renderingId = 'thumbnail' + id;
-
- this.canvasWidth = 98;
- this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
- this.scale = (this.canvasWidth / this.pageWidth);
-
- var div = this.el = document.createElement('div');
- div.id = 'thumbnailContainer' + id;
- div.className = 'thumbnail';
-
- if (id === 1) {
- // Highlight the thumbnail of the first page when no page number is
- // specified (or exists in cache) when the document is loaded.
- div.classList.add('selected');
- }
-
- var ring = document.createElement('div');
- ring.className = 'thumbnailSelectionRing';
- ring.style.width = this.canvasWidth + 'px';
- ring.style.height = this.canvasHeight + 'px';
-
- div.appendChild(ring);
- anchor.appendChild(div);
- container.appendChild(anchor);
-
- this.hasImage = false;
- this.renderingState = RenderingStates.INITIAL;
- this.renderingQueue = renderingQueue;
- this.pageSource = pageSource;
-
- this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
- this.pdfPage = pdfPage;
- this.pdfPageRotate = pdfPage.rotate;
- var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
- this.viewport = pdfPage.getViewport(1, totalRotation);
- this.update();
- };
-
- this.update = function thumbnailViewUpdate(rotation) {
- if (rotation !== undefined) {
- this.rotation = rotation;
- }
- var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
- this.viewport = this.viewport.clone({
- scale: 1,
- rotation: totalRotation
- });
- this.pageWidth = this.viewport.width;
- this.pageHeight = this.viewport.height;
- this.pageRatio = this.pageWidth / this.pageHeight;
-
- this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
- this.scale = (this.canvasWidth / this.pageWidth);
-
- div.removeAttribute('data-loaded');
- ring.textContent = '';
- ring.style.width = this.canvasWidth + 'px';
- ring.style.height = this.canvasHeight + 'px';
-
- this.hasImage = false;
- this.renderingState = RenderingStates.INITIAL;
- this.resume = null;
- };
-
- this.getPageDrawContext = function thumbnailViewGetPageDrawContext() {
- var canvas = document.createElement('canvas');
- canvas.id = 'thumbnail' + id;
-
- canvas.width = this.canvasWidth;
- canvas.height = this.canvasHeight;
- canvas.className = 'thumbnailImage';
- canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
- {page: id}, 'Thumbnail of Page {{page}}'));
-
- div.setAttribute('data-loaded', true);
-
- ring.appendChild(canvas);
-
- var ctx = canvas.getContext('2d');
- ctx.save();
- ctx.fillStyle = 'rgb(255, 255, 255)';
- ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
- ctx.restore();
- return ctx;
- };
-
- this.drawingRequired = function thumbnailViewDrawingRequired() {
- return !this.hasImage;
- };
-
- this.draw = function thumbnailViewDraw(callback) {
- if (!this.pdfPage) {
- var promise = this.pageSource.getPage(this.id);
- promise.then(function(pdfPage) {
- this.setPdfPage(pdfPage);
- this.draw(callback);
- }.bind(this));
+var HOSTED_VIEWER_ORIGINS = ['null',
+ 'http://mozilla.github.io', 'https://mozilla.github.io'];
+function validateFileURL(file) {
+ try {
+ var viewerOrigin = new URL(window.location.href).origin || 'null';
+ if (HOSTED_VIEWER_ORIGINS.indexOf(viewerOrigin) >= 0) {
+ // Hosted or local viewer, allow for any file locations
return;
}
-
- if (this.renderingState !== RenderingStates.INITIAL) {
- console.error('Must be in new state before drawing');
+ var fileOrigin = new URL(file, window.location.href).origin;
+ // Removing of the following line will not guarantee that the viewer will
+ // start accepting URLs from foreign origin -- CORS headers on the remote
+ // server must be properly configured.
+ if (fileOrigin !== viewerOrigin) {
+ throw new Error('file origin does not match viewer\'s');
}
+ } catch (e) {
+ var message = e && e.message;
+ var loadingErrorMessage = mozL10n.get('loading_error', null,
+ 'An error occurred while loading the PDF.');
- this.renderingState = RenderingStates.RUNNING;
- if (this.hasImage) {
- callback();
- return;
- }
-
- var self = this;
- var ctx = this.getPageDrawContext();
- var drawViewport = this.viewport.clone({ scale: this.scale });
- var renderContext = {
- canvasContext: ctx,
- viewport: drawViewport,
- continueCallback: function(cont) {
- if (!self.renderingQueue.isHighestPriority(self)) {
- self.renderingState = RenderingStates.PAUSED;
- self.resume = function() {
- self.renderingState = RenderingStates.RUNNING;
- cont();
- };
- return;
- }
- cont();
- }
+ var moreInfo = {
+ message: message
};
- this.pdfPage.render(renderContext).promise.then(
- function pdfPageRenderCallback() {
- self.renderingState = RenderingStates.FINISHED;
- callback();
- },
- function pdfPageRenderError(error) {
- self.renderingState = RenderingStates.FINISHED;
- callback();
- }
- );
- this.hasImage = true;
- };
-
- function getTempCanvas(width, height) {
- var tempCanvas = ThumbnailView.tempImageCache;
- if (!tempCanvas) {
- tempCanvas = document.createElement('canvas');
- ThumbnailView.tempImageCache = tempCanvas;
- }
- tempCanvas.width = width;
- tempCanvas.height = height;
- return tempCanvas;
+ PDFViewerApplication.error(loadingErrorMessage, moreInfo);
+ throw e;
}
+}
- this.setImage = function thumbnailViewSetImage(img) {
- if (!this.pdfPage) {
- var promise = this.pageSource.getPage();
- promise.then(function(pdfPage) {
- this.setPdfPage(pdfPage);
- this.setImage(img);
- }.bind(this));
- return;
- }
- if (this.hasImage || !img) {
- return;
- }
- this.renderingState = RenderingStates.FINISHED;
- var ctx = this.getPageDrawContext();
-
- var reducedImage = img;
- var reducedWidth = img.width;
- var reducedHeight = img.height;
-
- // drawImage does an awful job of rescaling the image, doing it gradually
- var MAX_SCALE_FACTOR = 2.0;
- if (Math.max(img.width / ctx.canvas.width,
- img.height / ctx.canvas.height) > MAX_SCALE_FACTOR) {
- reducedWidth >>= 1;
- reducedHeight >>= 1;
- reducedImage = getTempCanvas(reducedWidth, reducedHeight);
- var reducedImageCtx = reducedImage.getContext('2d');
- reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
- 0, 0, reducedWidth, reducedHeight);
- while (Math.max(reducedWidth / ctx.canvas.width,
- reducedHeight / ctx.canvas.height) > MAX_SCALE_FACTOR) {
- reducedImageCtx.drawImage(reducedImage,
- 0, 0, reducedWidth, reducedHeight,
- 0, 0, reducedWidth >> 1, reducedHeight >> 1);
- reducedWidth >>= 1;
- reducedHeight >>= 1;
- }
- }
-
- ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
- 0, 0, ctx.canvas.width, ctx.canvas.height);
-
- this.hasImage = true;
- };
-};
-
-ThumbnailView.tempImageCache = null;
-
-/**
- * @typedef {Object} PDFThumbnailViewerOptions
- * @property {HTMLDivElement} container - The container for the thumbs elements.
- * @property {IPDFLinkService} linkService - The navigation/linking service.
- * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
- */
-
-/**
- * Simple viewer control to display thumbs for pages.
- * @class
- */
-var PDFThumbnailViewer = (function pdfThumbnailViewer() {
- /**
- * @constructs
- * @param {PDFThumbnailViewerOptions} options
- */
- function PDFThumbnailViewer(options) {
- this.container = options.container;
- this.renderingQueue = options.renderingQueue;
- this.linkService = options.linkService;
-
- this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this));
- this._resetView();
- }
-
- PDFThumbnailViewer.prototype = {
- _scrollUpdated: function PDFThumbnailViewer_scrollUpdated() {
- this.renderingQueue.renderHighestPriority();
- },
-
- getThumbnail: function PDFThumbnailViewer_getThumbnail(index) {
- return this.thumbnails[index];
- },
-
- _getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() {
- return getVisibleElements(this.container, this.thumbnails);
- },
-
- scrollThumbnailIntoView: function (page) {
- var selected = document.querySelector('.thumbnail.selected');
- if (selected) {
- selected.classList.remove('selected');
- }
- var thumbnail = document.getElementById('thumbnailContainer' + page);
- thumbnail.classList.add('selected');
- var visibleThumbs = this._getVisibleThumbs();
- var numVisibleThumbs = visibleThumbs.views.length;
-
- // If the thumbnail isn't currently visible, scroll it into view.
- if (numVisibleThumbs > 0) {
- var first = visibleThumbs.first.id;
- // Account for only one thumbnail being visible.
- var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
- if (page <= first || page >= last) {
- scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
- }
- }
- },
-
- get pagesRotation() {
- return this._pagesRotation;
- },
-
- set pagesRotation(rotation) {
- this._pagesRotation = rotation;
- for (var i = 0, l = this.thumbnails.length; i < l; i++) {
- var thumb = this.thumbnails[i];
- thumb.update(rotation);
- }
- },
-
- cleanup: function PDFThumbnailViewer_cleanup() {
- ThumbnailView.tempImageCache = null;
- },
-
- _resetView: function () {
- this.thumbnails = [];
- this._pagesRotation = 0;
- },
-
- setDocument: function (pdfDocument) {
- if (this.pdfDocument) {
- // cleanup of the elements and views
- var thumbsView = this.container;
- while (thumbsView.hasChildNodes()) {
- thumbsView.removeChild(thumbsView.lastChild);
- }
- this._resetView();
- }
-
- this.pdfDocument = pdfDocument;
- if (!pdfDocument) {
- return Promise.resolve();
- }
-
- return pdfDocument.getPage(1).then(function (firstPage) {
- var pagesCount = pdfDocument.numPages;
- var viewport = firstPage.getViewport(1.0);
- for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
- var pageSource = new PDFPageSource(pdfDocument, pageNum);
- var thumbnail = new ThumbnailView(this.container, pageNum,
- viewport.clone(), this.linkService,
- this.renderingQueue, pageSource);
- this.thumbnails.push(thumbnail);
- }
- }.bind(this));
- },
-
- ensureThumbnailVisible:
- function PDFThumbnailViewer_ensureThumbnailVisible(page) {
- // Ensure that the thumbnail of the current page is visible
- // when switching from another view.
- scrollIntoView(document.getElementById('thumbnailContainer' + page));
- },
-
- forceRendering: function () {
- var visibleThumbs = this._getVisibleThumbs();
- var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs,
- this.thumbnails,
- this.scroll.down);
- if (thumbView) {
- this.renderingQueue.renderView(thumbView);
- return true;
- }
- return false;
- }
- };
-
- return PDFThumbnailViewer;
-})();
-
-
-var DocumentOutlineView = function documentOutlineView(options) {
- var outline = options.outline;
- var outlineView = options.outlineView;
- while (outlineView.firstChild) {
- outlineView.removeChild(outlineView.firstChild);
- }
-
- if (!outline) {
- return;
- }
-
- var linkService = options.linkService;
-
- function bindItemLink(domObj, item) {
- domObj.href = linkService.getDestinationHash(item.dest);
- domObj.onclick = function documentOutlineViewOnclick(e) {
- linkService.navigateTo(item.dest);
- return false;
- };
- }
-
- var queue = [{parent: outlineView, items: outline}];
- while (queue.length > 0) {
- var levelData = queue.shift();
- var i, n = levelData.items.length;
- for (i = 0; i < n; i++) {
- var item = levelData.items[i];
- var div = document.createElement('div');
- div.className = 'outlineItem';
- var a = document.createElement('a');
- bindItemLink(a, item);
- a.textContent = item.title;
- div.appendChild(a);
-
- if (item.items.length > 0) {
- var itemsDiv = document.createElement('div');
- itemsDiv.className = 'outlineItems';
- div.appendChild(itemsDiv);
- queue.push({parent: itemsDiv, items: item.items});
- }
-
- levelData.parent.appendChild(div);
- }
- }
-};
-
-
-var DocumentAttachmentsView = function documentAttachmentsView(options) {
- var attachments = options.attachments;
- var attachmentsView = options.attachmentsView;
- while (attachmentsView.firstChild) {
- attachmentsView.removeChild(attachmentsView.firstChild);
- }
-
- if (!attachments) {
- return;
- }
-
- function bindItemLink(domObj, item) {
- domObj.onclick = function documentAttachmentsViewOnclick(e) {
- var downloadManager = new DownloadManager();
- downloadManager.downloadData(item.content, getFileName(item.filename),
- '');
- return false;
- };
- }
-
- var names = Object.keys(attachments).sort(function(a,b) {
- return a.toLowerCase().localeCompare(b.toLowerCase());
- });
- for (var i = 0, ii = names.length; i < ii; i++) {
- var item = attachments[names[i]];
- var div = document.createElement('div');
- div.className = 'attachmentsItem';
- var button = document.createElement('button');
- bindItemLink(button, item);
- button.textContent = getFileName(item.filename);
- div.appendChild(button);
- attachmentsView.appendChild(div);
- }
-};
-
-
-
function webViewerLoad(evt) {
PDFViewerApplication.initialize().then(webViewerInitialized);
}
function webViewerInitialized() {
var queryString = document.location.search.substring(1);
- var params = PDFViewerApplication.parseQueryString(queryString);
+ var params = parseQueryString(queryString);
var file = 'file' in params ? params.file : DEFAULT_URL;
+ validateFileURL(file);
var fileInput = document.createElement('input');
fileInput.id = 'fileInput';
fileInput.className = 'fileInput';
fileInput.setAttribute('type', 'file');
@@ -6174,11 +7198,11 @@
var locale = PDFJS.locale || navigator.language;
if (PDFViewerApplication.preferencePdfBugEnabled) {
// Special debugging flags in the hash section of the URL.
var hash = document.location.hash.substring(1);
- var hashParams = PDFViewerApplication.parseQueryString(hash);
+ var hashParams = parseQueryString(hash);
if ('disableworker' in hashParams) {
PDFJS.disableWorker = (hashParams['disableworker'] === 'true');
}
if ('disablerange' in hashParams) {
@@ -6249,14 +7273,10 @@
if (PDFViewerApplication.supportsIntegratedFind) {
document.getElementById('viewFind').classList.add('hidden');
}
- // Listen for unsupported features to trigger the fallback UI.
- PDFJS.UnsupportedManager.listen(
- PDFViewerApplication.fallback.bind(PDFViewerApplication));
-
// Suppress context menus for some controls
document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler;
var mainContainer = document.getElementById('mainContainer');
var outerContainer = document.getElementById('outerContainer');
@@ -6274,10 +7294,13 @@
this.classList.toggle('toggled');
outerContainer.classList.add('sidebarMoving');
outerContainer.classList.toggle('sidebarOpen');
PDFViewerApplication.sidebarOpen =
outerContainer.classList.contains('sidebarOpen');
+ if (PDFViewerApplication.sidebarOpen) {
+ PDFViewerApplication.refreshThumbnailViewer();
+ }
PDFViewerApplication.forceRendering();
});
document.getElementById('viewThumbnail').addEventListener('click',
function() {
@@ -6287,10 +7310,15 @@
document.getElementById('viewOutline').addEventListener('click',
function() {
PDFViewerApplication.switchSidebarView('outline');
});
+ document.getElementById('viewOutline').addEventListener('dblclick',
+ function() {
+ PDFViewerApplication.outline.toggleOutlineTree();
+ });
+
document.getElementById('viewAttachments').addEventListener('click',
function() {
PDFViewerApplication.switchSidebarView('attachments');
});
@@ -6325,14 +7353,16 @@
if (this.value !== (this.value | 0).toString()) {
this.value = PDFViewerApplication.page;
}
});
- document.getElementById('scaleSelect').addEventListener('change',
- function() {
- PDFViewerApplication.setScale(this.value, false);
- });
+ document.getElementById('scaleSelect').addEventListener('change', function() {
+ if (this.value === 'custom') {
+ return;
+ }
+ PDFViewerApplication.pdfViewer.currentScaleValue = this.value;
+ });
document.getElementById('presentationMode').addEventListener('click',
SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar));
document.getElementById('openFile').addEventListener('click',
@@ -6350,11 +7380,11 @@
// cannot load file:-URLs in a Web Worker. file:-URLs are usually loaded
// very quickly, so there is no need to set up progress event listeners.
PDFViewerApplication.setTitleUsingUrl(file);
var xhr = new XMLHttpRequest();
xhr.onload = function() {
- PDFViewerApplication.open(new Uint8Array(xhr.response), 0);
+ PDFViewerApplication.open(new Uint8Array(xhr.response));
};
try {
xhr.open('GET', file);
xhr.responseType = 'arraybuffer';
xhr.send();
@@ -6364,60 +7394,109 @@
}
return;
}
if (file) {
- PDFViewerApplication.open(file, 0);
+ PDFViewerApplication.open(file);
}
}
document.addEventListener('DOMContentLoaded', webViewerLoad, true);
document.addEventListener('pagerendered', function (e) {
- var pageIndex = e.detail.pageNumber - 1;
+ var pageNumber = e.detail.pageNumber;
+ var pageIndex = pageNumber - 1;
var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
- var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
- getThumbnail(pageIndex);
- thumbnailView.setImage(pageView.canvas);
+ if (PDFViewerApplication.sidebarOpen) {
+ var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
+ getThumbnail(pageIndex);
+ thumbnailView.setImage(pageView);
+ }
+ if (PDFJS.pdfBug && Stats.enabled && pageView.stats) {
+ Stats.add(pageNumber, pageView.stats);
+ }
+
if (pageView.error) {
PDFViewerApplication.error(mozL10n.get('rendering_error', null,
'An error occurred while rendering the page.'), pageView.error);
}
-
// If the page is still visible when it has finished rendering,
// ensure that the page number input loading indicator is hidden.
- if ((pageIndex + 1) === PDFViewerApplication.page) {
+ if (pageNumber === PDFViewerApplication.page) {
var pageNumberInput = document.getElementById('pageNumber');
pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
}
+
}, true);
+document.addEventListener('textlayerrendered', function (e) {
+ var pageIndex = e.detail.pageNumber - 1;
+ var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
+
+}, true);
+
+document.addEventListener('pagemode', function (evt) {
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ // Handle the 'pagemode' hash parameter, see also `PDFLinkService_setHash`.
+ var mode = evt.detail.mode;
+ switch (mode) {
+ case 'bookmarks':
+ // Note: Our code calls this property 'outline', even though the
+ // Open Parameter specification calls it 'bookmarks'.
+ mode = 'outline';
+ /* falls through */
+ case 'thumbs':
+ case 'attachments':
+ PDFViewerApplication.switchSidebarView(mode, true);
+ break;
+ case 'none':
+ if (PDFViewerApplication.sidebarOpen) {
+ document.getElementById('sidebarToggle').click();
+ }
+ break;
+ }
+}, true);
+
+document.addEventListener('namedaction', function (e) {
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ // Processing couple of named actions that might be useful.
+ // See also PDFLinkService.executeNamedAction
+ var action = e.detail.action;
+ switch (action) {
+ case 'GoToPage':
+ document.getElementById('pageNumber').focus();
+ break;
+
+ case 'Find':
+ if (!PDFViewerApplication.supportsIntegratedFind) {
+ PDFViewerApplication.findBar.toggle();
+ }
+ break;
+ }
+}, true);
+
window.addEventListener('presentationmodechanged', function (e) {
var active = e.detail.active;
var switchInProgress = e.detail.switchInProgress;
PDFViewerApplication.pdfViewer.presentationModeState =
switchInProgress ? PresentationModeState.CHANGING :
active ? PresentationModeState.FULLSCREEN : PresentationModeState.NORMAL;
});
-function updateViewarea() {
+window.addEventListener('updateviewarea', function (evt) {
if (!PDFViewerApplication.initialized) {
return;
}
- PDFViewerApplication.pdfViewer.update();
-}
+ var location = evt.location;
-window.addEventListener('updateviewarea', function () {
- if (!PDFViewerApplication.initialized) {
- return;
- }
-
- var location = PDFViewerApplication.pdfViewer.location;
-
PDFViewerApplication.store.initializedPromise.then(function() {
PDFViewerApplication.store.setMultiple({
'exists': true,
'page': location.pageNumber,
'zoom': location.scale,
@@ -6425,16 +7504,18 @@
'scrollTop': location.top
}).catch(function() {
// unable to write to storage
});
});
- var href = PDFViewerApplication.getAnchorUrl(location.pdfOpenParams);
+ var href =
+ PDFViewerApplication.pdfLinkService.getAnchorUrl(location.pdfOpenParams);
document.getElementById('viewBookmark').href = href;
document.getElementById('secondaryViewBookmark').href = href;
// Update the current bookmark in the browsing history.
- PDFHistory.updateCurrentBookmark(location.pdfOpenParams, location.pageNumber);
+ PDFViewerApplication.pdfHistory.updateCurrentBookmark(location.pdfOpenParams,
+ location.pageNumber);
// Show/hide the loading indicator in the page number input element.
var pageNumberInput = document.getElementById('pageNumber');
var currentPage =
PDFViewerApplication.pdfViewer.getPageView(PDFViewerApplication.page - 1);
@@ -6445,26 +7526,41 @@
pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR);
}
}, true);
window.addEventListener('resize', function webViewerResize(evt) {
- if (PDFViewerApplication.initialized &&
- (document.getElementById('pageWidthOption').selected ||
- document.getElementById('pageFitOption').selected ||
- document.getElementById('pageAutoOption').selected)) {
- var selectedScale = document.getElementById('scaleSelect').value;
- PDFViewerApplication.setScale(selectedScale, false);
+ if (PDFViewerApplication.initialized) {
+ var currentScaleValue = PDFViewerApplication.pdfViewer.currentScaleValue;
+ if (currentScaleValue === 'auto' ||
+ currentScaleValue === 'page-fit' ||
+ currentScaleValue === 'page-width') {
+ // Note: the scale is constant for 'page-actual'.
+ PDFViewerApplication.pdfViewer.currentScaleValue = currentScaleValue;
+ } else if (!currentScaleValue) {
+ // Normally this shouldn't happen, but if the scale wasn't initialized
+ // we set it to the default value in order to prevent any issues.
+ // (E.g. the document being rendered with the wrong scale on load.)
+ PDFViewerApplication.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
+ }
+ PDFViewerApplication.pdfViewer.update();
}
- updateViewarea();
// Set the 'max-height' CSS property of the secondary toolbar.
SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
});
window.addEventListener('hashchange', function webViewerHashchange(evt) {
- if (PDFHistory.isHashChangeUnlocked) {
- PDFViewerApplication.setHash(document.location.hash.substring(1));
+ if (PDFViewerApplication.pdfHistory.isHashChangeUnlocked) {
+ var hash = document.location.hash.substring(1);
+ if (!hash) {
+ return;
+ }
+ if (!PDFViewerApplication.isInitialViewSet) {
+ PDFViewerApplication.initialBookmark = hash;
+ } else {
+ PDFViewerApplication.pdfLinkService.setHash(hash);
+ }
}
});
window.addEventListener('change', function webViewerChange(evt) {
var files = evt.target.files;
@@ -6473,18 +7569,18 @@
}
var file = files[0];
if (!PDFJS.disableCreateObjectURL &&
typeof URL !== 'undefined' && URL.createObjectURL) {
- PDFViewerApplication.open(URL.createObjectURL(file), 0);
+ PDFViewerApplication.open(URL.createObjectURL(file));
} else {
// Read the local file into a Uint8Array.
var fileReader = new FileReader();
fileReader.onload = function webViewerChangeFileReaderOnload(evt) {
var buffer = evt.target.result;
var uint8Array = new Uint8Array(buffer);
- PDFViewerApplication.open(uint8Array, 0);
+ PDFViewerApplication.open(uint8Array);
};
fileReader.readAsArrayBuffer(file);
}
PDFViewerApplication.setTitleUsingUrl(file.name);
@@ -6498,11 +7594,11 @@
}, true);
function selectScaleOption(value) {
var options = document.getElementById('scaleSelect').options;
var predefinedValueFound = false;
- for (var i = 0; i < options.length; i++) {
+ for (var i = 0, ii = options.length; i < ii; i++) {
var option = options[i];
if (option.value !== value) {
option.selected = false;
continue;
}
@@ -6540,88 +7636,97 @@
window.addEventListener('scalechange', function scalechange(evt) {
document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE);
document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE);
- var customScaleOption = document.getElementById('customScaleOption');
- customScaleOption.selected = false;
-
- if (!PDFViewerApplication.updateScaleControls &&
- (document.getElementById('pageWidthOption').selected ||
- document.getElementById('pageFitOption').selected ||
- document.getElementById('pageAutoOption').selected)) {
- updateViewarea();
- return;
- }
-
- if (evt.presetValue) {
- selectScaleOption(evt.presetValue);
- updateViewarea();
- return;
- }
-
- var predefinedValueFound = selectScaleOption('' + evt.scale);
+ // Update the 'scaleSelect' DOM element.
+ var predefinedValueFound = selectScaleOption(evt.presetValue ||
+ '' + evt.scale);
if (!predefinedValueFound) {
- customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
+ var customScaleOption = document.getElementById('customScaleOption');
+ var customScale = Math.round(evt.scale * 10000) / 100;
+ customScaleOption.textContent =
+ mozL10n.get('page_scale_percent', { scale: customScale }, '{{scale}}%');
customScaleOption.selected = true;
}
- updateViewarea();
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ PDFViewerApplication.pdfViewer.update();
}, true);
window.addEventListener('pagechange', function pagechange(evt) {
var page = evt.pageNumber;
if (evt.previousPageNumber !== page) {
document.getElementById('pageNumber').value = page;
- PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
+ if (PDFViewerApplication.sidebarOpen) {
+ PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
+ }
}
var numPages = PDFViewerApplication.pagesCount;
document.getElementById('previous').disabled = (page <= 1);
document.getElementById('next').disabled = (page >= numPages);
document.getElementById('firstPage').disabled = (page <= 1);
document.getElementById('lastPage').disabled = (page >= numPages);
- // checking if the this.page was called from the updateViewarea function
- if (evt.updateInProgress) {
- return;
+ // we need to update stats
+ if (PDFJS.pdfBug && Stats.enabled) {
+ var pageView = PDFViewerApplication.pdfViewer.getPageView(page - 1);
+ if (pageView.stats) {
+ Stats.add(page, pageView.stats);
+ }
}
- // Avoid scrolling the first page during loading
- if (this.loading && page === 1) {
- return;
- }
- PDFViewerApplication.pdfViewer.scrollPageIntoView(page);
}, true);
function handleMouseWheel(evt) {
var MOUSE_WHEEL_DELTA_FACTOR = 40;
var ticks = (evt.type === 'DOMMouseScroll') ? -evt.detail :
evt.wheelDelta / MOUSE_WHEEL_DELTA_FACTOR;
var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn';
- if (PresentationMode.active) {
+ var pdfViewer = PDFViewerApplication.pdfViewer;
+ if (pdfViewer.isInPresentationMode) {
evt.preventDefault();
- PDFViewerApplication.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR);
- } else if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer
+ PDFViewerApplication.scrollPresentationMode(ticks *
+ MOUSE_WHEEL_DELTA_FACTOR);
+ } else if (evt.ctrlKey || evt.metaKey) {
+ var support = PDFViewerApplication.supportedMouseWheelZoomModifierKeys;
+ if ((evt.ctrlKey && !support.ctrlKey) ||
+ (evt.metaKey && !support.metaKey)) {
+ return;
+ }
+ // Only zoom the pages, not the entire viewer.
evt.preventDefault();
+
+ var previousScale = pdfViewer.currentScale;
+
PDFViewerApplication[direction](Math.abs(ticks));
+
+ var currentScale = pdfViewer.currentScale;
+ if (previousScale !== currentScale) {
+ // After scaling the page via zoomIn/zoomOut, the position of the upper-
+ // left corner is restored. When the mouse wheel is used, the position
+ // under the cursor should be restored instead.
+ var scaleCorrectionFactor = currentScale / previousScale - 1;
+ var rect = pdfViewer.container.getBoundingClientRect();
+ var dx = evt.clientX - rect.left;
+ var dy = evt.clientY - rect.top;
+ pdfViewer.container.scrollLeft += dx * scaleCorrectionFactor;
+ pdfViewer.container.scrollTop += dy * scaleCorrectionFactor;
+ }
}
}
window.addEventListener('DOMMouseScroll', handleMouseWheel);
window.addEventListener('mousewheel', handleMouseWheel);
window.addEventListener('click', function click(evt) {
- if (!PresentationMode.active) {
- if (SecondaryToolbar.opened &&
+ if (SecondaryToolbar.opened &&
PDFViewerApplication.pdfViewer.containsElement(evt.target)) {
- SecondaryToolbar.close();
- }
- } else if (evt.button === 0) {
- // Necessary since preventDefault() in 'mousedown' won't stop
- // the event propagation in all circumstances in presentation mode.
- evt.preventDefault();
+ SecondaryToolbar.close();
}
}, false);
window.addEventListener('keydown', function keydown(evt) {
if (OverlayManager.active) {
@@ -6632,19 +7737,17 @@
var cmd = (evt.ctrlKey ? 1 : 0) |
(evt.altKey ? 2 : 0) |
(evt.shiftKey ? 4 : 0) |
(evt.metaKey ? 8 : 0);
+ var pdfViewer = PDFViewerApplication.pdfViewer;
+ var isViewerInPresentationMode = pdfViewer && pdfViewer.isInPresentationMode;
+
// First, handle the key bindings that are independent whether an input
// control is selected or not.
if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
// either CTRL or META key with optional SHIFT.
- var pdfViewer = PDFViewerApplication.pdfViewer;
- var inPresentationMode =
- pdfViewer.presentationModeState === PresentationModeState.CHANGING ||
- pdfViewer.presentationModeState === PresentationModeState.FULLSCREEN;
-
switch (evt.keyCode) {
case 70: // f
if (!PDFViewerApplication.supportsIntegratedFind) {
PDFViewerApplication.findBar.open();
handled = true;
@@ -6659,30 +7762,30 @@
break;
case 61: // FF/Mac '='
case 107: // FF '+' and '='
case 187: // Chrome '+'
case 171: // FF with German keyboard
- if (!inPresentationMode) {
+ if (!isViewerInPresentationMode) {
PDFViewerApplication.zoomIn();
}
handled = true;
break;
case 173: // FF/Mac '-'
case 109: // FF '-'
case 189: // Chrome '-'
- if (!inPresentationMode) {
+ if (!isViewerInPresentationMode) {
PDFViewerApplication.zoomOut();
}
handled = true;
break;
case 48: // '0'
case 96: // '0' on Numpad of Swedish keyboard
- if (!inPresentationMode) {
+ if (!isViewerInPresentationMode) {
// keeping it unhandled (to restore page zoom to 100%)
setTimeout(function () {
// ... and resetting the scale after browser adjusts its scale
- PDFViewerApplication.setScale(DEFAULT_SCALE, true);
+ pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
});
handled = false;
}
break;
}
@@ -6700,11 +7803,11 @@
// CTRL+ALT or Option+Command
if (cmd === 3 || cmd === 10) {
switch (evt.keyCode) {
case 80: // p
- SecondaryToolbar.presentationModeClick();
+ PDFViewerApplication.requestPresentationMode();
handled = true;
break;
case 71: // g
// focuses input#pageNumber field
document.getElementById('pageNumber').select();
@@ -6728,25 +7831,26 @@
// Make sure that the secondary toolbar is closed when Escape is pressed.
if (evt.keyCode !== 27) { // 'Esc'
return;
}
}
+ var ensureViewerFocused = false;
if (cmd === 0) { // no control key pressed at all.
switch (evt.keyCode) {
case 38: // up arrow
case 33: // pg up
case 8: // backspace
- if (!PresentationMode.active &&
- PDFViewerApplication.currentScaleValue !== 'page-fit') {
+ if (!isViewerInPresentationMode &&
+ pdfViewer.currentScaleValue !== 'page-fit') {
break;
}
/* in presentation mode */
/* falls through */
case 37: // left arrow
// horizontal scrolling using arrow keys
- if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) {
+ if (pdfViewer.isHorizontalScrollbarEnabled) {
break;
}
/* falls through */
case 75: // 'k'
case 80: // 'p'
@@ -6765,43 +7869,45 @@
}
break;
case 40: // down arrow
case 34: // pg down
case 32: // spacebar
- if (!PresentationMode.active &&
- PDFViewerApplication.currentScaleValue !== 'page-fit') {
+ if (!isViewerInPresentationMode &&
+ pdfViewer.currentScaleValue !== 'page-fit') {
break;
}
/* falls through */
case 39: // right arrow
// horizontal scrolling using arrow keys
- if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) {
+ if (pdfViewer.isHorizontalScrollbarEnabled) {
break;
}
/* falls through */
case 74: // 'j'
case 78: // 'n'
PDFViewerApplication.page++;
handled = true;
break;
case 36: // home
- if (PresentationMode.active || PDFViewerApplication.page > 1) {
+ if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
PDFViewerApplication.page = 1;
handled = true;
+ ensureViewerFocused = true;
}
break;
case 35: // end
- if (PresentationMode.active || (PDFViewerApplication.pdfDocument &&
+ if (isViewerInPresentationMode || (PDFViewerApplication.pdfDocument &&
PDFViewerApplication.page < PDFViewerApplication.pagesCount)) {
PDFViewerApplication.page = PDFViewerApplication.pagesCount;
handled = true;
+ ensureViewerFocused = true;
}
break;
case 72: // 'h'
- if (!PresentationMode.active) {
+ if (!isViewerInPresentationMode) {
HandTool.toggle();
}
break;
case 82: // 'r'
PDFViewerApplication.rotatePages(90);
@@ -6810,12 +7916,12 @@
}
if (cmd === 4) { // shift-key
switch (evt.keyCode) {
case 32: // spacebar
- if (!PresentationMode.active &&
- PDFViewerApplication.currentScaleValue !== 'page-fit') {
+ if (!isViewerInPresentationMode &&
+ pdfViewer.currentScaleValue !== 'page-fit') {
break;
}
PDFViewerApplication.page--;
handled = true;
break;
@@ -6824,47 +7930,45 @@
PDFViewerApplication.rotatePages(-90);
break;
}
}
- if (!handled && !PresentationMode.active) {
+ if (!handled && !isViewerInPresentationMode) {
// 33=Page Up 34=Page Down 35=End 36=Home
// 37=Left 38=Up 39=Right 40=Down
- if (evt.keyCode >= 33 && evt.keyCode <= 40 &&
- !PDFViewerApplication.pdfViewer.containsElement(curElement)) {
- // The page container is not focused, but a page navigation key has been
- // pressed. Change the focus to the viewer container to make sure that
- // navigation by keyboard works as expected.
- PDFViewerApplication.pdfViewer.focus();
- }
// 32=Spacebar
- if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
- if (!PDFViewerApplication.pdfViewer.containsElement(curElement)) {
- PDFViewerApplication.pdfViewer.focus();
- }
+ if ((evt.keyCode >= 33 && evt.keyCode <= 40) ||
+ (evt.keyCode === 32 && curElementTagName !== 'BUTTON')) {
+ ensureViewerFocused = true;
}
}
if (cmd === 2) { // alt-key
switch (evt.keyCode) {
case 37: // left arrow
- if (PresentationMode.active) {
- PDFHistory.back();
+ if (isViewerInPresentationMode) {
+ PDFViewerApplication.pdfHistory.back();
handled = true;
}
break;
case 39: // right arrow
- if (PresentationMode.active) {
- PDFHistory.forward();
+ if (isViewerInPresentationMode) {
+ PDFViewerApplication.pdfHistory.forward();
handled = true;
}
break;
}
}
+ if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) {
+ // The page container is not focused, but a page navigation key has been
+ // pressed. Change the focus to the viewer container to make sure that
+ // navigation by keyboard works as expected.
+ pdfViewer.focus();
+ }
+
if (handled) {
evt.preventDefault();
- PDFViewerApplication.clearMouseScrollState();
}
});
window.addEventListener('beforeprint', function beforePrint(evt) {
PDFViewerApplication.beforePrint();