Projet

Général

Profil

Paste
Télécharger (63,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / pdf_reader / js / viewer.js @ 87dbc3bf

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

    
4
'use strict';
5

    
6
var kDefaultURL = '';
7
var kDefaultScale = 'auto';
8
var kDefaultScaleDelta = 1.1;
9
var kUnknownScale = 0;
10
var kCacheSize = 20;
11
var kCssUnits = 96.0 / 72.0;
12
var kScrollbarPadding = 40;
13
var kMinScale = 0.25;
14
var kMaxScale = 4.0;
15
var kImageDirectory = './images/';
16
var kSettingsMemory = 20;
17
var RenderingStates = {
18
  INITIAL: 0,
19
  RUNNING: 1,
20
  PAUSED: 2,
21
  FINISHED: 3
22
};
23

    
24

    
25
var mozL10n = document.mozL10n || document.webL10n;
26

    
27
function getFileName(url) {
28
  var anchor = url.indexOf('#');
29
  var query = url.indexOf('?');
30
  var end = Math.min(
31
    anchor > 0 ? anchor : url.length,
32
    query > 0 ? query : url.length);
33
  return url.substring(url.lastIndexOf('/', end) + 1, end);
34
}
35

    
36
var Cache = function cacheCache(size) {
37
  var data = [];
38
  this.push = function cachePush(view) {
39
    var i = data.indexOf(view);
40
    if (i >= 0)
41
      data.splice(i);
42
    data.push(view);
43
    if (data.length > size)
44
      data.shift().destroy();
45
  };
46
};
47

    
48
var ProgressBar = (function ProgressBarClosure() {
49

    
50
  function clamp(v, min, max) {
51
    return Math.min(Math.max(v, min), max);
52
  }
53

    
54
  function ProgressBar(id, opts) {
55

    
56
    // Fetch the sub-elements for later
57
    this.div = document.querySelector(id + ' .progress');
58

    
59
    // Get options, with sensible defaults
60
    this.height = opts.height || 100;
61
    this.width = opts.width || 100;
62
    this.units = opts.units || '%';
63
    this.percent = opts.percent || 0;
64

    
65
    // Initialize heights
66
    this.div.style.height = this.height + this.units;
67
  }
68

    
69
  ProgressBar.prototype = {
70

    
71
    updateBar: function ProgressBar_updateBar() {
72
      var progressSize = this.width * this._percent / 100;
73

    
74
      if (this._percent > 95)
75
        this.div.classList.add('full');
76

    
77
      this.div.style.width = progressSize + this.units;
78
    },
79

    
80
    get percent() {
81
      return this._percent;
82
    },
83

    
84
    set percent(val) {
85
      this._percent = clamp(val, 0, 100);
86
      this.updateBar();
87
    }
88
  };
89

    
90
  return ProgressBar;
91
})();
92

    
93

    
94
// Settings Manager - This is a utility for saving settings
95
// First we see if localStorage is available
96
// If not, we use FUEL in FF
97
var Settings = (function SettingsClosure() {
98
  var isLocalStorageEnabled = (function localStorageEnabledTest() {
99
    // Feature test as per http://diveintohtml5.info/storage.html
100
    // The additional localStorage call is to get around a FF quirk, see
101
    // bug #495747 in bugzilla
102
    try {
103
      return 'localStorage' in window && window['localStorage'] !== null &&
104
          localStorage;
105
    } catch (e) {
106
      return false;
107
    }
108
  })();
109

    
110
  function Settings(fingerprint) {
111
    var database = null;
112
    var index;
113
    if (isLocalStorageEnabled)
114
      database = localStorage.getItem('database') || '{}';
115
    else
116
      return;
117

    
118
    database = JSON.parse(database);
119
    if (!('files' in database))
120
      database.files = [];
121
    if (database.files.length >= kSettingsMemory)
122
      database.files.shift();
123
    for (var i = 0, length = database.files.length; i < length; i++) {
124
      var branch = database.files[i];
125
      if (branch.fingerprint == fingerprint) {
126
        index = i;
127
        break;
128
      }
129
    }
130
    if (typeof index != 'number')
131
      index = database.files.push({fingerprint: fingerprint}) - 1;
132
    this.file = database.files[index];
133
    this.database = database;
134
  }
135

    
136
  Settings.prototype = {
137
    set: function settingsSet(name, val) {
138
      if (!('file' in this))
139
        return false;
140

    
141
      var file = this.file;
142
      file[name] = val;
143
      var database = JSON.stringify(this.database);
144
      if (isLocalStorageEnabled)
145
        localStorage.setItem('database', database);
146
    },
147

    
148
    get: function settingsGet(name, defaultValue) {
149
      if (!('file' in this))
150
        return defaultValue;
151

    
152
      return this.file[name] || defaultValue;
153
    }
154
  };
155

    
156
  return Settings;
157
})();
158

    
159
var cache = new Cache(kCacheSize);
160
var currentPageNumber = 1;
161

    
162
var PDFView = {
163
  pages: [],
164
  thumbnails: [],
165
  currentScale: kUnknownScale,
166
  currentScaleValue: null,
167
  initialBookmark: document.location.hash.substring(1),
168
  startedTextExtraction: false,
169
  pageText: [],
170
  container: null,
171
  thumbnailContainer: null,
172
  initialized: false,
173
  fellback: false,
174
  pdfDocument: null,
175
  sidebarOpen: false,
176
  pageViewScroll: null,
177
  thumbnailViewScroll: null,
178
  isFullscreen: false,
179
  previousScale: null,
180

    
181
  // called once when the document is loaded
182
  initialize: function pdfViewInitialize() {
183
    var container = this.container = document.getElementById('viewerContainer');
184
    this.pageViewScroll = {};
185
    this.watchScroll(container, this.pageViewScroll, updateViewarea);
186

    
187
    var thumbnailContainer = this.thumbnailContainer =
188
                             document.getElementById('thumbnailView');
189
    this.thumbnailViewScroll = {};
190
    this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
191
                     this.renderHighestPriority.bind(this));
192

    
193
    this.initialized = true;
194
  },
195

    
196
  // Helper function to keep track whether a div was scrolled up or down and
197
  // then call a callback.
198
  watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
199
    state.down = true;
200
    state.lastY = viewAreaElement.scrollTop;
201
    viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) {
202
      var currentY = viewAreaElement.scrollTop;
203
      var lastY = state.lastY;
204
      if (currentY > lastY)
205
        state.down = true;
206
      else if (currentY < lastY)
207
        state.down = false;
208
      // else do nothing and use previous value
209
      state.lastY = currentY;
210
      callback();
211
    }, true);
212
  },
213

    
214
  setScale: function pdfViewSetScale(val, resetAutoSettings, noScroll) {
215
    if (val == this.currentScale)
216
      return;
217

    
218
    var pages = this.pages;
219
    for (var i = 0; i < pages.length; i++)
220
      pages[i].update(val * kCssUnits);
221

    
222
    if (!noScroll && this.currentScale != val)
223
      this.pages[this.page - 1].scrollIntoView();
224
    this.currentScale = val;
225

    
226
    var event = document.createEvent('UIEvents');
227
    event.initUIEvent('scalechange', false, false, window, 0);
228
    event.scale = val;
229
    event.resetAutoSettings = resetAutoSettings;
230
    window.dispatchEvent(event);
231
  },
232

    
233
  parseScale: function pdfViewParseScale(value, resetAutoSettings, noScroll) {
234
    if ('custom' == value)
235
      return;
236

    
237
    var scale = parseFloat(value);
238
    this.currentScaleValue = value;
239
    if (scale) {
240
      this.setScale(scale, true, noScroll);
241
      return;
242
    }
243

    
244
    var container = this.container;
245
    var currentPage = this.pages[this.page - 1];
246

    
247
    var pageWidthScale = (container.clientWidth - kScrollbarPadding) /
248
                          currentPage.width * currentPage.scale / kCssUnits;
249
    var pageHeightScale = (container.clientHeight - kScrollbarPadding) /
250
                           currentPage.height * currentPage.scale / kCssUnits;
251
    switch (value) {
252
      case 'page-actual':
253
        scale = 1;
254
        break;
255
      case 'page-width':
256
        scale = pageWidthScale;
257
        break;
258
      case 'page-height':
259
        scale = pageHeightScale;
260
        break;
261
      case 'page-fit':
262
        scale = Math.min(pageWidthScale, pageHeightScale);
263
        break;
264
      case 'auto':
265
        scale = Math.min(1.0, pageWidthScale);
266
        break;
267
    }
268
    this.setScale(scale, resetAutoSettings, noScroll);
269

    
270
    selectScaleOption(value);
271
  },
272

    
273
  zoomIn: function pdfViewZoomIn() {
274
    var newScale = (this.currentScale * kDefaultScaleDelta).toFixed(2);
275
    newScale = Math.min(kMaxScale, newScale);
276
    this.parseScale(newScale, true);
277
  },
278

    
279
  zoomOut: function pdfViewZoomOut() {
280
    var newScale = (this.currentScale / kDefaultScaleDelta).toFixed(2);
281
    newScale = Math.max(kMinScale, newScale);
282
    this.parseScale(newScale, true);
283
  },
