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