1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/extensions/pdfjs/content/web/viewer.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,5805 @@ 1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 1.6 +/* Copyright 2012 Mozilla Foundation 1.7 + * 1.8 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.9 + * you may not use this file except in compliance with the License. 1.10 + * You may obtain a copy of the License at 1.11 + * 1.12 + * http://www.apache.org/licenses/LICENSE-2.0 1.13 + * 1.14 + * Unless required by applicable law or agreed to in writing, software 1.15 + * distributed under the License is distributed on an "AS IS" BASIS, 1.16 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.17 + * See the License for the specific language governing permissions and 1.18 + * limitations under the License. 1.19 + */ 1.20 +/* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar, CustomStyle, 1.21 + PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager, 1.22 + getFileName, scrollIntoView, getPDFFileNameFromURL, PDFHistory, 1.23 + Preferences, ViewHistory, PageView, ThumbnailView, URL, 1.24 + noContextMenuHandler, SecondaryToolbar, PasswordPrompt, 1.25 + PresentationMode, HandTool, Promise, DocumentProperties */ 1.26 + 1.27 +'use strict'; 1.28 + 1.29 +var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf'; 1.30 +var DEFAULT_SCALE = 'auto'; 1.31 +var DEFAULT_SCALE_DELTA = 1.1; 1.32 +var UNKNOWN_SCALE = 0; 1.33 +var CACHE_SIZE = 20; 1.34 +var CSS_UNITS = 96.0 / 72.0; 1.35 +var SCROLLBAR_PADDING = 40; 1.36 +var VERTICAL_PADDING = 5; 1.37 +var MAX_AUTO_SCALE = 1.25; 1.38 +var MIN_SCALE = 0.25; 1.39 +var MAX_SCALE = 4.0; 1.40 +var VIEW_HISTORY_MEMORY = 20; 1.41 +var SCALE_SELECT_CONTAINER_PADDING = 8; 1.42 +var SCALE_SELECT_PADDING = 22; 1.43 +var THUMBNAIL_SCROLL_MARGIN = -19; 1.44 +var USE_ONLY_CSS_ZOOM = false; 1.45 +var CLEANUP_TIMEOUT = 30000; 1.46 +var IGNORE_CURRENT_POSITION_ON_ZOOM = false; 1.47 +var RenderingStates = { 1.48 + INITIAL: 0, 1.49 + RUNNING: 1, 1.50 + PAUSED: 2, 1.51 + FINISHED: 3 1.52 +}; 1.53 +var FindStates = { 1.54 + FIND_FOUND: 0, 1.55 + FIND_NOTFOUND: 1, 1.56 + FIND_WRAPPED: 2, 1.57 + FIND_PENDING: 3 1.58 +}; 1.59 + 1.60 +PDFJS.imageResourcesPath = './images/'; 1.61 + PDFJS.workerSrc = '../build/pdf.worker.js'; 1.62 + PDFJS.cMapUrl = '../web/cmaps/'; 1.63 + PDFJS.cMapPacked = true; 1.64 + 1.65 +var mozL10n = document.mozL10n || document.webL10n; 1.66 + 1.67 + 1.68 +// optimised CSS custom property getter/setter 1.69 +var CustomStyle = (function CustomStyleClosure() { 1.70 + 1.71 + // As noted on: http://www.zachstronaut.com/posts/2009/02/17/ 1.72 + // animate-css-transforms-firefox-webkit.html 1.73 + // in some versions of IE9 it is critical that ms appear in this list 1.74 + // before Moz 1.75 + var prefixes = ['ms', 'Moz', 'Webkit', 'O']; 1.76 + var _cache = {}; 1.77 + 1.78 + function CustomStyle() {} 1.79 + 1.80 + CustomStyle.getProp = function get(propName, element) { 1.81 + // check cache only when no element is given 1.82 + if (arguments.length == 1 && typeof _cache[propName] == 'string') { 1.83 + return _cache[propName]; 1.84 + } 1.85 + 1.86 + element = element || document.documentElement; 1.87 + var style = element.style, prefixed, uPropName; 1.88 + 1.89 + // test standard property first 1.90 + if (typeof style[propName] == 'string') { 1.91 + return (_cache[propName] = propName); 1.92 + } 1.93 + 1.94 + // capitalize 1.95 + uPropName = propName.charAt(0).toUpperCase() + propName.slice(1); 1.96 + 1.97 + // test vendor specific properties 1.98 + for (var i = 0, l = prefixes.length; i < l; i++) { 1.99 + prefixed = prefixes[i] + uPropName; 1.100 + if (typeof style[prefixed] == 'string') { 1.101 + return (_cache[propName] = prefixed); 1.102 + } 1.103 + } 1.104 + 1.105 + //if all fails then set to undefined 1.106 + return (_cache[propName] = 'undefined'); 1.107 + }; 1.108 + 1.109 + CustomStyle.setProp = function set(propName, element, str) { 1.110 + var prop = this.getProp(propName); 1.111 + if (prop != 'undefined') { 1.112 + element.style[prop] = str; 1.113 + } 1.114 + }; 1.115 + 1.116 + return CustomStyle; 1.117 +})(); 1.118 + 1.119 +function getFileName(url) { 1.120 + var anchor = url.indexOf('#'); 1.121 + var query = url.indexOf('?'); 1.122 + var end = Math.min( 1.123 + anchor > 0 ? anchor : url.length, 1.124 + query > 0 ? query : url.length); 1.125 + return url.substring(url.lastIndexOf('/', end) + 1, end); 1.126 +} 1.127 + 1.128 +/** 1.129 + * Returns scale factor for the canvas. It makes sense for the HiDPI displays. 1.130 + * @return {Object} The object with horizontal (sx) and vertical (sy) 1.131 + scales. The scaled property is set to false if scaling is 1.132 + not required, true otherwise. 1.133 + */ 1.134 +function getOutputScale(ctx) { 1.135 + var devicePixelRatio = window.devicePixelRatio || 1; 1.136 + var backingStoreRatio = ctx.webkitBackingStorePixelRatio || 1.137 + ctx.mozBackingStorePixelRatio || 1.138 + ctx.msBackingStorePixelRatio || 1.139 + ctx.oBackingStorePixelRatio || 1.140 + ctx.backingStorePixelRatio || 1; 1.141 + var pixelRatio = devicePixelRatio / backingStoreRatio; 1.142 + return { 1.143 + sx: pixelRatio, 1.144 + sy: pixelRatio, 1.145 + scaled: pixelRatio != 1 1.146 + }; 1.147 +} 1.148 + 1.149 +/** 1.150 + * Scrolls specified element into view of its parent. 1.151 + * element {Object} The element to be visible. 1.152 + * spot {Object} An object with optional top and left properties, 1.153 + * specifying the offset from the top left edge. 1.154 + */ 1.155 +function scrollIntoView(element, spot) { 1.156 + // Assuming offsetParent is available (it's not available when viewer is in 1.157 + // hidden iframe or object). We have to scroll: if the offsetParent is not set 1.158 + // producing the error. See also animationStartedClosure. 1.159 + var parent = element.offsetParent; 1.160 + var offsetY = element.offsetTop + element.clientTop; 1.161 + var offsetX = element.offsetLeft + element.clientLeft; 1.162 + if (!parent) { 1.163 + console.error('offsetParent is not set -- cannot scroll'); 1.164 + return; 1.165 + } 1.166 + while (parent.clientHeight === parent.scrollHeight) { 1.167 + if (parent.dataset._scaleY) { 1.168 + offsetY /= parent.dataset._scaleY; 1.169 + offsetX /= parent.dataset._scaleX; 1.170 + } 1.171 + offsetY += parent.offsetTop; 1.172 + offsetX += parent.offsetLeft; 1.173 + parent = parent.offsetParent; 1.174 + if (!parent) { 1.175 + return; // no need to scroll 1.176 + } 1.177 + } 1.178 + if (spot) { 1.179 + if (spot.top !== undefined) { 1.180 + offsetY += spot.top; 1.181 + } 1.182 + if (spot.left !== undefined) { 1.183 + offsetX += spot.left; 1.184 + parent.scrollLeft = offsetX; 1.185 + } 1.186 + } 1.187 + parent.scrollTop = offsetY; 1.188 +} 1.189 + 1.190 +/** 1.191 + * Event handler to suppress context menu. 1.192 + */ 1.193 +function noContextMenuHandler(e) { 1.194 + e.preventDefault(); 1.195 +} 1.196 + 1.197 +/** 1.198 + * Returns the filename or guessed filename from the url (see issue 3455). 1.199 + * url {String} The original PDF location. 1.200 + * @return {String} Guessed PDF file name. 1.201 + */ 1.202 +function getPDFFileNameFromURL(url) { 1.203 + var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/; 1.204 + // SCHEME HOST 1.PATH 2.QUERY 3.REF 1.205 + // Pattern to get last matching NAME.pdf 1.206 + var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i; 1.207 + var splitURI = reURI.exec(url); 1.208 + var suggestedFilename = reFilename.exec(splitURI[1]) || 1.209 + reFilename.exec(splitURI[2]) || 1.210 + reFilename.exec(splitURI[3]); 1.211 + if (suggestedFilename) { 1.212 + suggestedFilename = suggestedFilename[0]; 1.213 + if (suggestedFilename.indexOf('%') != -1) { 1.214 + // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf 1.215 + try { 1.216 + suggestedFilename = 1.217 + reFilename.exec(decodeURIComponent(suggestedFilename))[0]; 1.218 + } catch(e) { // Possible (extremely rare) errors: 1.219 + // URIError "Malformed URI", e.g. for "%AA.pdf" 1.220 + // TypeError "null has no properties", e.g. for "%2F.pdf" 1.221 + } 1.222 + } 1.223 + } 1.224 + return suggestedFilename || 'document.pdf'; 1.225 +} 1.226 + 1.227 +var ProgressBar = (function ProgressBarClosure() { 1.228 + 1.229 + function clamp(v, min, max) { 1.230 + return Math.min(Math.max(v, min), max); 1.231 + } 1.232 + 1.233 + function ProgressBar(id, opts) { 1.234 + 1.235 + // Fetch the sub-elements for later. 1.236 + this.div = document.querySelector(id + ' .progress'); 1.237 + 1.238 + // Get the loading bar element, so it can be resized to fit the viewer. 1.239 + this.bar = this.div.parentNode; 1.240 + 1.241 + // Get options, with sensible defaults. 1.242 + this.height = opts.height || 100; 1.243 + this.width = opts.width || 100; 1.244 + this.units = opts.units || '%'; 1.245 + 1.246 + // Initialize heights. 1.247 + this.div.style.height = this.height + this.units; 1.248 + this.percent = 0; 1.249 + } 1.250 + 1.251 + ProgressBar.prototype = { 1.252 + 1.253 + updateBar: function ProgressBar_updateBar() { 1.254 + if (this._indeterminate) { 1.255 + this.div.classList.add('indeterminate'); 1.256 + this.div.style.width = this.width + this.units; 1.257 + return; 1.258 + } 1.259 + 1.260 + this.div.classList.remove('indeterminate'); 1.261 + var progressSize = this.width * this._percent / 100; 1.262 + this.div.style.width = progressSize + this.units; 1.263 + }, 1.264 + 1.265 + get percent() { 1.266 + return this._percent; 1.267 + }, 1.268 + 1.269 + set percent(val) { 1.270 + this._indeterminate = isNaN(val); 1.271 + this._percent = clamp(val, 0, 100); 1.272 + this.updateBar(); 1.273 + }, 1.274 + 1.275 + setWidth: function ProgressBar_setWidth(viewer) { 1.276 + if (viewer) { 1.277 + var container = viewer.parentNode; 1.278 + var scrollbarWidth = container.offsetWidth - viewer.offsetWidth; 1.279 + if (scrollbarWidth > 0) { 1.280 + this.bar.setAttribute('style', 'width: calc(100% - ' + 1.281 + scrollbarWidth + 'px);'); 1.282 + } 1.283 + } 1.284 + }, 1.285 + 1.286 + hide: function ProgressBar_hide() { 1.287 + this.bar.classList.add('hidden'); 1.288 + this.bar.removeAttribute('style'); 1.289 + } 1.290 + }; 1.291 + 1.292 + return ProgressBar; 1.293 +})(); 1.294 + 1.295 +var Cache = function cacheCache(size) { 1.296 + var data = []; 1.297 + this.push = function cachePush(view) { 1.298 + var i = data.indexOf(view); 1.299 + if (i >= 0) { 1.300 + data.splice(i); 1.301 + } 1.302 + data.push(view); 1.303 + if (data.length > size) { 1.304 + data.shift().destroy(); 1.305 + } 1.306 + }; 1.307 +}; 1.308 + 1.309 + 1.310 + 1.311 + 1.312 +var DEFAULT_PREFERENCES = { 1.313 + showPreviousViewOnLoad: true, 1.314 + defaultZoomValue: '', 1.315 + ifAvailableShowOutlineOnLoad: false, 1.316 + enableHandToolOnLoad: false, 1.317 + enableWebGL: false 1.318 +}; 1.319 + 1.320 + 1.321 +/** 1.322 + * Preferences - Utility for storing persistent settings. 1.323 + * Used for settings that should be applied to all opened documents, 1.324 + * or every time the viewer is loaded. 1.325 + */ 1.326 +var Preferences = { 1.327 + prefs: Object.create(DEFAULT_PREFERENCES), 1.328 + isInitializedPromiseResolved: false, 1.329 + initializedPromise: null, 1.330 + 1.331 + /** 1.332 + * Initialize and fetch the current preference values from storage. 1.333 + * @return {Promise} A promise that is resolved when the preferences 1.334 + * have been initialized. 1.335 + */ 1.336 + initialize: function preferencesInitialize() { 1.337 + return this.initializedPromise = 1.338 + this._readFromStorage(DEFAULT_PREFERENCES).then(function(prefObj) { 1.339 + this.isInitializedPromiseResolved = true; 1.340 + if (prefObj) { 1.341 + this.prefs = prefObj; 1.342 + } 1.343 + }.bind(this)); 1.344 + }, 1.345 + 1.346 + /** 1.347 + * Stub function for writing preferences to storage. 1.348 + * NOTE: This should be overridden by a build-specific function defined below. 1.349 + * @param {Object} prefObj The preferences that should be written to storage. 1.350 + * @return {Promise} A promise that is resolved when the preference values 1.351 + * have been written. 1.352 + */ 1.353 + _writeToStorage: function preferences_writeToStorage(prefObj) { 1.354 + return Promise.resolve(); 1.355 + }, 1.356 + 1.357 + /** 1.358 + * Stub function for reading preferences from storage. 1.359 + * NOTE: This should be overridden by a build-specific function defined below. 1.360 + * @param {Object} prefObj The preferences that should be read from storage. 1.361 + * @return {Promise} A promise that is resolved with an {Object} containing 1.362 + * the preferences that have been read. 1.363 + */ 1.364 + _readFromStorage: function preferences_readFromStorage(prefObj) { 1.365 + return Promise.resolve(); 1.366 + }, 1.367 + 1.368 + /** 1.369 + * Reset the preferences to their default values and update storage. 1.370 + * @return {Promise} A promise that is resolved when the preference values 1.371 + * have been reset. 1.372 + */ 1.373 + reset: function preferencesReset() { 1.374 + return this.initializedPromise.then(function() { 1.375 + this.prefs = Object.create(DEFAULT_PREFERENCES); 1.376 + return this._writeToStorage(DEFAULT_PREFERENCES); 1.377 + }.bind(this)); 1.378 + }, 1.379 + 1.380 + /** 1.381 + * Replace the current preference values with the ones from storage. 1.382 + * @return {Promise} A promise that is resolved when the preference values 1.383 + * have been updated. 1.384 + */ 1.385 + reload: function preferencesReload() { 1.386 + return this.initializedPromise.then(function () { 1.387 + this._readFromStorage(DEFAULT_PREFERENCES).then(function(prefObj) { 1.388 + if (prefObj) { 1.389 + this.prefs = prefObj; 1.390 + } 1.391 + }.bind(this)); 1.392 + }.bind(this)); 1.393 + }, 1.394 + 1.395 + /** 1.396 + * Set the value of a preference. 1.397 + * @param {string} name The name of the preference that should be changed. 1.398 + * @param {boolean|number|string} value The new value of the preference. 1.399 + * @return {Promise} A promise that is resolved when the value has been set, 1.400 + * provided that the preference exists and the types match. 1.401 + */ 1.402 + set: function preferencesSet(name, value) { 1.403 + return this.initializedPromise.then(function () { 1.404 + if (DEFAULT_PREFERENCES[name] === undefined) { 1.405 + throw new Error('preferencesSet: \'' + name + '\' is undefined.'); 1.406 + } else if (value === undefined) { 1.407 + throw new Error('preferencesSet: no value is specified.'); 1.408 + } 1.409 + var valueType = typeof value; 1.410 + var defaultType = typeof DEFAULT_PREFERENCES[name]; 1.411 + 1.412 + if (valueType !== defaultType) { 1.413 + if (valueType === 'number' && defaultType === 'string') { 1.414 + value = value.toString(); 1.415 + } else { 1.416 + throw new Error('Preferences_set: \'' + value + '\' is a \"' + 1.417 + valueType + '\", expected \"' + defaultType + '\".'); 1.418 + } 1.419 + } else { 1.420 + if (valueType === 'number' && (value | 0) !== value) { 1.421 + throw new Error('Preferences_set: \'' + value + 1.422 + '\' must be an \"integer\".'); 1.423 + } 1.424 + } 1.425 + this.prefs[name] = value; 1.426 + return this._writeToStorage(this.prefs); 1.427 + }.bind(this)); 1.428 + }, 1.429 + 1.430 + /** 1.431 + * Get the value of a preference. 1.432 + * @param {string} name The name of the preference whose value is requested. 1.433 + * @return {Promise} A promise that is resolved with a {boolean|number|string} 1.434 + * containing the value of the preference. 1.435 + */ 1.436 + get: function preferencesGet(name) { 1.437 + return this.initializedPromise.then(function () { 1.438 + var defaultValue = DEFAULT_PREFERENCES[name]; 1.439 + 1.440 + if (defaultValue === undefined) { 1.441 + throw new Error('preferencesGet: \'' + name + '\' is undefined.'); 1.442 + } else { 1.443 + var prefValue = this.prefs[name]; 1.444 + 1.445 + if (prefValue !== undefined) { 1.446 + return prefValue; 1.447 + } 1.448 + } 1.449 + return defaultValue; 1.450 + }.bind(this)); 1.451 + } 1.452 +}; 1.453 + 1.454 + 1.455 + 1.456 + 1.457 + 1.458 + 1.459 + 1.460 +var FirefoxCom = (function FirefoxComClosure() { 1.461 + return { 1.462 + /** 1.463 + * Creates an event that the extension is listening for and will 1.464 + * synchronously respond to. 1.465 + * NOTE: It is reccomended to use request() instead since one day we may not 1.466 + * be able to synchronously reply. 1.467 + * @param {String} action The action to trigger. 1.468 + * @param {String} data Optional data to send. 1.469 + * @return {*} The response. 1.470 + */ 1.471 + requestSync: function(action, data) { 1.472 + var request = document.createTextNode(''); 1.473 + document.documentElement.appendChild(request); 1.474 + 1.475 + var sender = document.createEvent('CustomEvent'); 1.476 + sender.initCustomEvent('pdf.js.message', true, false, 1.477 + {action: action, data: data, sync: true}); 1.478 + request.dispatchEvent(sender); 1.479 + var response = sender.detail.response; 1.480 + document.documentElement.removeChild(request); 1.481 + return response; 1.482 + }, 1.483 + /** 1.484 + * Creates an event that the extension is listening for and will 1.485 + * asynchronously respond by calling the callback. 1.486 + * @param {String} action The action to trigger. 1.487 + * @param {String} data Optional data to send. 1.488 + * @param {Function} callback Optional response callback that will be called 1.489 + * with one data argument. 1.490 + */ 1.491 + request: function(action, data, callback) { 1.492 + var request = document.createTextNode(''); 1.493 + if (callback) { 1.494 + document.addEventListener('pdf.js.response', function listener(event) { 1.495 + var node = event.target; 1.496 + var response = event.detail.response; 1.497 + 1.498 + document.documentElement.removeChild(node); 1.499 + 1.500 + document.removeEventListener('pdf.js.response', listener, false); 1.501 + return callback(response); 1.502 + }, false); 1.503 + } 1.504 + document.documentElement.appendChild(request); 1.505 + 1.506 + var sender = document.createEvent('CustomEvent'); 1.507 + sender.initCustomEvent('pdf.js.message', true, false, 1.508 + {action: action, data: data, sync: false, 1.509 + callback: callback}); 1.510 + return request.dispatchEvent(sender); 1.511 + } 1.512 + }; 1.513 +})(); 1.514 + 1.515 +var DownloadManager = (function DownloadManagerClosure() { 1.516 + function DownloadManager() {} 1.517 + 1.518 + DownloadManager.prototype = { 1.519 + downloadUrl: function DownloadManager_downloadUrl(url, filename) { 1.520 + FirefoxCom.request('download', { 1.521 + originalUrl: url, 1.522 + filename: filename 1.523 + }); 1.524 + }, 1.525 + 1.526 + downloadData: function DownloadManager_downloadData(data, filename, 1.527 + contentType) { 1.528 + var blobUrl = PDFJS.createObjectURL(data, contentType); 1.529 + 1.530 + FirefoxCom.request('download', { 1.531 + blobUrl: blobUrl, 1.532 + originalUrl: blobUrl, 1.533 + filename: filename, 1.534 + isAttachment: true 1.535 + }); 1.536 + }, 1.537 + 1.538 + download: function DownloadManager_download(blob, url, filename) { 1.539 + var blobUrl = window.URL.createObjectURL(blob); 1.540 + 1.541 + FirefoxCom.request('download', { 1.542 + blobUrl: blobUrl, 1.543 + originalUrl: url, 1.544 + filename: filename 1.545 + }, 1.546 + function response(err) { 1.547 + if (err && this.onerror) { 1.548 + this.onerror(err); 1.549 + } 1.550 + window.URL.revokeObjectURL(blobUrl); 1.551 + }.bind(this) 1.552 + ); 1.553 + } 1.554 + }; 1.555 + 1.556 + return DownloadManager; 1.557 +})(); 1.558 + 1.559 +Preferences._writeToStorage = function (prefObj) { 1.560 + return new Promise(function (resolve) { 1.561 + FirefoxCom.request('setPreferences', prefObj, resolve); 1.562 + }); 1.563 +}; 1.564 + 1.565 +Preferences._readFromStorage = function (prefObj) { 1.566 + return new Promise(function (resolve) { 1.567 + FirefoxCom.request('getPreferences', prefObj, function (prefStr) { 1.568 + var readPrefs = JSON.parse(prefStr); 1.569 + resolve(readPrefs); 1.570 + }); 1.571 + }); 1.572 +}; 1.573 + 1.574 + 1.575 + 1.576 +var cache = new Cache(CACHE_SIZE); 1.577 +var currentPageNumber = 1; 1.578 + 1.579 + 1.580 +/** 1.581 + * View History - This is a utility for saving various view parameters for 1.582 + * recently opened files. 1.583 + * 1.584 + * The way that the view parameters are stored depends on how PDF.js is built, 1.585 + * for 'node make <flag>' the following cases exist: 1.586 + * - FIREFOX or MOZCENTRAL - uses sessionStorage. 1.587 + * - B2G - uses asyncStorage. 1.588 + * - GENERIC or CHROME - uses localStorage, if it is available. 1.589 + */ 1.590 +var ViewHistory = (function ViewHistoryClosure() { 1.591 + function ViewHistory(fingerprint) { 1.592 + this.fingerprint = fingerprint; 1.593 + var initializedPromiseResolve; 1.594 + this.isInitializedPromiseResolved = false; 1.595 + this.initializedPromise = new Promise(function (resolve) { 1.596 + initializedPromiseResolve = resolve; 1.597 + }); 1.598 + 1.599 + var resolvePromise = (function ViewHistoryResolvePromise(db) { 1.600 + this.isInitializedPromiseResolved = true; 1.601 + this.initialize(db || '{}'); 1.602 + initializedPromiseResolve(); 1.603 + }).bind(this); 1.604 + 1.605 + 1.606 + var sessionHistory; 1.607 + try { 1.608 + // Workaround for security error when the preference 1.609 + // network.cookie.lifetimePolicy is set to 1, see Mozilla Bug 365772. 1.610 + sessionHistory = sessionStorage.getItem('pdfjsHistory'); 1.611 + } catch (ex) {} 1.612 + resolvePromise(sessionHistory); 1.613 + 1.614 + } 1.615 + 1.616 + ViewHistory.prototype = { 1.617 + initialize: function ViewHistory_initialize(database) { 1.618 + database = JSON.parse(database); 1.619 + if (!('files' in database)) { 1.620 + database.files = []; 1.621 + } 1.622 + if (database.files.length >= VIEW_HISTORY_MEMORY) { 1.623 + database.files.shift(); 1.624 + } 1.625 + var index; 1.626 + for (var i = 0, length = database.files.length; i < length; i++) { 1.627 + var branch = database.files[i]; 1.628 + if (branch.fingerprint === this.fingerprint) { 1.629 + index = i; 1.630 + break; 1.631 + } 1.632 + } 1.633 + if (typeof index !== 'number') { 1.634 + index = database.files.push({fingerprint: this.fingerprint}) - 1; 1.635 + } 1.636 + this.file = database.files[index]; 1.637 + this.database = database; 1.638 + }, 1.639 + 1.640 + set: function ViewHistory_set(name, val) { 1.641 + if (!this.isInitializedPromiseResolved) { 1.642 + return; 1.643 + } 1.644 + var file = this.file; 1.645 + file[name] = val; 1.646 + var database = JSON.stringify(this.database); 1.647 + 1.648 + 1.649 + try { 1.650 + // See comment in try-catch block above. 1.651 + sessionStorage.setItem('pdfjsHistory', database); 1.652 + } catch (ex) {} 1.653 + 1.654 + }, 1.655 + 1.656 + get: function ViewHistory_get(name, defaultValue) { 1.657 + if (!this.isInitializedPromiseResolved) { 1.658 + return defaultValue; 1.659 + } 1.660 + return this.file[name] || defaultValue; 1.661 + } 1.662 + }; 1.663 + 1.664 + return ViewHistory; 1.665 +})(); 1.666 + 1.667 + 1.668 +/** 1.669 + * Creates a "search bar" given set of DOM elements 1.670 + * that act as controls for searching, or for setting 1.671 + * search preferences in the UI. This object also sets 1.672 + * up the appropriate events for the controls. Actual 1.673 + * searching is done by PDFFindController 1.674 + */ 1.675 +var PDFFindBar = { 1.676 + opened: false, 1.677 + bar: null, 1.678 + toggleButton: null, 1.679 + findField: null, 1.680 + highlightAll: null, 1.681 + caseSensitive: null, 1.682 + findMsg: null, 1.683 + findStatusIcon: null, 1.684 + findPreviousButton: null, 1.685 + findNextButton: null, 1.686 + 1.687 + initialize: function(options) { 1.688 + if(typeof PDFFindController === 'undefined' || PDFFindController === null) { 1.689 + throw 'PDFFindBar cannot be initialized ' + 1.690 + 'without a PDFFindController instance.'; 1.691 + } 1.692 + 1.693 + this.bar = options.bar; 1.694 + this.toggleButton = options.toggleButton; 1.695 + this.findField = options.findField; 1.696 + this.highlightAll = options.highlightAllCheckbox; 1.697 + this.caseSensitive = options.caseSensitiveCheckbox; 1.698 + this.findMsg = options.findMsg; 1.699 + this.findStatusIcon = options.findStatusIcon; 1.700 + this.findPreviousButton = options.findPreviousButton; 1.701 + this.findNextButton = options.findNextButton; 1.702 + 1.703 + var self = this; 1.704 + this.toggleButton.addEventListener('click', function() { 1.705 + self.toggle(); 1.706 + }); 1.707 + 1.708 + this.findField.addEventListener('input', function() { 1.709 + self.dispatchEvent(''); 1.710 + }); 1.711 + 1.712 + this.bar.addEventListener('keydown', function(evt) { 1.713 + switch (evt.keyCode) { 1.714 + case 13: // Enter 1.715 + if (evt.target === self.findField) { 1.716 + self.dispatchEvent('again', evt.shiftKey); 1.717 + } 1.718 + break; 1.719 + case 27: // Escape 1.720 + self.close(); 1.721 + break; 1.722 + } 1.723 + }); 1.724 + 1.725 + this.findPreviousButton.addEventListener('click', 1.726 + function() { self.dispatchEvent('again', true); } 1.727 + ); 1.728 + 1.729 + this.findNextButton.addEventListener('click', function() { 1.730 + self.dispatchEvent('again', false); 1.731 + }); 1.732 + 1.733 + this.highlightAll.addEventListener('click', function() { 1.734 + self.dispatchEvent('highlightallchange'); 1.735 + }); 1.736 + 1.737 + this.caseSensitive.addEventListener('click', function() { 1.738 + self.dispatchEvent('casesensitivitychange'); 1.739 + }); 1.740 + }, 1.741 + 1.742 + dispatchEvent: function(aType, aFindPrevious) { 1.743 + var event = document.createEvent('CustomEvent'); 1.744 + event.initCustomEvent('find' + aType, true, true, { 1.745 + query: this.findField.value, 1.746 + caseSensitive: this.caseSensitive.checked, 1.747 + highlightAll: this.highlightAll.checked, 1.748 + findPrevious: aFindPrevious 1.749 + }); 1.750 + return window.dispatchEvent(event); 1.751 + }, 1.752 + 1.753 + updateUIState: function(state, previous) { 1.754 + var notFound = false; 1.755 + var findMsg = ''; 1.756 + var status = ''; 1.757 + 1.758 + switch (state) { 1.759 + case FindStates.FIND_FOUND: 1.760 + break; 1.761 + 1.762 + case FindStates.FIND_PENDING: 1.763 + status = 'pending'; 1.764 + break; 1.765 + 1.766 + case FindStates.FIND_NOTFOUND: 1.767 + findMsg = mozL10n.get('find_not_found', null, 'Phrase not found'); 1.768 + notFound = true; 1.769 + break; 1.770 + 1.771 + case FindStates.FIND_WRAPPED: 1.772 + if (previous) { 1.773 + findMsg = mozL10n.get('find_reached_top', null, 1.774 + 'Reached top of document, continued from bottom'); 1.775 + } else { 1.776 + findMsg = mozL10n.get('find_reached_bottom', null, 1.777 + 'Reached end of document, continued from top'); 1.778 + } 1.779 + break; 1.780 + } 1.781 + 1.782 + if (notFound) { 1.783 + this.findField.classList.add('notFound'); 1.784 + } else { 1.785 + this.findField.classList.remove('notFound'); 1.786 + } 1.787 + 1.788 + this.findField.setAttribute('data-status', status); 1.789 + this.findMsg.textContent = findMsg; 1.790 + }, 1.791 + 1.792 + open: function() { 1.793 + if (!this.opened) { 1.794 + this.opened = true; 1.795 + this.toggleButton.classList.add('toggled'); 1.796 + this.bar.classList.remove('hidden'); 1.797 + } 1.798 + 1.799 + this.findField.select(); 1.800 + this.findField.focus(); 1.801 + }, 1.802 + 1.803 + close: function() { 1.804 + if (!this.opened) { 1.805 + return; 1.806 + } 1.807 + this.opened = false; 1.808 + this.toggleButton.classList.remove('toggled'); 1.809 + this.bar.classList.add('hidden'); 1.810 + 1.811 + PDFFindController.active = false; 1.812 + }, 1.813 + 1.814 + toggle: function() { 1.815 + if (this.opened) { 1.816 + this.close(); 1.817 + } else { 1.818 + this.open(); 1.819 + } 1.820 + } 1.821 +}; 1.822 + 1.823 + 1.824 + 1.825 +/** 1.826 + * Provides a "search" or "find" functionality for the PDF. 1.827 + * This object actually performs the search for a given string. 1.828 + */ 1.829 + 1.830 +var PDFFindController = { 1.831 + startedTextExtraction: false, 1.832 + 1.833 + extractTextPromises: [], 1.834 + 1.835 + pendingFindMatches: {}, 1.836 + 1.837 + // If active, find results will be highlighted. 1.838 + active: false, 1.839 + 1.840 + // Stores the text for each page. 1.841 + pageContents: [], 1.842 + 1.843 + pageMatches: [], 1.844 + 1.845 + // Currently selected match. 1.846 + selected: { 1.847 + pageIdx: -1, 1.848 + matchIdx: -1 1.849 + }, 1.850 + 1.851 + // Where find algorithm currently is in the document. 1.852 + offset: { 1.853 + pageIdx: null, 1.854 + matchIdx: null 1.855 + }, 1.856 + 1.857 + resumePageIdx: null, 1.858 + 1.859 + state: null, 1.860 + 1.861 + dirtyMatch: false, 1.862 + 1.863 + findTimeout: null, 1.864 + 1.865 + pdfPageSource: null, 1.866 + 1.867 + integratedFind: false, 1.868 + 1.869 + initialize: function(options) { 1.870 + if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) { 1.871 + throw 'PDFFindController cannot be initialized ' + 1.872 + 'without a PDFFindBar instance'; 1.873 + } 1.874 + 1.875 + this.pdfPageSource = options.pdfPageSource; 1.876 + this.integratedFind = options.integratedFind; 1.877 + 1.878 + var events = [ 1.879 + 'find', 1.880 + 'findagain', 1.881 + 'findhighlightallchange', 1.882 + 'findcasesensitivitychange' 1.883 + ]; 1.884 + 1.885 + this.firstPagePromise = new Promise(function (resolve) { 1.886 + this.resolveFirstPage = resolve; 1.887 + }.bind(this)); 1.888 + this.handleEvent = this.handleEvent.bind(this); 1.889 + 1.890 + for (var i = 0; i < events.length; i++) { 1.891 + window.addEventListener(events[i], this.handleEvent); 1.892 + } 1.893 + }, 1.894 + 1.895 + reset: function pdfFindControllerReset() { 1.896 + this.startedTextExtraction = false; 1.897 + this.extractTextPromises = []; 1.898 + this.active = false; 1.899 + }, 1.900 + 1.901 + calcFindMatch: function(pageIndex) { 1.902 + var pageContent = this.pageContents[pageIndex]; 1.903 + var query = this.state.query; 1.904 + var caseSensitive = this.state.caseSensitive; 1.905 + var queryLen = query.length; 1.906 + 1.907 + if (queryLen === 0) { 1.908 + // Do nothing the matches should be wiped out already. 1.909 + return; 1.910 + } 1.911 + 1.912 + if (!caseSensitive) { 1.913 + pageContent = pageContent.toLowerCase(); 1.914 + query = query.toLowerCase(); 1.915 + } 1.916 + 1.917 + var matches = []; 1.918 + 1.919 + var matchIdx = -queryLen; 1.920 + while (true) { 1.921 + matchIdx = pageContent.indexOf(query, matchIdx + queryLen); 1.922 + if (matchIdx === -1) { 1.923 + break; 1.924 + } 1.925 + 1.926 + matches.push(matchIdx); 1.927 + } 1.928 + this.pageMatches[pageIndex] = matches; 1.929 + this.updatePage(pageIndex); 1.930 + if (this.resumePageIdx === pageIndex) { 1.931 + this.resumePageIdx = null; 1.932 + this.nextPageMatch(); 1.933 + } 1.934 + }, 1.935 + 1.936 + extractText: function() { 1.937 + if (this.startedTextExtraction) { 1.938 + return; 1.939 + } 1.940 + this.startedTextExtraction = true; 1.941 + 1.942 + this.pageContents = []; 1.943 + var extractTextPromisesResolves = []; 1.944 + for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) { 1.945 + this.extractTextPromises.push(new Promise(function (resolve) { 1.946 + extractTextPromisesResolves.push(resolve); 1.947 + })); 1.948 + } 1.949 + 1.950 + var self = this; 1.951 + function extractPageText(pageIndex) { 1.952 + self.pdfPageSource.pages[pageIndex].getTextContent().then( 1.953 + function textContentResolved(textContent) { 1.954 + var textItems = textContent.items; 1.955 + var str = ''; 1.956 + 1.957 + for (var i = 0; i < textItems.length; i++) { 1.958 + str += textItems[i].str; 1.959 + } 1.960 + 1.961 + // Store the pageContent as a string. 1.962 + self.pageContents.push(str); 1.963 + 1.964 + extractTextPromisesResolves[pageIndex](pageIndex); 1.965 + if ((pageIndex + 1) < self.pdfPageSource.pages.length) { 1.966 + extractPageText(pageIndex + 1); 1.967 + } 1.968 + } 1.969 + ); 1.970 + } 1.971 + extractPageText(0); 1.972 + }, 1.973 + 1.974 + handleEvent: function(e) { 1.975 + if (this.state === null || e.type !== 'findagain') { 1.976 + this.dirtyMatch = true; 1.977 + } 1.978 + this.state = e.detail; 1.979 + this.updateUIState(FindStates.FIND_PENDING); 1.980 + 1.981 + this.firstPagePromise.then(function() { 1.982 + this.extractText(); 1.983 + 1.984 + clearTimeout(this.findTimeout); 1.985 + if (e.type === 'find') { 1.986 + // Only trigger the find action after 250ms of silence. 1.987 + this.findTimeout = setTimeout(this.nextMatch.bind(this), 250); 1.988 + } else { 1.989 + this.nextMatch(); 1.990 + } 1.991 + }.bind(this)); 1.992 + }, 1.993 + 1.994 + updatePage: function(idx) { 1.995 + var page = this.pdfPageSource.pages[idx]; 1.996 + 1.997 + if (this.selected.pageIdx === idx) { 1.998 + // If the page is selected, scroll the page into view, which triggers 1.999 + // rendering the page, which adds the textLayer. Once the textLayer is 1.1000 + // build, it will scroll onto the selected match. 1.1001 + page.scrollIntoView(); 1.1002 + } 1.1003 + 1.1004 + if (page.textLayer) { 1.1005 + page.textLayer.updateMatches(); 1.1006 + } 1.1007 + }, 1.1008 + 1.1009 + nextMatch: function() { 1.1010 + var previous = this.state.findPrevious; 1.1011 + var currentPageIndex = this.pdfPageSource.page - 1; 1.1012 + var numPages = this.pdfPageSource.pages.length; 1.1013 + 1.1014 + this.active = true; 1.1015 + 1.1016 + if (this.dirtyMatch) { 1.1017 + // Need to recalculate the matches, reset everything. 1.1018 + this.dirtyMatch = false; 1.1019 + this.selected.pageIdx = this.selected.matchIdx = -1; 1.1020 + this.offset.pageIdx = currentPageIndex; 1.1021 + this.offset.matchIdx = null; 1.1022 + this.hadMatch = false; 1.1023 + this.resumePageIdx = null; 1.1024 + this.pageMatches = []; 1.1025 + var self = this; 1.1026 + 1.1027 + for (var i = 0; i < numPages; i++) { 1.1028 + // Wipe out any previous highlighted matches. 1.1029 + this.updatePage(i); 1.1030 + 1.1031 + // As soon as the text is extracted start finding the matches. 1.1032 + if (!(i in this.pendingFindMatches)) { 1.1033 + this.pendingFindMatches[i] = true; 1.1034 + this.extractTextPromises[i].then(function(pageIdx) { 1.1035 + delete self.pendingFindMatches[pageIdx]; 1.1036 + self.calcFindMatch(pageIdx); 1.1037 + }); 1.1038 + } 1.1039 + } 1.1040 + } 1.1041 + 1.1042 + // If there's no query there's no point in searching. 1.1043 + if (this.state.query === '') { 1.1044 + this.updateUIState(FindStates.FIND_FOUND); 1.1045 + return; 1.1046 + } 1.1047 + 1.1048 + // If we're waiting on a page, we return since we can't do anything else. 1.1049 + if (this.resumePageIdx) { 1.1050 + return; 1.1051 + } 1.1052 + 1.1053 + var offset = this.offset; 1.1054 + // If there's already a matchIdx that means we are iterating through a 1.1055 + // page's matches. 1.1056 + if (offset.matchIdx !== null) { 1.1057 + var numPageMatches = this.pageMatches[offset.pageIdx].length; 1.1058 + if ((!previous && offset.matchIdx + 1 < numPageMatches) || 1.1059 + (previous && offset.matchIdx > 0)) { 1.1060 + // The simple case, we just have advance the matchIdx to select the next 1.1061 + // match on the page. 1.1062 + this.hadMatch = true; 1.1063 + offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1; 1.1064 + this.updateMatch(true); 1.1065 + return; 1.1066 + } 1.1067 + // We went beyond the current page's matches, so we advance to the next 1.1068 + // page. 1.1069 + this.advanceOffsetPage(previous); 1.1070 + } 1.1071 + // Start searching through the page. 1.1072 + this.nextPageMatch(); 1.1073 + }, 1.1074 + 1.1075 + matchesReady: function(matches) { 1.1076 + var offset = this.offset; 1.1077 + var numMatches = matches.length; 1.1078 + var previous = this.state.findPrevious; 1.1079 + if (numMatches) { 1.1080 + // There were matches for the page, so initialize the matchIdx. 1.1081 + this.hadMatch = true; 1.1082 + offset.matchIdx = previous ? numMatches - 1 : 0; 1.1083 + this.updateMatch(true); 1.1084 + // matches were found 1.1085 + return true; 1.1086 + } else { 1.1087 + // No matches attempt to search the next page. 1.1088 + this.advanceOffsetPage(previous); 1.1089 + if (offset.wrapped) { 1.1090 + offset.matchIdx = null; 1.1091 + if (!this.hadMatch) { 1.1092 + // No point in wrapping there were no matches. 1.1093 + this.updateMatch(false); 1.1094 + // while matches were not found, searching for a page 1.1095 + // with matches should nevertheless halt. 1.1096 + return true; 1.1097 + } 1.1098 + } 1.1099 + // matches were not found (and searching is not done) 1.1100 + return false; 1.1101 + } 1.1102 + }, 1.1103 + 1.1104 + nextPageMatch: function() { 1.1105 + if (this.resumePageIdx !== null) { 1.1106 + console.error('There can only be one pending page.'); 1.1107 + } 1.1108 + do { 1.1109 + var pageIdx = this.offset.pageIdx; 1.1110 + var matches = this.pageMatches[pageIdx]; 1.1111 + if (!matches) { 1.1112 + // The matches don't exist yet for processing by "matchesReady", 1.1113 + // so set a resume point for when they do exist. 1.1114 + this.resumePageIdx = pageIdx; 1.1115 + break; 1.1116 + } 1.1117 + } while (!this.matchesReady(matches)); 1.1118 + }, 1.1119 + 1.1120 + advanceOffsetPage: function(previous) { 1.1121 + var offset = this.offset; 1.1122 + var numPages = this.extractTextPromises.length; 1.1123 + offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1; 1.1124 + offset.matchIdx = null; 1.1125 + if (offset.pageIdx >= numPages || offset.pageIdx < 0) { 1.1126 + offset.pageIdx = previous ? numPages - 1 : 0; 1.1127 + offset.wrapped = true; 1.1128 + return; 1.1129 + } 1.1130 + }, 1.1131 + 1.1132 + updateMatch: function(found) { 1.1133 + var state = FindStates.FIND_NOTFOUND; 1.1134 + var wrapped = this.offset.wrapped; 1.1135 + this.offset.wrapped = false; 1.1136 + if (found) { 1.1137 + var previousPage = this.selected.pageIdx; 1.1138 + this.selected.pageIdx = this.offset.pageIdx; 1.1139 + this.selected.matchIdx = this.offset.matchIdx; 1.1140 + state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND; 1.1141 + // Update the currently selected page to wipe out any selected matches. 1.1142 + if (previousPage !== -1 && previousPage !== this.selected.pageIdx) { 1.1143 + this.updatePage(previousPage); 1.1144 + } 1.1145 + } 1.1146 + this.updateUIState(state, this.state.findPrevious); 1.1147 + if (this.selected.pageIdx !== -1) { 1.1148 + this.updatePage(this.selected.pageIdx, true); 1.1149 + } 1.1150 + }, 1.1151 + 1.1152 + updateUIState: function(state, previous) { 1.1153 + if (this.integratedFind) { 1.1154 + FirefoxCom.request('updateFindControlState', 1.1155 + {result: state, findPrevious: previous}); 1.1156 + return; 1.1157 + } 1.1158 + PDFFindBar.updateUIState(state, previous); 1.1159 + } 1.1160 +}; 1.1161 + 1.1162 + 1.1163 + 1.1164 +var PDFHistory = { 1.1165 + initialized: false, 1.1166 + initialDestination: null, 1.1167 + 1.1168 + initialize: function pdfHistoryInitialize(fingerprint) { 1.1169 + if (PDFJS.disableHistory || PDFView.isViewerEmbedded) { 1.1170 + // The browsing history is only enabled when the viewer is standalone, 1.1171 + // i.e. not when it is embedded in a web page. 1.1172 + return; 1.1173 + } 1.1174 + this.initialized = true; 1.1175 + this.reInitialized = false; 1.1176 + this.allowHashChange = true; 1.1177 + this.historyUnlocked = true; 1.1178 + 1.1179 + this.previousHash = window.location.hash.substring(1); 1.1180 + this.currentBookmark = ''; 1.1181 + this.currentPage = 0; 1.1182 + this.updatePreviousBookmark = false; 1.1183 + this.previousBookmark = ''; 1.1184 + this.previousPage = 0; 1.1185 + this.nextHashParam = ''; 1.1186 + 1.1187 + this.fingerprint = fingerprint; 1.1188 + this.currentUid = this.uid = 0; 1.1189 + this.current = {}; 1.1190 + 1.1191 + var state = window.history.state; 1.1192 + if (this._isStateObjectDefined(state)) { 1.1193 + // This corresponds to navigating back to the document 1.1194 + // from another page in the browser history. 1.1195 + if (state.target.dest) { 1.1196 + this.initialDestination = state.target.dest; 1.1197 + } else { 1.1198 + PDFView.initialBookmark = state.target.hash; 1.1199 + } 1.1200 + this.currentUid = state.uid; 1.1201 + this.uid = state.uid + 1; 1.1202 + this.current = state.target; 1.1203 + } else { 1.1204 + // This corresponds to the loading of a new document. 1.1205 + if (state && state.fingerprint && 1.1206 + this.fingerprint !== state.fingerprint) { 1.1207 + // Reinitialize the browsing history when a new document 1.1208 + // is opened in the web viewer. 1.1209 + this.reInitialized = true; 1.1210 + } 1.1211 + this._pushOrReplaceState({ fingerprint: this.fingerprint }, true); 1.1212 + } 1.1213 + 1.1214 + var self = this; 1.1215 + window.addEventListener('popstate', function pdfHistoryPopstate(evt) { 1.1216 + evt.preventDefault(); 1.1217 + evt.stopPropagation(); 1.1218 + 1.1219 + if (!self.historyUnlocked) { 1.1220 + return; 1.1221 + } 1.1222 + if (evt.state) { 1.1223 + // Move back/forward in the history. 1.1224 + self._goTo(evt.state); 1.1225 + } else { 1.1226 + // Handle the user modifying the hash of a loaded document. 1.1227 + self.previousHash = window.location.hash.substring(1); 1.1228 + 1.1229 + // If the history is empty when the hash changes, 1.1230 + // update the previous entry in the browser history. 1.1231 + if (self.uid === 0) { 1.1232 + var previousParams = (self.previousHash && self.currentBookmark && 1.1233 + self.previousHash !== self.currentBookmark) ? 1.1234 + { hash: self.currentBookmark, page: self.currentPage } : 1.1235 + { page: 1 }; 1.1236 + self.historyUnlocked = false; 1.1237 + self.allowHashChange = false; 1.1238 + window.history.back(); 1.1239 + self._pushToHistory(previousParams, false, true); 1.1240 + window.history.forward(); 1.1241 + self.historyUnlocked = true; 1.1242 + } 1.1243 + self._pushToHistory({ hash: self.previousHash }, false, true); 1.1244 + self._updatePreviousBookmark(); 1.1245 + } 1.1246 + }, false); 1.1247 + 1.1248 + function pdfHistoryBeforeUnload() { 1.1249 + var previousParams = self._getPreviousParams(null, true); 1.1250 + if (previousParams) { 1.1251 + var replacePrevious = (!self.current.dest && 1.1252 + self.current.hash !== self.previousHash); 1.1253 + self._pushToHistory(previousParams, false, replacePrevious); 1.1254 + self._updatePreviousBookmark(); 1.1255 + } 1.1256 + // Remove the event listener when navigating away from the document, 1.1257 + // since 'beforeunload' prevents Firefox from caching the document. 1.1258 + window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false); 1.1259 + } 1.1260 + window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false); 1.1261 + 1.1262 + window.addEventListener('pageshow', function pdfHistoryPageShow(evt) { 1.1263 + // If the entire viewer (including the PDF file) is cached in the browser, 1.1264 + // we need to reattach the 'beforeunload' event listener since 1.1265 + // the 'DOMContentLoaded' event is not fired on 'pageshow'. 1.1266 + window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false); 1.1267 + }, false); 1.1268 + }, 1.1269 + 1.1270 + _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) { 1.1271 + return (state && state.uid >= 0 && 1.1272 + state.fingerprint && this.fingerprint === state.fingerprint && 1.1273 + state.target && state.target.hash) ? true : false; 1.1274 + }, 1.1275 + 1.1276 + _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj, 1.1277 + replace) { 1.1278 + if (replace) { 1.1279 + window.history.replaceState(stateObj, ''); 1.1280 + } else { 1.1281 + window.history.pushState(stateObj, ''); 1.1282 + } 1.1283 + }, 1.1284 + 1.1285 + get isHashChangeUnlocked() { 1.1286 + if (!this.initialized) { 1.1287 + return true; 1.1288 + } 1.1289 + // If the current hash changes when moving back/forward in the history, 1.1290 + // this will trigger a 'popstate' event *as well* as a 'hashchange' event. 1.1291 + // Since the hash generally won't correspond to the exact the position 1.1292 + // stored in the history's state object, triggering the 'hashchange' event 1.1293 + // can thus corrupt the browser history. 1.1294 + // 1.1295 + // When the hash changes during a 'popstate' event, we *only* prevent the 1.1296 + // first 'hashchange' event and immediately reset allowHashChange. 1.1297 + // If it is not reset, the user would not be able to change the hash. 1.1298 + 1.1299 + var temp = this.allowHashChange; 1.1300 + this.allowHashChange = true; 1.1301 + return temp; 1.1302 + }, 1.1303 + 1.1304 + _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() { 1.1305 + if (this.updatePreviousBookmark && 1.1306 + this.currentBookmark && this.currentPage) { 1.1307 + this.previousBookmark = this.currentBookmark; 1.1308 + this.previousPage = this.currentPage; 1.1309 + this.updatePreviousBookmark = false; 1.1310 + } 1.1311 + }, 1.1312 + 1.1313 + updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark, 1.1314 + pageNum) { 1.1315 + if (this.initialized) { 1.1316 + this.currentBookmark = bookmark.substring(1); 1.1317 + this.currentPage = pageNum | 0; 1.1318 + this._updatePreviousBookmark(); 1.1319 + } 1.1320 + }, 1.1321 + 1.1322 + updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) { 1.1323 + if (this.initialized) { 1.1324 + this.nextHashParam = param; 1.1325 + } 1.1326 + }, 1.1327 + 1.1328 + push: function pdfHistoryPush(params, isInitialBookmark) { 1.1329 + if (!(this.initialized && this.historyUnlocked)) { 1.1330 + return; 1.1331 + } 1.1332 + if (params.dest && !params.hash) { 1.1333 + params.hash = (this.current.hash && this.current.dest && 1.1334 + this.current.dest === params.dest) ? 1.1335 + this.current.hash : 1.1336 + PDFView.getDestinationHash(params.dest).split('#')[1]; 1.1337 + } 1.1338 + if (params.page) { 1.1339 + params.page |= 0; 1.1340 + } 1.1341 + if (isInitialBookmark) { 1.1342 + var target = window.history.state.target; 1.1343 + if (!target) { 1.1344 + // Invoked when the user specifies an initial bookmark, 1.1345 + // thus setting PDFView.initialBookmark, when the document is loaded. 1.1346 + this._pushToHistory(params, false); 1.1347 + this.previousHash = window.location.hash.substring(1); 1.1348 + } 1.1349 + this.updatePreviousBookmark = this.nextHashParam ? false : true; 1.1350 + if (target) { 1.1351 + // If the current document is reloaded, 1.1352 + // avoid creating duplicate entries in the history. 1.1353 + this._updatePreviousBookmark(); 1.1354 + } 1.1355 + return; 1.1356 + } 1.1357 + if (this.nextHashParam) { 1.1358 + if (this.nextHashParam === params.hash) { 1.1359 + this.nextHashParam = null; 1.1360 + this.updatePreviousBookmark = true; 1.1361 + return; 1.1362 + } else { 1.1363 + this.nextHashParam = null; 1.1364 + } 1.1365 + } 1.1366 + 1.1367 + if (params.hash) { 1.1368 + if (this.current.hash) { 1.1369 + if (this.current.hash !== params.hash) { 1.1370 + this._pushToHistory(params, true); 1.1371 + } else { 1.1372 + if (!this.current.page && params.page) { 1.1373 + this._pushToHistory(params, false, true); 1.1374 + } 1.1375 + this.updatePreviousBookmark = true; 1.1376 + } 1.1377 + } else { 1.1378 + this._pushToHistory(params, true); 1.1379 + } 1.1380 + } else if (this.current.page && params.page && 1.1381 + this.current.page !== params.page) { 1.1382 + this._pushToHistory(params, true); 1.1383 + } 1.1384 + }, 1.1385 + 1.1386 + _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage, 1.1387 + beforeUnload) { 1.1388 + if (!(this.currentBookmark && this.currentPage)) { 1.1389 + return null; 1.1390 + } else if (this.updatePreviousBookmark) { 1.1391 + this.updatePreviousBookmark = false; 1.1392 + } 1.1393 + if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) { 1.1394 + // Prevent the history from getting stuck in the current state, 1.1395 + // effectively preventing the user from going back/forward in the history. 1.1396 + // 1.1397 + // This happens if the current position in the document didn't change when 1.1398 + // the history was previously updated. The reasons for this are either: 1.1399 + // 1. The current zoom value is such that the document does not need to, 1.1400 + // or cannot, be scrolled to display the destination. 1.1401 + // 2. The previous destination is broken, and doesn't actally point to a 1.1402 + // position within the document. 1.1403 + // (This is either due to a bad PDF generator, or the user making a 1.1404 + // mistake when entering a destination in the hash parameters.) 1.1405 + return null; 1.1406 + } 1.1407 + if ((!this.current.dest && !onlyCheckPage) || beforeUnload) { 1.1408 + if (this.previousBookmark === this.currentBookmark) { 1.1409 + return null; 1.1410 + } 1.1411 + } else if (this.current.page || onlyCheckPage) { 1.1412 + if (this.previousPage === this.currentPage) { 1.1413 + return null; 1.1414 + } 1.1415 + } else { 1.1416 + return null; 1.1417 + } 1.1418 + var params = { hash: this.currentBookmark, page: this.currentPage }; 1.1419 + if (PresentationMode.active) { 1.1420 + params.hash = null; 1.1421 + } 1.1422 + return params; 1.1423 + }, 1.1424 + 1.1425 + _stateObj: function pdfHistory_stateObj(params) { 1.1426 + return { fingerprint: this.fingerprint, uid: this.uid, target: params }; 1.1427 + }, 1.1428 + 1.1429 + _pushToHistory: function pdfHistory_pushToHistory(params, 1.1430 + addPrevious, overwrite) { 1.1431 + if (!this.initialized) { 1.1432 + return; 1.1433 + } 1.1434 + if (!params.hash && params.page) { 1.1435 + params.hash = ('page=' + params.page); 1.1436 + } 1.1437 + if (addPrevious && !overwrite) { 1.1438 + var previousParams = this._getPreviousParams(); 1.1439 + if (previousParams) { 1.1440 + var replacePrevious = (!this.current.dest && 1.1441 + this.current.hash !== this.previousHash); 1.1442 + this._pushToHistory(previousParams, false, replacePrevious); 1.1443 + } 1.1444 + } 1.1445 + this._pushOrReplaceState(this._stateObj(params), 1.1446 + (overwrite || this.uid === 0)); 1.1447 + this.currentUid = this.uid++; 1.1448 + this.current = params; 1.1449 + this.updatePreviousBookmark = true; 1.1450 + }, 1.1451 + 1.1452 + _goTo: function pdfHistory_goTo(state) { 1.1453 + if (!(this.initialized && this.historyUnlocked && 1.1454 + this._isStateObjectDefined(state))) { 1.1455 + return; 1.1456 + } 1.1457 + if (!this.reInitialized && state.uid < this.currentUid) { 1.1458 + var previousParams = this._getPreviousParams(true); 1.1459 + if (previousParams) { 1.1460 + this._pushToHistory(this.current, false); 1.1461 + this._pushToHistory(previousParams, false); 1.1462 + this.currentUid = state.uid; 1.1463 + window.history.back(); 1.1464 + return; 1.1465 + } 1.1466 + } 1.1467 + this.historyUnlocked = false; 1.1468 + 1.1469 + if (state.target.dest) { 1.1470 + PDFView.navigateTo(state.target.dest); 1.1471 + } else { 1.1472 + PDFView.setHash(state.target.hash); 1.1473 + } 1.1474 + this.currentUid = state.uid; 1.1475 + if (state.uid > this.uid) { 1.1476 + this.uid = state.uid; 1.1477 + } 1.1478 + this.current = state.target; 1.1479 + this.updatePreviousBookmark = true; 1.1480 + 1.1481 + var currentHash = window.location.hash.substring(1); 1.1482 + if (this.previousHash !== currentHash) { 1.1483 + this.allowHashChange = false; 1.1484 + } 1.1485 + this.previousHash = currentHash; 1.1486 + 1.1487 + this.historyUnlocked = true; 1.1488 + }, 1.1489 + 1.1490 + back: function pdfHistoryBack() { 1.1491 + this.go(-1); 1.1492 + }, 1.1493 + 1.1494 + forward: function pdfHistoryForward() { 1.1495 + this.go(1); 1.1496 + }, 1.1497 + 1.1498 + go: function pdfHistoryGo(direction) { 1.1499 + if (this.initialized && this.historyUnlocked) { 1.1500 + var state = window.history.state; 1.1501 + if (direction === -1 && state && state.uid > 0) { 1.1502 + window.history.back(); 1.1503 + } else if (direction === 1 && state && state.uid < (this.uid - 1)) { 1.1504 + window.history.forward(); 1.1505 + } 1.1506 + } 1.1507 + } 1.1508 +}; 1.1509 + 1.1510 + 1.1511 +var SecondaryToolbar = { 1.1512 + opened: false, 1.1513 + previousContainerHeight: null, 1.1514 + newContainerHeight: null, 1.1515 + 1.1516 + initialize: function secondaryToolbarInitialize(options) { 1.1517 + this.toolbar = options.toolbar; 1.1518 + this.presentationMode = options.presentationMode; 1.1519 + this.documentProperties = options.documentProperties; 1.1520 + this.buttonContainer = this.toolbar.firstElementChild; 1.1521 + 1.1522 + // Define the toolbar buttons. 1.1523 + this.toggleButton = options.toggleButton; 1.1524 + this.presentationModeButton = options.presentationModeButton; 1.1525 + this.openFile = options.openFile; 1.1526 + this.print = options.print; 1.1527 + this.download = options.download; 1.1528 + this.viewBookmark = options.viewBookmark; 1.1529 + this.firstPage = options.firstPage; 1.1530 + this.lastPage = options.lastPage; 1.1531 + this.pageRotateCw = options.pageRotateCw; 1.1532 + this.pageRotateCcw = options.pageRotateCcw; 1.1533 + this.documentPropertiesButton = options.documentPropertiesButton; 1.1534 + 1.1535 + // Attach the event listeners. 1.1536 + var elements = [ 1.1537 + // Button to toggle the visibility of the secondary toolbar: 1.1538 + { element: this.toggleButton, handler: this.toggle }, 1.1539 + // All items within the secondary toolbar 1.1540 + // (except for toggleHandTool, hand_tool.js is responsible for it): 1.1541 + { element: this.presentationModeButton, 1.1542 + handler: this.presentationModeClick }, 1.1543 + { element: this.openFile, handler: this.openFileClick }, 1.1544 + { element: this.print, handler: this.printClick }, 1.1545 + { element: this.download, handler: this.downloadClick }, 1.1546 + { element: this.viewBookmark, handler: this.viewBookmarkClick }, 1.1547 + { element: this.firstPage, handler: this.firstPageClick }, 1.1548 + { element: this.lastPage, handler: this.lastPageClick }, 1.1549 + { element: this.pageRotateCw, handler: this.pageRotateCwClick }, 1.1550 + { element: this.pageRotateCcw, handler: this.pageRotateCcwClick }, 1.1551 + { element: this.documentPropertiesButton, 1.1552 + handler: this.documentPropertiesClick } 1.1553 + ]; 1.1554 + 1.1555 + for (var item in elements) { 1.1556 + var element = elements[item].element; 1.1557 + if (element) { 1.1558 + element.addEventListener('click', elements[item].handler.bind(this)); 1.1559 + } 1.1560 + } 1.1561 + }, 1.1562 + 1.1563 + // Event handling functions. 1.1564 + presentationModeClick: function secondaryToolbarPresentationModeClick(evt) { 1.1565 + this.presentationMode.request(); 1.1566 + this.close(); 1.1567 + }, 1.1568 + 1.1569 + openFileClick: function secondaryToolbarOpenFileClick(evt) { 1.1570 + document.getElementById('fileInput').click(); 1.1571 + this.close(); 1.1572 + }, 1.1573 + 1.1574 + printClick: function secondaryToolbarPrintClick(evt) { 1.1575 + window.print(); 1.1576 + this.close(); 1.1577 + }, 1.1578 + 1.1579 + downloadClick: function secondaryToolbarDownloadClick(evt) { 1.1580 + PDFView.download(); 1.1581 + this.close(); 1.1582 + }, 1.1583 + 1.1584 + viewBookmarkClick: function secondaryToolbarViewBookmarkClick(evt) { 1.1585 + this.close(); 1.1586 + }, 1.1587 + 1.1588 + firstPageClick: function secondaryToolbarFirstPageClick(evt) { 1.1589 + PDFView.page = 1; 1.1590 + this.close(); 1.1591 + }, 1.1592 + 1.1593 + lastPageClick: function secondaryToolbarLastPageClick(evt) { 1.1594 + PDFView.page = PDFView.pdfDocument.numPages; 1.1595 + this.close(); 1.1596 + }, 1.1597 + 1.1598 + pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) { 1.1599 + PDFView.rotatePages(90); 1.1600 + }, 1.1601 + 1.1602 + pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) { 1.1603 + PDFView.rotatePages(-90); 1.1604 + }, 1.1605 + 1.1606 + documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) { 1.1607 + this.documentProperties.show(); 1.1608 + this.close(); 1.1609 + }, 1.1610 + 1.1611 + // Misc. functions for interacting with the toolbar. 1.1612 + setMaxHeight: function secondaryToolbarSetMaxHeight(container) { 1.1613 + if (!container || !this.buttonContainer) { 1.1614 + return; 1.1615 + } 1.1616 + this.newContainerHeight = container.clientHeight; 1.1617 + if (this.previousContainerHeight === this.newContainerHeight) { 1.1618 + return; 1.1619 + } 1.1620 + this.buttonContainer.setAttribute('style', 1.1621 + 'max-height: ' + (this.newContainerHeight - SCROLLBAR_PADDING) + 'px;'); 1.1622 + this.previousContainerHeight = this.newContainerHeight; 1.1623 + }, 1.1624 + 1.1625 + open: function secondaryToolbarOpen() { 1.1626 + if (this.opened) { 1.1627 + return; 1.1628 + } 1.1629 + this.opened = true; 1.1630 + this.toggleButton.classList.add('toggled'); 1.1631 + this.toolbar.classList.remove('hidden'); 1.1632 + }, 1.1633 + 1.1634 + close: function secondaryToolbarClose(target) { 1.1635 + if (!this.opened) { 1.1636 + return; 1.1637 + } else if (target && !this.toolbar.contains(target)) { 1.1638 + return; 1.1639 + } 1.1640 + this.opened = false; 1.1641 + this.toolbar.classList.add('hidden'); 1.1642 + this.toggleButton.classList.remove('toggled'); 1.1643 + }, 1.1644 + 1.1645 + toggle: function secondaryToolbarToggle() { 1.1646 + if (this.opened) { 1.1647 + this.close(); 1.1648 + } else { 1.1649 + this.open(); 1.1650 + } 1.1651 + } 1.1652 +}; 1.1653 + 1.1654 + 1.1655 +var PasswordPrompt = { 1.1656 + visible: false, 1.1657 + updatePassword: null, 1.1658 + reason: null, 1.1659 + overlayContainer: null, 1.1660 + passwordField: null, 1.1661 + passwordText: null, 1.1662 + passwordSubmit: null, 1.1663 + passwordCancel: null, 1.1664 + 1.1665 + initialize: function secondaryToolbarInitialize(options) { 1.1666 + this.overlayContainer = options.overlayContainer; 1.1667 + this.passwordField = options.passwordField; 1.1668 + this.passwordText = options.passwordText; 1.1669 + this.passwordSubmit = options.passwordSubmit; 1.1670 + this.passwordCancel = options.passwordCancel; 1.1671 + 1.1672 + // Attach the event listeners. 1.1673 + this.passwordSubmit.addEventListener('click', 1.1674 + this.verifyPassword.bind(this)); 1.1675 + 1.1676 + this.passwordCancel.addEventListener('click', this.hide.bind(this)); 1.1677 + 1.1678 + this.passwordField.addEventListener('keydown', 1.1679 + function (e) { 1.1680 + if (e.keyCode === 13) { // Enter key 1.1681 + this.verifyPassword(); 1.1682 + } 1.1683 + }.bind(this)); 1.1684 + 1.1685 + window.addEventListener('keydown', 1.1686 + function (e) { 1.1687 + if (e.keyCode === 27) { // Esc key 1.1688 + this.hide(); 1.1689 + } 1.1690 + }.bind(this)); 1.1691 + }, 1.1692 + 1.1693 + show: function passwordPromptShow() { 1.1694 + if (this.visible) { 1.1695 + return; 1.1696 + } 1.1697 + this.visible = true; 1.1698 + this.overlayContainer.classList.remove('hidden'); 1.1699 + this.overlayContainer.firstElementChild.classList.remove('hidden'); 1.1700 + this.passwordField.focus(); 1.1701 + 1.1702 + var promptString = mozL10n.get('password_label', null, 1.1703 + 'Enter the password to open this PDF file.'); 1.1704 + 1.1705 + if (this.reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) { 1.1706 + promptString = mozL10n.get('password_invalid', null, 1.1707 + 'Invalid password. Please try again.'); 1.1708 + } 1.1709 + 1.1710 + this.passwordText.textContent = promptString; 1.1711 + }, 1.1712 + 1.1713 + hide: function passwordPromptClose() { 1.1714 + if (!this.visible) { 1.1715 + return; 1.1716 + } 1.1717 + this.visible = false; 1.1718 + this.passwordField.value = ''; 1.1719 + this.overlayContainer.classList.add('hidden'); 1.1720 + this.overlayContainer.firstElementChild.classList.add('hidden'); 1.1721 + }, 1.1722 + 1.1723 + verifyPassword: function passwordPromptVerifyPassword() { 1.1724 + var password = this.passwordField.value; 1.1725 + if (password && password.length > 0) { 1.1726 + this.hide(); 1.1727 + return this.updatePassword(password); 1.1728 + } 1.1729 + } 1.1730 +}; 1.1731 + 1.1732 + 1.1733 +var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms 1.1734 +var SELECTOR = 'presentationControls'; 1.1735 +var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1000; // in ms 1.1736 + 1.1737 +var PresentationMode = { 1.1738 + active: false, 1.1739 + args: null, 1.1740 + contextMenuOpen: false, 1.1741 + 1.1742 + initialize: function presentationModeInitialize(options) { 1.1743 + this.container = options.container; 1.1744 + this.secondaryToolbar = options.secondaryToolbar; 1.1745 + 1.1746 + this.viewer = this.container.firstElementChild; 1.1747 + 1.1748 + this.firstPage = options.firstPage; 1.1749 + this.lastPage = options.lastPage; 1.1750 + this.pageRotateCw = options.pageRotateCw; 1.1751 + this.pageRotateCcw = options.pageRotateCcw; 1.1752 + 1.1753 + this.firstPage.addEventListener('click', function() { 1.1754 + this.contextMenuOpen = false; 1.1755 + this.secondaryToolbar.firstPageClick(); 1.1756 + }.bind(this)); 1.1757 + this.lastPage.addEventListener('click', function() { 1.1758 + this.contextMenuOpen = false; 1.1759 + this.secondaryToolbar.lastPageClick(); 1.1760 + }.bind(this)); 1.1761 + 1.1762 + this.pageRotateCw.addEventListener('click', function() { 1.1763 + this.contextMenuOpen = false; 1.1764 + this.secondaryToolbar.pageRotateCwClick(); 1.1765 + }.bind(this)); 1.1766 + this.pageRotateCcw.addEventListener('click', function() { 1.1767 + this.contextMenuOpen = false; 1.1768 + this.secondaryToolbar.pageRotateCcwClick(); 1.1769 + }.bind(this)); 1.1770 + }, 1.1771 + 1.1772 + get isFullscreen() { 1.1773 + return (document.fullscreenElement || 1.1774 + document.mozFullScreen || 1.1775 + document.webkitIsFullScreen || 1.1776 + document.msFullscreenElement); 1.1777 + }, 1.1778 + 1.1779 + /** 1.1780 + * Initialize a timeout that is used to reset PDFView.currentPosition when the 1.1781 + * browser transitions to fullscreen mode. Since resize events are triggered 1.1782 + * multiple times during the switch to fullscreen mode, this is necessary in 1.1783 + * order to prevent the page from being scrolled partially, or completely, 1.1784 + * out of view when Presentation Mode is enabled. 1.1785 + * Note: This is only an issue at certain zoom levels, e.g. 'page-width'. 1.1786 + */ 1.1787 + _setSwitchInProgress: function presentationMode_setSwitchInProgress() { 1.1788 + if (this.switchInProgress) { 1.1789 + clearTimeout(this.switchInProgress); 1.1790 + } 1.1791 + this.switchInProgress = setTimeout(function switchInProgressTimeout() { 1.1792 + delete this.switchInProgress; 1.1793 + }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS); 1.1794 + 1.1795 + PDFView.currentPosition = null; 1.1796 + }, 1.1797 + 1.1798 + _resetSwitchInProgress: function presentationMode_resetSwitchInProgress() { 1.1799 + if (this.switchInProgress) { 1.1800 + clearTimeout(this.switchInProgress); 1.1801 + delete this.switchInProgress; 1.1802 + } 1.1803 + }, 1.1804 + 1.1805 + request: function presentationModeRequest() { 1.1806 + if (!PDFView.supportsFullscreen || this.isFullscreen || 1.1807 + !this.viewer.hasChildNodes()) { 1.1808 + return false; 1.1809 + } 1.1810 + this._setSwitchInProgress(); 1.1811 + 1.1812 + if (this.container.requestFullscreen) { 1.1813 + this.container.requestFullscreen(); 1.1814 + } else if (this.container.mozRequestFullScreen) { 1.1815 + this.container.mozRequestFullScreen(); 1.1816 + } else if (this.container.webkitRequestFullScreen) { 1.1817 + this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); 1.1818 + } else if (this.container.msRequestFullscreen) { 1.1819 + this.container.msRequestFullscreen(); 1.1820 + } else { 1.1821 + return false; 1.1822 + } 1.1823 + 1.1824 + this.args = { 1.1825 + page: PDFView.page, 1.1826 + previousScale: PDFView.currentScaleValue 1.1827 + }; 1.1828 + 1.1829 + return true; 1.1830 + }, 1.1831 + 1.1832 + enter: function presentationModeEnter() { 1.1833 + this.active = true; 1.1834 + this._resetSwitchInProgress(); 1.1835 + 1.1836 + // Ensure that the correct page is scrolled into view when entering 1.1837 + // Presentation Mode, by waiting until fullscreen mode in enabled. 1.1838 + // Note: This is only necessary in non-Mozilla browsers. 1.1839 + setTimeout(function enterPresentationModeTimeout() { 1.1840 + PDFView.page = this.args.page; 1.1841 + PDFView.setScale('page-fit', true); 1.1842 + }.bind(this), 0); 1.1843 + 1.1844 + window.addEventListener('mousemove', this.mouseMove, false); 1.1845 + window.addEventListener('mousedown', this.mouseDown, false); 1.1846 + window.addEventListener('contextmenu', this.contextMenu, false); 1.1847 + 1.1848 + this.showControls(); 1.1849 + HandTool.enterPresentationMode(); 1.1850 + this.contextMenuOpen = false; 1.1851 + this.container.setAttribute('contextmenu', 'viewerContextMenu'); 1.1852 + }, 1.1853 + 1.1854 + exit: function presentationModeExit() { 1.1855 + var page = PDFView.page; 1.1856 + 1.1857 + // Ensure that the correct page is scrolled into view when exiting 1.1858 + // Presentation Mode, by waiting until fullscreen mode is disabled. 1.1859 + // Note: This is only necessary in non-Mozilla browsers. 1.1860 + setTimeout(function exitPresentationModeTimeout() { 1.1861 + this.active = false; 1.1862 + PDFView.setScale(this.args.previousScale); 1.1863 + PDFView.page = page; 1.1864 + this.args = null; 1.1865 + }.bind(this), 0); 1.1866 + 1.1867 + window.removeEventListener('mousemove', this.mouseMove, false); 1.1868 + window.removeEventListener('mousedown', this.mouseDown, false); 1.1869 + window.removeEventListener('contextmenu', this.contextMenu, false); 1.1870 + 1.1871 + this.hideControls(); 1.1872 + PDFView.clearMouseScrollState(); 1.1873 + HandTool.exitPresentationMode(); 1.1874 + this.container.removeAttribute('contextmenu'); 1.1875 + this.contextMenuOpen = false; 1.1876 + 1.1877 + // Ensure that the thumbnail of the current page is visible 1.1878 + // when exiting presentation mode. 1.1879 + scrollIntoView(document.getElementById('thumbnailContainer' + page)); 1.1880 + }, 1.1881 + 1.1882 + showControls: function presentationModeShowControls() { 1.1883 + if (this.controlsTimeout) { 1.1884 + clearTimeout(this.controlsTimeout); 1.1885 + } else { 1.1886 + this.container.classList.add(SELECTOR); 1.1887 + } 1.1888 + this.controlsTimeout = setTimeout(function hideControlsTimeout() { 1.1889 + this.container.classList.remove(SELECTOR); 1.1890 + delete this.controlsTimeout; 1.1891 + }.bind(this), DELAY_BEFORE_HIDING_CONTROLS); 1.1892 + }, 1.1893 + 1.1894 + hideControls: function presentationModeHideControls() { 1.1895 + if (!this.controlsTimeout) { 1.1896 + return; 1.1897 + } 1.1898 + this.container.classList.remove(SELECTOR); 1.1899 + clearTimeout(this.controlsTimeout); 1.1900 + delete this.controlsTimeout; 1.1901 + }, 1.1902 + 1.1903 + mouseMove: function presentationModeMouseMove(evt) { 1.1904 + PresentationMode.showControls(); 1.1905 + }, 1.1906 + 1.1907 + mouseDown: function presentationModeMouseDown(evt) { 1.1908 + var self = PresentationMode; 1.1909 + if (self.contextMenuOpen) { 1.1910 + self.contextMenuOpen = false; 1.1911 + evt.preventDefault(); 1.1912 + return; 1.1913 + } 1.1914 + 1.1915 + if (evt.button === 0) { 1.1916 + // Enable clicking of links in presentation mode. Please note: 1.1917 + // Only links pointing to destinations in the current PDF document work. 1.1918 + var isInternalLink = (evt.target.href && 1.1919 + evt.target.classList.contains('internalLink')); 1.1920 + if (!isInternalLink) { 1.1921 + // Unless an internal link was clicked, advance one page. 1.1922 + evt.preventDefault(); 1.1923 + PDFView.page += (evt.shiftKey ? -1 : 1); 1.1924 + } 1.1925 + } 1.1926 + }, 1.1927 + 1.1928 + contextMenu: function presentationModeContextMenu(evt) { 1.1929 + PresentationMode.contextMenuOpen = true; 1.1930 + } 1.1931 +}; 1.1932 + 1.1933 +(function presentationModeClosure() { 1.1934 + function presentationModeChange(e) { 1.1935 + if (PresentationMode.isFullscreen) { 1.1936 + PresentationMode.enter(); 1.1937 + } else { 1.1938 + PresentationMode.exit(); 1.1939 + } 1.1940 + } 1.1941 + 1.1942 + window.addEventListener('fullscreenchange', presentationModeChange, false); 1.1943 + window.addEventListener('mozfullscreenchange', presentationModeChange, false); 1.1944 + window.addEventListener('webkitfullscreenchange', presentationModeChange, 1.1945 + false); 1.1946 + window.addEventListener('MSFullscreenChange', presentationModeChange, false); 1.1947 +})(); 1.1948 + 1.1949 + 1.1950 +/* Copyright 2013 Rob Wu <gwnRob@gmail.com> 1.1951 + * https://github.com/Rob--W/grab-to-pan.js 1.1952 + * 1.1953 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.1954 + * you may not use this file except in compliance with the License. 1.1955 + * You may obtain a copy of the License at 1.1956 + * 1.1957 + * http://www.apache.org/licenses/LICENSE-2.0 1.1958 + * 1.1959 + * Unless required by applicable law or agreed to in writing, software 1.1960 + * distributed under the License is distributed on an "AS IS" BASIS, 1.1961 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.1962 + * See the License for the specific language governing permissions and 1.1963 + * limitations under the License. 1.1964 + */ 1.1965 + 1.1966 +'use strict'; 1.1967 + 1.1968 +var GrabToPan = (function GrabToPanClosure() { 1.1969 + /** 1.1970 + * Construct a GrabToPan instance for a given HTML element. 1.1971 + * @param options.element {Element} 1.1972 + * @param options.ignoreTarget {function} optional. See `ignoreTarget(node)` 1.1973 + * @param options.onActiveChanged {function(boolean)} optional. Called 1.1974 + * when grab-to-pan is (de)activated. The first argument is a boolean that 1.1975 + * shows whether grab-to-pan is activated. 1.1976 + */ 1.1977 + function GrabToPan(options) { 1.1978 + this.element = options.element; 1.1979 + this.document = options.element.ownerDocument; 1.1980 + if (typeof options.ignoreTarget === 'function') { 1.1981 + this.ignoreTarget = options.ignoreTarget; 1.1982 + } 1.1983 + this.onActiveChanged = options.onActiveChanged; 1.1984 + 1.1985 + // Bind the contexts to ensure that `this` always points to 1.1986 + // the GrabToPan instance. 1.1987 + this.activate = this.activate.bind(this); 1.1988 + this.deactivate = this.deactivate.bind(this); 1.1989 + this.toggle = this.toggle.bind(this); 1.1990 + this._onmousedown = this._onmousedown.bind(this); 1.1991 + this._onmousemove = this._onmousemove.bind(this); 1.1992 + this._endPan = this._endPan.bind(this); 1.1993 + 1.1994 + // This overlay will be inserted in the document when the mouse moves during 1.1995 + // a grab operation, to ensure that the cursor has the desired appearance. 1.1996 + var overlay = this.overlay = document.createElement('div'); 1.1997 + overlay.className = 'grab-to-pan-grabbing'; 1.1998 + } 1.1999 + GrabToPan.prototype = { 1.2000 + /** 1.2001 + * Class name of element which can be grabbed 1.2002 + */ 1.2003 + CSS_CLASS_GRAB: 'grab-to-pan-grab', 1.2004 + 1.2005 + /** 1.2006 + * Bind a mousedown event to the element to enable grab-detection. 1.2007 + */ 1.2008 + activate: function GrabToPan_activate() { 1.2009 + if (!this.active) { 1.2010 + this.active = true; 1.2011 + this.element.addEventListener('mousedown', this._onmousedown, true); 1.2012 + this.element.classList.add(this.CSS_CLASS_GRAB); 1.2013 + if (this.onActiveChanged) { 1.2014 + this.onActiveChanged(true); 1.2015 + } 1.2016 + } 1.2017 + }, 1.2018 + 1.2019 + /** 1.2020 + * Removes all events. Any pending pan session is immediately stopped. 1.2021 + */ 1.2022 + deactivate: function GrabToPan_deactivate() { 1.2023 + if (this.active) { 1.2024 + this.active = false; 1.2025 + this.element.removeEventListener('mousedown', this._onmousedown, true); 1.2026 + this._endPan(); 1.2027 + this.element.classList.remove(this.CSS_CLASS_GRAB); 1.2028 + if (this.onActiveChanged) { 1.2029 + this.onActiveChanged(false); 1.2030 + } 1.2031 + } 1.2032 + }, 1.2033 + 1.2034 + toggle: function GrabToPan_toggle() { 1.2035 + if (this.active) { 1.2036 + this.deactivate(); 1.2037 + } else { 1.2038 + this.activate(); 1.2039 + } 1.2040 + }, 1.2041 + 1.2042 + /** 1.2043 + * Whether to not pan if the target element is clicked. 1.2044 + * Override this method to change the default behaviour. 1.2045 + * 1.2046 + * @param node {Element} The target of the event 1.2047 + * @return {boolean} Whether to not react to the click event. 1.2048 + */ 1.2049 + ignoreTarget: function GrabToPan_ignoreTarget(node) { 1.2050 + // Use matchesSelector to check whether the clicked element 1.2051 + // is (a child of) an input element / link 1.2052 + return node[matchesSelector]( 1.2053 + 'a[href], a[href] *, input, textarea, button, button *, select, option' 1.2054 + ); 1.2055 + }, 1.2056 + 1.2057 + /** 1.2058 + * @private 1.2059 + */ 1.2060 + _onmousedown: function GrabToPan__onmousedown(event) { 1.2061 + if (event.button !== 0 || this.ignoreTarget(event.target)) { 1.2062 + return; 1.2063 + } 1.2064 + if (event.originalTarget) { 1.2065 + try { 1.2066 + /* jshint expr:true */ 1.2067 + event.originalTarget.tagName; 1.2068 + } catch (e) { 1.2069 + // Mozilla-specific: element is a scrollbar (XUL element) 1.2070 + return; 1.2071 + } 1.2072 + } 1.2073 + 1.2074 + this.scrollLeftStart = this.element.scrollLeft; 1.2075 + this.scrollTopStart = this.element.scrollTop; 1.2076 + this.clientXStart = event.clientX; 1.2077 + this.clientYStart = event.clientY; 1.2078 + this.document.addEventListener('mousemove', this._onmousemove, true); 1.2079 + this.document.addEventListener('mouseup', this._endPan, true); 1.2080 + // When a scroll event occurs before a mousemove, assume that the user 1.2081 + // dragged a scrollbar (necessary for Opera Presto, Safari and IE) 1.2082 + // (not needed for Chrome/Firefox) 1.2083 + this.element.addEventListener('scroll', this._endPan, true); 1.2084 + event.preventDefault(); 1.2085 + event.stopPropagation(); 1.2086 + this.document.documentElement.classList.add(this.CSS_CLASS_GRABBING); 1.2087 + }, 1.2088 + 1.2089 + /** 1.2090 + * @private 1.2091 + */ 1.2092 + _onmousemove: function GrabToPan__onmousemove(event) { 1.2093 + this.element.removeEventListener('scroll', this._endPan, true); 1.2094 + if (isLeftMouseReleased(event)) { 1.2095 + this._endPan(); 1.2096 + return; 1.2097 + } 1.2098 + var xDiff = event.clientX - this.clientXStart; 1.2099 + var yDiff = event.clientY - this.clientYStart; 1.2100 + this.element.scrollTop = this.scrollTopStart - yDiff; 1.2101 + this.element.scrollLeft = this.scrollLeftStart - xDiff; 1.2102 + if (!this.overlay.parentNode) { 1.2103 + document.body.appendChild(this.overlay); 1.2104 + } 1.2105 + }, 1.2106 + 1.2107 + /** 1.2108 + * @private 1.2109 + */ 1.2110 + _endPan: function GrabToPan__endPan() { 1.2111 + this.element.removeEventListener('scroll', this._endPan, true); 1.2112 + this.document.removeEventListener('mousemove', this._onmousemove, true); 1.2113 + this.document.removeEventListener('mouseup', this._endPan, true); 1.2114 + if (this.overlay.parentNode) { 1.2115 + this.overlay.parentNode.removeChild(this.overlay); 1.2116 + } 1.2117 + } 1.2118 + }; 1.2119 + 1.2120 + // Get the correct (vendor-prefixed) name of the matches method. 1.2121 + var matchesSelector; 1.2122 + ['webkitM', 'mozM', 'msM', 'oM', 'm'].some(function(prefix) { 1.2123 + var name = prefix + 'atches'; 1.2124 + if (name in document.documentElement) { 1.2125 + matchesSelector = name; 1.2126 + } 1.2127 + name += 'Selector'; 1.2128 + if (name in document.documentElement) { 1.2129 + matchesSelector = name; 1.2130 + } 1.2131 + return matchesSelector; // If found, then truthy, and [].some() ends. 1.2132 + }); 1.2133 + 1.2134 + // Browser sniffing because it's impossible to feature-detect 1.2135 + // whether event.which for onmousemove is reliable 1.2136 + var isNotIEorIsIE10plus = !document.documentMode || document.documentMode > 9; 1.2137 + var chrome = window.chrome; 1.2138 + var isChrome15OrOpera15plus = chrome && (chrome.webstore || chrome.app); 1.2139 + // ^ Chrome 15+ ^ Opera 15+ 1.2140 + var isSafari6plus = /Apple/.test(navigator.vendor) && 1.2141 + /Version\/([6-9]\d*|[1-5]\d+)/.test(navigator.userAgent); 1.2142 + 1.2143 + /** 1.2144 + * Whether the left mouse is not pressed. 1.2145 + * @param event {MouseEvent} 1.2146 + * @return {boolean} True if the left mouse button is not pressed. 1.2147 + * False if unsure or if the left mouse button is pressed. 1.2148 + */ 1.2149 + function isLeftMouseReleased(event) { 1.2150 + if ('buttons' in event && isNotIEorIsIE10plus) { 1.2151 + // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-buttons 1.2152 + // Firefox 15+ 1.2153 + // Internet Explorer 10+ 1.2154 + return !(event.buttons | 1); 1.2155 + } 1.2156 + if (isChrome15OrOpera15plus || isSafari6plus) { 1.2157 + // Chrome 14+ 1.2158 + // Opera 15+ 1.2159 + // Safari 6.0+ 1.2160 + return event.which === 0; 1.2161 + } 1.2162 + } 1.2163 + 1.2164 + return GrabToPan; 1.2165 +})(); 1.2166 + 1.2167 +var HandTool = { 1.2168 + initialize: function handToolInitialize(options) { 1.2169 + var toggleHandTool = options.toggleHandTool; 1.2170 + this.handTool = new GrabToPan({ 1.2171 + element: options.container, 1.2172 + onActiveChanged: function(isActive) { 1.2173 + if (!toggleHandTool) { 1.2174 + return; 1.2175 + } 1.2176 + if (isActive) { 1.2177 + toggleHandTool.title = 1.2178 + mozL10n.get('hand_tool_disable.title', null, 'Disable hand tool'); 1.2179 + toggleHandTool.firstElementChild.textContent = 1.2180 + mozL10n.get('hand_tool_disable_label', null, 'Disable hand tool'); 1.2181 + } else { 1.2182 + toggleHandTool.title = 1.2183 + mozL10n.get('hand_tool_enable.title', null, 'Enable hand tool'); 1.2184 + toggleHandTool.firstElementChild.textContent = 1.2185 + mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool'); 1.2186 + } 1.2187 + } 1.2188 + }); 1.2189 + if (toggleHandTool) { 1.2190 + toggleHandTool.addEventListener('click', this.toggle.bind(this), false); 1.2191 + 1.2192 + window.addEventListener('localized', function (evt) { 1.2193 + Preferences.get('enableHandToolOnLoad').then(function (prefValue) { 1.2194 + if (prefValue) { 1.2195 + this.handTool.activate(); 1.2196 + } 1.2197 + }.bind(this)); 1.2198 + }.bind(this)); 1.2199 + } 1.2200 + }, 1.2201 + 1.2202 + toggle: function handToolToggle() { 1.2203 + this.handTool.toggle(); 1.2204 + SecondaryToolbar.close(); 1.2205 + }, 1.2206 + 1.2207 + enterPresentationMode: function handToolEnterPresentationMode() { 1.2208 + if (this.handTool.active) { 1.2209 + this.wasActive = true; 1.2210 + this.handTool.deactivate(); 1.2211 + } 1.2212 + }, 1.2213 + 1.2214 + exitPresentationMode: function handToolExitPresentationMode() { 1.2215 + if (this.wasActive) { 1.2216 + this.wasActive = null; 1.2217 + this.handTool.activate(); 1.2218 + } 1.2219 + } 1.2220 +}; 1.2221 + 1.2222 + 1.2223 +var DocumentProperties = { 1.2224 + overlayContainer: null, 1.2225 + fileName: '', 1.2226 + fileSize: '', 1.2227 + visible: false, 1.2228 + 1.2229 + // Document property fields (in the viewer). 1.2230 + fileNameField: null, 1.2231 + fileSizeField: null, 1.2232 + titleField: null, 1.2233 + authorField: null, 1.2234 + subjectField: null, 1.2235 + keywordsField: null, 1.2236 + creationDateField: null, 1.2237 + modificationDateField: null, 1.2238 + creatorField: null, 1.2239 + producerField: null, 1.2240 + versionField: null, 1.2241 + pageCountField: null, 1.2242 + 1.2243 + initialize: function documentPropertiesInitialize(options) { 1.2244 + this.overlayContainer = options.overlayContainer; 1.2245 + 1.2246 + // Set the document property fields. 1.2247 + this.fileNameField = options.fileNameField; 1.2248 + this.fileSizeField = options.fileSizeField; 1.2249 + this.titleField = options.titleField; 1.2250 + this.authorField = options.authorField; 1.2251 + this.subjectField = options.subjectField; 1.2252 + this.keywordsField = options.keywordsField; 1.2253 + this.creationDateField = options.creationDateField; 1.2254 + this.modificationDateField = options.modificationDateField; 1.2255 + this.creatorField = options.creatorField; 1.2256 + this.producerField = options.producerField; 1.2257 + this.versionField = options.versionField; 1.2258 + this.pageCountField = options.pageCountField; 1.2259 + 1.2260 + // Bind the event listener for the Close button. 1.2261 + if (options.closeButton) { 1.2262 + options.closeButton.addEventListener('click', this.hide.bind(this)); 1.2263 + } 1.2264 + 1.2265 + this.dataAvailablePromise = new Promise(function (resolve) { 1.2266 + this.resolveDataAvailable = resolve; 1.2267 + }.bind(this)); 1.2268 + 1.2269 + // Bind the event listener for the Esc key (to close the dialog). 1.2270 + window.addEventListener('keydown', 1.2271 + function (e) { 1.2272 + if (e.keyCode === 27) { // Esc key 1.2273 + this.hide(); 1.2274 + } 1.2275 + }.bind(this)); 1.2276 + }, 1.2277 + 1.2278 + getProperties: function documentPropertiesGetProperties() { 1.2279 + if (!this.visible) { 1.2280 + // If the dialog was closed before dataAvailablePromise was resolved, 1.2281 + // don't bother updating the properties. 1.2282 + return; 1.2283 + } 1.2284 + // Get the file name. 1.2285 + this.fileName = getPDFFileNameFromURL(PDFView.url); 1.2286 + 1.2287 + // Get the file size. 1.2288 + PDFView.pdfDocument.getDownloadInfo().then(function(data) { 1.2289 + this.setFileSize(data.length); 1.2290 + this.updateUI(this.fileSizeField, this.fileSize); 1.2291 + }.bind(this)); 1.2292 + 1.2293 + // Get the other document properties. 1.2294 + PDFView.pdfDocument.getMetadata().then(function(data) { 1.2295 + var fields = [ 1.2296 + { field: this.fileNameField, content: this.fileName }, 1.2297 + // The fileSize field is updated once getDownloadInfo is resolved. 1.2298 + { field: this.titleField, content: data.info.Title }, 1.2299 + { field: this.authorField, content: data.info.Author }, 1.2300 + { field: this.subjectField, content: data.info.Subject }, 1.2301 + { field: this.keywordsField, content: data.info.Keywords }, 1.2302 + { field: this.creationDateField, 1.2303 + content: this.parseDate(data.info.CreationDate) }, 1.2304 + { field: this.modificationDateField, 1.2305 + content: this.parseDate(data.info.ModDate) }, 1.2306 + { field: this.creatorField, content: data.info.Creator }, 1.2307 + { field: this.producerField, content: data.info.Producer }, 1.2308 + { field: this.versionField, content: data.info.PDFFormatVersion }, 1.2309 + { field: this.pageCountField, content: PDFView.pdfDocument.numPages } 1.2310 + ]; 1.2311 + 1.2312 + // Show the properties in the dialog. 1.2313 + for (var item in fields) { 1.2314 + var element = fields[item]; 1.2315 + this.updateUI(element.field, element.content); 1.2316 + } 1.2317 + }.bind(this)); 1.2318 + }, 1.2319 + 1.2320 + updateUI: function documentPropertiesUpdateUI(field, content) { 1.2321 + if (field && content !== undefined && content !== '') { 1.2322 + field.textContent = content; 1.2323 + } 1.2324 + }, 1.2325 + 1.2326 + setFileSize: function documentPropertiesSetFileSize(fileSize) { 1.2327 + var kb = fileSize / 1024; 1.2328 + if (kb < 1024) { 1.2329 + this.fileSize = mozL10n.get('document_properties_kb', { 1.2330 + size_kb: (+kb.toPrecision(3)).toLocaleString(), 1.2331 + size_b: fileSize.toLocaleString() 1.2332 + }, '{{size_kb}} KB ({{size_b}} bytes)'); 1.2333 + } else { 1.2334 + this.fileSize = mozL10n.get('document_properties_mb', { 1.2335 + size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(), 1.2336 + size_b: fileSize.toLocaleString() 1.2337 + }, '{{size_mb}} MB ({{size_b}} bytes)'); 1.2338 + } 1.2339 + }, 1.2340 + 1.2341 + show: function documentPropertiesShow() { 1.2342 + if (this.visible) { 1.2343 + return; 1.2344 + } 1.2345 + this.visible = true; 1.2346 + this.overlayContainer.classList.remove('hidden'); 1.2347 + this.overlayContainer.lastElementChild.classList.remove('hidden'); 1.2348 + 1.2349 + this.dataAvailablePromise.then(function () { 1.2350 + this.getProperties(); 1.2351 + }.bind(this)); 1.2352 + }, 1.2353 + 1.2354 + hide: function documentPropertiesClose() { 1.2355 + if (!this.visible) { 1.2356 + return; 1.2357 + } 1.2358 + this.visible = false; 1.2359 + this.overlayContainer.classList.add('hidden'); 1.2360 + this.overlayContainer.lastElementChild.classList.add('hidden'); 1.2361 + }, 1.2362 + 1.2363 + parseDate: function documentPropertiesParseDate(inputDate) { 1.2364 + // This is implemented according to the PDF specification (see 1.2365 + // http://www.gnupdf.org/Date for an overview), but note that 1.2366 + // Adobe Reader doesn't handle changing the date to universal time 1.2367 + // and doesn't use the user's time zone (they're effectively ignoring 1.2368 + // the HH' and mm' parts of the date string). 1.2369 + var dateToParse = inputDate; 1.2370 + if (dateToParse === undefined) { 1.2371 + return ''; 1.2372 + } 1.2373 + 1.2374 + // Remove the D: prefix if it is available. 1.2375 + if (dateToParse.substring(0,2) === 'D:') { 1.2376 + dateToParse = dateToParse.substring(2); 1.2377 + } 1.2378 + 1.2379 + // Get all elements from the PDF date string. 1.2380 + // JavaScript's Date object expects the month to be between 1.2381 + // 0 and 11 instead of 1 and 12, so we're correcting for this. 1.2382 + var year = parseInt(dateToParse.substring(0,4), 10); 1.2383 + var month = parseInt(dateToParse.substring(4,6), 10) - 1; 1.2384 + var day = parseInt(dateToParse.substring(6,8), 10); 1.2385 + var hours = parseInt(dateToParse.substring(8,10), 10); 1.2386 + var minutes = parseInt(dateToParse.substring(10,12), 10); 1.2387 + var seconds = parseInt(dateToParse.substring(12,14), 10); 1.2388 + var utRel = dateToParse.substring(14,15); 1.2389 + var offsetHours = parseInt(dateToParse.substring(15,17), 10); 1.2390 + var offsetMinutes = parseInt(dateToParse.substring(18,20), 10); 1.2391 + 1.2392 + // As per spec, utRel = 'Z' means equal to universal time. 1.2393 + // The other cases ('-' and '+') have to be handled here. 1.2394 + if (utRel == '-') { 1.2395 + hours += offsetHours; 1.2396 + minutes += offsetMinutes; 1.2397 + } else if (utRel == '+') { 1.2398 + hours -= offsetHours; 1.2399 + minutes += offsetMinutes; 1.2400 + } 1.2401 + 1.2402 + // Return the new date format from the user's locale. 1.2403 + var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds)); 1.2404 + var dateString = date.toLocaleDateString(); 1.2405 + var timeString = date.toLocaleTimeString(); 1.2406 + return mozL10n.get('document_properties_date_string', 1.2407 + {date: dateString, time: timeString}, 1.2408 + '{{date}}, {{time}}'); 1.2409 + } 1.2410 +}; 1.2411 + 1.2412 + 1.2413 +var PDFView = { 1.2414 + pages: [], 1.2415 + thumbnails: [], 1.2416 + currentScale: UNKNOWN_SCALE, 1.2417 + currentScaleValue: null, 1.2418 + initialBookmark: document.location.hash.substring(1), 1.2419 + container: null, 1.2420 + thumbnailContainer: null, 1.2421 + initialized: false, 1.2422 + fellback: false, 1.2423 + pdfDocument: null, 1.2424 + sidebarOpen: false, 1.2425 + pageViewScroll: null, 1.2426 + thumbnailViewScroll: null, 1.2427 + pageRotation: 0, 1.2428 + mouseScrollTimeStamp: 0, 1.2429 + mouseScrollDelta: 0, 1.2430 + lastScroll: 0, 1.2431 + previousPageNumber: 1, 1.2432 + isViewerEmbedded: (window.parent !== window), 1.2433 + idleTimeout: null, 1.2434 + currentPosition: null, 1.2435 + 1.2436 + // called once when the document is loaded 1.2437 + initialize: function pdfViewInitialize() { 1.2438 + var self = this; 1.2439 + var container = this.container = document.getElementById('viewerContainer'); 1.2440 + this.pageViewScroll = {}; 1.2441 + this.watchScroll(container, this.pageViewScroll, updateViewarea); 1.2442 + 1.2443 + var thumbnailContainer = this.thumbnailContainer = 1.2444 + document.getElementById('thumbnailView'); 1.2445 + this.thumbnailViewScroll = {}; 1.2446 + this.watchScroll(thumbnailContainer, this.thumbnailViewScroll, 1.2447 + this.renderHighestPriority.bind(this)); 1.2448 + 1.2449 + Preferences.initialize(); 1.2450 + 1.2451 + PDFFindBar.initialize({ 1.2452 + bar: document.getElementById('findbar'), 1.2453 + toggleButton: document.getElementById('viewFind'), 1.2454 + findField: document.getElementById('findInput'), 1.2455 + highlightAllCheckbox: document.getElementById('findHighlightAll'), 1.2456 + caseSensitiveCheckbox: document.getElementById('findMatchCase'), 1.2457 + findMsg: document.getElementById('findMsg'), 1.2458 + findStatusIcon: document.getElementById('findStatusIcon'), 1.2459 + findPreviousButton: document.getElementById('findPrevious'), 1.2460 + findNextButton: document.getElementById('findNext') 1.2461 + }); 1.2462 + 1.2463 + PDFFindController.initialize({ 1.2464 + pdfPageSource: this, 1.2465 + integratedFind: this.supportsIntegratedFind 1.2466 + }); 1.2467 + 1.2468 + HandTool.initialize({ 1.2469 + container: container, 1.2470 + toggleHandTool: document.getElementById('toggleHandTool') 1.2471 + }); 1.2472 + 1.2473 + SecondaryToolbar.initialize({ 1.2474 + toolbar: document.getElementById('secondaryToolbar'), 1.2475 + presentationMode: PresentationMode, 1.2476 + toggleButton: document.getElementById('secondaryToolbarToggle'), 1.2477 + presentationModeButton: 1.2478 + document.getElementById('secondaryPresentationMode'), 1.2479 + openFile: document.getElementById('secondaryOpenFile'), 1.2480 + print: document.getElementById('secondaryPrint'), 1.2481 + download: document.getElementById('secondaryDownload'), 1.2482 + viewBookmark: document.getElementById('secondaryViewBookmark'), 1.2483 + firstPage: document.getElementById('firstPage'), 1.2484 + lastPage: document.getElementById('lastPage'), 1.2485 + pageRotateCw: document.getElementById('pageRotateCw'), 1.2486 + pageRotateCcw: document.getElementById('pageRotateCcw'), 1.2487 + documentProperties: DocumentProperties, 1.2488 + documentPropertiesButton: document.getElementById('documentProperties') 1.2489 + }); 1.2490 + 1.2491 + PasswordPrompt.initialize({ 1.2492 + overlayContainer: document.getElementById('overlayContainer'), 1.2493 + passwordField: document.getElementById('password'), 1.2494 + passwordText: document.getElementById('passwordText'), 1.2495 + passwordSubmit: document.getElementById('passwordSubmit'), 1.2496 + passwordCancel: document.getElementById('passwordCancel') 1.2497 + }); 1.2498 + 1.2499 + PresentationMode.initialize({ 1.2500 + container: container, 1.2501 + secondaryToolbar: SecondaryToolbar, 1.2502 + firstPage: document.getElementById('contextFirstPage'), 1.2503 + lastPage: document.getElementById('contextLastPage'), 1.2504 + pageRotateCw: document.getElementById('contextPageRotateCw'), 1.2505 + pageRotateCcw: document.getElementById('contextPageRotateCcw') 1.2506 + }); 1.2507 + 1.2508 + DocumentProperties.initialize({ 1.2509 + overlayContainer: document.getElementById('overlayContainer'), 1.2510 + closeButton: document.getElementById('documentPropertiesClose'), 1.2511 + fileNameField: document.getElementById('fileNameField'), 1.2512 + fileSizeField: document.getElementById('fileSizeField'), 1.2513 + titleField: document.getElementById('titleField'), 1.2514 + authorField: document.getElementById('authorField'), 1.2515 + subjectField: document.getElementById('subjectField'), 1.2516 + keywordsField: document.getElementById('keywordsField'), 1.2517 + creationDateField: document.getElementById('creationDateField'), 1.2518 + modificationDateField: document.getElementById('modificationDateField'), 1.2519 + creatorField: document.getElementById('creatorField'), 1.2520 + producerField: document.getElementById('producerField'), 1.2521 + versionField: document.getElementById('versionField'), 1.2522 + pageCountField: document.getElementById('pageCountField') 1.2523 + }); 1.2524 + 1.2525 + container.addEventListener('scroll', function() { 1.2526 + self.lastScroll = Date.now(); 1.2527 + }, false); 1.2528 + 1.2529 + var initializedPromise = Promise.all([ 1.2530 + Preferences.get('enableWebGL').then(function (value) { 1.2531 + PDFJS.disableWebGL = !value; 1.2532 + }) 1.2533 + // TODO move more preferences and other async stuff here 1.2534 + ]); 1.2535 + 1.2536 + return initializedPromise.then(function () { 1.2537 + PDFView.initialized = true; 1.2538 + }); 1.2539 + }, 1.2540 + 1.2541 + getPage: function pdfViewGetPage(n) { 1.2542 + return this.pdfDocument.getPage(n); 1.2543 + }, 1.2544 + 1.2545 + // Helper function to keep track whether a div was scrolled up or down and 1.2546 + // then call a callback. 1.2547 + watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) { 1.2548 + state.down = true; 1.2549 + state.lastY = viewAreaElement.scrollTop; 1.2550 + viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) { 1.2551 + var currentY = viewAreaElement.scrollTop; 1.2552 + var lastY = state.lastY; 1.2553 + if (currentY > lastY) { 1.2554 + state.down = true; 1.2555 + } else if (currentY < lastY) { 1.2556 + state.down = false; 1.2557 + } 1.2558 + // else do nothing and use previous value 1.2559 + state.lastY = currentY; 1.2560 + callback(); 1.2561 + }, true); 1.2562 + }, 1.2563 + 1.2564 + _setScaleUpdatePages: function pdfView_setScaleUpdatePages( 1.2565 + newScale, newValue, resetAutoSettings, noScroll) { 1.2566 + this.currentScaleValue = newValue; 1.2567 + if (newScale === this.currentScale) { 1.2568 + return; 1.2569 + } 1.2570 + for (var i = 0, ii = this.pages.length; i < ii; i++) { 1.2571 + this.pages[i].update(newScale); 1.2572 + } 1.2573 + this.currentScale = newScale; 1.2574 + 1.2575 + if (!noScroll) { 1.2576 + var page = this.page, dest; 1.2577 + if (this.currentPosition && !IGNORE_CURRENT_POSITION_ON_ZOOM) { 1.2578 + page = this.currentPosition.page; 1.2579 + dest = [null, { name: 'XYZ' }, this.currentPosition.left, 1.2580 + this.currentPosition.top, null]; 1.2581 + } 1.2582 + this.pages[page - 1].scrollIntoView(dest); 1.2583 + } 1.2584 + var event = document.createEvent('UIEvents'); 1.2585 + event.initUIEvent('scalechange', false, false, window, 0); 1.2586 + event.scale = newScale; 1.2587 + event.resetAutoSettings = resetAutoSettings; 1.2588 + window.dispatchEvent(event); 1.2589 + }, 1.2590 + 1.2591 + setScale: function pdfViewSetScale(value, resetAutoSettings, noScroll) { 1.2592 + if (value === 'custom') { 1.2593 + return; 1.2594 + } 1.2595 + var scale = parseFloat(value); 1.2596 + 1.2597 + if (scale > 0) { 1.2598 + this._setScaleUpdatePages(scale, value, true, noScroll); 1.2599 + } else { 1.2600 + var currentPage = this.pages[this.page - 1]; 1.2601 + if (!currentPage) { 1.2602 + return; 1.2603 + } 1.2604 + var hPadding = PresentationMode.active ? 0 : SCROLLBAR_PADDING; 1.2605 + var vPadding = PresentationMode.active ? 0 : VERTICAL_PADDING; 1.2606 + var pageWidthScale = (this.container.clientWidth - hPadding) / 1.2607 + currentPage.width * currentPage.scale; 1.2608 + var pageHeightScale = (this.container.clientHeight - vPadding) / 1.2609 + currentPage.height * currentPage.scale; 1.2610 + switch (value) { 1.2611 + case 'page-actual': 1.2612 + scale = 1; 1.2613 + break; 1.2614 + case 'page-width': 1.2615 + scale = pageWidthScale; 1.2616 + break; 1.2617 + case 'page-height': 1.2618 + scale = pageHeightScale; 1.2619 + break; 1.2620 + case 'page-fit': 1.2621 + scale = Math.min(pageWidthScale, pageHeightScale); 1.2622 + break; 1.2623 + case 'auto': 1.2624 + scale = Math.min(MAX_AUTO_SCALE, pageWidthScale); 1.2625 + break; 1.2626 + default: 1.2627 + console.error('pdfViewSetScale: \'' + value + 1.2628 + '\' is an unknown zoom value.'); 1.2629 + return; 1.2630 + } 1.2631 + this._setScaleUpdatePages(scale, value, resetAutoSettings, noScroll); 1.2632 + 1.2633 + selectScaleOption(value); 1.2634 + } 1.2635 + }, 1.2636 + 1.2637 + zoomIn: function pdfViewZoomIn(ticks) { 1.2638 + var newScale = this.currentScale; 1.2639 + do { 1.2640 + newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2); 1.2641 + newScale = Math.ceil(newScale * 10) / 10; 1.2642 + newScale = Math.min(MAX_SCALE, newScale); 1.2643 + } while (--ticks && newScale < MAX_SCALE); 1.2644 + this.setScale(newScale, true); 1.2645 + }, 1.2646 + 1.2647 + zoomOut: function pdfViewZoomOut(ticks) { 1.2648 + var newScale = this.currentScale; 1.2649 + do { 1.2650 + newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2); 1.2651 + newScale = Math.floor(newScale * 10) / 10; 1.2652 + newScale = Math.max(MIN_SCALE, newScale); 1.2653 + } while (--ticks && newScale > MIN_SCALE); 1.2654 + this.setScale(newScale, true); 1.2655 + }, 1.2656 + 1.2657 + set page(val) { 1.2658 + var pages = this.pages; 1.2659 + var event = document.createEvent('UIEvents'); 1.2660 + event.initUIEvent('pagechange', false, false, window, 0); 1.2661 + 1.2662 + if (!(0 < val && val <= pages.length)) { 1.2663 + this.previousPageNumber = val; 1.2664 + event.pageNumber = this.page; 1.2665 + window.dispatchEvent(event); 1.2666 + return; 1.2667 + } 1.2668 + 1.2669 + pages[val - 1].updateStats(); 1.2670 + this.previousPageNumber = currentPageNumber; 1.2671 + currentPageNumber = val; 1.2672 + event.pageNumber = val; 1.2673 + window.dispatchEvent(event); 1.2674 + 1.2675 + // checking if the this.page was called from the updateViewarea function: 1.2676 + // avoiding the creation of two "set page" method (internal and public) 1.2677 + if (updateViewarea.inProgress) { 1.2678 + return; 1.2679 + } 1.2680 + // Avoid scrolling the first page during loading 1.2681 + if (this.loading && val === 1) { 1.2682 + return; 1.2683 + } 1.2684 + pages[val - 1].scrollIntoView(); 1.2685 + }, 1.2686 + 1.2687 + get page() { 1.2688 + return currentPageNumber; 1.2689 + }, 1.2690 + 1.2691 + get supportsPrinting() { 1.2692 + var canvas = document.createElement('canvas'); 1.2693 + var value = 'mozPrintCallback' in canvas; 1.2694 + // shadow 1.2695 + Object.defineProperty(this, 'supportsPrinting', { value: value, 1.2696 + enumerable: true, 1.2697 + configurable: true, 1.2698 + writable: false }); 1.2699 + return value; 1.2700 + }, 1.2701 + 1.2702 + get supportsFullscreen() { 1.2703 + var doc = document.documentElement; 1.2704 + var support = doc.requestFullscreen || doc.mozRequestFullScreen || 1.2705 + doc.webkitRequestFullScreen || doc.msRequestFullscreen; 1.2706 + 1.2707 + if (document.fullscreenEnabled === false || 1.2708 + document.mozFullScreenEnabled === false || 1.2709 + document.webkitFullscreenEnabled === false || 1.2710 + document.msFullscreenEnabled === false) { 1.2711 + support = false; 1.2712 + } 1.2713 + 1.2714 + Object.defineProperty(this, 'supportsFullscreen', { value: support, 1.2715 + enumerable: true, 1.2716 + configurable: true, 1.2717 + writable: false }); 1.2718 + return support; 1.2719 + }, 1.2720 + 1.2721 + get supportsIntegratedFind() { 1.2722 + var support = false; 1.2723 + support = FirefoxCom.requestSync('supportsIntegratedFind'); 1.2724 + Object.defineProperty(this, 'supportsIntegratedFind', { value: support, 1.2725 + enumerable: true, 1.2726 + configurable: true, 1.2727 + writable: false }); 1.2728 + return support; 1.2729 + }, 1.2730 + 1.2731 + get supportsDocumentFonts() { 1.2732 + var support = true; 1.2733 + support = FirefoxCom.requestSync('supportsDocumentFonts'); 1.2734 + Object.defineProperty(this, 'supportsDocumentFonts', { value: support, 1.2735 + enumerable: true, 1.2736 + configurable: true, 1.2737 + writable: false }); 1.2738 + return support; 1.2739 + }, 1.2740 + 1.2741 + get supportsDocumentColors() { 1.2742 + var support = true; 1.2743 + support = FirefoxCom.requestSync('supportsDocumentColors'); 1.2744 + Object.defineProperty(this, 'supportsDocumentColors', { value: support, 1.2745 + enumerable: true, 1.2746 + configurable: true, 1.2747 + writable: false }); 1.2748 + return support; 1.2749 + }, 1.2750 + 1.2751 + get loadingBar() { 1.2752 + var bar = new ProgressBar('#loadingBar', {}); 1.2753 + Object.defineProperty(this, 'loadingBar', { value: bar, 1.2754 + enumerable: true, 1.2755 + configurable: true, 1.2756 + writable: false }); 1.2757 + return bar; 1.2758 + }, 1.2759 + 1.2760 + get isHorizontalScrollbarEnabled() { 1.2761 + return (PresentationMode.active ? false : 1.2762 + (this.container.scrollWidth > this.container.clientWidth)); 1.2763 + }, 1.2764 + 1.2765 + initPassiveLoading: function pdfViewInitPassiveLoading() { 1.2766 + var pdfDataRangeTransport = { 1.2767 + rangeListeners: [], 1.2768 + progressListeners: [], 1.2769 + 1.2770 + addRangeListener: function PdfDataRangeTransport_addRangeListener( 1.2771 + listener) { 1.2772 + this.rangeListeners.push(listener); 1.2773 + }, 1.2774 + 1.2775 + addProgressListener: function PdfDataRangeTransport_addProgressListener( 1.2776 + listener) { 1.2777 + this.progressListeners.push(listener); 1.2778 + }, 1.2779 + 1.2780 + onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) { 1.2781 + var listeners = this.rangeListeners; 1.2782 + for (var i = 0, n = listeners.length; i < n; ++i) { 1.2783 + listeners[i](begin, chunk); 1.2784 + } 1.2785 + }, 1.2786 + 1.2787 + onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) { 1.2788 + var listeners = this.progressListeners; 1.2789 + for (var i = 0, n = listeners.length; i < n; ++i) { 1.2790 + listeners[i](loaded); 1.2791 + } 1.2792 + }, 1.2793 + 1.2794 + requestDataRange: function PdfDataRangeTransport_requestDataRange( 1.2795 + begin, end) { 1.2796 + FirefoxCom.request('requestDataRange', { begin: begin, end: end }); 1.2797 + } 1.2798 + }; 1.2799 + 1.2800 + window.addEventListener('message', function windowMessage(e) { 1.2801 + if (e.source !== null) { 1.2802 + // The message MUST originate from Chrome code. 1.2803 + console.warn('Rejected untrusted message from ' + e.origin); 1.2804 + return; 1.2805 + } 1.2806 + var args = e.data; 1.2807 + 1.2808 + if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) { 1.2809 + return; 1.2810 + } 1.2811 + switch (args.pdfjsLoadAction) { 1.2812 + case 'supportsRangedLoading': 1.2813 + PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, { 1.2814 + length: args.length, 1.2815 + initialData: args.data 1.2816 + }); 1.2817 + break; 1.2818 + case 'range': 1.2819 + pdfDataRangeTransport.onDataRange(args.begin, args.chunk); 1.2820 + break; 1.2821 + case 'rangeProgress': 1.2822 + pdfDataRangeTransport.onDataProgress(args.loaded); 1.2823 + break; 1.2824 + case 'progress': 1.2825 + PDFView.progress(args.loaded / args.total); 1.2826 + break; 1.2827 + case 'complete': 1.2828 + if (!args.data) { 1.2829 + PDFView.error(mozL10n.get('loading_error', null, 1.2830 + 'An error occurred while loading the PDF.'), e); 1.2831 + break; 1.2832 + } 1.2833 + PDFView.open(args.data, 0); 1.2834 + break; 1.2835 + } 1.2836 + }); 1.2837 + FirefoxCom.requestSync('initPassiveLoading', null); 1.2838 + }, 1.2839 + 1.2840 + setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) { 1.2841 + this.url = url; 1.2842 + try { 1.2843 + this.setTitle(decodeURIComponent(getFileName(url)) || url); 1.2844 + } catch (e) { 1.2845 + // decodeURIComponent may throw URIError, 1.2846 + // fall back to using the unprocessed url in that case 1.2847 + this.setTitle(url); 1.2848 + } 1.2849 + }, 1.2850 + 1.2851 + setTitle: function pdfViewSetTitle(title) { 1.2852 + document.title = title; 1.2853 + }, 1.2854 + 1.2855 + close: function pdfViewClose() { 1.2856 + var errorWrapper = document.getElementById('errorWrapper'); 1.2857 + errorWrapper.setAttribute('hidden', 'true'); 1.2858 + 1.2859 + if (!this.pdfDocument) { 1.2860 + return; 1.2861 + } 1.2862 + 1.2863 + this.pdfDocument.destroy(); 1.2864 + this.pdfDocument = null; 1.2865 + 1.2866 + var thumbsView = document.getElementById('thumbnailView'); 1.2867 + while (thumbsView.hasChildNodes()) { 1.2868 + thumbsView.removeChild(thumbsView.lastChild); 1.2869 + } 1.2870 + 1.2871 + if ('_loadingInterval' in thumbsView) { 1.2872 + clearInterval(thumbsView._loadingInterval); 1.2873 + } 1.2874 + 1.2875 + var container = document.getElementById('viewer'); 1.2876 + while (container.hasChildNodes()) { 1.2877 + container.removeChild(container.lastChild); 1.2878 + } 1.2879 + 1.2880 + if (typeof PDFBug !== 'undefined') { 1.2881 + PDFBug.cleanup(); 1.2882 + } 1.2883 + }, 1.2884 + 1.2885 + // TODO(mack): This function signature should really be pdfViewOpen(url, args) 1.2886 + open: function pdfViewOpen(url, scale, password, 1.2887 + pdfDataRangeTransport, args) { 1.2888 + if (this.pdfDocument) { 1.2889 + // Reload the preferences if a document was previously opened. 1.2890 + Preferences.reload(); 1.2891 + } 1.2892 + this.close(); 1.2893 + 1.2894 + var parameters = {password: password}; 1.2895 + if (typeof url === 'string') { // URL 1.2896 + this.setTitleUsingUrl(url); 1.2897 + parameters.url = url; 1.2898 + } else if (url && 'byteLength' in url) { // ArrayBuffer 1.2899 + parameters.data = url; 1.2900 + } 1.2901 + if (args) { 1.2902 + for (var prop in args) { 1.2903 + parameters[prop] = args[prop]; 1.2904 + } 1.2905 + } 1.2906 + 1.2907 + var self = this; 1.2908 + self.loading = true; 1.2909 + self.downloadComplete = false; 1.2910 + 1.2911 + var passwordNeeded = function passwordNeeded(updatePassword, reason) { 1.2912 + PasswordPrompt.updatePassword = updatePassword; 1.2913 + PasswordPrompt.reason = reason; 1.2914 + PasswordPrompt.show(); 1.2915 + }; 1.2916 + 1.2917 + function getDocumentProgress(progressData) { 1.2918 + self.progress(progressData.loaded / progressData.total); 1.2919 + } 1.2920 + 1.2921 + PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded, 1.2922 + getDocumentProgress).then( 1.2923 + function getDocumentCallback(pdfDocument) { 1.2924 + self.load(pdfDocument, scale); 1.2925 + self.loading = false; 1.2926 + }, 1.2927 + function getDocumentError(message, exception) { 1.2928 + var loadingErrorMessage = mozL10n.get('loading_error', null, 1.2929 + 'An error occurred while loading the PDF.'); 1.2930 + 1.2931 + if (exception && exception.name === 'InvalidPDFException') { 1.2932 + // change error message also for other builds 1.2933 + loadingErrorMessage = mozL10n.get('invalid_file_error', null, 1.2934 + 'Invalid or corrupted PDF file.'); 1.2935 + } 1.2936 + 1.2937 + if (exception && exception.name === 'MissingPDFException') { 1.2938 + // special message for missing PDF's 1.2939 + loadingErrorMessage = mozL10n.get('missing_file_error', null, 1.2940 + 'Missing PDF file.'); 1.2941 + 1.2942 + } 1.2943 + 1.2944 + var moreInfo = { 1.2945 + message: message 1.2946 + }; 1.2947 + self.error(loadingErrorMessage, moreInfo); 1.2948 + self.loading = false; 1.2949 + } 1.2950 + ); 1.2951 + }, 1.2952 + 1.2953 + download: function pdfViewDownload() { 1.2954 + function downloadByUrl() { 1.2955 + downloadManager.downloadUrl(url, filename); 1.2956 + } 1.2957 + 1.2958 + var url = this.url.split('#')[0]; 1.2959 + var filename = getPDFFileNameFromURL(url); 1.2960 + var downloadManager = new DownloadManager(); 1.2961 + downloadManager.onerror = function (err) { 1.2962 + // This error won't really be helpful because it's likely the 1.2963 + // fallback won't work either (or is already open). 1.2964 + PDFView.error('PDF failed to download.'); 1.2965 + }; 1.2966 + 1.2967 + if (!this.pdfDocument) { // the PDF is not ready yet 1.2968 + downloadByUrl(); 1.2969 + return; 1.2970 + } 1.2971 + 1.2972 + if (!this.downloadComplete) { // the PDF is still downloading 1.2973 + downloadByUrl(); 1.2974 + return; 1.2975 + } 1.2976 + 1.2977 + this.pdfDocument.getData().then( 1.2978 + function getDataSuccess(data) { 1.2979 + var blob = PDFJS.createBlob(data, 'application/pdf'); 1.2980 + downloadManager.download(blob, url, filename); 1.2981 + }, 1.2982 + downloadByUrl // Error occurred try downloading with just the url. 1.2983 + ).then(null, downloadByUrl); 1.2984 + }, 1.2985 + 1.2986 + fallback: function pdfViewFallback(featureId) { 1.2987 + // Only trigger the fallback once so we don't spam the user with messages 1.2988 + // for one PDF. 1.2989 + if (this.fellback) 1.2990 + return; 1.2991 + this.fellback = true; 1.2992 + var url = this.url.split('#')[0]; 1.2993 + FirefoxCom.request('fallback', { featureId: featureId, url: url }, 1.2994 + function response(download) { 1.2995 + if (!download) { 1.2996 + return; 1.2997 + } 1.2998 + PDFView.download(); 1.2999 + }); 1.3000 + }, 1.3001 + 1.3002 + navigateTo: function pdfViewNavigateTo(dest) { 1.3003 + var destString = ''; 1.3004 + var self = this; 1.3005 + 1.3006 + var goToDestination = function(destRef) { 1.3007 + self.pendingRefStr = null; 1.3008 + // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..> 1.3009 + var pageNumber = destRef instanceof Object ? 1.3010 + self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : 1.3011 + (destRef + 1); 1.3012 + if (pageNumber) { 1.3013 + if (pageNumber > self.pages.length) { 1.3014 + pageNumber = self.pages.length; 1.3015 + } 1.3016 + var currentPage = self.pages[pageNumber - 1]; 1.3017 + currentPage.scrollIntoView(dest); 1.3018 + 1.3019 + // Update the browsing history. 1.3020 + PDFHistory.push({ dest: dest, hash: destString, page: pageNumber }); 1.3021 + } else { 1.3022 + self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) { 1.3023 + var pageNum = pageIndex + 1; 1.3024 + self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum; 1.3025 + goToDestination(destRef); 1.3026 + }); 1.3027 + } 1.3028 + }; 1.3029 + 1.3030 + this.destinationsPromise.then(function() { 1.3031 + if (typeof dest === 'string') { 1.3032 + destString = dest; 1.3033 + dest = self.destinations[dest]; 1.3034 + } 1.3035 + if (!(dest instanceof Array)) { 1.3036 + return; // invalid destination 1.3037 + } 1.3038 + goToDestination(dest[0]); 1.3039 + }); 1.3040 + }, 1.3041 + 1.3042 + getDestinationHash: function pdfViewGetDestinationHash(dest) { 1.3043 + if (typeof dest === 'string') { 1.3044 + return PDFView.getAnchorUrl('#' + escape(dest)); 1.3045 + } 1.3046 + if (dest instanceof Array) { 1.3047 + var destRef = dest[0]; // see navigateTo method for dest format 1.3048 + var pageNumber = destRef instanceof Object ? 1.3049 + this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : 1.3050 + (destRef + 1); 1.3051 + if (pageNumber) { 1.3052 + var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber); 1.3053 + var destKind = dest[1]; 1.3054 + if (typeof destKind === 'object' && 'name' in destKind && 1.3055 + destKind.name == 'XYZ') { 1.3056 + var scale = (dest[4] || this.currentScaleValue); 1.3057 + var scaleNumber = parseFloat(scale); 1.3058 + if (scaleNumber) { 1.3059 + scale = scaleNumber * 100; 1.3060 + } 1.3061 + pdfOpenParams += '&zoom=' + scale; 1.3062 + if (dest[2] || dest[3]) { 1.3063 + pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0); 1.3064 + } 1.3065 + } 1.3066 + return pdfOpenParams; 1.3067 + } 1.3068 + } 1.3069 + return ''; 1.3070 + }, 1.3071 + 1.3072 + /** 1.3073 + * Prefix the full url on anchor links to make sure that links are resolved 1.3074 + * relative to the current URL instead of the one defined in <base href>. 1.3075 + * @param {String} anchor The anchor hash, including the #. 1.3076 + */ 1.3077 + getAnchorUrl: function getAnchorUrl(anchor) { 1.3078 + return this.url.split('#')[0] + anchor; 1.3079 + }, 1.3080 + 1.3081 + /** 1.3082 + * Show the error box. 1.3083 + * @param {String} message A message that is human readable. 1.3084 + * @param {Object} moreInfo (optional) Further information about the error 1.3085 + * that is more technical. Should have a 'message' 1.3086 + * and optionally a 'stack' property. 1.3087 + */ 1.3088 + error: function pdfViewError(message, moreInfo) { 1.3089 + var moreInfoText = mozL10n.get('error_version_info', 1.3090 + {version: PDFJS.version || '?', build: PDFJS.build || '?'}, 1.3091 + 'PDF.js v{{version}} (build: {{build}})') + '\n'; 1.3092 + if (moreInfo) { 1.3093 + moreInfoText += 1.3094 + mozL10n.get('error_message', {message: moreInfo.message}, 1.3095 + 'Message: {{message}}'); 1.3096 + if (moreInfo.stack) { 1.3097 + moreInfoText += '\n' + 1.3098 + mozL10n.get('error_stack', {stack: moreInfo.stack}, 1.3099 + 'Stack: {{stack}}'); 1.3100 + } else { 1.3101 + if (moreInfo.filename) { 1.3102 + moreInfoText += '\n' + 1.3103 + mozL10n.get('error_file', {file: moreInfo.filename}, 1.3104 + 'File: {{file}}'); 1.3105 + } 1.3106 + if (moreInfo.lineNumber) { 1.3107 + moreInfoText += '\n' + 1.3108 + mozL10n.get('error_line', {line: moreInfo.lineNumber}, 1.3109 + 'Line: {{line}}'); 1.3110 + } 1.3111 + } 1.3112 + } 1.3113 + 1.3114 + console.error(message + '\n' + moreInfoText); 1.3115 + this.fallback(); 1.3116 + }, 1.3117 + 1.3118 + progress: function pdfViewProgress(level) { 1.3119 + var percent = Math.round(level * 100); 1.3120 + // When we transition from full request to range requests, it's possible 1.3121 + // that we discard some of the loaded data. This can cause the loading 1.3122 + // bar to move backwards. So prevent this by only updating the bar if it 1.3123 + // increases. 1.3124 + if (percent > PDFView.loadingBar.percent) { 1.3125 + PDFView.loadingBar.percent = percent; 1.3126 + } 1.3127 + }, 1.3128 + 1.3129 + load: function pdfViewLoad(pdfDocument, scale) { 1.3130 + var self = this; 1.3131 + var isOnePageRenderedResolved = false; 1.3132 + var resolveOnePageRendered = null; 1.3133 + var onePageRendered = new Promise(function (resolve) { 1.3134 + resolveOnePageRendered = resolve; 1.3135 + }); 1.3136 + function bindOnAfterDraw(pageView, thumbnailView) { 1.3137 + // when page is painted, using the image as thumbnail base 1.3138 + pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() { 1.3139 + if (!isOnePageRenderedResolved) { 1.3140 + isOnePageRenderedResolved = true; 1.3141 + resolveOnePageRendered(); 1.3142 + } 1.3143 + thumbnailView.setImage(pageView.canvas); 1.3144 + }; 1.3145 + } 1.3146 + 1.3147 + PDFFindController.reset(); 1.3148 + 1.3149 + this.pdfDocument = pdfDocument; 1.3150 + 1.3151 + DocumentProperties.resolveDataAvailable(); 1.3152 + 1.3153 + var downloadedPromise = pdfDocument.getDownloadInfo().then(function() { 1.3154 + self.downloadComplete = true; 1.3155 + PDFView.loadingBar.hide(); 1.3156 + var outerContainer = document.getElementById('outerContainer'); 1.3157 + outerContainer.classList.remove('loadingInProgress'); 1.3158 + }); 1.3159 + 1.3160 + var pagesCount = pdfDocument.numPages; 1.3161 + 1.3162 + var id = pdfDocument.fingerprint; 1.3163 + document.getElementById('numPages').textContent = 1.3164 + mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}'); 1.3165 + document.getElementById('pageNumber').max = pagesCount; 1.3166 + 1.3167 + PDFView.documentFingerprint = id; 1.3168 + var store = PDFView.store = new ViewHistory(id); 1.3169 + 1.3170 + this.pageRotation = 0; 1.3171 + 1.3172 + var pages = this.pages = []; 1.3173 + var pagesRefMap = this.pagesRefMap = {}; 1.3174 + var thumbnails = this.thumbnails = []; 1.3175 + 1.3176 + var resolvePagesPromise; 1.3177 + var pagesPromise = new Promise(function (resolve) { 1.3178 + resolvePagesPromise = resolve; 1.3179 + }); 1.3180 + this.pagesPromise = pagesPromise; 1.3181 + 1.3182 + var firstPagePromise = pdfDocument.getPage(1); 1.3183 + var container = document.getElementById('viewer'); 1.3184 + var thumbsView = document.getElementById('thumbnailView'); 1.3185 + 1.3186 + // Fetch a single page so we can get a viewport that will be the default 1.3187 + // viewport for all pages 1.3188 + firstPagePromise.then(function(pdfPage) { 1.3189 + var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS); 1.3190 + for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { 1.3191 + var viewportClone = viewport.clone(); 1.3192 + var pageView = new PageView(container, pageNum, scale, 1.3193 + self.navigateTo.bind(self), 1.3194 + viewportClone); 1.3195 + var thumbnailView = new ThumbnailView(thumbsView, pageNum, 1.3196 + viewportClone); 1.3197 + bindOnAfterDraw(pageView, thumbnailView); 1.3198 + pages.push(pageView); 1.3199 + thumbnails.push(thumbnailView); 1.3200 + } 1.3201 + 1.3202 + // Fetch all the pages since the viewport is needed before printing 1.3203 + // starts to create the correct size canvas. Wait until one page is 1.3204 + // rendered so we don't tie up too many resources early on. 1.3205 + onePageRendered.then(function () { 1.3206 + if (!PDFJS.disableAutoFetch) { 1.3207 + var getPagesLeft = pagesCount; 1.3208 + for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { 1.3209 + pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) { 1.3210 + var pageView = pages[pageNum - 1]; 1.3211 + if (!pageView.pdfPage) { 1.3212 + pageView.setPdfPage(pdfPage); 1.3213 + } 1.3214 + var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R'; 1.3215 + pagesRefMap[refStr] = pageNum; 1.3216 + getPagesLeft--; 1.3217 + if (!getPagesLeft) { 1.3218 + resolvePagesPromise(); 1.3219 + } 1.3220 + }.bind(null, pageNum)); 1.3221 + } 1.3222 + } else { 1.3223 + // XXX: Printing is semi-broken with auto fetch disabled. 1.3224 + resolvePagesPromise(); 1.3225 + } 1.3226 + }); 1.3227 + 1.3228 + downloadedPromise.then(function () { 1.3229 + var event = document.createEvent('CustomEvent'); 1.3230 + event.initCustomEvent('documentload', true, true, {}); 1.3231 + window.dispatchEvent(event); 1.3232 + }); 1.3233 + 1.3234 + PDFView.loadingBar.setWidth(container); 1.3235 + 1.3236 + PDFFindController.resolveFirstPage(); 1.3237 + 1.3238 + // Initialize the browsing history. 1.3239 + PDFHistory.initialize(self.documentFingerprint); 1.3240 + }); 1.3241 + 1.3242 + // Fetch the necessary preference values. 1.3243 + var showPreviousViewOnLoad; 1.3244 + var showPreviousViewOnLoadPromise = 1.3245 + Preferences.get('showPreviousViewOnLoad').then(function (prefValue) { 1.3246 + showPreviousViewOnLoad = prefValue; 1.3247 + }); 1.3248 + var defaultZoomValue; 1.3249 + var defaultZoomValuePromise = 1.3250 + Preferences.get('defaultZoomValue').then(function (prefValue) { 1.3251 + defaultZoomValue = prefValue; 1.3252 + }); 1.3253 + 1.3254 + var storePromise = store.initializedPromise; 1.3255 + Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise, 1.3256 + defaultZoomValuePromise]).then(function resolved() { 1.3257 + var storedHash = null; 1.3258 + if (showPreviousViewOnLoad && store.get('exists', false)) { 1.3259 + var pageNum = store.get('page', '1'); 1.3260 + var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale); 1.3261 + var left = store.get('scrollLeft', '0'); 1.3262 + var top = store.get('scrollTop', '0'); 1.3263 + 1.3264 + storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' + 1.3265 + left + ',' + top; 1.3266 + } else if (defaultZoomValue) { 1.3267 + storedHash = 'page=1&zoom=' + defaultZoomValue; 1.3268 + } 1.3269 + self.setInitialView(storedHash, scale); 1.3270 + 1.3271 + // Make all navigation keys work on document load, 1.3272 + // unless the viewer is embedded in a web page. 1.3273 + if (!self.isViewerEmbedded) { 1.3274 + self.container.focus(); 1.3275 + self.container.blur(); 1.3276 + } 1.3277 + }, function rejected(errorMsg) { 1.3278 + console.error(errorMsg); 1.3279 + 1.3280 + firstPagePromise.then(function () { 1.3281 + self.setInitialView(null, scale); 1.3282 + }); 1.3283 + }); 1.3284 + 1.3285 + pagesPromise.then(function() { 1.3286 + if (PDFView.supportsPrinting) { 1.3287 + pdfDocument.getJavaScript().then(function(javaScript) { 1.3288 + if (javaScript.length) { 1.3289 + console.warn('Warning: JavaScript is not supported'); 1.3290 + PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript); 1.3291 + } 1.3292 + // Hack to support auto printing. 1.3293 + var regex = /\bprint\s*\(/g; 1.3294 + for (var i = 0, ii = javaScript.length; i < ii; i++) { 1.3295 + var js = javaScript[i]; 1.3296 + if (js && regex.test(js)) { 1.3297 + setTimeout(function() { 1.3298 + window.print(); 1.3299 + }); 1.3300 + return; 1.3301 + } 1.3302 + } 1.3303 + }); 1.3304 + } 1.3305 + }); 1.3306 + 1.3307 + var destinationsPromise = 1.3308 + this.destinationsPromise = pdfDocument.getDestinations(); 1.3309 + destinationsPromise.then(function(destinations) { 1.3310 + self.destinations = destinations; 1.3311 + }); 1.3312 + 1.3313 + // outline depends on destinations and pagesRefMap 1.3314 + var promises = [pagesPromise, destinationsPromise, 1.3315 + PDFView.animationStartedPromise]; 1.3316 + Promise.all(promises).then(function() { 1.3317 + pdfDocument.getOutline().then(function(outline) { 1.3318 + self.outline = new DocumentOutlineView(outline); 1.3319 + document.getElementById('viewOutline').disabled = !outline; 1.3320 + 1.3321 + if (outline) { 1.3322 + Preferences.get('ifAvailableShowOutlineOnLoad').then( 1.3323 + function (prefValue) { 1.3324 + if (prefValue) { 1.3325 + if (!self.sidebarOpen) { 1.3326 + document.getElementById('sidebarToggle').click(); 1.3327 + } 1.3328 + self.switchSidebarView('outline'); 1.3329 + } 1.3330 + }); 1.3331 + } 1.3332 + }); 1.3333 + pdfDocument.getAttachments().then(function(attachments) { 1.3334 + self.attachments = new DocumentAttachmentsView(attachments); 1.3335 + document.getElementById('viewAttachments').disabled = !attachments; 1.3336 + }); 1.3337 + }); 1.3338 + 1.3339 + pdfDocument.getMetadata().then(function(data) { 1.3340 + var info = data.info, metadata = data.metadata; 1.3341 + self.documentInfo = info; 1.3342 + self.metadata = metadata; 1.3343 + 1.3344 + // Provides some basic debug information 1.3345 + console.log('PDF ' + pdfDocument.fingerprint + ' [' + 1.3346 + info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() + 1.3347 + ' / ' + (info.Creator || '-').trim() + ']' + 1.3348 + ' (PDF.js: ' + (PDFJS.version || '-') + 1.3349 + (!PDFJS.disableWebGL ? ' [WebGL]' : '') + ')'); 1.3350 + 1.3351 + var pdfTitle; 1.3352 + if (metadata && metadata.has('dc:title')) { 1.3353 + pdfTitle = metadata.get('dc:title'); 1.3354 + } 1.3355 + 1.3356 + if (!pdfTitle && info && info['Title']) { 1.3357 + pdfTitle = info['Title']; 1.3358 + } 1.3359 + 1.3360 + if (pdfTitle) { 1.3361 + self.setTitle(pdfTitle + ' - ' + document.title); 1.3362 + } 1.3363 + 1.3364 + if (info.IsAcroFormPresent) { 1.3365 + console.warn('Warning: AcroForm/XFA is not supported'); 1.3366 + PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.forms); 1.3367 + } 1.3368 + 1.3369 + var versionId = String(info.PDFFormatVersion).slice(-1) | 0; 1.3370 + var generatorId = 0; 1.3371 + var KNOWN_GENERATORS = ["acrobat distiller", "acrobat pdfwritter", 1.3372 + "adobe livecycle", "adobe pdf library", "adobe photoshop", "ghostscript", 1.3373 + "tcpdf", "cairo", "dvipdfm", "dvips", "pdftex", "pdfkit", "itext", 1.3374 + "prince", "quarkxpress", "mac os x", "microsoft", "openoffice", "oracle", 1.3375 + "luradocument", "pdf-xchange", "antenna house", "aspose.cells", "fpdf"]; 1.3376 + var generatorId = 0; 1.3377 + if (info.Producer) { 1.3378 + KNOWN_GENERATORS.some(function (generator, s, i) { 1.3379 + if (generator.indexOf(s) < 0) { 1.3380 + return false; 1.3381 + } 1.3382 + generatorId = i + 1; 1.3383 + return true; 1.3384 + }.bind(null, info.Producer.toLowerCase())); 1.3385 + } 1.3386 + var formType = !info.IsAcroFormPresent ? null : info.IsXFAPresent ? 1.3387 + 'xfa' : 'acroform'; 1.3388 + FirefoxCom.request('reportTelemetry', JSON.stringify({ 1.3389 + type: 'documentInfo', 1.3390 + version: versionId, 1.3391 + generator: generatorId, 1.3392 + formType: formType 1.3393 + })); 1.3394 + }); 1.3395 + }, 1.3396 + 1.3397 + setInitialView: function pdfViewSetInitialView(storedHash, scale) { 1.3398 + // Reset the current scale, as otherwise the page's scale might not get 1.3399 + // updated if the zoom level stayed the same. 1.3400 + this.currentScale = 0; 1.3401 + this.currentScaleValue = null; 1.3402 + // When opening a new file (when one is already loaded in the viewer): 1.3403 + // Reset 'currentPageNumber', since otherwise the page's scale will be wrong 1.3404 + // if 'currentPageNumber' is larger than the number of pages in the file. 1.3405 + document.getElementById('pageNumber').value = currentPageNumber = 1; 1.3406 + // Reset the current position when loading a new file, 1.3407 + // to prevent displaying the wrong position in the document. 1.3408 + this.currentPosition = null; 1.3409 + 1.3410 + if (PDFHistory.initialDestination) { 1.3411 + this.navigateTo(PDFHistory.initialDestination); 1.3412 + PDFHistory.initialDestination = null; 1.3413 + } else if (this.initialBookmark) { 1.3414 + this.setHash(this.initialBookmark); 1.3415 + PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark); 1.3416 + this.initialBookmark = null; 1.3417 + } else if (storedHash) { 1.3418 + this.setHash(storedHash); 1.3419 + } else if (scale) { 1.3420 + this.setScale(scale, true); 1.3421 + this.page = 1; 1.3422 + } 1.3423 + 1.3424 + if (PDFView.currentScale === UNKNOWN_SCALE) { 1.3425 + // Scale was not initialized: invalid bookmark or scale was not specified. 1.3426 + // Setting the default one. 1.3427 + this.setScale(DEFAULT_SCALE, true); 1.3428 + } 1.3429 + }, 1.3430 + 1.3431 + renderHighestPriority: function pdfViewRenderHighestPriority() { 1.3432 + if (PDFView.idleTimeout) { 1.3433 + clearTimeout(PDFView.idleTimeout); 1.3434 + PDFView.idleTimeout = null; 1.3435 + } 1.3436 + 1.3437 + // Pages have a higher priority than thumbnails, so check them first. 1.3438 + var visiblePages = this.getVisiblePages(); 1.3439 + var pageView = this.getHighestPriority(visiblePages, this.pages, 1.3440 + this.pageViewScroll.down); 1.3441 + if (pageView) { 1.3442 + this.renderView(pageView, 'page'); 1.3443 + return; 1.3444 + } 1.3445 + // No pages needed rendering so check thumbnails. 1.3446 + if (this.sidebarOpen) { 1.3447 + var visibleThumbs = this.getVisibleThumbs(); 1.3448 + var thumbView = this.getHighestPriority(visibleThumbs, 1.3449 + this.thumbnails, 1.3450 + this.thumbnailViewScroll.down); 1.3451 + if (thumbView) { 1.3452 + this.renderView(thumbView, 'thumbnail'); 1.3453 + return; 1.3454 + } 1.3455 + } 1.3456 + 1.3457 + PDFView.idleTimeout = setTimeout(function () { 1.3458 + PDFView.cleanup(); 1.3459 + }, CLEANUP_TIMEOUT); 1.3460 + }, 1.3461 + 1.3462 + cleanup: function pdfViewCleanup() { 1.3463 + for (var i = 0, ii = this.pages.length; i < ii; i++) { 1.3464 + if (this.pages[i] && 1.3465 + this.pages[i].renderingState !== RenderingStates.FINISHED) { 1.3466 + this.pages[i].reset(); 1.3467 + } 1.3468 + } 1.3469 + this.pdfDocument.cleanup(); 1.3470 + }, 1.3471 + 1.3472 + getHighestPriority: function pdfViewGetHighestPriority(visible, views, 1.3473 + scrolledDown) { 1.3474 + // The state has changed figure out which page has the highest priority to 1.3475 + // render next (if any). 1.3476 + // Priority: 1.3477 + // 1 visible pages 1.3478 + // 2 if last scrolled down page after the visible pages 1.3479 + // 2 if last scrolled up page before the visible pages 1.3480 + var visibleViews = visible.views; 1.3481 + 1.3482 + var numVisible = visibleViews.length; 1.3483 + if (numVisible === 0) { 1.3484 + return false; 1.3485 + } 1.3486 + for (var i = 0; i < numVisible; ++i) { 1.3487 + var view = visibleViews[i].view; 1.3488 + if (!this.isViewFinished(view)) { 1.3489 + return view; 1.3490 + } 1.3491 + } 1.3492 + 1.3493 + // All the visible views have rendered, try to render next/previous pages. 1.3494 + if (scrolledDown) { 1.3495 + var nextPageIndex = visible.last.id; 1.3496 + // ID's start at 1 so no need to add 1. 1.3497 + if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) { 1.3498 + return views[nextPageIndex]; 1.3499 + } 1.3500 + } else { 1.3501 + var previousPageIndex = visible.first.id - 2; 1.3502 + if (views[previousPageIndex] && 1.3503 + !this.isViewFinished(views[previousPageIndex])) { 1.3504 + return views[previousPageIndex]; 1.3505 + } 1.3506 + } 1.3507 + // Everything that needs to be rendered has been. 1.3508 + return false; 1.3509 + }, 1.3510 + 1.3511 + isViewFinished: function pdfViewIsViewFinished(view) { 1.3512 + return view.renderingState === RenderingStates.FINISHED; 1.3513 + }, 1.3514 + 1.3515 + // Render a page or thumbnail view. This calls the appropriate function based 1.3516 + // on the views state. If the view is already rendered it will return false. 1.3517 + renderView: function pdfViewRender(view, type) { 1.3518 + var state = view.renderingState; 1.3519 + switch (state) { 1.3520 + case RenderingStates.FINISHED: 1.3521 + return false; 1.3522 + case RenderingStates.PAUSED: 1.3523 + PDFView.highestPriorityPage = type + view.id; 1.3524 + view.resume(); 1.3525 + break; 1.3526 + case RenderingStates.RUNNING: 1.3527 + PDFView.highestPriorityPage = type + view.id; 1.3528 + break; 1.3529 + case RenderingStates.INITIAL: 1.3530 + PDFView.highestPriorityPage = type + view.id; 1.3531 + view.draw(this.renderHighestPriority.bind(this)); 1.3532 + break; 1.3533 + } 1.3534 + return true; 1.3535 + }, 1.3536 + 1.3537 + setHash: function pdfViewSetHash(hash) { 1.3538 + if (!hash) { 1.3539 + return; 1.3540 + } 1.3541 + 1.3542 + if (hash.indexOf('=') >= 0) { 1.3543 + var params = PDFView.parseQueryString(hash); 1.3544 + // borrowing syntax from "Parameters for Opening PDF Files" 1.3545 + if ('nameddest' in params) { 1.3546 + PDFHistory.updateNextHashParam(params.nameddest); 1.3547 + PDFView.navigateTo(params.nameddest); 1.3548 + return; 1.3549 + } 1.3550 + var pageNumber, dest; 1.3551 + if ('page' in params) { 1.3552 + pageNumber = (params.page | 0) || 1; 1.3553 + } 1.3554 + if ('zoom' in params) { 1.3555 + var zoomArgs = params.zoom.split(','); // scale,left,top 1.3556 + // building destination array 1.3557 + 1.3558 + // If the zoom value, it has to get divided by 100. If it is a string, 1.3559 + // it should stay as it is. 1.3560 + var zoomArg = zoomArgs[0]; 1.3561 + var zoomArgNumber = parseFloat(zoomArg); 1.3562 + if (zoomArgNumber) { 1.3563 + zoomArg = zoomArgNumber / 100; 1.3564 + } 1.3565 + dest = [null, {name: 'XYZ'}, 1.3566 + zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null, 1.3567 + zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null, 1.3568 + zoomArg]; 1.3569 + } 1.3570 + if (dest) { 1.3571 + var currentPage = this.pages[(pageNumber || this.page) - 1]; 1.3572 + currentPage.scrollIntoView(dest); 1.3573 + } else if (pageNumber) { 1.3574 + this.page = pageNumber; // simple page 1.3575 + } 1.3576 + if ('pagemode' in params) { 1.3577 + var toggle = document.getElementById('sidebarToggle'); 1.3578 + if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' || 1.3579 + params.pagemode === 'attachments') { 1.3580 + if (!this.sidebarOpen) { 1.3581 + toggle.click(); 1.3582 + } 1.3583 + this.switchSidebarView(params.pagemode === 'bookmarks' ? 1.3584 + 'outline' : 1.3585 + params.pagemode); 1.3586 + } else if (params.pagemode === 'none' && this.sidebarOpen) { 1.3587 + toggle.click(); 1.3588 + } 1.3589 + } 1.3590 + } else if (/^\d+$/.test(hash)) { // page number 1.3591 + this.page = hash; 1.3592 + } else { // named destination 1.3593 + PDFHistory.updateNextHashParam(unescape(hash)); 1.3594 + PDFView.navigateTo(unescape(hash)); 1.3595 + } 1.3596 + }, 1.3597 + 1.3598 + switchSidebarView: function pdfViewSwitchSidebarView(view) { 1.3599 + var thumbsView = document.getElementById('thumbnailView'); 1.3600 + var outlineView = document.getElementById('outlineView'); 1.3601 + var attachmentsView = document.getElementById('attachmentsView'); 1.3602 + 1.3603 + var thumbsButton = document.getElementById('viewThumbnail'); 1.3604 + var outlineButton = document.getElementById('viewOutline'); 1.3605 + var attachmentsButton = document.getElementById('viewAttachments'); 1.3606 + 1.3607 + switch (view) { 1.3608 + case 'thumbs': 1.3609 + var wasAnotherViewVisible = thumbsView.classList.contains('hidden'); 1.3610 + 1.3611 + thumbsButton.classList.add('toggled'); 1.3612 + outlineButton.classList.remove('toggled'); 1.3613 + attachmentsButton.classList.remove('toggled'); 1.3614 + thumbsView.classList.remove('hidden'); 1.3615 + outlineView.classList.add('hidden'); 1.3616 + attachmentsView.classList.add('hidden'); 1.3617 + 1.3618 + PDFView.renderHighestPriority(); 1.3619 + 1.3620 + if (wasAnotherViewVisible) { 1.3621 + // Ensure that the thumbnail of the current page is visible 1.3622 + // when switching from another view. 1.3623 + scrollIntoView(document.getElementById('thumbnailContainer' + 1.3624 + this.page)); 1.3625 + } 1.3626 + break; 1.3627 + 1.3628 + case 'outline': 1.3629 + thumbsButton.classList.remove('toggled'); 1.3630 + outlineButton.classList.add('toggled'); 1.3631 + attachmentsButton.classList.remove('toggled'); 1.3632 + thumbsView.classList.add('hidden'); 1.3633 + outlineView.classList.remove('hidden'); 1.3634 + attachmentsView.classList.add('hidden'); 1.3635 + 1.3636 + if (outlineButton.getAttribute('disabled')) { 1.3637 + return; 1.3638 + } 1.3639 + break; 1.3640 + 1.3641 + case 'attachments': 1.3642 + thumbsButton.classList.remove('toggled'); 1.3643 + outlineButton.classList.remove('toggled'); 1.3644 + attachmentsButton.classList.add('toggled'); 1.3645 + thumbsView.classList.add('hidden'); 1.3646 + outlineView.classList.add('hidden'); 1.3647 + attachmentsView.classList.remove('hidden'); 1.3648 + 1.3649 + if (attachmentsButton.getAttribute('disabled')) { 1.3650 + return; 1.3651 + } 1.3652 + break; 1.3653 + } 1.3654 + }, 1.3655 + 1.3656 + getVisiblePages: function pdfViewGetVisiblePages() { 1.3657 + if (!PresentationMode.active) { 1.3658 + return this.getVisibleElements(this.container, this.pages, true); 1.3659 + } else { 1.3660 + // The algorithm in getVisibleElements doesn't work in all browsers and 1.3661 + // configurations when presentation mode is active. 1.3662 + var visible = []; 1.3663 + var currentPage = this.pages[this.page - 1]; 1.3664 + visible.push({ id: currentPage.id, view: currentPage }); 1.3665 + return { first: currentPage, last: currentPage, views: visible }; 1.3666 + } 1.3667 + }, 1.3668 + 1.3669 + getVisibleThumbs: function pdfViewGetVisibleThumbs() { 1.3670 + return this.getVisibleElements(this.thumbnailContainer, this.thumbnails); 1.3671 + }, 1.3672 + 1.3673 + // Generic helper to find out what elements are visible within a scroll pane. 1.3674 + getVisibleElements: function pdfViewGetVisibleElements( 1.3675 + scrollEl, views, sortByVisibility) { 1.3676 + var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight; 1.3677 + var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth; 1.3678 + 1.3679 + var visible = [], view; 1.3680 + var currentHeight, viewHeight, hiddenHeight, percentHeight; 1.3681 + var currentWidth, viewWidth; 1.3682 + for (var i = 0, ii = views.length; i < ii; ++i) { 1.3683 + view = views[i]; 1.3684 + currentHeight = view.el.offsetTop + view.el.clientTop; 1.3685 + viewHeight = view.el.clientHeight; 1.3686 + if ((currentHeight + viewHeight) < top) { 1.3687 + continue; 1.3688 + } 1.3689 + if (currentHeight > bottom) { 1.3690 + break; 1.3691 + } 1.3692 + currentWidth = view.el.offsetLeft + view.el.clientLeft; 1.3693 + viewWidth = view.el.clientWidth; 1.3694 + if ((currentWidth + viewWidth) < left || currentWidth > right) { 1.3695 + continue; 1.3696 + } 1.3697 + hiddenHeight = Math.max(0, top - currentHeight) + 1.3698 + Math.max(0, currentHeight + viewHeight - bottom); 1.3699 + percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0; 1.3700 + 1.3701 + visible.push({ id: view.id, x: currentWidth, y: currentHeight, 1.3702 + view: view, percent: percentHeight }); 1.3703 + } 1.3704 + 1.3705 + var first = visible[0]; 1.3706 + var last = visible[visible.length - 1]; 1.3707 + 1.3708 + if (sortByVisibility) { 1.3709 + visible.sort(function(a, b) { 1.3710 + var pc = a.percent - b.percent; 1.3711 + if (Math.abs(pc) > 0.001) { 1.3712 + return -pc; 1.3713 + } 1.3714 + return a.id - b.id; // ensure stability 1.3715 + }); 1.3716 + } 1.3717 + return {first: first, last: last, views: visible}; 1.3718 + }, 1.3719 + 1.3720 + // Helper function to parse query string (e.g. ?param1=value&parm2=...). 1.3721 + parseQueryString: function pdfViewParseQueryString(query) { 1.3722 + var parts = query.split('&'); 1.3723 + var params = {}; 1.3724 + for (var i = 0, ii = parts.length; i < ii; ++i) { 1.3725 + var param = parts[i].split('='); 1.3726 + var key = param[0]; 1.3727 + var value = param.length > 1 ? param[1] : null; 1.3728 + params[decodeURIComponent(key)] = decodeURIComponent(value); 1.3729 + } 1.3730 + return params; 1.3731 + }, 1.3732 + 1.3733 + beforePrint: function pdfViewSetupBeforePrint() { 1.3734 + if (!this.supportsPrinting) { 1.3735 + var printMessage = mozL10n.get('printing_not_supported', null, 1.3736 + 'Warning: Printing is not fully supported by this browser.'); 1.3737 + this.error(printMessage); 1.3738 + return; 1.3739 + } 1.3740 + 1.3741 + var alertNotReady = false; 1.3742 + var i, ii; 1.3743 + if (!this.pages.length) { 1.3744 + alertNotReady = true; 1.3745 + } else { 1.3746 + for (i = 0, ii = this.pages.length; i < ii; ++i) { 1.3747 + if (!this.pages[i].pdfPage) { 1.3748 + alertNotReady = true; 1.3749 + break; 1.3750 + } 1.3751 + } 1.3752 + } 1.3753 + if (alertNotReady) { 1.3754 + var notReadyMessage = mozL10n.get('printing_not_ready', null, 1.3755 + 'Warning: The PDF is not fully loaded for printing.'); 1.3756 + window.alert(notReadyMessage); 1.3757 + return; 1.3758 + } 1.3759 + 1.3760 + var body = document.querySelector('body'); 1.3761 + body.setAttribute('data-mozPrintCallback', true); 1.3762 + for (i = 0, ii = this.pages.length; i < ii; ++i) { 1.3763 + this.pages[i].beforePrint(); 1.3764 + } 1.3765 + }, 1.3766 + 1.3767 + afterPrint: function pdfViewSetupAfterPrint() { 1.3768 + var div = document.getElementById('printContainer'); 1.3769 + while (div.hasChildNodes()) { 1.3770 + div.removeChild(div.lastChild); 1.3771 + } 1.3772 + }, 1.3773 + 1.3774 + rotatePages: function pdfViewRotatePages(delta) { 1.3775 + var currentPage = this.pages[this.page - 1]; 1.3776 + var i, l; 1.3777 + this.pageRotation = (this.pageRotation + 360 + delta) % 360; 1.3778 + 1.3779 + for (i = 0, l = this.pages.length; i < l; i++) { 1.3780 + var page = this.pages[i]; 1.3781 + page.update(page.scale, this.pageRotation); 1.3782 + } 1.3783 + 1.3784 + for (i = 0, l = this.thumbnails.length; i < l; i++) { 1.3785 + var thumb = this.thumbnails[i]; 1.3786 + thumb.update(this.pageRotation); 1.3787 + } 1.3788 + 1.3789 + this.setScale(this.currentScaleValue, true, true); 1.3790 + 1.3791 + this.renderHighestPriority(); 1.3792 + 1.3793 + if (currentPage) { 1.3794 + currentPage.scrollIntoView(); 1.3795 + } 1.3796 + }, 1.3797 + 1.3798 + /** 1.3799 + * This function flips the page in presentation mode if the user scrolls up 1.3800 + * or down with large enough motion and prevents page flipping too often. 1.3801 + * 1.3802 + * @this {PDFView} 1.3803 + * @param {number} mouseScrollDelta The delta value from the mouse event. 1.3804 + */ 1.3805 + mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) { 1.3806 + var MOUSE_SCROLL_COOLDOWN_TIME = 50; 1.3807 + 1.3808 + var currentTime = (new Date()).getTime(); 1.3809 + var storedTime = this.mouseScrollTimeStamp; 1.3810 + 1.3811 + // In case one page has already been flipped there is a cooldown time 1.3812 + // which has to expire before next page can be scrolled on to. 1.3813 + if (currentTime > storedTime && 1.3814 + currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) { 1.3815 + return; 1.3816 + } 1.3817 + 1.3818 + // In case the user decides to scroll to the opposite direction than before 1.3819 + // clear the accumulated delta. 1.3820 + if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) || 1.3821 + (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) { 1.3822 + this.clearMouseScrollState(); 1.3823 + } 1.3824 + 1.3825 + this.mouseScrollDelta += mouseScrollDelta; 1.3826 + 1.3827 + var PAGE_FLIP_THRESHOLD = 120; 1.3828 + if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) { 1.3829 + 1.3830 + var PageFlipDirection = { 1.3831 + UP: -1, 1.3832 + DOWN: 1 1.3833 + }; 1.3834 + 1.3835 + // In presentation mode scroll one page at a time. 1.3836 + var pageFlipDirection = (this.mouseScrollDelta > 0) ? 1.3837 + PageFlipDirection.UP : 1.3838 + PageFlipDirection.DOWN; 1.3839 + this.clearMouseScrollState(); 1.3840 + var currentPage = this.page; 1.3841 + 1.3842 + // In case we are already on the first or the last page there is no need 1.3843 + // to do anything. 1.3844 + if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) || 1.3845 + (currentPage == this.pages.length && 1.3846 + pageFlipDirection == PageFlipDirection.DOWN)) { 1.3847 + return; 1.3848 + } 1.3849 + 1.3850 + this.page += pageFlipDirection; 1.3851 + this.mouseScrollTimeStamp = currentTime; 1.3852 + } 1.3853 + }, 1.3854 + 1.3855 + /** 1.3856 + * This function clears the member attributes used with mouse scrolling in 1.3857 + * presentation mode. 1.3858 + * 1.3859 + * @this {PDFView} 1.3860 + */ 1.3861 + clearMouseScrollState: function pdfViewClearMouseScrollState() { 1.3862 + this.mouseScrollTimeStamp = 0; 1.3863 + this.mouseScrollDelta = 0; 1.3864 + } 1.3865 +}; 1.3866 + 1.3867 + 1.3868 +var PageView = function pageView(container, id, scale, 1.3869 + navigateTo, defaultViewport) { 1.3870 + this.id = id; 1.3871 + 1.3872 + this.rotation = 0; 1.3873 + this.scale = scale || 1.0; 1.3874 + this.viewport = defaultViewport; 1.3875 + this.pdfPageRotate = defaultViewport.rotation; 1.3876 + 1.3877 + this.renderingState = RenderingStates.INITIAL; 1.3878 + this.resume = null; 1.3879 + 1.3880 + this.textLayer = null; 1.3881 + 1.3882 + this.zoomLayer = null; 1.3883 + 1.3884 + this.annotationLayer = null; 1.3885 + 1.3886 + var anchor = document.createElement('a'); 1.3887 + anchor.name = '' + this.id; 1.3888 + 1.3889 + var div = this.el = document.createElement('div'); 1.3890 + div.id = 'pageContainer' + this.id; 1.3891 + div.className = 'page'; 1.3892 + div.style.width = Math.floor(this.viewport.width) + 'px'; 1.3893 + div.style.height = Math.floor(this.viewport.height) + 'px'; 1.3894 + 1.3895 + container.appendChild(anchor); 1.3896 + container.appendChild(div); 1.3897 + 1.3898 + this.setPdfPage = function pageViewSetPdfPage(pdfPage) { 1.3899 + this.pdfPage = pdfPage; 1.3900 + this.pdfPageRotate = pdfPage.rotate; 1.3901 + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; 1.3902 + this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation); 1.3903 + this.stats = pdfPage.stats; 1.3904 + this.reset(); 1.3905 + }; 1.3906 + 1.3907 + this.destroy = function pageViewDestroy() { 1.3908 + this.zoomLayer = null; 1.3909 + this.reset(); 1.3910 + if (this.pdfPage) { 1.3911 + this.pdfPage.destroy(); 1.3912 + } 1.3913 + }; 1.3914 + 1.3915 + this.reset = function pageViewReset(keepAnnotations) { 1.3916 + if (this.renderTask) { 1.3917 + this.renderTask.cancel(); 1.3918 + } 1.3919 + this.resume = null; 1.3920 + this.renderingState = RenderingStates.INITIAL; 1.3921 + 1.3922 + div.style.width = Math.floor(this.viewport.width) + 'px'; 1.3923 + div.style.height = Math.floor(this.viewport.height) + 'px'; 1.3924 + 1.3925 + var childNodes = div.childNodes; 1.3926 + for (var i = div.childNodes.length - 1; i >= 0; i--) { 1.3927 + var node = childNodes[i]; 1.3928 + if ((this.zoomLayer && this.zoomLayer === node) || 1.3929 + (keepAnnotations && this.annotationLayer === node)) { 1.3930 + continue; 1.3931 + } 1.3932 + div.removeChild(node); 1.3933 + } 1.3934 + div.removeAttribute('data-loaded'); 1.3935 + 1.3936 + if (keepAnnotations) { 1.3937 + if (this.annotationLayer) { 1.3938 + // Hide annotationLayer until all elements are resized 1.3939 + // so they are not displayed on the already-resized page 1.3940 + this.annotationLayer.setAttribute('hidden', 'true'); 1.3941 + } 1.3942 + } else { 1.3943 + this.annotationLayer = null; 1.3944 + } 1.3945 + 1.3946 + delete this.canvas; 1.3947 + 1.3948 + this.loadingIconDiv = document.createElement('div'); 1.3949 + this.loadingIconDiv.className = 'loadingIcon'; 1.3950 + div.appendChild(this.loadingIconDiv); 1.3951 + }; 1.3952 + 1.3953 + this.update = function pageViewUpdate(scale, rotation) { 1.3954 + this.scale = scale || this.scale; 1.3955 + 1.3956 + if (typeof rotation !== 'undefined') { 1.3957 + this.rotation = rotation; 1.3958 + } 1.3959 + 1.3960 + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; 1.3961 + this.viewport = this.viewport.clone({ 1.3962 + scale: this.scale * CSS_UNITS, 1.3963 + rotation: totalRotation 1.3964 + }); 1.3965 + 1.3966 + if (USE_ONLY_CSS_ZOOM && this.canvas) { 1.3967 + this.cssTransform(this.canvas); 1.3968 + return; 1.3969 + } else if (this.canvas && !this.zoomLayer) { 1.3970 + this.zoomLayer = this.canvas.parentNode; 1.3971 + this.zoomLayer.style.position = 'absolute'; 1.3972 + } 1.3973 + if (this.zoomLayer) { 1.3974 + this.cssTransform(this.zoomLayer.firstChild); 1.3975 + } 1.3976 + this.reset(true); 1.3977 + }; 1.3978 + 1.3979 + this.cssTransform = function pageCssTransform(canvas) { 1.3980 + // Scale canvas, canvas wrapper, and page container. 1.3981 + var width = this.viewport.width; 1.3982 + var height = this.viewport.height; 1.3983 + canvas.style.width = canvas.parentNode.style.width = div.style.width = 1.3984 + Math.floor(width) + 'px'; 1.3985 + canvas.style.height = canvas.parentNode.style.height = div.style.height = 1.3986 + Math.floor(height) + 'px'; 1.3987 + // The canvas may have been originally rotated, so rotate relative to that. 1.3988 + var relativeRotation = this.viewport.rotation - canvas._viewport.rotation; 1.3989 + var absRotation = Math.abs(relativeRotation); 1.3990 + var scaleX = 1, scaleY = 1; 1.3991 + if (absRotation === 90 || absRotation === 270) { 1.3992 + // Scale x and y because of the rotation. 1.3993 + scaleX = height / width; 1.3994 + scaleY = width / height; 1.3995 + } 1.3996 + var cssTransform = 'rotate(' + relativeRotation + 'deg) ' + 1.3997 + 'scale(' + scaleX + ',' + scaleY + ')'; 1.3998 + CustomStyle.setProp('transform', canvas, cssTransform); 1.3999 + 1.4000 + if (this.textLayer) { 1.4001 + // Rotating the text layer is more complicated since the divs inside the 1.4002 + // the text layer are rotated. 1.4003 + // TODO: This could probably be simplified by drawing the text layer in 1.4004 + // one orientation then rotating overall. 1.4005 + var textRelativeRotation = this.viewport.rotation - 1.4006 + this.textLayer.viewport.rotation; 1.4007 + var textAbsRotation = Math.abs(textRelativeRotation); 1.4008 + var scale = (width / canvas.width); 1.4009 + if (textAbsRotation === 90 || textAbsRotation === 270) { 1.4010 + scale = width / canvas.height; 1.4011 + } 1.4012 + var textLayerDiv = this.textLayer.textLayerDiv; 1.4013 + var transX, transY; 1.4014 + switch (textAbsRotation) { 1.4015 + case 0: 1.4016 + transX = transY = 0; 1.4017 + break; 1.4018 + case 90: 1.4019 + transX = 0; 1.4020 + transY = '-' + textLayerDiv.style.height; 1.4021 + break; 1.4022 + case 180: 1.4023 + transX = '-' + textLayerDiv.style.width; 1.4024 + transY = '-' + textLayerDiv.style.height; 1.4025 + break; 1.4026 + case 270: 1.4027 + transX = '-' + textLayerDiv.style.width; 1.4028 + transY = 0; 1.4029 + break; 1.4030 + default: 1.4031 + console.error('Bad rotation value.'); 1.4032 + break; 1.4033 + } 1.4034 + CustomStyle.setProp('transform', textLayerDiv, 1.4035 + 'rotate(' + textAbsRotation + 'deg) ' + 1.4036 + 'scale(' + scale + ', ' + scale + ') ' + 1.4037 + 'translate(' + transX + ', ' + transY + ')'); 1.4038 + CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%'); 1.4039 + } 1.4040 + 1.4041 + if (USE_ONLY_CSS_ZOOM && this.annotationLayer) { 1.4042 + setupAnnotations(div, this.pdfPage, this.viewport); 1.4043 + } 1.4044 + }; 1.4045 + 1.4046 + Object.defineProperty(this, 'width', { 1.4047 + get: function PageView_getWidth() { 1.4048 + return this.viewport.width; 1.4049 + }, 1.4050 + enumerable: true 1.4051 + }); 1.4052 + 1.4053 + Object.defineProperty(this, 'height', { 1.4054 + get: function PageView_getHeight() { 1.4055 + return this.viewport.height; 1.4056 + }, 1.4057 + enumerable: true 1.4058 + }); 1.4059 + 1.4060 + var self = this; 1.4061 + 1.4062 + function setupAnnotations(pageDiv, pdfPage, viewport) { 1.4063 + 1.4064 + function bindLink(link, dest) { 1.4065 + link.href = PDFView.getDestinationHash(dest); 1.4066 + link.onclick = function pageViewSetupLinksOnclick() { 1.4067 + if (dest) { 1.4068 + PDFView.navigateTo(dest); 1.4069 + } 1.4070 + return false; 1.4071 + }; 1.4072 + if (dest) { 1.4073 + link.className = 'internalLink'; 1.4074 + } 1.4075 + } 1.4076 + 1.4077 + function bindNamedAction(link, action) { 1.4078 + link.href = PDFView.getAnchorUrl(''); 1.4079 + link.onclick = function pageViewSetupNamedActionOnClick() { 1.4080 + // See PDF reference, table 8.45 - Named action 1.4081 + switch (action) { 1.4082 + case 'GoToPage': 1.4083 + document.getElementById('pageNumber').focus(); 1.4084 + break; 1.4085 + 1.4086 + case 'GoBack': 1.4087 + PDFHistory.back(); 1.4088 + break; 1.4089 + 1.4090 + case 'GoForward': 1.4091 + PDFHistory.forward(); 1.4092 + break; 1.4093 + 1.4094 + case 'Find': 1.4095 + if (!PDFView.supportsIntegratedFind) { 1.4096 + PDFFindBar.toggle(); 1.4097 + } 1.4098 + break; 1.4099 + 1.4100 + case 'NextPage': 1.4101 + PDFView.page++; 1.4102 + break; 1.4103 + 1.4104 + case 'PrevPage': 1.4105 + PDFView.page--; 1.4106 + break; 1.4107 + 1.4108 + case 'LastPage': 1.4109 + PDFView.page = PDFView.pages.length; 1.4110 + break; 1.4111 + 1.4112 + case 'FirstPage': 1.4113 + PDFView.page = 1; 1.4114 + break; 1.4115 + 1.4116 + default: 1.4117 + break; // No action according to spec 1.4118 + } 1.4119 + return false; 1.4120 + }; 1.4121 + link.className = 'internalLink'; 1.4122 + } 1.4123 + 1.4124 + pdfPage.getAnnotations().then(function(annotationsData) { 1.4125 + viewport = viewport.clone({ dontFlip: true }); 1.4126 + var transform = viewport.transform; 1.4127 + var transformStr = 'matrix(' + transform.join(',') + ')'; 1.4128 + var data, element, i, ii; 1.4129 + 1.4130 + if (self.annotationLayer) { 1.4131 + // If an annotationLayer already exists, refresh its children's 1.4132 + // transformation matrices 1.4133 + for (i = 0, ii = annotationsData.length; i < ii; i++) { 1.4134 + data = annotationsData[i]; 1.4135 + element = self.annotationLayer.querySelector( 1.4136 + '[data-annotation-id="' + data.id + '"]'); 1.4137 + if (element) { 1.4138 + CustomStyle.setProp('transform', element, transformStr); 1.4139 + } 1.4140 + } 1.4141 + // See this.reset() 1.4142 + self.annotationLayer.removeAttribute('hidden'); 1.4143 + } else { 1.4144 + for (i = 0, ii = annotationsData.length; i < ii; i++) { 1.4145 + data = annotationsData[i]; 1.4146 + var annotation = PDFJS.Annotation.fromData(data); 1.4147 + if (!annotation || !annotation.hasHtml()) { 1.4148 + continue; 1.4149 + } 1.4150 + 1.4151 + element = annotation.getHtmlElement(pdfPage.commonObjs); 1.4152 + element.setAttribute('data-annotation-id', data.id); 1.4153 + mozL10n.translate(element); 1.4154 + 1.4155 + data = annotation.getData(); 1.4156 + var rect = data.rect; 1.4157 + var view = pdfPage.view; 1.4158 + rect = PDFJS.Util.normalizeRect([ 1.4159 + rect[0], 1.4160 + view[3] - rect[1] + view[1], 1.4161 + rect[2], 1.4162 + view[3] - rect[3] + view[1] 1.4163 + ]); 1.4164 + element.style.left = rect[0] + 'px'; 1.4165 + element.style.top = rect[1] + 'px'; 1.4166 + element.style.position = 'absolute'; 1.4167 + 1.4168 + CustomStyle.setProp('transform', element, transformStr); 1.4169 + var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px'; 1.4170 + CustomStyle.setProp('transformOrigin', element, transformOriginStr); 1.4171 + 1.4172 + if (data.subtype === 'Link' && !data.url) { 1.4173 + var link = element.getElementsByTagName('a')[0]; 1.4174 + if (link) { 1.4175 + if (data.action) { 1.4176 + bindNamedAction(link, data.action); 1.4177 + } else { 1.4178 + bindLink(link, ('dest' in data) ? data.dest : null); 1.4179 + } 1.4180 + } 1.4181 + } 1.4182 + 1.4183 + if (!self.annotationLayer) { 1.4184 + var annotationLayerDiv = document.createElement('div'); 1.4185 + annotationLayerDiv.className = 'annotationLayer'; 1.4186 + pageDiv.appendChild(annotationLayerDiv); 1.4187 + self.annotationLayer = annotationLayerDiv; 1.4188 + } 1.4189 + 1.4190 + self.annotationLayer.appendChild(element); 1.4191 + } 1.4192 + } 1.4193 + }); 1.4194 + } 1.4195 + 1.4196 + this.getPagePoint = function pageViewGetPagePoint(x, y) { 1.4197 + return this.viewport.convertToPdfPoint(x, y); 1.4198 + }; 1.4199 + 1.4200 + this.scrollIntoView = function pageViewScrollIntoView(dest) { 1.4201 + if (PresentationMode.active) { 1.4202 + if (PDFView.page !== this.id) { 1.4203 + // Avoid breaking PDFView.getVisiblePages in presentation mode. 1.4204 + PDFView.page = this.id; 1.4205 + return; 1.4206 + } 1.4207 + dest = null; 1.4208 + PDFView.setScale(PDFView.currentScaleValue, true, true); 1.4209 + } 1.4210 + if (!dest) { 1.4211 + scrollIntoView(div); 1.4212 + return; 1.4213 + } 1.4214 + 1.4215 + var x = 0, y = 0; 1.4216 + var width = 0, height = 0, widthScale, heightScale; 1.4217 + var changeOrientation = (this.rotation % 180 === 0 ? false : true); 1.4218 + var pageWidth = (changeOrientation ? this.height : this.width) / 1.4219 + this.scale / CSS_UNITS; 1.4220 + var pageHeight = (changeOrientation ? this.width : this.height) / 1.4221 + this.scale / CSS_UNITS; 1.4222 + var scale = 0; 1.4223 + switch (dest[1].name) { 1.4224 + case 'XYZ': 1.4225 + x = dest[2]; 1.4226 + y = dest[3]; 1.4227 + scale = dest[4]; 1.4228 + // If x and/or y coordinates are not supplied, default to 1.4229 + // _top_ left of the page (not the obvious bottom left, 1.4230 + // since aligning the bottom of the intended page with the 1.4231 + // top of the window is rarely helpful). 1.4232 + x = x !== null ? x : 0; 1.4233 + y = y !== null ? y : pageHeight; 1.4234 + break; 1.4235 + case 'Fit': 1.4236 + case 'FitB': 1.4237 + scale = 'page-fit'; 1.4238 + break; 1.4239 + case 'FitH': 1.4240 + case 'FitBH': 1.4241 + y = dest[2]; 1.4242 + scale = 'page-width'; 1.4243 + break; 1.4244 + case 'FitV': 1.4245 + case 'FitBV': 1.4246 + x = dest[2]; 1.4247 + width = pageWidth; 1.4248 + height = pageHeight; 1.4249 + scale = 'page-height'; 1.4250 + break; 1.4251 + case 'FitR': 1.4252 + x = dest[2]; 1.4253 + y = dest[3]; 1.4254 + width = dest[4] - x; 1.4255 + height = dest[5] - y; 1.4256 + widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) / 1.4257 + width / CSS_UNITS; 1.4258 + heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) / 1.4259 + height / CSS_UNITS; 1.4260 + scale = Math.min(Math.abs(widthScale), Math.abs(heightScale)); 1.4261 + break; 1.4262 + default: 1.4263 + return; 1.4264 + } 1.4265 + 1.4266 + if (scale && scale !== PDFView.currentScale) { 1.4267 + PDFView.setScale(scale, true, true); 1.4268 + } else if (PDFView.currentScale === UNKNOWN_SCALE) { 1.4269 + PDFView.setScale(DEFAULT_SCALE, true, true); 1.4270 + } 1.4271 + 1.4272 + if (scale === 'page-fit' && !dest[4]) { 1.4273 + scrollIntoView(div); 1.4274 + return; 1.4275 + } 1.4276 + 1.4277 + var boundingRect = [ 1.4278 + this.viewport.convertToViewportPoint(x, y), 1.4279 + this.viewport.convertToViewportPoint(x + width, y + height) 1.4280 + ]; 1.4281 + var left = Math.min(boundingRect[0][0], boundingRect[1][0]); 1.4282 + var top = Math.min(boundingRect[0][1], boundingRect[1][1]); 1.4283 + 1.4284 + scrollIntoView(div, { left: left, top: top }); 1.4285 + }; 1.4286 + 1.4287 + this.getTextContent = function pageviewGetTextContent() { 1.4288 + return PDFView.getPage(this.id).then(function(pdfPage) { 1.4289 + return pdfPage.getTextContent(); 1.4290 + }); 1.4291 + }; 1.4292 + 1.4293 + this.draw = function pageviewDraw(callback) { 1.4294 + var pdfPage = this.pdfPage; 1.4295 + 1.4296 + if (this.pagePdfPromise) { 1.4297 + return; 1.4298 + } 1.4299 + if (!pdfPage) { 1.4300 + var promise = PDFView.getPage(this.id); 1.4301 + promise.then(function(pdfPage) { 1.4302 + delete this.pagePdfPromise; 1.4303 + this.setPdfPage(pdfPage); 1.4304 + this.draw(callback); 1.4305 + }.bind(this)); 1.4306 + this.pagePdfPromise = promise; 1.4307 + return; 1.4308 + } 1.4309 + 1.4310 + if (this.renderingState !== RenderingStates.INITIAL) { 1.4311 + console.error('Must be in new state before drawing'); 1.4312 + } 1.4313 + 1.4314 + this.renderingState = RenderingStates.RUNNING; 1.4315 + 1.4316 + var viewport = this.viewport; 1.4317 + // Wrap the canvas so if it has a css transform for highdpi the overflow 1.4318 + // will be hidden in FF. 1.4319 + var canvasWrapper = document.createElement('div'); 1.4320 + canvasWrapper.style.width = div.style.width; 1.4321 + canvasWrapper.style.height = div.style.height; 1.4322 + canvasWrapper.classList.add('canvasWrapper'); 1.4323 + 1.4324 + var canvas = document.createElement('canvas'); 1.4325 + canvas.id = 'page' + this.id; 1.4326 + canvasWrapper.appendChild(canvas); 1.4327 + if (this.annotationLayer) { 1.4328 + // annotationLayer needs to stay on top 1.4329 + div.insertBefore(canvasWrapper, this.annotationLayer); 1.4330 + } else { 1.4331 + div.appendChild(canvasWrapper); 1.4332 + } 1.4333 + this.canvas = canvas; 1.4334 + 1.4335 + var ctx = canvas.getContext('2d'); 1.4336 + var outputScale = getOutputScale(ctx); 1.4337 + 1.4338 + if (USE_ONLY_CSS_ZOOM) { 1.4339 + var actualSizeViewport = viewport.clone({ scale: CSS_UNITS }); 1.4340 + // Use a scale that will make the canvas be the original intended size 1.4341 + // of the page. 1.4342 + outputScale.sx *= actualSizeViewport.width / viewport.width; 1.4343 + outputScale.sy *= actualSizeViewport.height / viewport.height; 1.4344 + outputScale.scaled = true; 1.4345 + } 1.4346 + 1.4347 + canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0; 1.4348 + canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0; 1.4349 + canvas.style.width = Math.floor(viewport.width) + 'px'; 1.4350 + canvas.style.height = Math.floor(viewport.height) + 'px'; 1.4351 + // Add the viewport so it's known what it was originally drawn with. 1.4352 + canvas._viewport = viewport; 1.4353 + 1.4354 + var textLayerDiv = null; 1.4355 + if (!PDFJS.disableTextLayer) { 1.4356 + textLayerDiv = document.createElement('div'); 1.4357 + textLayerDiv.className = 'textLayer'; 1.4358 + textLayerDiv.style.width = canvas.style.width; 1.4359 + textLayerDiv.style.height = canvas.style.height; 1.4360 + if (this.annotationLayer) { 1.4361 + // annotationLayer needs to stay on top 1.4362 + div.insertBefore(textLayerDiv, this.annotationLayer); 1.4363 + } else { 1.4364 + div.appendChild(textLayerDiv); 1.4365 + } 1.4366 + } 1.4367 + var textLayer = this.textLayer = 1.4368 + textLayerDiv ? new TextLayerBuilder({ 1.4369 + textLayerDiv: textLayerDiv, 1.4370 + pageIndex: this.id - 1, 1.4371 + lastScrollSource: PDFView, 1.4372 + viewport: this.viewport, 1.4373 + isViewerInPresentationMode: PresentationMode.active 1.4374 + }) : null; 1.4375 + // TODO(mack): use data attributes to store these 1.4376 + ctx._scaleX = outputScale.sx; 1.4377 + ctx._scaleY = outputScale.sy; 1.4378 + if (outputScale.scaled) { 1.4379 + ctx.scale(outputScale.sx, outputScale.sy); 1.4380 + } 1.4381 + 1.4382 + // Rendering area 1.4383 + 1.4384 + var self = this; 1.4385 + function pageViewDrawCallback(error) { 1.4386 + // The renderTask may have been replaced by a new one, so only remove the 1.4387 + // reference to the renderTask if it matches the one that is triggering 1.4388 + // this callback. 1.4389 + if (renderTask === self.renderTask) { 1.4390 + self.renderTask = null; 1.4391 + } 1.4392 + 1.4393 + if (error === 'cancelled') { 1.4394 + return; 1.4395 + } 1.4396 + 1.4397 + self.renderingState = RenderingStates.FINISHED; 1.4398 + 1.4399 + if (self.loadingIconDiv) { 1.4400 + div.removeChild(self.loadingIconDiv); 1.4401 + delete self.loadingIconDiv; 1.4402 + } 1.4403 + 1.4404 + if (self.zoomLayer) { 1.4405 + div.removeChild(self.zoomLayer); 1.4406 + self.zoomLayer = null; 1.4407 + } 1.4408 + 1.4409 + if (self.textLayer && self.textLayer.textDivs && 1.4410 + self.textLayer.textDivs.length > 0 && 1.4411 + !PDFView.supportsDocumentColors) { 1.4412 + console.error(mozL10n.get('document_colors_disabled', null, 1.4413 + 'PDF documents are not allowed to use their own colors: ' + 1.4414 + '\'Allow pages to choose their own colors\' ' + 1.4415 + 'is deactivated in the browser.')); 1.4416 + PDFView.fallback(); 1.4417 + } 1.4418 + if (error) { 1.4419 + PDFView.error(mozL10n.get('rendering_error', null, 1.4420 + 'An error occurred while rendering the page.'), error); 1.4421 + } 1.4422 + 1.4423 + self.stats = pdfPage.stats; 1.4424 + self.updateStats(); 1.4425 + if (self.onAfterDraw) { 1.4426 + self.onAfterDraw(); 1.4427 + } 1.4428 + 1.4429 + cache.push(self); 1.4430 + 1.4431 + var event = document.createEvent('CustomEvent'); 1.4432 + event.initCustomEvent('pagerender', true, true, { 1.4433 + pageNumber: pdfPage.pageNumber 1.4434 + }); 1.4435 + div.dispatchEvent(event); 1.4436 + 1.4437 + FirefoxCom.request('reportTelemetry', JSON.stringify({ 1.4438 + type: 'pageInfo' 1.4439 + })); 1.4440 + // TODO add stream types report here 1.4441 + callback(); 1.4442 + } 1.4443 + 1.4444 + var renderContext = { 1.4445 + canvasContext: ctx, 1.4446 + viewport: this.viewport, 1.4447 + textLayer: textLayer, 1.4448 + // intent: 'default', // === 'display' 1.4449 + continueCallback: function pdfViewcContinueCallback(cont) { 1.4450 + if (PDFView.highestPriorityPage !== 'page' + self.id) { 1.4451 + self.renderingState = RenderingStates.PAUSED; 1.4452 + self.resume = function resumeCallback() { 1.4453 + self.renderingState = RenderingStates.RUNNING; 1.4454 + cont(); 1.4455 + }; 1.4456 + return; 1.4457 + } 1.4458 + cont(); 1.4459 + } 1.4460 + }; 1.4461 + var renderTask = this.renderTask = this.pdfPage.render(renderContext); 1.4462 + 1.4463 + this.renderTask.promise.then( 1.4464 + function pdfPageRenderCallback() { 1.4465 + pageViewDrawCallback(null); 1.4466 + if (textLayer) { 1.4467 + self.getTextContent().then( 1.4468 + function textContentResolved(textContent) { 1.4469 + textLayer.setTextContent(textContent); 1.4470 + } 1.4471 + ); 1.4472 + } 1.4473 + }, 1.4474 + function pdfPageRenderError(error) { 1.4475 + pageViewDrawCallback(error); 1.4476 + } 1.4477 + ); 1.4478 + 1.4479 + setupAnnotations(div, pdfPage, this.viewport); 1.4480 + div.setAttribute('data-loaded', true); 1.4481 + }; 1.4482 + 1.4483 + this.beforePrint = function pageViewBeforePrint() { 1.4484 + var pdfPage = this.pdfPage; 1.4485 + 1.4486 + var viewport = pdfPage.getViewport(1); 1.4487 + // Use the same hack we use for high dpi displays for printing to get better 1.4488 + // output until bug 811002 is fixed in FF. 1.4489 + var PRINT_OUTPUT_SCALE = 2; 1.4490 + var canvas = document.createElement('canvas'); 1.4491 + canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE; 1.4492 + canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE; 1.4493 + canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt'; 1.4494 + canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt'; 1.4495 + var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' + 1.4496 + (1 / PRINT_OUTPUT_SCALE) + ')'; 1.4497 + CustomStyle.setProp('transform' , canvas, cssScale); 1.4498 + CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); 1.4499 + 1.4500 + var printContainer = document.getElementById('printContainer'); 1.4501 + var canvasWrapper = document.createElement('div'); 1.4502 + canvasWrapper.style.width = viewport.width + 'pt'; 1.4503 + canvasWrapper.style.height = viewport.height + 'pt'; 1.4504 + canvasWrapper.appendChild(canvas); 1.4505 + printContainer.appendChild(canvasWrapper); 1.4506 + 1.4507 + canvas.mozPrintCallback = function(obj) { 1.4508 + var ctx = obj.context; 1.4509 + 1.4510 + ctx.save(); 1.4511 + ctx.fillStyle = 'rgb(255, 255, 255)'; 1.4512 + ctx.fillRect(0, 0, canvas.width, canvas.height); 1.4513 + ctx.restore(); 1.4514 + ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE); 1.4515 + 1.4516 + var renderContext = { 1.4517 + canvasContext: ctx, 1.4518 + viewport: viewport, 1.4519 + intent: 'print' 1.4520 + }; 1.4521 + 1.4522 + pdfPage.render(renderContext).promise.then(function() { 1.4523 + // Tell the printEngine that rendering this canvas/page has finished. 1.4524 + obj.done(); 1.4525 + }, function(error) { 1.4526 + console.error(error); 1.4527 + // Tell the printEngine that rendering this canvas/page has failed. 1.4528 + // This will make the print proces stop. 1.4529 + if ('abort' in obj) { 1.4530 + obj.abort(); 1.4531 + } else { 1.4532 + obj.done(); 1.4533 + } 1.4534 + }); 1.4535 + }; 1.4536 + }; 1.4537 + 1.4538 + this.updateStats = function pageViewUpdateStats() { 1.4539 + if (!this.stats) { 1.4540 + return; 1.4541 + } 1.4542 + 1.4543 + if (PDFJS.pdfBug && Stats.enabled) { 1.4544 + var stats = this.stats; 1.4545 + Stats.add(this.id, stats); 1.4546 + } 1.4547 + }; 1.4548 +}; 1.4549 + 1.4550 + 1.4551 +var ThumbnailView = function thumbnailView(container, id, defaultViewport) { 1.4552 + var anchor = document.createElement('a'); 1.4553 + anchor.href = PDFView.getAnchorUrl('#page=' + id); 1.4554 + anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); 1.4555 + anchor.onclick = function stopNavigation() { 1.4556 + PDFView.page = id; 1.4557 + return false; 1.4558 + }; 1.4559 + 1.4560 + this.pdfPage = undefined; 1.4561 + this.viewport = defaultViewport; 1.4562 + this.pdfPageRotate = defaultViewport.rotation; 1.4563 + 1.4564 + this.rotation = 0; 1.4565 + this.pageWidth = this.viewport.width; 1.4566 + this.pageHeight = this.viewport.height; 1.4567 + this.pageRatio = this.pageWidth / this.pageHeight; 1.4568 + this.id = id; 1.4569 + 1.4570 + this.canvasWidth = 98; 1.4571 + this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight; 1.4572 + this.scale = (this.canvasWidth / this.pageWidth); 1.4573 + 1.4574 + var div = this.el = document.createElement('div'); 1.4575 + div.id = 'thumbnailContainer' + id; 1.4576 + div.className = 'thumbnail'; 1.4577 + 1.4578 + if (id === 1) { 1.4579 + // Highlight the thumbnail of the first page when no page number is 1.4580 + // specified (or exists in cache) when the document is loaded. 1.4581 + div.classList.add('selected'); 1.4582 + } 1.4583 + 1.4584 + var ring = document.createElement('div'); 1.4585 + ring.className = 'thumbnailSelectionRing'; 1.4586 + ring.style.width = this.canvasWidth + 'px'; 1.4587 + ring.style.height = this.canvasHeight + 'px'; 1.4588 + 1.4589 + div.appendChild(ring); 1.4590 + anchor.appendChild(div); 1.4591 + container.appendChild(anchor); 1.4592 + 1.4593 + this.hasImage = false; 1.4594 + this.renderingState = RenderingStates.INITIAL; 1.4595 + 1.4596 + this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) { 1.4597 + this.pdfPage = pdfPage; 1.4598 + this.pdfPageRotate = pdfPage.rotate; 1.4599 + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; 1.4600 + this.viewport = pdfPage.getViewport(1, totalRotation); 1.4601 + this.update(); 1.4602 + }; 1.4603 + 1.4604 + this.update = function thumbnailViewUpdate(rotation) { 1.4605 + if (rotation !== undefined) { 1.4606 + this.rotation = rotation; 1.4607 + } 1.4608 + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; 1.4609 + this.viewport = this.viewport.clone({ 1.4610 + scale: 1, 1.4611 + rotation: totalRotation 1.4612 + }); 1.4613 + this.pageWidth = this.viewport.width; 1.4614 + this.pageHeight = this.viewport.height; 1.4615 + this.pageRatio = this.pageWidth / this.pageHeight; 1.4616 + 1.4617 + this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight; 1.4618 + this.scale = (this.canvasWidth / this.pageWidth); 1.4619 + 1.4620 + div.removeAttribute('data-loaded'); 1.4621 + ring.textContent = ''; 1.4622 + ring.style.width = this.canvasWidth + 'px'; 1.4623 + ring.style.height = this.canvasHeight + 'px'; 1.4624 + 1.4625 + this.hasImage = false; 1.4626 + this.renderingState = RenderingStates.INITIAL; 1.4627 + this.resume = null; 1.4628 + }; 1.4629 + 1.4630 + this.getPageDrawContext = function thumbnailViewGetPageDrawContext() { 1.4631 + var canvas = document.createElement('canvas'); 1.4632 + canvas.id = 'thumbnail' + id; 1.4633 + 1.4634 + canvas.width = this.canvasWidth; 1.4635 + canvas.height = this.canvasHeight; 1.4636 + canvas.className = 'thumbnailImage'; 1.4637 + canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas', 1.4638 + {page: id}, 'Thumbnail of Page {{page}}')); 1.4639 + 1.4640 + div.setAttribute('data-loaded', true); 1.4641 + 1.4642 + ring.appendChild(canvas); 1.4643 + 1.4644 + var ctx = canvas.getContext('2d'); 1.4645 + ctx.save(); 1.4646 + ctx.fillStyle = 'rgb(255, 255, 255)'; 1.4647 + ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); 1.4648 + ctx.restore(); 1.4649 + return ctx; 1.4650 + }; 1.4651 + 1.4652 + this.drawingRequired = function thumbnailViewDrawingRequired() { 1.4653 + return !this.hasImage; 1.4654 + }; 1.4655 + 1.4656 + this.draw = function thumbnailViewDraw(callback) { 1.4657 + if (!this.pdfPage) { 1.4658 + var promise = PDFView.getPage(this.id); 1.4659 + promise.then(function(pdfPage) { 1.4660 + this.setPdfPage(pdfPage); 1.4661 + this.draw(callback); 1.4662 + }.bind(this)); 1.4663 + return; 1.4664 + } 1.4665 + 1.4666 + if (this.renderingState !== RenderingStates.INITIAL) { 1.4667 + console.error('Must be in new state before drawing'); 1.4668 + } 1.4669 + 1.4670 + this.renderingState = RenderingStates.RUNNING; 1.4671 + if (this.hasImage) { 1.4672 + callback(); 1.4673 + return; 1.4674 + } 1.4675 + 1.4676 + var self = this; 1.4677 + var ctx = this.getPageDrawContext(); 1.4678 + var drawViewport = this.viewport.clone({ scale: this.scale }); 1.4679 + var renderContext = { 1.4680 + canvasContext: ctx, 1.4681 + viewport: drawViewport, 1.4682 + continueCallback: function(cont) { 1.4683 + if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) { 1.4684 + self.renderingState = RenderingStates.PAUSED; 1.4685 + self.resume = function() { 1.4686 + self.renderingState = RenderingStates.RUNNING; 1.4687 + cont(); 1.4688 + }; 1.4689 + return; 1.4690 + } 1.4691 + cont(); 1.4692 + } 1.4693 + }; 1.4694 + this.pdfPage.render(renderContext).promise.then( 1.4695 + function pdfPageRenderCallback() { 1.4696 + self.renderingState = RenderingStates.FINISHED; 1.4697 + callback(); 1.4698 + }, 1.4699 + function pdfPageRenderError(error) { 1.4700 + self.renderingState = RenderingStates.FINISHED; 1.4701 + callback(); 1.4702 + } 1.4703 + ); 1.4704 + this.hasImage = true; 1.4705 + }; 1.4706 + 1.4707 + this.setImage = function thumbnailViewSetImage(img) { 1.4708 + if (!this.pdfPage) { 1.4709 + var promise = PDFView.getPage(this.id); 1.4710 + promise.then(function(pdfPage) { 1.4711 + this.setPdfPage(pdfPage); 1.4712 + this.setImage(img); 1.4713 + }.bind(this)); 1.4714 + return; 1.4715 + } 1.4716 + if (this.hasImage || !img) { 1.4717 + return; 1.4718 + } 1.4719 + this.renderingState = RenderingStates.FINISHED; 1.4720 + var ctx = this.getPageDrawContext(); 1.4721 + ctx.drawImage(img, 0, 0, img.width, img.height, 1.4722 + 0, 0, ctx.canvas.width, ctx.canvas.height); 1.4723 + 1.4724 + this.hasImage = true; 1.4725 + }; 1.4726 +}; 1.4727 + 1.4728 + 1.4729 +var FIND_SCROLL_OFFSET_TOP = -50; 1.4730 +var FIND_SCROLL_OFFSET_LEFT = -400; 1.4731 + 1.4732 +/** 1.4733 + * TextLayerBuilder provides text-selection 1.4734 + * functionality for the PDF. It does this 1.4735 + * by creating overlay divs over the PDF 1.4736 + * text. This divs contain text that matches 1.4737 + * the PDF text they are overlaying. This 1.4738 + * object also provides for a way to highlight 1.4739 + * text that is being searched for. 1.4740 + */ 1.4741 +var TextLayerBuilder = function textLayerBuilder(options) { 1.4742 + var textLayerFrag = document.createDocumentFragment(); 1.4743 + 1.4744 + this.textLayerDiv = options.textLayerDiv; 1.4745 + this.layoutDone = false; 1.4746 + this.divContentDone = false; 1.4747 + this.pageIdx = options.pageIndex; 1.4748 + this.matches = []; 1.4749 + this.lastScrollSource = options.lastScrollSource; 1.4750 + this.viewport = options.viewport; 1.4751 + this.isViewerInPresentationMode = options.isViewerInPresentationMode; 1.4752 + this.textDivs = []; 1.4753 + 1.4754 + if (typeof PDFFindController === 'undefined') { 1.4755 + window.PDFFindController = null; 1.4756 + } 1.4757 + 1.4758 + if (typeof this.lastScrollSource === 'undefined') { 1.4759 + this.lastScrollSource = null; 1.4760 + } 1.4761 + 1.4762 + this.renderLayer = function textLayerBuilderRenderLayer() { 1.4763 + var textDivs = this.textDivs; 1.4764 + var canvas = document.createElement('canvas'); 1.4765 + var ctx = canvas.getContext('2d'); 1.4766 + 1.4767 + // No point in rendering so many divs as it'd make the browser unusable 1.4768 + // even after the divs are rendered 1.4769 + var MAX_TEXT_DIVS_TO_RENDER = 100000; 1.4770 + if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) { 1.4771 + return; 1.4772 + } 1.4773 + 1.4774 + for (var i = 0, ii = textDivs.length; i < ii; i++) { 1.4775 + var textDiv = textDivs[i]; 1.4776 + if ('isWhitespace' in textDiv.dataset) { 1.4777 + continue; 1.4778 + } 1.4779 + 1.4780 + ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily; 1.4781 + var width = ctx.measureText(textDiv.textContent).width; 1.4782 + 1.4783 + if (width > 0) { 1.4784 + textLayerFrag.appendChild(textDiv); 1.4785 + var textScale = textDiv.dataset.canvasWidth / width; 1.4786 + var rotation = textDiv.dataset.angle; 1.4787 + var transform = 'scale(' + textScale + ', 1)'; 1.4788 + transform = 'rotate(' + rotation + 'deg) ' + transform; 1.4789 + CustomStyle.setProp('transform' , textDiv, transform); 1.4790 + CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%'); 1.4791 + } 1.4792 + } 1.4793 + 1.4794 + this.textLayerDiv.appendChild(textLayerFrag); 1.4795 + this.renderingDone = true; 1.4796 + this.updateMatches(); 1.4797 + }; 1.4798 + 1.4799 + this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() { 1.4800 + // Schedule renderLayout() if user has been scrolling, otherwise 1.4801 + // run it right away 1.4802 + var RENDER_DELAY = 200; // in ms 1.4803 + var self = this; 1.4804 + var lastScroll = (this.lastScrollSource === null ? 1.4805 + 0 : this.lastScrollSource.lastScroll); 1.4806 + 1.4807 + if (Date.now() - lastScroll > RENDER_DELAY) { 1.4808 + // Render right away 1.4809 + this.renderLayer(); 1.4810 + } else { 1.4811 + // Schedule 1.4812 + if (this.renderTimer) { 1.4813 + clearTimeout(this.renderTimer); 1.4814 + } 1.4815 + this.renderTimer = setTimeout(function() { 1.4816 + self.setupRenderLayoutTimer(); 1.4817 + }, RENDER_DELAY); 1.4818 + } 1.4819 + }; 1.4820 + 1.4821 + this.appendText = function textLayerBuilderAppendText(geom, styles) { 1.4822 + var style = styles[geom.fontName]; 1.4823 + var textDiv = document.createElement('div'); 1.4824 + this.textDivs.push(textDiv); 1.4825 + if (!/\S/.test(geom.str)) { 1.4826 + textDiv.dataset.isWhitespace = true; 1.4827 + return; 1.4828 + } 1.4829 + var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform); 1.4830 + var angle = Math.atan2(tx[1], tx[0]); 1.4831 + if (style.vertical) { 1.4832 + angle += Math.PI / 2; 1.4833 + } 1.4834 + var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3])); 1.4835 + var fontAscent = (style.ascent ? style.ascent * fontHeight : 1.4836 + (style.descent ? (1 + style.descent) * fontHeight : fontHeight)); 1.4837 + 1.4838 + textDiv.style.position = 'absolute'; 1.4839 + textDiv.style.left = (tx[4] + (fontAscent * Math.sin(angle))) + 'px'; 1.4840 + textDiv.style.top = (tx[5] - (fontAscent * Math.cos(angle))) + 'px'; 1.4841 + textDiv.style.fontSize = fontHeight + 'px'; 1.4842 + textDiv.style.fontFamily = style.fontFamily; 1.4843 + 1.4844 + textDiv.textContent = geom.str; 1.4845 + textDiv.dataset.fontName = geom.fontName; 1.4846 + textDiv.dataset.angle = angle * (180 / Math.PI); 1.4847 + if (style.vertical) { 1.4848 + textDiv.dataset.canvasWidth = geom.height * this.viewport.scale; 1.4849 + } else { 1.4850 + textDiv.dataset.canvasWidth = geom.width * this.viewport.scale; 1.4851 + } 1.4852 + 1.4853 + }; 1.4854 + 1.4855 + this.setTextContent = function textLayerBuilderSetTextContent(textContent) { 1.4856 + this.textContent = textContent; 1.4857 + 1.4858 + var textItems = textContent.items; 1.4859 + for (var i = 0; i < textItems.length; i++) { 1.4860 + this.appendText(textItems[i], textContent.styles); 1.4861 + } 1.4862 + this.divContentDone = true; 1.4863 + 1.4864 + this.setupRenderLayoutTimer(); 1.4865 + }; 1.4866 + 1.4867 + this.convertMatches = function textLayerBuilderConvertMatches(matches) { 1.4868 + var i = 0; 1.4869 + var iIndex = 0; 1.4870 + var bidiTexts = this.textContent.items; 1.4871 + var end = bidiTexts.length - 1; 1.4872 + var queryLen = (PDFFindController === null ? 1.4873 + 0 : PDFFindController.state.query.length); 1.4874 + 1.4875 + var ret = []; 1.4876 + 1.4877 + // Loop over all the matches. 1.4878 + for (var m = 0; m < matches.length; m++) { 1.4879 + var matchIdx = matches[m]; 1.4880 + // # Calculate the begin position. 1.4881 + 1.4882 + // Loop over the divIdxs. 1.4883 + while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) { 1.4884 + iIndex += bidiTexts[i].str.length; 1.4885 + i++; 1.4886 + } 1.4887 + 1.4888 + // TODO: Do proper handling here if something goes wrong. 1.4889 + if (i == bidiTexts.length) { 1.4890 + console.error('Could not find matching mapping'); 1.4891 + } 1.4892 + 1.4893 + var match = { 1.4894 + begin: { 1.4895 + divIdx: i, 1.4896 + offset: matchIdx - iIndex 1.4897 + } 1.4898 + }; 1.4899 + 1.4900 + // # Calculate the end position. 1.4901 + matchIdx += queryLen; 1.4902 + 1.4903 + // Somewhat same array as above, but use a > instead of >= to get the end 1.4904 + // position right. 1.4905 + while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) { 1.4906 + iIndex += bidiTexts[i].str.length; 1.4907 + i++; 1.4908 + } 1.4909 + 1.4910 + match.end = { 1.4911 + divIdx: i, 1.4912 + offset: matchIdx - iIndex 1.4913 + }; 1.4914 + ret.push(match); 1.4915 + } 1.4916 + 1.4917 + return ret; 1.4918 + }; 1.4919 + 1.4920 + this.renderMatches = function textLayerBuilder_renderMatches(matches) { 1.4921 + // Early exit if there is nothing to render. 1.4922 + if (matches.length === 0) { 1.4923 + return; 1.4924 + } 1.4925 + 1.4926 + var bidiTexts = this.textContent.items; 1.4927 + var textDivs = this.textDivs; 1.4928 + var prevEnd = null; 1.4929 + var isSelectedPage = (PDFFindController === null ? 1.4930 + false : (this.pageIdx === PDFFindController.selected.pageIdx)); 1.4931 + 1.4932 + var selectedMatchIdx = (PDFFindController === null ? 1.4933 + -1 : PDFFindController.selected.matchIdx); 1.4934 + 1.4935 + var highlightAll = (PDFFindController === null ? 1.4936 + false : PDFFindController.state.highlightAll); 1.4937 + 1.4938 + var infty = { 1.4939 + divIdx: -1, 1.4940 + offset: undefined 1.4941 + }; 1.4942 + 1.4943 + function beginText(begin, className) { 1.4944 + var divIdx = begin.divIdx; 1.4945 + var div = textDivs[divIdx]; 1.4946 + div.textContent = ''; 1.4947 + appendTextToDiv(divIdx, 0, begin.offset, className); 1.4948 + } 1.4949 + 1.4950 + function appendText(from, to, className) { 1.4951 + appendTextToDiv(from.divIdx, from.offset, to.offset, className); 1.4952 + } 1.4953 + 1.4954 + function appendTextToDiv(divIdx, fromOffset, toOffset, className) { 1.4955 + var div = textDivs[divIdx]; 1.4956 + 1.4957 + var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset); 1.4958 + var node = document.createTextNode(content); 1.4959 + if (className) { 1.4960 + var span = document.createElement('span'); 1.4961 + span.className = className; 1.4962 + span.appendChild(node); 1.4963 + div.appendChild(span); 1.4964 + return; 1.4965 + } 1.4966 + div.appendChild(node); 1.4967 + } 1.4968 + 1.4969 + function highlightDiv(divIdx, className) { 1.4970 + textDivs[divIdx].className = className; 1.4971 + } 1.4972 + 1.4973 + var i0 = selectedMatchIdx, i1 = i0 + 1, i; 1.4974 + 1.4975 + if (highlightAll) { 1.4976 + i0 = 0; 1.4977 + i1 = matches.length; 1.4978 + } else if (!isSelectedPage) { 1.4979 + // Not highlighting all and this isn't the selected page, so do nothing. 1.4980 + return; 1.4981 + } 1.4982 + 1.4983 + for (i = i0; i < i1; i++) { 1.4984 + var match = matches[i]; 1.4985 + var begin = match.begin; 1.4986 + var end = match.end; 1.4987 + 1.4988 + var isSelected = isSelectedPage && i === selectedMatchIdx; 1.4989 + var highlightSuffix = (isSelected ? ' selected' : ''); 1.4990 + if (isSelected && !this.isViewerInPresentationMode) { 1.4991 + scrollIntoView(textDivs[begin.divIdx], { top: FIND_SCROLL_OFFSET_TOP, 1.4992 + left: FIND_SCROLL_OFFSET_LEFT }); 1.4993 + } 1.4994 + 1.4995 + // Match inside new div. 1.4996 + if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { 1.4997 + // If there was a previous div, then add the text at the end 1.4998 + if (prevEnd !== null) { 1.4999 + appendText(prevEnd, infty); 1.5000 + } 1.5001 + // clears the divs and set the content until the begin point. 1.5002 + beginText(begin); 1.5003 + } else { 1.5004 + appendText(prevEnd, begin); 1.5005 + } 1.5006 + 1.5007 + if (begin.divIdx === end.divIdx) { 1.5008 + appendText(begin, end, 'highlight' + highlightSuffix); 1.5009 + } else { 1.5010 + appendText(begin, infty, 'highlight begin' + highlightSuffix); 1.5011 + for (var n = begin.divIdx + 1; n < end.divIdx; n++) { 1.5012 + highlightDiv(n, 'highlight middle' + highlightSuffix); 1.5013 + } 1.5014 + beginText(end, 'highlight end' + highlightSuffix); 1.5015 + } 1.5016 + prevEnd = end; 1.5017 + } 1.5018 + 1.5019 + if (prevEnd) { 1.5020 + appendText(prevEnd, infty); 1.5021 + } 1.5022 + }; 1.5023 + 1.5024 + this.updateMatches = function textLayerUpdateMatches() { 1.5025 + // Only show matches, once all rendering is done. 1.5026 + if (!this.renderingDone) { 1.5027 + return; 1.5028 + } 1.5029 + 1.5030 + // Clear out all matches. 1.5031 + var matches = this.matches; 1.5032 + var textDivs = this.textDivs; 1.5033 + var bidiTexts = this.textContent.items; 1.5034 + var clearedUntilDivIdx = -1; 1.5035 + 1.5036 + // Clear out all current matches. 1.5037 + for (var i = 0; i < matches.length; i++) { 1.5038 + var match = matches[i]; 1.5039 + var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); 1.5040 + for (var n = begin; n <= match.end.divIdx; n++) { 1.5041 + var div = textDivs[n]; 1.5042 + div.textContent = bidiTexts[n].str; 1.5043 + div.className = ''; 1.5044 + } 1.5045 + clearedUntilDivIdx = match.end.divIdx + 1; 1.5046 + } 1.5047 + 1.5048 + if (PDFFindController === null || !PDFFindController.active) { 1.5049 + return; 1.5050 + } 1.5051 + 1.5052 + // Convert the matches on the page controller into the match format used 1.5053 + // for the textLayer. 1.5054 + this.matches = matches = (this.convertMatches(PDFFindController === null ? 1.5055 + [] : (PDFFindController.pageMatches[this.pageIdx] || []))); 1.5056 + 1.5057 + this.renderMatches(this.matches); 1.5058 + }; 1.5059 +}; 1.5060 + 1.5061 + 1.5062 + 1.5063 +var DocumentOutlineView = function documentOutlineView(outline) { 1.5064 + var outlineView = document.getElementById('outlineView'); 1.5065 + while (outlineView.firstChild) { 1.5066 + outlineView.removeChild(outlineView.firstChild); 1.5067 + } 1.5068 + 1.5069 + if (!outline) { 1.5070 + if (!outlineView.classList.contains('hidden')) { 1.5071 + PDFView.switchSidebarView('thumbs'); 1.5072 + } 1.5073 + return; 1.5074 + } 1.5075 + 1.5076 + function bindItemLink(domObj, item) { 1.5077 + domObj.href = PDFView.getDestinationHash(item.dest); 1.5078 + domObj.onclick = function documentOutlineViewOnclick(e) { 1.5079 + PDFView.navigateTo(item.dest); 1.5080 + return false; 1.5081 + }; 1.5082 + } 1.5083 + 1.5084 + 1.5085 + var queue = [{parent: outlineView, items: outline}]; 1.5086 + while (queue.length > 0) { 1.5087 + var levelData = queue.shift(); 1.5088 + var i, n = levelData.items.length; 1.5089 + for (i = 0; i < n; i++) { 1.5090 + var item = levelData.items[i]; 1.5091 + var div = document.createElement('div'); 1.5092 + div.className = 'outlineItem'; 1.5093 + var a = document.createElement('a'); 1.5094 + bindItemLink(a, item); 1.5095 + a.textContent = item.title; 1.5096 + div.appendChild(a); 1.5097 + 1.5098 + if (item.items.length > 0) { 1.5099 + var itemsDiv = document.createElement('div'); 1.5100 + itemsDiv.className = 'outlineItems'; 1.5101 + div.appendChild(itemsDiv); 1.5102 + queue.push({parent: itemsDiv, items: item.items}); 1.5103 + } 1.5104 + 1.5105 + levelData.parent.appendChild(div); 1.5106 + } 1.5107 + } 1.5108 +}; 1.5109 + 1.5110 +var DocumentAttachmentsView = function documentAttachmentsView(attachments) { 1.5111 + var attachmentsView = document.getElementById('attachmentsView'); 1.5112 + while (attachmentsView.firstChild) { 1.5113 + attachmentsView.removeChild(attachmentsView.firstChild); 1.5114 + } 1.5115 + 1.5116 + if (!attachments) { 1.5117 + if (!attachmentsView.classList.contains('hidden')) { 1.5118 + PDFView.switchSidebarView('thumbs'); 1.5119 + } 1.5120 + return; 1.5121 + } 1.5122 + 1.5123 + function bindItemLink(domObj, item) { 1.5124 + domObj.href = '#'; 1.5125 + domObj.onclick = function documentAttachmentsViewOnclick(e) { 1.5126 + var downloadManager = new DownloadManager(); 1.5127 + downloadManager.downloadData(item.content, getFileName(item.filename), 1.5128 + ''); 1.5129 + return false; 1.5130 + }; 1.5131 + } 1.5132 + 1.5133 + var names = Object.keys(attachments).sort(function(a,b) { 1.5134 + return a.toLowerCase().localeCompare(b.toLowerCase()); 1.5135 + }); 1.5136 + for (var i = 0, ii = names.length; i < ii; i++) { 1.5137 + var item = attachments[names[i]]; 1.5138 + var div = document.createElement('div'); 1.5139 + div.className = 'attachmentsItem'; 1.5140 + var a = document.createElement('a'); 1.5141 + bindItemLink(a, item); 1.5142 + a.textContent = getFileName(item.filename); 1.5143 + div.appendChild(a); 1.5144 + attachmentsView.appendChild(div); 1.5145 + } 1.5146 +}; 1.5147 + 1.5148 + 1.5149 +function webViewerLoad(evt) { 1.5150 + PDFView.initialize().then(webViewerInitialized); 1.5151 +} 1.5152 + 1.5153 +function webViewerInitialized() { 1.5154 + var file = window.location.href.split('#')[0]; 1.5155 + 1.5156 + document.getElementById('openFile').setAttribute('hidden', 'true'); 1.5157 + document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true'); 1.5158 + 1.5159 + // Special debugging flags in the hash section of the URL. 1.5160 + var hash = document.location.hash.substring(1); 1.5161 + var hashParams = PDFView.parseQueryString(hash); 1.5162 + 1.5163 + if ('disableWorker' in hashParams) { 1.5164 + PDFJS.disableWorker = (hashParams['disableWorker'] === 'true'); 1.5165 + } 1.5166 + 1.5167 + if ('disableRange' in hashParams) { 1.5168 + PDFJS.disableRange = (hashParams['disableRange'] === 'true'); 1.5169 + } 1.5170 + 1.5171 + if ('disableAutoFetch' in hashParams) { 1.5172 + PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true'); 1.5173 + } 1.5174 + 1.5175 + if ('disableFontFace' in hashParams) { 1.5176 + PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true'); 1.5177 + } 1.5178 + 1.5179 + if ('disableHistory' in hashParams) { 1.5180 + PDFJS.disableHistory = (hashParams['disableHistory'] === 'true'); 1.5181 + } 1.5182 + 1.5183 + if ('webgl' in hashParams) { 1.5184 + PDFJS.disableWebGL = (hashParams['webgl'] !== 'true'); 1.5185 + } 1.5186 + 1.5187 + if ('useOnlyCssZoom' in hashParams) { 1.5188 + USE_ONLY_CSS_ZOOM = (hashParams['useOnlyCssZoom'] === 'true'); 1.5189 + } 1.5190 + 1.5191 + if ('verbosity' in hashParams) { 1.5192 + PDFJS.verbosity = hashParams['verbosity'] | 0; 1.5193 + } 1.5194 + 1.5195 + if ('ignoreCurrentPositionOnZoom' in hashParams) { 1.5196 + IGNORE_CURRENT_POSITION_ON_ZOOM = 1.5197 + (hashParams['ignoreCurrentPositionOnZoom'] === 'true'); 1.5198 + } 1.5199 + 1.5200 + 1.5201 + 1.5202 + if (!PDFView.supportsDocumentFonts) { 1.5203 + PDFJS.disableFontFace = true; 1.5204 + console.warn(mozL10n.get('web_fonts_disabled', null, 1.5205 + 'Web fonts are disabled: unable to use embedded PDF fonts.')); 1.5206 + } 1.5207 + 1.5208 + if ('textLayer' in hashParams) { 1.5209 + switch (hashParams['textLayer']) { 1.5210 + case 'off': 1.5211 + PDFJS.disableTextLayer = true; 1.5212 + break; 1.5213 + case 'visible': 1.5214 + case 'shadow': 1.5215 + case 'hover': 1.5216 + var viewer = document.getElementById('viewer'); 1.5217 + viewer.classList.add('textLayer-' + hashParams['textLayer']); 1.5218 + break; 1.5219 + } 1.5220 + } 1.5221 + 1.5222 + if ('pdfBug' in hashParams && FirefoxCom.requestSync('pdfBugEnabled')) { 1.5223 + PDFJS.pdfBug = true; 1.5224 + var pdfBug = hashParams['pdfBug']; 1.5225 + var enabled = pdfBug.split(','); 1.5226 + PDFBug.enable(enabled); 1.5227 + PDFBug.init(); 1.5228 + } 1.5229 + 1.5230 + if (!PDFView.supportsPrinting) { 1.5231 + document.getElementById('print').classList.add('hidden'); 1.5232 + document.getElementById('secondaryPrint').classList.add('hidden'); 1.5233 + } 1.5234 + 1.5235 + if (!PDFView.supportsFullscreen) { 1.5236 + document.getElementById('presentationMode').classList.add('hidden'); 1.5237 + document.getElementById('secondaryPresentationMode'). 1.5238 + classList.add('hidden'); 1.5239 + } 1.5240 + 1.5241 + if (PDFView.supportsIntegratedFind) { 1.5242 + document.getElementById('viewFind').classList.add('hidden'); 1.5243 + } 1.5244 + 1.5245 + // Listen for unsuporrted features to trigger the fallback UI. 1.5246 + PDFJS.UnsupportedManager.listen(PDFView.fallback.bind(PDFView)); 1.5247 + 1.5248 + // Suppress context menus for some controls 1.5249 + document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler; 1.5250 + 1.5251 + var mainContainer = document.getElementById('mainContainer'); 1.5252 + var outerContainer = document.getElementById('outerContainer'); 1.5253 + mainContainer.addEventListener('transitionend', function(e) { 1.5254 + if (e.target == mainContainer) { 1.5255 + var event = document.createEvent('UIEvents'); 1.5256 + event.initUIEvent('resize', false, false, window, 0); 1.5257 + window.dispatchEvent(event); 1.5258 + outerContainer.classList.remove('sidebarMoving'); 1.5259 + } 1.5260 + }, true); 1.5261 + 1.5262 + document.getElementById('sidebarToggle').addEventListener('click', 1.5263 + function() { 1.5264 + this.classList.toggle('toggled'); 1.5265 + outerContainer.classList.add('sidebarMoving'); 1.5266 + outerContainer.classList.toggle('sidebarOpen'); 1.5267 + PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen'); 1.5268 + PDFView.renderHighestPriority(); 1.5269 + }); 1.5270 + 1.5271 + document.getElementById('viewThumbnail').addEventListener('click', 1.5272 + function() { 1.5273 + PDFView.switchSidebarView('thumbs'); 1.5274 + }); 1.5275 + 1.5276 + document.getElementById('viewOutline').addEventListener('click', 1.5277 + function() { 1.5278 + PDFView.switchSidebarView('outline'); 1.5279 + }); 1.5280 + 1.5281 + document.getElementById('viewAttachments').addEventListener('click', 1.5282 + function() { 1.5283 + PDFView.switchSidebarView('attachments'); 1.5284 + }); 1.5285 + 1.5286 + document.getElementById('previous').addEventListener('click', 1.5287 + function() { 1.5288 + PDFView.page--; 1.5289 + }); 1.5290 + 1.5291 + document.getElementById('next').addEventListener('click', 1.5292 + function() { 1.5293 + PDFView.page++; 1.5294 + }); 1.5295 + 1.5296 + document.getElementById('zoomIn').addEventListener('click', 1.5297 + function() { 1.5298 + PDFView.zoomIn(); 1.5299 + }); 1.5300 + 1.5301 + document.getElementById('zoomOut').addEventListener('click', 1.5302 + function() { 1.5303 + PDFView.zoomOut(); 1.5304 + }); 1.5305 + 1.5306 + document.getElementById('pageNumber').addEventListener('click', 1.5307 + function() { 1.5308 + this.select(); 1.5309 + }); 1.5310 + 1.5311 + document.getElementById('pageNumber').addEventListener('change', 1.5312 + function() { 1.5313 + // Handle the user inputting a floating point number. 1.5314 + PDFView.page = (this.value | 0); 1.5315 + 1.5316 + if (this.value !== (this.value | 0).toString()) { 1.5317 + this.value = PDFView.page; 1.5318 + } 1.5319 + }); 1.5320 + 1.5321 + document.getElementById('scaleSelect').addEventListener('change', 1.5322 + function() { 1.5323 + PDFView.setScale(this.value); 1.5324 + }); 1.5325 + 1.5326 + document.getElementById('presentationMode').addEventListener('click', 1.5327 + SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar)); 1.5328 + 1.5329 + document.getElementById('openFile').addEventListener('click', 1.5330 + SecondaryToolbar.openFileClick.bind(SecondaryToolbar)); 1.5331 + 1.5332 + document.getElementById('print').addEventListener('click', 1.5333 + SecondaryToolbar.printClick.bind(SecondaryToolbar)); 1.5334 + 1.5335 + document.getElementById('download').addEventListener('click', 1.5336 + SecondaryToolbar.downloadClick.bind(SecondaryToolbar)); 1.5337 + 1.5338 + PDFView.setTitleUsingUrl(file); 1.5339 + PDFView.initPassiveLoading(); 1.5340 + return; 1.5341 + 1.5342 + if (file) { 1.5343 + PDFView.open(file, 0); 1.5344 + } 1.5345 +} 1.5346 + 1.5347 +document.addEventListener('DOMContentLoaded', webViewerLoad, true); 1.5348 + 1.5349 +function updateViewarea() { 1.5350 + 1.5351 + if (!PDFView.initialized) { 1.5352 + return; 1.5353 + } 1.5354 + var visible = PDFView.getVisiblePages(); 1.5355 + var visiblePages = visible.views; 1.5356 + if (visiblePages.length === 0) { 1.5357 + return; 1.5358 + } 1.5359 + 1.5360 + PDFView.renderHighestPriority(); 1.5361 + 1.5362 + var currentId = PDFView.page; 1.5363 + var firstPage = visible.first; 1.5364 + 1.5365 + for (var i = 0, ii = visiblePages.length, stillFullyVisible = false; 1.5366 + i < ii; ++i) { 1.5367 + var page = visiblePages[i]; 1.5368 + 1.5369 + if (page.percent < 100) { 1.5370 + break; 1.5371 + } 1.5372 + if (page.id === PDFView.page) { 1.5373 + stillFullyVisible = true; 1.5374 + break; 1.5375 + } 1.5376 + } 1.5377 + 1.5378 + if (!stillFullyVisible) { 1.5379 + currentId = visiblePages[0].id; 1.5380 + } 1.5381 + 1.5382 + if (!PresentationMode.active) { 1.5383 + updateViewarea.inProgress = true; // used in "set page" 1.5384 + PDFView.page = currentId; 1.5385 + updateViewarea.inProgress = false; 1.5386 + } 1.5387 + 1.5388 + var currentScale = PDFView.currentScale; 1.5389 + var currentScaleValue = PDFView.currentScaleValue; 1.5390 + var normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? 1.5391 + Math.round(currentScale * 10000) / 100 : currentScaleValue; 1.5392 + 1.5393 + var pageNumber = firstPage.id; 1.5394 + var pdfOpenParams = '#page=' + pageNumber; 1.5395 + pdfOpenParams += '&zoom=' + normalizedScaleValue; 1.5396 + var currentPage = PDFView.pages[pageNumber - 1]; 1.5397 + var container = PDFView.container; 1.5398 + var topLeft = currentPage.getPagePoint((container.scrollLeft - firstPage.x), 1.5399 + (container.scrollTop - firstPage.y)); 1.5400 + var intLeft = Math.round(topLeft[0]); 1.5401 + var intTop = Math.round(topLeft[1]); 1.5402 + pdfOpenParams += ',' + intLeft + ',' + intTop; 1.5403 + 1.5404 + if (PresentationMode.active || PresentationMode.switchInProgress) { 1.5405 + PDFView.currentPosition = null; 1.5406 + } else { 1.5407 + PDFView.currentPosition = { page: pageNumber, left: intLeft, top: intTop }; 1.5408 + } 1.5409 + 1.5410 + var store = PDFView.store; 1.5411 + store.initializedPromise.then(function() { 1.5412 + store.set('exists', true); 1.5413 + store.set('page', pageNumber); 1.5414 + store.set('zoom', normalizedScaleValue); 1.5415 + store.set('scrollLeft', intLeft); 1.5416 + store.set('scrollTop', intTop); 1.5417 + }); 1.5418 + var href = PDFView.getAnchorUrl(pdfOpenParams); 1.5419 + document.getElementById('viewBookmark').href = href; 1.5420 + document.getElementById('secondaryViewBookmark').href = href; 1.5421 + 1.5422 + // Update the current bookmark in the browsing history. 1.5423 + PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber); 1.5424 +} 1.5425 + 1.5426 +window.addEventListener('resize', function webViewerResize(evt) { 1.5427 + if (PDFView.initialized && 1.5428 + (document.getElementById('pageWidthOption').selected || 1.5429 + document.getElementById('pageFitOption').selected || 1.5430 + document.getElementById('pageAutoOption').selected)) { 1.5431 + PDFView.setScale(document.getElementById('scaleSelect').value); 1.5432 + } 1.5433 + updateViewarea(); 1.5434 + 1.5435 + // Set the 'max-height' CSS property of the secondary toolbar. 1.5436 + SecondaryToolbar.setMaxHeight(PDFView.container); 1.5437 +}); 1.5438 + 1.5439 +window.addEventListener('hashchange', function webViewerHashchange(evt) { 1.5440 + if (PDFHistory.isHashChangeUnlocked) { 1.5441 + PDFView.setHash(document.location.hash.substring(1)); 1.5442 + } 1.5443 +}); 1.5444 + 1.5445 + 1.5446 +function selectScaleOption(value) { 1.5447 + var options = document.getElementById('scaleSelect').options; 1.5448 + var predefinedValueFound = false; 1.5449 + for (var i = 0; i < options.length; i++) { 1.5450 + var option = options[i]; 1.5451 + if (option.value != value) { 1.5452 + option.selected = false; 1.5453 + continue; 1.5454 + } 1.5455 + option.selected = true; 1.5456 + predefinedValueFound = true; 1.5457 + } 1.5458 + return predefinedValueFound; 1.5459 +} 1.5460 + 1.5461 +window.addEventListener('localized', function localized(evt) { 1.5462 + document.getElementsByTagName('html')[0].dir = mozL10n.getDirection(); 1.5463 + 1.5464 + PDFView.animationStartedPromise.then(function() { 1.5465 + // Adjust the width of the zoom box to fit the content. 1.5466 + // Note: This is only done if the zoom box is actually visible, 1.5467 + // since otherwise element.clientWidth will return 0. 1.5468 + var container = document.getElementById('scaleSelectContainer'); 1.5469 + if (container.clientWidth > 0) { 1.5470 + var select = document.getElementById('scaleSelect'); 1.5471 + select.setAttribute('style', 'min-width: inherit;'); 1.5472 + var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING; 1.5473 + select.setAttribute('style', 'min-width: ' + 1.5474 + (width + SCALE_SELECT_PADDING) + 'px;'); 1.5475 + container.setAttribute('style', 'min-width: ' + width + 'px; ' + 1.5476 + 'max-width: ' + width + 'px;'); 1.5477 + } 1.5478 + 1.5479 + // Set the 'max-height' CSS property of the secondary toolbar. 1.5480 + SecondaryToolbar.setMaxHeight(PDFView.container); 1.5481 + }); 1.5482 +}, true); 1.5483 + 1.5484 +window.addEventListener('scalechange', function scalechange(evt) { 1.5485 + document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE); 1.5486 + document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE); 1.5487 + 1.5488 + var customScaleOption = document.getElementById('customScaleOption'); 1.5489 + customScaleOption.selected = false; 1.5490 + 1.5491 + if (!evt.resetAutoSettings && 1.5492 + (document.getElementById('pageWidthOption').selected || 1.5493 + document.getElementById('pageFitOption').selected || 1.5494 + document.getElementById('pageAutoOption').selected)) { 1.5495 + updateViewarea(); 1.5496 + return; 1.5497 + } 1.5498 + 1.5499 + var predefinedValueFound = selectScaleOption('' + evt.scale); 1.5500 + if (!predefinedValueFound) { 1.5501 + customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%'; 1.5502 + customScaleOption.selected = true; 1.5503 + } 1.5504 + updateViewarea(); 1.5505 +}, true); 1.5506 + 1.5507 +window.addEventListener('pagechange', function pagechange(evt) { 1.5508 + var page = evt.pageNumber; 1.5509 + if (PDFView.previousPageNumber !== page) { 1.5510 + document.getElementById('pageNumber').value = page; 1.5511 + var selected = document.querySelector('.thumbnail.selected'); 1.5512 + if (selected) { 1.5513 + selected.classList.remove('selected'); 1.5514 + } 1.5515 + var thumbnail = document.getElementById('thumbnailContainer' + page); 1.5516 + thumbnail.classList.add('selected'); 1.5517 + var visibleThumbs = PDFView.getVisibleThumbs(); 1.5518 + var numVisibleThumbs = visibleThumbs.views.length; 1.5519 + 1.5520 + // If the thumbnail isn't currently visible, scroll it into view. 1.5521 + if (numVisibleThumbs > 0) { 1.5522 + var first = visibleThumbs.first.id; 1.5523 + // Account for only one thumbnail being visible. 1.5524 + var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first); 1.5525 + if (page <= first || page >= last) { 1.5526 + scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN }); 1.5527 + } 1.5528 + } 1.5529 + } 1.5530 + document.getElementById('previous').disabled = (page <= 1); 1.5531 + document.getElementById('next').disabled = (page >= PDFView.pages.length); 1.5532 +}, true); 1.5533 + 1.5534 +function handleMouseWheel(evt) { 1.5535 + var MOUSE_WHEEL_DELTA_FACTOR = 40; 1.5536 + var ticks = (evt.type === 'DOMMouseScroll') ? -evt.detail : 1.5537 + evt.wheelDelta / MOUSE_WHEEL_DELTA_FACTOR; 1.5538 + var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn'; 1.5539 + 1.5540 + if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer 1.5541 + evt.preventDefault(); 1.5542 + PDFView[direction](Math.abs(ticks)); 1.5543 + } else if (PresentationMode.active) { 1.5544 + PDFView.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR); 1.5545 + } 1.5546 +} 1.5547 + 1.5548 +window.addEventListener('DOMMouseScroll', handleMouseWheel); 1.5549 +window.addEventListener('mousewheel', handleMouseWheel); 1.5550 + 1.5551 +window.addEventListener('click', function click(evt) { 1.5552 + if (!PresentationMode.active) { 1.5553 + if (SecondaryToolbar.opened && PDFView.container.contains(evt.target)) { 1.5554 + SecondaryToolbar.close(); 1.5555 + } 1.5556 + } else if (evt.button === 0) { 1.5557 + // Necessary since preventDefault() in 'mousedown' won't stop 1.5558 + // the event propagation in all circumstances in presentation mode. 1.5559 + evt.preventDefault(); 1.5560 + } 1.5561 +}, false); 1.5562 + 1.5563 +window.addEventListener('keydown', function keydown(evt) { 1.5564 + if (PasswordPrompt.visible) { 1.5565 + return; 1.5566 + } 1.5567 + 1.5568 + var handled = false; 1.5569 + var cmd = (evt.ctrlKey ? 1 : 0) | 1.5570 + (evt.altKey ? 2 : 0) | 1.5571 + (evt.shiftKey ? 4 : 0) | 1.5572 + (evt.metaKey ? 8 : 0); 1.5573 + 1.5574 + // First, handle the key bindings that are independent whether an input 1.5575 + // control is selected or not. 1.5576 + if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) { 1.5577 + // either CTRL or META key with optional SHIFT. 1.5578 + switch (evt.keyCode) { 1.5579 + case 70: // f 1.5580 + if (!PDFView.supportsIntegratedFind) { 1.5581 + PDFFindBar.open(); 1.5582 + handled = true; 1.5583 + } 1.5584 + break; 1.5585 + case 71: // g 1.5586 + if (!PDFView.supportsIntegratedFind) { 1.5587 + PDFFindBar.dispatchEvent('again', cmd === 5 || cmd === 12); 1.5588 + handled = true; 1.5589 + } 1.5590 + break; 1.5591 + case 61: // FF/Mac '=' 1.5592 + case 107: // FF '+' and '=' 1.5593 + case 187: // Chrome '+' 1.5594 + case 171: // FF with German keyboard 1.5595 + PDFView.zoomIn(); 1.5596 + handled = true; 1.5597 + break; 1.5598 + case 173: // FF/Mac '-' 1.5599 + case 109: // FF '-' 1.5600 + case 189: // Chrome '-' 1.5601 + PDFView.zoomOut(); 1.5602 + handled = true; 1.5603 + break; 1.5604 + case 48: // '0' 1.5605 + case 96: // '0' on Numpad of Swedish keyboard 1.5606 + // keeping it unhandled (to restore page zoom to 100%) 1.5607 + setTimeout(function () { 1.5608 + // ... and resetting the scale after browser adjusts its scale 1.5609 + PDFView.setScale(DEFAULT_SCALE, true); 1.5610 + }); 1.5611 + handled = false; 1.5612 + break; 1.5613 + } 1.5614 + } 1.5615 + 1.5616 + 1.5617 + // CTRL+ALT or Option+Command 1.5618 + if (cmd === 3 || cmd === 10) { 1.5619 + switch (evt.keyCode) { 1.5620 + case 80: // p 1.5621 + SecondaryToolbar.presentationModeClick(); 1.5622 + handled = true; 1.5623 + break; 1.5624 + case 71: // g 1.5625 + // focuses input#pageNumber field 1.5626 + document.getElementById('pageNumber').select(); 1.5627 + handled = true; 1.5628 + break; 1.5629 + } 1.5630 + } 1.5631 + 1.5632 + if (handled) { 1.5633 + evt.preventDefault(); 1.5634 + return; 1.5635 + } 1.5636 + 1.5637 + // Some shortcuts should not get handled if a control/input element 1.5638 + // is selected. 1.5639 + var curElement = document.activeElement || document.querySelector(':focus'); 1.5640 + var curElementTagName = curElement && curElement.tagName.toUpperCase(); 1.5641 + if (curElementTagName === 'INPUT' || 1.5642 + curElementTagName === 'TEXTAREA' || 1.5643 + curElementTagName === 'SELECT') { 1.5644 + // Make sure that the secondary toolbar is closed when Escape is pressed. 1.5645 + if (evt.keyCode !== 27) { // 'Esc' 1.5646 + return; 1.5647 + } 1.5648 + } 1.5649 + 1.5650 + if (cmd === 0) { // no control key pressed at all. 1.5651 + switch (evt.keyCode) { 1.5652 + case 38: // up arrow 1.5653 + case 33: // pg up 1.5654 + case 8: // backspace 1.5655 + if (!PresentationMode.active && 1.5656 + PDFView.currentScaleValue !== 'page-fit') { 1.5657 + break; 1.5658 + } 1.5659 + /* in presentation mode */ 1.5660 + /* falls through */ 1.5661 + case 37: // left arrow 1.5662 + // horizontal scrolling using arrow keys 1.5663 + if (PDFView.isHorizontalScrollbarEnabled) { 1.5664 + break; 1.5665 + } 1.5666 + /* falls through */ 1.5667 + case 75: // 'k' 1.5668 + case 80: // 'p' 1.5669 + PDFView.page--; 1.5670 + handled = true; 1.5671 + break; 1.5672 + case 27: // esc key 1.5673 + if (SecondaryToolbar.opened) { 1.5674 + SecondaryToolbar.close(); 1.5675 + handled = true; 1.5676 + } 1.5677 + if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) { 1.5678 + PDFFindBar.close(); 1.5679 + handled = true; 1.5680 + } 1.5681 + break; 1.5682 + case 40: // down arrow 1.5683 + case 34: // pg down 1.5684 + case 32: // spacebar 1.5685 + if (!PresentationMode.active && 1.5686 + PDFView.currentScaleValue !== 'page-fit') { 1.5687 + break; 1.5688 + } 1.5689 + /* falls through */ 1.5690 + case 39: // right arrow 1.5691 + // horizontal scrolling using arrow keys 1.5692 + if (PDFView.isHorizontalScrollbarEnabled) { 1.5693 + break; 1.5694 + } 1.5695 + /* falls through */ 1.5696 + case 74: // 'j' 1.5697 + case 78: // 'n' 1.5698 + PDFView.page++; 1.5699 + handled = true; 1.5700 + break; 1.5701 + 1.5702 + case 36: // home 1.5703 + if (PresentationMode.active) { 1.5704 + PDFView.page = 1; 1.5705 + handled = true; 1.5706 + } 1.5707 + break; 1.5708 + case 35: // end 1.5709 + if (PresentationMode.active) { 1.5710 + PDFView.page = PDFView.pdfDocument.numPages; 1.5711 + handled = true; 1.5712 + } 1.5713 + break; 1.5714 + 1.5715 + case 72: // 'h' 1.5716 + if (!PresentationMode.active) { 1.5717 + HandTool.toggle(); 1.5718 + } 1.5719 + break; 1.5720 + case 82: // 'r' 1.5721 + PDFView.rotatePages(90); 1.5722 + break; 1.5723 + } 1.5724 + } 1.5725 + 1.5726 + if (cmd === 4) { // shift-key 1.5727 + switch (evt.keyCode) { 1.5728 + case 32: // spacebar 1.5729 + if (!PresentationMode.active && 1.5730 + PDFView.currentScaleValue !== 'page-fit') { 1.5731 + break; 1.5732 + } 1.5733 + PDFView.page--; 1.5734 + handled = true; 1.5735 + break; 1.5736 + 1.5737 + case 82: // 'r' 1.5738 + PDFView.rotatePages(-90); 1.5739 + break; 1.5740 + } 1.5741 + } 1.5742 + 1.5743 + if (!handled && !PresentationMode.active) { 1.5744 + // 33=Page Up 34=Page Down 35=End 36=Home 1.5745 + // 37=Left 38=Up 39=Right 40=Down 1.5746 + if (evt.keyCode >= 33 && evt.keyCode <= 40 && 1.5747 + !PDFView.container.contains(curElement)) { 1.5748 + // The page container is not focused, but a page navigation key has been 1.5749 + // pressed. Change the focus to the viewer container to make sure that 1.5750 + // navigation by keyboard works as expected. 1.5751 + PDFView.container.focus(); 1.5752 + } 1.5753 + // 32=Spacebar 1.5754 + if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') { 1.5755 + // Workaround for issue in Firefox, that prevents scroll keys from 1.5756 + // working when elements with 'tabindex' are focused. (#3498) 1.5757 + PDFView.container.blur(); 1.5758 + } 1.5759 + } 1.5760 + 1.5761 + if (cmd === 2) { // alt-key 1.5762 + switch (evt.keyCode) { 1.5763 + case 37: // left arrow 1.5764 + if (PresentationMode.active) { 1.5765 + PDFHistory.back(); 1.5766 + handled = true; 1.5767 + } 1.5768 + break; 1.5769 + case 39: // right arrow 1.5770 + if (PresentationMode.active) { 1.5771 + PDFHistory.forward(); 1.5772 + handled = true; 1.5773 + } 1.5774 + break; 1.5775 + } 1.5776 + } 1.5777 + 1.5778 + if (handled) { 1.5779 + evt.preventDefault(); 1.5780 + PDFView.clearMouseScrollState(); 1.5781 + } 1.5782 +}); 1.5783 + 1.5784 +window.addEventListener('beforeprint', function beforePrint(evt) { 1.5785 + PDFView.beforePrint(); 1.5786 +}); 1.5787 + 1.5788 +window.addEventListener('afterprint', function afterPrint(evt) { 1.5789 + PDFView.afterPrint(); 1.5790 +}); 1.5791 + 1.5792 +(function animationStartedClosure() { 1.5793 + // The offsetParent is not set until the pdf.js iframe or object is visible. 1.5794 + // Waiting for first animation. 1.5795 + var requestAnimationFrame = window.requestAnimationFrame || 1.5796 + window.mozRequestAnimationFrame || 1.5797 + window.webkitRequestAnimationFrame || 1.5798 + window.oRequestAnimationFrame || 1.5799 + window.msRequestAnimationFrame || 1.5800 + function startAtOnce(callback) { callback(); }; 1.5801 + PDFView.animationStartedPromise = new Promise(function (resolve) { 1.5802 + requestAnimationFrame(function onAnimationFrame() { 1.5803 + resolve(); 1.5804 + }); 1.5805 + }); 1.5806 +})(); 1.5807 + 1.5808 +