284

    
285
  set page(val) {
286
    var pages = this.pages;
287
    var input = document.getElementById('pageNumber');
288
    var event = document.createEvent('UIEvents');
289
    event.initUIEvent('pagechange', false, false, window, 0);
290

    
291
    if (!(0 < val && val <= pages.length)) {
292
      event.pageNumber = this.page;
293
      window.dispatchEvent(event);
294
      return;
295
    }
296

    
297
    pages[val - 1].updateStats();
298
    currentPageNumber = val;
299
    event.pageNumber = val;
300
    window.dispatchEvent(event);
301

    
302
    // checking if the this.page was called from the updateViewarea function:
303
    // avoiding the creation of two "set page" method (internal and public)
304
    if (updateViewarea.inProgress)
305
      return;
306

    
307
    // Avoid scrolling the first page during loading
308
    if (this.loading && val == 1)
309
      return;
310

    
311
    pages[val - 1].scrollIntoView();
312
  },
313

    
314
  get page() {
315
    return currentPageNumber;
316
  },
317

    
318
  get supportsPrinting() {
319
    var canvas = document.createElement('canvas');
320
    var value = 'mozPrintCallback' in canvas;
321
    // shadow
322
    Object.defineProperty(this, 'supportsPrinting', { value: value,
323
                                                      enumerable: true,
324
                                                      configurable: true,
325
                                                      writable: false });
326
    return value;
327
  },
328

    
329
  get supportsFullscreen() {
330
    var doc = document.documentElement;
331
    var support = doc.requestFullScreen || doc.mozRequestFullScreen ||
332
                  doc.webkitRequestFullScreen;
333
    Object.defineProperty(this, 'supportsFullScreen', { value: support,
334
                                                        enumerable: true,
335
                                                        configurable: true,
336
                                                        writable: false });
337
    return support;
338
  },
339

    
340
  open: function pdfViewOpen(url, scale, password) {
341
    var parameters = {password: password};
342
    if (typeof url === 'string') { // URL
343
      this.url = url;
344
      document.title = decodeURIComponent(getFileName(url)) || url;
345
      parameters.url = url;
346
    } else if (url && 'byteLength' in url) { // ArrayBuffer
347
      parameters.data = url;
348
    }
349

    
350
    if (!PDFView.loadingBar) {
351
      PDFView.loadingBar = new ProgressBar('#loadingBar', {});
352
    }
353

    
354
    this.pdfDocument = null;
355
    var self = this;
356
    self.loading = true;
357
    PDFJS.getDocument(parameters).then(
358
      function getDocumentCallback(pdfDocument) {
359
        self.load(pdfDocument, scale);
360
        self.loading = false;
361
      },
362
      function getDocumentError(message, exception) {
363
        if (exception && exception.name === 'PasswordException') {
364
          if (exception.code === 'needpassword') {
365
            var promptString = mozL10n.get('request_password', null,
366
                                      'PDF is protected by a password:');
367
            password = prompt(promptString);
368
            if (password && password.length > 0) {
369
              return PDFView.open(url, scale, password);
370
            }
371
          }
372
        }
373

    
374
        var loadingIndicator = document.getElementById('loading');
375
        loadingIndicator.textContent = mozL10n.get('loading_error_indicator',
376
          null, 'Error');
377
        var moreInfo = {
378
          message: message
379
        };
380
        self.error(mozL10n.get('loading_error', null,
381
          'An error occurred while loading the PDF.'), moreInfo);
382
        self.loading = false;
383
      },
384
      function getDocumentProgress(progressData) {
385
        self.progress(progressData.loaded / progressData.total);
386
      }
387
    );
388
  },
389

    
390
  download: function pdfViewDownload() {
391
    function noData() {
392
      FirefoxCom.request('download', { originalUrl: url });
393
    }
394

    
395
    var url = this.url.split('#')[0];
396
    url += '#pdfjs.action=download';
397
    window.open(url, '_parent');
398
  },
399

    
400
  fallback: function pdfViewFallback() {
401
    return;
402
  },
403

    
404
  navigateTo: function pdfViewNavigateTo(dest) {
405
    if (typeof dest === 'string')
406
      dest = this.destinations[dest];
407
    if (!(dest instanceof Array))
408
      return; // invalid destination
409
    // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
410
    var destRef = dest[0];
411
    var pageNumber = destRef instanceof Object ?
412
      this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1);
413
    if (pageNumber > this.pages.length)
414
      pageNumber = this.pages.length;
415
    if (pageNumber) {
416
      this.page = pageNumber;
417
      var currentPage = this.pages[pageNumber - 1];
418
      currentPage.scrollIntoView(dest);
419
    }
420
  },
421

    
422
  getDestinationHash: function pdfViewGetDestinationHash(dest) {
423
    if (typeof dest === 'string')
424
      return PDFView.getAnchorUrl('#' + escape(dest));
425
    if (dest instanceof Array) {
426
      var destRef = dest[0]; // see navigateTo method for dest format
427
      var pageNumber = destRef instanceof Object ?
428
        this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
429
        (destRef + 1);
430
      if (pageNumber) {
431
        var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
432
        var destKind = dest[1];
433
        if (typeof destKind === 'object' && 'name' in destKind &&
434
            destKind.name == 'XYZ') {
435
          var scale = (dest[4] || this.currentScale);
436
          pdfOpenParams += '&zoom=' + (scale * 100);
437
          if (dest[2] || dest[3]) {
438
            pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
439
          }
440
        }
441
        return pdfOpenParams;
442
      }
443
    }
444
    return '';
445
  },
446

    
447
  /**
448
   * For the firefox extension we prefix the full url on anchor links so they
449
   * don't come up as resource:// urls and so open in new tab/window works.
450
   * @param {String} anchor The anchor hash include the #.
451
   */
452
  getAnchorUrl: function getAnchorUrl(anchor) {
453
    return anchor;
454
  },
455

    
456
  /**
457
   * Show the error box.
458
   * @param {String} message A message that is human readable.
459
   * @param {Object} moreInfo (optional) Further information about the error
460
   *                            that is more technical.  Should have a 'message'
461
   *                            and optionally a 'stack' property.
462
   */
463
  error: function pdfViewError(message, moreInfo) {
464
    var moreInfoText = mozL10n.get('error_build', {build: PDFJS.build},
465
      'PDF.JS Build: {{build}}') + '\n';
466
    if (moreInfo) {
467
      moreInfoText +=
468
        mozL10n.get('error_message', {message: moreInfo.message},
469
        'Message: {{message}}');
470
      if (moreInfo.stack) {
471
        moreInfoText += '\n' +
472
          mozL10n.get('error_stack', {stack: moreInfo.stack},
473
          'Stack: {{stack}}');
474
      } else {
475
        if (moreInfo.filename) {
476
          moreInfoText += '\n' +
477
            mozL10n.get('error_file', {file: moreInfo.filename},
478
            'File: {{file}}');
479
        }
480
        if (moreInfo.lineNumber) {
481
          moreInfoText += '\n' +
482
            mozL10n.get('error_line', {line: moreInfo.lineNumber},
483
            'Line: {{line}}');
484
        }
485
      }
486
    }
487
    var errorWrapper = document.getElementById('errorWrapper');
488
    errorWrapper.removeAttribute('hidden');
489

    
490
    var errorMessage = document.getElementById('errorMessage');
491
    errorMessage.textContent = message;
492

    
493
    var closeButton = document.getElementById('errorClose');
494
    closeButton.onclick = function() {
495
      errorWrapper.setAttribute('hidden', 'true');
496
    };
497

    
498
    var errorMoreInfo = document.getElementById('errorMoreInfo');
499
    var moreInfoButton = document.getElementById('errorShowMore');
500
    var lessInfoButton = document.getElementById('errorShowLess');
501
    moreInfoButton.onclick = function() {
502
      errorMoreInfo.removeAttribute('hidden');
503
      moreInfoButton.setAttribute('hidden', 'true');
504
      lessInfoButton.removeAttribute('hidden');
505
    };
506
    lessInfoButton.onclick = function() {
507
      errorMoreInfo.setAttribute('hidden', 'true');
508
      moreInfoButton.removeAttribute('hidden');
509
      lessInfoButton.setAttribute('hidden', 'true');
510
    };
511
    moreInfoButton.removeAttribute('hidden');
512
    lessInfoButton.setAttribute('hidden', 'true');
513
    errorMoreInfo.value = moreInfoText;
514

    
515
    errorMoreInfo.rows = moreInfoText.split('\n').length - 1;
516
  },
517

    
518
  progress: function pdfViewProgress(level) {
519
    var percent = Math.round(level * 100);
520
    PDFView.loadingBar.percent = percent;
521
  },
522

    
523
  load: function pdfViewLoad(pdfDocument, scale) {
524
    function bindOnAfterDraw(pageView, thumbnailView) {
525
      // when page is painted, using the image as thumbnail base
526
      pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
527
        thumbnailView.setImage(pageView.canvas);
528
      };
529
    }
530

    
531
    this.pdfDocument = pdfDocument;
532

    
533
    var errorWrapper = document.getElementById('errorWrapper');
534
    errorWrapper.setAttribute('hidden', 'true');
535

    
536
    var loadingBox = document.getElementById('loadingBox');
537
    loadingBox.setAttribute('hidden', 'true');
538
    var loadingIndicator = document.getElementById('loading');
539
    loadingIndicator.textContent = '';
540

    
541
    var thumbsView = document.getElementById('thumbnailView');
542
    thumbsView.parentNode.scrollTop = 0;
543

    
544
    while (thumbsView.hasChildNodes())
545
      thumbsView.removeChild(thumbsView.lastChild);
546

    
547
    if ('_loadingInterval' in thumbsView)
