blob: 006e18ef1446ecae432bf34c59f4897dea45464b [file] [log] [blame]
/**
* @licstart The following is the entire license notice for the
* Javascript code in this page
*
* Copyright 2020 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.
*
* @licend The above is the entire license notice for the
* Javascript code in this page
*/
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.PDFThumbnailView = void 0;
var _pdf = require("../pdf");
var _ui_utils = require("./ui_utils.js");
var _pdf_rendering_queue = require("./pdf_rendering_queue.js");
const MAX_NUM_SCALING_STEPS = 3;
const THUMBNAIL_CANVAS_BORDER_WIDTH = 1;
const THUMBNAIL_WIDTH = 98;
const TempImageFactory = function TempImageFactoryClosure() {
let tempCanvasCache = null;
return {
getCanvas(width, height) {
let tempCanvas = tempCanvasCache;
if (!tempCanvas) {
tempCanvas = document.createElement("canvas");
tempCanvasCache = tempCanvas;
}
tempCanvas.width = width;
tempCanvas.height = height;
tempCanvas.mozOpaque = true;
const ctx = tempCanvas.getContext("2d", {
alpha: false
});
ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, width, height);
ctx.restore();
return tempCanvas;
},
destroyCanvas() {
const tempCanvas = tempCanvasCache;
if (tempCanvas) {
tempCanvas.width = 0;
tempCanvas.height = 0;
}
tempCanvasCache = null;
}
};
}();
class PDFThumbnailView {
constructor({
container,
id,
defaultViewport,
optionalContentConfigPromise,
linkService,
renderingQueue,
checkSetImageDisabled,
disableCanvasToImageConversion = false,
l10n = _ui_utils.NullL10n
}) {
this.id = id;
this.renderingId = "thumbnail" + id;
this.pageLabel = null;
this.pdfPage = null;
this.rotation = 0;
this.viewport = defaultViewport;
this.pdfPageRotate = defaultViewport.rotation;
this._optionalContentConfigPromise = optionalContentConfigPromise || null;
this.linkService = linkService;
this.renderingQueue = renderingQueue;
this.renderTask = null;
this.renderingState = _pdf_rendering_queue.RenderingStates.INITIAL;
this.resume = null;
this._checkSetImageDisabled = checkSetImageDisabled || function () {
return false;
};
this.disableCanvasToImageConversion = disableCanvasToImageConversion;
this.pageWidth = this.viewport.width;
this.pageHeight = this.viewport.height;
this.pageRatio = this.pageWidth / this.pageHeight;
this.canvasWidth = THUMBNAIL_WIDTH;
this.canvasHeight = this.canvasWidth / this.pageRatio | 0;
this.scale = this.canvasWidth / this.pageWidth;
this.l10n = l10n;
const anchor = document.createElement("a");
anchor.href = linkService.getAnchorUrl("#page=" + id);
this._thumbPageTitle.then(msg => {
anchor.title = msg;
});
anchor.onclick = function () {
linkService.page = id;
return false;
};
this.anchor = anchor;
const div = document.createElement("div");
div.className = "thumbnail";
div.setAttribute("data-page-number", this.id);
this.div = div;
const ring = document.createElement("div");
ring.className = "thumbnailSelectionRing";
const borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
ring.style.width = this.canvasWidth + borderAdjustment + "px";
ring.style.height = this.canvasHeight + borderAdjustment + "px";
this.ring = ring;
div.appendChild(ring);
anchor.appendChild(div);
container.appendChild(anchor);
}
setPdfPage(pdfPage) {
this.pdfPage = pdfPage;
this.pdfPageRotate = pdfPage.rotate;
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
this.viewport = pdfPage.getViewport({
scale: 1,
rotation: totalRotation
});
this.reset();
}
reset() {
this.cancelRendering();
this.renderingState = _pdf_rendering_queue.RenderingStates.INITIAL;
this.pageWidth = this.viewport.width;
this.pageHeight = this.viewport.height;
this.pageRatio = this.pageWidth / this.pageHeight;
this.canvasHeight = this.canvasWidth / this.pageRatio | 0;
this.scale = this.canvasWidth / this.pageWidth;
this.div.removeAttribute("data-loaded");
const ring = this.ring;
const childNodes = ring.childNodes;
for (let i = childNodes.length - 1; i >= 0; i--) {
ring.removeChild(childNodes[i]);
}
const borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
ring.style.width = this.canvasWidth + borderAdjustment + "px";
ring.style.height = this.canvasHeight + borderAdjustment + "px";
if (this.canvas) {
this.canvas.width = 0;
this.canvas.height = 0;
delete this.canvas;
}
if (this.image) {
this.image.removeAttribute("src");
delete this.image;
}
}
update(rotation) {
if (typeof rotation !== "undefined") {
this.rotation = rotation;
}
const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
this.viewport = this.viewport.clone({
scale: 1,
rotation: totalRotation
});
this.reset();
}
cancelRendering() {
if (this.renderTask) {
this.renderTask.cancel();
this.renderTask = null;
}
this.resume = null;
}
_getPageDrawContext(noCtxScale = false) {
const canvas = document.createElement("canvas");
this.canvas = canvas;
canvas.mozOpaque = true;
const ctx = canvas.getContext("2d", {
alpha: false
});
const outputScale = (0, _ui_utils.getOutputScale)(ctx);
canvas.width = this.canvasWidth * outputScale.sx | 0;
canvas.height = this.canvasHeight * outputScale.sy | 0;
canvas.style.width = this.canvasWidth + "px";
canvas.style.height = this.canvasHeight + "px";
if (!noCtxScale && outputScale.scaled) {
ctx.scale(outputScale.sx, outputScale.sy);
}
return ctx;
}
_convertCanvasToImage() {
if (!this.canvas) {
return;
}
if (this.renderingState !== _pdf_rendering_queue.RenderingStates.FINISHED) {
return;
}
const className = "thumbnailImage";
if (this.disableCanvasToImageConversion) {
this.canvas.className = className;
this._thumbPageCanvas.then(msg => {
this.canvas.setAttribute("aria-label", msg);
});
this.div.setAttribute("data-loaded", true);
this.ring.appendChild(this.canvas);
return;
}
const image = document.createElement("img");
image.className = className;
this._thumbPageCanvas.then(msg => {
image.setAttribute("aria-label", msg);
});
image.style.width = this.canvasWidth + "px";
image.style.height = this.canvasHeight + "px";
image.src = this.canvas.toDataURL();
this.image = image;
this.div.setAttribute("data-loaded", true);
this.ring.appendChild(image);
this.canvas.width = 0;
this.canvas.height = 0;
delete this.canvas;
}
draw() {
if (this.renderingState !== _pdf_rendering_queue.RenderingStates.INITIAL) {
console.error("Must be in new state before drawing");
return Promise.resolve(undefined);
}
const {
pdfPage
} = this;
if (!pdfPage) {
this.renderingState = _pdf_rendering_queue.RenderingStates.FINISHED;
return Promise.reject(new Error("pdfPage is not loaded"));
}
this.renderingState = _pdf_rendering_queue.RenderingStates.RUNNING;
const renderCapability = (0, _pdf.createPromiseCapability)();
const finishRenderTask = error => {
if (renderTask === this.renderTask) {
this.renderTask = null;
}
if (error instanceof _pdf.RenderingCancelledException) {
renderCapability.resolve(undefined);
return;
}
this.renderingState = _pdf_rendering_queue.RenderingStates.FINISHED;
this._convertCanvasToImage();
if (!error) {
renderCapability.resolve(undefined);
} else {
renderCapability.reject(error);
}
};
const ctx = this._getPageDrawContext();
const drawViewport = this.viewport.clone({
scale: this.scale
});
const renderContinueCallback = cont => {
if (!this.renderingQueue.isHighestPriority(this)) {
this.renderingState = _pdf_rendering_queue.RenderingStates.PAUSED;
this.resume = () => {
this.renderingState = _pdf_rendering_queue.RenderingStates.RUNNING;
cont();
};
return;
}
cont();
};
const renderContext = {
canvasContext: ctx,
viewport: drawViewport,
optionalContentConfigPromise: this._optionalContentConfigPromise
};
const renderTask = this.renderTask = pdfPage.render(renderContext);
renderTask.onContinue = renderContinueCallback;
renderTask.promise.then(function () {
finishRenderTask(null);
}, function (error) {
finishRenderTask(error);
});
return renderCapability.promise;
}
setImage(pageView) {
if (this._checkSetImageDisabled()) {
return;
}
if (this.renderingState !== _pdf_rendering_queue.RenderingStates.INITIAL) {
return;
}
const img = pageView.canvas;
if (!img) {
return;
}
if (!this.pdfPage) {
this.setPdfPage(pageView.pdfPage);
}
this.renderingState = _pdf_rendering_queue.RenderingStates.FINISHED;
const ctx = this._getPageDrawContext(true);
const canvas = ctx.canvas;
if (img.width <= 2 * canvas.width) {
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
this._convertCanvasToImage();
return;
}
let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS;
let reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS;
const reducedImage = TempImageFactory.getCanvas(reducedWidth, reducedHeight);
const reducedImageCtx = reducedImage.getContext("2d");
while (reducedWidth > img.width || reducedHeight > img.height) {
reducedWidth >>= 1;
reducedHeight >>= 1;
}
reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, reducedWidth, reducedHeight);
while (reducedWidth > 2 * canvas.width) {
reducedImageCtx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, reducedWidth >> 1, reducedHeight >> 1);
reducedWidth >>= 1;
reducedHeight >>= 1;
}
ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, canvas.width, canvas.height);
this._convertCanvasToImage();
}
get _thumbPageTitle() {
return this.l10n.get("thumb_page_title", {
page: this.pageLabel ?? this.id
}, "Page {{page}}");
}
get _thumbPageCanvas() {
return this.l10n.get("thumb_page_canvas", {
page: this.pageLabel ?? this.id
}, "Thumbnail of Page {{page}}");
}
setPageLabel(label) {
this.pageLabel = typeof label === "string" ? label : null;
this._thumbPageTitle.then(msg => {
this.anchor.title = msg;
});
if (this.renderingState !== _pdf_rendering_queue.RenderingStates.FINISHED) {
return;
}
this._thumbPageCanvas.then(msg => {
if (this.image) {
this.image.setAttribute("aria-label", msg);
} else if (this.disableCanvasToImageConversion && this.canvas) {
this.canvas.setAttribute("aria-label", msg);
}
});
}
static cleanup() {
TempImageFactory.destroyCanvas();
}
}
exports.PDFThumbnailView = PDFThumbnailView;