diff --git a/src/display/display_utils.js b/src/display/display_utils.js index fae0abce3..4ebe785dc 100644 --- a/src/display/display_utils.js +++ b/src/display/display_utils.js @@ -640,9 +640,39 @@ class OutputScale { return this.sx !== 1 || this.sy !== 1; } + /** + * @type {boolean} Returns `true` when scaling is symmetric, + * `false` otherwise. + */ get symmetric() { return this.sx === this.sy; } + + /** + * @returns {boolean} Returns `true` if scaling was limited, + * `false` otherwise. + */ + limitCanvas(width, height, maxPixels, maxDim) { + let maxAreaScale = Infinity, + maxWidthScale = Infinity, + maxHeightScale = Infinity; + + if (maxPixels > 0) { + maxAreaScale = Math.sqrt(maxPixels / (width * height)); + } + if (maxDim !== -1) { + maxWidthScale = maxDim / width; + maxHeightScale = maxDim / height; + } + const maxScale = Math.min(maxAreaScale, maxWidthScale, maxHeightScale); + + if (this.sx > maxScale || this.sy > maxScale) { + this.sx = maxScale; + this.sy = maxScale; + return true; + } + return false; + } } // See https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types diff --git a/web/app.js b/web/app.js index b73c7a0ad..ddb9006a0 100644 --- a/web/app.js +++ b/web/app.js @@ -478,6 +478,7 @@ const PDFViewerApplication = { : null; const enableHWA = AppOptions.get("enableHWA"), + maxCanvasPixels = AppOptions.get("maxCanvasPixels"), maxCanvasDim = AppOptions.get("maxCanvasDim"); const pdfViewer = new PDFViewer({ container, @@ -506,7 +507,7 @@ const PDFViewerApplication = { ), imageResourcesPath: AppOptions.get("imageResourcesPath"), enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), - maxCanvasPixels: AppOptions.get("maxCanvasPixels"), + maxCanvasPixels, maxCanvasDim, enableDetailCanvas: AppOptions.get("enableDetailCanvas"), enablePermissions: AppOptions.get("enablePermissions"), @@ -529,6 +530,7 @@ const PDFViewerApplication = { eventBus, renderingQueue: pdfRenderingQueue, linkService: pdfLinkService, + maxCanvasPixels, maxCanvasDim, pageColors, abortSignal, diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 164c668fd..91e4867bb 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -775,28 +775,13 @@ class PDFPageView extends BasePDFPageView { outputScale.sx *= invScale; outputScale.sy *= invScale; this.#needsRestrictedScaling = true; - } else if (this.maxCanvasPixels > 0 || this.maxCanvasDim !== -1) { - let maxAreaScale = Infinity, - maxWidthScale = Infinity, - maxHeightScale = Infinity; - - if (this.maxCanvasPixels > 0) { - const pixelsInViewport = width * height; - maxAreaScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport); - } - if (this.maxCanvasDim !== -1) { - maxWidthScale = this.maxCanvasDim / width; - maxHeightScale = this.maxCanvasDim / height; - } - const maxScale = Math.min(maxAreaScale, maxWidthScale, maxHeightScale); - - if (outputScale.sx > maxScale || outputScale.sy > maxScale) { - outputScale.sx = maxScale; - outputScale.sy = maxScale; - this.#needsRestrictedScaling = true; - } else { - this.#needsRestrictedScaling = false; - } + } else { + this.#needsRestrictedScaling = outputScale.limitCanvas( + width, + height, + this.maxCanvasPixels, + this.maxCanvasDim + ); } } diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js index a1ef6fd44..59e171bb3 100644 --- a/web/pdf_thumbnail_view.js +++ b/web/pdf_thumbnail_view.js @@ -42,6 +42,9 @@ const THUMBNAIL_WIDTH = 98; // px * The default value is `null`. * @property {IPDFLinkService} linkService - The navigation/linking service. * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. + * @property {number} [maxCanvasPixels] - The maximum supported canvas size in + * total pixels, i.e. width * height. Use `-1` for no limit, or `0` for + * CSS-only zooming. The default value is 4096 * 8192 (32 mega-pixels). * @property {number} [maxCanvasDim] - The maximum supported canvas dimension, * in either width or height. Use `-1` for no limit. * The default value is 32767. @@ -97,6 +100,7 @@ class PDFThumbnailView { optionalContentConfigPromise, linkService, renderingQueue, + maxCanvasPixels, maxCanvasDim, pageColors, enableHWA, @@ -110,6 +114,7 @@ class PDFThumbnailView { this.viewport = defaultViewport; this.pdfPageRotate = defaultViewport.rotation; this._optionalContentConfigPromise = optionalContentConfigPromise || null; + this.maxCanvasPixels = maxCanvasPixels ?? AppOptions.get("maxCanvasPixels"); this.maxCanvasDim = maxCanvasDim || AppOptions.get("maxCanvasDim"); this.pageColors = pageColors || null; this.enableHWA = enableHWA || false; @@ -218,16 +223,12 @@ class PDFThumbnailView { const width = upscaleFactor * this.canvasWidth, height = upscaleFactor * this.canvasHeight; - if (this.maxCanvasDim !== -1) { - const maxScale = Math.min( - this.maxCanvasDim / width, - this.maxCanvasDim / height - ); - if (outputScale.sx > maxScale || outputScale.sy > maxScale) { - outputScale.sx = maxScale; - outputScale.sy = maxScale; - } - } + outputScale.limitCanvas( + width, + height, + this.maxCanvasPixels, + this.maxCanvasDim + ); canvas.width = (width * outputScale.sx) | 0; canvas.height = (height * outputScale.sy) | 0; @@ -364,6 +365,27 @@ class PDFThumbnailView { this.#convertCanvasToImage(canvas); } + #getReducedImageDims(canvas) { + let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS, + reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS; + + const outputScale = new OutputScale(); + // Here we're not actually "rendering" to the canvas and the `OutputScale` + // is thus only used to limit the canvas size, hence the identity scale. + outputScale.sx = outputScale.sy = 1; + + outputScale.limitCanvas( + reducedWidth, + reducedHeight, + this.maxCanvasPixels, + this.maxCanvasDim + ); + reducedWidth = (reducedWidth * outputScale.sx) | 0; + reducedHeight = (reducedHeight * outputScale.sy) | 0; + + return [reducedWidth, reducedHeight]; + } + #reduceImage(img) { const { ctx, canvas } = this.#getPageDrawContext(1, true); @@ -381,24 +403,8 @@ class PDFThumbnailView { ); return canvas; } - const { maxCanvasDim } = this; - // 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; - - if (maxCanvasDim !== -1) { - const maxWidthScale = maxCanvasDim / reducedWidth, - maxHeightScale = maxCanvasDim / reducedHeight; - - if (maxWidthScale < 1) { - reducedWidth = maxCanvasDim; - reducedHeight = (reducedHeight * maxWidthScale) | 0; - } else if (maxHeightScale < 1) { - reducedWidth = (reducedWidth * maxHeightScale) | 0; - reducedHeight = maxCanvasDim; - } - } + let [reducedWidth, reducedHeight] = this.#getReducedImageDims(canvas); const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas( reducedWidth, reducedHeight diff --git a/web/pdf_thumbnail_viewer.js b/web/pdf_thumbnail_viewer.js index d0a5eb576..836dd5e86 100644 --- a/web/pdf_thumbnail_viewer.js +++ b/web/pdf_thumbnail_viewer.js @@ -39,6 +39,9 @@ const THUMBNAIL_SELECTED_CLASS = "selected"; * @property {EventBus} eventBus - The application event bus. * @property {IPDFLinkService} linkService - The navigation/linking service. * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. + * @property {number} [maxCanvasPixels] - The maximum supported canvas size in + * total pixels, i.e. width * height. Use `-1` for no limit, or `0` for + * CSS-only zooming. The default value is 4096 * 8192 (32 mega-pixels). * @property {number} [maxCanvasDim] - The maximum supported canvas dimension, * in either width or height. Use `-1` for no limit. * The default value is 32767. @@ -63,6 +66,7 @@ class PDFThumbnailViewer { eventBus, linkService, renderingQueue, + maxCanvasPixels, maxCanvasDim, pageColors, abortSignal, @@ -72,6 +76,7 @@ class PDFThumbnailViewer { this.eventBus = eventBus; this.linkService = linkService; this.renderingQueue = renderingQueue; + this.maxCanvasPixels = maxCanvasPixels; this.maxCanvasDim = maxCanvasDim; this.pageColors = pageColors || null; this.enableHWA = enableHWA || false; @@ -214,6 +219,7 @@ class PDFThumbnailViewer { optionalContentConfigPromise, linkService: this.linkService, renderingQueue: this.renderingQueue, + maxCanvasPixels: this.maxCanvasPixels, maxCanvasDim: this.maxCanvasDim, pageColors: this.pageColors, enableHWA: this.enableHWA,