548
      clearInterval(thumbsView._loadingInterval);
549

    
550
    var container = document.getElementById('viewer');
551
    while (container.hasChildNodes())
552
      container.removeChild(container.lastChild);
553

    
554
    var pagesCount = pdfDocument.numPages;
555
    var id = pdfDocument.fingerprint;
556
    var storedHash = null;
557
    document.getElementById('numPages').textContent =
558
      mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
559
    document.getElementById('pageNumber').max = pagesCount;
560
    PDFView.documentFingerprint = id;
561
    var store = PDFView.store = new Settings(id);
562
    if (store.get('exists', false)) {
563
      var page = store.get('page', '1');
564
      var zoom = store.get('zoom', PDFView.currentScale);
565
      var left = store.get('scrollLeft', '0');
566
      var top = store.get('scrollTop', '0');
567

    
568
      storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top;
569
    }
570

    
571
    var pages = this.pages = [];
572
    this.pageText = [];
573
    this.startedTextExtraction = false;
574
    var pagesRefMap = {};
575
    var thumbnails = this.thumbnails = [];
576
    var pagePromises = [];
577
    for (var i = 1; i <= pagesCount; i++)
578
      pagePromises.push(pdfDocument.getPage(i));
579
    var self = this;
580
    var pagesPromise = PDFJS.Promise.all(pagePromises);
581
    pagesPromise.then(function(promisedPages) {
582
      for (var i = 1; i <= pagesCount; i++) {
583
        var page = promisedPages[i - 1];
584
        var pageView = new PageView(container, page, i, scale,
585
                                    page.stats, self.navigateTo.bind(self));
586
        var thumbnailView = new ThumbnailView(thumbsView, page, i);
587
        bindOnAfterDraw(pageView, thumbnailView);
588

    
589
        pages.push(pageView);
590
        thumbnails.push(thumbnailView);
591
        var pageRef = page.ref;
592
        pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
593
      }
594

    
595
      self.pagesRefMap = pagesRefMap;
596
    });
597

    
598
    var destinationsPromise = pdfDocument.getDestinations();
599
    destinationsPromise.then(function(destinations) {
600
      self.destinations = destinations;
601
    });
602

    
603
    // outline and initial view depends on destinations and pagesRefMap
604
    PDFJS.Promise.all([pagesPromise, destinationsPromise]).then(function() {
605
      pdfDocument.getOutline().then(function(outline) {
606
        self.outline = new DocumentOutlineView(outline);
607
      });
608

    
609
      self.setInitialView(storedHash, scale);
610
    });
611

    
612
    pdfDocument.getMetadata().then(function(data) {
613
      var info = data.info, metadata = data.metadata;
614
      self.documentInfo = info;
615
      self.metadata = metadata;
616

    
617
      var pdfTitle;
618
      if (metadata) {
619
        if (metadata.has('dc:title'))
620
          pdfTitle = metadata.get('dc:title');
621
      }
622

    
623
      if (!pdfTitle && info && info['Title'])
624
        pdfTitle = info['Title'];
625

    
626
      if (pdfTitle)
627
        document.title = pdfTitle + ' - ' + document.title;
628
    });
629
  },
630

    
631
  setInitialView: function pdfViewSetInitialView(storedHash, scale) {
632
    // Reset the current scale, as otherwise the page's scale might not get
633
    // updated if the zoom level stayed the same.
634
    this.currentScale = 0;
635
    this.currentScaleValue = null;
636
    if (this.initialBookmark) {
637
      this.setHash(this.initialBookmark);
638
      this.initialBookmark = null;
639
    }
640
    else if (storedHash)
641
      this.setHash(storedHash);
642
    else if (scale) {
643
      this.parseScale(scale, true);
644
      this.page = 1;
645
    }
646

    
647
    if (PDFView.currentScale === kUnknownScale) {
648
      // Scale was not initialized: invalid bookmark or scale was not specified.
649
      // Setting the default one.
650
      this.parseScale(kDefaultScale, true);
651
    }
652
  },
653

    
654
  renderHighestPriority: function pdfViewRenderHighestPriority() {
655
    // Pages have a higher priority than thumbnails, so check them first.
656
    var visiblePages = this.getVisiblePages();
657
    var pageView = this.getHighestPriority(visiblePages, this.pages,
658
                                           this.pageViewScroll.down);
659
    if (pageView) {
660
      this.renderView(pageView, 'page');
661
      return;
662
    }
663
    // No pages needed rendering so check thumbnails.
664
    if (this.sidebarOpen) {
665
      var visibleThumbs = this.getVisibleThumbs();
666
      var thumbView = this.getHighestPriority(visibleThumbs,
667
                                              this.thumbnails,
668
                                              this.thumbnailViewScroll.down);
669
      if (thumbView)
670
        this.renderView(thumbView, 'thumbnail');
671
    }
672
  },
673

    
674
  getHighestPriority: function pdfViewGetHighestPriority(visible, views,
675
                                                         scrolledDown) {
676
    // The state has changed figure out which page has the highest priority to
677
    // render next (if any).
678
    // Priority:
679
    // 1 visible pages
680
    // 2 if last scrolled down page after the visible pages
681
    // 2 if last scrolled up page before the visible pages
682
    var visibleViews = visible.views;
683

    
684
    var numVisible = visibleViews.length;
685
    if (numVisible === 0) {
686
      return false;
687
    }
688
    for (var i = 0; i < numVisible; ++i) {
689
      var view = visibleViews[i].view;
690
      if (!this.isViewFinished(view))
691
        return view;
692
    }
693

    
694
    // All the visible views have rendered, try to render next/previous pages.
695
    if (scrolledDown) {
696
      var nextPageIndex = visible.last.id;
697
      // ID's start at 1 so no need to add 1.
698
      if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex]))
699
        return views[nextPageIndex];
700
    } else {
701
      var previousPageIndex = visible.first.id - 2;
702
      if (views[previousPageIndex] &&
703
          !this.isViewFinished(views[previousPageIndex]))
704
        return views[previousPageIndex];
705
    }
706
    // Everything that needs to be rendered has been.
707
    return false;
708
  },
709

    
710
  isViewFinished: function pdfViewNeedsRendering(view) {
711
    return view.renderingState === RenderingStates.FINISHED;
712
  },
713

    
714
  // Render a page or thumbnail view. This calls the appropriate function based
715
  // on the views state. If the view is already rendered it will return false.
716
  renderView: function pdfViewRender(view, type) {
717
    var state = view.renderingState;
718
    switch (state) {
719
      case RenderingStates.FINISHED:
720
        return false;
721
      case RenderingStates.PAUSED:
722
        PDFView.highestPriorityPage = type + view.id;
723
        view.resume();
724
        break;
725
      case RenderingStates.RUNNING:
726
        PDFView.highestPriorityPage = type + view.id;
727
        break;
728
      case RenderingStates.INITIAL:
729
        PDFView.highestPriorityPage = type + view.id;
730
        view.draw(this.renderHighestPriority.bind(this));
731
        break;
732
    }
733
    return true;
734
  },
735

    
736
  search: function pdfViewStartSearch() {
737
    // Limit this function to run every <SEARCH_TIMEOUT>ms.
738
    var SEARCH_TIMEOUT = 250;
739
    var lastSearch = this.lastSearch;
740
    var now = Date.now();
741
    if (lastSearch && (now - lastSearch) < SEARCH_TIMEOUT) {
742
      if (!this.searchTimer) {
743
        this.searchTimer = setTimeout(function resumeSearch() {
744
            PDFView.search();
745
          },
746
          SEARCH_TIMEOUT - (now - lastSearch)
747
        );
748
      }
749
      return;
750
    }
751
    this.searchTimer = null;
752
    this.lastSearch = now;
753

    
754
    function bindLink(link, pageNumber) {
755
      link.href = '#' + pageNumber;
756
      link.onclick = function searchBindLink() {
757
        PDFView.page = pageNumber;
758
        return false;
759
      };
760
    }
761

    
762
    var searchResults = document.getElementById('searchResults');
763

    
764
    var searchTermsInput = document.getElementById('searchTermsInput');
765
    searchResults.removeAttribute('hidden');
766
    searchResults.textContent = '';
767

    
768
    var terms = searchTermsInput.value;
769

    
770
    if (!terms)
771
      return;
772

    
773
    // simple search: removing spaces and hyphens, then scanning every
774
    terms = terms.replace(/\s-/g, '').toLowerCase();
775
    var index = PDFView.pageText;
776
    var pageFound = false;
777
    for (var i = 0, ii = index.length; i < ii; i++) {
778
      var pageText = index[i].replace(/\s-/g, '').toLowerCase();
779
      var j = pageText.indexOf(terms);
780
      if (j < 0)
781
        continue;
782

    
783
      var pageNumber = i + 1;
784
      var textSample = index[i].substr(j, 50);
785
      var link = document.createElement('a');
786
      bindLink(link, pageNumber);
787
      link.textContent = 'Page ' + pageNumber + ': ' + textSample;
788
      searchResults.appendChild(link);
789

    
790
      pageFound = true;
791
    }
792
    if (!pageFound) {
793
      searchResults.textContent = '';
794
      var noResults = document.createElement('div');
795
      noResults.classList.add('noResults');
796
      noResults.textContent = mozL10n.get('search_terms_not_found', null,
797
                                              '(Not found)');
798
      searchResults.appendChild(noResults);
799
    }
800
  },
