viewer.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. /* Copyright 2016 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. "use strict";
  16. if (!pdfjsLib.getDocument || !pdfjsViewer.PDFViewer) {
  17. // eslint-disable-next-line no-alert
  18. alert("Please build the pdfjs-dist library using\n `gulp dist-install`");
  19. }
  20. const USE_ONLY_CSS_ZOOM = true;
  21. const TEXT_LAYER_MODE = 0; // DISABLE
  22. const MAX_IMAGE_SIZE = 1024 * 1024;
  23. const CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
  24. const CMAP_PACKED = true;
  25. pdfjsLib.GlobalWorkerOptions.workerSrc =
  26. "../../node_modules/pdfjs-dist/build/pdf.worker.js";
  27. const DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
  28. const DEFAULT_SCALE_DELTA = 1.1;
  29. const MIN_SCALE = 0.25;
  30. const MAX_SCALE = 10.0;
  31. const DEFAULT_SCALE_VALUE = "auto";
  32. const PDFViewerApplication = {
  33. pdfLoadingTask: null,
  34. pdfDocument: null,
  35. pdfViewer: null,
  36. pdfHistory: null,
  37. pdfLinkService: null,
  38. eventBus: null,
  39. /**
  40. * Opens PDF document specified by URL.
  41. * @returns {Promise} - Returns the promise, which is resolved when document
  42. * is opened.
  43. */
  44. open(params) {
  45. if (this.pdfLoadingTask) {
  46. // We need to destroy already opened document
  47. return this.close().then(
  48. function () {
  49. // ... and repeat the open() call.
  50. return this.open(params);
  51. }.bind(this)
  52. );
  53. }
  54. const url = params.url;
  55. const self = this;
  56. this.setTitleUsingUrl(url);
  57. // Loading document.
  58. const loadingTask = pdfjsLib.getDocument({
  59. url,
  60. maxImageSize: MAX_IMAGE_SIZE,
  61. cMapUrl: CMAP_URL,
  62. cMapPacked: CMAP_PACKED,
  63. });
  64. this.pdfLoadingTask = loadingTask;
  65. loadingTask.onProgress = function (progressData) {
  66. self.progress(progressData.loaded / progressData.total);
  67. };
  68. return loadingTask.promise.then(
  69. function (pdfDocument) {
  70. // Document loaded, specifying document for the viewer.
  71. self.pdfDocument = pdfDocument;
  72. self.pdfViewer.setDocument(pdfDocument);
  73. self.pdfLinkService.setDocument(pdfDocument);
  74. self.pdfHistory.initialize({
  75. fingerprint: pdfDocument.fingerprints[0],
  76. });
  77. self.loadingBar.hide();
  78. self.setTitleUsingMetadata(pdfDocument);
  79. },
  80. function (exception) {
  81. const message = exception && exception.message;
  82. const l10n = self.l10n;
  83. let loadingErrorMessage;
  84. if (exception instanceof pdfjsLib.InvalidPDFException) {
  85. // change error message also for other builds
  86. loadingErrorMessage = l10n.get(
  87. "invalid_file_error",
  88. null,
  89. "Invalid or corrupted PDF file."
  90. );
  91. } else if (exception instanceof pdfjsLib.MissingPDFException) {
  92. // special message for missing PDFs
  93. loadingErrorMessage = l10n.get(
  94. "missing_file_error",
  95. null,
  96. "Missing PDF file."
  97. );
  98. } else if (exception instanceof pdfjsLib.UnexpectedResponseException) {
  99. loadingErrorMessage = l10n.get(
  100. "unexpected_response_error",
  101. null,
  102. "Unexpected server response."
  103. );
  104. } else {
  105. loadingErrorMessage = l10n.get(
  106. "loading_error",
  107. null,
  108. "An error occurred while loading the PDF."
  109. );
  110. }
  111. loadingErrorMessage.then(function (msg) {
  112. self.error(msg, { message });
  113. });
  114. self.loadingBar.hide();
  115. }
  116. );
  117. },
  118. /**
  119. * Closes opened PDF document.
  120. * @returns {Promise} - Returns the promise, which is resolved when all
  121. * destruction is completed.
  122. */
  123. close() {
  124. const errorWrapper = document.getElementById("errorWrapper");
  125. errorWrapper.hidden = true;
  126. if (!this.pdfLoadingTask) {
  127. return Promise.resolve();
  128. }
  129. const promise = this.pdfLoadingTask.destroy();
  130. this.pdfLoadingTask = null;
  131. if (this.pdfDocument) {
  132. this.pdfDocument = null;
  133. this.pdfViewer.setDocument(null);
  134. this.pdfLinkService.setDocument(null, null);
  135. if (this.pdfHistory) {
  136. this.pdfHistory.reset();
  137. }
  138. }
  139. return promise;
  140. },
  141. get loadingBar() {
  142. const bar = document.getElementById("loadingBar");
  143. return pdfjsLib.shadow(
  144. this,
  145. "loadingBar",
  146. new pdfjsViewer.ProgressBar(bar)
  147. );
  148. },
  149. setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
  150. this.url = url;
  151. let title = pdfjsLib.getFilenameFromUrl(url) || url;
  152. try {
  153. title = decodeURIComponent(title);
  154. } catch (e) {
  155. // decodeURIComponent may throw URIError,
  156. // fall back to using the unprocessed url in that case
  157. }
  158. this.setTitle(title);
  159. },
  160. setTitleUsingMetadata(pdfDocument) {
  161. const self = this;
  162. pdfDocument.getMetadata().then(function (data) {
  163. const info = data.info,
  164. metadata = data.metadata;
  165. self.documentInfo = info;
  166. self.metadata = metadata;
  167. // Provides some basic debug information
  168. console.log(
  169. "PDF " +
  170. pdfDocument.fingerprints[0] +
  171. " [" +
  172. info.PDFFormatVersion +
  173. " " +
  174. (info.Producer || "-").trim() +
  175. " / " +
  176. (info.Creator || "-").trim() +
  177. "]" +
  178. " (PDF.js: " +
  179. (pdfjsLib.version || "-") +
  180. ")"
  181. );
  182. let pdfTitle;
  183. if (metadata && metadata.has("dc:title")) {
  184. const title = metadata.get("dc:title");
  185. // Ghostscript sometimes returns 'Untitled', so prevent setting the
  186. // title to 'Untitled.
  187. if (title !== "Untitled") {
  188. pdfTitle = title;
  189. }
  190. }
  191. if (!pdfTitle && info && info.Title) {
  192. pdfTitle = info.Title;
  193. }
  194. if (pdfTitle) {
  195. self.setTitle(pdfTitle + " - " + document.title);
  196. }
  197. });
  198. },
  199. setTitle: function pdfViewSetTitle(title) {
  200. document.title = title;
  201. document.getElementById("title").textContent = title;
  202. },
  203. error: function pdfViewError(message, moreInfo) {
  204. const l10n = this.l10n;
  205. const moreInfoText = [
  206. l10n.get(
  207. "error_version_info",
  208. { version: pdfjsLib.version || "?", build: pdfjsLib.build || "?" },
  209. "PDF.js v{{version}} (build: {{build}})"
  210. ),
  211. ];
  212. if (moreInfo) {
  213. moreInfoText.push(
  214. l10n.get(
  215. "error_message",
  216. { message: moreInfo.message },
  217. "Message: {{message}}"
  218. )
  219. );
  220. if (moreInfo.stack) {
  221. moreInfoText.push(
  222. l10n.get("error_stack", { stack: moreInfo.stack }, "Stack: {{stack}}")
  223. );
  224. } else {
  225. if (moreInfo.filename) {
  226. moreInfoText.push(
  227. l10n.get(
  228. "error_file",
  229. { file: moreInfo.filename },
  230. "File: {{file}}"
  231. )
  232. );
  233. }
  234. if (moreInfo.lineNumber) {
  235. moreInfoText.push(
  236. l10n.get(
  237. "error_line",
  238. { line: moreInfo.lineNumber },
  239. "Line: {{line}}"
  240. )
  241. );
  242. }
  243. }
  244. }
  245. const errorWrapper = document.getElementById("errorWrapper");
  246. errorWrapper.hidden = false;
  247. const errorMessage = document.getElementById("errorMessage");
  248. errorMessage.textContent = message;
  249. const closeButton = document.getElementById("errorClose");
  250. closeButton.onclick = function () {
  251. errorWrapper.hidden = true;
  252. };
  253. const errorMoreInfo = document.getElementById("errorMoreInfo");
  254. const moreInfoButton = document.getElementById("errorShowMore");
  255. const lessInfoButton = document.getElementById("errorShowLess");
  256. moreInfoButton.onclick = function () {
  257. errorMoreInfo.hidden = false;
  258. moreInfoButton.hidden = true;
  259. lessInfoButton.hidden = false;
  260. errorMoreInfo.style.height = errorMoreInfo.scrollHeight + "px";
  261. };
  262. lessInfoButton.onclick = function () {
  263. errorMoreInfo.hidden = true;
  264. moreInfoButton.hidden = false;
  265. lessInfoButton.hidden = true;
  266. };
  267. moreInfoButton.hidden = false;
  268. lessInfoButton.hidden = true;
  269. Promise.all(moreInfoText).then(function (parts) {
  270. errorMoreInfo.value = parts.join("\n");
  271. });
  272. },
  273. progress: function pdfViewProgress(level) {
  274. const percent = Math.round(level * 100);
  275. // Updating the bar if value increases.
  276. if (percent > this.loadingBar.percent || isNaN(percent)) {
  277. this.loadingBar.percent = percent;
  278. }
  279. },
  280. get pagesCount() {
  281. return this.pdfDocument.numPages;
  282. },
  283. get page() {
  284. return this.pdfViewer.currentPageNumber;
  285. },
  286. set page(val) {
  287. this.pdfViewer.currentPageNumber = val;
  288. },
  289. zoomIn: function pdfViewZoomIn(ticks) {
  290. let newScale = this.pdfViewer.currentScale;
  291. do {
  292. newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
  293. newScale = Math.ceil(newScale * 10) / 10;
  294. newScale = Math.min(MAX_SCALE, newScale);
  295. } while (--ticks && newScale < MAX_SCALE);
  296. this.pdfViewer.currentScaleValue = newScale;
  297. },
  298. zoomOut: function pdfViewZoomOut(ticks) {
  299. let newScale = this.pdfViewer.currentScale;
  300. do {
  301. newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
  302. newScale = Math.floor(newScale * 10) / 10;
  303. newScale = Math.max(MIN_SCALE, newScale);
  304. } while (--ticks && newScale > MIN_SCALE);
  305. this.pdfViewer.currentScaleValue = newScale;
  306. },
  307. initUI: function pdfViewInitUI() {
  308. const eventBus = new pdfjsViewer.EventBus();
  309. this.eventBus = eventBus;
  310. const linkService = new pdfjsViewer.PDFLinkService({
  311. eventBus,
  312. });
  313. this.pdfLinkService = linkService;
  314. this.l10n = pdfjsViewer.NullL10n;
  315. const container = document.getElementById("viewerContainer");
  316. const pdfViewer = new pdfjsViewer.PDFViewer({
  317. container,
  318. eventBus,
  319. linkService,
  320. l10n: this.l10n,
  321. useOnlyCssZoom: USE_ONLY_CSS_ZOOM,
  322. textLayerMode: TEXT_LAYER_MODE,
  323. });
  324. this.pdfViewer = pdfViewer;
  325. linkService.setViewer(pdfViewer);
  326. this.pdfHistory = new pdfjsViewer.PDFHistory({
  327. eventBus,
  328. linkService,
  329. });
  330. linkService.setHistory(this.pdfHistory);
  331. document.getElementById("previous").addEventListener("click", function () {
  332. PDFViewerApplication.page--;
  333. });
  334. document.getElementById("next").addEventListener("click", function () {
  335. PDFViewerApplication.page++;
  336. });
  337. document.getElementById("zoomIn").addEventListener("click", function () {
  338. PDFViewerApplication.zoomIn();
  339. });
  340. document.getElementById("zoomOut").addEventListener("click", function () {
  341. PDFViewerApplication.zoomOut();
  342. });
  343. document
  344. .getElementById("pageNumber")
  345. .addEventListener("click", function () {
  346. this.select();
  347. });
  348. document
  349. .getElementById("pageNumber")
  350. .addEventListener("change", function () {
  351. PDFViewerApplication.page = this.value | 0;
  352. // Ensure that the page number input displays the correct value,
  353. // even if the value entered by the user was invalid
  354. // (e.g. a floating point number).
  355. if (this.value !== PDFViewerApplication.page.toString()) {
  356. this.value = PDFViewerApplication.page;
  357. }
  358. });
  359. eventBus.on("pagesinit", function () {
  360. // We can use pdfViewer now, e.g. let's change default scale.
  361. pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
  362. });
  363. eventBus.on(
  364. "pagechanging",
  365. function (evt) {
  366. const page = evt.pageNumber;
  367. const numPages = PDFViewerApplication.pagesCount;
  368. document.getElementById("pageNumber").value = page;
  369. document.getElementById("previous").disabled = page <= 1;
  370. document.getElementById("next").disabled = page >= numPages;
  371. },
  372. true
  373. );
  374. },
  375. };
  376. window.PDFViewerApplication = PDFViewerApplication;
  377. document.addEventListener(
  378. "DOMContentLoaded",
  379. function () {
  380. PDFViewerApplication.initUI();
  381. },
  382. true
  383. );
  384. // The offsetParent is not set until the PDF.js iframe or object is visible;
  385. // waiting for first animation.
  386. const animationStarted = new Promise(function (resolve) {
  387. window.requestAnimationFrame(resolve);
  388. });
  389. // We need to delay opening until all HTML is loaded.
  390. animationStarted.then(function () {
  391. PDFViewerApplication.open({
  392. url: DEFAULT_URL,
  393. });
  394. });