mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-02-08 00:21:11 +01:00
The percentage calculation is currently "spread out" across various viewer functionality, which we can avoid by having the API handle that instead. Also, remove the `this.#lastProgress` special-case[1] and just register a "normal" `fullReader.onProgress` callback unconditionally. Once `headersReady` is resolved the callback can simply be removed when not needed, since the "worst" thing that could theoretically happen is that the loadingBar (in the viewer) updates sooner this way. In practice though, since `fullReader.read` cannot return data until `headersReady` is resolved, this change is not actually observable in the API. --- [1] This was added in PR 8617, close to a decade ago, but it's not obvious to me that it was ever necessary to implement it that way.
351 lines
9.8 KiB
JavaScript
351 lines
9.8 KiB
JavaScript
/* 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.
|
|
*/
|
|
|
|
if (!pdfjsLib.getDocument || !pdfjsViewer.PDFViewer) {
|
|
// eslint-disable-next-line no-alert
|
|
alert("Please build the pdfjs-dist library using\n `gulp dist-install`");
|
|
}
|
|
|
|
const MAX_CANVAS_PIXELS = 0; // CSS-only zooming.
|
|
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.mjs";
|
|
|
|
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.
|
|
*/
|
|
async open(params) {
|
|
if (this.pdfLoadingTask) {
|
|
// We need to destroy already opened document.
|
|
await this.close();
|
|
}
|
|
|
|
const { url } = params;
|
|
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 = evt => this.progress(evt.percent);
|
|
|
|
return loadingTask.promise.then(
|
|
pdfDocument => {
|
|
// Document loaded, specifying document for the viewer.
|
|
this.pdfDocument = pdfDocument;
|
|
this.pdfViewer.setDocument(pdfDocument);
|
|
this.pdfLinkService.setDocument(pdfDocument);
|
|
this.pdfHistory.initialize({
|
|
fingerprint: pdfDocument.fingerprints[0],
|
|
});
|
|
|
|
this.loadingBar.hide();
|
|
this.setTitleUsingMetadata(pdfDocument);
|
|
},
|
|
reason => {
|
|
let key = "pdfjs-loading-error";
|
|
if (reason instanceof pdfjsLib.InvalidPDFException) {
|
|
key = "pdfjs-invalid-file-error";
|
|
} else if (reason instanceof pdfjsLib.ResponseException) {
|
|
key = reason.missing
|
|
? "pdfjs-missing-file-error"
|
|
: "pdfjs-unexpected-response-error";
|
|
}
|
|
this.l10n.get(key).then(msg => {
|
|
this.error(msg, { message: reason.message });
|
|
});
|
|
this.loadingBar.hide();
|
|
}
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Closes opened PDF document.
|
|
* @returns {Promise} - Returns the promise, which is resolved when all
|
|
* destruction is completed.
|
|
*/
|
|
async close() {
|
|
if (!this.pdfLoadingTask) {
|
|
return;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
await 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 {
|
|
// decodeURIComponent may throw URIError,
|
|
// fall back to using the unprocessed url in that case
|
|
}
|
|
this.setTitle(title);
|
|
},
|
|
|
|
async setTitleUsingMetadata(pdfDocument) {
|
|
const { info, metadata } = await pdfDocument.getMetadata();
|
|
this.documentInfo = info;
|
|
this.metadata = metadata;
|
|
|
|
// Provides some basic debug information
|
|
console.log(
|
|
`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` +
|
|
`${(metadata?.get("pdf:producer") || info.Producer || "-").trim()} / ` +
|
|
`${(metadata?.get("xmp:creatortool") || info.Creator || "-").trim()}` +
|
|
`] (PDF.js: ${pdfjsLib.version || "?"} [${pdfjsLib.build || "?"}])`
|
|
);
|
|
|
|
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) {
|
|
this.setTitle(pdfTitle + " - " + document.title);
|
|
}
|
|
},
|
|
|
|
setTitle: function pdfViewSetTitle(title) {
|
|
document.title = title;
|
|
document.getElementById("title").textContent = title;
|
|
},
|
|
|
|
error: function pdfViewError(message, moreInfo) {
|
|
const moreInfoText = [
|
|
`PDF.js v${pdfjsLib.version || "?"} (build: ${pdfjsLib.build || "?"})`,
|
|
];
|
|
if (moreInfo) {
|
|
moreInfoText.push(`Message: ${moreInfo.message}`);
|
|
|
|
if (moreInfo.stack) {
|
|
moreInfoText.push(`Stack: ${moreInfo.stack}`);
|
|
} else {
|
|
if (moreInfo.filename) {
|
|
moreInfoText.push(`File: ${moreInfo.filename}`);
|
|
}
|
|
if (moreInfo.lineNumber) {
|
|
moreInfoText.push(`Line: ${moreInfo.lineNumber}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.error(`${message}\n\n${moreInfoText.join("\n")}`);
|
|
},
|
|
|
|
progress(percent) {
|
|
// 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 = new pdfjsViewer.GenericL10n();
|
|
|
|
const container = document.getElementById("viewerContainer");
|
|
const pdfViewer = new pdfjsViewer.PDFViewer({
|
|
container,
|
|
eventBus,
|
|
linkService,
|
|
l10n: this.l10n,
|
|
maxCanvasPixels: MAX_CANVAS_PIXELS,
|
|
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,
|
|
});
|
|
});
|