801

    
802
  setHash: function pdfViewSetHash(hash) {
803
    if (!hash)
804
      return;
805

    
806
    if (hash.indexOf('=') >= 0) {
807
      var params = PDFView.parseQueryString(hash);
808
      // borrowing syntax from "Parameters for Opening PDF Files"
809
      if ('nameddest' in params) {
810
        PDFView.navigateTo(params.nameddest);
811
        return;
812
      }
813
      if ('page' in params) {
814
        var pageNumber = (params.page | 0) || 1;
815
        if ('zoom' in params) {
816
          var zoomArgs = params.zoom.split(','); // scale,left,top
817
          // building destination array
818

    
819
          // If the zoom value, it has to get divided by 100. If it is a string,
820
          // it should stay as it is.
821
          var zoomArg = zoomArgs[0];
822
          var zoomArgNumber = parseFloat(zoomArg);
823
          if (zoomArgNumber)
824
            zoomArg = zoomArgNumber / 100;
825

    
826
          var dest = [null, {name: 'XYZ'}, (zoomArgs[1] | 0),
827
            (zoomArgs[2] | 0), zoomArg];
828
          var currentPage = this.pages[pageNumber - 1];
829
          currentPage.scrollIntoView(dest);
830
        } else {
831
          this.page = pageNumber; // simple page
832
        }
833
      }
834
    } else if (/^\d+$/.test(hash)) // page number
835
      this.page = hash;
836
    else // named destination
837
      PDFView.navigateTo(unescape(hash));
838
  },
839

    
840
  switchSidebarView: function pdfViewSwitchSidebarView(view) {
841
    var thumbsView = document.getElementById('thumbnailView');
842
    var outlineView = document.getElementById('outlineView');
843
    var searchView = document.getElementById('searchView');
844

    
845
    var thumbsButton = document.getElementById('viewThumbnail');
846
    var outlineButton = document.getElementById('viewOutline');
847
    var searchButton = document.getElementById('viewSearch');
848

    
849
    switch (view) {
850
      case 'thumbs':
851
        thumbsButton.classList.add('toggled');
852
        outlineButton.classList.remove('toggled');
853
        searchButton.classList.remove('toggled');
854
        thumbsView.classList.remove('hidden');
855
        outlineView.classList.add('hidden');
856
        searchView.classList.add('hidden');
857

    
858
        PDFView.renderHighestPriority();
859
        break;
860

    
861
      case 'outline':
862
        thumbsButton.classList.remove('toggled');
863
        outlineButton.classList.add('toggled');
864
        searchButton.classList.remove('toggled');
865
        thumbsView.classList.add('hidden');
866
        outlineView.classList.remove('hidden');
867
        searchView.classList.add('hidden');
868

    
869
        if (outlineButton.getAttribute('disabled'))
870
          return;
871
        break;
872

    
873
      case 'search':
874
        thumbsButton.classList.remove('toggled');
875
        outlineButton.classList.remove('toggled');
876
        searchButton.classList.add('toggled');
877
        thumbsView.classList.add('hidden');
878
        outlineView.classList.add('hidden');
879
        searchView.classList.remove('hidden');
880

    
881
        var searchTermsInput = document.getElementById('searchTermsInput');
882
        searchTermsInput.focus();
883
        // Start text extraction as soon as the search gets displayed.
884
        this.extractText();
885
        break;
886
    }
887
  },
888

    
889
  extractText: function() {
890
    if (this.startedTextExtraction)
891
      return;
892
    this.startedTextExtraction = true;
893
    var self = this;
894
    function extractPageText(pageIndex) {
895
      self.pages[pageIndex].pdfPage.getTextContent().then(
896
        function textContentResolved(textContent) {
897
          self.pageText[pageIndex] = textContent;
898
          self.search();
899
          if ((pageIndex + 1) < self.pages.length)
900
            extractPageText(pageIndex + 1);
901
        }
902
      );
903
    }
904
    extractPageText(0);
905
  },
906

    
907
  getVisiblePages: function pdfViewGetVisiblePages() {
908
    return this.getVisibleElements(this.container,
909
                                   this.pages, true);
910
  },
911

    
912
  getVisibleThumbs: function pdfViewGetVisibleThumbs() {
913
    return this.getVisibleElements(this.thumbnailContainer,
914
                                   this.thumbnails);
915
  },
916

    
917
  // Generic helper to find out what elements are visible within a scroll pane.
918
  getVisibleElements: function pdfViewGetVisibleElements(
919
      scrollEl, views, sortByVisibility) {
920
    var currentHeight = 0, view;
921
    var top = scrollEl.scrollTop;
922

    
923
    for (var i = 1, ii = views.length; i <= ii; ++i) {
924
      view = views[i - 1];
925
      currentHeight = view.el.offsetTop;
926
      if (currentHeight + view.el.clientHeight > top)
927
        break;
928
      currentHeight += view.el.clientHeight;
929
    }
930

    
931
    var visible = [];
932

    
933
    // Algorithm broken in fullscreen mode
934
    if (this.isFullscreen) {
935
      var currentPage = this.pages[this.page - 1];
936
      visible.push({
937
        id: currentPage.id,
938
        view: currentPage
939
      });
940

    
941
      return { first: currentPage, last: currentPage, views: visible};
942
    }
943

    
944
    var bottom = top + scrollEl.clientHeight;
945
    var nextHeight, hidden, percent, viewHeight;
946
    for (; i <= ii && currentHeight < bottom; ++i) {
947
      view = views[i - 1];
948
      viewHeight = view.el.clientHeight;
949
      currentHeight = view.el.offsetTop;
950
      nextHeight = currentHeight + viewHeight;
951
      hidden = Math.max(0, top - currentHeight) +
952
               Math.max(0, nextHeight - bottom);
953
      percent = Math.floor((viewHeight - hidden) * 100.0 / viewHeight);
954
      visible.push({ id: view.id, y: currentHeight,
955
                     view: view, percent: percent });
956
      currentHeight = nextHeight;
957
    }
958

    
959
    var first = visible[0];
960
    var last = visible[visible.length - 1];
961

    
962
    if (sortByVisibility) {
963
      visible.sort(function(a, b) {
964
        var pc = a.percent - b.percent;
965
        if (Math.abs(pc) > 0.001)
966
          return -pc;
967

    
968
        return a.id - b.id; // ensure stability
969
      });
970
    }
971

    
972
    return {first: first, last: last, views: visible};
973
  },
974

    
975
  // Helper function to parse query string (e.g. ?param1=value&parm2=...).
976
  parseQueryString: function pdfViewParseQueryString(query) {
977
    var parts = query.split('&');
978
    var params = {};
979
    for (var i = 0, ii = parts.length; i < parts.length; ++i) {
980
      var param = parts[i].split('=');
981
      var key = param[0];
982
      var value = param.length > 1 ? param[1] : null;
983
      params[unescape(key)] = unescape(value);
984
    }
985
    return params;
986
  },
987

    
988
  beforePrint: function pdfViewSetupBeforePrint() {
989
    if (!this.supportsPrinting) {
990
      var printMessage = mozL10n.get('printing_not_supported', null,
991
          'Warning: Printing is not fully supported by this browser.');
992
      this.error(printMessage);
993
      return;
994
    }
995
    var body = document.querySelector('body');
996
    body.setAttribute('data-mozPrintCallback', true);
997
    for (var i = 0, ii = this.pages.length; i < ii; ++i) {
998
      this.pages[i].beforePrint();
999
    }
1000
  },
1001

    
1002
  afterPrint: function pdfViewSetupAfterPrint() {
1003
    var div = document.getElementById('printContainer');
1004
    while (div.hasChildNodes())
1005
      div.removeChild(div.lastChild);
1006
  },
1007

    
1008
  fullscreen: function pdfViewFullscreen() {
1009
    var isFullscreen = document.fullscreen || document.mozFullScreen ||
1010
        document.webkitIsFullScreen;
1011

    
1012
    if (isFullscreen) {
1013
      return false;
1014
    }
1015

    
1016
    var wrapper = document.getElementById('viewerContainer');
1017
    if (document.documentElement.requestFullScreen) {
1018
      wrapper.requestFullScreen();
1019
    } else if (document.documentElement.mozRequestFullScreen) {
1020
      wrapper.mozRequestFullScreen();
1021
    } else if (document.documentElement.webkitRequestFullScreen) {
1022
      wrapper.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
1023
    } else {
1024
      return false;
1025
    }
1026

    
1027
    this.isFullscreen = true;
1028
    var currentPage = this.pages[this.page - 1];
1029
    this.previousScale = this.currentScaleValue;
1030
    this.parseScale('page-fit', true);
1031

    
1032
    // Wait for fullscreen to take effect
1033
    setTimeout(function() {
1034
      currentPage.scrollIntoView();
1035
    }, 0);
1036

    
1037
    return true;
1038
  },
1039

    
1040
  exitFullscreen: function pdfViewExitFullscreen() {
1041
    this.isFullscreen = false;
1042
    this.parseScale(this.previousScale);
1043
    this.page = this.page;
1044
  }
