browser/extensions/pdfjs/content/web/viewer.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
michael@0 3 /* Copyright 2012 Mozilla Foundation
michael@0 4 *
michael@0 5 * Licensed under the Apache License, Version 2.0 (the "License");
michael@0 6 * you may not use this file except in compliance with the License.
michael@0 7 * You may obtain a copy of the License at
michael@0 8 *
michael@0 9 * http://www.apache.org/licenses/LICENSE-2.0
michael@0 10 *
michael@0 11 * Unless required by applicable law or agreed to in writing, software
michael@0 12 * distributed under the License is distributed on an "AS IS" BASIS,
michael@0 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0 14 * See the License for the specific language governing permissions and
michael@0 15 * limitations under the License.
michael@0 16 */
michael@0 17 /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar, CustomStyle,
michael@0 18 PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager,
michael@0 19 getFileName, scrollIntoView, getPDFFileNameFromURL, PDFHistory,
michael@0 20 Preferences, ViewHistory, PageView, ThumbnailView, URL,
michael@0 21 noContextMenuHandler, SecondaryToolbar, PasswordPrompt,
michael@0 22 PresentationMode, HandTool, Promise, DocumentProperties */
michael@0 23
michael@0 24 'use strict';
michael@0 25
michael@0 26 var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
michael@0 27 var DEFAULT_SCALE = 'auto';
michael@0 28 var DEFAULT_SCALE_DELTA = 1.1;
michael@0 29 var UNKNOWN_SCALE = 0;
michael@0 30 var CACHE_SIZE = 20;
michael@0 31 var CSS_UNITS = 96.0 / 72.0;
michael@0 32 var SCROLLBAR_PADDING = 40;
michael@0 33 var VERTICAL_PADDING = 5;
michael@0 34 var MAX_AUTO_SCALE = 1.25;
michael@0 35 var MIN_SCALE = 0.25;
michael@0 36 var MAX_SCALE = 4.0;
michael@0 37 var VIEW_HISTORY_MEMORY = 20;
michael@0 38 var SCALE_SELECT_CONTAINER_PADDING = 8;
michael@0 39 var SCALE_SELECT_PADDING = 22;
michael@0 40 var THUMBNAIL_SCROLL_MARGIN = -19;
michael@0 41 var USE_ONLY_CSS_ZOOM = false;
michael@0 42 var CLEANUP_TIMEOUT = 30000;
michael@0 43 var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
michael@0 44 var RenderingStates = {
michael@0 45 INITIAL: 0,
michael@0 46 RUNNING: 1,
michael@0 47 PAUSED: 2,
michael@0 48 FINISHED: 3
michael@0 49 };
michael@0 50 var FindStates = {
michael@0 51 FIND_FOUND: 0,
michael@0 52 FIND_NOTFOUND: 1,
michael@0 53 FIND_WRAPPED: 2,
michael@0 54 FIND_PENDING: 3
michael@0 55 };
michael@0 56
michael@0 57 PDFJS.imageResourcesPath = './images/';
michael@0 58 PDFJS.workerSrc = '../build/pdf.worker.js';
michael@0 59 PDFJS.cMapUrl = '../web/cmaps/';
michael@0 60 PDFJS.cMapPacked = true;
michael@0 61
michael@0 62 var mozL10n = document.mozL10n || document.webL10n;
michael@0 63
michael@0 64
michael@0 65 // optimised CSS custom property getter/setter
michael@0 66 var CustomStyle = (function CustomStyleClosure() {
michael@0 67
michael@0 68 // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
michael@0 69 // animate-css-transforms-firefox-webkit.html
michael@0 70 // in some versions of IE9 it is critical that ms appear in this list
michael@0 71 // before Moz
michael@0 72 var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
michael@0 73 var _cache = {};
michael@0 74
michael@0 75 function CustomStyle() {}
michael@0 76
michael@0 77 CustomStyle.getProp = function get(propName, element) {
michael@0 78 // check cache only when no element is given
michael@0 79 if (arguments.length == 1 && typeof _cache[propName] == 'string') {
michael@0 80 return _cache[propName];
michael@0 81 }
michael@0 82
michael@0 83 element = element || document.documentElement;
michael@0 84 var style = element.style, prefixed, uPropName;
michael@0 85
michael@0 86 // test standard property first
michael@0 87 if (typeof style[propName] == 'string') {
michael@0 88 return (_cache[propName] = propName);
michael@0 89 }
michael@0 90
michael@0 91 // capitalize
michael@0 92 uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
michael@0 93
michael@0 94 // test vendor specific properties
michael@0 95 for (var i = 0, l = prefixes.length; i < l; i++) {
michael@0 96 prefixed = prefixes[i] + uPropName;
michael@0 97 if (typeof style[prefixed] == 'string') {
michael@0 98 return (_cache[propName] = prefixed);
michael@0 99 }
michael@0 100 }
michael@0 101
michael@0 102 //if all fails then set to undefined
michael@0 103 return (_cache[propName] = 'undefined');
michael@0 104 };
michael@0 105
michael@0 106 CustomStyle.setProp = function set(propName, element, str) {
michael@0 107 var prop = this.getProp(propName);
michael@0 108 if (prop != 'undefined') {
michael@0 109 element.style[prop] = str;
michael@0 110 }
michael@0 111 };
michael@0 112
michael@0 113 return CustomStyle;
michael@0 114 })();
michael@0 115
michael@0 116 function getFileName(url) {
michael@0 117 var anchor = url.indexOf('#');
michael@0 118 var query = url.indexOf('?');
michael@0 119 var end = Math.min(
michael@0 120 anchor > 0 ? anchor : url.length,
michael@0 121 query > 0 ? query : url.length);
michael@0 122 return url.substring(url.lastIndexOf('/', end) + 1, end);
michael@0 123 }
michael@0 124
michael@0 125 /**
michael@0 126 * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
michael@0 127 * @return {Object} The object with horizontal (sx) and vertical (sy)
michael@0 128 scales. The scaled property is set to false if scaling is
michael@0 129 not required, true otherwise.
michael@0 130 */
michael@0 131 function getOutputScale(ctx) {
michael@0 132 var devicePixelRatio = window.devicePixelRatio || 1;
michael@0 133 var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
michael@0 134 ctx.mozBackingStorePixelRatio ||
michael@0 135 ctx.msBackingStorePixelRatio ||
michael@0 136 ctx.oBackingStorePixelRatio ||
michael@0 137 ctx.backingStorePixelRatio || 1;
michael@0 138 var pixelRatio = devicePixelRatio / backingStoreRatio;
michael@0 139 return {
michael@0 140 sx: pixelRatio,
michael@0 141 sy: pixelRatio,
michael@0 142 scaled: pixelRatio != 1
michael@0 143 };
michael@0 144 }
michael@0 145
michael@0 146 /**
michael@0 147 * Scrolls specified element into view of its parent.
michael@0 148 * element {Object} The element to be visible.
michael@0 149 * spot {Object} An object with optional top and left properties,
michael@0 150 * specifying the offset from the top left edge.
michael@0 151 */
michael@0 152 function scrollIntoView(element, spot) {
michael@0 153 // Assuming offsetParent is available (it's not available when viewer is in
michael@0 154 // hidden iframe or object). We have to scroll: if the offsetParent is not set
michael@0 155 // producing the error. See also animationStartedClosure.
michael@0 156 var parent = element.offsetParent;
michael@0 157 var offsetY = element.offsetTop + element.clientTop;
michael@0 158 var offsetX = element.offsetLeft + element.clientLeft;
michael@0 159 if (!parent) {
michael@0 160 console.error('offsetParent is not set -- cannot scroll');
michael@0 161 return;
michael@0 162 }
michael@0 163 while (parent.clientHeight === parent.scrollHeight) {
michael@0 164 if (parent.dataset._scaleY) {
michael@0 165 offsetY /= parent.dataset._scaleY;
michael@0 166 offsetX /= parent.dataset._scaleX;
michael@0 167 }
michael@0 168 offsetY += parent.offsetTop;
michael@0 169 offsetX += parent.offsetLeft;
michael@0 170 parent = parent.offsetParent;
michael@0 171 if (!parent) {
michael@0 172 return; // no need to scroll
michael@0 173 }
michael@0 174 }
michael@0 175 if (spot) {
michael@0 176 if (spot.top !== undefined) {
michael@0 177 offsetY += spot.top;
michael@0 178 }
michael@0 179 if (spot.left !== undefined) {
michael@0 180 offsetX += spot.left;
michael@0 181 parent.scrollLeft = offsetX;
michael@0 182 }
michael@0 183 }
michael@0 184 parent.scrollTop = offsetY;
michael@0 185 }
michael@0 186
michael@0 187 /**
michael@0 188 * Event handler to suppress context menu.
michael@0 189 */
michael@0 190 function noContextMenuHandler(e) {
michael@0 191 e.preventDefault();
michael@0 192 }
michael@0 193
michael@0 194 /**
michael@0 195 * Returns the filename or guessed filename from the url (see issue 3455).
michael@0 196 * url {String} The original PDF location.
michael@0 197 * @return {String} Guessed PDF file name.
michael@0 198 */
michael@0 199 function getPDFFileNameFromURL(url) {
michael@0 200 var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
michael@0 201 // SCHEME HOST 1.PATH 2.QUERY 3.REF
michael@0 202 // Pattern to get last matching NAME.pdf
michael@0 203 var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
michael@0 204 var splitURI = reURI.exec(url);
michael@0 205 var suggestedFilename = reFilename.exec(splitURI[1]) ||
michael@0 206 reFilename.exec(splitURI[2]) ||
michael@0 207 reFilename.exec(splitURI[3]);
michael@0 208 if (suggestedFilename) {
michael@0 209 suggestedFilename = suggestedFilename[0];
michael@0 210 if (suggestedFilename.indexOf('%') != -1) {
michael@0 211 // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
michael@0 212 try {
michael@0 213 suggestedFilename =
michael@0 214 reFilename.exec(decodeURIComponent(suggestedFilename))[0];
michael@0 215 } catch(e) { // Possible (extremely rare) errors:
michael@0 216 // URIError "Malformed URI", e.g. for "%AA.pdf"
michael@0 217 // TypeError "null has no properties", e.g. for "%2F.pdf"
michael@0 218 }
michael@0 219 }
michael@0 220 }
michael@0 221 return suggestedFilename || 'document.pdf';
michael@0 222 }
michael@0 223
michael@0 224 var ProgressBar = (function ProgressBarClosure() {
michael@0 225
michael@0 226 function clamp(v, min, max) {
michael@0 227 return Math.min(Math.max(v, min), max);
michael@0 228 }
michael@0 229
michael@0 230 function ProgressBar(id, opts) {
michael@0 231
michael@0 232 // Fetch the sub-elements for later.
michael@0 233 this.div = document.querySelector(id + ' .progress');
michael@0 234
michael@0 235 // Get the loading bar element, so it can be resized to fit the viewer.
michael@0 236 this.bar = this.div.parentNode;
michael@0 237
michael@0 238 // Get options, with sensible defaults.
michael@0 239 this.height = opts.height || 100;
michael@0 240 this.width = opts.width || 100;
michael@0 241 this.units = opts.units || '%';
michael@0 242
michael@0 243 // Initialize heights.
michael@0 244 this.div.style.height = this.height + this.units;
michael@0 245 this.percent = 0;
michael@0 246 }
michael@0 247
michael@0 248 ProgressBar.prototype = {
michael@0 249
michael@0 250 updateBar: function ProgressBar_updateBar() {
michael@0 251 if (this._indeterminate) {
michael@0 252 this.div.classList.add('indeterminate');
michael@0 253 this.div.style.width = this.width + this.units;
michael@0 254 return;
michael@0 255 }
michael@0 256
michael@0 257 this.div.classList.remove('indeterminate');
michael@0 258 var progressSize = this.width * this._percent / 100;
michael@0 259 this.div.style.width = progressSize + this.units;
michael@0 260 },
michael@0 261
michael@0 262 get percent() {
michael@0 263 return this._percent;
michael@0 264 },
michael@0 265
michael@0 266 set percent(val) {
michael@0 267 this._indeterminate = isNaN(val);
michael@0 268 this._percent = clamp(val, 0, 100);
michael@0 269 this.updateBar();
michael@0 270 },
michael@0 271
michael@0 272 setWidth: function ProgressBar_setWidth(viewer) {
michael@0 273 if (viewer) {
michael@0 274 var container = viewer.parentNode;
michael@0 275 var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
michael@0 276 if (scrollbarWidth > 0) {
michael@0 277 this.bar.setAttribute('style', 'width: calc(100% - ' +
michael@0 278 scrollbarWidth + 'px);');
michael@0 279 }
michael@0 280 }
michael@0 281 },
michael@0 282
michael@0 283 hide: function ProgressBar_hide() {
michael@0 284 this.bar.classList.add('hidden');
michael@0 285 this.bar.removeAttribute('style');
michael@0 286 }
michael@0 287 };
michael@0 288
michael@0 289 return ProgressBar;
michael@0 290 })();
michael@0 291
michael@0 292 var Cache = function cacheCache(size) {
michael@0 293 var data = [];
michael@0 294 this.push = function cachePush(view) {
michael@0 295 var i = data.indexOf(view);
michael@0 296 if (i >= 0) {
michael@0 297 data.splice(i);
michael@0 298 }
michael@0 299 data.push(view);
michael@0 300 if (data.length > size) {
michael@0 301 data.shift().destroy();
michael@0 302 }
michael@0 303 };
michael@0 304 };
michael@0 305
michael@0 306
michael@0 307
michael@0 308
michael@0 309 var DEFAULT_PREFERENCES = {
michael@0 310 showPreviousViewOnLoad: true,
michael@0 311 defaultZoomValue: '',
michael@0 312 ifAvailableShowOutlineOnLoad: false,
michael@0 313 enableHandToolOnLoad: false,
michael@0 314 enableWebGL: false
michael@0 315 };
michael@0 316
michael@0 317
michael@0 318 /**
michael@0 319 * Preferences - Utility for storing persistent settings.
michael@0 320 * Used for settings that should be applied to all opened documents,
michael@0 321 * or every time the viewer is loaded.
michael@0 322 */
michael@0 323 var Preferences = {
michael@0 324 prefs: Object.create(DEFAULT_PREFERENCES),
michael@0 325 isInitializedPromiseResolved: false,
michael@0 326 initializedPromise: null,
michael@0 327
michael@0 328 /**
michael@0 329 * Initialize and fetch the current preference values from storage.
michael@0 330 * @return {Promise} A promise that is resolved when the preferences
michael@0 331 * have been initialized.
michael@0 332 */
michael@0 333 initialize: function preferencesInitialize() {
michael@0 334 return this.initializedPromise =
michael@0 335 this._readFromStorage(DEFAULT_PREFERENCES).then(function(prefObj) {
michael@0 336 this.isInitializedPromiseResolved = true;
michael@0 337 if (prefObj) {
michael@0 338 this.prefs = prefObj;
michael@0 339 }
michael@0 340 }.bind(this));
michael@0 341 },
michael@0 342
michael@0 343 /**
michael@0 344 * Stub function for writing preferences to storage.
michael@0 345 * NOTE: This should be overridden by a build-specific function defined below.
michael@0 346 * @param {Object} prefObj The preferences that should be written to storage.
michael@0 347 * @return {Promise} A promise that is resolved when the preference values
michael@0 348 * have been written.
michael@0 349 */
michael@0 350 _writeToStorage: function preferences_writeToStorage(prefObj) {
michael@0 351 return Promise.resolve();
michael@0 352 },
michael@0 353
michael@0 354 /**
michael@0 355 * Stub function for reading preferences from storage.
michael@0 356 * NOTE: This should be overridden by a build-specific function defined below.
michael@0 357 * @param {Object} prefObj The preferences that should be read from storage.
michael@0 358 * @return {Promise} A promise that is resolved with an {Object} containing
michael@0 359 * the preferences that have been read.
michael@0 360 */
michael@0 361 _readFromStorage: function preferences_readFromStorage(prefObj) {
michael@0 362 return Promise.resolve();
michael@0 363 },
michael@0 364
michael@0 365 /**
michael@0 366 * Reset the preferences to their default values and update storage.
michael@0 367 * @return {Promise} A promise that is resolved when the preference values
michael@0 368 * have been reset.
michael@0 369 */
michael@0 370 reset: function preferencesReset() {
michael@0 371 return this.initializedPromise.then(function() {
michael@0 372 this.prefs = Object.create(DEFAULT_PREFERENCES);
michael@0 373 return this._writeToStorage(DEFAULT_PREFERENCES);
michael@0 374 }.bind(this));
michael@0 375 },
michael@0 376
michael@0 377 /**
michael@0 378 * Replace the current preference values with the ones from storage.
michael@0 379 * @return {Promise} A promise that is resolved when the preference values
michael@0 380 * have been updated.
michael@0 381 */
michael@0 382 reload: function preferencesReload() {
michael@0 383 return this.initializedPromise.then(function () {
michael@0 384 this._readFromStorage(DEFAULT_PREFERENCES).then(function(prefObj) {
michael@0 385 if (prefObj) {
michael@0 386 this.prefs = prefObj;
michael@0 387 }
michael@0 388 }.bind(this));
michael@0 389 }.bind(this));
michael@0 390 },
michael@0 391
michael@0 392 /**
michael@0 393 * Set the value of a preference.
michael@0 394 * @param {string} name The name of the preference that should be changed.
michael@0 395 * @param {boolean|number|string} value The new value of the preference.
michael@0 396 * @return {Promise} A promise that is resolved when the value has been set,
michael@0 397 * provided that the preference exists and the types match.
michael@0 398 */
michael@0 399 set: function preferencesSet(name, value) {
michael@0 400 return this.initializedPromise.then(function () {
michael@0 401 if (DEFAULT_PREFERENCES[name] === undefined) {
michael@0 402 throw new Error('preferencesSet: \'' + name + '\' is undefined.');
michael@0 403 } else if (value === undefined) {
michael@0 404 throw new Error('preferencesSet: no value is specified.');
michael@0 405 }
michael@0 406 var valueType = typeof value;
michael@0 407 var defaultType = typeof DEFAULT_PREFERENCES[name];
michael@0 408
michael@0 409 if (valueType !== defaultType) {
michael@0 410 if (valueType === 'number' && defaultType === 'string') {
michael@0 411 value = value.toString();
michael@0 412 } else {
michael@0 413 throw new Error('Preferences_set: \'' + value + '\' is a \"' +
michael@0 414 valueType + '\", expected \"' + defaultType + '\".');
michael@0 415 }
michael@0 416 } else {
michael@0 417 if (valueType === 'number' && (value | 0) !== value) {
michael@0 418 throw new Error('Preferences_set: \'' + value +
michael@0 419 '\' must be an \"integer\".');
michael@0 420 }
michael@0 421 }
michael@0 422 this.prefs[name] = value;
michael@0 423 return this._writeToStorage(this.prefs);
michael@0 424 }.bind(this));
michael@0 425 },
michael@0 426
michael@0 427 /**
michael@0 428 * Get the value of a preference.
michael@0 429 * @param {string} name The name of the preference whose value is requested.
michael@0 430 * @return {Promise} A promise that is resolved with a {boolean|number|string}
michael@0 431 * containing the value of the preference.
michael@0 432 */
michael@0 433 get: function preferencesGet(name) {
michael@0 434 return this.initializedPromise.then(function () {
michael@0 435 var defaultValue = DEFAULT_PREFERENCES[name];
michael@0 436
michael@0 437 if (defaultValue === undefined) {
michael@0 438 throw new Error('preferencesGet: \'' + name + '\' is undefined.');
michael@0 439 } else {
michael@0 440 var prefValue = this.prefs[name];
michael@0 441
michael@0 442 if (prefValue !== undefined) {
michael@0 443 return prefValue;
michael@0 444 }
michael@0 445 }
michael@0 446 return defaultValue;
michael@0 447 }.bind(this));
michael@0 448 }
michael@0 449 };
michael@0 450
michael@0 451
michael@0 452
michael@0 453
michael@0 454
michael@0 455
michael@0 456
michael@0 457 var FirefoxCom = (function FirefoxComClosure() {
michael@0 458 return {
michael@0 459 /**
michael@0 460 * Creates an event that the extension is listening for and will
michael@0 461 * synchronously respond to.
michael@0 462 * NOTE: It is reccomended to use request() instead since one day we may not
michael@0 463 * be able to synchronously reply.
michael@0 464 * @param {String} action The action to trigger.
michael@0 465 * @param {String} data Optional data to send.
michael@0 466 * @return {*} The response.
michael@0 467 */
michael@0 468 requestSync: function(action, data) {
michael@0 469 var request = document.createTextNode('');
michael@0 470 document.documentElement.appendChild(request);
michael@0 471
michael@0 472 var sender = document.createEvent('CustomEvent');
michael@0 473 sender.initCustomEvent('pdf.js.message', true, false,
michael@0 474 {action: action, data: data, sync: true});
michael@0 475 request.dispatchEvent(sender);
michael@0 476 var response = sender.detail.response;
michael@0 477 document.documentElement.removeChild(request);
michael@0 478 return response;
michael@0 479 },
michael@0 480 /**
michael@0 481 * Creates an event that the extension is listening for and will
michael@0 482 * asynchronously respond by calling the callback.
michael@0 483 * @param {String} action The action to trigger.
michael@0 484 * @param {String} data Optional data to send.
michael@0 485 * @param {Function} callback Optional response callback that will be called
michael@0 486 * with one data argument.
michael@0 487 */
michael@0 488 request: function(action, data, callback) {
michael@0 489 var request = document.createTextNode('');
michael@0 490 if (callback) {
michael@0 491 document.addEventListener('pdf.js.response', function listener(event) {
michael@0 492 var node = event.target;
michael@0 493 var response = event.detail.response;
michael@0 494
michael@0 495 document.documentElement.removeChild(node);
michael@0 496
michael@0 497 document.removeEventListener('pdf.js.response', listener, false);
michael@0 498 return callback(response);
michael@0 499 }, false);
michael@0 500 }
michael@0 501 document.documentElement.appendChild(request);
michael@0 502
michael@0 503 var sender = document.createEvent('CustomEvent');
michael@0 504 sender.initCustomEvent('pdf.js.message', true, false,
michael@0 505 {action: action, data: data, sync: false,
michael@0 506 callback: callback});
michael@0 507 return request.dispatchEvent(sender);
michael@0 508 }
michael@0 509 };
michael@0 510 })();
michael@0 511
michael@0 512 var DownloadManager = (function DownloadManagerClosure() {
michael@0 513 function DownloadManager() {}
michael@0 514
michael@0 515 DownloadManager.prototype = {
michael@0 516 downloadUrl: function DownloadManager_downloadUrl(url, filename) {
michael@0 517 FirefoxCom.request('download', {
michael@0 518 originalUrl: url,
michael@0 519 filename: filename
michael@0 520 });
michael@0 521 },
michael@0 522
michael@0 523 downloadData: function DownloadManager_downloadData(data, filename,
michael@0 524 contentType) {
michael@0 525 var blobUrl = PDFJS.createObjectURL(data, contentType);
michael@0 526
michael@0 527 FirefoxCom.request('download', {
michael@0 528 blobUrl: blobUrl,
michael@0 529 originalUrl: blobUrl,
michael@0 530 filename: filename,
michael@0 531 isAttachment: true
michael@0 532 });
michael@0 533 },
michael@0 534
michael@0 535 download: function DownloadManager_download(blob, url, filename) {
michael@0 536 var blobUrl = window.URL.createObjectURL(blob);
michael@0 537
michael@0 538 FirefoxCom.request('download', {
michael@0 539 blobUrl: blobUrl,
michael@0 540 originalUrl: url,
michael@0 541 filename: filename
michael@0 542 },
michael@0 543 function response(err) {
michael@0 544 if (err && this.onerror) {
michael@0 545 this.onerror(err);
michael@0 546 }
michael@0 547 window.URL.revokeObjectURL(blobUrl);
michael@0 548 }.bind(this)
michael@0 549 );
michael@0 550 }
michael@0 551 };
michael@0 552
michael@0 553 return DownloadManager;
michael@0 554 })();
michael@0 555
michael@0 556 Preferences._writeToStorage = function (prefObj) {
michael@0 557 return new Promise(function (resolve) {
michael@0 558 FirefoxCom.request('setPreferences', prefObj, resolve);
michael@0 559 });
michael@0 560 };
michael@0 561
michael@0 562 Preferences._readFromStorage = function (prefObj) {
michael@0 563 return new Promise(function (resolve) {
michael@0 564 FirefoxCom.request('getPreferences', prefObj, function (prefStr) {
michael@0 565 var readPrefs = JSON.parse(prefStr);
michael@0 566 resolve(readPrefs);
michael@0 567 });
michael@0 568 });
michael@0 569 };
michael@0 570
michael@0 571
michael@0 572
michael@0 573 var cache = new Cache(CACHE_SIZE);
michael@0 574 var currentPageNumber = 1;
michael@0 575
michael@0 576
michael@0 577 /**
michael@0 578 * View History - This is a utility for saving various view parameters for
michael@0 579 * recently opened files.
michael@0 580 *
michael@0 581 * The way that the view parameters are stored depends on how PDF.js is built,
michael@0 582 * for 'node make <flag>' the following cases exist:
michael@0 583 * - FIREFOX or MOZCENTRAL - uses sessionStorage.
michael@0 584 * - B2G - uses asyncStorage.
michael@0 585 * - GENERIC or CHROME - uses localStorage, if it is available.
michael@0 586 */
michael@0 587 var ViewHistory = (function ViewHistoryClosure() {
michael@0 588 function ViewHistory(fingerprint) {
michael@0 589 this.fingerprint = fingerprint;
michael@0 590 var initializedPromiseResolve;
michael@0 591 this.isInitializedPromiseResolved = false;
michael@0 592 this.initializedPromise = new Promise(function (resolve) {
michael@0 593 initializedPromiseResolve = resolve;
michael@0 594 });
michael@0 595
michael@0 596 var resolvePromise = (function ViewHistoryResolvePromise(db) {
michael@0 597 this.isInitializedPromiseResolved = true;
michael@0 598 this.initialize(db || '{}');
michael@0 599 initializedPromiseResolve();
michael@0 600 }).bind(this);
michael@0 601
michael@0 602
michael@0 603 var sessionHistory;
michael@0 604 try {
michael@0 605 // Workaround for security error when the preference
michael@0 606 // network.cookie.lifetimePolicy is set to 1, see Mozilla Bug 365772.
michael@0 607 sessionHistory = sessionStorage.getItem('pdfjsHistory');
michael@0 608 } catch (ex) {}
michael@0 609 resolvePromise(sessionHistory);
michael@0 610
michael@0 611 }
michael@0 612
michael@0 613 ViewHistory.prototype = {
michael@0 614 initialize: function ViewHistory_initialize(database) {
michael@0 615 database = JSON.parse(database);
michael@0 616 if (!('files' in database)) {
michael@0 617 database.files = [];
michael@0 618 }
michael@0 619 if (database.files.length >= VIEW_HISTORY_MEMORY) {
michael@0 620 database.files.shift();
michael@0 621 }
michael@0 622 var index;
michael@0 623 for (var i = 0, length = database.files.length; i < length; i++) {
michael@0 624 var branch = database.files[i];
michael@0 625 if (branch.fingerprint === this.fingerprint) {
michael@0 626 index = i;
michael@0 627 break;
michael@0 628 }
michael@0 629 }
michael@0 630 if (typeof index !== 'number') {
michael@0 631 index = database.files.push({fingerprint: this.fingerprint}) - 1;
michael@0 632 }
michael@0 633 this.file = database.files[index];
michael@0 634 this.database = database;
michael@0 635 },
michael@0 636
michael@0 637 set: function ViewHistory_set(name, val) {
michael@0 638 if (!this.isInitializedPromiseResolved) {
michael@0 639 return;
michael@0 640 }
michael@0 641 var file = this.file;
michael@0 642 file[name] = val;
michael@0 643 var database = JSON.stringify(this.database);
michael@0 644
michael@0 645
michael@0 646 try {
michael@0 647 // See comment in try-catch block above.
michael@0 648 sessionStorage.setItem('pdfjsHistory', database);
michael@0 649 } catch (ex) {}
michael@0 650
michael@0 651 },
michael@0 652
michael@0 653 get: function ViewHistory_get(name, defaultValue) {
michael@0 654 if (!this.isInitializedPromiseResolved) {
michael@0 655 return defaultValue;
michael@0 656 }
michael@0 657 return this.file[name] || defaultValue;
michael@0 658 }
michael@0 659 };
michael@0 660
michael@0 661 return ViewHistory;
michael@0 662 })();
michael@0 663
michael@0 664
michael@0 665 /**
michael@0 666 * Creates a "search bar" given set of DOM elements
michael@0 667 * that act as controls for searching, or for setting
michael@0 668 * search preferences in the UI. This object also sets
michael@0 669 * up the appropriate events for the controls. Actual
michael@0 670 * searching is done by PDFFindController
michael@0 671 */
michael@0 672 var PDFFindBar = {
michael@0 673 opened: false,
michael@0 674 bar: null,
michael@0 675 toggleButton: null,
michael@0 676 findField: null,
michael@0 677 highlightAll: null,
michael@0 678 caseSensitive: null,
michael@0 679 findMsg: null,
michael@0 680 findStatusIcon: null,
michael@0 681 findPreviousButton: null,
michael@0 682 findNextButton: null,
michael@0 683
michael@0 684 initialize: function(options) {
michael@0 685 if(typeof PDFFindController === 'undefined' || PDFFindController === null) {
michael@0 686 throw 'PDFFindBar cannot be initialized ' +
michael@0 687 'without a PDFFindController instance.';
michael@0 688 }
michael@0 689
michael@0 690 this.bar = options.bar;
michael@0 691 this.toggleButton = options.toggleButton;
michael@0 692 this.findField = options.findField;
michael@0 693 this.highlightAll = options.highlightAllCheckbox;
michael@0 694 this.caseSensitive = options.caseSensitiveCheckbox;
michael@0 695 this.findMsg = options.findMsg;
michael@0 696 this.findStatusIcon = options.findStatusIcon;
michael@0 697 this.findPreviousButton = options.findPreviousButton;
michael@0 698 this.findNextButton = options.findNextButton;
michael@0 699
michael@0 700 var self = this;
michael@0 701 this.toggleButton.addEventListener('click', function() {
michael@0 702 self.toggle();
michael@0 703 });
michael@0 704
michael@0 705 this.findField.addEventListener('input', function() {
michael@0 706 self.dispatchEvent('');
michael@0 707 });
michael@0 708
michael@0 709 this.bar.addEventListener('keydown', function(evt) {
michael@0 710 switch (evt.keyCode) {
michael@0 711 case 13: // Enter
michael@0 712 if (evt.target === self.findField) {
michael@0 713 self.dispatchEvent('again', evt.shiftKey);
michael@0 714 }
michael@0 715 break;
michael@0 716 case 27: // Escape
michael@0 717 self.close();
michael@0 718 break;
michael@0 719 }
michael@0 720 });
michael@0 721
michael@0 722 this.findPreviousButton.addEventListener('click',
michael@0 723 function() { self.dispatchEvent('again', true); }
michael@0 724 );
michael@0 725
michael@0 726 this.findNextButton.addEventListener('click', function() {
michael@0 727 self.dispatchEvent('again', false);
michael@0 728 });
michael@0 729
michael@0 730 this.highlightAll.addEventListener('click', function() {
michael@0 731 self.dispatchEvent('highlightallchange');
michael@0 732 });
michael@0 733
michael@0 734 this.caseSensitive.addEventListener('click', function() {
michael@0 735 self.dispatchEvent('casesensitivitychange');
michael@0 736 });
michael@0 737 },
michael@0 738
michael@0 739 dispatchEvent: function(aType, aFindPrevious) {
michael@0 740 var event = document.createEvent('CustomEvent');
michael@0 741 event.initCustomEvent('find' + aType, true, true, {
michael@0 742 query: this.findField.value,
michael@0 743 caseSensitive: this.caseSensitive.checked,
michael@0 744 highlightAll: this.highlightAll.checked,
michael@0 745 findPrevious: aFindPrevious
michael@0 746 });
michael@0 747 return window.dispatchEvent(event);
michael@0 748 },
michael@0 749
michael@0 750 updateUIState: function(state, previous) {
michael@0 751 var notFound = false;
michael@0 752 var findMsg = '';
michael@0 753 var status = '';
michael@0 754
michael@0 755 switch (state) {
michael@0 756 case FindStates.FIND_FOUND:
michael@0 757 break;
michael@0 758
michael@0 759 case FindStates.FIND_PENDING:
michael@0 760 status = 'pending';
michael@0 761 break;
michael@0 762
michael@0 763 case FindStates.FIND_NOTFOUND:
michael@0 764 findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
michael@0 765 notFound = true;
michael@0 766 break;
michael@0 767
michael@0 768 case FindStates.FIND_WRAPPED:
michael@0 769 if (previous) {
michael@0 770 findMsg = mozL10n.get('find_reached_top', null,
michael@0 771 'Reached top of document, continued from bottom');
michael@0 772 } else {
michael@0 773 findMsg = mozL10n.get('find_reached_bottom', null,
michael@0 774 'Reached end of document, continued from top');
michael@0 775 }
michael@0 776 break;
michael@0 777 }
michael@0 778
michael@0 779 if (notFound) {
michael@0 780 this.findField.classList.add('notFound');
michael@0 781 } else {
michael@0 782 this.findField.classList.remove('notFound');
michael@0 783 }
michael@0 784
michael@0 785 this.findField.setAttribute('data-status', status);
michael@0 786 this.findMsg.textContent = findMsg;
michael@0 787 },
michael@0 788
michael@0 789 open: function() {
michael@0 790 if (!this.opened) {
michael@0 791 this.opened = true;
michael@0 792 this.toggleButton.classList.add('toggled');
michael@0 793 this.bar.classList.remove('hidden');
michael@0 794 }
michael@0 795
michael@0 796 this.findField.select();
michael@0 797 this.findField.focus();
michael@0 798 },
michael@0 799
michael@0 800 close: function() {
michael@0 801 if (!this.opened) {
michael@0 802 return;
michael@0 803 }
michael@0 804 this.opened = false;
michael@0 805 this.toggleButton.classList.remove('toggled');
michael@0 806 this.bar.classList.add('hidden');
michael@0 807
michael@0 808 PDFFindController.active = false;
michael@0 809 },
michael@0 810
michael@0 811 toggle: function() {
michael@0 812 if (this.opened) {
michael@0 813 this.close();
michael@0 814 } else {
michael@0 815 this.open();
michael@0 816 }
michael@0 817 }
michael@0 818 };
michael@0 819
michael@0 820
michael@0 821
michael@0 822 /**
michael@0 823 * Provides a "search" or "find" functionality for the PDF.
michael@0 824 * This object actually performs the search for a given string.
michael@0 825 */
michael@0 826
michael@0 827 var PDFFindController = {
michael@0 828 startedTextExtraction: false,
michael@0 829
michael@0 830 extractTextPromises: [],
michael@0 831
michael@0 832 pendingFindMatches: {},
michael@0 833
michael@0 834 // If active, find results will be highlighted.
michael@0 835 active: false,
michael@0 836
michael@0 837 // Stores the text for each page.
michael@0 838 pageContents: [],
michael@0 839
michael@0 840 pageMatches: [],
michael@0 841
michael@0 842 // Currently selected match.
michael@0 843 selected: {
michael@0 844 pageIdx: -1,
michael@0 845 matchIdx: -1
michael@0 846 },
michael@0 847
michael@0 848 // Where find algorithm currently is in the document.
michael@0 849 offset: {
michael@0 850 pageIdx: null,
michael@0 851 matchIdx: null
michael@0 852 },
michael@0 853
michael@0 854 resumePageIdx: null,
michael@0 855
michael@0 856 state: null,
michael@0 857
michael@0 858 dirtyMatch: false,
michael@0 859
michael@0 860 findTimeout: null,
michael@0 861
michael@0 862 pdfPageSource: null,
michael@0 863
michael@0 864 integratedFind: false,
michael@0 865
michael@0 866 initialize: function(options) {
michael@0 867 if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) {
michael@0 868 throw 'PDFFindController cannot be initialized ' +
michael@0 869 'without a PDFFindBar instance';
michael@0 870 }
michael@0 871
michael@0 872 this.pdfPageSource = options.pdfPageSource;
michael@0 873 this.integratedFind = options.integratedFind;
michael@0 874
michael@0 875 var events = [
michael@0 876 'find',
michael@0 877 'findagain',
michael@0 878 'findhighlightallchange',
michael@0 879 'findcasesensitivitychange'
michael@0 880 ];
michael@0 881
michael@0 882 this.firstPagePromise = new Promise(function (resolve) {
michael@0 883 this.resolveFirstPage = resolve;
michael@0 884 }.bind(this));
michael@0 885 this.handleEvent = this.handleEvent.bind(this);
michael@0 886
michael@0 887 for (var i = 0; i < events.length; i++) {
michael@0 888 window.addEventListener(events[i], this.handleEvent);
michael@0 889 }
michael@0 890 },
michael@0 891
michael@0 892 reset: function pdfFindControllerReset() {
michael@0 893 this.startedTextExtraction = false;
michael@0 894 this.extractTextPromises = [];
michael@0 895 this.active = false;
michael@0 896 },
michael@0 897
michael@0 898 calcFindMatch: function(pageIndex) {
michael@0 899 var pageContent = this.pageContents[pageIndex];
michael@0 900 var query = this.state.query;
michael@0 901 var caseSensitive = this.state.caseSensitive;
michael@0 902 var queryLen = query.length;
michael@0 903
michael@0 904 if (queryLen === 0) {
michael@0 905 // Do nothing the matches should be wiped out already.
michael@0 906 return;
michael@0 907 }
michael@0 908
michael@0 909 if (!caseSensitive) {
michael@0 910 pageContent = pageContent.toLowerCase();
michael@0 911 query = query.toLowerCase();
michael@0 912 }
michael@0 913
michael@0 914 var matches = [];
michael@0 915
michael@0 916 var matchIdx = -queryLen;
michael@0 917 while (true) {
michael@0 918 matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
michael@0 919 if (matchIdx === -1) {
michael@0 920 break;
michael@0 921 }
michael@0 922
michael@0 923 matches.push(matchIdx);
michael@0 924 }
michael@0 925 this.pageMatches[pageIndex] = matches;
michael@0 926 this.updatePage(pageIndex);
michael@0 927 if (this.resumePageIdx === pageIndex) {
michael@0 928 this.resumePageIdx = null;
michael@0 929 this.nextPageMatch();
michael@0 930 }
michael@0 931 },
michael@0 932
michael@0 933 extractText: function() {
michael@0 934 if (this.startedTextExtraction) {
michael@0 935 return;
michael@0 936 }
michael@0 937 this.startedTextExtraction = true;
michael@0 938
michael@0 939 this.pageContents = [];
michael@0 940 var extractTextPromisesResolves = [];
michael@0 941 for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) {
michael@0 942 this.extractTextPromises.push(new Promise(function (resolve) {
michael@0 943 extractTextPromisesResolves.push(resolve);
michael@0 944 }));
michael@0 945 }
michael@0 946
michael@0 947 var self = this;
michael@0 948 function extractPageText(pageIndex) {
michael@0 949 self.pdfPageSource.pages[pageIndex].getTextContent().then(
michael@0 950 function textContentResolved(textContent) {
michael@0 951 var textItems = textContent.items;
michael@0 952 var str = '';
michael@0 953
michael@0 954 for (var i = 0; i < textItems.length; i++) {
michael@0 955 str += textItems[i].str;
michael@0 956 }
michael@0 957
michael@0 958 // Store the pageContent as a string.
michael@0 959 self.pageContents.push(str);
michael@0 960
michael@0 961 extractTextPromisesResolves[pageIndex](pageIndex);
michael@0 962 if ((pageIndex + 1) < self.pdfPageSource.pages.length) {
michael@0 963 extractPageText(pageIndex + 1);
michael@0 964 }
michael@0 965 }
michael@0 966 );
michael@0 967 }
michael@0 968 extractPageText(0);
michael@0 969 },
michael@0 970
michael@0 971 handleEvent: function(e) {
michael@0 972 if (this.state === null || e.type !== 'findagain') {
michael@0 973 this.dirtyMatch = true;
michael@0 974 }
michael@0 975 this.state = e.detail;
michael@0 976 this.updateUIState(FindStates.FIND_PENDING);
michael@0 977
michael@0 978 this.firstPagePromise.then(function() {
michael@0 979 this.extractText();
michael@0 980
michael@0 981 clearTimeout(this.findTimeout);
michael@0 982 if (e.type === 'find') {
michael@0 983 // Only trigger the find action after 250ms of silence.
michael@0 984 this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
michael@0 985 } else {
michael@0 986 this.nextMatch();
michael@0 987 }
michael@0 988 }.bind(this));
michael@0 989 },
michael@0 990
michael@0 991 updatePage: function(idx) {
michael@0 992 var page = this.pdfPageSource.pages[idx];
michael@0 993
michael@0 994 if (this.selected.pageIdx === idx) {
michael@0 995 // If the page is selected, scroll the page into view, which triggers
michael@0 996 // rendering the page, which adds the textLayer. Once the textLayer is
michael@0 997 // build, it will scroll onto the selected match.
michael@0 998 page.scrollIntoView();
michael@0 999 }
michael@0 1000
michael@0 1001 if (page.textLayer) {
michael@0 1002 page.textLayer.updateMatches();
michael@0 1003 }
michael@0 1004 },
michael@0 1005
michael@0 1006 nextMatch: function() {
michael@0 1007 var previous = this.state.findPrevious;
michael@0 1008 var currentPageIndex = this.pdfPageSource.page - 1;
michael@0 1009 var numPages = this.pdfPageSource.pages.length;
michael@0 1010
michael@0 1011 this.active = true;
michael@0 1012
michael@0 1013 if (this.dirtyMatch) {
michael@0 1014 // Need to recalculate the matches, reset everything.
michael@0 1015 this.dirtyMatch = false;
michael@0 1016 this.selected.pageIdx = this.selected.matchIdx = -1;
michael@0 1017 this.offset.pageIdx = currentPageIndex;
michael@0 1018 this.offset.matchIdx = null;
michael@0 1019 this.hadMatch = false;
michael@0 1020 this.resumePageIdx = null;
michael@0 1021 this.pageMatches = [];
michael@0 1022 var self = this;
michael@0 1023
michael@0 1024 for (var i = 0; i < numPages; i++) {
michael@0 1025 // Wipe out any previous highlighted matches.
michael@0 1026 this.updatePage(i);
michael@0 1027
michael@0 1028 // As soon as the text is extracted start finding the matches.
michael@0 1029 if (!(i in this.pendingFindMatches)) {
michael@0 1030 this.pendingFindMatches[i] = true;
michael@0 1031 this.extractTextPromises[i].then(function(pageIdx) {
michael@0 1032 delete self.pendingFindMatches[pageIdx];
michael@0 1033 self.calcFindMatch(pageIdx);
michael@0 1034 });
michael@0 1035 }
michael@0 1036 }
michael@0 1037 }
michael@0 1038
michael@0 1039 // If there's no query there's no point in searching.
michael@0 1040 if (this.state.query === '') {
michael@0 1041 this.updateUIState(FindStates.FIND_FOUND);
michael@0 1042 return;
michael@0 1043 }
michael@0 1044
michael@0 1045 // If we're waiting on a page, we return since we can't do anything else.
michael@0 1046 if (this.resumePageIdx) {
michael@0 1047 return;
michael@0 1048 }
michael@0 1049
michael@0 1050 var offset = this.offset;
michael@0 1051 // If there's already a matchIdx that means we are iterating through a
michael@0 1052 // page's matches.
michael@0 1053 if (offset.matchIdx !== null) {
michael@0 1054 var numPageMatches = this.pageMatches[offset.pageIdx].length;
michael@0 1055 if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
michael@0 1056 (previous && offset.matchIdx > 0)) {
michael@0 1057 // The simple case, we just have advance the matchIdx to select the next
michael@0 1058 // match on the page.
michael@0 1059 this.hadMatch = true;
michael@0 1060 offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
michael@0 1061 this.updateMatch(true);
michael@0 1062 return;
michael@0 1063 }
michael@0 1064 // We went beyond the current page's matches, so we advance to the next
michael@0 1065 // page.
michael@0 1066 this.advanceOffsetPage(previous);
michael@0 1067 }
michael@0 1068 // Start searching through the page.
michael@0 1069 this.nextPageMatch();
michael@0 1070 },
michael@0 1071
michael@0 1072 matchesReady: function(matches) {
michael@0 1073 var offset = this.offset;
michael@0 1074 var numMatches = matches.length;
michael@0 1075 var previous = this.state.findPrevious;
michael@0 1076 if (numMatches) {
michael@0 1077 // There were matches for the page, so initialize the matchIdx.
michael@0 1078 this.hadMatch = true;
michael@0 1079 offset.matchIdx = previous ? numMatches - 1 : 0;
michael@0 1080 this.updateMatch(true);
michael@0 1081 // matches were found
michael@0 1082 return true;
michael@0 1083 } else {
michael@0 1084 // No matches attempt to search the next page.
michael@0 1085 this.advanceOffsetPage(previous);
michael@0 1086 if (offset.wrapped) {
michael@0 1087 offset.matchIdx = null;
michael@0 1088 if (!this.hadMatch) {
michael@0 1089 // No point in wrapping there were no matches.
michael@0 1090 this.updateMatch(false);
michael@0 1091 // while matches were not found, searching for a page
michael@0 1092 // with matches should nevertheless halt.
michael@0 1093 return true;
michael@0 1094 }
michael@0 1095 }
michael@0 1096 // matches were not found (and searching is not done)
michael@0 1097 return false;
michael@0 1098 }
michael@0 1099 },
michael@0 1100
michael@0 1101 nextPageMatch: function() {
michael@0 1102 if (this.resumePageIdx !== null) {
michael@0 1103 console.error('There can only be one pending page.');
michael@0 1104 }
michael@0 1105 do {
michael@0 1106 var pageIdx = this.offset.pageIdx;
michael@0 1107 var matches = this.pageMatches[pageIdx];
michael@0 1108 if (!matches) {
michael@0 1109 // The matches don't exist yet for processing by "matchesReady",
michael@0 1110 // so set a resume point for when they do exist.
michael@0 1111 this.resumePageIdx = pageIdx;
michael@0 1112 break;
michael@0 1113 }
michael@0 1114 } while (!this.matchesReady(matches));
michael@0 1115 },
michael@0 1116
michael@0 1117 advanceOffsetPage: function(previous) {
michael@0 1118 var offset = this.offset;
michael@0 1119 var numPages = this.extractTextPromises.length;
michael@0 1120 offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
michael@0 1121 offset.matchIdx = null;
michael@0 1122 if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
michael@0 1123 offset.pageIdx = previous ? numPages - 1 : 0;
michael@0 1124 offset.wrapped = true;
michael@0 1125 return;
michael@0 1126 }
michael@0 1127 },
michael@0 1128
michael@0 1129 updateMatch: function(found) {
michael@0 1130 var state = FindStates.FIND_NOTFOUND;
michael@0 1131 var wrapped = this.offset.wrapped;
michael@0 1132 this.offset.wrapped = false;
michael@0 1133 if (found) {
michael@0 1134 var previousPage = this.selected.pageIdx;
michael@0 1135 this.selected.pageIdx = this.offset.pageIdx;
michael@0 1136 this.selected.matchIdx = this.offset.matchIdx;
michael@0 1137 state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND;
michael@0 1138 // Update the currently selected page to wipe out any selected matches.
michael@0 1139 if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
michael@0 1140 this.updatePage(previousPage);
michael@0 1141 }
michael@0 1142 }
michael@0 1143 this.updateUIState(state, this.state.findPrevious);
michael@0 1144 if (this.selected.pageIdx !== -1) {
michael@0 1145 this.updatePage(this.selected.pageIdx, true);
michael@0 1146 }
michael@0 1147 },
michael@0 1148
michael@0 1149 updateUIState: function(state, previous) {
michael@0 1150 if (this.integratedFind) {
michael@0 1151 FirefoxCom.request('updateFindControlState',
michael@0 1152 {result: state, findPrevious: previous});
michael@0 1153 return;
michael@0 1154 }
michael@0 1155 PDFFindBar.updateUIState(state, previous);
michael@0 1156 }
michael@0 1157 };
michael@0 1158
michael@0 1159
michael@0 1160
michael@0 1161 var PDFHistory = {
michael@0 1162 initialized: false,
michael@0 1163 initialDestination: null,
michael@0 1164
michael@0 1165 initialize: function pdfHistoryInitialize(fingerprint) {
michael@0 1166 if (PDFJS.disableHistory || PDFView.isViewerEmbedded) {
michael@0 1167 // The browsing history is only enabled when the viewer is standalone,
michael@0 1168 // i.e. not when it is embedded in a web page.
michael@0 1169 return;
michael@0 1170 }
michael@0 1171 this.initialized = true;
michael@0 1172 this.reInitialized = false;
michael@0 1173 this.allowHashChange = true;
michael@0 1174 this.historyUnlocked = true;
michael@0 1175
michael@0 1176 this.previousHash = window.location.hash.substring(1);
michael@0 1177 this.currentBookmark = '';
michael@0 1178 this.currentPage = 0;
michael@0 1179 this.updatePreviousBookmark = false;
michael@0 1180 this.previousBookmark = '';
michael@0 1181 this.previousPage = 0;
michael@0 1182 this.nextHashParam = '';
michael@0 1183
michael@0 1184 this.fingerprint = fingerprint;
michael@0 1185 this.currentUid = this.uid = 0;
michael@0 1186 this.current = {};
michael@0 1187
michael@0 1188 var state = window.history.state;
michael@0 1189 if (this._isStateObjectDefined(state)) {
michael@0 1190 // This corresponds to navigating back to the document
michael@0 1191 // from another page in the browser history.
michael@0 1192 if (state.target.dest) {
michael@0 1193 this.initialDestination = state.target.dest;
michael@0 1194 } else {
michael@0 1195 PDFView.initialBookmark = state.target.hash;
michael@0 1196 }
michael@0 1197 this.currentUid = state.uid;
michael@0 1198 this.uid = state.uid + 1;
michael@0 1199 this.current = state.target;
michael@0 1200 } else {
michael@0 1201 // This corresponds to the loading of a new document.
michael@0 1202 if (state && state.fingerprint &&
michael@0 1203 this.fingerprint !== state.fingerprint) {
michael@0 1204 // Reinitialize the browsing history when a new document
michael@0 1205 // is opened in the web viewer.
michael@0 1206 this.reInitialized = true;
michael@0 1207 }
michael@0 1208 this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
michael@0 1209 }
michael@0 1210
michael@0 1211 var self = this;
michael@0 1212 window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
michael@0 1213 evt.preventDefault();
michael@0 1214 evt.stopPropagation();
michael@0 1215
michael@0 1216 if (!self.historyUnlocked) {
michael@0 1217 return;
michael@0 1218 }
michael@0 1219 if (evt.state) {
michael@0 1220 // Move back/forward in the history.
michael@0 1221 self._goTo(evt.state);
michael@0 1222 } else {
michael@0 1223 // Handle the user modifying the hash of a loaded document.
michael@0 1224 self.previousHash = window.location.hash.substring(1);
michael@0 1225
michael@0 1226 // If the history is empty when the hash changes,
michael@0 1227 // update the previous entry in the browser history.
michael@0 1228 if (self.uid === 0) {
michael@0 1229 var previousParams = (self.previousHash && self.currentBookmark &&
michael@0 1230 self.previousHash !== self.currentBookmark) ?
michael@0 1231 { hash: self.currentBookmark, page: self.currentPage } :
michael@0 1232 { page: 1 };
michael@0 1233 self.historyUnlocked = false;
michael@0 1234 self.allowHashChange = false;
michael@0 1235 window.history.back();
michael@0 1236 self._pushToHistory(previousParams, false, true);
michael@0 1237 window.history.forward();
michael@0 1238 self.historyUnlocked = true;
michael@0 1239 }
michael@0 1240 self._pushToHistory({ hash: self.previousHash }, false, true);
michael@0 1241 self._updatePreviousBookmark();
michael@0 1242 }
michael@0 1243 }, false);
michael@0 1244
michael@0 1245 function pdfHistoryBeforeUnload() {
michael@0 1246 var previousParams = self._getPreviousParams(null, true);
michael@0 1247 if (previousParams) {
michael@0 1248 var replacePrevious = (!self.current.dest &&
michael@0 1249 self.current.hash !== self.previousHash);
michael@0 1250 self._pushToHistory(previousParams, false, replacePrevious);
michael@0 1251 self._updatePreviousBookmark();
michael@0 1252 }
michael@0 1253 // Remove the event listener when navigating away from the document,
michael@0 1254 // since 'beforeunload' prevents Firefox from caching the document.
michael@0 1255 window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false);
michael@0 1256 }
michael@0 1257 window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
michael@0 1258
michael@0 1259 window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
michael@0 1260 // If the entire viewer (including the PDF file) is cached in the browser,
michael@0 1261 // we need to reattach the 'beforeunload' event listener since
michael@0 1262 // the 'DOMContentLoaded' event is not fired on 'pageshow'.
michael@0 1263 window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
michael@0 1264 }, false);
michael@0 1265 },
michael@0 1266
michael@0 1267 _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
michael@0 1268 return (state && state.uid >= 0 &&
michael@0 1269 state.fingerprint && this.fingerprint === state.fingerprint &&
michael@0 1270 state.target && state.target.hash) ? true : false;
michael@0 1271 },
michael@0 1272
michael@0 1273 _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
michael@0 1274 replace) {
michael@0 1275 if (replace) {
michael@0 1276 window.history.replaceState(stateObj, '');
michael@0 1277 } else {
michael@0 1278 window.history.pushState(stateObj, '');
michael@0 1279 }
michael@0 1280 },
michael@0 1281
michael@0 1282 get isHashChangeUnlocked() {
michael@0 1283 if (!this.initialized) {
michael@0 1284 return true;
michael@0 1285 }
michael@0 1286 // If the current hash changes when moving back/forward in the history,
michael@0 1287 // this will trigger a 'popstate' event *as well* as a 'hashchange' event.
michael@0 1288 // Since the hash generally won't correspond to the exact the position
michael@0 1289 // stored in the history's state object, triggering the 'hashchange' event
michael@0 1290 // can thus corrupt the browser history.
michael@0 1291 //
michael@0 1292 // When the hash changes during a 'popstate' event, we *only* prevent the
michael@0 1293 // first 'hashchange' event and immediately reset allowHashChange.
michael@0 1294 // If it is not reset, the user would not be able to change the hash.
michael@0 1295
michael@0 1296 var temp = this.allowHashChange;
michael@0 1297 this.allowHashChange = true;
michael@0 1298 return temp;
michael@0 1299 },
michael@0 1300
michael@0 1301 _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
michael@0 1302 if (this.updatePreviousBookmark &&
michael@0 1303 this.currentBookmark && this.currentPage) {
michael@0 1304 this.previousBookmark = this.currentBookmark;
michael@0 1305 this.previousPage = this.currentPage;
michael@0 1306 this.updatePreviousBookmark = false;
michael@0 1307 }
michael@0 1308 },
michael@0 1309
michael@0 1310 updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
michael@0 1311 pageNum) {
michael@0 1312 if (this.initialized) {
michael@0 1313 this.currentBookmark = bookmark.substring(1);
michael@0 1314 this.currentPage = pageNum | 0;
michael@0 1315 this._updatePreviousBookmark();
michael@0 1316 }
michael@0 1317 },
michael@0 1318
michael@0 1319 updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
michael@0 1320 if (this.initialized) {
michael@0 1321 this.nextHashParam = param;
michael@0 1322 }
michael@0 1323 },
michael@0 1324
michael@0 1325 push: function pdfHistoryPush(params, isInitialBookmark) {
michael@0 1326 if (!(this.initialized && this.historyUnlocked)) {
michael@0 1327 return;
michael@0 1328 }
michael@0 1329 if (params.dest && !params.hash) {
michael@0 1330 params.hash = (this.current.hash && this.current.dest &&
michael@0 1331 this.current.dest === params.dest) ?
michael@0 1332 this.current.hash :
michael@0 1333 PDFView.getDestinationHash(params.dest).split('#')[1];
michael@0 1334 }
michael@0 1335 if (params.page) {
michael@0 1336 params.page |= 0;
michael@0 1337 }
michael@0 1338 if (isInitialBookmark) {
michael@0 1339 var target = window.history.state.target;
michael@0 1340 if (!target) {
michael@0 1341 // Invoked when the user specifies an initial bookmark,
michael@0 1342 // thus setting PDFView.initialBookmark, when the document is loaded.
michael@0 1343 this._pushToHistory(params, false);
michael@0 1344 this.previousHash = window.location.hash.substring(1);
michael@0 1345 }
michael@0 1346 this.updatePreviousBookmark = this.nextHashParam ? false : true;
michael@0 1347 if (target) {
michael@0 1348 // If the current document is reloaded,
michael@0 1349 // avoid creating duplicate entries in the history.
michael@0 1350 this._updatePreviousBookmark();
michael@0 1351 }
michael@0 1352 return;
michael@0 1353 }
michael@0 1354 if (this.nextHashParam) {
michael@0 1355 if (this.nextHashParam === params.hash) {
michael@0 1356 this.nextHashParam = null;
michael@0 1357 this.updatePreviousBookmark = true;
michael@0 1358 return;
michael@0 1359 } else {
michael@0 1360 this.nextHashParam = null;
michael@0 1361 }
michael@0 1362 }
michael@0 1363
michael@0 1364 if (params.hash) {
michael@0 1365 if (this.current.hash) {
michael@0 1366 if (this.current.hash !== params.hash) {
michael@0 1367 this._pushToHistory(params, true);
michael@0 1368 } else {
michael@0 1369 if (!this.current.page && params.page) {
michael@0 1370 this._pushToHistory(params, false, true);
michael@0 1371 }
michael@0 1372 this.updatePreviousBookmark = true;
michael@0 1373 }
michael@0 1374 } else {
michael@0 1375 this._pushToHistory(params, true);
michael@0 1376 }
michael@0 1377 } else if (this.current.page && params.page &&
michael@0 1378 this.current.page !== params.page) {
michael@0 1379 this._pushToHistory(params, true);
michael@0 1380 }
michael@0 1381 },
michael@0 1382
michael@0 1383 _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
michael@0 1384 beforeUnload) {
michael@0 1385 if (!(this.currentBookmark && this.currentPage)) {
michael@0 1386 return null;
michael@0 1387 } else if (this.updatePreviousBookmark) {
michael@0 1388 this.updatePreviousBookmark = false;
michael@0 1389 }
michael@0 1390 if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
michael@0 1391 // Prevent the history from getting stuck in the current state,
michael@0 1392 // effectively preventing the user from going back/forward in the history.
michael@0 1393 //
michael@0 1394 // This happens if the current position in the document didn't change when
michael@0 1395 // the history was previously updated. The reasons for this are either:
michael@0 1396 // 1. The current zoom value is such that the document does not need to,
michael@0 1397 // or cannot, be scrolled to display the destination.
michael@0 1398 // 2. The previous destination is broken, and doesn't actally point to a
michael@0 1399 // position within the document.
michael@0 1400 // (This is either due to a bad PDF generator, or the user making a
michael@0 1401 // mistake when entering a destination in the hash parameters.)
michael@0 1402 return null;
michael@0 1403 }
michael@0 1404 if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
michael@0 1405 if (this.previousBookmark === this.currentBookmark) {
michael@0 1406 return null;
michael@0 1407 }
michael@0 1408 } else if (this.current.page || onlyCheckPage) {
michael@0 1409 if (this.previousPage === this.currentPage) {
michael@0 1410 return null;
michael@0 1411 }
michael@0 1412 } else {
michael@0 1413 return null;
michael@0 1414 }
michael@0 1415 var params = { hash: this.currentBookmark, page: this.currentPage };
michael@0 1416 if (PresentationMode.active) {
michael@0 1417 params.hash = null;
michael@0 1418 }
michael@0 1419 return params;
michael@0 1420 },
michael@0 1421
michael@0 1422 _stateObj: function pdfHistory_stateObj(params) {
michael@0 1423 return { fingerprint: this.fingerprint, uid: this.uid, target: params };
michael@0 1424 },
michael@0 1425
michael@0 1426 _pushToHistory: function pdfHistory_pushToHistory(params,
michael@0 1427 addPrevious, overwrite) {
michael@0 1428 if (!this.initialized) {
michael@0 1429 return;
michael@0 1430 }
michael@0 1431 if (!params.hash && params.page) {
michael@0 1432 params.hash = ('page=' + params.page);
michael@0 1433 }
michael@0 1434 if (addPrevious && !overwrite) {
michael@0 1435 var previousParams = this._getPreviousParams();
michael@0 1436 if (previousParams) {
michael@0 1437 var replacePrevious = (!this.current.dest &&
michael@0 1438 this.current.hash !== this.previousHash);
michael@0 1439 this._pushToHistory(previousParams, false, replacePrevious);
michael@0 1440 }
michael@0 1441 }
michael@0 1442 this._pushOrReplaceState(this._stateObj(params),
michael@0 1443 (overwrite || this.uid === 0));
michael@0 1444 this.currentUid = this.uid++;
michael@0 1445 this.current = params;
michael@0 1446 this.updatePreviousBookmark = true;
michael@0 1447 },
michael@0 1448
michael@0 1449 _goTo: function pdfHistory_goTo(state) {
michael@0 1450 if (!(this.initialized && this.historyUnlocked &&
michael@0 1451 this._isStateObjectDefined(state))) {
michael@0 1452 return;
michael@0 1453 }
michael@0 1454 if (!this.reInitialized && state.uid < this.currentUid) {
michael@0 1455 var previousParams = this._getPreviousParams(true);
michael@0 1456 if (previousParams) {
michael@0 1457 this._pushToHistory(this.current, false);
michael@0 1458 this._pushToHistory(previousParams, false);
michael@0 1459 this.currentUid = state.uid;
michael@0 1460 window.history.back();
michael@0 1461 return;
michael@0 1462 }
michael@0 1463 }
michael@0 1464 this.historyUnlocked = false;
michael@0 1465
michael@0 1466 if (state.target.dest) {
michael@0 1467 PDFView.navigateTo(state.target.dest);
michael@0 1468 } else {
michael@0 1469 PDFView.setHash(state.target.hash);
michael@0 1470 }
michael@0 1471 this.currentUid = state.uid;
michael@0 1472 if (state.uid > this.uid) {
michael@0 1473 this.uid = state.uid;
michael@0 1474 }
michael@0 1475 this.current = state.target;
michael@0 1476 this.updatePreviousBookmark = true;
michael@0 1477
michael@0 1478 var currentHash = window.location.hash.substring(1);
michael@0 1479 if (this.previousHash !== currentHash) {
michael@0 1480 this.allowHashChange = false;
michael@0 1481 }
michael@0 1482 this.previousHash = currentHash;
michael@0 1483
michael@0 1484 this.historyUnlocked = true;
michael@0 1485 },
michael@0 1486
michael@0 1487 back: function pdfHistoryBack() {
michael@0 1488 this.go(-1);
michael@0 1489 },
michael@0 1490
michael@0 1491 forward: function pdfHistoryForward() {
michael@0 1492 this.go(1);
michael@0 1493 },
michael@0 1494
michael@0 1495 go: function pdfHistoryGo(direction) {
michael@0 1496 if (this.initialized && this.historyUnlocked) {
michael@0 1497 var state = window.history.state;
michael@0 1498 if (direction === -1 && state && state.uid > 0) {
michael@0 1499 window.history.back();
michael@0 1500 } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
michael@0 1501 window.history.forward();
michael@0 1502 }
michael@0 1503 }
michael@0 1504 }
michael@0 1505 };
michael@0 1506
michael@0 1507
michael@0 1508 var SecondaryToolbar = {
michael@0 1509 opened: false,
michael@0 1510 previousContainerHeight: null,
michael@0 1511 newContainerHeight: null,
michael@0 1512
michael@0 1513 initialize: function secondaryToolbarInitialize(options) {
michael@0 1514 this.toolbar = options.toolbar;
michael@0 1515 this.presentationMode = options.presentationMode;
michael@0 1516 this.documentProperties = options.documentProperties;
michael@0 1517 this.buttonContainer = this.toolbar.firstElementChild;
michael@0 1518
michael@0 1519 // Define the toolbar buttons.
michael@0 1520 this.toggleButton = options.toggleButton;
michael@0 1521 this.presentationModeButton = options.presentationModeButton;
michael@0 1522 this.openFile = options.openFile;
michael@0 1523 this.print = options.print;
michael@0 1524 this.download = options.download;
michael@0 1525 this.viewBookmark = options.viewBookmark;
michael@0 1526 this.firstPage = options.firstPage;
michael@0 1527 this.lastPage = options.lastPage;
michael@0 1528 this.pageRotateCw = options.pageRotateCw;
michael@0 1529 this.pageRotateCcw = options.pageRotateCcw;
michael@0 1530 this.documentPropertiesButton = options.documentPropertiesButton;
michael@0 1531
michael@0 1532 // Attach the event listeners.
michael@0 1533 var elements = [
michael@0 1534 // Button to toggle the visibility of the secondary toolbar:
michael@0 1535 { element: this.toggleButton, handler: this.toggle },
michael@0 1536 // All items within the secondary toolbar
michael@0 1537 // (except for toggleHandTool, hand_tool.js is responsible for it):
michael@0 1538 { element: this.presentationModeButton,
michael@0 1539 handler: this.presentationModeClick },
michael@0 1540 { element: this.openFile, handler: this.openFileClick },
michael@0 1541 { element: this.print, handler: this.printClick },
michael@0 1542 { element: this.download, handler: this.downloadClick },
michael@0 1543 { element: this.viewBookmark, handler: this.viewBookmarkClick },
michael@0 1544 { element: this.firstPage, handler: this.firstPageClick },
michael@0 1545 { element: this.lastPage, handler: this.lastPageClick },
michael@0 1546 { element: this.pageRotateCw, handler: this.pageRotateCwClick },
michael@0 1547 { element: this.pageRotateCcw, handler: this.pageRotateCcwClick },
michael@0 1548 { element: this.documentPropertiesButton,
michael@0 1549 handler: this.documentPropertiesClick }
michael@0 1550 ];
michael@0 1551
michael@0 1552 for (var item in elements) {
michael@0 1553 var element = elements[item].element;
michael@0 1554 if (element) {
michael@0 1555 element.addEventListener('click', elements[item].handler.bind(this));
michael@0 1556 }
michael@0 1557 }
michael@0 1558 },
michael@0 1559
michael@0 1560 // Event handling functions.
michael@0 1561 presentationModeClick: function secondaryToolbarPresentationModeClick(evt) {
michael@0 1562 this.presentationMode.request();
michael@0 1563 this.close();
michael@0 1564 },
michael@0 1565
michael@0 1566 openFileClick: function secondaryToolbarOpenFileClick(evt) {
michael@0 1567 document.getElementById('fileInput').click();
michael@0 1568 this.close();
michael@0 1569 },
michael@0 1570
michael@0 1571 printClick: function secondaryToolbarPrintClick(evt) {
michael@0 1572 window.print();
michael@0 1573 this.close();
michael@0 1574 },
michael@0 1575
michael@0 1576 downloadClick: function secondaryToolbarDownloadClick(evt) {
michael@0 1577 PDFView.download();
michael@0 1578 this.close();
michael@0 1579 },
michael@0 1580
michael@0 1581 viewBookmarkClick: function secondaryToolbarViewBookmarkClick(evt) {
michael@0 1582 this.close();
michael@0 1583 },
michael@0 1584
michael@0 1585 firstPageClick: function secondaryToolbarFirstPageClick(evt) {
michael@0 1586 PDFView.page = 1;
michael@0 1587 this.close();
michael@0 1588 },
michael@0 1589
michael@0 1590 lastPageClick: function secondaryToolbarLastPageClick(evt) {
michael@0 1591 PDFView.page = PDFView.pdfDocument.numPages;
michael@0 1592 this.close();
michael@0 1593 },
michael@0 1594
michael@0 1595 pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) {
michael@0 1596 PDFView.rotatePages(90);
michael@0 1597 },
michael@0 1598
michael@0 1599 pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
michael@0 1600 PDFView.rotatePages(-90);
michael@0 1601 },
michael@0 1602
michael@0 1603 documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) {
michael@0 1604 this.documentProperties.show();
michael@0 1605 this.close();
michael@0 1606 },
michael@0 1607
michael@0 1608 // Misc. functions for interacting with the toolbar.
michael@0 1609 setMaxHeight: function secondaryToolbarSetMaxHeight(container) {
michael@0 1610 if (!container || !this.buttonContainer) {
michael@0 1611 return;
michael@0 1612 }
michael@0 1613 this.newContainerHeight = container.clientHeight;
michael@0 1614 if (this.previousContainerHeight === this.newContainerHeight) {
michael@0 1615 return;
michael@0 1616 }
michael@0 1617 this.buttonContainer.setAttribute('style',
michael@0 1618 'max-height: ' + (this.newContainerHeight - SCROLLBAR_PADDING) + 'px;');
michael@0 1619 this.previousContainerHeight = this.newContainerHeight;
michael@0 1620 },
michael@0 1621
michael@0 1622 open: function secondaryToolbarOpen() {
michael@0 1623 if (this.opened) {
michael@0 1624 return;
michael@0 1625 }
michael@0 1626 this.opened = true;
michael@0 1627 this.toggleButton.classList.add('toggled');
michael@0 1628 this.toolbar.classList.remove('hidden');
michael@0 1629 },
michael@0 1630
michael@0 1631 close: function secondaryToolbarClose(target) {
michael@0 1632 if (!this.opened) {
michael@0 1633 return;
michael@0 1634 } else if (target && !this.toolbar.contains(target)) {
michael@0 1635 return;
michael@0 1636 }
michael@0 1637 this.opened = false;
michael@0 1638 this.toolbar.classList.add('hidden');
michael@0 1639 this.toggleButton.classList.remove('toggled');
michael@0 1640 },
michael@0 1641
michael@0 1642 toggle: function secondaryToolbarToggle() {
michael@0 1643 if (this.opened) {
michael@0 1644 this.close();
michael@0 1645 } else {
michael@0 1646 this.open();
michael@0 1647 }
michael@0 1648 }
michael@0 1649 };
michael@0 1650
michael@0 1651
michael@0 1652 var PasswordPrompt = {
michael@0 1653 visible: false,
michael@0 1654 updatePassword: null,
michael@0 1655 reason: null,
michael@0 1656 overlayContainer: null,
michael@0 1657 passwordField: null,
michael@0 1658 passwordText: null,
michael@0 1659 passwordSubmit: null,
michael@0 1660 passwordCancel: null,
michael@0 1661
michael@0 1662 initialize: function secondaryToolbarInitialize(options) {
michael@0 1663 this.overlayContainer = options.overlayContainer;
michael@0 1664 this.passwordField = options.passwordField;
michael@0 1665 this.passwordText = options.passwordText;
michael@0 1666 this.passwordSubmit = options.passwordSubmit;
michael@0 1667 this.passwordCancel = options.passwordCancel;
michael@0 1668
michael@0 1669 // Attach the event listeners.
michael@0 1670 this.passwordSubmit.addEventListener('click',
michael@0 1671 this.verifyPassword.bind(this));
michael@0 1672
michael@0 1673 this.passwordCancel.addEventListener('click', this.hide.bind(this));
michael@0 1674
michael@0 1675 this.passwordField.addEventListener('keydown',
michael@0 1676 function (e) {
michael@0 1677 if (e.keyCode === 13) { // Enter key
michael@0 1678 this.verifyPassword();
michael@0 1679 }
michael@0 1680 }.bind(this));
michael@0 1681
michael@0 1682 window.addEventListener('keydown',
michael@0 1683 function (e) {
michael@0 1684 if (e.keyCode === 27) { // Esc key
michael@0 1685 this.hide();
michael@0 1686 }
michael@0 1687 }.bind(this));
michael@0 1688 },
michael@0 1689
michael@0 1690 show: function passwordPromptShow() {
michael@0 1691 if (this.visible) {
michael@0 1692 return;
michael@0 1693 }
michael@0 1694 this.visible = true;
michael@0 1695 this.overlayContainer.classList.remove('hidden');
michael@0 1696 this.overlayContainer.firstElementChild.classList.remove('hidden');
michael@0 1697 this.passwordField.focus();
michael@0 1698
michael@0 1699 var promptString = mozL10n.get('password_label', null,
michael@0 1700 'Enter the password to open this PDF file.');
michael@0 1701
michael@0 1702 if (this.reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) {
michael@0 1703 promptString = mozL10n.get('password_invalid', null,
michael@0 1704 'Invalid password. Please try again.');
michael@0 1705 }
michael@0 1706
michael@0 1707 this.passwordText.textContent = promptString;
michael@0 1708 },
michael@0 1709
michael@0 1710 hide: function passwordPromptClose() {
michael@0 1711 if (!this.visible) {
michael@0 1712 return;
michael@0 1713 }
michael@0 1714 this.visible = false;
michael@0 1715 this.passwordField.value = '';
michael@0 1716 this.overlayContainer.classList.add('hidden');
michael@0 1717 this.overlayContainer.firstElementChild.classList.add('hidden');
michael@0 1718 },
michael@0 1719
michael@0 1720 verifyPassword: function passwordPromptVerifyPassword() {
michael@0 1721 var password = this.passwordField.value;
michael@0 1722 if (password && password.length > 0) {
michael@0 1723 this.hide();
michael@0 1724 return this.updatePassword(password);
michael@0 1725 }
michael@0 1726 }
michael@0 1727 };
michael@0 1728
michael@0 1729
michael@0 1730 var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms
michael@0 1731 var SELECTOR = 'presentationControls';
michael@0 1732 var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1000; // in ms
michael@0 1733
michael@0 1734 var PresentationMode = {
michael@0 1735 active: false,
michael@0 1736 args: null,
michael@0 1737 contextMenuOpen: false,
michael@0 1738
michael@0 1739 initialize: function presentationModeInitialize(options) {
michael@0 1740 this.container = options.container;
michael@0 1741 this.secondaryToolbar = options.secondaryToolbar;
michael@0 1742
michael@0 1743 this.viewer = this.container.firstElementChild;
michael@0 1744
michael@0 1745 this.firstPage = options.firstPage;
michael@0 1746 this.lastPage = options.lastPage;
michael@0 1747 this.pageRotateCw = options.pageRotateCw;
michael@0 1748 this.pageRotateCcw = options.pageRotateCcw;
michael@0 1749
michael@0 1750 this.firstPage.addEventListener('click', function() {
michael@0 1751 this.contextMenuOpen = false;
michael@0 1752 this.secondaryToolbar.firstPageClick();
michael@0 1753 }.bind(this));
michael@0 1754 this.lastPage.addEventListener('click', function() {
michael@0 1755 this.contextMenuOpen = false;
michael@0 1756 this.secondaryToolbar.lastPageClick();
michael@0 1757 }.bind(this));
michael@0 1758
michael@0 1759 this.pageRotateCw.addEventListener('click', function() {
michael@0 1760 this.contextMenuOpen = false;
michael@0 1761 this.secondaryToolbar.pageRotateCwClick();
michael@0 1762 }.bind(this));
michael@0 1763 this.pageRotateCcw.addEventListener('click', function() {
michael@0 1764 this.contextMenuOpen = false;
michael@0 1765 this.secondaryToolbar.pageRotateCcwClick();
michael@0 1766 }.bind(this));
michael@0 1767 },
michael@0 1768
michael@0 1769 get isFullscreen() {
michael@0 1770 return (document.fullscreenElement ||
michael@0 1771 document.mozFullScreen ||
michael@0 1772 document.webkitIsFullScreen ||
michael@0 1773 document.msFullscreenElement);
michael@0 1774 },
michael@0 1775
michael@0 1776 /**
michael@0 1777 * Initialize a timeout that is used to reset PDFView.currentPosition when the
michael@0 1778 * browser transitions to fullscreen mode. Since resize events are triggered
michael@0 1779 * multiple times during the switch to fullscreen mode, this is necessary in
michael@0 1780 * order to prevent the page from being scrolled partially, or completely,
michael@0 1781 * out of view when Presentation Mode is enabled.
michael@0 1782 * Note: This is only an issue at certain zoom levels, e.g. 'page-width'.
michael@0 1783 */
michael@0 1784 _setSwitchInProgress: function presentationMode_setSwitchInProgress() {
michael@0 1785 if (this.switchInProgress) {
michael@0 1786 clearTimeout(this.switchInProgress);
michael@0 1787 }
michael@0 1788 this.switchInProgress = setTimeout(function switchInProgressTimeout() {
michael@0 1789 delete this.switchInProgress;
michael@0 1790 }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
michael@0 1791
michael@0 1792 PDFView.currentPosition = null;
michael@0 1793 },
michael@0 1794
michael@0 1795 _resetSwitchInProgress: function presentationMode_resetSwitchInProgress() {
michael@0 1796 if (this.switchInProgress) {
michael@0 1797 clearTimeout(this.switchInProgress);
michael@0 1798 delete this.switchInProgress;
michael@0 1799 }
michael@0 1800 },
michael@0 1801
michael@0 1802 request: function presentationModeRequest() {
michael@0 1803 if (!PDFView.supportsFullscreen || this.isFullscreen ||
michael@0 1804 !this.viewer.hasChildNodes()) {
michael@0 1805 return false;
michael@0 1806 }
michael@0 1807 this._setSwitchInProgress();
michael@0 1808
michael@0 1809 if (this.container.requestFullscreen) {
michael@0 1810 this.container.requestFullscreen();
michael@0 1811 } else if (this.container.mozRequestFullScreen) {
michael@0 1812 this.container.mozRequestFullScreen();
michael@0 1813 } else if (this.container.webkitRequestFullScreen) {
michael@0 1814 this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
michael@0 1815 } else if (this.container.msRequestFullscreen) {
michael@0 1816 this.container.msRequestFullscreen();
michael@0 1817 } else {
michael@0 1818 return false;
michael@0 1819 }
michael@0 1820
michael@0 1821 this.args = {
michael@0 1822 page: PDFView.page,
michael@0 1823 previousScale: PDFView.currentScaleValue
michael@0 1824 };
michael@0 1825
michael@0 1826 return true;
michael@0 1827 },
michael@0 1828
michael@0 1829 enter: function presentationModeEnter() {
michael@0 1830 this.active = true;
michael@0 1831 this._resetSwitchInProgress();
michael@0 1832
michael@0 1833 // Ensure that the correct page is scrolled into view when entering
michael@0 1834 // Presentation Mode, by waiting until fullscreen mode in enabled.
michael@0 1835 // Note: This is only necessary in non-Mozilla browsers.
michael@0 1836 setTimeout(function enterPresentationModeTimeout() {
michael@0 1837 PDFView.page = this.args.page;
michael@0 1838 PDFView.setScale('page-fit', true);
michael@0 1839 }.bind(this), 0);
michael@0 1840
michael@0 1841 window.addEventListener('mousemove', this.mouseMove, false);
michael@0 1842 window.addEventListener('mousedown', this.mouseDown, false);
michael@0 1843 window.addEventListener('contextmenu', this.contextMenu, false);
michael@0 1844
michael@0 1845 this.showControls();
michael@0 1846 HandTool.enterPresentationMode();
michael@0 1847 this.contextMenuOpen = false;
michael@0 1848 this.container.setAttribute('contextmenu', 'viewerContextMenu');
michael@0 1849 },
michael@0 1850
michael@0 1851 exit: function presentationModeExit() {
michael@0 1852 var page = PDFView.page;
michael@0 1853
michael@0 1854 // Ensure that the correct page is scrolled into view when exiting
michael@0 1855 // Presentation Mode, by waiting until fullscreen mode is disabled.
michael@0 1856 // Note: This is only necessary in non-Mozilla browsers.
michael@0 1857 setTimeout(function exitPresentationModeTimeout() {
michael@0 1858 this.active = false;
michael@0 1859 PDFView.setScale(this.args.previousScale);
michael@0 1860 PDFView.page = page;
michael@0 1861 this.args = null;
michael@0 1862 }.bind(this), 0);
michael@0 1863
michael@0 1864 window.removeEventListener('mousemove', this.mouseMove, false);
michael@0 1865 window.removeEventListener('mousedown', this.mouseDown, false);
michael@0 1866 window.removeEventListener('contextmenu', this.contextMenu, false);
michael@0 1867
michael@0 1868 this.hideControls();
michael@0 1869 PDFView.clearMouseScrollState();
michael@0 1870 HandTool.exitPresentationMode();
michael@0 1871 this.container.removeAttribute('contextmenu');
michael@0 1872 this.contextMenuOpen = false;
michael@0 1873
michael@0 1874 // Ensure that the thumbnail of the current page is visible
michael@0 1875 // when exiting presentation mode.
michael@0 1876 scrollIntoView(document.getElementById('thumbnailContainer' + page));
michael@0 1877 },
michael@0 1878
michael@0 1879 showControls: function presentationModeShowControls() {
michael@0 1880 if (this.controlsTimeout) {
michael@0 1881 clearTimeout(this.controlsTimeout);
michael@0 1882 } else {
michael@0 1883 this.container.classList.add(SELECTOR);
michael@0 1884 }
michael@0 1885 this.controlsTimeout = setTimeout(function hideControlsTimeout() {
michael@0 1886 this.container.classList.remove(SELECTOR);
michael@0 1887 delete this.controlsTimeout;
michael@0 1888 }.bind(this), DELAY_BEFORE_HIDING_CONTROLS);
michael@0 1889 },
michael@0 1890
michael@0 1891 hideControls: function presentationModeHideControls() {
michael@0 1892 if (!this.controlsTimeout) {
michael@0 1893 return;
michael@0 1894 }
michael@0 1895 this.container.classList.remove(SELECTOR);
michael@0 1896 clearTimeout(this.controlsTimeout);
michael@0 1897 delete this.controlsTimeout;
michael@0 1898 },
michael@0 1899
michael@0 1900 mouseMove: function presentationModeMouseMove(evt) {
michael@0 1901 PresentationMode.showControls();
michael@0 1902 },
michael@0 1903
michael@0 1904 mouseDown: function presentationModeMouseDown(evt) {
michael@0 1905 var self = PresentationMode;
michael@0 1906 if (self.contextMenuOpen) {
michael@0 1907 self.contextMenuOpen = false;
michael@0 1908 evt.preventDefault();
michael@0 1909 return;
michael@0 1910 }
michael@0 1911
michael@0 1912 if (evt.button === 0) {
michael@0 1913 // Enable clicking of links in presentation mode. Please note:
michael@0 1914 // Only links pointing to destinations in the current PDF document work.
michael@0 1915 var isInternalLink = (evt.target.href &&
michael@0 1916 evt.target.classList.contains('internalLink'));
michael@0 1917 if (!isInternalLink) {
michael@0 1918 // Unless an internal link was clicked, advance one page.
michael@0 1919 evt.preventDefault();
michael@0 1920 PDFView.page += (evt.shiftKey ? -1 : 1);
michael@0 1921 }
michael@0 1922 }
michael@0 1923 },
michael@0 1924
michael@0 1925 contextMenu: function presentationModeContextMenu(evt) {
michael@0 1926 PresentationMode.contextMenuOpen = true;
michael@0 1927 }
michael@0 1928 };
michael@0 1929
michael@0 1930 (function presentationModeClosure() {
michael@0 1931 function presentationModeChange(e) {
michael@0 1932 if (PresentationMode.isFullscreen) {
michael@0 1933 PresentationMode.enter();
michael@0 1934 } else {
michael@0 1935 PresentationMode.exit();
michael@0 1936 }
michael@0 1937 }
michael@0 1938
michael@0 1939 window.addEventListener('fullscreenchange', presentationModeChange, false);
michael@0 1940 window.addEventListener('mozfullscreenchange', presentationModeChange, false);
michael@0 1941 window.addEventListener('webkitfullscreenchange', presentationModeChange,
michael@0 1942 false);
michael@0 1943 window.addEventListener('MSFullscreenChange', presentationModeChange, false);
michael@0 1944 })();
michael@0 1945
michael@0 1946
michael@0 1947 /* Copyright 2013 Rob Wu <gwnRob@gmail.com>
michael@0 1948 * https://github.com/Rob--W/grab-to-pan.js
michael@0 1949 *
michael@0 1950 * Licensed under the Apache License, Version 2.0 (the "License");
michael@0 1951 * you may not use this file except in compliance with the License.
michael@0 1952 * You may obtain a copy of the License at
michael@0 1953 *
michael@0 1954 * http://www.apache.org/licenses/LICENSE-2.0
michael@0 1955 *
michael@0 1956 * Unless required by applicable law or agreed to in writing, software
michael@0 1957 * distributed under the License is distributed on an "AS IS" BASIS,
michael@0 1958 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0 1959 * See the License for the specific language governing permissions and
michael@0 1960 * limitations under the License.
michael@0 1961 */
michael@0 1962
michael@0 1963 'use strict';
michael@0 1964
michael@0 1965 var GrabToPan = (function GrabToPanClosure() {
michael@0 1966 /**
michael@0 1967 * Construct a GrabToPan instance for a given HTML element.
michael@0 1968 * @param options.element {Element}
michael@0 1969 * @param options.ignoreTarget {function} optional. See `ignoreTarget(node)`
michael@0 1970 * @param options.onActiveChanged {function(boolean)} optional. Called
michael@0 1971 * when grab-to-pan is (de)activated. The first argument is a boolean that
michael@0 1972 * shows whether grab-to-pan is activated.
michael@0 1973 */
michael@0 1974 function GrabToPan(options) {
michael@0 1975 this.element = options.element;
michael@0 1976 this.document = options.element.ownerDocument;
michael@0 1977 if (typeof options.ignoreTarget === 'function') {
michael@0 1978 this.ignoreTarget = options.ignoreTarget;
michael@0 1979 }
michael@0 1980 this.onActiveChanged = options.onActiveChanged;
michael@0 1981
michael@0 1982 // Bind the contexts to ensure that `this` always points to
michael@0 1983 // the GrabToPan instance.
michael@0 1984 this.activate = this.activate.bind(this);
michael@0 1985 this.deactivate = this.deactivate.bind(this);
michael@0 1986 this.toggle = this.toggle.bind(this);
michael@0 1987 this._onmousedown = this._onmousedown.bind(this);
michael@0 1988 this._onmousemove = this._onmousemove.bind(this);
michael@0 1989 this._endPan = this._endPan.bind(this);
michael@0 1990
michael@0 1991 // This overlay will be inserted in the document when the mouse moves during
michael@0 1992 // a grab operation, to ensure that the cursor has the desired appearance.
michael@0 1993 var overlay = this.overlay = document.createElement('div');
michael@0 1994 overlay.className = 'grab-to-pan-grabbing';
michael@0 1995 }
michael@0 1996 GrabToPan.prototype = {
michael@0 1997 /**
michael@0 1998 * Class name of element which can be grabbed
michael@0 1999 */
michael@0 2000 CSS_CLASS_GRAB: 'grab-to-pan-grab',
michael@0 2001
michael@0 2002 /**
michael@0 2003 * Bind a mousedown event to the element to enable grab-detection.
michael@0 2004 */
michael@0 2005 activate: function GrabToPan_activate() {
michael@0 2006 if (!this.active) {
michael@0 2007 this.active = true;
michael@0 2008 this.element.addEventListener('mousedown', this._onmousedown, true);
michael@0 2009 this.element.classList.add(this.CSS_CLASS_GRAB);
michael@0 2010 if (this.onActiveChanged) {
michael@0 2011 this.onActiveChanged(true);
michael@0 2012 }
michael@0 2013 }
michael@0 2014 },
michael@0 2015
michael@0 2016 /**
michael@0 2017 * Removes all events. Any pending pan session is immediately stopped.
michael@0 2018 */
michael@0 2019 deactivate: function GrabToPan_deactivate() {
michael@0 2020 if (this.active) {
michael@0 2021 this.active = false;
michael@0 2022 this.element.removeEventListener('mousedown', this._onmousedown, true);
michael@0 2023 this._endPan();
michael@0 2024 this.element.classList.remove(this.CSS_CLASS_GRAB);
michael@0 2025 if (this.onActiveChanged) {
michael@0 2026 this.onActiveChanged(false);
michael@0 2027 }
michael@0 2028 }
michael@0 2029 },
michael@0 2030
michael@0 2031 toggle: function GrabToPan_toggle() {
michael@0 2032 if (this.active) {
michael@0 2033 this.deactivate();
michael@0 2034 } else {
michael@0 2035 this.activate();
michael@0 2036 }
michael@0 2037 },
michael@0 2038
michael@0 2039 /**
michael@0 2040 * Whether to not pan if the target element is clicked.
michael@0 2041 * Override this method to change the default behaviour.
michael@0 2042 *
michael@0 2043 * @param node {Element} The target of the event
michael@0 2044 * @return {boolean} Whether to not react to the click event.
michael@0 2045 */
michael@0 2046 ignoreTarget: function GrabToPan_ignoreTarget(node) {
michael@0 2047 // Use matchesSelector to check whether the clicked element
michael@0 2048 // is (a child of) an input element / link
michael@0 2049 return node[matchesSelector](
michael@0 2050 'a[href], a[href] *, input, textarea, button, button *, select, option'
michael@0 2051 );
michael@0 2052 },
michael@0 2053
michael@0 2054 /**
michael@0 2055 * @private
michael@0 2056 */
michael@0 2057 _onmousedown: function GrabToPan__onmousedown(event) {
michael@0 2058 if (event.button !== 0 || this.ignoreTarget(event.target)) {
michael@0 2059 return;
michael@0 2060 }
michael@0 2061 if (event.originalTarget) {
michael@0 2062 try {
michael@0 2063 /* jshint expr:true */
michael@0 2064 event.originalTarget.tagName;
michael@0 2065 } catch (e) {
michael@0 2066 // Mozilla-specific: element is a scrollbar (XUL element)
michael@0 2067 return;
michael@0 2068 }
michael@0 2069 }
michael@0 2070
michael@0 2071 this.scrollLeftStart = this.element.scrollLeft;
michael@0 2072 this.scrollTopStart = this.element.scrollTop;
michael@0 2073 this.clientXStart = event.clientX;
michael@0 2074 this.clientYStart = event.clientY;
michael@0 2075 this.document.addEventListener('mousemove', this._onmousemove, true);
michael@0 2076 this.document.addEventListener('mouseup', this._endPan, true);
michael@0 2077 // When a scroll event occurs before a mousemove, assume that the user
michael@0 2078 // dragged a scrollbar (necessary for Opera Presto, Safari and IE)
michael@0 2079 // (not needed for Chrome/Firefox)
michael@0 2080 this.element.addEventListener('scroll', this._endPan, true);
michael@0 2081 event.preventDefault();
michael@0 2082 event.stopPropagation();
michael@0 2083 this.document.documentElement.classList.add(this.CSS_CLASS_GRABBING);
michael@0 2084 },
michael@0 2085
michael@0 2086 /**
michael@0 2087 * @private
michael@0 2088 */
michael@0 2089 _onmousemove: function GrabToPan__onmousemove(event) {
michael@0 2090 this.element.removeEventListener('scroll', this._endPan, true);
michael@0 2091 if (isLeftMouseReleased(event)) {
michael@0 2092 this._endPan();
michael@0 2093 return;
michael@0 2094 }
michael@0 2095 var xDiff = event.clientX - this.clientXStart;
michael@0 2096 var yDiff = event.clientY - this.clientYStart;
michael@0 2097 this.element.scrollTop = this.scrollTopStart - yDiff;
michael@0 2098 this.element.scrollLeft = this.scrollLeftStart - xDiff;
michael@0 2099 if (!this.overlay.parentNode) {
michael@0 2100 document.body.appendChild(this.overlay);
michael@0 2101 }
michael@0 2102 },
michael@0 2103
michael@0 2104 /**
michael@0 2105 * @private
michael@0 2106 */
michael@0 2107 _endPan: function GrabToPan__endPan() {
michael@0 2108 this.element.removeEventListener('scroll', this._endPan, true);
michael@0 2109 this.document.removeEventListener('mousemove', this._onmousemove, true);
michael@0 2110 this.document.removeEventListener('mouseup', this._endPan, true);
michael@0 2111 if (this.overlay.parentNode) {
michael@0 2112 this.overlay.parentNode.removeChild(this.overlay);
michael@0 2113 }
michael@0 2114 }
michael@0 2115 };
michael@0 2116
michael@0 2117 // Get the correct (vendor-prefixed) name of the matches method.
michael@0 2118 var matchesSelector;
michael@0 2119 ['webkitM', 'mozM', 'msM', 'oM', 'm'].some(function(prefix) {
michael@0 2120 var name = prefix + 'atches';
michael@0 2121 if (name in document.documentElement) {
michael@0 2122 matchesSelector = name;
michael@0 2123 }
michael@0 2124 name += 'Selector';
michael@0 2125 if (name in document.documentElement) {
michael@0 2126 matchesSelector = name;
michael@0 2127 }
michael@0 2128 return matchesSelector; // If found, then truthy, and [].some() ends.
michael@0 2129 });
michael@0 2130
michael@0 2131 // Browser sniffing because it's impossible to feature-detect
michael@0 2132 // whether event.which for onmousemove is reliable
michael@0 2133 var isNotIEorIsIE10plus = !document.documentMode || document.documentMode > 9;
michael@0 2134 var chrome = window.chrome;
michael@0 2135 var isChrome15OrOpera15plus = chrome && (chrome.webstore || chrome.app);
michael@0 2136 // ^ Chrome 15+ ^ Opera 15+
michael@0 2137 var isSafari6plus = /Apple/.test(navigator.vendor) &&
michael@0 2138 /Version\/([6-9]\d*|[1-5]\d+)/.test(navigator.userAgent);
michael@0 2139
michael@0 2140 /**
michael@0 2141 * Whether the left mouse is not pressed.
michael@0 2142 * @param event {MouseEvent}
michael@0 2143 * @return {boolean} True if the left mouse button is not pressed.
michael@0 2144 * False if unsure or if the left mouse button is pressed.
michael@0 2145 */
michael@0 2146 function isLeftMouseReleased(event) {
michael@0 2147 if ('buttons' in event && isNotIEorIsIE10plus) {
michael@0 2148 // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-buttons
michael@0 2149 // Firefox 15+
michael@0 2150 // Internet Explorer 10+
michael@0 2151 return !(event.buttons | 1);
michael@0 2152 }
michael@0 2153 if (isChrome15OrOpera15plus || isSafari6plus) {
michael@0 2154 // Chrome 14+
michael@0 2155 // Opera 15+
michael@0 2156 // Safari 6.0+
michael@0 2157 return event.which === 0;
michael@0 2158 }
michael@0 2159 }
michael@0 2160
michael@0 2161 return GrabToPan;
michael@0 2162 })();
michael@0 2163
michael@0 2164 var HandTool = {
michael@0 2165 initialize: function handToolInitialize(options) {
michael@0 2166 var toggleHandTool = options.toggleHandTool;
michael@0 2167 this.handTool = new GrabToPan({
michael@0 2168 element: options.container,
michael@0 2169 onActiveChanged: function(isActive) {
michael@0 2170 if (!toggleHandTool) {
michael@0 2171 return;
michael@0 2172 }
michael@0 2173 if (isActive) {
michael@0 2174 toggleHandTool.title =
michael@0 2175 mozL10n.get('hand_tool_disable.title', null, 'Disable hand tool');
michael@0 2176 toggleHandTool.firstElementChild.textContent =
michael@0 2177 mozL10n.get('hand_tool_disable_label', null, 'Disable hand tool');
michael@0 2178 } else {
michael@0 2179 toggleHandTool.title =
michael@0 2180 mozL10n.get('hand_tool_enable.title', null, 'Enable hand tool');
michael@0 2181 toggleHandTool.firstElementChild.textContent =
michael@0 2182 mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool');
michael@0 2183 }
michael@0 2184 }
michael@0 2185 });
michael@0 2186 if (toggleHandTool) {
michael@0 2187 toggleHandTool.addEventListener('click', this.toggle.bind(this), false);
michael@0 2188
michael@0 2189 window.addEventListener('localized', function (evt) {
michael@0 2190 Preferences.get('enableHandToolOnLoad').then(function (prefValue) {
michael@0 2191 if (prefValue) {
michael@0 2192 this.handTool.activate();
michael@0 2193 }
michael@0 2194 }.bind(this));
michael@0 2195 }.bind(this));
michael@0 2196 }
michael@0 2197 },
michael@0 2198
michael@0 2199 toggle: function handToolToggle() {
michael@0 2200 this.handTool.toggle();
michael@0 2201 SecondaryToolbar.close();
michael@0 2202 },
michael@0 2203
michael@0 2204 enterPresentationMode: function handToolEnterPresentationMode() {
michael@0 2205 if (this.handTool.active) {
michael@0 2206 this.wasActive = true;
michael@0 2207 this.handTool.deactivate();
michael@0 2208 }
michael@0 2209 },
michael@0 2210
michael@0 2211 exitPresentationMode: function handToolExitPresentationMode() {
michael@0 2212 if (this.wasActive) {
michael@0 2213 this.wasActive = null;
michael@0 2214 this.handTool.activate();
michael@0 2215 }
michael@0 2216 }
michael@0 2217 };
michael@0 2218
michael@0 2219
michael@0 2220 var DocumentProperties = {
michael@0 2221 overlayContainer: null,
michael@0 2222 fileName: '',
michael@0 2223 fileSize: '',
michael@0 2224 visible: false,
michael@0 2225
michael@0 2226 // Document property fields (in the viewer).
michael@0 2227 fileNameField: null,
michael@0 2228 fileSizeField: null,
michael@0 2229 titleField: null,
michael@0 2230 authorField: null,
michael@0 2231 subjectField: null,
michael@0 2232 keywordsField: null,
michael@0 2233 creationDateField: null,
michael@0 2234 modificationDateField: null,
michael@0 2235 creatorField: null,
michael@0 2236 producerField: null,
michael@0 2237 versionField: null,
michael@0 2238 pageCountField: null,
michael@0 2239
michael@0 2240 initialize: function documentPropertiesInitialize(options) {
michael@0 2241 this.overlayContainer = options.overlayContainer;
michael@0 2242
michael@0 2243 // Set the document property fields.
michael@0 2244 this.fileNameField = options.fileNameField;
michael@0 2245 this.fileSizeField = options.fileSizeField;
michael@0 2246 this.titleField = options.titleField;
michael@0 2247 this.authorField = options.authorField;
michael@0 2248 this.subjectField = options.subjectField;
michael@0 2249 this.keywordsField = options.keywordsField;
michael@0 2250 this.creationDateField = options.creationDateField;
michael@0 2251 this.modificationDateField = options.modificationDateField;
michael@0 2252 this.creatorField = options.creatorField;
michael@0 2253 this.producerField = options.producerField;
michael@0 2254 this.versionField = options.versionField;
michael@0 2255 this.pageCountField = options.pageCountField;
michael@0 2256
michael@0 2257 // Bind the event listener for the Close button.
michael@0 2258 if (options.closeButton) {
michael@0 2259 options.closeButton.addEventListener('click', this.hide.bind(this));
michael@0 2260 }
michael@0 2261
michael@0 2262 this.dataAvailablePromise = new Promise(function (resolve) {
michael@0 2263 this.resolveDataAvailable = resolve;
michael@0 2264 }.bind(this));
michael@0 2265
michael@0 2266 // Bind the event listener for the Esc key (to close the dialog).
michael@0 2267 window.addEventListener('keydown',
michael@0 2268 function (e) {
michael@0 2269 if (e.keyCode === 27) { // Esc key
michael@0 2270 this.hide();
michael@0 2271 }
michael@0 2272 }.bind(this));
michael@0 2273 },
michael@0 2274
michael@0 2275 getProperties: function documentPropertiesGetProperties() {
michael@0 2276 if (!this.visible) {
michael@0 2277 // If the dialog was closed before dataAvailablePromise was resolved,
michael@0 2278 // don't bother updating the properties.
michael@0 2279 return;
michael@0 2280 }
michael@0 2281 // Get the file name.
michael@0 2282 this.fileName = getPDFFileNameFromURL(PDFView.url);
michael@0 2283
michael@0 2284 // Get the file size.
michael@0 2285 PDFView.pdfDocument.getDownloadInfo().then(function(data) {
michael@0 2286 this.setFileSize(data.length);
michael@0 2287 this.updateUI(this.fileSizeField, this.fileSize);
michael@0 2288 }.bind(this));
michael@0 2289
michael@0 2290 // Get the other document properties.
michael@0 2291 PDFView.pdfDocument.getMetadata().then(function(data) {
michael@0 2292 var fields = [
michael@0 2293 { field: this.fileNameField, content: this.fileName },
michael@0 2294 // The fileSize field is updated once getDownloadInfo is resolved.
michael@0 2295 { field: this.titleField, content: data.info.Title },
michael@0 2296 { field: this.authorField, content: data.info.Author },
michael@0 2297 { field: this.subjectField, content: data.info.Subject },
michael@0 2298 { field: this.keywordsField, content: data.info.Keywords },
michael@0 2299 { field: this.creationDateField,
michael@0 2300 content: this.parseDate(data.info.CreationDate) },
michael@0 2301 { field: this.modificationDateField,
michael@0 2302 content: this.parseDate(data.info.ModDate) },
michael@0 2303 { field: this.creatorField, content: data.info.Creator },
michael@0 2304 { field: this.producerField, content: data.info.Producer },
michael@0 2305 { field: this.versionField, content: data.info.PDFFormatVersion },
michael@0 2306 { field: this.pageCountField, content: PDFView.pdfDocument.numPages }
michael@0 2307 ];
michael@0 2308
michael@0 2309 // Show the properties in the dialog.
michael@0 2310 for (var item in fields) {
michael@0 2311 var element = fields[item];
michael@0 2312 this.updateUI(element.field, element.content);
michael@0 2313 }
michael@0 2314 }.bind(this));
michael@0 2315 },
michael@0 2316
michael@0 2317 updateUI: function documentPropertiesUpdateUI(field, content) {
michael@0 2318 if (field && content !== undefined && content !== '') {
michael@0 2319 field.textContent = content;
michael@0 2320 }
michael@0 2321 },
michael@0 2322
michael@0 2323 setFileSize: function documentPropertiesSetFileSize(fileSize) {
michael@0 2324 var kb = fileSize / 1024;
michael@0 2325 if (kb < 1024) {
michael@0 2326 this.fileSize = mozL10n.get('document_properties_kb', {
michael@0 2327 size_kb: (+kb.toPrecision(3)).toLocaleString(),
michael@0 2328 size_b: fileSize.toLocaleString()
michael@0 2329 }, '{{size_kb}} KB ({{size_b}} bytes)');
michael@0 2330 } else {
michael@0 2331 this.fileSize = mozL10n.get('document_properties_mb', {
michael@0 2332 size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
michael@0 2333 size_b: fileSize.toLocaleString()
michael@0 2334 }, '{{size_mb}} MB ({{size_b}} bytes)');
michael@0 2335 }
michael@0 2336 },
michael@0 2337
michael@0 2338 show: function documentPropertiesShow() {
michael@0 2339 if (this.visible) {
michael@0 2340 return;
michael@0 2341 }
michael@0 2342 this.visible = true;
michael@0 2343 this.overlayContainer.classList.remove('hidden');
michael@0 2344 this.overlayContainer.lastElementChild.classList.remove('hidden');
michael@0 2345
michael@0 2346 this.dataAvailablePromise.then(function () {
michael@0 2347 this.getProperties();
michael@0 2348 }.bind(this));
michael@0 2349 },
michael@0 2350
michael@0 2351 hide: function documentPropertiesClose() {
michael@0 2352 if (!this.visible) {
michael@0 2353 return;
michael@0 2354 }
michael@0 2355 this.visible = false;
michael@0 2356 this.overlayContainer.classList.add('hidden');
michael@0 2357 this.overlayContainer.lastElementChild.classList.add('hidden');
michael@0 2358 },
michael@0 2359
michael@0 2360 parseDate: function documentPropertiesParseDate(inputDate) {
michael@0 2361 // This is implemented according to the PDF specification (see
michael@0 2362 // http://www.gnupdf.org/Date for an overview), but note that
michael@0 2363 // Adobe Reader doesn't handle changing the date to universal time
michael@0 2364 // and doesn't use the user's time zone (they're effectively ignoring
michael@0 2365 // the HH' and mm' parts of the date string).
michael@0 2366 var dateToParse = inputDate;
michael@0 2367 if (dateToParse === undefined) {
michael@0 2368 return '';
michael@0 2369 }
michael@0 2370
michael@0 2371 // Remove the D: prefix if it is available.
michael@0 2372 if (dateToParse.substring(0,2) === 'D:') {
michael@0 2373 dateToParse = dateToParse.substring(2);
michael@0 2374 }
michael@0 2375
michael@0 2376 // Get all elements from the PDF date string.
michael@0 2377 // JavaScript's Date object expects the month to be between
michael@0 2378 // 0 and 11 instead of 1 and 12, so we're correcting for this.
michael@0 2379 var year = parseInt(dateToParse.substring(0,4), 10);
michael@0 2380 var month = parseInt(dateToParse.substring(4,6), 10) - 1;
michael@0 2381 var day = parseInt(dateToParse.substring(6,8), 10);
michael@0 2382 var hours = parseInt(dateToParse.substring(8,10), 10);
michael@0 2383 var minutes = parseInt(dateToParse.substring(10,12), 10);
michael@0 2384 var seconds = parseInt(dateToParse.substring(12,14), 10);
michael@0 2385 var utRel = dateToParse.substring(14,15);
michael@0 2386 var offsetHours = parseInt(dateToParse.substring(15,17), 10);
michael@0 2387 var offsetMinutes = parseInt(dateToParse.substring(18,20), 10);
michael@0 2388
michael@0 2389 // As per spec, utRel = 'Z' means equal to universal time.
michael@0 2390 // The other cases ('-' and '+') have to be handled here.
michael@0 2391 if (utRel == '-') {
michael@0 2392 hours += offsetHours;
michael@0 2393 minutes += offsetMinutes;
michael@0 2394 } else if (utRel == '+') {
michael@0 2395 hours -= offsetHours;
michael@0 2396 minutes += offsetMinutes;
michael@0 2397 }
michael@0 2398
michael@0 2399 // Return the new date format from the user's locale.
michael@0 2400 var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
michael@0 2401 var dateString = date.toLocaleDateString();
michael@0 2402 var timeString = date.toLocaleTimeString();
michael@0 2403 return mozL10n.get('document_properties_date_string',
michael@0 2404 {date: dateString, time: timeString},
michael@0 2405 '{{date}}, {{time}}');
michael@0 2406 }
michael@0 2407 };
michael@0 2408
michael@0 2409
michael@0 2410 var PDFView = {
michael@0 2411 pages: [],
michael@0 2412 thumbnails: [],
michael@0 2413 currentScale: UNKNOWN_SCALE,
michael@0 2414 currentScaleValue: null,
michael@0 2415 initialBookmark: document.location.hash.substring(1),
michael@0 2416 container: null,
michael@0 2417 thumbnailContainer: null,
michael@0 2418 initialized: false,
michael@0 2419 fellback: false,
michael@0 2420 pdfDocument: null,
michael@0 2421 sidebarOpen: false,
michael@0 2422 pageViewScroll: null,
michael@0 2423 thumbnailViewScroll: null,
michael@0 2424 pageRotation: 0,
michael@0 2425 mouseScrollTimeStamp: 0,
michael@0 2426 mouseScrollDelta: 0,
michael@0 2427 lastScroll: 0,
michael@0 2428 previousPageNumber: 1,
michael@0 2429 isViewerEmbedded: (window.parent !== window),
michael@0 2430 idleTimeout: null,
michael@0 2431 currentPosition: null,
michael@0 2432
michael@0 2433 // called once when the document is loaded
michael@0 2434 initialize: function pdfViewInitialize() {
michael@0 2435 var self = this;
michael@0 2436 var container = this.container = document.getElementById('viewerContainer');
michael@0 2437 this.pageViewScroll = {};
michael@0 2438 this.watchScroll(container, this.pageViewScroll, updateViewarea);
michael@0 2439
michael@0 2440 var thumbnailContainer = this.thumbnailContainer =
michael@0 2441 document.getElementById('thumbnailView');
michael@0 2442 this.thumbnailViewScroll = {};
michael@0 2443 this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
michael@0 2444 this.renderHighestPriority.bind(this));
michael@0 2445
michael@0 2446 Preferences.initialize();
michael@0 2447
michael@0 2448 PDFFindBar.initialize({
michael@0 2449 bar: document.getElementById('findbar'),
michael@0 2450 toggleButton: document.getElementById('viewFind'),
michael@0 2451 findField: document.getElementById('findInput'),
michael@0 2452 highlightAllCheckbox: document.getElementById('findHighlightAll'),
michael@0 2453 caseSensitiveCheckbox: document.getElementById('findMatchCase'),
michael@0 2454 findMsg: document.getElementById('findMsg'),
michael@0 2455 findStatusIcon: document.getElementById('findStatusIcon'),
michael@0 2456 findPreviousButton: document.getElementById('findPrevious'),
michael@0 2457 findNextButton: document.getElementById('findNext')
michael@0 2458 });
michael@0 2459
michael@0 2460 PDFFindController.initialize({
michael@0 2461 pdfPageSource: this,
michael@0 2462 integratedFind: this.supportsIntegratedFind
michael@0 2463 });
michael@0 2464
michael@0 2465 HandTool.initialize({
michael@0 2466 container: container,
michael@0 2467 toggleHandTool: document.getElementById('toggleHandTool')
michael@0 2468 });
michael@0 2469
michael@0 2470 SecondaryToolbar.initialize({
michael@0 2471 toolbar: document.getElementById('secondaryToolbar'),
michael@0 2472 presentationMode: PresentationMode,
michael@0 2473 toggleButton: document.getElementById('secondaryToolbarToggle'),
michael@0 2474 presentationModeButton:
michael@0 2475 document.getElementById('secondaryPresentationMode'),
michael@0 2476 openFile: document.getElementById('secondaryOpenFile'),
michael@0 2477 print: document.getElementById('secondaryPrint'),
michael@0 2478 download: document.getElementById('secondaryDownload'),
michael@0 2479 viewBookmark: document.getElementById('secondaryViewBookmark'),
michael@0 2480 firstPage: document.getElementById('firstPage'),
michael@0 2481 lastPage: document.getElementById('lastPage'),
michael@0 2482 pageRotateCw: document.getElementById('pageRotateCw'),
michael@0 2483 pageRotateCcw: document.getElementById('pageRotateCcw'),
michael@0 2484 documentProperties: DocumentProperties,
michael@0 2485 documentPropertiesButton: document.getElementById('documentProperties')
michael@0 2486 });
michael@0 2487
michael@0 2488 PasswordPrompt.initialize({
michael@0 2489 overlayContainer: document.getElementById('overlayContainer'),
michael@0 2490 passwordField: document.getElementById('password'),
michael@0 2491 passwordText: document.getElementById('passwordText'),
michael@0 2492 passwordSubmit: document.getElementById('passwordSubmit'),
michael@0 2493 passwordCancel: document.getElementById('passwordCancel')
michael@0 2494 });
michael@0 2495
michael@0 2496 PresentationMode.initialize({
michael@0 2497 container: container,
michael@0 2498 secondaryToolbar: SecondaryToolbar,
michael@0 2499 firstPage: document.getElementById('contextFirstPage'),
michael@0 2500 lastPage: document.getElementById('contextLastPage'),
michael@0 2501 pageRotateCw: document.getElementById('contextPageRotateCw'),
michael@0 2502 pageRotateCcw: document.getElementById('contextPageRotateCcw')
michael@0 2503 });
michael@0 2504
michael@0 2505 DocumentProperties.initialize({
michael@0 2506 overlayContainer: document.getElementById('overlayContainer'),
michael@0 2507 closeButton: document.getElementById('documentPropertiesClose'),
michael@0 2508 fileNameField: document.getElementById('fileNameField'),
michael@0 2509 fileSizeField: document.getElementById('fileSizeField'),
michael@0 2510 titleField: document.getElementById('titleField'),
michael@0 2511 authorField: document.getElementById('authorField'),
michael@0 2512 subjectField: document.getElementById('subjectField'),
michael@0 2513 keywordsField: document.getElementById('keywordsField'),
michael@0 2514 creationDateField: document.getElementById('creationDateField'),
michael@0 2515 modificationDateField: document.getElementById('modificationDateField'),
michael@0 2516 creatorField: document.getElementById('creatorField'),
michael@0 2517 producerField: document.getElementById('producerField'),
michael@0 2518 versionField: document.getElementById('versionField'),
michael@0 2519 pageCountField: document.getElementById('pageCountField')
michael@0 2520 });
michael@0 2521
michael@0 2522 container.addEventListener('scroll', function() {
michael@0 2523 self.lastScroll = Date.now();
michael@0 2524 }, false);
michael@0 2525
michael@0 2526 var initializedPromise = Promise.all([
michael@0 2527 Preferences.get('enableWebGL').then(function (value) {
michael@0 2528 PDFJS.disableWebGL = !value;
michael@0 2529 })
michael@0 2530 // TODO move more preferences and other async stuff here
michael@0 2531 ]);
michael@0 2532
michael@0 2533 return initializedPromise.then(function () {
michael@0 2534 PDFView.initialized = true;
michael@0 2535 });
michael@0 2536 },
michael@0 2537
michael@0 2538 getPage: function pdfViewGetPage(n) {
michael@0 2539 return this.pdfDocument.getPage(n);
michael@0 2540 },
michael@0 2541
michael@0 2542 // Helper function to keep track whether a div was scrolled up or down and
michael@0 2543 // then call a callback.
michael@0 2544 watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
michael@0 2545 state.down = true;
michael@0 2546 state.lastY = viewAreaElement.scrollTop;
michael@0 2547 viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) {
michael@0 2548 var currentY = viewAreaElement.scrollTop;
michael@0 2549 var lastY = state.lastY;
michael@0 2550 if (currentY > lastY) {
michael@0 2551 state.down = true;
michael@0 2552 } else if (currentY < lastY) {
michael@0 2553 state.down = false;
michael@0 2554 }
michael@0 2555 // else do nothing and use previous value
michael@0 2556 state.lastY = currentY;
michael@0 2557 callback();
michael@0 2558 }, true);
michael@0 2559 },
michael@0 2560
michael@0 2561 _setScaleUpdatePages: function pdfView_setScaleUpdatePages(
michael@0 2562 newScale, newValue, resetAutoSettings, noScroll) {
michael@0 2563 this.currentScaleValue = newValue;
michael@0 2564 if (newScale === this.currentScale) {
michael@0 2565 return;
michael@0 2566 }
michael@0 2567 for (var i = 0, ii = this.pages.length; i < ii; i++) {
michael@0 2568 this.pages[i].update(newScale);
michael@0 2569 }
michael@0 2570 this.currentScale = newScale;
michael@0 2571
michael@0 2572 if (!noScroll) {
michael@0 2573 var page = this.page, dest;
michael@0 2574 if (this.currentPosition && !IGNORE_CURRENT_POSITION_ON_ZOOM) {
michael@0 2575 page = this.currentPosition.page;
michael@0 2576 dest = [null, { name: 'XYZ' }, this.currentPosition.left,
michael@0 2577 this.currentPosition.top, null];
michael@0 2578 }
michael@0 2579 this.pages[page - 1].scrollIntoView(dest);
michael@0 2580 }
michael@0 2581 var event = document.createEvent('UIEvents');
michael@0 2582 event.initUIEvent('scalechange', false, false, window, 0);
michael@0 2583 event.scale = newScale;
michael@0 2584 event.resetAutoSettings = resetAutoSettings;
michael@0 2585 window.dispatchEvent(event);
michael@0 2586 },
michael@0 2587
michael@0 2588 setScale: function pdfViewSetScale(value, resetAutoSettings, noScroll) {
michael@0 2589 if (value === 'custom') {
michael@0 2590 return;
michael@0 2591 }
michael@0 2592 var scale = parseFloat(value);
michael@0 2593
michael@0 2594 if (scale > 0) {
michael@0 2595 this._setScaleUpdatePages(scale, value, true, noScroll);
michael@0 2596 } else {
michael@0 2597 var currentPage = this.pages[this.page - 1];
michael@0 2598 if (!currentPage) {
michael@0 2599 return;
michael@0 2600 }
michael@0 2601 var hPadding = PresentationMode.active ? 0 : SCROLLBAR_PADDING;
michael@0 2602 var vPadding = PresentationMode.active ? 0 : VERTICAL_PADDING;
michael@0 2603 var pageWidthScale = (this.container.clientWidth - hPadding) /
michael@0 2604 currentPage.width * currentPage.scale;
michael@0 2605 var pageHeightScale = (this.container.clientHeight - vPadding) /
michael@0 2606 currentPage.height * currentPage.scale;
michael@0 2607 switch (value) {
michael@0 2608 case 'page-actual':
michael@0 2609 scale = 1;
michael@0 2610 break;
michael@0 2611 case 'page-width':
michael@0 2612 scale = pageWidthScale;
michael@0 2613 break;
michael@0 2614 case 'page-height':
michael@0 2615 scale = pageHeightScale;
michael@0 2616 break;
michael@0 2617 case 'page-fit':
michael@0 2618 scale = Math.min(pageWidthScale, pageHeightScale);
michael@0 2619 break;
michael@0 2620 case 'auto':
michael@0 2621 scale = Math.min(MAX_AUTO_SCALE, pageWidthScale);
michael@0 2622 break;
michael@0 2623 default:
michael@0 2624 console.error('pdfViewSetScale: \'' + value +
michael@0 2625 '\' is an unknown zoom value.');
michael@0 2626 return;
michael@0 2627 }
michael@0 2628 this._setScaleUpdatePages(scale, value, resetAutoSettings, noScroll);
michael@0 2629
michael@0 2630 selectScaleOption(value);
michael@0 2631 }
michael@0 2632 },
michael@0 2633
michael@0 2634 zoomIn: function pdfViewZoomIn(ticks) {
michael@0 2635 var newScale = this.currentScale;
michael@0 2636 do {
michael@0 2637 newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
michael@0 2638 newScale = Math.ceil(newScale * 10) / 10;
michael@0 2639 newScale = Math.min(MAX_SCALE, newScale);
michael@0 2640 } while (--ticks && newScale < MAX_SCALE);
michael@0 2641 this.setScale(newScale, true);
michael@0 2642 },
michael@0 2643
michael@0 2644 zoomOut: function pdfViewZoomOut(ticks) {
michael@0 2645 var newScale = this.currentScale;
michael@0 2646 do {
michael@0 2647 newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
michael@0 2648 newScale = Math.floor(newScale * 10) / 10;
michael@0 2649 newScale = Math.max(MIN_SCALE, newScale);
michael@0 2650 } while (--ticks && newScale > MIN_SCALE);
michael@0 2651 this.setScale(newScale, true);
michael@0 2652 },
michael@0 2653
michael@0 2654 set page(val) {
michael@0 2655 var pages = this.pages;
michael@0 2656 var event = document.createEvent('UIEvents');
michael@0 2657 event.initUIEvent('pagechange', false, false, window, 0);
michael@0 2658
michael@0 2659 if (!(0 < val && val <= pages.length)) {
michael@0 2660 this.previousPageNumber = val;
michael@0 2661 event.pageNumber = this.page;
michael@0 2662 window.dispatchEvent(event);
michael@0 2663 return;
michael@0 2664 }
michael@0 2665
michael@0 2666 pages[val - 1].updateStats();
michael@0 2667 this.previousPageNumber = currentPageNumber;
michael@0 2668 currentPageNumber = val;
michael@0 2669 event.pageNumber = val;
michael@0 2670 window.dispatchEvent(event);
michael@0 2671
michael@0 2672 // checking if the this.page was called from the updateViewarea function:
michael@0 2673 // avoiding the creation of two "set page" method (internal and public)
michael@0 2674 if (updateViewarea.inProgress) {
michael@0 2675 return;
michael@0 2676 }
michael@0 2677 // Avoid scrolling the first page during loading
michael@0 2678 if (this.loading && val === 1) {
michael@0 2679 return;
michael@0 2680 }
michael@0 2681 pages[val - 1].scrollIntoView();
michael@0 2682 },
michael@0 2683
michael@0 2684 get page() {
michael@0 2685 return currentPageNumber;
michael@0 2686 },
michael@0 2687
michael@0 2688 get supportsPrinting() {
michael@0 2689 var canvas = document.createElement('canvas');
michael@0 2690 var value = 'mozPrintCallback' in canvas;
michael@0 2691 // shadow
michael@0 2692 Object.defineProperty(this, 'supportsPrinting', { value: value,
michael@0 2693 enumerable: true,
michael@0 2694 configurable: true,
michael@0 2695 writable: false });
michael@0 2696 return value;
michael@0 2697 },
michael@0 2698
michael@0 2699 get supportsFullscreen() {
michael@0 2700 var doc = document.documentElement;
michael@0 2701 var support = doc.requestFullscreen || doc.mozRequestFullScreen ||
michael@0 2702 doc.webkitRequestFullScreen || doc.msRequestFullscreen;
michael@0 2703
michael@0 2704 if (document.fullscreenEnabled === false ||
michael@0 2705 document.mozFullScreenEnabled === false ||
michael@0 2706 document.webkitFullscreenEnabled === false ||
michael@0 2707 document.msFullscreenEnabled === false) {
michael@0 2708 support = false;
michael@0 2709 }
michael@0 2710
michael@0 2711 Object.defineProperty(this, 'supportsFullscreen', { value: support,
michael@0 2712 enumerable: true,
michael@0 2713 configurable: true,
michael@0 2714 writable: false });
michael@0 2715 return support;
michael@0 2716 },
michael@0 2717
michael@0 2718 get supportsIntegratedFind() {
michael@0 2719 var support = false;
michael@0 2720 support = FirefoxCom.requestSync('supportsIntegratedFind');
michael@0 2721 Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
michael@0 2722 enumerable: true,
michael@0 2723 configurable: true,
michael@0 2724 writable: false });
michael@0 2725 return support;
michael@0 2726 },
michael@0 2727
michael@0 2728 get supportsDocumentFonts() {
michael@0 2729 var support = true;
michael@0 2730 support = FirefoxCom.requestSync('supportsDocumentFonts');
michael@0 2731 Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
michael@0 2732 enumerable: true,
michael@0 2733 configurable: true,
michael@0 2734 writable: false });
michael@0 2735 return support;
michael@0 2736 },
michael@0 2737
michael@0 2738 get supportsDocumentColors() {
michael@0 2739 var support = true;
michael@0 2740 support = FirefoxCom.requestSync('supportsDocumentColors');
michael@0 2741 Object.defineProperty(this, 'supportsDocumentColors', { value: support,
michael@0 2742 enumerable: true,
michael@0 2743 configurable: true,
michael@0 2744 writable: false });
michael@0 2745 return support;
michael@0 2746 },
michael@0 2747
michael@0 2748 get loadingBar() {
michael@0 2749 var bar = new ProgressBar('#loadingBar', {});
michael@0 2750 Object.defineProperty(this, 'loadingBar', { value: bar,
michael@0 2751 enumerable: true,
michael@0 2752 configurable: true,
michael@0 2753 writable: false });
michael@0 2754 return bar;
michael@0 2755 },
michael@0 2756
michael@0 2757 get isHorizontalScrollbarEnabled() {
michael@0 2758 return (PresentationMode.active ? false :
michael@0 2759 (this.container.scrollWidth > this.container.clientWidth));
michael@0 2760 },
michael@0 2761
michael@0 2762 initPassiveLoading: function pdfViewInitPassiveLoading() {
michael@0 2763 var pdfDataRangeTransport = {
michael@0 2764 rangeListeners: [],
michael@0 2765 progressListeners: [],
michael@0 2766
michael@0 2767 addRangeListener: function PdfDataRangeTransport_addRangeListener(
michael@0 2768 listener) {
michael@0 2769 this.rangeListeners.push(listener);
michael@0 2770 },
michael@0 2771
michael@0 2772 addProgressListener: function PdfDataRangeTransport_addProgressListener(
michael@0 2773 listener) {
michael@0 2774 this.progressListeners.push(listener);
michael@0 2775 },
michael@0 2776
michael@0 2777 onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) {
michael@0 2778 var listeners = this.rangeListeners;
michael@0 2779 for (var i = 0, n = listeners.length; i < n; ++i) {
michael@0 2780 listeners[i](begin, chunk);
michael@0 2781 }
michael@0 2782 },
michael@0 2783
michael@0 2784 onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) {
michael@0 2785 var listeners = this.progressListeners;
michael@0 2786 for (var i = 0, n = listeners.length; i < n; ++i) {
michael@0 2787 listeners[i](loaded);
michael@0 2788 }
michael@0 2789 },
michael@0 2790
michael@0 2791 requestDataRange: function PdfDataRangeTransport_requestDataRange(
michael@0 2792 begin, end) {
michael@0 2793 FirefoxCom.request('requestDataRange', { begin: begin, end: end });
michael@0 2794 }
michael@0 2795 };
michael@0 2796
michael@0 2797 window.addEventListener('message', function windowMessage(e) {
michael@0 2798 if (e.source !== null) {
michael@0 2799 // The message MUST originate from Chrome code.
michael@0 2800 console.warn('Rejected untrusted message from ' + e.origin);
michael@0 2801 return;
michael@0 2802 }
michael@0 2803 var args = e.data;
michael@0 2804
michael@0 2805 if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) {
michael@0 2806 return;
michael@0 2807 }
michael@0 2808 switch (args.pdfjsLoadAction) {
michael@0 2809 case 'supportsRangedLoading':
michael@0 2810 PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, {
michael@0 2811 length: args.length,
michael@0 2812 initialData: args.data
michael@0 2813 });
michael@0 2814 break;
michael@0 2815 case 'range':
michael@0 2816 pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
michael@0 2817 break;
michael@0 2818 case 'rangeProgress':
michael@0 2819 pdfDataRangeTransport.onDataProgress(args.loaded);
michael@0 2820 break;
michael@0 2821 case 'progress':
michael@0 2822 PDFView.progress(args.loaded / args.total);
michael@0 2823 break;
michael@0 2824 case 'complete':
michael@0 2825 if (!args.data) {
michael@0 2826 PDFView.error(mozL10n.get('loading_error', null,
michael@0 2827 'An error occurred while loading the PDF.'), e);
michael@0 2828 break;
michael@0 2829 }
michael@0 2830 PDFView.open(args.data, 0);
michael@0 2831 break;
michael@0 2832 }
michael@0 2833 });
michael@0 2834 FirefoxCom.requestSync('initPassiveLoading', null);
michael@0 2835 },
michael@0 2836
michael@0 2837 setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
michael@0 2838 this.url = url;
michael@0 2839 try {
michael@0 2840 this.setTitle(decodeURIComponent(getFileName(url)) || url);
michael@0 2841 } catch (e) {
michael@0 2842 // decodeURIComponent may throw URIError,
michael@0 2843 // fall back to using the unprocessed url in that case
michael@0 2844 this.setTitle(url);
michael@0 2845 }
michael@0 2846 },
michael@0 2847
michael@0 2848 setTitle: function pdfViewSetTitle(title) {
michael@0 2849 document.title = title;
michael@0 2850 },
michael@0 2851
michael@0 2852 close: function pdfViewClose() {
michael@0 2853 var errorWrapper = document.getElementById('errorWrapper');
michael@0 2854 errorWrapper.setAttribute('hidden', 'true');
michael@0 2855
michael@0 2856 if (!this.pdfDocument) {
michael@0 2857 return;
michael@0 2858 }
michael@0 2859
michael@0 2860 this.pdfDocument.destroy();
michael@0 2861 this.pdfDocument = null;
michael@0 2862
michael@0 2863 var thumbsView = document.getElementById('thumbnailView');
michael@0 2864 while (thumbsView.hasChildNodes()) {
michael@0 2865 thumbsView.removeChild(thumbsView.lastChild);
michael@0 2866 }
michael@0 2867
michael@0 2868 if ('_loadingInterval' in thumbsView) {
michael@0 2869 clearInterval(thumbsView._loadingInterval);
michael@0 2870 }
michael@0 2871
michael@0 2872 var container = document.getElementById('viewer');
michael@0 2873 while (container.hasChildNodes()) {
michael@0 2874 container.removeChild(container.lastChild);
michael@0 2875 }
michael@0 2876
michael@0 2877 if (typeof PDFBug !== 'undefined') {
michael@0 2878 PDFBug.cleanup();
michael@0 2879 }
michael@0 2880 },
michael@0 2881
michael@0 2882 // TODO(mack): This function signature should really be pdfViewOpen(url, args)
michael@0 2883 open: function pdfViewOpen(url, scale, password,
michael@0 2884 pdfDataRangeTransport, args) {
michael@0 2885 if (this.pdfDocument) {
michael@0 2886 // Reload the preferences if a document was previously opened.
michael@0 2887 Preferences.reload();
michael@0 2888 }
michael@0 2889 this.close();
michael@0 2890
michael@0 2891 var parameters = {password: password};
michael@0 2892 if (typeof url === 'string') { // URL
michael@0 2893 this.setTitleUsingUrl(url);
michael@0 2894 parameters.url = url;
michael@0 2895 } else if (url && 'byteLength' in url) { // ArrayBuffer
michael@0 2896 parameters.data = url;
michael@0 2897 }
michael@0 2898 if (args) {
michael@0 2899 for (var prop in args) {
michael@0 2900 parameters[prop] = args[prop];
michael@0 2901 }
michael@0 2902 }
michael@0 2903
michael@0 2904 var self = this;
michael@0 2905 self.loading = true;
michael@0 2906 self.downloadComplete = false;
michael@0 2907
michael@0 2908 var passwordNeeded = function passwordNeeded(updatePassword, reason) {
michael@0 2909 PasswordPrompt.updatePassword = updatePassword;
michael@0 2910 PasswordPrompt.reason = reason;
michael@0 2911 PasswordPrompt.show();
michael@0 2912 };
michael@0 2913
michael@0 2914 function getDocumentProgress(progressData) {
michael@0 2915 self.progress(progressData.loaded / progressData.total);
michael@0 2916 }
michael@0 2917
michael@0 2918 PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
michael@0 2919 getDocumentProgress).then(
michael@0 2920 function getDocumentCallback(pdfDocument) {
michael@0 2921 self.load(pdfDocument, scale);
michael@0 2922 self.loading = false;
michael@0 2923 },
michael@0 2924 function getDocumentError(message, exception) {
michael@0 2925 var loadingErrorMessage = mozL10n.get('loading_error', null,
michael@0 2926 'An error occurred while loading the PDF.');
michael@0 2927
michael@0 2928 if (exception && exception.name === 'InvalidPDFException') {
michael@0 2929 // change error message also for other builds
michael@0 2930 loadingErrorMessage = mozL10n.get('invalid_file_error', null,
michael@0 2931 'Invalid or corrupted PDF file.');
michael@0 2932 }
michael@0 2933
michael@0 2934 if (exception && exception.name === 'MissingPDFException') {
michael@0 2935 // special message for missing PDF's
michael@0 2936 loadingErrorMessage = mozL10n.get('missing_file_error', null,
michael@0 2937 'Missing PDF file.');
michael@0 2938
michael@0 2939 }
michael@0 2940
michael@0 2941 var moreInfo = {
michael@0 2942 message: message
michael@0 2943 };
michael@0 2944 self.error(loadingErrorMessage, moreInfo);
michael@0 2945 self.loading = false;
michael@0 2946 }
michael@0 2947 );
michael@0 2948 },
michael@0 2949
michael@0 2950 download: function pdfViewDownload() {
michael@0 2951 function downloadByUrl() {
michael@0 2952 downloadManager.downloadUrl(url, filename);
michael@0 2953 }
michael@0 2954
michael@0 2955 var url = this.url.split('#')[0];
michael@0 2956 var filename = getPDFFileNameFromURL(url);
michael@0 2957 var downloadManager = new DownloadManager();
michael@0 2958 downloadManager.onerror = function (err) {
michael@0 2959 // This error won't really be helpful because it's likely the
michael@0 2960 // fallback won't work either (or is already open).
michael@0 2961 PDFView.error('PDF failed to download.');
michael@0 2962 };
michael@0 2963
michael@0 2964 if (!this.pdfDocument) { // the PDF is not ready yet
michael@0 2965 downloadByUrl();
michael@0 2966 return;
michael@0 2967 }
michael@0 2968
michael@0 2969 if (!this.downloadComplete) { // the PDF is still downloading
michael@0 2970 downloadByUrl();
michael@0 2971 return;
michael@0 2972 }
michael@0 2973
michael@0 2974 this.pdfDocument.getData().then(
michael@0 2975 function getDataSuccess(data) {
michael@0 2976 var blob = PDFJS.createBlob(data, 'application/pdf');
michael@0 2977 downloadManager.download(blob, url, filename);
michael@0 2978 },
michael@0 2979 downloadByUrl // Error occurred try downloading with just the url.
michael@0 2980 ).then(null, downloadByUrl);
michael@0 2981 },
michael@0 2982
michael@0 2983 fallback: function pdfViewFallback(featureId) {
michael@0 2984 // Only trigger the fallback once so we don't spam the user with messages
michael@0 2985 // for one PDF.
michael@0 2986 if (this.fellback)
michael@0 2987 return;
michael@0 2988 this.fellback = true;
michael@0 2989 var url = this.url.split('#')[0];
michael@0 2990 FirefoxCom.request('fallback', { featureId: featureId, url: url },
michael@0 2991 function response(download) {
michael@0 2992 if (!download) {
michael@0 2993 return;
michael@0 2994 }
michael@0 2995 PDFView.download();
michael@0 2996 });
michael@0 2997 },
michael@0 2998
michael@0 2999 navigateTo: function pdfViewNavigateTo(dest) {
michael@0 3000 var destString = '';
michael@0 3001 var self = this;
michael@0 3002
michael@0 3003 var goToDestination = function(destRef) {
michael@0 3004 self.pendingRefStr = null;
michael@0 3005 // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
michael@0 3006 var pageNumber = destRef instanceof Object ?
michael@0 3007 self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
michael@0 3008 (destRef + 1);
michael@0 3009 if (pageNumber) {
michael@0 3010 if (pageNumber > self.pages.length) {
michael@0 3011 pageNumber = self.pages.length;
michael@0 3012 }
michael@0 3013 var currentPage = self.pages[pageNumber - 1];
michael@0 3014 currentPage.scrollIntoView(dest);
michael@0 3015
michael@0 3016 // Update the browsing history.
michael@0 3017 PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
michael@0 3018 } else {
michael@0 3019 self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
michael@0 3020 var pageNum = pageIndex + 1;
michael@0 3021 self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
michael@0 3022 goToDestination(destRef);
michael@0 3023 });
michael@0 3024 }
michael@0 3025 };
michael@0 3026
michael@0 3027 this.destinationsPromise.then(function() {
michael@0 3028 if (typeof dest === 'string') {
michael@0 3029 destString = dest;
michael@0 3030 dest = self.destinations[dest];
michael@0 3031 }
michael@0 3032 if (!(dest instanceof Array)) {
michael@0 3033 return; // invalid destination
michael@0 3034 }
michael@0 3035 goToDestination(dest[0]);
michael@0 3036 });
michael@0 3037 },
michael@0 3038
michael@0 3039 getDestinationHash: function pdfViewGetDestinationHash(dest) {
michael@0 3040 if (typeof dest === 'string') {
michael@0 3041 return PDFView.getAnchorUrl('#' + escape(dest));
michael@0 3042 }
michael@0 3043 if (dest instanceof Array) {
michael@0 3044 var destRef = dest[0]; // see navigateTo method for dest format
michael@0 3045 var pageNumber = destRef instanceof Object ?
michael@0 3046 this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
michael@0 3047 (destRef + 1);
michael@0 3048 if (pageNumber) {
michael@0 3049 var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
michael@0 3050 var destKind = dest[1];
michael@0 3051 if (typeof destKind === 'object' && 'name' in destKind &&
michael@0 3052 destKind.name == 'XYZ') {
michael@0 3053 var scale = (dest[4] || this.currentScaleValue);
michael@0 3054 var scaleNumber = parseFloat(scale);
michael@0 3055 if (scaleNumber) {
michael@0 3056 scale = scaleNumber * 100;
michael@0 3057 }
michael@0 3058 pdfOpenParams += '&zoom=' + scale;
michael@0 3059 if (dest[2] || dest[3]) {
michael@0 3060 pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
michael@0 3061 }
michael@0 3062 }
michael@0 3063 return pdfOpenParams;
michael@0 3064 }
michael@0 3065 }
michael@0 3066 return '';
michael@0 3067 },
michael@0 3068
michael@0 3069 /**
michael@0 3070 * Prefix the full url on anchor links to make sure that links are resolved
michael@0 3071 * relative to the current URL instead of the one defined in <base href>.
michael@0 3072 * @param {String} anchor The anchor hash, including the #.
michael@0 3073 */
michael@0 3074 getAnchorUrl: function getAnchorUrl(anchor) {
michael@0 3075 return this.url.split('#')[0] + anchor;
michael@0 3076 },
michael@0 3077
michael@0 3078 /**
michael@0 3079 * Show the error box.
michael@0 3080 * @param {String} message A message that is human readable.
michael@0 3081 * @param {Object} moreInfo (optional) Further information about the error
michael@0 3082 * that is more technical. Should have a 'message'
michael@0 3083 * and optionally a 'stack' property.
michael@0 3084 */
michael@0 3085 error: function pdfViewError(message, moreInfo) {
michael@0 3086 var moreInfoText = mozL10n.get('error_version_info',
michael@0 3087 {version: PDFJS.version || '?', build: PDFJS.build || '?'},
michael@0 3088 'PDF.js v{{version}} (build: {{build}})') + '\n';
michael@0 3089 if (moreInfo) {
michael@0 3090 moreInfoText +=
michael@0 3091 mozL10n.get('error_message', {message: moreInfo.message},
michael@0 3092 'Message: {{message}}');
michael@0 3093 if (moreInfo.stack) {
michael@0 3094 moreInfoText += '\n' +
michael@0 3095 mozL10n.get('error_stack', {stack: moreInfo.stack},
michael@0 3096 'Stack: {{stack}}');
michael@0 3097 } else {
michael@0 3098 if (moreInfo.filename) {
michael@0 3099 moreInfoText += '\n' +
michael@0 3100 mozL10n.get('error_file', {file: moreInfo.filename},
michael@0 3101 'File: {{file}}');
michael@0 3102 }
michael@0 3103 if (moreInfo.lineNumber) {
michael@0 3104 moreInfoText += '\n' +
michael@0 3105 mozL10n.get('error_line', {line: moreInfo.lineNumber},
michael@0 3106 'Line: {{line}}');
michael@0 3107 }
michael@0 3108 }
michael@0 3109 }
michael@0 3110
michael@0 3111 console.error(message + '\n' + moreInfoText);
michael@0 3112 this.fallback();
michael@0 3113 },
michael@0 3114
michael@0 3115 progress: function pdfViewProgress(level) {
michael@0 3116 var percent = Math.round(level * 100);
michael@0 3117 // When we transition from full request to range requests, it's possible
michael@0 3118 // that we discard some of the loaded data. This can cause the loading
michael@0 3119 // bar to move backwards. So prevent this by only updating the bar if it
michael@0 3120 // increases.
michael@0 3121 if (percent > PDFView.loadingBar.percent) {
michael@0 3122 PDFView.loadingBar.percent = percent;
michael@0 3123 }
michael@0 3124 },
michael@0 3125
michael@0 3126 load: function pdfViewLoad(pdfDocument, scale) {
michael@0 3127 var self = this;
michael@0 3128 var isOnePageRenderedResolved = false;
michael@0 3129 var resolveOnePageRendered = null;
michael@0 3130 var onePageRendered = new Promise(function (resolve) {
michael@0 3131 resolveOnePageRendered = resolve;
michael@0 3132 });
michael@0 3133 function bindOnAfterDraw(pageView, thumbnailView) {
michael@0 3134 // when page is painted, using the image as thumbnail base
michael@0 3135 pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
michael@0 3136 if (!isOnePageRenderedResolved) {
michael@0 3137 isOnePageRenderedResolved = true;
michael@0 3138 resolveOnePageRendered();
michael@0 3139 }
michael@0 3140 thumbnailView.setImage(pageView.canvas);
michael@0 3141 };
michael@0 3142 }
michael@0 3143
michael@0 3144 PDFFindController.reset();
michael@0 3145
michael@0 3146 this.pdfDocument = pdfDocument;
michael@0 3147
michael@0 3148 DocumentProperties.resolveDataAvailable();
michael@0 3149
michael@0 3150 var downloadedPromise = pdfDocument.getDownloadInfo().then(function() {
michael@0 3151 self.downloadComplete = true;
michael@0 3152 PDFView.loadingBar.hide();
michael@0 3153 var outerContainer = document.getElementById('outerContainer');
michael@0 3154 outerContainer.classList.remove('loadingInProgress');
michael@0 3155 });
michael@0 3156
michael@0 3157 var pagesCount = pdfDocument.numPages;
michael@0 3158
michael@0 3159 var id = pdfDocument.fingerprint;
michael@0 3160 document.getElementById('numPages').textContent =
michael@0 3161 mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
michael@0 3162 document.getElementById('pageNumber').max = pagesCount;
michael@0 3163
michael@0 3164 PDFView.documentFingerprint = id;
michael@0 3165 var store = PDFView.store = new ViewHistory(id);
michael@0 3166
michael@0 3167 this.pageRotation = 0;
michael@0 3168
michael@0 3169 var pages = this.pages = [];
michael@0 3170 var pagesRefMap = this.pagesRefMap = {};
michael@0 3171 var thumbnails = this.thumbnails = [];
michael@0 3172
michael@0 3173 var resolvePagesPromise;
michael@0 3174 var pagesPromise = new Promise(function (resolve) {
michael@0 3175 resolvePagesPromise = resolve;
michael@0 3176 });
michael@0 3177 this.pagesPromise = pagesPromise;
michael@0 3178
michael@0 3179 var firstPagePromise = pdfDocument.getPage(1);
michael@0 3180 var container = document.getElementById('viewer');
michael@0 3181 var thumbsView = document.getElementById('thumbnailView');
michael@0 3182
michael@0 3183 // Fetch a single page so we can get a viewport that will be the default
michael@0 3184 // viewport for all pages
michael@0 3185 firstPagePromise.then(function(pdfPage) {
michael@0 3186 var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS);
michael@0 3187 for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
michael@0 3188 var viewportClone = viewport.clone();
michael@0 3189 var pageView = new PageView(container, pageNum, scale,
michael@0 3190 self.navigateTo.bind(self),
michael@0 3191 viewportClone);
michael@0 3192 var thumbnailView = new ThumbnailView(thumbsView, pageNum,
michael@0 3193 viewportClone);
michael@0 3194 bindOnAfterDraw(pageView, thumbnailView);
michael@0 3195 pages.push(pageView);
michael@0 3196 thumbnails.push(thumbnailView);
michael@0 3197 }
michael@0 3198
michael@0 3199 // Fetch all the pages since the viewport is needed before printing
michael@0 3200 // starts to create the correct size canvas. Wait until one page is
michael@0 3201 // rendered so we don't tie up too many resources early on.
michael@0 3202 onePageRendered.then(function () {
michael@0 3203 if (!PDFJS.disableAutoFetch) {
michael@0 3204 var getPagesLeft = pagesCount;
michael@0 3205 for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
michael@0 3206 pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
michael@0 3207 var pageView = pages[pageNum - 1];
michael@0 3208 if (!pageView.pdfPage) {
michael@0 3209 pageView.setPdfPage(pdfPage);
michael@0 3210 }
michael@0 3211 var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
michael@0 3212 pagesRefMap[refStr] = pageNum;
michael@0 3213 getPagesLeft--;
michael@0 3214 if (!getPagesLeft) {
michael@0 3215 resolvePagesPromise();
michael@0 3216 }
michael@0 3217 }.bind(null, pageNum));
michael@0 3218 }
michael@0 3219 } else {
michael@0 3220 // XXX: Printing is semi-broken with auto fetch disabled.
michael@0 3221 resolvePagesPromise();
michael@0 3222 }
michael@0 3223 });
michael@0 3224
michael@0 3225 downloadedPromise.then(function () {
michael@0 3226 var event = document.createEvent('CustomEvent');
michael@0 3227 event.initCustomEvent('documentload', true, true, {});
michael@0 3228 window.dispatchEvent(event);
michael@0 3229 });
michael@0 3230
michael@0 3231 PDFView.loadingBar.setWidth(container);
michael@0 3232
michael@0 3233 PDFFindController.resolveFirstPage();
michael@0 3234
michael@0 3235 // Initialize the browsing history.
michael@0 3236 PDFHistory.initialize(self.documentFingerprint);
michael@0 3237 });
michael@0 3238
michael@0 3239 // Fetch the necessary preference values.
michael@0 3240 var showPreviousViewOnLoad;
michael@0 3241 var showPreviousViewOnLoadPromise =
michael@0 3242 Preferences.get('showPreviousViewOnLoad').then(function (prefValue) {
michael@0 3243 showPreviousViewOnLoad = prefValue;
michael@0 3244 });
michael@0 3245 var defaultZoomValue;
michael@0 3246 var defaultZoomValuePromise =
michael@0 3247 Preferences.get('defaultZoomValue').then(function (prefValue) {
michael@0 3248 defaultZoomValue = prefValue;
michael@0 3249 });
michael@0 3250
michael@0 3251 var storePromise = store.initializedPromise;
michael@0 3252 Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise,
michael@0 3253 defaultZoomValuePromise]).then(function resolved() {
michael@0 3254 var storedHash = null;
michael@0 3255 if (showPreviousViewOnLoad && store.get('exists', false)) {
michael@0 3256 var pageNum = store.get('page', '1');
michael@0 3257 var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale);
michael@0 3258 var left = store.get('scrollLeft', '0');
michael@0 3259 var top = store.get('scrollTop', '0');
michael@0 3260
michael@0 3261 storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
michael@0 3262 left + ',' + top;
michael@0 3263 } else if (defaultZoomValue) {
michael@0 3264 storedHash = 'page=1&zoom=' + defaultZoomValue;
michael@0 3265 }
michael@0 3266 self.setInitialView(storedHash, scale);
michael@0 3267
michael@0 3268 // Make all navigation keys work on document load,
michael@0 3269 // unless the viewer is embedded in a web page.
michael@0 3270 if (!self.isViewerEmbedded) {
michael@0 3271 self.container.focus();
michael@0 3272 self.container.blur();
michael@0 3273 }
michael@0 3274 }, function rejected(errorMsg) {
michael@0 3275 console.error(errorMsg);
michael@0 3276
michael@0 3277 firstPagePromise.then(function () {
michael@0 3278 self.setInitialView(null, scale);
michael@0 3279 });
michael@0 3280 });
michael@0 3281
michael@0 3282 pagesPromise.then(function() {
michael@0 3283 if (PDFView.supportsPrinting) {
michael@0 3284 pdfDocument.getJavaScript().then(function(javaScript) {
michael@0 3285 if (javaScript.length) {
michael@0 3286 console.warn('Warning: JavaScript is not supported');
michael@0 3287 PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript);
michael@0 3288 }
michael@0 3289 // Hack to support auto printing.
michael@0 3290 var regex = /\bprint\s*\(/g;
michael@0 3291 for (var i = 0, ii = javaScript.length; i < ii; i++) {
michael@0 3292 var js = javaScript[i];
michael@0 3293 if (js && regex.test(js)) {
michael@0 3294 setTimeout(function() {
michael@0 3295 window.print();
michael@0 3296 });
michael@0 3297 return;
michael@0 3298 }
michael@0 3299 }
michael@0 3300 });
michael@0 3301 }
michael@0 3302 });
michael@0 3303
michael@0 3304 var destinationsPromise =
michael@0 3305 this.destinationsPromise = pdfDocument.getDestinations();
michael@0 3306 destinationsPromise.then(function(destinations) {
michael@0 3307 self.destinations = destinations;
michael@0 3308 });
michael@0 3309
michael@0 3310 // outline depends on destinations and pagesRefMap
michael@0 3311 var promises = [pagesPromise, destinationsPromise,
michael@0 3312 PDFView.animationStartedPromise];
michael@0 3313 Promise.all(promises).then(function() {
michael@0 3314 pdfDocument.getOutline().then(function(outline) {
michael@0 3315 self.outline = new DocumentOutlineView(outline);
michael@0 3316 document.getElementById('viewOutline').disabled = !outline;
michael@0 3317
michael@0 3318 if (outline) {
michael@0 3319 Preferences.get('ifAvailableShowOutlineOnLoad').then(
michael@0 3320 function (prefValue) {
michael@0 3321 if (prefValue) {
michael@0 3322 if (!self.sidebarOpen) {
michael@0 3323 document.getElementById('sidebarToggle').click();
michael@0 3324 }
michael@0 3325 self.switchSidebarView('outline');
michael@0 3326 }
michael@0 3327 });
michael@0 3328 }
michael@0 3329 });
michael@0 3330 pdfDocument.getAttachments().then(function(attachments) {
michael@0 3331 self.attachments = new DocumentAttachmentsView(attachments);
michael@0 3332 document.getElementById('viewAttachments').disabled = !attachments;
michael@0 3333 });
michael@0 3334 });
michael@0 3335
michael@0 3336 pdfDocument.getMetadata().then(function(data) {
michael@0 3337 var info = data.info, metadata = data.metadata;
michael@0 3338 self.documentInfo = info;
michael@0 3339 self.metadata = metadata;
michael@0 3340
michael@0 3341 // Provides some basic debug information
michael@0 3342 console.log('PDF ' + pdfDocument.fingerprint + ' [' +
michael@0 3343 info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() +
michael@0 3344 ' / ' + (info.Creator || '-').trim() + ']' +
michael@0 3345 ' (PDF.js: ' + (PDFJS.version || '-') +
michael@0 3346 (!PDFJS.disableWebGL ? ' [WebGL]' : '') + ')');
michael@0 3347
michael@0 3348 var pdfTitle;
michael@0 3349 if (metadata && metadata.has('dc:title')) {
michael@0 3350 pdfTitle = metadata.get('dc:title');
michael@0 3351 }
michael@0 3352
michael@0 3353 if (!pdfTitle && info && info['Title']) {
michael@0 3354 pdfTitle = info['Title'];
michael@0 3355 }
michael@0 3356
michael@0 3357 if (pdfTitle) {
michael@0 3358 self.setTitle(pdfTitle + ' - ' + document.title);
michael@0 3359 }
michael@0 3360
michael@0 3361 if (info.IsAcroFormPresent) {
michael@0 3362 console.warn('Warning: AcroForm/XFA is not supported');
michael@0 3363 PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.forms);
michael@0 3364 }
michael@0 3365
michael@0 3366 var versionId = String(info.PDFFormatVersion).slice(-1) | 0;
michael@0 3367 var generatorId = 0;
michael@0 3368 var KNOWN_GENERATORS = ["acrobat distiller", "acrobat pdfwritter",
michael@0 3369 "adobe livecycle", "adobe pdf library", "adobe photoshop", "ghostscript",
michael@0 3370 "tcpdf", "cairo", "dvipdfm", "dvips", "pdftex", "pdfkit", "itext",
michael@0 3371 "prince", "quarkxpress", "mac os x", "microsoft", "openoffice", "oracle",
michael@0 3372 "luradocument", "pdf-xchange", "antenna house", "aspose.cells", "fpdf"];
michael@0 3373 var generatorId = 0;
michael@0 3374 if (info.Producer) {
michael@0 3375 KNOWN_GENERATORS.some(function (generator, s, i) {
michael@0 3376 if (generator.indexOf(s) < 0) {
michael@0 3377 return false;
michael@0 3378 }
michael@0 3379 generatorId = i + 1;
michael@0 3380 return true;
michael@0 3381 }.bind(null, info.Producer.toLowerCase()));
michael@0 3382 }
michael@0 3383 var formType = !info.IsAcroFormPresent ? null : info.IsXFAPresent ?
michael@0 3384 'xfa' : 'acroform';
michael@0 3385 FirefoxCom.request('reportTelemetry', JSON.stringify({
michael@0 3386 type: 'documentInfo',
michael@0 3387 version: versionId,
michael@0 3388 generator: generatorId,
michael@0 3389 formType: formType
michael@0 3390 }));
michael@0 3391 });
michael@0 3392 },
michael@0 3393
michael@0 3394 setInitialView: function pdfViewSetInitialView(storedHash, scale) {
michael@0 3395 // Reset the current scale, as otherwise the page's scale might not get
michael@0 3396 // updated if the zoom level stayed the same.
michael@0 3397 this.currentScale = 0;
michael@0 3398 this.currentScaleValue = null;
michael@0 3399 // When opening a new file (when one is already loaded in the viewer):
michael@0 3400 // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
michael@0 3401 // if 'currentPageNumber' is larger than the number of pages in the file.
michael@0 3402 document.getElementById('pageNumber').value = currentPageNumber = 1;
michael@0 3403 // Reset the current position when loading a new file,
michael@0 3404 // to prevent displaying the wrong position in the document.
michael@0 3405 this.currentPosition = null;
michael@0 3406
michael@0 3407 if (PDFHistory.initialDestination) {
michael@0 3408 this.navigateTo(PDFHistory.initialDestination);
michael@0 3409 PDFHistory.initialDestination = null;
michael@0 3410 } else if (this.initialBookmark) {
michael@0 3411 this.setHash(this.initialBookmark);
michael@0 3412 PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
michael@0 3413 this.initialBookmark = null;
michael@0 3414 } else if (storedHash) {
michael@0 3415 this.setHash(storedHash);
michael@0 3416 } else if (scale) {
michael@0 3417 this.setScale(scale, true);
michael@0 3418 this.page = 1;
michael@0 3419 }
michael@0 3420
michael@0 3421 if (PDFView.currentScale === UNKNOWN_SCALE) {
michael@0 3422 // Scale was not initialized: invalid bookmark or scale was not specified.
michael@0 3423 // Setting the default one.
michael@0 3424 this.setScale(DEFAULT_SCALE, true);
michael@0 3425 }
michael@0 3426 },
michael@0 3427
michael@0 3428 renderHighestPriority: function pdfViewRenderHighestPriority() {
michael@0 3429 if (PDFView.idleTimeout) {
michael@0 3430 clearTimeout(PDFView.idleTimeout);
michael@0 3431 PDFView.idleTimeout = null;
michael@0 3432 }
michael@0 3433
michael@0 3434 // Pages have a higher priority than thumbnails, so check them first.
michael@0 3435 var visiblePages = this.getVisiblePages();
michael@0 3436 var pageView = this.getHighestPriority(visiblePages, this.pages,
michael@0 3437 this.pageViewScroll.down);
michael@0 3438 if (pageView) {
michael@0 3439 this.renderView(pageView, 'page');
michael@0 3440 return;
michael@0 3441 }
michael@0 3442 // No pages needed rendering so check thumbnails.
michael@0 3443 if (this.sidebarOpen) {
michael@0 3444 var visibleThumbs = this.getVisibleThumbs();
michael@0 3445 var thumbView = this.getHighestPriority(visibleThumbs,
michael@0 3446 this.thumbnails,
michael@0 3447 this.thumbnailViewScroll.down);
michael@0 3448 if (thumbView) {
michael@0 3449 this.renderView(thumbView, 'thumbnail');
michael@0 3450 return;
michael@0 3451 }
michael@0 3452 }
michael@0 3453
michael@0 3454 PDFView.idleTimeout = setTimeout(function () {
michael@0 3455 PDFView.cleanup();
michael@0 3456 }, CLEANUP_TIMEOUT);
michael@0 3457 },
michael@0 3458
michael@0 3459 cleanup: function pdfViewCleanup() {
michael@0 3460 for (var i = 0, ii = this.pages.length; i < ii; i++) {
michael@0 3461 if (this.pages[i] &&
michael@0 3462 this.pages[i].renderingState !== RenderingStates.FINISHED) {
michael@0 3463 this.pages[i].reset();
michael@0 3464 }
michael@0 3465 }
michael@0 3466 this.pdfDocument.cleanup();
michael@0 3467 },
michael@0 3468
michael@0 3469 getHighestPriority: function pdfViewGetHighestPriority(visible, views,
michael@0 3470 scrolledDown) {
michael@0 3471 // The state has changed figure out which page has the highest priority to
michael@0 3472 // render next (if any).
michael@0 3473 // Priority:
michael@0 3474 // 1 visible pages
michael@0 3475 // 2 if last scrolled down page after the visible pages
michael@0 3476 // 2 if last scrolled up page before the visible pages
michael@0 3477 var visibleViews = visible.views;
michael@0 3478
michael@0 3479 var numVisible = visibleViews.length;
michael@0 3480 if (numVisible === 0) {
michael@0 3481 return false;
michael@0 3482 }
michael@0 3483 for (var i = 0; i < numVisible; ++i) {
michael@0 3484 var view = visibleViews[i].view;
michael@0 3485 if (!this.isViewFinished(view)) {
michael@0 3486 return view;
michael@0 3487 }
michael@0 3488 }
michael@0 3489
michael@0 3490 // All the visible views have rendered, try to render next/previous pages.
michael@0 3491 if (scrolledDown) {
michael@0 3492 var nextPageIndex = visible.last.id;
michael@0 3493 // ID's start at 1 so no need to add 1.
michael@0 3494 if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) {
michael@0 3495 return views[nextPageIndex];
michael@0 3496 }
michael@0 3497 } else {
michael@0 3498 var previousPageIndex = visible.first.id - 2;
michael@0 3499 if (views[previousPageIndex] &&
michael@0 3500 !this.isViewFinished(views[previousPageIndex])) {
michael@0 3501 return views[previousPageIndex];
michael@0 3502 }
michael@0 3503 }
michael@0 3504 // Everything that needs to be rendered has been.
michael@0 3505 return false;
michael@0 3506 },
michael@0 3507
michael@0 3508 isViewFinished: function pdfViewIsViewFinished(view) {
michael@0 3509 return view.renderingState === RenderingStates.FINISHED;
michael@0 3510 },
michael@0 3511
michael@0 3512 // Render a page or thumbnail view. This calls the appropriate function based
michael@0 3513 // on the views state. If the view is already rendered it will return false.
michael@0 3514 renderView: function pdfViewRender(view, type) {
michael@0 3515 var state = view.renderingState;
michael@0 3516 switch (state) {
michael@0 3517 case RenderingStates.FINISHED:
michael@0 3518 return false;
michael@0 3519 case RenderingStates.PAUSED:
michael@0 3520 PDFView.highestPriorityPage = type + view.id;
michael@0 3521 view.resume();
michael@0 3522 break;
michael@0 3523 case RenderingStates.RUNNING:
michael@0 3524 PDFView.highestPriorityPage = type + view.id;
michael@0 3525 break;
michael@0 3526 case RenderingStates.INITIAL:
michael@0 3527 PDFView.highestPriorityPage = type + view.id;
michael@0 3528 view.draw(this.renderHighestPriority.bind(this));
michael@0 3529 break;
michael@0 3530 }
michael@0 3531 return true;
michael@0 3532 },
michael@0 3533
michael@0 3534 setHash: function pdfViewSetHash(hash) {
michael@0 3535 if (!hash) {
michael@0 3536 return;
michael@0 3537 }
michael@0 3538
michael@0 3539 if (hash.indexOf('=') >= 0) {
michael@0 3540 var params = PDFView.parseQueryString(hash);
michael@0 3541 // borrowing syntax from "Parameters for Opening PDF Files"
michael@0 3542 if ('nameddest' in params) {
michael@0 3543 PDFHistory.updateNextHashParam(params.nameddest);
michael@0 3544 PDFView.navigateTo(params.nameddest);
michael@0 3545 return;
michael@0 3546 }
michael@0 3547 var pageNumber, dest;
michael@0 3548 if ('page' in params) {
michael@0 3549 pageNumber = (params.page | 0) || 1;
michael@0 3550 }
michael@0 3551 if ('zoom' in params) {
michael@0 3552 var zoomArgs = params.zoom.split(','); // scale,left,top
michael@0 3553 // building destination array
michael@0 3554
michael@0 3555 // If the zoom value, it has to get divided by 100. If it is a string,
michael@0 3556 // it should stay as it is.
michael@0 3557 var zoomArg = zoomArgs[0];
michael@0 3558 var zoomArgNumber = parseFloat(zoomArg);
michael@0 3559 if (zoomArgNumber) {
michael@0 3560 zoomArg = zoomArgNumber / 100;
michael@0 3561 }
michael@0 3562 dest = [null, {name: 'XYZ'},
michael@0 3563 zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
michael@0 3564 zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
michael@0 3565 zoomArg];
michael@0 3566 }
michael@0 3567 if (dest) {
michael@0 3568 var currentPage = this.pages[(pageNumber || this.page) - 1];
michael@0 3569 currentPage.scrollIntoView(dest);
michael@0 3570 } else if (pageNumber) {
michael@0 3571 this.page = pageNumber; // simple page
michael@0 3572 }
michael@0 3573 if ('pagemode' in params) {
michael@0 3574 var toggle = document.getElementById('sidebarToggle');
michael@0 3575 if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
michael@0 3576 params.pagemode === 'attachments') {
michael@0 3577 if (!this.sidebarOpen) {
michael@0 3578 toggle.click();
michael@0 3579 }
michael@0 3580 this.switchSidebarView(params.pagemode === 'bookmarks' ?
michael@0 3581 'outline' :
michael@0 3582 params.pagemode);
michael@0 3583 } else if (params.pagemode === 'none' && this.sidebarOpen) {
michael@0 3584 toggle.click();
michael@0 3585 }
michael@0 3586 }
michael@0 3587 } else if (/^\d+$/.test(hash)) { // page number
michael@0 3588 this.page = hash;
michael@0 3589 } else { // named destination
michael@0 3590 PDFHistory.updateNextHashParam(unescape(hash));
michael@0 3591 PDFView.navigateTo(unescape(hash));
michael@0 3592 }
michael@0 3593 },
michael@0 3594
michael@0 3595 switchSidebarView: function pdfViewSwitchSidebarView(view) {
michael@0 3596 var thumbsView = document.getElementById('thumbnailView');
michael@0 3597 var outlineView = document.getElementById('outlineView');
michael@0 3598 var attachmentsView = document.getElementById('attachmentsView');
michael@0 3599
michael@0 3600 var thumbsButton = document.getElementById('viewThumbnail');
michael@0 3601 var outlineButton = document.getElementById('viewOutline');
michael@0 3602 var attachmentsButton = document.getElementById('viewAttachments');
michael@0 3603
michael@0 3604 switch (view) {
michael@0 3605 case 'thumbs':
michael@0 3606 var wasAnotherViewVisible = thumbsView.classList.contains('hidden');
michael@0 3607
michael@0 3608 thumbsButton.classList.add('toggled');
michael@0 3609 outlineButton.classList.remove('toggled');
michael@0 3610 attachmentsButton.classList.remove('toggled');
michael@0 3611 thumbsView.classList.remove('hidden');
michael@0 3612 outlineView.classList.add('hidden');
michael@0 3613 attachmentsView.classList.add('hidden');
michael@0 3614
michael@0 3615 PDFView.renderHighestPriority();
michael@0 3616
michael@0 3617 if (wasAnotherViewVisible) {
michael@0 3618 // Ensure that the thumbnail of the current page is visible
michael@0 3619 // when switching from another view.
michael@0 3620 scrollIntoView(document.getElementById('thumbnailContainer' +
michael@0 3621 this.page));
michael@0 3622 }
michael@0 3623 break;
michael@0 3624
michael@0 3625 case 'outline':
michael@0 3626 thumbsButton.classList.remove('toggled');
michael@0 3627 outlineButton.classList.add('toggled');
michael@0 3628 attachmentsButton.classList.remove('toggled');
michael@0 3629 thumbsView.classList.add('hidden');
michael@0 3630 outlineView.classList.remove('hidden');
michael@0 3631 attachmentsView.classList.add('hidden');
michael@0 3632
michael@0 3633 if (outlineButton.getAttribute('disabled')) {
michael@0 3634 return;
michael@0 3635 }
michael@0 3636 break;
michael@0 3637
michael@0 3638 case 'attachments':
michael@0 3639 thumbsButton.classList.remove('toggled');
michael@0 3640 outlineButton.classList.remove('toggled');
michael@0 3641 attachmentsButton.classList.add('toggled');
michael@0 3642 thumbsView.classList.add('hidden');
michael@0 3643 outlineView.classList.add('hidden');
michael@0 3644 attachmentsView.classList.remove('hidden');
michael@0 3645
michael@0 3646 if (attachmentsButton.getAttribute('disabled')) {
michael@0 3647 return;
michael@0 3648 }
michael@0 3649 break;
michael@0 3650 }
michael@0 3651 },
michael@0 3652
michael@0 3653 getVisiblePages: function pdfViewGetVisiblePages() {
michael@0 3654 if (!PresentationMode.active) {
michael@0 3655 return this.getVisibleElements(this.container, this.pages, true);
michael@0 3656 } else {
michael@0 3657 // The algorithm in getVisibleElements doesn't work in all browsers and
michael@0 3658 // configurations when presentation mode is active.
michael@0 3659 var visible = [];
michael@0 3660 var currentPage = this.pages[this.page - 1];
michael@0 3661 visible.push({ id: currentPage.id, view: currentPage });
michael@0 3662 return { first: currentPage, last: currentPage, views: visible };
michael@0 3663 }
michael@0 3664 },
michael@0 3665
michael@0 3666 getVisibleThumbs: function pdfViewGetVisibleThumbs() {
michael@0 3667 return this.getVisibleElements(this.thumbnailContainer, this.thumbnails);
michael@0 3668 },
michael@0 3669
michael@0 3670 // Generic helper to find out what elements are visible within a scroll pane.
michael@0 3671 getVisibleElements: function pdfViewGetVisibleElements(
michael@0 3672 scrollEl, views, sortByVisibility) {
michael@0 3673 var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
michael@0 3674 var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
michael@0 3675
michael@0 3676 var visible = [], view;
michael@0 3677 var currentHeight, viewHeight, hiddenHeight, percentHeight;
michael@0 3678 var currentWidth, viewWidth;
michael@0 3679 for (var i = 0, ii = views.length; i < ii; ++i) {
michael@0 3680 view = views[i];
michael@0 3681 currentHeight = view.el.offsetTop + view.el.clientTop;
michael@0 3682 viewHeight = view.el.clientHeight;
michael@0 3683 if ((currentHeight + viewHeight) < top) {
michael@0 3684 continue;
michael@0 3685 }
michael@0 3686 if (currentHeight > bottom) {
michael@0 3687 break;
michael@0 3688 }
michael@0 3689 currentWidth = view.el.offsetLeft + view.el.clientLeft;
michael@0 3690 viewWidth = view.el.clientWidth;
michael@0 3691 if ((currentWidth + viewWidth) < left || currentWidth > right) {
michael@0 3692 continue;
michael@0 3693 }
michael@0 3694 hiddenHeight = Math.max(0, top - currentHeight) +
michael@0 3695 Math.max(0, currentHeight + viewHeight - bottom);
michael@0 3696 percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
michael@0 3697
michael@0 3698 visible.push({ id: view.id, x: currentWidth, y: currentHeight,
michael@0 3699 view: view, percent: percentHeight });
michael@0 3700 }
michael@0 3701
michael@0 3702 var first = visible[0];
michael@0 3703 var last = visible[visible.length - 1];
michael@0 3704
michael@0 3705 if (sortByVisibility) {
michael@0 3706 visible.sort(function(a, b) {
michael@0 3707 var pc = a.percent - b.percent;
michael@0 3708 if (Math.abs(pc) > 0.001) {
michael@0 3709 return -pc;
michael@0 3710 }
michael@0 3711 return a.id - b.id; // ensure stability
michael@0 3712 });
michael@0 3713 }
michael@0 3714 return {first: first, last: last, views: visible};
michael@0 3715 },
michael@0 3716
michael@0 3717 // Helper function to parse query string (e.g. ?param1=value&parm2=...).
michael@0 3718 parseQueryString: function pdfViewParseQueryString(query) {
michael@0 3719 var parts = query.split('&');
michael@0 3720 var params = {};
michael@0 3721 for (var i = 0, ii = parts.length; i < ii; ++i) {
michael@0 3722 var param = parts[i].split('=');
michael@0 3723 var key = param[0];
michael@0 3724 var value = param.length > 1 ? param[1] : null;
michael@0 3725 params[decodeURIComponent(key)] = decodeURIComponent(value);
michael@0 3726 }
michael@0 3727 return params;
michael@0 3728 },
michael@0 3729
michael@0 3730 beforePrint: function pdfViewSetupBeforePrint() {
michael@0 3731 if (!this.supportsPrinting) {
michael@0 3732 var printMessage = mozL10n.get('printing_not_supported', null,
michael@0 3733 'Warning: Printing is not fully supported by this browser.');
michael@0 3734 this.error(printMessage);
michael@0 3735 return;
michael@0 3736 }
michael@0 3737
michael@0 3738 var alertNotReady = false;
michael@0 3739 var i, ii;
michael@0 3740 if (!this.pages.length) {
michael@0 3741 alertNotReady = true;
michael@0 3742 } else {
michael@0 3743 for (i = 0, ii = this.pages.length; i < ii; ++i) {
michael@0 3744 if (!this.pages[i].pdfPage) {
michael@0 3745 alertNotReady = true;
michael@0 3746 break;
michael@0 3747 }
michael@0 3748 }
michael@0 3749 }
michael@0 3750 if (alertNotReady) {
michael@0 3751 var notReadyMessage = mozL10n.get('printing_not_ready', null,
michael@0 3752 'Warning: The PDF is not fully loaded for printing.');
michael@0 3753 window.alert(notReadyMessage);
michael@0 3754 return;
michael@0 3755 }
michael@0 3756
michael@0 3757 var body = document.querySelector('body');
michael@0 3758 body.setAttribute('data-mozPrintCallback', true);
michael@0 3759 for (i = 0, ii = this.pages.length; i < ii; ++i) {
michael@0 3760 this.pages[i].beforePrint();
michael@0 3761 }
michael@0 3762 },
michael@0 3763
michael@0 3764 afterPrint: function pdfViewSetupAfterPrint() {
michael@0 3765 var div = document.getElementById('printContainer');
michael@0 3766 while (div.hasChildNodes()) {
michael@0 3767 div.removeChild(div.lastChild);
michael@0 3768 }
michael@0 3769 },
michael@0 3770
michael@0 3771 rotatePages: function pdfViewRotatePages(delta) {
michael@0 3772 var currentPage = this.pages[this.page - 1];
michael@0 3773 var i, l;
michael@0 3774 this.pageRotation = (this.pageRotation + 360 + delta) % 360;
michael@0 3775
michael@0 3776 for (i = 0, l = this.pages.length; i < l; i++) {
michael@0 3777 var page = this.pages[i];
michael@0 3778 page.update(page.scale, this.pageRotation);
michael@0 3779 }
michael@0 3780
michael@0 3781 for (i = 0, l = this.thumbnails.length; i < l; i++) {
michael@0 3782 var thumb = this.thumbnails[i];
michael@0 3783 thumb.update(this.pageRotation);
michael@0 3784 }
michael@0 3785
michael@0 3786 this.setScale(this.currentScaleValue, true, true);
michael@0 3787
michael@0 3788 this.renderHighestPriority();
michael@0 3789
michael@0 3790 if (currentPage) {
michael@0 3791 currentPage.scrollIntoView();
michael@0 3792 }
michael@0 3793 },
michael@0 3794
michael@0 3795 /**
michael@0 3796 * This function flips the page in presentation mode if the user scrolls up
michael@0 3797 * or down with large enough motion and prevents page flipping too often.
michael@0 3798 *
michael@0 3799 * @this {PDFView}
michael@0 3800 * @param {number} mouseScrollDelta The delta value from the mouse event.
michael@0 3801 */
michael@0 3802 mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
michael@0 3803 var MOUSE_SCROLL_COOLDOWN_TIME = 50;
michael@0 3804
michael@0 3805 var currentTime = (new Date()).getTime();
michael@0 3806 var storedTime = this.mouseScrollTimeStamp;
michael@0 3807
michael@0 3808 // In case one page has already been flipped there is a cooldown time
michael@0 3809 // which has to expire before next page can be scrolled on to.
michael@0 3810 if (currentTime > storedTime &&
michael@0 3811 currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
michael@0 3812 return;
michael@0 3813 }
michael@0 3814
michael@0 3815 // In case the user decides to scroll to the opposite direction than before
michael@0 3816 // clear the accumulated delta.
michael@0 3817 if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
michael@0 3818 (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) {
michael@0 3819 this.clearMouseScrollState();
michael@0 3820 }
michael@0 3821
michael@0 3822 this.mouseScrollDelta += mouseScrollDelta;
michael@0 3823
michael@0 3824 var PAGE_FLIP_THRESHOLD = 120;
michael@0 3825 if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
michael@0 3826
michael@0 3827 var PageFlipDirection = {
michael@0 3828 UP: -1,
michael@0 3829 DOWN: 1
michael@0 3830 };
michael@0 3831
michael@0 3832 // In presentation mode scroll one page at a time.
michael@0 3833 var pageFlipDirection = (this.mouseScrollDelta > 0) ?
michael@0 3834 PageFlipDirection.UP :
michael@0 3835 PageFlipDirection.DOWN;
michael@0 3836 this.clearMouseScrollState();
michael@0 3837 var currentPage = this.page;
michael@0 3838
michael@0 3839 // In case we are already on the first or the last page there is no need
michael@0 3840 // to do anything.
michael@0 3841 if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) ||
michael@0 3842 (currentPage == this.pages.length &&
michael@0 3843 pageFlipDirection == PageFlipDirection.DOWN)) {
michael@0 3844 return;
michael@0 3845 }
michael@0 3846
michael@0 3847 this.page += pageFlipDirection;
michael@0 3848 this.mouseScrollTimeStamp = currentTime;
michael@0 3849 }
michael@0 3850 },
michael@0 3851
michael@0 3852 /**
michael@0 3853 * This function clears the member attributes used with mouse scrolling in
michael@0 3854 * presentation mode.
michael@0 3855 *
michael@0 3856 * @this {PDFView}
michael@0 3857 */
michael@0 3858 clearMouseScrollState: function pdfViewClearMouseScrollState() {
michael@0 3859 this.mouseScrollTimeStamp = 0;
michael@0 3860 this.mouseScrollDelta = 0;
michael@0 3861 }
michael@0 3862 };
michael@0 3863
michael@0 3864
michael@0 3865 var PageView = function pageView(container, id, scale,
michael@0 3866 navigateTo, defaultViewport) {
michael@0 3867 this.id = id;
michael@0 3868
michael@0 3869 this.rotation = 0;
michael@0 3870 this.scale = scale || 1.0;
michael@0 3871 this.viewport = defaultViewport;
michael@0 3872 this.pdfPageRotate = defaultViewport.rotation;
michael@0 3873
michael@0 3874 this.renderingState = RenderingStates.INITIAL;
michael@0 3875 this.resume = null;
michael@0 3876
michael@0 3877 this.textLayer = null;
michael@0 3878
michael@0 3879 this.zoomLayer = null;
michael@0 3880
michael@0 3881 this.annotationLayer = null;
michael@0 3882
michael@0 3883 var anchor = document.createElement('a');
michael@0 3884 anchor.name = '' + this.id;
michael@0 3885
michael@0 3886 var div = this.el = document.createElement('div');
michael@0 3887 div.id = 'pageContainer' + this.id;
michael@0 3888 div.className = 'page';
michael@0 3889 div.style.width = Math.floor(this.viewport.width) + 'px';
michael@0 3890 div.style.height = Math.floor(this.viewport.height) + 'px';
michael@0 3891
michael@0 3892 container.appendChild(anchor);
michael@0 3893 container.appendChild(div);
michael@0 3894
michael@0 3895 this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
michael@0 3896 this.pdfPage = pdfPage;
michael@0 3897 this.pdfPageRotate = pdfPage.rotate;
michael@0 3898 var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
michael@0 3899 this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
michael@0 3900 this.stats = pdfPage.stats;
michael@0 3901 this.reset();
michael@0 3902 };
michael@0 3903
michael@0 3904 this.destroy = function pageViewDestroy() {
michael@0 3905 this.zoomLayer = null;
michael@0 3906 this.reset();
michael@0 3907 if (this.pdfPage) {
michael@0 3908 this.pdfPage.destroy();
michael@0 3909 }
michael@0 3910 };
michael@0 3911
michael@0 3912 this.reset = function pageViewReset(keepAnnotations) {
michael@0 3913 if (this.renderTask) {
michael@0 3914 this.renderTask.cancel();
michael@0 3915 }
michael@0 3916 this.resume = null;
michael@0 3917 this.renderingState = RenderingStates.INITIAL;
michael@0 3918
michael@0 3919 div.style.width = Math.floor(this.viewport.width) + 'px';
michael@0 3920 div.style.height = Math.floor(this.viewport.height) + 'px';
michael@0 3921
michael@0 3922 var childNodes = div.childNodes;
michael@0 3923 for (var i = div.childNodes.length - 1; i >= 0; i--) {
michael@0 3924 var node = childNodes[i];
michael@0 3925 if ((this.zoomLayer && this.zoomLayer === node) ||
michael@0 3926 (keepAnnotations && this.annotationLayer === node)) {
michael@0 3927 continue;
michael@0 3928 }
michael@0 3929 div.removeChild(node);
michael@0 3930 }
michael@0 3931 div.removeAttribute('data-loaded');
michael@0 3932
michael@0 3933 if (keepAnnotations) {
michael@0 3934 if (this.annotationLayer) {
michael@0 3935 // Hide annotationLayer until all elements are resized
michael@0 3936 // so they are not displayed on the already-resized page
michael@0 3937 this.annotationLayer.setAttribute('hidden', 'true');
michael@0 3938 }
michael@0 3939 } else {
michael@0 3940 this.annotationLayer = null;
michael@0 3941 }
michael@0 3942
michael@0 3943 delete this.canvas;
michael@0 3944
michael@0 3945 this.loadingIconDiv = document.createElement('div');
michael@0 3946 this.loadingIconDiv.className = 'loadingIcon';
michael@0 3947 div.appendChild(this.loadingIconDiv);
michael@0 3948 };
michael@0 3949
michael@0 3950 this.update = function pageViewUpdate(scale, rotation) {
michael@0 3951 this.scale = scale || this.scale;
michael@0 3952
michael@0 3953 if (typeof rotation !== 'undefined') {
michael@0 3954 this.rotation = rotation;
michael@0 3955 }
michael@0 3956
michael@0 3957 var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
michael@0 3958 this.viewport = this.viewport.clone({
michael@0 3959 scale: this.scale * CSS_UNITS,
michael@0 3960 rotation: totalRotation
michael@0 3961 });
michael@0 3962
michael@0 3963 if (USE_ONLY_CSS_ZOOM && this.canvas) {
michael@0 3964 this.cssTransform(this.canvas);
michael@0 3965 return;
michael@0 3966 } else if (this.canvas && !this.zoomLayer) {
michael@0 3967 this.zoomLayer = this.canvas.parentNode;
michael@0 3968 this.zoomLayer.style.position = 'absolute';
michael@0 3969 }
michael@0 3970 if (this.zoomLayer) {
michael@0 3971 this.cssTransform(this.zoomLayer.firstChild);
michael@0 3972 }
michael@0 3973 this.reset(true);
michael@0 3974 };
michael@0 3975
michael@0 3976 this.cssTransform = function pageCssTransform(canvas) {
michael@0 3977 // Scale canvas, canvas wrapper, and page container.
michael@0 3978 var width = this.viewport.width;
michael@0 3979 var height = this.viewport.height;
michael@0 3980 canvas.style.width = canvas.parentNode.style.width = div.style.width =
michael@0 3981 Math.floor(width) + 'px';
michael@0 3982 canvas.style.height = canvas.parentNode.style.height = div.style.height =
michael@0 3983 Math.floor(height) + 'px';
michael@0 3984 // The canvas may have been originally rotated, so rotate relative to that.
michael@0 3985 var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
michael@0 3986 var absRotation = Math.abs(relativeRotation);
michael@0 3987 var scaleX = 1, scaleY = 1;
michael@0 3988 if (absRotation === 90 || absRotation === 270) {
michael@0 3989 // Scale x and y because of the rotation.
michael@0 3990 scaleX = height / width;
michael@0 3991 scaleY = width / height;
michael@0 3992 }
michael@0 3993 var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
michael@0 3994 'scale(' + scaleX + ',' + scaleY + ')';
michael@0 3995 CustomStyle.setProp('transform', canvas, cssTransform);
michael@0 3996
michael@0 3997 if (this.textLayer) {
michael@0 3998 // Rotating the text layer is more complicated since the divs inside the
michael@0 3999 // the text layer are rotated.
michael@0 4000 // TODO: This could probably be simplified by drawing the text layer in
michael@0 4001 // one orientation then rotating overall.
michael@0 4002 var textRelativeRotation = this.viewport.rotation -
michael@0 4003 this.textLayer.viewport.rotation;
michael@0 4004 var textAbsRotation = Math.abs(textRelativeRotation);
michael@0 4005 var scale = (width / canvas.width);
michael@0 4006 if (textAbsRotation === 90 || textAbsRotation === 270) {
michael@0 4007 scale = width / canvas.height;
michael@0 4008 }
michael@0 4009 var textLayerDiv = this.textLayer.textLayerDiv;
michael@0 4010 var transX, transY;
michael@0 4011 switch (textAbsRotation) {
michael@0 4012 case 0:
michael@0 4013 transX = transY = 0;
michael@0 4014 break;
michael@0 4015 case 90:
michael@0 4016 transX = 0;
michael@0 4017 transY = '-' + textLayerDiv.style.height;
michael@0 4018 break;
michael@0 4019 case 180:
michael@0 4020 transX = '-' + textLayerDiv.style.width;
michael@0 4021 transY = '-' + textLayerDiv.style.height;
michael@0 4022 break;
michael@0 4023 case 270:
michael@0 4024 transX = '-' + textLayerDiv.style.width;
michael@0 4025 transY = 0;
michael@0 4026 break;
michael@0 4027 default:
michael@0 4028 console.error('Bad rotation value.');
michael@0 4029 break;
michael@0 4030 }
michael@0 4031 CustomStyle.setProp('transform', textLayerDiv,
michael@0 4032 'rotate(' + textAbsRotation + 'deg) ' +
michael@0 4033 'scale(' + scale + ', ' + scale + ') ' +
michael@0 4034 'translate(' + transX + ', ' + transY + ')');
michael@0 4035 CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
michael@0 4036 }
michael@0 4037
michael@0 4038 if (USE_ONLY_CSS_ZOOM && this.annotationLayer) {
michael@0 4039 setupAnnotations(div, this.pdfPage, this.viewport);
michael@0 4040 }
michael@0 4041 };
michael@0 4042
michael@0 4043 Object.defineProperty(this, 'width', {
michael@0 4044 get: function PageView_getWidth() {
michael@0 4045 return this.viewport.width;
michael@0 4046 },
michael@0 4047 enumerable: true
michael@0 4048 });
michael@0 4049
michael@0 4050 Object.defineProperty(this, 'height', {
michael@0 4051 get: function PageView_getHeight() {
michael@0 4052 return this.viewport.height;
michael@0 4053 },
michael@0 4054 enumerable: true
michael@0 4055 });
michael@0 4056
michael@0 4057 var self = this;
michael@0 4058
michael@0 4059 function setupAnnotations(pageDiv, pdfPage, viewport) {
michael@0 4060
michael@0 4061 function bindLink(link, dest) {
michael@0 4062 link.href = PDFView.getDestinationHash(dest);
michael@0 4063 link.onclick = function pageViewSetupLinksOnclick() {
michael@0 4064 if (dest) {
michael@0 4065 PDFView.navigateTo(dest);
michael@0 4066 }
michael@0 4067 return false;
michael@0 4068 };
michael@0 4069 if (dest) {
michael@0 4070 link.className = 'internalLink';
michael@0 4071 }
michael@0 4072 }
michael@0 4073
michael@0 4074 function bindNamedAction(link, action) {
michael@0 4075 link.href = PDFView.getAnchorUrl('');
michael@0 4076 link.onclick = function pageViewSetupNamedActionOnClick() {
michael@0 4077 // See PDF reference, table 8.45 - Named action
michael@0 4078 switch (action) {
michael@0 4079 case 'GoToPage':
michael@0 4080 document.getElementById('pageNumber').focus();
michael@0 4081 break;
michael@0 4082
michael@0 4083 case 'GoBack':
michael@0 4084 PDFHistory.back();
michael@0 4085 break;
michael@0 4086
michael@0 4087 case 'GoForward':
michael@0 4088 PDFHistory.forward();
michael@0 4089 break;
michael@0 4090
michael@0 4091 case 'Find':
michael@0 4092 if (!PDFView.supportsIntegratedFind) {
michael@0 4093 PDFFindBar.toggle();
michael@0 4094 }
michael@0 4095 break;
michael@0 4096
michael@0 4097 case 'NextPage':
michael@0 4098 PDFView.page++;
michael@0 4099 break;
michael@0 4100
michael@0 4101 case 'PrevPage':
michael@0 4102 PDFView.page--;
michael@0 4103 break;
michael@0 4104
michael@0 4105 case 'LastPage':
michael@0 4106 PDFView.page = PDFView.pages.length;
michael@0 4107 break;
michael@0 4108
michael@0 4109 case 'FirstPage':
michael@0 4110 PDFView.page = 1;
michael@0 4111 break;
michael@0 4112
michael@0 4113 default:
michael@0 4114 break; // No action according to spec
michael@0 4115 }
michael@0 4116 return false;
michael@0 4117 };
michael@0 4118 link.className = 'internalLink';
michael@0 4119 }
michael@0 4120
michael@0 4121 pdfPage.getAnnotations().then(function(annotationsData) {
michael@0 4122 viewport = viewport.clone({ dontFlip: true });
michael@0 4123 var transform = viewport.transform;
michael@0 4124 var transformStr = 'matrix(' + transform.join(',') + ')';
michael@0 4125 var data, element, i, ii;
michael@0 4126
michael@0 4127 if (self.annotationLayer) {
michael@0 4128 // If an annotationLayer already exists, refresh its children's
michael@0 4129 // transformation matrices
michael@0 4130 for (i = 0, ii = annotationsData.length; i < ii; i++) {
michael@0 4131 data = annotationsData[i];
michael@0 4132 element = self.annotationLayer.querySelector(
michael@0 4133 '[data-annotation-id="' + data.id + '"]');
michael@0 4134 if (element) {
michael@0 4135 CustomStyle.setProp('transform', element, transformStr);
michael@0 4136 }
michael@0 4137 }
michael@0 4138 // See this.reset()
michael@0 4139 self.annotationLayer.removeAttribute('hidden');
michael@0 4140 } else {
michael@0 4141 for (i = 0, ii = annotationsData.length; i < ii; i++) {
michael@0 4142 data = annotationsData[i];
michael@0 4143 var annotation = PDFJS.Annotation.fromData(data);
michael@0 4144 if (!annotation || !annotation.hasHtml()) {
michael@0 4145 continue;
michael@0 4146 }
michael@0 4147
michael@0 4148 element = annotation.getHtmlElement(pdfPage.commonObjs);
michael@0 4149 element.setAttribute('data-annotation-id', data.id);
michael@0 4150 mozL10n.translate(element);
michael@0 4151
michael@0 4152 data = annotation.getData();
michael@0 4153 var rect = data.rect;
michael@0 4154 var view = pdfPage.view;
michael@0 4155 rect = PDFJS.Util.normalizeRect([
michael@0 4156 rect[0],
michael@0 4157 view[3] - rect[1] + view[1],
michael@0 4158 rect[2],
michael@0 4159 view[3] - rect[3] + view[1]
michael@0 4160 ]);
michael@0 4161 element.style.left = rect[0] + 'px';
michael@0 4162 element.style.top = rect[1] + 'px';
michael@0 4163 element.style.position = 'absolute';
michael@0 4164
michael@0 4165 CustomStyle.setProp('transform', element, transformStr);
michael@0 4166 var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
michael@0 4167 CustomStyle.setProp('transformOrigin', element, transformOriginStr);
michael@0 4168
michael@0 4169 if (data.subtype === 'Link' && !data.url) {
michael@0 4170 var link = element.getElementsByTagName('a')[0];
michael@0 4171 if (link) {
michael@0 4172 if (data.action) {
michael@0 4173 bindNamedAction(link, data.action);
michael@0 4174 } else {
michael@0 4175 bindLink(link, ('dest' in data) ? data.dest : null);
michael@0 4176 }
michael@0 4177 }
michael@0 4178 }
michael@0 4179
michael@0 4180 if (!self.annotationLayer) {
michael@0 4181 var annotationLayerDiv = document.createElement('div');
michael@0 4182 annotationLayerDiv.className = 'annotationLayer';
michael@0 4183 pageDiv.appendChild(annotationLayerDiv);
michael@0 4184 self.annotationLayer = annotationLayerDiv;
michael@0 4185 }
michael@0 4186
michael@0 4187 self.annotationLayer.appendChild(element);
michael@0 4188 }
michael@0 4189 }
michael@0 4190 });
michael@0 4191 }
michael@0 4192
michael@0 4193 this.getPagePoint = function pageViewGetPagePoint(x, y) {
michael@0 4194 return this.viewport.convertToPdfPoint(x, y);
michael@0 4195 };
michael@0 4196
michael@0 4197 this.scrollIntoView = function pageViewScrollIntoView(dest) {
michael@0 4198 if (PresentationMode.active) {
michael@0 4199 if (PDFView.page !== this.id) {
michael@0 4200 // Avoid breaking PDFView.getVisiblePages in presentation mode.
michael@0 4201 PDFView.page = this.id;
michael@0 4202 return;
michael@0 4203 }
michael@0 4204 dest = null;
michael@0 4205 PDFView.setScale(PDFView.currentScaleValue, true, true);
michael@0 4206 }
michael@0 4207 if (!dest) {
michael@0 4208 scrollIntoView(div);
michael@0 4209 return;
michael@0 4210 }
michael@0 4211
michael@0 4212 var x = 0, y = 0;
michael@0 4213 var width = 0, height = 0, widthScale, heightScale;
michael@0 4214 var changeOrientation = (this.rotation % 180 === 0 ? false : true);
michael@0 4215 var pageWidth = (changeOrientation ? this.height : this.width) /
michael@0 4216 this.scale / CSS_UNITS;
michael@0 4217 var pageHeight = (changeOrientation ? this.width : this.height) /
michael@0 4218 this.scale / CSS_UNITS;
michael@0 4219 var scale = 0;
michael@0 4220 switch (dest[1].name) {
michael@0 4221 case 'XYZ':
michael@0 4222 x = dest[2];
michael@0 4223 y = dest[3];
michael@0 4224 scale = dest[4];
michael@0 4225 // If x and/or y coordinates are not supplied, default to
michael@0 4226 // _top_ left of the page (not the obvious bottom left,
michael@0 4227 // since aligning the bottom of the intended page with the
michael@0 4228 // top of the window is rarely helpful).
michael@0 4229 x = x !== null ? x : 0;
michael@0 4230 y = y !== null ? y : pageHeight;
michael@0 4231 break;
michael@0 4232 case 'Fit':
michael@0 4233 case 'FitB':
michael@0 4234 scale = 'page-fit';
michael@0 4235 break;
michael@0 4236 case 'FitH':
michael@0 4237 case 'FitBH':
michael@0 4238 y = dest[2];
michael@0 4239 scale = 'page-width';
michael@0 4240 break;
michael@0 4241 case 'FitV':
michael@0 4242 case 'FitBV':
michael@0 4243 x = dest[2];
michael@0 4244 width = pageWidth;
michael@0 4245 height = pageHeight;
michael@0 4246 scale = 'page-height';
michael@0 4247 break;
michael@0 4248 case 'FitR':
michael@0 4249 x = dest[2];
michael@0 4250 y = dest[3];
michael@0 4251 width = dest[4] - x;
michael@0 4252 height = dest[5] - y;
michael@0 4253 widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) /
michael@0 4254 width / CSS_UNITS;
michael@0 4255 heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) /
michael@0 4256 height / CSS_UNITS;
michael@0 4257 scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
michael@0 4258 break;
michael@0 4259 default:
michael@0 4260 return;
michael@0 4261 }
michael@0 4262
michael@0 4263 if (scale && scale !== PDFView.currentScale) {
michael@0 4264 PDFView.setScale(scale, true, true);
michael@0 4265 } else if (PDFView.currentScale === UNKNOWN_SCALE) {
michael@0 4266 PDFView.setScale(DEFAULT_SCALE, true, true);
michael@0 4267 }
michael@0 4268
michael@0 4269 if (scale === 'page-fit' && !dest[4]) {
michael@0 4270 scrollIntoView(div);
michael@0 4271 return;
michael@0 4272 }
michael@0 4273
michael@0 4274 var boundingRect = [
michael@0 4275 this.viewport.convertToViewportPoint(x, y),
michael@0 4276 this.viewport.convertToViewportPoint(x + width, y + height)
michael@0 4277 ];
michael@0 4278 var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
michael@0 4279 var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
michael@0 4280
michael@0 4281 scrollIntoView(div, { left: left, top: top });
michael@0 4282 };
michael@0 4283
michael@0 4284 this.getTextContent = function pageviewGetTextContent() {
michael@0 4285 return PDFView.getPage(this.id).then(function(pdfPage) {
michael@0 4286 return pdfPage.getTextContent();
michael@0 4287 });
michael@0 4288 };
michael@0 4289
michael@0 4290 this.draw = function pageviewDraw(callback) {
michael@0 4291 var pdfPage = this.pdfPage;
michael@0 4292
michael@0 4293 if (this.pagePdfPromise) {
michael@0 4294 return;
michael@0 4295 }
michael@0 4296 if (!pdfPage) {
michael@0 4297 var promise = PDFView.getPage(this.id);
michael@0 4298 promise.then(function(pdfPage) {
michael@0 4299 delete this.pagePdfPromise;
michael@0 4300 this.setPdfPage(pdfPage);
michael@0 4301 this.draw(callback);
michael@0 4302 }.bind(this));
michael@0 4303 this.pagePdfPromise = promise;
michael@0 4304 return;
michael@0 4305 }
michael@0 4306
michael@0 4307 if (this.renderingState !== RenderingStates.INITIAL) {
michael@0 4308 console.error('Must be in new state before drawing');
michael@0 4309 }
michael@0 4310
michael@0 4311 this.renderingState = RenderingStates.RUNNING;
michael@0 4312
michael@0 4313 var viewport = this.viewport;
michael@0 4314 // Wrap the canvas so if it has a css transform for highdpi the overflow
michael@0 4315 // will be hidden in FF.
michael@0 4316 var canvasWrapper = document.createElement('div');
michael@0 4317 canvasWrapper.style.width = div.style.width;
michael@0 4318 canvasWrapper.style.height = div.style.height;
michael@0 4319 canvasWrapper.classList.add('canvasWrapper');
michael@0 4320
michael@0 4321 var canvas = document.createElement('canvas');
michael@0 4322 canvas.id = 'page' + this.id;
michael@0 4323 canvasWrapper.appendChild(canvas);
michael@0 4324 if (this.annotationLayer) {
michael@0 4325 // annotationLayer needs to stay on top
michael@0 4326 div.insertBefore(canvasWrapper, this.annotationLayer);
michael@0 4327 } else {
michael@0 4328 div.appendChild(canvasWrapper);
michael@0 4329 }
michael@0 4330 this.canvas = canvas;
michael@0 4331
michael@0 4332 var ctx = canvas.getContext('2d');
michael@0 4333 var outputScale = getOutputScale(ctx);
michael@0 4334
michael@0 4335 if (USE_ONLY_CSS_ZOOM) {
michael@0 4336 var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
michael@0 4337 // Use a scale that will make the canvas be the original intended size
michael@0 4338 // of the page.
michael@0 4339 outputScale.sx *= actualSizeViewport.width / viewport.width;
michael@0 4340 outputScale.sy *= actualSizeViewport.height / viewport.height;
michael@0 4341 outputScale.scaled = true;
michael@0 4342 }
michael@0 4343
michael@0 4344 canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
michael@0 4345 canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
michael@0 4346 canvas.style.width = Math.floor(viewport.width) + 'px';
michael@0 4347 canvas.style.height = Math.floor(viewport.height) + 'px';
michael@0 4348 // Add the viewport so it's known what it was originally drawn with.
michael@0 4349 canvas._viewport = viewport;
michael@0 4350
michael@0 4351 var textLayerDiv = null;
michael@0 4352 if (!PDFJS.disableTextLayer) {
michael@0 4353 textLayerDiv = document.createElement('div');
michael@0 4354 textLayerDiv.className = 'textLayer';
michael@0 4355 textLayerDiv.style.width = canvas.style.width;
michael@0 4356 textLayerDiv.style.height = canvas.style.height;
michael@0 4357 if (this.annotationLayer) {
michael@0 4358 // annotationLayer needs to stay on top
michael@0 4359 div.insertBefore(textLayerDiv, this.annotationLayer);
michael@0 4360 } else {
michael@0 4361 div.appendChild(textLayerDiv);
michael@0 4362 }
michael@0 4363 }
michael@0 4364 var textLayer = this.textLayer =
michael@0 4365 textLayerDiv ? new TextLayerBuilder({
michael@0 4366 textLayerDiv: textLayerDiv,
michael@0 4367 pageIndex: this.id - 1,
michael@0 4368 lastScrollSource: PDFView,
michael@0 4369 viewport: this.viewport,
michael@0 4370 isViewerInPresentationMode: PresentationMode.active
michael@0 4371 }) : null;
michael@0 4372 // TODO(mack): use data attributes to store these
michael@0 4373 ctx._scaleX = outputScale.sx;
michael@0 4374 ctx._scaleY = outputScale.sy;
michael@0 4375 if (outputScale.scaled) {
michael@0 4376 ctx.scale(outputScale.sx, outputScale.sy);
michael@0 4377 }
michael@0 4378
michael@0 4379 // Rendering area
michael@0 4380
michael@0 4381 var self = this;
michael@0 4382 function pageViewDrawCallback(error) {
michael@0 4383 // The renderTask may have been replaced by a new one, so only remove the
michael@0 4384 // reference to the renderTask if it matches the one that is triggering
michael@0 4385 // this callback.
michael@0 4386 if (renderTask === self.renderTask) {
michael@0 4387 self.renderTask = null;
michael@0 4388 }
michael@0 4389
michael@0 4390 if (error === 'cancelled') {
michael@0 4391 return;
michael@0 4392 }
michael@0 4393
michael@0 4394 self.renderingState = RenderingStates.FINISHED;
michael@0 4395
michael@0 4396 if (self.loadingIconDiv) {
michael@0 4397 div.removeChild(self.loadingIconDiv);
michael@0 4398 delete self.loadingIconDiv;
michael@0 4399 }
michael@0 4400
michael@0 4401 if (self.zoomLayer) {
michael@0 4402 div.removeChild(self.zoomLayer);
michael@0 4403 self.zoomLayer = null;
michael@0 4404 }
michael@0 4405
michael@0 4406 if (self.textLayer && self.textLayer.textDivs &&
michael@0 4407 self.textLayer.textDivs.length > 0 &&
michael@0 4408 !PDFView.supportsDocumentColors) {
michael@0 4409 console.error(mozL10n.get('document_colors_disabled', null,
michael@0 4410 'PDF documents are not allowed to use their own colors: ' +
michael@0 4411 '\'Allow pages to choose their own colors\' ' +
michael@0 4412 'is deactivated in the browser.'));
michael@0 4413 PDFView.fallback();
michael@0 4414 }
michael@0 4415 if (error) {
michael@0 4416 PDFView.error(mozL10n.get('rendering_error', null,
michael@0 4417 'An error occurred while rendering the page.'), error);
michael@0 4418 }
michael@0 4419
michael@0 4420 self.stats = pdfPage.stats;
michael@0 4421 self.updateStats();
michael@0 4422 if (self.onAfterDraw) {
michael@0 4423 self.onAfterDraw();
michael@0 4424 }
michael@0 4425
michael@0 4426 cache.push(self);
michael@0 4427
michael@0 4428 var event = document.createEvent('CustomEvent');
michael@0 4429 event.initCustomEvent('pagerender', true, true, {
michael@0 4430 pageNumber: pdfPage.pageNumber
michael@0 4431 });
michael@0 4432 div.dispatchEvent(event);
michael@0 4433
michael@0 4434 FirefoxCom.request('reportTelemetry', JSON.stringify({
michael@0 4435 type: 'pageInfo'
michael@0 4436 }));
michael@0 4437 // TODO add stream types report here
michael@0 4438 callback();
michael@0 4439 }
michael@0 4440
michael@0 4441 var renderContext = {
michael@0 4442 canvasContext: ctx,
michael@0 4443 viewport: this.viewport,
michael@0 4444 textLayer: textLayer,
michael@0 4445 // intent: 'default', // === 'display'
michael@0 4446 continueCallback: function pdfViewcContinueCallback(cont) {
michael@0 4447 if (PDFView.highestPriorityPage !== 'page' + self.id) {
michael@0 4448 self.renderingState = RenderingStates.PAUSED;
michael@0 4449 self.resume = function resumeCallback() {
michael@0 4450 self.renderingState = RenderingStates.RUNNING;
michael@0 4451 cont();
michael@0 4452 };
michael@0 4453 return;
michael@0 4454 }
michael@0 4455 cont();
michael@0 4456 }
michael@0 4457 };
michael@0 4458 var renderTask = this.renderTask = this.pdfPage.render(renderContext);
michael@0 4459
michael@0 4460 this.renderTask.promise.then(
michael@0 4461 function pdfPageRenderCallback() {
michael@0 4462 pageViewDrawCallback(null);
michael@0 4463 if (textLayer) {
michael@0 4464 self.getTextContent().then(
michael@0 4465 function textContentResolved(textContent) {
michael@0 4466 textLayer.setTextContent(textContent);
michael@0 4467 }
michael@0 4468 );
michael@0 4469 }
michael@0 4470 },
michael@0 4471 function pdfPageRenderError(error) {
michael@0 4472 pageViewDrawCallback(error);
michael@0 4473 }
michael@0 4474 );
michael@0 4475
michael@0 4476 setupAnnotations(div, pdfPage, this.viewport);
michael@0 4477 div.setAttribute('data-loaded', true);
michael@0 4478 };
michael@0 4479
michael@0 4480 this.beforePrint = function pageViewBeforePrint() {
michael@0 4481 var pdfPage = this.pdfPage;
michael@0 4482
michael@0 4483 var viewport = pdfPage.getViewport(1);
michael@0 4484 // Use the same hack we use for high dpi displays for printing to get better
michael@0 4485 // output until bug 811002 is fixed in FF.
michael@0 4486 var PRINT_OUTPUT_SCALE = 2;
michael@0 4487 var canvas = document.createElement('canvas');
michael@0 4488 canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
michael@0 4489 canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
michael@0 4490 canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';
michael@0 4491 canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt';
michael@0 4492 var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
michael@0 4493 (1 / PRINT_OUTPUT_SCALE) + ')';
michael@0 4494 CustomStyle.setProp('transform' , canvas, cssScale);
michael@0 4495 CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
michael@0 4496
michael@0 4497 var printContainer = document.getElementById('printContainer');
michael@0 4498 var canvasWrapper = document.createElement('div');
michael@0 4499 canvasWrapper.style.width = viewport.width + 'pt';
michael@0 4500 canvasWrapper.style.height = viewport.height + 'pt';
michael@0 4501 canvasWrapper.appendChild(canvas);
michael@0 4502 printContainer.appendChild(canvasWrapper);
michael@0 4503
michael@0 4504 canvas.mozPrintCallback = function(obj) {
michael@0 4505 var ctx = obj.context;
michael@0 4506
michael@0 4507 ctx.save();
michael@0 4508 ctx.fillStyle = 'rgb(255, 255, 255)';
michael@0 4509 ctx.fillRect(0, 0, canvas.width, canvas.height);
michael@0 4510 ctx.restore();
michael@0 4511 ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
michael@0 4512
michael@0 4513 var renderContext = {
michael@0 4514 canvasContext: ctx,
michael@0 4515 viewport: viewport,
michael@0 4516 intent: 'print'
michael@0 4517 };
michael@0 4518
michael@0 4519 pdfPage.render(renderContext).promise.then(function() {
michael@0 4520 // Tell the printEngine that rendering this canvas/page has finished.
michael@0 4521 obj.done();
michael@0 4522 }, function(error) {
michael@0 4523 console.error(error);
michael@0 4524 // Tell the printEngine that rendering this canvas/page has failed.
michael@0 4525 // This will make the print proces stop.
michael@0 4526 if ('abort' in obj) {
michael@0 4527 obj.abort();
michael@0 4528 } else {
michael@0 4529 obj.done();
michael@0 4530 }
michael@0 4531 });
michael@0 4532 };
michael@0 4533 };
michael@0 4534
michael@0 4535 this.updateStats = function pageViewUpdateStats() {
michael@0 4536 if (!this.stats) {
michael@0 4537 return;
michael@0 4538 }
michael@0 4539
michael@0 4540 if (PDFJS.pdfBug && Stats.enabled) {
michael@0 4541 var stats = this.stats;
michael@0 4542 Stats.add(this.id, stats);
michael@0 4543 }
michael@0 4544 };
michael@0 4545 };
michael@0 4546
michael@0 4547
michael@0 4548 var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
michael@0 4549 var anchor = document.createElement('a');
michael@0 4550 anchor.href = PDFView.getAnchorUrl('#page=' + id);
michael@0 4551 anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
michael@0 4552 anchor.onclick = function stopNavigation() {
michael@0 4553 PDFView.page = id;
michael@0 4554 return false;
michael@0 4555 };
michael@0 4556
michael@0 4557 this.pdfPage = undefined;
michael@0 4558 this.viewport = defaultViewport;
michael@0 4559 this.pdfPageRotate = defaultViewport.rotation;
michael@0 4560
michael@0 4561 this.rotation = 0;
michael@0 4562 this.pageWidth = this.viewport.width;
michael@0 4563 this.pageHeight = this.viewport.height;
michael@0 4564 this.pageRatio = this.pageWidth / this.pageHeight;
michael@0 4565 this.id = id;
michael@0 4566
michael@0 4567 this.canvasWidth = 98;
michael@0 4568 this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
michael@0 4569 this.scale = (this.canvasWidth / this.pageWidth);
michael@0 4570
michael@0 4571 var div = this.el = document.createElement('div');
michael@0 4572 div.id = 'thumbnailContainer' + id;
michael@0 4573 div.className = 'thumbnail';
michael@0 4574
michael@0 4575 if (id === 1) {
michael@0 4576 // Highlight the thumbnail of the first page when no page number is
michael@0 4577 // specified (or exists in cache) when the document is loaded.
michael@0 4578 div.classList.add('selected');
michael@0 4579 }
michael@0 4580
michael@0 4581 var ring = document.createElement('div');
michael@0 4582 ring.className = 'thumbnailSelectionRing';
michael@0 4583 ring.style.width = this.canvasWidth + 'px';
michael@0 4584 ring.style.height = this.canvasHeight + 'px';
michael@0 4585
michael@0 4586 div.appendChild(ring);
michael@0 4587 anchor.appendChild(div);
michael@0 4588 container.appendChild(anchor);
michael@0 4589
michael@0 4590 this.hasImage = false;
michael@0 4591 this.renderingState = RenderingStates.INITIAL;
michael@0 4592
michael@0 4593 this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
michael@0 4594 this.pdfPage = pdfPage;
michael@0 4595 this.pdfPageRotate = pdfPage.rotate;
michael@0 4596 var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
michael@0 4597 this.viewport = pdfPage.getViewport(1, totalRotation);
michael@0 4598 this.update();
michael@0 4599 };
michael@0 4600
michael@0 4601 this.update = function thumbnailViewUpdate(rotation) {
michael@0 4602 if (rotation !== undefined) {
michael@0 4603 this.rotation = rotation;
michael@0 4604 }
michael@0 4605 var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
michael@0 4606 this.viewport = this.viewport.clone({
michael@0 4607 scale: 1,
michael@0 4608 rotation: totalRotation
michael@0 4609 });
michael@0 4610 this.pageWidth = this.viewport.width;
michael@0 4611 this.pageHeight = this.viewport.height;
michael@0 4612 this.pageRatio = this.pageWidth / this.pageHeight;
michael@0 4613
michael@0 4614 this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
michael@0 4615 this.scale = (this.canvasWidth / this.pageWidth);
michael@0 4616
michael@0 4617 div.removeAttribute('data-loaded');
michael@0 4618 ring.textContent = '';
michael@0 4619 ring.style.width = this.canvasWidth + 'px';
michael@0 4620 ring.style.height = this.canvasHeight + 'px';
michael@0 4621
michael@0 4622 this.hasImage = false;
michael@0 4623 this.renderingState = RenderingStates.INITIAL;
michael@0 4624 this.resume = null;
michael@0 4625 };
michael@0 4626
michael@0 4627 this.getPageDrawContext = function thumbnailViewGetPageDrawContext() {
michael@0 4628 var canvas = document.createElement('canvas');
michael@0 4629 canvas.id = 'thumbnail' + id;
michael@0 4630
michael@0 4631 canvas.width = this.canvasWidth;
michael@0 4632 canvas.height = this.canvasHeight;
michael@0 4633 canvas.className = 'thumbnailImage';
michael@0 4634 canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
michael@0 4635 {page: id}, 'Thumbnail of Page {{page}}'));
michael@0 4636
michael@0 4637 div.setAttribute('data-loaded', true);
michael@0 4638
michael@0 4639 ring.appendChild(canvas);
michael@0 4640
michael@0 4641 var ctx = canvas.getContext('2d');
michael@0 4642 ctx.save();
michael@0 4643 ctx.fillStyle = 'rgb(255, 255, 255)';
michael@0 4644 ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
michael@0 4645 ctx.restore();
michael@0 4646 return ctx;
michael@0 4647 };
michael@0 4648
michael@0 4649 this.drawingRequired = function thumbnailViewDrawingRequired() {
michael@0 4650 return !this.hasImage;
michael@0 4651 };
michael@0 4652
michael@0 4653 this.draw = function thumbnailViewDraw(callback) {
michael@0 4654 if (!this.pdfPage) {
michael@0 4655 var promise = PDFView.getPage(this.id);
michael@0 4656 promise.then(function(pdfPage) {
michael@0 4657 this.setPdfPage(pdfPage);
michael@0 4658 this.draw(callback);
michael@0 4659 }.bind(this));
michael@0 4660 return;
michael@0 4661 }
michael@0 4662
michael@0 4663 if (this.renderingState !== RenderingStates.INITIAL) {
michael@0 4664 console.error('Must be in new state before drawing');
michael@0 4665 }
michael@0 4666
michael@0 4667 this.renderingState = RenderingStates.RUNNING;
michael@0 4668 if (this.hasImage) {
michael@0 4669 callback();
michael@0 4670 return;
michael@0 4671 }
michael@0 4672
michael@0 4673 var self = this;
michael@0 4674 var ctx = this.getPageDrawContext();
michael@0 4675 var drawViewport = this.viewport.clone({ scale: this.scale });
michael@0 4676 var renderContext = {
michael@0 4677 canvasContext: ctx,
michael@0 4678 viewport: drawViewport,
michael@0 4679 continueCallback: function(cont) {
michael@0 4680 if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) {
michael@0 4681 self.renderingState = RenderingStates.PAUSED;
michael@0 4682 self.resume = function() {
michael@0 4683 self.renderingState = RenderingStates.RUNNING;
michael@0 4684 cont();
michael@0 4685 };
michael@0 4686 return;
michael@0 4687 }
michael@0 4688 cont();
michael@0 4689 }
michael@0 4690 };
michael@0 4691 this.pdfPage.render(renderContext).promise.then(
michael@0 4692 function pdfPageRenderCallback() {
michael@0 4693 self.renderingState = RenderingStates.FINISHED;
michael@0 4694 callback();
michael@0 4695 },
michael@0 4696 function pdfPageRenderError(error) {
michael@0 4697 self.renderingState = RenderingStates.FINISHED;
michael@0 4698 callback();
michael@0 4699 }
michael@0 4700 );
michael@0 4701 this.hasImage = true;
michael@0 4702 };
michael@0 4703
michael@0 4704 this.setImage = function thumbnailViewSetImage(img) {
michael@0 4705 if (!this.pdfPage) {
michael@0 4706 var promise = PDFView.getPage(this.id);
michael@0 4707 promise.then(function(pdfPage) {
michael@0 4708 this.setPdfPage(pdfPage);
michael@0 4709 this.setImage(img);
michael@0 4710 }.bind(this));
michael@0 4711 return;
michael@0 4712 }
michael@0 4713 if (this.hasImage || !img) {
michael@0 4714 return;
michael@0 4715 }
michael@0 4716 this.renderingState = RenderingStates.FINISHED;
michael@0 4717 var ctx = this.getPageDrawContext();
michael@0 4718 ctx.drawImage(img, 0, 0, img.width, img.height,
michael@0 4719 0, 0, ctx.canvas.width, ctx.canvas.height);
michael@0 4720
michael@0 4721 this.hasImage = true;
michael@0 4722 };
michael@0 4723 };
michael@0 4724
michael@0 4725
michael@0 4726 var FIND_SCROLL_OFFSET_TOP = -50;
michael@0 4727 var FIND_SCROLL_OFFSET_LEFT = -400;
michael@0 4728
michael@0 4729 /**
michael@0 4730 * TextLayerBuilder provides text-selection
michael@0 4731 * functionality for the PDF. It does this
michael@0 4732 * by creating overlay divs over the PDF
michael@0 4733 * text. This divs contain text that matches
michael@0 4734 * the PDF text they are overlaying. This
michael@0 4735 * object also provides for a way to highlight
michael@0 4736 * text that is being searched for.
michael@0 4737 */
michael@0 4738 var TextLayerBuilder = function textLayerBuilder(options) {
michael@0 4739 var textLayerFrag = document.createDocumentFragment();
michael@0 4740
michael@0 4741 this.textLayerDiv = options.textLayerDiv;
michael@0 4742 this.layoutDone = false;
michael@0 4743 this.divContentDone = false;
michael@0 4744 this.pageIdx = options.pageIndex;
michael@0 4745 this.matches = [];
michael@0 4746 this.lastScrollSource = options.lastScrollSource;
michael@0 4747 this.viewport = options.viewport;
michael@0 4748 this.isViewerInPresentationMode = options.isViewerInPresentationMode;
michael@0 4749 this.textDivs = [];
michael@0 4750
michael@0 4751 if (typeof PDFFindController === 'undefined') {
michael@0 4752 window.PDFFindController = null;
michael@0 4753 }
michael@0 4754
michael@0 4755 if (typeof this.lastScrollSource === 'undefined') {
michael@0 4756 this.lastScrollSource = null;
michael@0 4757 }
michael@0 4758
michael@0 4759 this.renderLayer = function textLayerBuilderRenderLayer() {
michael@0 4760 var textDivs = this.textDivs;
michael@0 4761 var canvas = document.createElement('canvas');
michael@0 4762 var ctx = canvas.getContext('2d');
michael@0 4763
michael@0 4764 // No point in rendering so many divs as it'd make the browser unusable
michael@0 4765 // even after the divs are rendered
michael@0 4766 var MAX_TEXT_DIVS_TO_RENDER = 100000;
michael@0 4767 if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) {
michael@0 4768 return;
michael@0 4769 }
michael@0 4770
michael@0 4771 for (var i = 0, ii = textDivs.length; i < ii; i++) {
michael@0 4772 var textDiv = textDivs[i];
michael@0 4773 if ('isWhitespace' in textDiv.dataset) {
michael@0 4774 continue;
michael@0 4775 }
michael@0 4776
michael@0 4777 ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
michael@0 4778 var width = ctx.measureText(textDiv.textContent).width;
michael@0 4779
michael@0 4780 if (width > 0) {
michael@0 4781 textLayerFrag.appendChild(textDiv);
michael@0 4782 var textScale = textDiv.dataset.canvasWidth / width;
michael@0 4783 var rotation = textDiv.dataset.angle;
michael@0 4784 var transform = 'scale(' + textScale + ', 1)';
michael@0 4785 transform = 'rotate(' + rotation + 'deg) ' + transform;
michael@0 4786 CustomStyle.setProp('transform' , textDiv, transform);
michael@0 4787 CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
michael@0 4788 }
michael@0 4789 }
michael@0 4790
michael@0 4791 this.textLayerDiv.appendChild(textLayerFrag);
michael@0 4792 this.renderingDone = true;
michael@0 4793 this.updateMatches();
michael@0 4794 };
michael@0 4795
michael@0 4796 this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
michael@0 4797 // Schedule renderLayout() if user has been scrolling, otherwise
michael@0 4798 // run it right away
michael@0 4799 var RENDER_DELAY = 200; // in ms
michael@0 4800 var self = this;
michael@0 4801 var lastScroll = (this.lastScrollSource === null ?
michael@0 4802 0 : this.lastScrollSource.lastScroll);
michael@0 4803
michael@0 4804 if (Date.now() - lastScroll > RENDER_DELAY) {
michael@0 4805 // Render right away
michael@0 4806 this.renderLayer();
michael@0 4807 } else {
michael@0 4808 // Schedule
michael@0 4809 if (this.renderTimer) {
michael@0 4810 clearTimeout(this.renderTimer);
michael@0 4811 }
michael@0 4812 this.renderTimer = setTimeout(function() {
michael@0 4813 self.setupRenderLayoutTimer();
michael@0 4814 }, RENDER_DELAY);
michael@0 4815 }
michael@0 4816 };
michael@0 4817
michael@0 4818 this.appendText = function textLayerBuilderAppendText(geom, styles) {
michael@0 4819 var style = styles[geom.fontName];
michael@0 4820 var textDiv = document.createElement('div');
michael@0 4821 this.textDivs.push(textDiv);
michael@0 4822 if (!/\S/.test(geom.str)) {
michael@0 4823 textDiv.dataset.isWhitespace = true;
michael@0 4824 return;
michael@0 4825 }
michael@0 4826 var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
michael@0 4827 var angle = Math.atan2(tx[1], tx[0]);
michael@0 4828 if (style.vertical) {
michael@0 4829 angle += Math.PI / 2;
michael@0 4830 }
michael@0 4831 var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
michael@0 4832 var fontAscent = (style.ascent ? style.ascent * fontHeight :
michael@0 4833 (style.descent ? (1 + style.descent) * fontHeight : fontHeight));
michael@0 4834
michael@0 4835 textDiv.style.position = 'absolute';
michael@0 4836 textDiv.style.left = (tx[4] + (fontAscent * Math.sin(angle))) + 'px';
michael@0 4837 textDiv.style.top = (tx[5] - (fontAscent * Math.cos(angle))) + 'px';
michael@0 4838 textDiv.style.fontSize = fontHeight + 'px';
michael@0 4839 textDiv.style.fontFamily = style.fontFamily;
michael@0 4840
michael@0 4841 textDiv.textContent = geom.str;
michael@0 4842 textDiv.dataset.fontName = geom.fontName;
michael@0 4843 textDiv.dataset.angle = angle * (180 / Math.PI);
michael@0 4844 if (style.vertical) {
michael@0 4845 textDiv.dataset.canvasWidth = geom.height * this.viewport.scale;
michael@0 4846 } else {
michael@0 4847 textDiv.dataset.canvasWidth = geom.width * this.viewport.scale;
michael@0 4848 }
michael@0 4849
michael@0 4850 };
michael@0 4851
michael@0 4852 this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
michael@0 4853 this.textContent = textContent;
michael@0 4854
michael@0 4855 var textItems = textContent.items;
michael@0 4856 for (var i = 0; i < textItems.length; i++) {
michael@0 4857 this.appendText(textItems[i], textContent.styles);
michael@0 4858 }
michael@0 4859 this.divContentDone = true;
michael@0 4860
michael@0 4861 this.setupRenderLayoutTimer();
michael@0 4862 };
michael@0 4863
michael@0 4864 this.convertMatches = function textLayerBuilderConvertMatches(matches) {
michael@0 4865 var i = 0;
michael@0 4866 var iIndex = 0;
michael@0 4867 var bidiTexts = this.textContent.items;
michael@0 4868 var end = bidiTexts.length - 1;
michael@0 4869 var queryLen = (PDFFindController === null ?
michael@0 4870 0 : PDFFindController.state.query.length);
michael@0 4871
michael@0 4872 var ret = [];
michael@0 4873
michael@0 4874 // Loop over all the matches.
michael@0 4875 for (var m = 0; m < matches.length; m++) {
michael@0 4876 var matchIdx = matches[m];
michael@0 4877 // # Calculate the begin position.
michael@0 4878
michael@0 4879 // Loop over the divIdxs.
michael@0 4880 while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
michael@0 4881 iIndex += bidiTexts[i].str.length;
michael@0 4882 i++;
michael@0 4883 }
michael@0 4884
michael@0 4885 // TODO: Do proper handling here if something goes wrong.
michael@0 4886 if (i == bidiTexts.length) {
michael@0 4887 console.error('Could not find matching mapping');
michael@0 4888 }
michael@0 4889
michael@0 4890 var match = {
michael@0 4891 begin: {
michael@0 4892 divIdx: i,
michael@0 4893 offset: matchIdx - iIndex
michael@0 4894 }
michael@0 4895 };
michael@0 4896
michael@0 4897 // # Calculate the end position.
michael@0 4898 matchIdx += queryLen;
michael@0 4899
michael@0 4900 // Somewhat same array as above, but use a > instead of >= to get the end
michael@0 4901 // position right.
michael@0 4902 while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
michael@0 4903 iIndex += bidiTexts[i].str.length;
michael@0 4904 i++;
michael@0 4905 }
michael@0 4906
michael@0 4907 match.end = {
michael@0 4908 divIdx: i,
michael@0 4909 offset: matchIdx - iIndex
michael@0 4910 };
michael@0 4911 ret.push(match);
michael@0 4912 }
michael@0 4913
michael@0 4914 return ret;
michael@0 4915 };
michael@0 4916
michael@0 4917 this.renderMatches = function textLayerBuilder_renderMatches(matches) {
michael@0 4918 // Early exit if there is nothing to render.
michael@0 4919 if (matches.length === 0) {
michael@0 4920 return;
michael@0 4921 }
michael@0 4922
michael@0 4923 var bidiTexts = this.textContent.items;
michael@0 4924 var textDivs = this.textDivs;
michael@0 4925 var prevEnd = null;
michael@0 4926 var isSelectedPage = (PDFFindController === null ?
michael@0 4927 false : (this.pageIdx === PDFFindController.selected.pageIdx));
michael@0 4928
michael@0 4929 var selectedMatchIdx = (PDFFindController === null ?
michael@0 4930 -1 : PDFFindController.selected.matchIdx);
michael@0 4931
michael@0 4932 var highlightAll = (PDFFindController === null ?
michael@0 4933 false : PDFFindController.state.highlightAll);
michael@0 4934
michael@0 4935 var infty = {
michael@0 4936 divIdx: -1,
michael@0 4937 offset: undefined
michael@0 4938 };
michael@0 4939
michael@0 4940 function beginText(begin, className) {
michael@0 4941 var divIdx = begin.divIdx;
michael@0 4942 var div = textDivs[divIdx];
michael@0 4943 div.textContent = '';
michael@0 4944 appendTextToDiv(divIdx, 0, begin.offset, className);
michael@0 4945 }
michael@0 4946
michael@0 4947 function appendText(from, to, className) {
michael@0 4948 appendTextToDiv(from.divIdx, from.offset, to.offset, className);
michael@0 4949 }
michael@0 4950
michael@0 4951 function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
michael@0 4952 var div = textDivs[divIdx];
michael@0 4953
michael@0 4954 var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
michael@0 4955 var node = document.createTextNode(content);
michael@0 4956 if (className) {
michael@0 4957 var span = document.createElement('span');
michael@0 4958 span.className = className;
michael@0 4959 span.appendChild(node);
michael@0 4960 div.appendChild(span);
michael@0 4961 return;
michael@0 4962 }
michael@0 4963 div.appendChild(node);
michael@0 4964 }
michael@0 4965
michael@0 4966 function highlightDiv(divIdx, className) {
michael@0 4967 textDivs[divIdx].className = className;
michael@0 4968 }
michael@0 4969
michael@0 4970 var i0 = selectedMatchIdx, i1 = i0 + 1, i;
michael@0 4971
michael@0 4972 if (highlightAll) {
michael@0 4973 i0 = 0;
michael@0 4974 i1 = matches.length;
michael@0 4975 } else if (!isSelectedPage) {
michael@0 4976 // Not highlighting all and this isn't the selected page, so do nothing.
michael@0 4977 return;
michael@0 4978 }
michael@0 4979
michael@0 4980 for (i = i0; i < i1; i++) {
michael@0 4981 var match = matches[i];
michael@0 4982 var begin = match.begin;
michael@0 4983 var end = match.end;
michael@0 4984
michael@0 4985 var isSelected = isSelectedPage && i === selectedMatchIdx;
michael@0 4986 var highlightSuffix = (isSelected ? ' selected' : '');
michael@0 4987 if (isSelected && !this.isViewerInPresentationMode) {
michael@0 4988 scrollIntoView(textDivs[begin.divIdx], { top: FIND_SCROLL_OFFSET_TOP,
michael@0 4989 left: FIND_SCROLL_OFFSET_LEFT });
michael@0 4990 }
michael@0 4991
michael@0 4992 // Match inside new div.
michael@0 4993 if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
michael@0 4994 // If there was a previous div, then add the text at the end
michael@0 4995 if (prevEnd !== null) {
michael@0 4996 appendText(prevEnd, infty);
michael@0 4997 }
michael@0 4998 // clears the divs and set the content until the begin point.
michael@0 4999 beginText(begin);
michael@0 5000 } else {
michael@0 5001 appendText(prevEnd, begin);
michael@0 5002 }
michael@0 5003
michael@0 5004 if (begin.divIdx === end.divIdx) {
michael@0 5005 appendText(begin, end, 'highlight' + highlightSuffix);
michael@0 5006 } else {
michael@0 5007 appendText(begin, infty, 'highlight begin' + highlightSuffix);
michael@0 5008 for (var n = begin.divIdx + 1; n < end.divIdx; n++) {
michael@0 5009 highlightDiv(n, 'highlight middle' + highlightSuffix);
michael@0 5010 }
michael@0 5011 beginText(end, 'highlight end' + highlightSuffix);
michael@0 5012 }
michael@0 5013 prevEnd = end;
michael@0 5014 }
michael@0 5015
michael@0 5016 if (prevEnd) {
michael@0 5017 appendText(prevEnd, infty);
michael@0 5018 }
michael@0 5019 };
michael@0 5020
michael@0 5021 this.updateMatches = function textLayerUpdateMatches() {
michael@0 5022 // Only show matches, once all rendering is done.
michael@0 5023 if (!this.renderingDone) {
michael@0 5024 return;
michael@0 5025 }
michael@0 5026
michael@0 5027 // Clear out all matches.
michael@0 5028 var matches = this.matches;
michael@0 5029 var textDivs = this.textDivs;
michael@0 5030 var bidiTexts = this.textContent.items;
michael@0 5031 var clearedUntilDivIdx = -1;
michael@0 5032
michael@0 5033 // Clear out all current matches.
michael@0 5034 for (var i = 0; i < matches.length; i++) {
michael@0 5035 var match = matches[i];
michael@0 5036 var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
michael@0 5037 for (var n = begin; n <= match.end.divIdx; n++) {
michael@0 5038 var div = textDivs[n];
michael@0 5039 div.textContent = bidiTexts[n].str;
michael@0 5040 div.className = '';
michael@0 5041 }
michael@0 5042 clearedUntilDivIdx = match.end.divIdx + 1;
michael@0 5043 }
michael@0 5044
michael@0 5045 if (PDFFindController === null || !PDFFindController.active) {
michael@0 5046 return;
michael@0 5047 }
michael@0 5048
michael@0 5049 // Convert the matches on the page controller into the match format used
michael@0 5050 // for the textLayer.
michael@0 5051 this.matches = matches = (this.convertMatches(PDFFindController === null ?
michael@0 5052 [] : (PDFFindController.pageMatches[this.pageIdx] || [])));
michael@0 5053
michael@0 5054 this.renderMatches(this.matches);
michael@0 5055 };
michael@0 5056 };
michael@0 5057
michael@0 5058
michael@0 5059
michael@0 5060 var DocumentOutlineView = function documentOutlineView(outline) {
michael@0 5061 var outlineView = document.getElementById('outlineView');
michael@0 5062 while (outlineView.firstChild) {
michael@0 5063 outlineView.removeChild(outlineView.firstChild);
michael@0 5064 }
michael@0 5065
michael@0 5066 if (!outline) {
michael@0 5067 if (!outlineView.classList.contains('hidden')) {
michael@0 5068 PDFView.switchSidebarView('thumbs');
michael@0 5069 }
michael@0 5070 return;
michael@0 5071 }
michael@0 5072
michael@0 5073 function bindItemLink(domObj, item) {
michael@0 5074 domObj.href = PDFView.getDestinationHash(item.dest);
michael@0 5075 domObj.onclick = function documentOutlineViewOnclick(e) {
michael@0 5076 PDFView.navigateTo(item.dest);
michael@0 5077 return false;
michael@0 5078 };
michael@0 5079 }
michael@0 5080
michael@0 5081
michael@0 5082 var queue = [{parent: outlineView, items: outline}];
michael@0 5083 while (queue.length > 0) {
michael@0 5084 var levelData = queue.shift();
michael@0 5085 var i, n = levelData.items.length;
michael@0 5086 for (i = 0; i < n; i++) {
michael@0 5087 var item = levelData.items[i];
michael@0 5088 var div = document.createElement('div');
michael@0 5089 div.className = 'outlineItem';
michael@0 5090 var a = document.createElement('a');
michael@0 5091 bindItemLink(a, item);
michael@0 5092 a.textContent = item.title;
michael@0 5093 div.appendChild(a);
michael@0 5094
michael@0 5095 if (item.items.length > 0) {
michael@0 5096 var itemsDiv = document.createElement('div');
michael@0 5097 itemsDiv.className = 'outlineItems';
michael@0 5098 div.appendChild(itemsDiv);
michael@0 5099 queue.push({parent: itemsDiv, items: item.items});
michael@0 5100 }
michael@0 5101
michael@0 5102 levelData.parent.appendChild(div);
michael@0 5103 }
michael@0 5104 }
michael@0 5105 };
michael@0 5106
michael@0 5107 var DocumentAttachmentsView = function documentAttachmentsView(attachments) {
michael@0 5108 var attachmentsView = document.getElementById('attachmentsView');
michael@0 5109 while (attachmentsView.firstChild) {
michael@0 5110 attachmentsView.removeChild(attachmentsView.firstChild);
michael@0 5111 }
michael@0 5112
michael@0 5113 if (!attachments) {
michael@0 5114 if (!attachmentsView.classList.contains('hidden')) {
michael@0 5115 PDFView.switchSidebarView('thumbs');
michael@0 5116 }
michael@0 5117 return;
michael@0 5118 }
michael@0 5119
michael@0 5120 function bindItemLink(domObj, item) {
michael@0 5121 domObj.href = '#';
michael@0 5122 domObj.onclick = function documentAttachmentsViewOnclick(e) {
michael@0 5123 var downloadManager = new DownloadManager();
michael@0 5124 downloadManager.downloadData(item.content, getFileName(item.filename),
michael@0 5125 '');
michael@0 5126 return false;
michael@0 5127 };
michael@0 5128 }
michael@0 5129
michael@0 5130 var names = Object.keys(attachments).sort(function(a,b) {
michael@0 5131 return a.toLowerCase().localeCompare(b.toLowerCase());
michael@0 5132 });
michael@0 5133 for (var i = 0, ii = names.length; i < ii; i++) {
michael@0 5134 var item = attachments[names[i]];
michael@0 5135 var div = document.createElement('div');
michael@0 5136 div.className = 'attachmentsItem';
michael@0 5137 var a = document.createElement('a');
michael@0 5138 bindItemLink(a, item);
michael@0 5139 a.textContent = getFileName(item.filename);
michael@0 5140 div.appendChild(a);
michael@0 5141 attachmentsView.appendChild(div);
michael@0 5142 }
michael@0 5143 };
michael@0 5144
michael@0 5145
michael@0 5146 function webViewerLoad(evt) {
michael@0 5147 PDFView.initialize().then(webViewerInitialized);
michael@0 5148 }
michael@0 5149
michael@0 5150 function webViewerInitialized() {
michael@0 5151 var file = window.location.href.split('#')[0];
michael@0 5152
michael@0 5153 document.getElementById('openFile').setAttribute('hidden', 'true');
michael@0 5154 document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true');
michael@0 5155
michael@0 5156 // Special debugging flags in the hash section of the URL.
michael@0 5157 var hash = document.location.hash.substring(1);
michael@0 5158 var hashParams = PDFView.parseQueryString(hash);
michael@0 5159
michael@0 5160 if ('disableWorker' in hashParams) {
michael@0 5161 PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
michael@0 5162 }
michael@0 5163
michael@0 5164 if ('disableRange' in hashParams) {
michael@0 5165 PDFJS.disableRange = (hashParams['disableRange'] === 'true');
michael@0 5166 }
michael@0 5167
michael@0 5168 if ('disableAutoFetch' in hashParams) {
michael@0 5169 PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true');
michael@0 5170 }
michael@0 5171
michael@0 5172 if ('disableFontFace' in hashParams) {
michael@0 5173 PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true');
michael@0 5174 }
michael@0 5175
michael@0 5176 if ('disableHistory' in hashParams) {
michael@0 5177 PDFJS.disableHistory = (hashParams['disableHistory'] === 'true');
michael@0 5178 }
michael@0 5179
michael@0 5180 if ('webgl' in hashParams) {
michael@0 5181 PDFJS.disableWebGL = (hashParams['webgl'] !== 'true');
michael@0 5182 }
michael@0 5183
michael@0 5184 if ('useOnlyCssZoom' in hashParams) {
michael@0 5185 USE_ONLY_CSS_ZOOM = (hashParams['useOnlyCssZoom'] === 'true');
michael@0 5186 }
michael@0 5187
michael@0 5188 if ('verbosity' in hashParams) {
michael@0 5189 PDFJS.verbosity = hashParams['verbosity'] | 0;
michael@0 5190 }
michael@0 5191
michael@0 5192 if ('ignoreCurrentPositionOnZoom' in hashParams) {
michael@0 5193 IGNORE_CURRENT_POSITION_ON_ZOOM =
michael@0 5194 (hashParams['ignoreCurrentPositionOnZoom'] === 'true');
michael@0 5195 }
michael@0 5196
michael@0 5197
michael@0 5198
michael@0 5199 if (!PDFView.supportsDocumentFonts) {
michael@0 5200 PDFJS.disableFontFace = true;
michael@0 5201 console.warn(mozL10n.get('web_fonts_disabled', null,
michael@0 5202 'Web fonts are disabled: unable to use embedded PDF fonts.'));
michael@0 5203 }
michael@0 5204
michael@0 5205 if ('textLayer' in hashParams) {
michael@0 5206 switch (hashParams['textLayer']) {
michael@0 5207 case 'off':
michael@0 5208 PDFJS.disableTextLayer = true;
michael@0 5209 break;
michael@0 5210 case 'visible':
michael@0 5211 case 'shadow':
michael@0 5212 case 'hover':
michael@0 5213 var viewer = document.getElementById('viewer');
michael@0 5214 viewer.classList.add('textLayer-' + hashParams['textLayer']);
michael@0 5215 break;
michael@0 5216 }
michael@0 5217 }
michael@0 5218
michael@0 5219 if ('pdfBug' in hashParams && FirefoxCom.requestSync('pdfBugEnabled')) {
michael@0 5220 PDFJS.pdfBug = true;
michael@0 5221 var pdfBug = hashParams['pdfBug'];
michael@0 5222 var enabled = pdfBug.split(',');
michael@0 5223 PDFBug.enable(enabled);
michael@0 5224 PDFBug.init();
michael@0 5225 }
michael@0 5226
michael@0 5227 if (!PDFView.supportsPrinting) {
michael@0 5228 document.getElementById('print').classList.add('hidden');
michael@0 5229 document.getElementById('secondaryPrint').classList.add('hidden');
michael@0 5230 }
michael@0 5231
michael@0 5232 if (!PDFView.supportsFullscreen) {
michael@0 5233 document.getElementById('presentationMode').classList.add('hidden');
michael@0 5234 document.getElementById('secondaryPresentationMode').
michael@0 5235 classList.add('hidden');
michael@0 5236 }
michael@0 5237
michael@0 5238 if (PDFView.supportsIntegratedFind) {
michael@0 5239 document.getElementById('viewFind').classList.add('hidden');
michael@0 5240 }
michael@0 5241
michael@0 5242 // Listen for unsuporrted features to trigger the fallback UI.
michael@0 5243 PDFJS.UnsupportedManager.listen(PDFView.fallback.bind(PDFView));
michael@0 5244
michael@0 5245 // Suppress context menus for some controls
michael@0 5246 document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler;
michael@0 5247
michael@0 5248 var mainContainer = document.getElementById('mainContainer');
michael@0 5249 var outerContainer = document.getElementById('outerContainer');
michael@0 5250 mainContainer.addEventListener('transitionend', function(e) {
michael@0 5251 if (e.target == mainContainer) {
michael@0 5252 var event = document.createEvent('UIEvents');
michael@0 5253 event.initUIEvent('resize', false, false, window, 0);
michael@0 5254 window.dispatchEvent(event);
michael@0 5255 outerContainer.classList.remove('sidebarMoving');
michael@0 5256 }
michael@0 5257 }, true);
michael@0 5258
michael@0 5259 document.getElementById('sidebarToggle').addEventListener('click',
michael@0 5260 function() {
michael@0 5261 this.classList.toggle('toggled');
michael@0 5262 outerContainer.classList.add('sidebarMoving');
michael@0 5263 outerContainer.classList.toggle('sidebarOpen');
michael@0 5264 PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen');
michael@0 5265 PDFView.renderHighestPriority();
michael@0 5266 });
michael@0 5267
michael@0 5268 document.getElementById('viewThumbnail').addEventListener('click',
michael@0 5269 function() {
michael@0 5270 PDFView.switchSidebarView('thumbs');
michael@0 5271 });
michael@0 5272
michael@0 5273 document.getElementById('viewOutline').addEventListener('click',
michael@0 5274 function() {
michael@0 5275 PDFView.switchSidebarView('outline');
michael@0 5276 });
michael@0 5277
michael@0 5278 document.getElementById('viewAttachments').addEventListener('click',
michael@0 5279 function() {
michael@0 5280 PDFView.switchSidebarView('attachments');
michael@0 5281 });
michael@0 5282
michael@0 5283 document.getElementById('previous').addEventListener('click',
michael@0 5284 function() {
michael@0 5285 PDFView.page--;
michael@0 5286 });
michael@0 5287
michael@0 5288 document.getElementById('next').addEventListener('click',
michael@0 5289 function() {
michael@0 5290 PDFView.page++;
michael@0 5291 });
michael@0 5292
michael@0 5293 document.getElementById('zoomIn').addEventListener('click',
michael@0 5294 function() {
michael@0 5295 PDFView.zoomIn();
michael@0 5296 });
michael@0 5297
michael@0 5298 document.getElementById('zoomOut').addEventListener('click',
michael@0 5299 function() {
michael@0 5300 PDFView.zoomOut();
michael@0 5301 });
michael@0 5302
michael@0 5303 document.getElementById('pageNumber').addEventListener('click',
michael@0 5304 function() {
michael@0 5305 this.select();
michael@0 5306 });
michael@0 5307
michael@0 5308 document.getElementById('pageNumber').addEventListener('change',
michael@0 5309 function() {
michael@0 5310 // Handle the user inputting a floating point number.
michael@0 5311 PDFView.page = (this.value | 0);
michael@0 5312
michael@0 5313 if (this.value !== (this.value | 0).toString()) {
michael@0 5314 this.value = PDFView.page;
michael@0 5315 }
michael@0 5316 });
michael@0 5317
michael@0 5318 document.getElementById('scaleSelect').addEventListener('change',
michael@0 5319 function() {
michael@0 5320 PDFView.setScale(this.value);
michael@0 5321 });
michael@0 5322
michael@0 5323 document.getElementById('presentationMode').addEventListener('click',
michael@0 5324 SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar));
michael@0 5325
michael@0 5326 document.getElementById('openFile').addEventListener('click',
michael@0 5327 SecondaryToolbar.openFileClick.bind(SecondaryToolbar));
michael@0 5328
michael@0 5329 document.getElementById('print').addEventListener('click',
michael@0 5330 SecondaryToolbar.printClick.bind(SecondaryToolbar));
michael@0 5331
michael@0 5332 document.getElementById('download').addEventListener('click',
michael@0 5333 SecondaryToolbar.downloadClick.bind(SecondaryToolbar));
michael@0 5334
michael@0 5335 PDFView.setTitleUsingUrl(file);
michael@0 5336 PDFView.initPassiveLoading();
michael@0 5337 return;
michael@0 5338
michael@0 5339 if (file) {
michael@0 5340 PDFView.open(file, 0);
michael@0 5341 }
michael@0 5342 }
michael@0 5343
michael@0 5344 document.addEventListener('DOMContentLoaded', webViewerLoad, true);
michael@0 5345
michael@0 5346 function updateViewarea() {
michael@0 5347
michael@0 5348 if (!PDFView.initialized) {
michael@0 5349 return;
michael@0 5350 }
michael@0 5351 var visible = PDFView.getVisiblePages();
michael@0 5352 var visiblePages = visible.views;
michael@0 5353 if (visiblePages.length === 0) {
michael@0 5354 return;
michael@0 5355 }
michael@0 5356
michael@0 5357 PDFView.renderHighestPriority();
michael@0 5358
michael@0 5359 var currentId = PDFView.page;
michael@0 5360 var firstPage = visible.first;
michael@0 5361
michael@0 5362 for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
michael@0 5363 i < ii; ++i) {
michael@0 5364 var page = visiblePages[i];
michael@0 5365
michael@0 5366 if (page.percent < 100) {
michael@0 5367 break;
michael@0 5368 }
michael@0 5369 if (page.id === PDFView.page) {
michael@0 5370 stillFullyVisible = true;
michael@0 5371 break;
michael@0 5372 }
michael@0 5373 }
michael@0 5374
michael@0 5375 if (!stillFullyVisible) {
michael@0 5376 currentId = visiblePages[0].id;
michael@0 5377 }
michael@0 5378
michael@0 5379 if (!PresentationMode.active) {
michael@0 5380 updateViewarea.inProgress = true; // used in "set page"
michael@0 5381 PDFView.page = currentId;
michael@0 5382 updateViewarea.inProgress = false;
michael@0 5383 }
michael@0 5384
michael@0 5385 var currentScale = PDFView.currentScale;
michael@0 5386 var currentScaleValue = PDFView.currentScaleValue;
michael@0 5387 var normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ?
michael@0 5388 Math.round(currentScale * 10000) / 100 : currentScaleValue;
michael@0 5389
michael@0 5390 var pageNumber = firstPage.id;
michael@0 5391 var pdfOpenParams = '#page=' + pageNumber;
michael@0 5392 pdfOpenParams += '&zoom=' + normalizedScaleValue;
michael@0 5393 var currentPage = PDFView.pages[pageNumber - 1];
michael@0 5394 var container = PDFView.container;
michael@0 5395 var topLeft = currentPage.getPagePoint((container.scrollLeft - firstPage.x),
michael@0 5396 (container.scrollTop - firstPage.y));
michael@0 5397 var intLeft = Math.round(topLeft[0]);
michael@0 5398 var intTop = Math.round(topLeft[1]);
michael@0 5399 pdfOpenParams += ',' + intLeft + ',' + intTop;
michael@0 5400
michael@0 5401 if (PresentationMode.active || PresentationMode.switchInProgress) {
michael@0 5402 PDFView.currentPosition = null;
michael@0 5403 } else {
michael@0 5404 PDFView.currentPosition = { page: pageNumber, left: intLeft, top: intTop };
michael@0 5405 }
michael@0 5406
michael@0 5407 var store = PDFView.store;
michael@0 5408 store.initializedPromise.then(function() {
michael@0 5409 store.set('exists', true);
michael@0 5410 store.set('page', pageNumber);
michael@0 5411 store.set('zoom', normalizedScaleValue);
michael@0 5412 store.set('scrollLeft', intLeft);
michael@0 5413 store.set('scrollTop', intTop);
michael@0 5414 });
michael@0 5415 var href = PDFView.getAnchorUrl(pdfOpenParams);
michael@0 5416 document.getElementById('viewBookmark').href = href;
michael@0 5417 document.getElementById('secondaryViewBookmark').href = href;
michael@0 5418
michael@0 5419 // Update the current bookmark in the browsing history.
michael@0 5420 PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber);
michael@0 5421 }
michael@0 5422
michael@0 5423 window.addEventListener('resize', function webViewerResize(evt) {
michael@0 5424 if (PDFView.initialized &&
michael@0 5425 (document.getElementById('pageWidthOption').selected ||
michael@0 5426 document.getElementById('pageFitOption').selected ||
michael@0 5427 document.getElementById('pageAutoOption').selected)) {
michael@0 5428 PDFView.setScale(document.getElementById('scaleSelect').value);
michael@0 5429 }
michael@0 5430 updateViewarea();
michael@0 5431
michael@0 5432 // Set the 'max-height' CSS property of the secondary toolbar.
michael@0 5433 SecondaryToolbar.setMaxHeight(PDFView.container);
michael@0 5434 });
michael@0 5435
michael@0 5436 window.addEventListener('hashchange', function webViewerHashchange(evt) {
michael@0 5437 if (PDFHistory.isHashChangeUnlocked) {
michael@0 5438 PDFView.setHash(document.location.hash.substring(1));
michael@0 5439 }
michael@0 5440 });
michael@0 5441
michael@0 5442
michael@0 5443 function selectScaleOption(value) {
michael@0 5444 var options = document.getElementById('scaleSelect').options;
michael@0 5445 var predefinedValueFound = false;
michael@0 5446 for (var i = 0; i < options.length; i++) {
michael@0 5447 var option = options[i];
michael@0 5448 if (option.value != value) {
michael@0 5449 option.selected = false;
michael@0 5450 continue;
michael@0 5451 }
michael@0 5452 option.selected = true;
michael@0 5453 predefinedValueFound = true;
michael@0 5454 }
michael@0 5455 return predefinedValueFound;
michael@0 5456 }
michael@0 5457
michael@0 5458 window.addEventListener('localized', function localized(evt) {
michael@0 5459 document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
michael@0 5460
michael@0 5461 PDFView.animationStartedPromise.then(function() {
michael@0 5462 // Adjust the width of the zoom box to fit the content.
michael@0 5463 // Note: This is only done if the zoom box is actually visible,
michael@0 5464 // since otherwise element.clientWidth will return 0.
michael@0 5465 var container = document.getElementById('scaleSelectContainer');
michael@0 5466 if (container.clientWidth > 0) {
michael@0 5467 var select = document.getElementById('scaleSelect');
michael@0 5468 select.setAttribute('style', 'min-width: inherit;');
michael@0 5469 var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING;
michael@0 5470 select.setAttribute('style', 'min-width: ' +
michael@0 5471 (width + SCALE_SELECT_PADDING) + 'px;');
michael@0 5472 container.setAttribute('style', 'min-width: ' + width + 'px; ' +
michael@0 5473 'max-width: ' + width + 'px;');
michael@0 5474 }
michael@0 5475
michael@0 5476 // Set the 'max-height' CSS property of the secondary toolbar.
michael@0 5477 SecondaryToolbar.setMaxHeight(PDFView.container);
michael@0 5478 });
michael@0 5479 }, true);
michael@0 5480
michael@0 5481 window.addEventListener('scalechange', function scalechange(evt) {
michael@0 5482 document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE);
michael@0 5483 document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE);
michael@0 5484
michael@0 5485 var customScaleOption = document.getElementById('customScaleOption');
michael@0 5486 customScaleOption.selected = false;
michael@0 5487
michael@0 5488 if (!evt.resetAutoSettings &&
michael@0 5489 (document.getElementById('pageWidthOption').selected ||
michael@0 5490 document.getElementById('pageFitOption').selected ||
michael@0 5491 document.getElementById('pageAutoOption').selected)) {
michael@0 5492 updateViewarea();
michael@0 5493 return;
michael@0 5494 }
michael@0 5495
michael@0 5496 var predefinedValueFound = selectScaleOption('' + evt.scale);
michael@0 5497 if (!predefinedValueFound) {
michael@0 5498 customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
michael@0 5499 customScaleOption.selected = true;
michael@0 5500 }
michael@0 5501 updateViewarea();
michael@0 5502 }, true);
michael@0 5503
michael@0 5504 window.addEventListener('pagechange', function pagechange(evt) {
michael@0 5505 var page = evt.pageNumber;
michael@0 5506 if (PDFView.previousPageNumber !== page) {
michael@0 5507 document.getElementById('pageNumber').value = page;
michael@0 5508 var selected = document.querySelector('.thumbnail.selected');
michael@0 5509 if (selected) {
michael@0 5510 selected.classList.remove('selected');
michael@0 5511 }
michael@0 5512 var thumbnail = document.getElementById('thumbnailContainer' + page);
michael@0 5513 thumbnail.classList.add('selected');
michael@0 5514 var visibleThumbs = PDFView.getVisibleThumbs();
michael@0 5515 var numVisibleThumbs = visibleThumbs.views.length;
michael@0 5516
michael@0 5517 // If the thumbnail isn't currently visible, scroll it into view.
michael@0 5518 if (numVisibleThumbs > 0) {
michael@0 5519 var first = visibleThumbs.first.id;
michael@0 5520 // Account for only one thumbnail being visible.
michael@0 5521 var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
michael@0 5522 if (page <= first || page >= last) {
michael@0 5523 scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
michael@0 5524 }
michael@0 5525 }
michael@0 5526 }
michael@0 5527 document.getElementById('previous').disabled = (page <= 1);
michael@0 5528 document.getElementById('next').disabled = (page >= PDFView.pages.length);
michael@0 5529 }, true);
michael@0 5530
michael@0 5531 function handleMouseWheel(evt) {
michael@0 5532 var MOUSE_WHEEL_DELTA_FACTOR = 40;
michael@0 5533 var ticks = (evt.type === 'DOMMouseScroll') ? -evt.detail :
michael@0 5534 evt.wheelDelta / MOUSE_WHEEL_DELTA_FACTOR;
michael@0 5535 var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn';
michael@0 5536
michael@0 5537 if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer
michael@0 5538 evt.preventDefault();
michael@0 5539 PDFView[direction](Math.abs(ticks));
michael@0 5540 } else if (PresentationMode.active) {
michael@0 5541 PDFView.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR);
michael@0 5542 }
michael@0 5543 }
michael@0 5544
michael@0 5545 window.addEventListener('DOMMouseScroll', handleMouseWheel);
michael@0 5546 window.addEventListener('mousewheel', handleMouseWheel);
michael@0 5547
michael@0 5548 window.addEventListener('click', function click(evt) {
michael@0 5549 if (!PresentationMode.active) {
michael@0 5550 if (SecondaryToolbar.opened && PDFView.container.contains(evt.target)) {
michael@0 5551 SecondaryToolbar.close();
michael@0 5552 }
michael@0 5553 } else if (evt.button === 0) {
michael@0 5554 // Necessary since preventDefault() in 'mousedown' won't stop
michael@0 5555 // the event propagation in all circumstances in presentation mode.
michael@0 5556 evt.preventDefault();
michael@0 5557 }
michael@0 5558 }, false);
michael@0 5559
michael@0 5560 window.addEventListener('keydown', function keydown(evt) {
michael@0 5561 if (PasswordPrompt.visible) {
michael@0 5562 return;
michael@0 5563 }
michael@0 5564
michael@0 5565 var handled = false;
michael@0 5566 var cmd = (evt.ctrlKey ? 1 : 0) |
michael@0 5567 (evt.altKey ? 2 : 0) |
michael@0 5568 (evt.shiftKey ? 4 : 0) |
michael@0 5569 (evt.metaKey ? 8 : 0);
michael@0 5570
michael@0 5571 // First, handle the key bindings that are independent whether an input
michael@0 5572 // control is selected or not.
michael@0 5573 if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
michael@0 5574 // either CTRL or META key with optional SHIFT.
michael@0 5575 switch (evt.keyCode) {
michael@0 5576 case 70: // f
michael@0 5577 if (!PDFView.supportsIntegratedFind) {
michael@0 5578 PDFFindBar.open();
michael@0 5579 handled = true;
michael@0 5580 }
michael@0 5581 break;
michael@0 5582 case 71: // g
michael@0 5583 if (!PDFView.supportsIntegratedFind) {
michael@0 5584 PDFFindBar.dispatchEvent('again', cmd === 5 || cmd === 12);
michael@0 5585 handled = true;
michael@0 5586 }
michael@0 5587 break;
michael@0 5588 case 61: // FF/Mac '='
michael@0 5589 case 107: // FF '+' and '='
michael@0 5590 case 187: // Chrome '+'
michael@0 5591 case 171: // FF with German keyboard
michael@0 5592 PDFView.zoomIn();
michael@0 5593 handled = true;
michael@0 5594 break;
michael@0 5595 case 173: // FF/Mac '-'
michael@0 5596 case 109: // FF '-'
michael@0 5597 case 189: // Chrome '-'
michael@0 5598 PDFView.zoomOut();
michael@0 5599 handled = true;
michael@0 5600 break;
michael@0 5601 case 48: // '0'
michael@0 5602 case 96: // '0' on Numpad of Swedish keyboard
michael@0 5603 // keeping it unhandled (to restore page zoom to 100%)
michael@0 5604 setTimeout(function () {
michael@0 5605 // ... and resetting the scale after browser adjusts its scale
michael@0 5606 PDFView.setScale(DEFAULT_SCALE, true);
michael@0 5607 });
michael@0 5608 handled = false;
michael@0 5609 break;
michael@0 5610 }
michael@0 5611 }
michael@0 5612
michael@0 5613
michael@0 5614 // CTRL+ALT or Option+Command
michael@0 5615 if (cmd === 3 || cmd === 10) {
michael@0 5616 switch (evt.keyCode) {
michael@0 5617 case 80: // p
michael@0 5618 SecondaryToolbar.presentationModeClick();
michael@0 5619 handled = true;
michael@0 5620 break;
michael@0 5621 case 71: // g
michael@0 5622 // focuses input#pageNumber field
michael@0 5623 document.getElementById('pageNumber').select();
michael@0 5624 handled = true;
michael@0 5625 break;
michael@0 5626 }
michael@0 5627 }
michael@0 5628
michael@0 5629 if (handled) {
michael@0 5630 evt.preventDefault();
michael@0 5631 return;
michael@0 5632 }
michael@0 5633
michael@0 5634 // Some shortcuts should not get handled if a control/input element
michael@0 5635 // is selected.
michael@0 5636 var curElement = document.activeElement || document.querySelector(':focus');
michael@0 5637 var curElementTagName = curElement && curElement.tagName.toUpperCase();
michael@0 5638 if (curElementTagName === 'INPUT' ||
michael@0 5639 curElementTagName === 'TEXTAREA' ||
michael@0 5640 curElementTagName === 'SELECT') {
michael@0 5641 // Make sure that the secondary toolbar is closed when Escape is pressed.
michael@0 5642 if (evt.keyCode !== 27) { // 'Esc'
michael@0 5643 return;
michael@0 5644 }
michael@0 5645 }
michael@0 5646
michael@0 5647 if (cmd === 0) { // no control key pressed at all.
michael@0 5648 switch (evt.keyCode) {
michael@0 5649 case 38: // up arrow
michael@0 5650 case 33: // pg up
michael@0 5651 case 8: // backspace
michael@0 5652 if (!PresentationMode.active &&
michael@0 5653 PDFView.currentScaleValue !== 'page-fit') {
michael@0 5654 break;
michael@0 5655 }
michael@0 5656 /* in presentation mode */
michael@0 5657 /* falls through */
michael@0 5658 case 37: // left arrow
michael@0 5659 // horizontal scrolling using arrow keys
michael@0 5660 if (PDFView.isHorizontalScrollbarEnabled) {
michael@0 5661 break;
michael@0 5662 }
michael@0 5663 /* falls through */
michael@0 5664 case 75: // 'k'
michael@0 5665 case 80: // 'p'
michael@0 5666 PDFView.page--;
michael@0 5667 handled = true;
michael@0 5668 break;
michael@0 5669 case 27: // esc key
michael@0 5670 if (SecondaryToolbar.opened) {
michael@0 5671 SecondaryToolbar.close();
michael@0 5672 handled = true;
michael@0 5673 }
michael@0 5674 if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) {
michael@0 5675 PDFFindBar.close();
michael@0 5676 handled = true;
michael@0 5677 }
michael@0 5678 break;
michael@0 5679 case 40: // down arrow
michael@0 5680 case 34: // pg down
michael@0 5681 case 32: // spacebar
michael@0 5682 if (!PresentationMode.active &&
michael@0 5683 PDFView.currentScaleValue !== 'page-fit') {
michael@0 5684 break;
michael@0 5685 }
michael@0 5686 /* falls through */
michael@0 5687 case 39: // right arrow
michael@0 5688 // horizontal scrolling using arrow keys
michael@0 5689 if (PDFView.isHorizontalScrollbarEnabled) {
michael@0 5690 break;
michael@0 5691 }
michael@0 5692 /* falls through */
michael@0 5693 case 74: // 'j'
michael@0 5694 case 78: // 'n'
michael@0 5695 PDFView.page++;
michael@0 5696 handled = true;
michael@0 5697 break;
michael@0 5698
michael@0 5699 case 36: // home
michael@0 5700 if (PresentationMode.active) {
michael@0 5701 PDFView.page = 1;
michael@0 5702 handled = true;
michael@0 5703 }
michael@0 5704 break;
michael@0 5705 case 35: // end
michael@0 5706 if (PresentationMode.active) {
michael@0 5707 PDFView.page = PDFView.pdfDocument.numPages;
michael@0 5708 handled = true;
michael@0 5709 }
michael@0 5710 break;
michael@0 5711
michael@0 5712 case 72: // 'h'
michael@0 5713 if (!PresentationMode.active) {
michael@0 5714 HandTool.toggle();
michael@0 5715 }
michael@0 5716 break;
michael@0 5717 case 82: // 'r'
michael@0 5718 PDFView.rotatePages(90);
michael@0 5719 break;
michael@0 5720 }
michael@0 5721 }
michael@0 5722
michael@0 5723 if (cmd === 4) { // shift-key
michael@0 5724 switch (evt.keyCode) {
michael@0 5725 case 32: // spacebar
michael@0 5726 if (!PresentationMode.active &&
michael@0 5727 PDFView.currentScaleValue !== 'page-fit') {
michael@0 5728 break;
michael@0 5729 }
michael@0 5730 PDFView.page--;
michael@0 5731 handled = true;
michael@0 5732 break;
michael@0 5733
michael@0 5734 case 82: // 'r'
michael@0 5735 PDFView.rotatePages(-90);
michael@0 5736 break;
michael@0 5737 }
michael@0 5738 }
michael@0 5739
michael@0 5740 if (!handled && !PresentationMode.active) {
michael@0 5741 // 33=Page Up 34=Page Down 35=End 36=Home
michael@0 5742 // 37=Left 38=Up 39=Right 40=Down
michael@0 5743 if (evt.keyCode >= 33 && evt.keyCode <= 40 &&
michael@0 5744 !PDFView.container.contains(curElement)) {
michael@0 5745 // The page container is not focused, but a page navigation key has been
michael@0 5746 // pressed. Change the focus to the viewer container to make sure that
michael@0 5747 // navigation by keyboard works as expected.
michael@0 5748 PDFView.container.focus();
michael@0 5749 }
michael@0 5750 // 32=Spacebar
michael@0 5751 if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
michael@0 5752 // Workaround for issue in Firefox, that prevents scroll keys from
michael@0 5753 // working when elements with 'tabindex' are focused. (#3498)
michael@0 5754 PDFView.container.blur();
michael@0 5755 }
michael@0 5756 }
michael@0 5757
michael@0 5758 if (cmd === 2) { // alt-key
michael@0 5759 switch (evt.keyCode) {
michael@0 5760 case 37: // left arrow
michael@0 5761 if (PresentationMode.active) {
michael@0 5762 PDFHistory.back();
michael@0 5763 handled = true;
michael@0 5764 }
michael@0 5765 break;
michael@0 5766 case 39: // right arrow
michael@0 5767 if (PresentationMode.active) {
michael@0 5768 PDFHistory.forward();
michael@0 5769 handled = true;
michael@0 5770 }
michael@0 5771 break;
michael@0 5772 }
michael@0 5773 }
michael@0 5774
michael@0 5775 if (handled) {
michael@0 5776 evt.preventDefault();
michael@0 5777 PDFView.clearMouseScrollState();
michael@0 5778 }
michael@0 5779 });
michael@0 5780
michael@0 5781 window.addEventListener('beforeprint', function beforePrint(evt) {
michael@0 5782 PDFView.beforePrint();
michael@0 5783 });
michael@0 5784
michael@0 5785 window.addEventListener('afterprint', function afterPrint(evt) {
michael@0 5786 PDFView.afterPrint();
michael@0 5787 });
michael@0 5788
michael@0 5789 (function animationStartedClosure() {
michael@0 5790 // The offsetParent is not set until the pdf.js iframe or object is visible.
michael@0 5791 // Waiting for first animation.
michael@0 5792 var requestAnimationFrame = window.requestAnimationFrame ||
michael@0 5793 window.mozRequestAnimationFrame ||
michael@0 5794 window.webkitRequestAnimationFrame ||
michael@0 5795 window.oRequestAnimationFrame ||
michael@0 5796 window.msRequestAnimationFrame ||
michael@0 5797 function startAtOnce(callback) { callback(); };
michael@0 5798 PDFView.animationStartedPromise = new Promise(function (resolve) {
michael@0 5799 requestAnimationFrame(function onAnimationFrame() {
michael@0 5800 resolve();
michael@0 5801 });
michael@0 5802 });
michael@0 5803 })();
michael@0 5804
michael@0 5805

mercurial