Wed, 31 Dec 2014 06:09:35 +0100
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 |