1045
};
1046

    
1047
var PageView = function pageView(container, pdfPage, id, scale,
1048
                                 stats, navigateTo) {
1049
  this.id = id;
1050
  this.pdfPage = pdfPage;
1051

    
1052
  this.scale = scale || 1.0;
1053
  this.viewport = this.pdfPage.getViewport(this.scale);
1054

    
1055
  this.renderingState = RenderingStates.INITIAL;
1056
  this.resume = null;
1057

    
1058
  var anchor = document.createElement('a');
1059
  anchor.name = '' + this.id;
1060

    
1061
  var div = this.el = document.createElement('div');
1062
  div.id = 'pageContainer' + this.id;
1063
  div.className = 'page';
1064

    
1065
  container.appendChild(anchor);
1066
  container.appendChild(div);
1067

    
1068
  this.destroy = function pageViewDestroy() {
1069
    this.update();
1070
    this.pdfPage.destroy();
1071
  };
1072

    
1073
  this.update = function pageViewUpdate(scale) {
1074
    this.renderingState = RenderingStates.INITIAL;
1075
    this.resume = null;
1076

    
1077
    this.scale = scale || this.scale;
1078
    var viewport = this.pdfPage.getViewport(this.scale);
1079

    
1080
    this.viewport = viewport;
1081
    div.style.width = viewport.width + 'px';
1082
    div.style.height = viewport.height + 'px';
1083

    
1084
    while (div.hasChildNodes())
1085
      div.removeChild(div.lastChild);
1086
    div.removeAttribute('data-loaded');
1087

    
1088
    delete this.canvas;
1089

    
1090
    this.loadingIconDiv = document.createElement('div');
1091
    this.loadingIconDiv.className = 'loadingIcon';
1092
    div.appendChild(this.loadingIconDiv);
1093
  };
1094

    
1095
  Object.defineProperty(this, 'width', {
1096
    get: function PageView_getWidth() {
1097
      return this.viewport.width;
1098
    },
1099
    enumerable: true
1100
  });
1101

    
1102
  Object.defineProperty(this, 'height', {
1103
    get: function PageView_getHeight() {
1104
      return this.viewport.height;
1105
    },
1106
    enumerable: true
1107
  });
1108

    
1109
  function setupAnnotations(pdfPage, viewport) {
1110
    function bindLink(link, dest) {
1111
      link.href = PDFView.getDestinationHash(dest);
1112
      link.onclick = function pageViewSetupLinksOnclick() {
1113
        if (dest)
1114
          PDFView.navigateTo(dest);
1115
        return false;
1116
      };
1117
    }
1118
    function createElementWithStyle(tagName, item) {
1119
      var rect = viewport.convertToViewportRectangle(item.rect);
1120
      rect = PDFJS.Util.normalizeRect(rect);
1121
      var element = document.createElement(tagName);
1122
      element.style.left = Math.floor(rect[0]) + 'px';
1123
      element.style.top = Math.floor(rect[1]) + 'px';
1124
      element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
1125
      element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
1126
      return element;
1127
    }
1128
    function createCommentAnnotation(type, item) {
1129
      var container = document.createElement('section');
1130
      container.className = 'annotComment';
1131

    
1132
      var image = createElementWithStyle('img', item);
1133
      var type = item.type;
1134
      var rect = viewport.convertToViewportRectangle(item.rect);
1135
      rect = PDFJS.Util.normalizeRect(rect);
1136
      image.src = kImageDirectory + 'annotation-' + type.toLowerCase() + '.svg';
1137
      image.alt = mozL10n.get('text_annotation_type', {type: type},
1138
        '[{{type}} Annotation]');
1139
      var content = document.createElement('div');
1140
      content.setAttribute('hidden', true);
1141
      var title = document.createElement('h1');
1142
      var text = document.createElement('p');
1143
      content.style.left = Math.floor(rect[2]) + 'px';
1144
      content.style.top = Math.floor(rect[1]) + 'px';
1145
      title.textContent = item.title;
1146

    
1147
      if (!item.content && !item.title) {
1148
        content.setAttribute('hidden', true);
1149
      } else {
1150
        var e = document.createElement('span');
1151
        var lines = item.content.split('\n');
1152
        for (var i = 0, ii = lines.length; i < ii; ++i) {
1153
          var line = lines[i];
1154
          e.appendChild(document.createTextNode(line));
1155
          if (i < (ii - 1))
1156
            e.appendChild(document.createElement('br'));
1157
        }
1158
        text.appendChild(e);
1159
        image.addEventListener('mouseover', function annotationImageOver() {
1160
           content.removeAttribute('hidden');
1161
        }, false);
1162

    
1163
        image.addEventListener('mouseout', function annotationImageOut() {
1164
           content.setAttribute('hidden', true);
1165
        }, false);
1166
      }
1167

    
1168
      content.appendChild(title);
1169
      content.appendChild(text);
1170
      container.appendChild(image);
1171
      container.appendChild(content);
1172

    
1173
      return container;
1174
    }
1175

    
1176
    pdfPage.getAnnotations().then(function(items) {
1177
      for (var i = 0; i < items.length; i++) {
1178
        var item = items[i];
1179
        switch (item.type) {
1180
          case 'Link':
1181
            var link = createElementWithStyle('a', item);
1182
            link.href = item.url || '';
1183
            if (!item.url)
1184
              bindLink(link, ('dest' in item) ? item.dest : null);
1185
            div.appendChild(link);
1186
            break;
1187
          case 'Text':
1188
            var comment = createCommentAnnotation(item.name, item);
1189
            if (comment)
1190
              div.appendChild(comment);
1191
            break;
1192
          case 'Widget':
1193
            // TODO: support forms
1194
            PDFView.fallback();
1195
            break;
1196
        }
1197
      }
1198
    });
1199
  }
1200

    
1201
  this.getPagePoint = function pageViewGetPagePoint(x, y) {
1202
    return this.viewport.convertToPdfPoint(x, y);
1203
  };
1204

    
1205
  this.scrollIntoView = function pageViewScrollIntoView(dest) {
1206
      if (!dest) {
1207
        div.scrollIntoView(true);
1208
        return;
1209
      }
1210

    
1211
      var x = 0, y = 0;
1212
      var width = 0, height = 0, widthScale, heightScale;
1213
      var scale = 0;
1214
      switch (dest[1].name) {
1215
        case 'XYZ':
1216
          x = dest[2];
1217
          y = dest[3];
1218
          scale = dest[4];
1219
          break;
1220
        case 'Fit':
1221
        case 'FitB':
1222
          scale = 'page-fit';
1223
          break;
1224
        case 'FitH':
1225
        case 'FitBH':
1226
          y = dest[2];
1227
          scale = 'page-width';
1228
          break;
1229
        case 'FitV':
1230
        case 'FitBV':
1231
          x = dest[2];
1232
          scale = 'page-height';
1233
          break;
1234
        case 'FitR':
1235
          x = dest[2];
1236
          y = dest[3];
1237
          width = dest[4] - x;
1238
          height = dest[5] - y;
1239
          widthScale = (this.container.clientWidth - kScrollbarPadding) /
1240
            width / kCssUnits;
1241
          heightScale = (this.container.clientHeight - kScrollbarPadding) /
1242
            height / kCssUnits;
1243
          scale = Math.min(widthScale, heightScale);
1244
          break;
1245
        default:
1246
          return;
1247
      }
1248

    
1249
      if (scale && scale !== PDFView.currentScale)
1250
        PDFView.parseScale(scale, true, true);
1251
      else if (PDFView.currentScale === kUnknownScale)
1252
        PDFView.parseScale(kDefaultScale, true, true);
1253

    
1254
      var boundingRect = [
1255
        this.viewport.convertToViewportPoint(x, y),
1256
        this.viewport.convertToViewportPoint(x + width, y + height)
1257
      ];
1258
      setTimeout(function pageViewScrollIntoViewRelayout() {
1259
        // letting page to re-layout before scrolling
1260
        var scale = PDFView.currentScale;
1261
        var x = Math.min(boundingRect[0][0], boundingRect[1][0]);
1262
        var y = Math.min(boundingRect[0][1], boundingRect[1][1]);
1263
        var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]);
1264
        var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]);
1265

    
1266
        // using temporary div to scroll it into view
1267
        var tempDiv = document.createElement('div');
1268
        tempDiv.style.position = 'absolute';
1269
        tempDiv.style.left = Math.floor(x) + 'px';
1270
        tempDiv.style.top = Math.floor(y) + 'px';
1271
        tempDiv.style.width = Math.ceil(width) + 'px';
1272
        tempDiv.style.height = Math.ceil(height) + 'px';
1273
        div.appendChild(tempDiv);
1274
        tempDiv.scrollIntoView(true);
1275
        div.removeChild(tempDiv);
1276
      }, 0);
1277
  };
