diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index 1de4e530a..1c0c3a42f 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -19,8 +19,9 @@ import { import { getOutputScale, NullL10n } from './ui_utils'; import { RenderingStates } from './pdf_rendering_queue'; -var THUMBNAIL_WIDTH = 98; // px -var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px +const MAX_NUM_SCALING_STEPS = 3; +const THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px +const THUMBNAIL_WIDTH = 98; // px /** * @typedef {Object} PDFThumbnailViewOptions @@ -31,7 +32,7 @@ var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. * @property {boolean} disableCanvasToImageConversion - (optional) Don't convert * the canvas thumbnails to images. This prevents `toDataURL` calls, - * but increases the overall memory usage. The default value is false. + * but increases the overall memory usage. The default value is `false`. * @property {IL10n} l10n - Localization service. */ @@ -77,23 +78,14 @@ const TempImageFactory = (function TempImageFactoryClosure() { })(); /** - * @class * @implements {IRenderableView} */ -var PDFThumbnailView = (function PDFThumbnailViewClosure() { +class PDFThumbnailView { /** - * @constructs PDFThumbnailView * @param {PDFThumbnailViewOptions} options */ - function PDFThumbnailView(options) { - var container = options.container; - var id = options.id; - var defaultViewport = options.defaultViewport; - var linkService = options.linkService; - var renderingQueue = options.renderingQueue; - var disableCanvasToImageConversion = - options.disableCanvasToImageConversion || false; - + constructor({ container, id, defaultViewport, linkService, renderingQueue, + disableCanvasToImageConversion = false, l10n = NullL10n, }) { this.id = id; this.renderingId = 'thumbnail' + id; this.pageLabel = null; @@ -119,21 +111,21 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; this.scale = this.canvasWidth / this.pageWidth; - this.l10n = options.l10n || NullL10n; + this.l10n = l10n; - var anchor = document.createElement('a'); + let anchor = document.createElement('a'); anchor.href = linkService.getAnchorUrl('#page=' + id); this.l10n.get('thumb_page_title', { page: id, }, 'Page {{page}}'). then((msg) => { anchor.title = msg; }); - anchor.onclick = function stopNavigation() { + anchor.onclick = function() { linkService.page = id; return false; }; this.anchor = anchor; - var div = document.createElement('div'); + let div = document.createElement('div'); div.className = 'thumbnail'; div.setAttribute('data-page-number', this.id); this.div = div; @@ -144,9 +136,9 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { div.classList.add('selected'); } - var ring = document.createElement('div'); + let ring = document.createElement('div'); ring.className = 'thumbnailSelectionRing'; - var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH; + let borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH; ring.style.width = this.canvasWidth + borderAdjustment + 'px'; ring.style.height = this.canvasHeight + borderAdjustment + 'px'; this.ring = ring; @@ -156,296 +148,288 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() { container.appendChild(anchor); } - PDFThumbnailView.prototype = { - setPdfPage: function PDFThumbnailView_setPdfPage(pdfPage) { - this.pdfPage = pdfPage; - this.pdfPageRotate = pdfPage.rotate; - var totalRotation = (this.rotation + this.pdfPageRotate) % 360; - this.viewport = pdfPage.getViewport(1, totalRotation); - this.reset(); - }, + setPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + let totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = pdfPage.getViewport(1, totalRotation); + this.reset(); + } - reset: function PDFThumbnailView_reset() { - this.cancelRendering(); + reset() { + this.cancelRendering(); - this.pageWidth = this.viewport.width; - this.pageHeight = this.viewport.height; - this.pageRatio = this.pageWidth / this.pageHeight; + 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.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; + this.scale = (this.canvasWidth / this.pageWidth); - this.div.removeAttribute('data-loaded'); - var ring = this.ring; - var childNodes = ring.childNodes; - for (var i = childNodes.length - 1; i >= 0; i--) { - ring.removeChild(childNodes[i]); - } - var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH; - ring.style.width = this.canvasWidth + borderAdjustment + 'px'; - ring.style.height = this.canvasHeight + borderAdjustment + 'px'; + this.div.removeAttribute('data-loaded'); + let ring = this.ring; + let childNodes = ring.childNodes; + for (let i = childNodes.length - 1; i >= 0; i--) { + ring.removeChild(childNodes[i]); + } + let borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH; + ring.style.width = this.canvasWidth + borderAdjustment + 'px'; + ring.style.height = this.canvasHeight + borderAdjustment + 'px'; - if (this.canvas) { - // Zeroing the width and height causes Firefox to release graphics - // resources immediately, which can greatly reduce memory consumption. - this.canvas.width = 0; - this.canvas.height = 0; - delete this.canvas; - } - if (this.image) { - this.image.removeAttribute('src'); - delete this.image; - } - }, + if (this.canvas) { + // Zeroing the width and height causes Firefox to release graphics + // resources immediately, which can greatly reduce memory consumption. + this.canvas.width = 0; + this.canvas.height = 0; + delete this.canvas; + } + if (this.image) { + this.image.removeAttribute('src'); + delete this.image; + } + } - update: function PDFThumbnailView_update(rotation) { - if (typeof rotation !== 'undefined') { - this.rotation = rotation; - } - var totalRotation = (this.rotation + this.pdfPageRotate) % 360; - this.viewport = this.viewport.clone({ - scale: 1, - rotation: totalRotation, - }); - this.reset(); - }, + update(rotation) { + if (typeof rotation !== 'undefined') { + this.rotation = rotation; + } + let totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: 1, + rotation: totalRotation, + }); + this.reset(); + } - cancelRendering: function PDFThumbnailView_cancelRendering() { - if (this.renderTask) { - this.renderTask.cancel(); - this.renderTask = null; - } - this.renderingState = RenderingStates.INITIAL; - this.resume = null; - }, + cancelRendering() { + if (this.renderTask) { + this.renderTask.cancel(); + this.renderTask = null; + } + this.renderingState = RenderingStates.INITIAL; + this.resume = null; + } - /** - * @private - */ - _getPageDrawContext: - function PDFThumbnailView_getPageDrawContext(noCtxScale) { - var canvas = document.createElement('canvas'); - // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`, - // until rendering/image conversion is complete, to avoid display issues. - this.canvas = canvas; + /** + * @private + */ + _getPageDrawContext(noCtxScale = false) { + let canvas = document.createElement('canvas'); + // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`, + // until rendering/image conversion is complete, to avoid display issues. + this.canvas = canvas; + + if (typeof PDFJSDev === 'undefined' || + PDFJSDev.test('MOZCENTRAL || FIREFOX || GENERIC')) { + canvas.mozOpaque = true; + } + let ctx = canvas.getContext('2d', { alpha: false, }); + let outputScale = getOutputScale(ctx); - if (typeof PDFJSDev === 'undefined' || - PDFJSDev.test('MOZCENTRAL || FIREFOX || GENERIC')) { - canvas.mozOpaque = true; - } - var ctx = canvas.getContext('2d', { alpha: false, }); - var outputScale = 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'; - 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; + } - if (!noCtxScale && outputScale.scaled) { - ctx.scale(outputScale.sx, outputScale.sy); - } - return ctx; - }, + /** + * @private + */ + _convertCanvasToImage() { + if (!this.canvas) { + return; + } + if (this.renderingState !== RenderingStates.FINISHED) { + return; + } + let id = this.renderingId; + let className = 'thumbnailImage'; - /** - * @private - */ - _convertCanvasToImage: function PDFThumbnailView_convertCanvasToImage() { - if (!this.canvas) { - return; - } - if (this.renderingState !== RenderingStates.FINISHED) { - return; - } - var id = this.renderingId; - var className = 'thumbnailImage'; - - if (this.disableCanvasToImageConversion) { - this.canvas.id = id; - this.canvas.className = className; - this.l10n.get('thumb_page_canvas', { page: this.pageId, }, - 'Thumbnail of Page {{page}}').then((msg) => { - this.canvas.setAttribute('aria-label', msg); - }); - - this.div.setAttribute('data-loaded', true); - this.ring.appendChild(this.canvas); - return; - } - var image = document.createElement('img'); - image.id = id; - image.className = className; + if (this.disableCanvasToImageConversion) { + this.canvas.id = id; + this.canvas.className = className; this.l10n.get('thumb_page_canvas', { page: this.pageId, }, - 'Thumbnail of Page {{page}}'). - then((msg) => { - image.setAttribute('aria-label', msg); + 'Thumbnail of Page {{page}}').then((msg) => { + this.canvas.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.ring.appendChild(this.canvas); + return; + } + let image = document.createElement('img'); + image.id = id; + image.className = className; + this.l10n.get('thumb_page_canvas', { page: this.pageId, }, + 'Thumbnail of Page {{page}}'). + then((msg) => { + image.setAttribute('aria-label', msg); + }); - // Zeroing the width and height causes Firefox to release graphics - // resources immediately, which can greatly reduce memory consumption. - this.canvas.width = 0; - this.canvas.height = 0; - delete this.canvas; - }, + image.style.width = this.canvasWidth + 'px'; + image.style.height = this.canvasHeight + 'px'; - draw() { - if (this.renderingState !== RenderingStates.INITIAL) { - console.error('Must be in new state before drawing'); - return Promise.resolve(undefined); - } + image.src = this.canvas.toDataURL(); + this.image = image; - this.renderingState = RenderingStates.RUNNING; - - let renderCapability = createPromiseCapability(); - - let finishRenderTask = (error) => { - // The renderTask may have been replaced by a new one, so only remove - // the reference to the renderTask if it matches the one that is - // triggering this callback. - if (renderTask === this.renderTask) { - this.renderTask = null; - } - - if (((typeof PDFJSDev === 'undefined' || - !PDFJSDev.test('PDFJS_NEXT')) && error === 'cancelled') || - error instanceof RenderingCancelledException) { - renderCapability.resolve(undefined); - return; - } - - this.renderingState = RenderingStates.FINISHED; - this._convertCanvasToImage(); - - if (!error) { - renderCapability.resolve(undefined); - } else { - renderCapability.reject(error); - } - }; - - let ctx = this._getPageDrawContext(); - let drawViewport = this.viewport.clone({ scale: this.scale, }); - let renderContinueCallback = (cont) => { - if (!this.renderingQueue.isHighestPriority(this)) { - this.renderingState = RenderingStates.PAUSED; - this.resume = () => { - this.renderingState = RenderingStates.RUNNING; - cont(); - }; - return; - } - cont(); - }; - - let renderContext = { - canvasContext: ctx, - viewport: drawViewport, - }; - let renderTask = this.renderTask = this.pdfPage.render(renderContext); - renderTask.onContinue = renderContinueCallback; - - renderTask.promise.then(function() { - finishRenderTask(null); - }, function(error) { - finishRenderTask(error); - }); - return renderCapability.promise; - }, + this.div.setAttribute('data-loaded', true); + this.ring.appendChild(image); - setImage: function PDFThumbnailView_setImage(pageView) { - if (this.renderingState !== RenderingStates.INITIAL) { - return; + // Zeroing the width and height causes Firefox to release graphics + // resources immediately, which can greatly reduce memory consumption. + this.canvas.width = 0; + this.canvas.height = 0; + delete this.canvas; + } + + draw() { + if (this.renderingState !== RenderingStates.INITIAL) { + console.error('Must be in new state before drawing'); + return Promise.resolve(undefined); + } + this.renderingState = RenderingStates.RUNNING; + + let renderCapability = createPromiseCapability(); + let finishRenderTask = (error) => { + // The renderTask may have been replaced by a new one, so only remove + // the reference to the renderTask if it matches the one that is + // triggering this callback. + if (renderTask === this.renderTask) { + this.renderTask = null; } - var img = pageView.canvas; - if (!img) { + + if (((typeof PDFJSDev === 'undefined' || + !PDFJSDev.test('PDFJS_NEXT')) && error === 'cancelled') || + error instanceof RenderingCancelledException) { + renderCapability.resolve(undefined); return; } - if (!this.pdfPage) { - this.setPdfPage(pageView.pdfPage); - } this.renderingState = RenderingStates.FINISHED; + this._convertCanvasToImage(); - var ctx = this._getPageDrawContext(true); - var canvas = ctx.canvas; + if (!error) { + renderCapability.resolve(undefined); + } else { + renderCapability.reject(error); + } + }; - if (img.width <= 2 * canvas.width) { - ctx.drawImage(img, 0, 0, img.width, img.height, - 0, 0, canvas.width, canvas.height); - this._convertCanvasToImage(); + let ctx = this._getPageDrawContext(); + let drawViewport = this.viewport.clone({ scale: this.scale, }); + let renderContinueCallback = (cont) => { + if (!this.renderingQueue.isHighestPriority(this)) { + this.renderingState = RenderingStates.PAUSED; + this.resume = () => { + this.renderingState = RenderingStates.RUNNING; + cont(); + }; return; } - // drawImage does an awful job of rescaling the image, doing it gradually. - var MAX_NUM_SCALING_STEPS = 3; - var reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS; - var reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS; - var reducedImage = TempImageFactory.getCanvas(reducedWidth, - reducedHeight); - var 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, + cont(); + }; + + let renderContext = { + canvasContext: ctx, + viewport: drawViewport, + }; + let renderTask = this.renderTask = this.pdfPage.render(renderContext); + renderTask.onContinue = renderContinueCallback; + + renderTask.promise.then(function() { + finishRenderTask(null); + }, function(error) { + finishRenderTask(error); + }); + return renderCapability.promise; + } + + setImage(pageView) { + if (this.renderingState !== RenderingStates.INITIAL) { + return; + } + let img = pageView.canvas; + if (!img) { + return; + } + if (!this.pdfPage) { + this.setPdfPage(pageView.pdfPage); + } + + this.renderingState = RenderingStates.FINISHED; + + let ctx = this._getPageDrawContext(true); + let 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; + } - get pageId() { - return (this.pageLabel !== null ? this.pageLabel : this.id); - }, + // drawImage does an awful job of rescaling the image, doing it gradually. + let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS; + let reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS; + let reducedImage = TempImageFactory.getCanvas(reducedWidth, + reducedHeight); + let reducedImageCtx = reducedImage.getContext('2d'); - /** - * @param {string|null} label - */ - setPageLabel: function PDFThumbnailView_setPageLabel(label) { - this.pageLabel = (typeof label === 'string' ? label : null); + 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(); + } - this.l10n.get('thumb_page_title', { page: this.pageId, }, - 'Page {{page}}').then((msg) => { - this.anchor.title = msg; - }); + get pageId() { + return (this.pageLabel !== null ? this.pageLabel : this.id); + } - if (this.renderingState !== RenderingStates.FINISHED) { - return; - } + /** + * @param {string|null} label + */ + setPageLabel(label) { + this.pageLabel = (typeof label === 'string' ? label : null); - this.l10n.get('thumb_page_canvas', { page: this.pageId, }, - 'Thumbnail of Page {{page}}').then((ariaLabel) => { - if (this.image) { - this.image.setAttribute('aria-label', ariaLabel); - } else if (this.disableCanvasToImageConversion && this.canvas) { - this.canvas.setAttribute('aria-label', ariaLabel); - } - }); - }, - }; + this.l10n.get('thumb_page_title', { page: this.pageId, }, + 'Page {{page}}').then((msg) => { + this.anchor.title = msg; + }); - PDFThumbnailView.cleanup = function() { - TempImageFactory.destroyCanvas(); - }; + if (this.renderingState !== RenderingStates.FINISHED) { + return; + } - return PDFThumbnailView; -})(); + this.l10n.get('thumb_page_canvas', { page: this.pageId, }, + 'Thumbnail of Page {{page}}').then((ariaLabel) => { + if (this.image) { + this.image.setAttribute('aria-label', ariaLabel); + } else if (this.disableCanvasToImageConversion && this.canvas) { + this.canvas.setAttribute('aria-label', ariaLabel); + } + }); + } + + static cleanup() { + TempImageFactory.destroyCanvas(); + } +} export { PDFThumbnailView,