PageRenderTime 72ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/data/ext/viewerPDF/pdfjs/web/viewer.js

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