1278

    
1279
  this.draw = function pageviewDraw(callback) {
1280
    if (this.renderingState !== RenderingStates.INITIAL)
1281
      error('Must be in new state before drawing');
1282

    
1283
    this.renderingState = RenderingStates.RUNNING;
1284

    
1285
    var canvas = document.createElement('canvas');
1286
    canvas.id = 'page' + this.id;
1287
    canvas.mozOpaque = true;
1288
    div.appendChild(canvas);
1289
    this.canvas = canvas;
1290

    
1291
    var textLayerDiv = null;
1292
    if (!PDFJS.disableTextLayer) {
1293
      textLayerDiv = document.createElement('div');
1294
      textLayerDiv.className = 'textLayer';
1295
      div.appendChild(textLayerDiv);
1296
    }
1297
    var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null;
1298

    
1299
    var scale = this.scale, viewport = this.viewport;
1300
    canvas.width = viewport.width;
1301
    canvas.height = viewport.height;
1302

    
1303
    var ctx = canvas.getContext('2d');
1304
    ctx.save();
1305
    ctx.fillStyle = 'rgb(255, 255, 255)';
1306
    ctx.fillRect(0, 0, canvas.width, canvas.height);
1307
    ctx.restore();
1308

    
1309
    // Rendering area
1310

    
1311
    var self = this;
1312
    function pageViewDrawCallback(error) {
1313
      self.renderingState = RenderingStates.FINISHED;
1314

    
1315
      if (self.loadingIconDiv) {
1316
        div.removeChild(self.loadingIconDiv);
1317
        delete self.loadingIconDiv;
1318
      }
1319

    
1320
      if (error) {
1321
        PDFView.error(mozL10n.get('rendering_error', null,
1322
          'An error occurred while rendering the page.'), error);
1323
      }
1324

    
1325
      self.stats = pdfPage.stats;
1326
      self.updateStats();
1327
      if (self.onAfterDraw)
1328
        self.onAfterDraw();
1329

    
1330
      cache.push(self);
1331
      callback();
1332
    }
1333

    
1334
    var renderContext = {
1335
      canvasContext: ctx,
1336
      viewport: this.viewport,
1337
      textLayer: textLayer,
1338
      continueCallback: function pdfViewcContinueCallback(cont) {
1339
        if (PDFView.highestPriorityPage !== 'page' + self.id) {
1340
          self.renderingState = RenderingStates.PAUSED;
1341
          self.resume = function resumeCallback() {
1342
            self.renderingState = RenderingStates.RUNNING;
1343
            cont();
1344
          };
1345
          return;
1346
        }
1347
        cont();
1348
      }
1349
    };
1350
    this.pdfPage.render(renderContext).then(
1351
      function pdfPageRenderCallback() {
1352
        pageViewDrawCallback(null);
1353
      },
1354
      function pdfPageRenderError(error) {
1355
        pageViewDrawCallback(error);
1356
      }
1357
    );
1358

    
1359
    setupAnnotations(this.pdfPage, this.viewport);
1360
    div.setAttribute('data-loaded', true);
1361
  };
1362

    
1363
  this.beforePrint = function pageViewBeforePrint() {
1364
    var pdfPage = this.pdfPage;
1365
    var viewport = pdfPage.getViewport(1);
1366

    
1367
    var canvas = this.canvas = document.createElement('canvas');
1368
    canvas.width = viewport.width;
1369
    canvas.height = viewport.height;
1370
    canvas.style.width = viewport.width + 'pt';
1371
    canvas.style.height = viewport.height + 'pt';
1372

    
1373
    var printContainer = document.getElementById('printContainer');
1374
    printContainer.appendChild(canvas);
1375

    
1376
    var self = this;
1377
    canvas.mozPrintCallback = function(obj) {
1378
      var ctx = obj.context;
1379
      var renderContext = {
1380
        canvasContext: ctx,
1381
        viewport: viewport
1382
      };
1383

    
1384
      pdfPage.render(renderContext).then(function() {
1385
        // Tell the printEngine that rendering this canvas/page has finished.
1386
        obj.done();
1387
        self.pdfPage.destroy();
1388
      }, function(error) {
1389
        console.error(error);
1390
        // Tell the printEngine that rendering this canvas/page has failed.
1391
        // This will make the print proces stop.
1392
        if ('abort' in object)
1393
          obj.abort();
1394
        else
1395
          obj.done();
1396
        self.pdfPage.destroy();
1397
      });
1398
    };
1399
  };
1400

    
1401
  this.updateStats = function pageViewUpdateStats() {
1402
    if (PDFJS.pdfBug && Stats.enabled) {
1403
      var stats = this.stats;
1404
      Stats.add(this.id, stats);
1405
    }
1406
  };
1407
};
1408

    
1409
var ThumbnailView = function thumbnailView(container, pdfPage, id) {
1410
  var anchor = document.createElement('a');
1411
  anchor.href = PDFView.getAnchorUrl('#page=' + id);
1412
  anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
1413
  anchor.onclick = function stopNavigation() {
1414
    PDFView.page = id;
1415
    return false;
1416
  };
1417

    
1418
  var viewport = pdfPage.getViewport(1);
1419
  var pageWidth = this.width = viewport.width;
1420
  var pageHeight = this.height = viewport.height;
1421
  var pageRatio = pageWidth / pageHeight;
1422
  this.id = id;
1423

    
1424
  var canvasWidth = 98;
1425
  var canvasHeight = canvasWidth / this.width * this.height;
1426
  var scaleX = this.scaleX = (canvasWidth / pageWidth);
1427
  var scaleY = this.scaleY = (canvasHeight / pageHeight);
1428

    
1429
  var div = this.el = document.createElement('div');
1430
  div.id = 'thumbnailContainer' + id;
1431
  div.className = 'thumbnail';
1432

    
1433
  anchor.appendChild(div);
1434
  container.appendChild(anchor);
1435

    
1436
  this.hasImage = false;
1437
  this.renderingState = RenderingStates.INITIAL;
1438

    
1439
  function getPageDrawContext() {
1440
    var canvas = document.createElement('canvas');
1441
    canvas.id = 'thumbnail' + id;
1442
    canvas.mozOpaque = true;
1443

    
1444
    canvas.width = canvasWidth;
1445
    canvas.height = canvasHeight;
1446
    canvas.className = 'thumbnailImage';
1447
    canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
1448
      {page: id}, 'Thumbnail of Page {{page}}'));
1449

    
1450
    div.setAttribute('data-loaded', true);
1451

    
1452
    var ring = document.createElement('div');
1453
    ring.className = 'thumbnailSelectionRing';
1454
    ring.appendChild(canvas);
1455
    div.appendChild(ring);
1456

    
1457
    var ctx = canvas.getContext('2d');
1458
    ctx.save();
1459
    ctx.fillStyle = 'rgb(255, 255, 255)';
1460
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);
1461
    ctx.restore();
1462
    return ctx;
1463
  }
1464

    
1465
  this.drawingRequired = function thumbnailViewDrawingRequired() {
1466
    return !this.hasImage;
1467
  };
1468

    
1469
  this.draw = function thumbnailViewDraw(callback) {
1470
    if (this.renderingState !== RenderingStates.INITIAL)
1471
      error('Must be in new state before drawing');
1472

    
1473
    this.renderingState = RenderingStates.RUNNING;
1474
    if (this.hasImage) {
1475
      callback();
1476
      return;
1477
    }
1478

    
1479
    var self = this;
1480
    var ctx = getPageDrawContext();
1481
    var drawViewport = pdfPage.getViewport(scaleX);
1482
    var renderContext = {
1483
      canvasContext: ctx,
1484
      viewport: drawViewport,
1485
      continueCallback: function(cont) {
1486
        if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) {
1487
          self.renderingState = RenderingStates.PAUSED;
1488
          self.resume = function() {
1489
            self.renderingState = RenderingStates.RUNNING;
1490
            cont();
1491
          };
1492
          return;
1493
        }
1494
        cont();
1495
      }
1496
    };
1497
    pdfPage.render(renderContext).then(
1498
      function pdfPageRenderCallback() {
1499
        self.renderingState = RenderingStates.FINISHED;
1500
        callback();
1501
      },
1502
      function pdfPageRenderError(error) {
1503
        self.renderingState = RenderingStates.FINISHED;
1504
        callback();
1505
      }
1506
    );
1507
    this.hasImage = true;
1508
  };
1509

    
1510
  this.setImage = function thumbnailViewSetImage(img) {
1511
    if (this.hasImage || !img)
1512
      return;
1513
    this.renderingState = RenderingStates.FINISHED;
1514
    var ctx = getPageDrawContext();
1515
    ctx.drawImage(img, 0, 0, img.width, img.height,
1516
                  0, 0, ctx.canvas.width, ctx.canvas.height);
1517

    
1518
    this.hasImage = true;
1519
  };
