PageRenderTime 59ms CodeModel.GetById 18ms RepoModel.GetById 1ms 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

Large files files are truncated, but you can click here to view the full file

  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

Large files files are truncated, but you can click here to view the full file