|
@@ -0,0 +1,454 @@
|
|
|
|
+/* Copyright 2016 Mozilla Foundation
|
|
|
|
+ *
|
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
|
+ * You may obtain a copy of the License at
|
|
|
|
+ *
|
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
+ *
|
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
|
+ * limitations under the License.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+"use strict";
|
|
|
|
+
|
|
|
|
+if (!pdfjsLib.getDocument || !pdfjsViewer.PDFViewer) {
|
|
|
|
+ // eslint-disable-next-line no-alert
|
|
|
|
+ alert("Please build the pdfjs-dist library using\n `gulp dist-install`");
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const USE_ONLY_CSS_ZOOM = true;
|
|
|
|
+const TEXT_LAYER_MODE = 0; // DISABLE
|
|
|
|
+const MAX_IMAGE_SIZE = 1024 * 1024;
|
|
|
|
+const CMAP_URL = "../../node_modules/pdfjs-dist/cmaps/";
|
|
|
|
+const CMAP_PACKED = true;
|
|
|
|
+
|
|
|
|
+pdfjsLib.GlobalWorkerOptions.workerSrc =
|
|
|
|
+ "../../node_modules/pdfjs-dist/build/pdf.worker.js";
|
|
|
|
+
|
|
|
|
+const DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
|
|
|
+const DEFAULT_SCALE_DELTA = 1.1;
|
|
|
|
+const MIN_SCALE = 0.25;
|
|
|
|
+const MAX_SCALE = 10.0;
|
|
|
|
+const DEFAULT_SCALE_VALUE = "auto";
|
|
|
|
+
|
|
|
|
+const PDFViewerApplication = {
|
|
|
|
+ pdfLoadingTask: null,
|
|
|
|
+ pdfDocument: null,
|
|
|
|
+ pdfViewer: null,
|
|
|
|
+ pdfHistory: null,
|
|
|
|
+ pdfLinkService: null,
|
|
|
|
+ eventBus: null,
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Opens PDF document specified by URL.
|
|
|
|
+ * @returns {Promise} - Returns the promise, which is resolved when document
|
|
|
|
+ * is opened.
|
|
|
|
+ */
|
|
|
|
+ open(params) {
|
|
|
|
+ if (this.pdfLoadingTask) {
|
|
|
|
+ // We need to destroy already opened document
|
|
|
|
+ return this.close().then(
|
|
|
|
+ function () {
|
|
|
|
+ // ... and repeat the open() call.
|
|
|
|
+ return this.open(params);
|
|
|
|
+ }.bind(this)
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const url = params.url;
|
|
|
|
+ const self = this;
|
|
|
|
+ this.setTitleUsingUrl(url);
|
|
|
|
+
|
|
|
|
+ // Loading document.
|
|
|
|
+ const loadingTask = pdfjsLib.getDocument({
|
|
|
|
+ url,
|
|
|
|
+ maxImageSize: MAX_IMAGE_SIZE,
|
|
|
|
+ cMapUrl: CMAP_URL,
|
|
|
|
+ cMapPacked: CMAP_PACKED,
|
|
|
|
+ });
|
|
|
|
+ this.pdfLoadingTask = loadingTask;
|
|
|
|
+
|
|
|
|
+ loadingTask.onProgress = function (progressData) {
|
|
|
|
+ self.progress(progressData.loaded / progressData.total);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return loadingTask.promise.then(
|
|
|
|
+ function (pdfDocument) {
|
|
|
|
+ // Document loaded, specifying document for the viewer.
|
|
|
|
+ self.pdfDocument = pdfDocument;
|
|
|
|
+ self.pdfViewer.setDocument(pdfDocument);
|
|
|
|
+ self.pdfLinkService.setDocument(pdfDocument);
|
|
|
|
+ self.pdfHistory.initialize({
|
|
|
|
+ fingerprint: pdfDocument.fingerprints[0],
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ self.loadingBar.hide();
|
|
|
|
+ self.setTitleUsingMetadata(pdfDocument);
|
|
|
|
+ },
|
|
|
|
+ function (exception) {
|
|
|
|
+ const message = exception && exception.message;
|
|
|
|
+ const l10n = self.l10n;
|
|
|
|
+ let loadingErrorMessage;
|
|
|
|
+
|
|
|
|
+ if (exception instanceof pdfjsLib.InvalidPDFException) {
|
|
|
|
+ // change error message also for other builds
|
|
|
|
+ loadingErrorMessage = l10n.get(
|
|
|
|
+ "invalid_file_error",
|
|
|
|
+ null,
|
|
|
|
+ "Invalid or corrupted PDF file."
|
|
|
|
+ );
|
|
|
|
+ } else if (exception instanceof pdfjsLib.MissingPDFException) {
|
|
|
|
+ // special message for missing PDFs
|
|
|
|
+ loadingErrorMessage = l10n.get(
|
|
|
|
+ "missing_file_error",
|
|
|
|
+ null,
|
|
|
|
+ "Missing PDF file."
|
|
|
|
+ );
|
|
|
|
+ } else if (exception instanceof pdfjsLib.UnexpectedResponseException) {
|
|
|
|
+ loadingErrorMessage = l10n.get(
|
|
|
|
+ "unexpected_response_error",
|
|
|
|
+ null,
|
|
|
|
+ "Unexpected server response."
|
|
|
|
+ );
|
|
|
|
+ } else {
|
|
|
|
+ loadingErrorMessage = l10n.get(
|
|
|
|
+ "loading_error",
|
|
|
|
+ null,
|
|
|
|
+ "An error occurred while loading the PDF."
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ loadingErrorMessage.then(function (msg) {
|
|
|
|
+ self.error(msg, { message });
|
|
|
|
+ });
|
|
|
|
+ self.loadingBar.hide();
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Closes opened PDF document.
|
|
|
|
+ * @returns {Promise} - Returns the promise, which is resolved when all
|
|
|
|
+ * destruction is completed.
|
|
|
|
+ */
|
|
|
|
+ close() {
|
|
|
|
+ const errorWrapper = document.getElementById("errorWrapper");
|
|
|
|
+ errorWrapper.hidden = true;
|
|
|
|
+
|
|
|
|
+ if (!this.pdfLoadingTask) {
|
|
|
|
+ return Promise.resolve();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const promise = this.pdfLoadingTask.destroy();
|
|
|
|
+ this.pdfLoadingTask = null;
|
|
|
|
+
|
|
|
|
+ if (this.pdfDocument) {
|
|
|
|
+ this.pdfDocument = null;
|
|
|
|
+
|
|
|
|
+ this.pdfViewer.setDocument(null);
|
|
|
|
+ this.pdfLinkService.setDocument(null, null);
|
|
|
|
+
|
|
|
|
+ if (this.pdfHistory) {
|
|
|
|
+ this.pdfHistory.reset();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return promise;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ get loadingBar() {
|
|
|
|
+ const bar = document.getElementById("loadingBar");
|
|
|
|
+ return pdfjsLib.shadow(
|
|
|
|
+ this,
|
|
|
|
+ "loadingBar",
|
|
|
|
+ new pdfjsViewer.ProgressBar(bar)
|
|
|
|
+ );
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
|
|
|
|
+ this.url = url;
|
|
|
|
+ let title = pdfjsLib.getFilenameFromUrl(url) || url;
|
|
|
|
+ try {
|
|
|
|
+ title = decodeURIComponent(title);
|
|
|
|
+ } catch (e) {
|
|
|
|
+ // decodeURIComponent may throw URIError,
|
|
|
|
+ // fall back to using the unprocessed url in that case
|
|
|
|
+ }
|
|
|
|
+ this.setTitle(title);
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ setTitleUsingMetadata(pdfDocument) {
|
|
|
|
+ const self = this;
|
|
|
|
+ pdfDocument.getMetadata().then(function (data) {
|
|
|
|
+ const info = data.info,
|
|
|
|
+ metadata = data.metadata;
|
|
|
|
+ self.documentInfo = info;
|
|
|
|
+ self.metadata = metadata;
|
|
|
|
+
|
|
|
|
+ // Provides some basic debug information
|
|
|
|
+ console.log(
|
|
|
|
+ "PDF " +
|
|
|
|
+ pdfDocument.fingerprints[0] +
|
|
|
|
+ " [" +
|
|
|
|
+ info.PDFFormatVersion +
|
|
|
|
+ " " +
|
|
|
|
+ (info.Producer || "-").trim() +
|
|
|
|
+ " / " +
|
|
|
|
+ (info.Creator || "-").trim() +
|
|
|
|
+ "]" +
|
|
|
|
+ " (PDF.js: " +
|
|
|
|
+ (pdfjsLib.version || "-") +
|
|
|
|
+ ")"
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ let pdfTitle;
|
|
|
|
+ if (metadata && metadata.has("dc:title")) {
|
|
|
|
+ const title = metadata.get("dc:title");
|
|
|
|
+ // Ghostscript sometimes returns 'Untitled', so prevent setting the
|
|
|
|
+ // title to 'Untitled.
|
|
|
|
+ if (title !== "Untitled") {
|
|
|
|
+ pdfTitle = title;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!pdfTitle && info && info.Title) {
|
|
|
|
+ pdfTitle = info.Title;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (pdfTitle) {
|
|
|
|
+ self.setTitle(pdfTitle + " - " + document.title);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ setTitle: function pdfViewSetTitle(title) {
|
|
|
|
+ document.title = title;
|
|
|
|
+ document.getElementById("title").textContent = title;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ error: function pdfViewError(message, moreInfo) {
|
|
|
|
+ const l10n = this.l10n;
|
|
|
|
+ const moreInfoText = [
|
|
|
|
+ l10n.get(
|
|
|
|
+ "error_version_info",
|
|
|
|
+ { version: pdfjsLib.version || "?", build: pdfjsLib.build || "?" },
|
|
|
|
+ "PDF.js v{{version}} (build: {{build}})"
|
|
|
|
+ ),
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ if (moreInfo) {
|
|
|
|
+ moreInfoText.push(
|
|
|
|
+ l10n.get(
|
|
|
|
+ "error_message",
|
|
|
|
+ { message: moreInfo.message },
|
|
|
|
+ "Message: {{message}}"
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ if (moreInfo.stack) {
|
|
|
|
+ moreInfoText.push(
|
|
|
|
+ l10n.get("error_stack", { stack: moreInfo.stack }, "Stack: {{stack}}")
|
|
|
|
+ );
|
|
|
|
+ } else {
|
|
|
|
+ if (moreInfo.filename) {
|
|
|
|
+ moreInfoText.push(
|
|
|
|
+ l10n.get(
|
|
|
|
+ "error_file",
|
|
|
|
+ { file: moreInfo.filename },
|
|
|
|
+ "File: {{file}}"
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ if (moreInfo.lineNumber) {
|
|
|
|
+ moreInfoText.push(
|
|
|
|
+ l10n.get(
|
|
|
|
+ "error_line",
|
|
|
|
+ { line: moreInfo.lineNumber },
|
|
|
|
+ "Line: {{line}}"
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const errorWrapper = document.getElementById("errorWrapper");
|
|
|
|
+ errorWrapper.hidden = false;
|
|
|
|
+
|
|
|
|
+ const errorMessage = document.getElementById("errorMessage");
|
|
|
|
+ errorMessage.textContent = message;
|
|
|
|
+
|
|
|
|
+ const closeButton = document.getElementById("errorClose");
|
|
|
|
+ closeButton.onclick = function () {
|
|
|
|
+ errorWrapper.hidden = true;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const errorMoreInfo = document.getElementById("errorMoreInfo");
|
|
|
|
+ const moreInfoButton = document.getElementById("errorShowMore");
|
|
|
|
+ const lessInfoButton = document.getElementById("errorShowLess");
|
|
|
|
+ moreInfoButton.onclick = function () {
|
|
|
|
+ errorMoreInfo.hidden = false;
|
|
|
|
+ moreInfoButton.hidden = true;
|
|
|
|
+ lessInfoButton.hidden = false;
|
|
|
|
+ errorMoreInfo.style.height = errorMoreInfo.scrollHeight + "px";
|
|
|
|
+ };
|
|
|
|
+ lessInfoButton.onclick = function () {
|
|
|
|
+ errorMoreInfo.hidden = true;
|
|
|
|
+ moreInfoButton.hidden = false;
|
|
|
|
+ lessInfoButton.hidden = true;
|
|
|
|
+ };
|
|
|
|
+ moreInfoButton.hidden = false;
|
|
|
|
+ lessInfoButton.hidden = true;
|
|
|
|
+ Promise.all(moreInfoText).then(function (parts) {
|
|
|
|
+ errorMoreInfo.value = parts.join("\n");
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ progress: function pdfViewProgress(level) {
|
|
|
|
+ const percent = Math.round(level * 100);
|
|
|
|
+ // Updating the bar if value increases.
|
|
|
|
+ if (percent > this.loadingBar.percent || isNaN(percent)) {
|
|
|
|
+ this.loadingBar.percent = percent;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ get pagesCount() {
|
|
|
|
+ return this.pdfDocument.numPages;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ get page() {
|
|
|
|
+ return this.pdfViewer.currentPageNumber;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ set page(val) {
|
|
|
|
+ this.pdfViewer.currentPageNumber = val;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ zoomIn: function pdfViewZoomIn(ticks) {
|
|
|
|
+ let newScale = this.pdfViewer.currentScale;
|
|
|
|
+ do {
|
|
|
|
+ newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
|
|
|
|
+ newScale = Math.ceil(newScale * 10) / 10;
|
|
|
|
+ newScale = Math.min(MAX_SCALE, newScale);
|
|
|
|
+ } while (--ticks && newScale < MAX_SCALE);
|
|
|
|
+ this.pdfViewer.currentScaleValue = newScale;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ zoomOut: function pdfViewZoomOut(ticks) {
|
|
|
|
+ let newScale = this.pdfViewer.currentScale;
|
|
|
|
+ do {
|
|
|
|
+ newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
|
|
|
|
+ newScale = Math.floor(newScale * 10) / 10;
|
|
|
|
+ newScale = Math.max(MIN_SCALE, newScale);
|
|
|
|
+ } while (--ticks && newScale > MIN_SCALE);
|
|
|
|
+ this.pdfViewer.currentScaleValue = newScale;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ initUI: function pdfViewInitUI() {
|
|
|
|
+ const eventBus = new pdfjsViewer.EventBus();
|
|
|
|
+ this.eventBus = eventBus;
|
|
|
|
+
|
|
|
|
+ const linkService = new pdfjsViewer.PDFLinkService({
|
|
|
|
+ eventBus,
|
|
|
|
+ });
|
|
|
|
+ this.pdfLinkService = linkService;
|
|
|
|
+
|
|
|
|
+ this.l10n = pdfjsViewer.NullL10n;
|
|
|
|
+
|
|
|
|
+ const container = document.getElementById("viewerContainer");
|
|
|
|
+ const pdfViewer = new pdfjsViewer.PDFViewer({
|
|
|
|
+ container,
|
|
|
|
+ eventBus,
|
|
|
|
+ linkService,
|
|
|
|
+ l10n: this.l10n,
|
|
|
|
+ useOnlyCssZoom: USE_ONLY_CSS_ZOOM,
|
|
|
|
+ textLayerMode: TEXT_LAYER_MODE,
|
|
|
|
+ });
|
|
|
|
+ this.pdfViewer = pdfViewer;
|
|
|
|
+ linkService.setViewer(pdfViewer);
|
|
|
|
+
|
|
|
|
+ this.pdfHistory = new pdfjsViewer.PDFHistory({
|
|
|
|
+ eventBus,
|
|
|
|
+ linkService,
|
|
|
|
+ });
|
|
|
|
+ linkService.setHistory(this.pdfHistory);
|
|
|
|
+
|
|
|
|
+ document.getElementById("previous").addEventListener("click", function () {
|
|
|
|
+ PDFViewerApplication.page--;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ document.getElementById("next").addEventListener("click", function () {
|
|
|
|
+ PDFViewerApplication.page++;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ document.getElementById("zoomIn").addEventListener("click", function () {
|
|
|
|
+ PDFViewerApplication.zoomIn();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ document.getElementById("zoomOut").addEventListener("click", function () {
|
|
|
|
+ PDFViewerApplication.zoomOut();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ document
|
|
|
|
+ .getElementById("pageNumber")
|
|
|
|
+ .addEventListener("click", function () {
|
|
|
|
+ this.select();
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ document
|
|
|
|
+ .getElementById("pageNumber")
|
|
|
|
+ .addEventListener("change", function () {
|
|
|
|
+ PDFViewerApplication.page = this.value | 0;
|
|
|
|
+
|
|
|
|
+ // Ensure that the page number input displays the correct value,
|
|
|
|
+ // even if the value entered by the user was invalid
|
|
|
|
+ // (e.g. a floating point number).
|
|
|
|
+ if (this.value !== PDFViewerApplication.page.toString()) {
|
|
|
|
+ this.value = PDFViewerApplication.page;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ eventBus.on("pagesinit", function () {
|
|
|
|
+ // We can use pdfViewer now, e.g. let's change default scale.
|
|
|
|
+ pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ eventBus.on(
|
|
|
|
+ "pagechanging",
|
|
|
|
+ function (evt) {
|
|
|
|
+ const page = evt.pageNumber;
|
|
|
|
+ const numPages = PDFViewerApplication.pagesCount;
|
|
|
|
+
|
|
|
|
+ document.getElementById("pageNumber").value = page;
|
|
|
|
+ document.getElementById("previous").disabled = page <= 1;
|
|
|
|
+ document.getElementById("next").disabled = page >= numPages;
|
|
|
|
+ },
|
|
|
|
+ true
|
|
|
|
+ );
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+window.PDFViewerApplication = PDFViewerApplication;
|
|
|
|
+
|
|
|
|
+document.addEventListener(
|
|
|
|
+ "DOMContentLoaded",
|
|
|
|
+ function () {
|
|
|
|
+ PDFViewerApplication.initUI();
|
|
|
|
+ },
|
|
|
|
+ true
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+// The offsetParent is not set until the PDF.js iframe or object is visible;
|
|
|
|
+// waiting for first animation.
|
|
|
|
+const animationStarted = new Promise(function (resolve) {
|
|
|
|
+ window.requestAnimationFrame(resolve);
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// We need to delay opening until all HTML is loaded.
|
|
|
|
+animationStarted.then(function () {
|
|
|
|
+ PDFViewerApplication.open({
|
|
|
|
+ url: DEFAULT_URL,
|
|
|
|
+ });
|
|
|
|
+});
|