1520
};
1521

    
1522
var DocumentOutlineView = function documentOutlineView(outline) {
1523
  var outlineView = document.getElementById('outlineView');
1524
  while (outlineView.firstChild)
1525
    outlineView.removeChild(outlineView.firstChild);
1526

    
1527
  function bindItemLink(domObj, item) {
1528
    domObj.href = PDFView.getDestinationHash(item.dest);
1529
    domObj.onclick = function documentOutlineViewOnclick(e) {
1530
      PDFView.navigateTo(item.dest);
1531
      return false;
1532
    };
1533
  }
1534

    
1535
  if (!outline) {
1536
    var noOutline = document.createElement('div');
1537
    noOutline.classList.add('noOutline');
1538
    noOutline.textContent = mozL10n.get('no_outline', null,
1539
      'No Outline Available');
1540
    outlineView.appendChild(noOutline);
1541
    return;
1542
  }
1543

    
1544
  var queue = [{parent: outlineView, items: outline}];
1545
  while (queue.length > 0) {
1546
    var levelData = queue.shift();
1547
    var i, n = levelData.items.length;
1548
    for (i = 0; i < n; i++) {
1549
      var item = levelData.items[i];
1550
      var div = document.createElement('div');
1551
      div.className = 'outlineItem';
1552
      var a = document.createElement('a');
1553
      bindItemLink(a, item);
1554
      a.textContent = item.title;
1555
      div.appendChild(a);
1556

    
1557
      if (item.items.length > 0) {
1558
        var itemsDiv = document.createElement('div');
1559
        itemsDiv.className = 'outlineItems';
1560
        div.appendChild(itemsDiv);
1561
        queue.push({parent: itemsDiv, items: item.items});
1562
      }
1563

    
1564
      levelData.parent.appendChild(div);
1565
    }
1566
  }
1567
};
1568

    
1569
// optimised CSS custom property getter/setter
1570
var CustomStyle = (function CustomStyleClosure() {
1571

    
1572
  // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
1573
  //              animate-css-transforms-firefox-webkit.html
1574
  // in some versions of IE9 it is critical that ms appear in this list
1575
  // before Moz
1576
  var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
1577
  var _cache = { };
1578

    
1579
  function CustomStyle() {
1580
  }
1581

    
1582
  CustomStyle.getProp = function get(propName, element) {
1583
    // check cache only when no element is given
1584
    if (arguments.length == 1 && typeof _cache[propName] == 'string') {
1585
      return _cache[propName];
1586
    }
1587

    
1588
    element = element || document.documentElement;
1589
    var style = element.style, prefixed, uPropName;
1590

    
1591
    // test standard property first
1592
    if (typeof style[propName] == 'string') {
1593
      return (_cache[propName] = propName);
1594
    }
1595

    
1596
    // capitalize
1597
    uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
1598

    
1599
    // test vendor specific properties
1600
    for (var i = 0, l = prefixes.length; i < l; i++) {
1601
      prefixed = prefixes[i] + uPropName;
1602
      if (typeof style[prefixed] == 'string') {
1603
        return (_cache[propName] = prefixed);
1604
      }
1605
    }
1606

    
1607
    //if all fails then set to undefined
1608
    return (_cache[propName] = 'undefined');
1609
  };
1610

    
1611
  CustomStyle.setProp = function set(propName, element, str) {
1612
    var prop = this.getProp(propName);
1613
    if (prop != 'undefined')
1614
      element.style[prop] = str;
1615
  };
1616

    
1617
  return CustomStyle;
1618
})();
1619

    
1620
var TextLayerBuilder = function textLayerBuilder(textLayerDiv) {
1621
  this.textLayerDiv = textLayerDiv;
1622

    
1623
  this.beginLayout = function textLayerBuilderBeginLayout() {
1624
    this.textDivs = [];
1625
    this.textLayerQueue = [];
1626
  };
1627

    
1628
  this.endLayout = function textLayerBuilderEndLayout() {
1629
    var self = this;
1630
    var textDivs = this.textDivs;
1631
    var textLayerDiv = this.textLayerDiv;
1632
    var renderTimer = null;
1633
    var renderingDone = false;
1634
    var renderInterval = 0;
1635
    var resumeInterval = 500; // in ms
1636

    
1637
    var canvas = document.createElement('canvas');
1638
    var ctx = canvas.getContext('2d');
1639

    
1640
    // Render the text layer, one div at a time
1641
    function renderTextLayer() {
1642
      if (textDivs.length === 0) {
1643
        clearInterval(renderTimer);
1644
        renderingDone = true;
1645
        self.textLayerDiv = textLayerDiv = canvas = ctx = null;
1646
        return;
1647
      }
1648
      var textDiv = textDivs.shift();
1649
      if (textDiv.dataset.textLength > 0) {
1650
        textLayerDiv.appendChild(textDiv);
1651

    
1652
        if (textDiv.dataset.textLength > 1) { // avoid div by zero
1653
          // Adjust div width to match canvas text
1654

    
1655
          ctx.font = textDiv.style.fontSize + ' sans-serif';
1656
          var width = ctx.measureText(textDiv.textContent).width;
1657

    
1658
          var textScale = textDiv.dataset.canvasWidth / width;
1659

    
1660
          CustomStyle.setProp('transform' , textDiv,
1661
            'scale(' + textScale + ', 1)');
1662
          CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
1663
        }
1664
      } // textLength > 0
1665
    }
1666
    renderTimer = setInterval(renderTextLayer, renderInterval);
1667

    
1668
    // Stop rendering when user scrolls. Resume after XXX milliseconds
1669
    // of no scroll events
1670
    var scrollTimer = null;
1671
    function textLayerOnScroll() {
1672
      if (renderingDone) {
1673
        window.removeEventListener('scroll', textLayerOnScroll, false);
1674
        return;
1675
      }
1676

    
1677
      // Immediately pause rendering
1678
      clearInterval(renderTimer);
1679

    
1680
      clearTimeout(scrollTimer);
1681
      scrollTimer = setTimeout(function textLayerScrollTimer() {
1682
        // Resume rendering
1683
        renderTimer = setInterval(renderTextLayer, renderInterval);
1684
      }, resumeInterval);
1685
    } // textLayerOnScroll
1686

    
1687
    window.addEventListener('scroll', textLayerOnScroll, false);
1688
  }; // endLayout
1689

    
1690
  this.appendText = function textLayerBuilderAppendText(text,
1691
                                                        fontName, fontSize) {
1692
    var textDiv = document.createElement('div');
1693

    
1694
    // vScale and hScale already contain the scaling to pixel units
1695
    var fontHeight = fontSize * text.geom.vScale;
1696
    textDiv.dataset.canvasWidth = text.canvasWidth * text.geom.hScale;
1697
    textDiv.dataset.fontName = fontName;
1698

    
1699
    textDiv.style.fontSize = fontHeight + 'px';
1700
    textDiv.style.left = text.geom.x + 'px';
1701
    textDiv.style.top = (text.geom.y - fontHeight) + 'px';
1702
    textDiv.textContent = PDFJS.bidi(text, -1);
1703
    textDiv.dir = text.direction;
1704
    textDiv.dataset.textLength = text.length;
1705
    this.textDivs.push(textDiv);
1706
  };
1707
};
1708

    
1709
window.addEventListener('load', function webViewerLoad(evt) {
1710
  PDFView.initialize();
1711
  var params = PDFView.parseQueryString(document.location.search.substring(1));
1712

    
1713
  var file = params.file || kDefaultURL;
1714

    
1715
  /*
1716
  if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
1717
    document.getElementById('openFile').setAttribute('hidden', 'true');
1718
  } else {
1719
    document.getElementById('fileInput').value = null;
1720
  }*/
1721

    
1722
  // Special debugging flags in the hash section of the URL.
1723
  var hash = document.location.hash.substring(1);
1724
  var hashParams = PDFView.parseQueryString(hash);
1725

    
1726
  if ('disableWorker' in hashParams)
1727
    PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
1728

    
1729
  var locale = navigator.language;
1730
  if ('locale' in hashParams)
1731
    locale = hashParams['locale'];
1732
  mozL10n.language.code = locale;
1733

    
1734
  if ('textLayer' in hashParams) {
1735
    switch (hashParams['textLayer']) {
1736
      case 'off':
1737
        PDFJS.disableTextLayer = true;
1738
        break;
1739
      case 'visible':
1740
      case 'shadow':
1741
      case 'hover':
1742
        var viewer = document.getElementById('viewer');
1743
        viewer.classList.add('textLayer-' + hashParams['textLayer']);
1744
        break;
1745
    }
1746
  }
1747

    
1748
  if ('pdfBug' in hashParams) {
1749
    PDFJS.pdfBug = true;
1750
    var pdfBug = hashParams['pdfBug'];
1751
    var enabled = pdfBug.split(',');
1752
    PDFBug.enable(enabled);
1753
    PDFBug.init();
1754
  }
1755

    
1756

    
1757
  if (!PDFView.supportsPrinting) {
1758
    document.getElementById('print').classList.add('hidden');
1759
  }
1760

    
1761
  if (!PDFView.supportsFullscreen) {
1762
    document.getElementById('fullscreen').classList.add('hidden');
1763
  }
1764

    
1765
  // Listen for warnings to trigger the fallback UI.  Errors should be caught
1766
  // and call PDFView.error() so we don't need to listen for those.
1767
  PDFJS.LogManager.addLogger({
1768
    warn: function() {
1769
      PDFView.fallback();
1770
    }
1771
  });
1772

    
1773
  var mainContainer = document.getElementById('mainContainer');
1774
  var outerContainer = document.getElementById('outerContainer');
1775
  mainContainer.addEventListener('transitionend', function(e) {
1776
    if (e.target == mainContainer) {
1777
      var event = document.createEvent('UIEvents');
1778
      event.initUIEvent('resize', false, false, window, 0);
1779
      window.dispatchEvent(event);
1780
      outerContainer.classList.remove('sidebarMoving');
1781
    }
1782
  }, true);
1783

    
1784
  document.getElementById('sidebarToggle').addEventListener('click',
1785
    function() {
1786
      this.classList.toggle('toggled');
1787
      outerContainer.classList.add('sidebarMoving');
1788
      outerContainer.classList.toggle('sidebarOpen');
1789
      PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen');
1790
      PDFView.renderHighestPriority();
1791
    });
1792
  PDFView.open(file, 0);
1793
}, true);
1794

    
1795
function updateViewarea() {
1796

    
1797
  if (!PDFView.initialized)
1798
    return;
1799
  var visible = PDFView.getVisiblePages();
1800
  var visiblePages = visible.views;
1801

    
1802
  PDFView.renderHighestPriority();
1803

    
1804
  var currentId = PDFView.page;
1805
  var firstPage = visible.first;
1806

    
1807
  for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
1808
       i < ii; ++i) {
1809
    var page = visiblePages[i];
1810

    
1811
    if (page.percent < 100)
1812
      break;
1813

    
1814
    if (page.id === PDFView.page) {
1815
      stillFullyVisible = true;
1816
      break;
1817
    }
1818
  }
1819

    
1820
  if (!stillFullyVisible) {
1821
    currentId = visiblePages[0].id;
1822
  }
