Merge pull request #19635 from Snuffleupagus/thumbnails-maxCanvasPixels

Support the `maxCanvasPixels` option in the thumbnails code
This commit is contained in:
Jonas Jenwald 2025-03-10 16:30:25 +01:00 committed by GitHub
commit 4152eae3fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 79 additions and 50 deletions

View File

@ -640,9 +640,39 @@ class OutputScale {
return this.sx !== 1 || this.sy !== 1; return this.sx !== 1 || this.sy !== 1;
} }
/**
* @type {boolean} Returns `true` when scaling is symmetric,
* `false` otherwise.
*/
get symmetric() { get symmetric() {
return this.sx === this.sy; 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 // See https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types

View File

@ -478,6 +478,7 @@ const PDFViewerApplication = {
: null; : null;
const enableHWA = AppOptions.get("enableHWA"), const enableHWA = AppOptions.get("enableHWA"),
maxCanvasPixels = AppOptions.get("maxCanvasPixels"),
maxCanvasDim = AppOptions.get("maxCanvasDim"); maxCanvasDim = AppOptions.get("maxCanvasDim");
const pdfViewer = new PDFViewer({ const pdfViewer = new PDFViewer({
container, container,
@ -506,7 +507,7 @@ const PDFViewerApplication = {
), ),
imageResourcesPath: AppOptions.get("imageResourcesPath"), imageResourcesPath: AppOptions.get("imageResourcesPath"),
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
maxCanvasPixels: AppOptions.get("maxCanvasPixels"), maxCanvasPixels,
maxCanvasDim, maxCanvasDim,
enableDetailCanvas: AppOptions.get("enableDetailCanvas"), enableDetailCanvas: AppOptions.get("enableDetailCanvas"),
enablePermissions: AppOptions.get("enablePermissions"), enablePermissions: AppOptions.get("enablePermissions"),
@ -529,6 +530,7 @@ const PDFViewerApplication = {
eventBus, eventBus,
renderingQueue: pdfRenderingQueue, renderingQueue: pdfRenderingQueue,
linkService: pdfLinkService, linkService: pdfLinkService,
maxCanvasPixels,
maxCanvasDim, maxCanvasDim,
pageColors, pageColors,
abortSignal, abortSignal,

View File

@ -775,28 +775,13 @@ class PDFPageView extends BasePDFPageView {
outputScale.sx *= invScale; outputScale.sx *= invScale;
outputScale.sy *= invScale; outputScale.sy *= invScale;
this.#needsRestrictedScaling = true; this.#needsRestrictedScaling = true;
} else if (this.maxCanvasPixels > 0 || this.maxCanvasDim !== -1) { } else {
let maxAreaScale = Infinity, this.#needsRestrictedScaling = outputScale.limitCanvas(
maxWidthScale = Infinity, width,
maxHeightScale = Infinity; height,
this.maxCanvasPixels,
if (this.maxCanvasPixels > 0) { this.maxCanvasDim
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;
}
} }
} }

View File

@ -42,6 +42,9 @@ const THUMBNAIL_WIDTH = 98; // px
* The default value is `null`. * The default value is `null`.
* @property {IPDFLinkService} linkService - The navigation/linking service. * @property {IPDFLinkService} linkService - The navigation/linking service.
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object. * @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, * @property {number} [maxCanvasDim] - The maximum supported canvas dimension,
* in either width or height. Use `-1` for no limit. * in either width or height. Use `-1` for no limit.
* The default value is 32767. * The default value is 32767.
@ -97,6 +100,7 @@ class PDFThumbnailView {
optionalContentConfigPromise, optionalContentConfigPromise,
linkService, linkService,
renderingQueue, renderingQueue,
maxCanvasPixels,
maxCanvasDim, maxCanvasDim,
pageColors, pageColors,
enableHWA, enableHWA,
@ -110,6 +114,7 @@ class PDFThumbnailView {
this.viewport = defaultViewport; this.viewport = defaultViewport;
this.pdfPageRotate = defaultViewport.rotation; this.pdfPageRotate = defaultViewport.rotation;
this._optionalContentConfigPromise = optionalContentConfigPromise || null; this._optionalContentConfigPromise = optionalContentConfigPromise || null;
this.maxCanvasPixels = maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
this.maxCanvasDim = maxCanvasDim || AppOptions.get("maxCanvasDim"); this.maxCanvasDim = maxCanvasDim || AppOptions.get("maxCanvasDim");
this.pageColors = pageColors || null; this.pageColors = pageColors || null;
this.enableHWA = enableHWA || false; this.enableHWA = enableHWA || false;
@ -218,16 +223,12 @@ class PDFThumbnailView {
const width = upscaleFactor * this.canvasWidth, const width = upscaleFactor * this.canvasWidth,
height = upscaleFactor * this.canvasHeight; height = upscaleFactor * this.canvasHeight;
if (this.maxCanvasDim !== -1) { outputScale.limitCanvas(
const maxScale = Math.min( width,
this.maxCanvasDim / width, height,
this.maxCanvasDim / height this.maxCanvasPixels,
); this.maxCanvasDim
if (outputScale.sx > maxScale || outputScale.sy > maxScale) { );
outputScale.sx = maxScale;
outputScale.sy = maxScale;
}
}
canvas.width = (width * outputScale.sx) | 0; canvas.width = (width * outputScale.sx) | 0;
canvas.height = (height * outputScale.sy) | 0; canvas.height = (height * outputScale.sy) | 0;
@ -364,6 +365,27 @@ class PDFThumbnailView {
this.#convertCanvasToImage(canvas); 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) { #reduceImage(img) {
const { ctx, canvas } = this.#getPageDrawContext(1, true); const { ctx, canvas } = this.#getPageDrawContext(1, true);
@ -381,24 +403,8 @@ class PDFThumbnailView {
); );
return canvas; return canvas;
} }
const { maxCanvasDim } = this;
// drawImage does an awful job of rescaling the image, doing it gradually. // drawImage does an awful job of rescaling the image, doing it gradually.
let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS; let [reducedWidth, reducedHeight] = this.#getReducedImageDims(canvas);
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;
}
}
const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas( const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas(
reducedWidth, reducedWidth,
reducedHeight reducedHeight

View File

@ -39,6 +39,9 @@ const THUMBNAIL_SELECTED_CLASS = "selected";
* @property {EventBus} eventBus - The application event bus. * @property {EventBus} eventBus - The application event bus.
* @property {IPDFLinkService} linkService - The navigation/linking service. * @property {IPDFLinkService} linkService - The navigation/linking service.
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object. * @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, * @property {number} [maxCanvasDim] - The maximum supported canvas dimension,
* in either width or height. Use `-1` for no limit. * in either width or height. Use `-1` for no limit.
* The default value is 32767. * The default value is 32767.
@ -63,6 +66,7 @@ class PDFThumbnailViewer {
eventBus, eventBus,
linkService, linkService,
renderingQueue, renderingQueue,
maxCanvasPixels,
maxCanvasDim, maxCanvasDim,
pageColors, pageColors,
abortSignal, abortSignal,
@ -72,6 +76,7 @@ class PDFThumbnailViewer {
this.eventBus = eventBus; this.eventBus = eventBus;
this.linkService = linkService; this.linkService = linkService;
this.renderingQueue = renderingQueue; this.renderingQueue = renderingQueue;
this.maxCanvasPixels = maxCanvasPixels;
this.maxCanvasDim = maxCanvasDim; this.maxCanvasDim = maxCanvasDim;
this.pageColors = pageColors || null; this.pageColors = pageColors || null;
this.enableHWA = enableHWA || false; this.enableHWA = enableHWA || false;
@ -214,6 +219,7 @@ class PDFThumbnailViewer {
optionalContentConfigPromise, optionalContentConfigPromise,
linkService: this.linkService, linkService: this.linkService,
renderingQueue: this.renderingQueue, renderingQueue: this.renderingQueue,
maxCanvasPixels: this.maxCanvasPixels,
maxCanvasDim: this.maxCanvasDim, maxCanvasDim: this.maxCanvasDim,
pageColors: this.pageColors, pageColors: this.pageColors,
enableHWA: this.enableHWA, enableHWA: this.enableHWA,