1823

    
1824
  if (!PDFView.isFullscreen) {
1825
    updateViewarea.inProgress = true; // used in "set page"
1826
    PDFView.page = currentId;
1827
    updateViewarea.inProgress = false;
1828
  }
1829

    
1830
  var currentScale = PDFView.currentScale;
1831
  var currentScaleValue = PDFView.currentScaleValue;
1832
  var normalizedScaleValue = currentScaleValue == currentScale ?
1833
    currentScale * 100 : currentScaleValue;
1834

    
1835
  var pageNumber = firstPage.id;
1836
  var pdfOpenParams = '#page=' + pageNumber;
1837
  pdfOpenParams += '&zoom=' + normalizedScaleValue;
1838
  var currentPage = PDFView.pages[pageNumber - 1];
1839
  var topLeft = currentPage.getPagePoint(PDFView.container.scrollLeft,
1840
    (PDFView.container.scrollTop - firstPage.y));
1841
  pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]);
1842

    
1843
  var store = PDFView.store;
1844
  store.set('exists', true);
1845
  store.set('page', pageNumber);
1846
  store.set('zoom', normalizedScaleValue);
1847
  store.set('scrollLeft', Math.round(topLeft[0]));
1848
  store.set('scrollTop', Math.round(topLeft[1]));
1849
  var href = PDFView.getAnchorUrl(pdfOpenParams);
1850
  document.getElementById('viewBookmark').href = href;
1851
}
1852

    
1853
window.addEventListener('resize', function webViewerResize(evt) {
1854
  if (PDFView.initialized &&
1855
      (document.getElementById('pageWidthOption').selected ||
1856
      document.getElementById('pageFitOption').selected ||
1857
      document.getElementById('pageAutoOption').selected))
1858
      PDFView.parseScale(document.getElementById('scaleSelect').value);
1859
  updateViewarea();
1860
});
1861

    
1862
window.addEventListener('hashchange', function webViewerHashchange(evt) {
1863
  PDFView.setHash(document.location.hash.substring(1));
1864
});
1865

    
1866
window.addEventListener('change', function webViewerChange(evt) {
1867
  var files = evt.target.files;
1868
  if (!files || files.length == 0)
1869
    return;
1870

    
1871
  // Read the local file into a Uint8Array.
1872
  var fileReader = new FileReader();
1873
  fileReader.onload = function webViewerChangeFileReaderOnload(evt) {
1874
    var buffer = evt.target.result;
1875
    var uint8Array = new Uint8Array(buffer);
1876
    PDFView.open(uint8Array, 0);
1877
  };
1878

    
1879
  var file = files[0];
1880
  fileReader.readAsArrayBuffer(file);
1881
  document.title = file.name;
1882

    
1883
  // URL does not reflect proper document location - hiding some icons.
1884
  document.getElementById('viewBookmark').setAttribute('hidden', 'true');
1885
  document.getElementById('download').setAttribute('hidden', 'true');
1886
}, true);
1887

    
1888
function selectScaleOption(value) {
1889
  var options = document.getElementById('scaleSelect').options;
1890
  var predefinedValueFound = false;
1891
  for (var i = 0; i < options.length; i++) {
1892
    var option = options[i];
1893
    if (option.value != value) {
1894
      option.selected = false;
1895
      continue;
1896
    }
1897
    option.selected = true;
1898
    predefinedValueFound = true;
1899
  }
1900
  return predefinedValueFound;
1901
}
1902

    
1903
window.addEventListener('localized', function localized(evt) {
1904
  document.getElementsByTagName('html')[0].dir = mozL10n.language.direction;
1905
}, true);
1906

    
1907
window.addEventListener('scalechange', function scalechange(evt) {
1908
  var customScaleOption = document.getElementById('customScaleOption');
1909
  customScaleOption.selected = false;
1910

    
1911
  if (!evt.resetAutoSettings &&
1912
       (document.getElementById('pageWidthOption').selected ||
1913
        document.getElementById('pageFitOption').selected ||
1914
        document.getElementById('pageAutoOption').selected)) {
1915
      updateViewarea();
1916
      return;
1917
  }
1918

    
1919
  var predefinedValueFound = selectScaleOption('' + evt.scale);
1920
  if (!predefinedValueFound) {
1921
    customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
1922
    customScaleOption.selected = true;
1923
  }
1924

    
1925
  updateViewarea();
1926
}, true);
1927

    
1928
window.addEventListener('pagechange', function pagechange(evt) {
1929
  var page = evt.pageNumber;
1930
  if (document.getElementById('pageNumber').value != page) {
1931
    document.getElementById('pageNumber').value = page;
1932
    var selected = document.querySelector('.thumbnail.selected');
1933
    if (selected)
1934
      selected.classList.remove('selected');
1935
    var thumbnail = document.getElementById('thumbnailContainer' + page);
1936
    thumbnail.classList.add('selected');
1937
    var visibleThumbs = PDFView.getVisibleThumbs();
1938
    var numVisibleThumbs = visibleThumbs.views.length;
1939
    // If the thumbnail isn't currently visible scroll it into view.
1940
    if (numVisibleThumbs > 0) {
1941
      var first = visibleThumbs.first.id;
1942
      // Account for only one thumbnail being visible.
1943
      var last = numVisibleThumbs > 1 ?
1944
                  visibleThumbs.last.id : first;
1945
      if (page <= first || page >= last)
1946
        thumbnail.scrollIntoView();
1947
    }
1948

    
1949
  }
1950
  document.getElementById('previous').disabled = (page <= 1);
1951
  document.getElementById('next').disabled = (page >= PDFView.pages.length);
1952
}, true);
1953

    
1954
// Firefox specific event, so that we can prevent browser from zooming
1955
window.addEventListener('DOMMouseScroll', function(evt) {
1956
  if (evt.ctrlKey) {
1957
    evt.preventDefault();
1958

    
1959
    var ticks = evt.detail;
1960
    var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn';
1961
    for (var i = 0, length = Math.abs(ticks); i < length; i++)
1962
      PDFView[direction]();
1963
  }
1964
}, false);
1965

    
1966
window.addEventListener('keydown', function keydown(evt) {
1967
  var handled = false;
1968
  var cmd = (evt.ctrlKey ? 1 : 0) |
1969
            (evt.altKey ? 2 : 0) |
1970
            (evt.shiftKey ? 4 : 0) |
1971
            (evt.metaKey ? 8 : 0);
1972

    
1973
  // First, handle the key bindings that are independent whether an input
1974
  // control is selected or not.
1975
  if (cmd == 1 || cmd == 8) { // either CTRL or META key.
1976
    switch (evt.keyCode) {
1977
      case 61: // FF/Mac '='
1978
      case 107: // FF '+' and '='
1979
      case 187: // Chrome '+'
1980
        PDFView.zoomIn();
1981
        handled = true;
1982
        break;
1983
      case 109: // FF '-'
1984
      case 189: // Chrome '-'
1985
        PDFView.zoomOut();
1986
        handled = true;
1987
        break;
1988
      case 48: // '0'
1989
        PDFView.parseScale(kDefaultScale, true);
1990
        handled = true;
1991
        break;
1992
    }
1993
  }
1994

    
1995
  if (handled) {
1996
    evt.preventDefault();
1997
    return;
1998
  }
1999

    
2000
  // Some shortcuts should not get handled if a control/input element
2001
  // is selected.
2002
  var curElement = document.activeElement;
2003
  if (curElement && curElement.tagName == 'INPUT')
2004
    return;
2005
  var controlsElement = document.getElementById('controls');
2006
  while (curElement) {
2007
    if (curElement === controlsElement && !PDFView.isFullscreen)
2008
      return; // ignoring if the 'controls' element is focused
2009
    curElement = curElement.parentNode;
2010
  }
2011

    
2012
  if (cmd == 0) { // no control key pressed at all.
2013
    switch (evt.keyCode) {
2014
      case 37: // left arrow
2015
      case 75: // 'k'
2016
      case 80: // 'p'
2017
        PDFView.page--;
2018
        handled = true;
2019
        break;
2020
      case 39: // right arrow
2021
      case 74: // 'j'
2022
      case 78: // 'n'
2023
        PDFView.page++;
2024
        handled = true;
2025
        break;
2026

    
2027
      case 32: // spacebar
2028
        if (PDFView.isFullscreen) {
2029
          PDFView.page++;
2030
          handled = true;
2031
        }
2032
        break;
2033
    }
2034
  }
2035

    
2036
  if (handled) {
2037
    evt.preventDefault();
2038
  }
2039
});
2040

    
2041
window.addEventListener('beforeprint', function beforePrint(evt) {
2042
  PDFView.beforePrint();
2043
});
2044

    
2045
window.addEventListener('afterprint', function afterPrint(evt) {
2046
  PDFView.afterPrint();
2047
});
2048

    
2049
(function fullscreenClosure() {
2050
  function fullscreenChange(e) {
2051
    var isFullscreen = document.fullscreen || document.mozFullScreen ||
2052
        document.webkitIsFullScreen;
2053

    
2054
    if (!isFullscreen) {
2055
      PDFView.exitFullscreen();
2056
    }
2057
  }
2058

    
2059
  window.addEventListener('fullscreenchange', fullscreenChange, false);
2060
  window.addEventListener('mozfullscreenchange', fullscreenChange, false);
2061
  window.addEventListener('webkitfullscreenchange', fullscreenChange, false);
2062
})();