Skip to content

Commit a806f00

Browse files
authored
Merge pull request #19755 from calixteman/reduce_canvas_size
Add a pref in order to cap the canvas area to a factor of the window one (bug 1958015)
2 parents 69595a2 + 1225c1e commit a806f00

File tree

8 files changed

+138
-17
lines changed

8 files changed

+138
-17
lines changed

extensions/chromium/preferences_schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@
172172
"enum": [-1, 0, 3, 15],
173173
"default": 0
174174
},
175+
"capCanvasAreaFactor": {
176+
"type": "integer",
177+
"default": 200
178+
},
175179
"enablePermissions": {
176180
"type": "boolean",
177181
"default": false

src/display/display_utils.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,11 +660,18 @@ class OutputScale {
660660
* @returns {boolean} Returns `true` if scaling was limited,
661661
* `false` otherwise.
662662
*/
663-
limitCanvas(width, height, maxPixels, maxDim) {
663+
limitCanvas(width, height, maxPixels, maxDim, capAreaFactor = -1) {
664664
let maxAreaScale = Infinity,
665665
maxWidthScale = Infinity,
666666
maxHeightScale = Infinity;
667667

668+
if (capAreaFactor >= 0) {
669+
const cappedWindowArea = OutputScale.getCappedWindowArea(capAreaFactor);
670+
maxPixels =
671+
maxPixels > 0
672+
? Math.min(maxPixels, cappedWindowArea)
673+
: cappedWindowArea;
674+
}
668675
if (maxPixels > 0) {
669676
maxAreaScale = Math.sqrt(maxPixels / (width * height));
670677
}
@@ -685,6 +692,23 @@ class OutputScale {
685692
static get pixelRatio() {
686693
return globalThis.devicePixelRatio || 1;
687694
}
695+
696+
static getCappedWindowArea(capAreaFactor) {
697+
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
698+
return Math.ceil(
699+
window.innerWidth *
700+
window.innerHeight *
701+
this.pixelRatio ** 2 *
702+
(1 + capAreaFactor / 100)
703+
);
704+
}
705+
return Math.ceil(
706+
window.screen.availWidth *
707+
window.screen.availHeight *
708+
this.pixelRatio ** 2 *
709+
(1 + capAreaFactor / 100)
710+
);
711+
}
688712
}
689713

690714
// See https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types

test/integration/viewer_spec.mjs

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,9 @@ describe("PDF viewer", () => {
389389
pages = await loadAndWait(
390390
"issue18694.pdf",
391391
".textLayer .endOfContent",
392-
"page-width"
392+
"page-width",
393+
null,
394+
{ capCanvasAreaFactor: -1 }
393395
);
394396
});
395397

@@ -459,7 +461,12 @@ describe("PDF viewer", () => {
459461
describe("Detail view on zoom", () => {
460462
const BASE_MAX_CANVAS_PIXELS = 1e6;
461463

462-
function setupPages(zoom, devicePixelRatio, setups = {}) {
464+
function setupPages(
465+
zoom,
466+
devicePixelRatio,
467+
capCanvasAreaFactor,
468+
setups = {}
469+
) {
463470
let pages;
464471

465472
beforeEach(async () => {
@@ -476,7 +483,10 @@ describe("PDF viewer", () => {
476483
}`,
477484
...setups,
478485
},
479-
{ maxCanvasPixels: BASE_MAX_CANVAS_PIXELS * devicePixelRatio ** 2 },
486+
{
487+
maxCanvasPixels: BASE_MAX_CANVAS_PIXELS * devicePixelRatio ** 2,
488+
capCanvasAreaFactor,
489+
},
480490
{ height: 600, width: 800, devicePixelRatio }
481491
);
482492
});
@@ -503,6 +513,8 @@ describe("PDF viewer", () => {
503513
const bottomRight = ctx.getImageData(width - 3, height - 3, 1, 1).data;
504514
return {
505515
size: width * height,
516+
width,
517+
height,
506518
topLeft: globalThis.pdfjsLib.Util.makeHexColor(...topLeft),
507519
bottomRight: globalThis.pdfjsLib.Util.makeHexColor(...bottomRight),
508520
};
@@ -528,7 +540,7 @@ describe("PDF viewer", () => {
528540
for (const pixelRatio of [1, 2]) {
529541
describe(`with pixel ratio ${pixelRatio}`, () => {
530542
describe("setupPages()", () => {
531-
const forEachPage = setupPages("100%", pixelRatio);
543+
const forEachPage = setupPages("100%", pixelRatio, -1);
532544

533545
it("sets the proper devicePixelRatio", async () => {
534546
await forEachPage(async (browserName, page) => {
@@ -543,8 +555,63 @@ describe("PDF viewer", () => {
543555
});
544556
});
545557

558+
describe("when zooming with a cap on the canvas dimensions", () => {
559+
const forEachPage = setupPages("10%", pixelRatio, 0);
560+
561+
it("must render the detail view", async () => {
562+
await forEachPage(async (browserName, page) => {
563+
await page.waitForSelector(
564+
".page[data-page-number='1'] .textLayer"
565+
);
566+
567+
const before = await page.evaluate(extractCanvases, 1);
568+
expect(before.length)
569+
.withContext(`In ${browserName}, before`)
570+
.toBe(1);
571+
572+
const factor = 50;
573+
const handle = await waitForDetailRendered(page);
574+
await page.evaluate(scaleFactor => {
575+
window.PDFViewerApplication.pdfViewer.updateScale({
576+
drawingDelay: 0,
577+
scaleFactor,
578+
});
579+
}, factor);
580+
await awaitPromise(handle);
581+
582+
const after = await page.evaluate(extractCanvases, 1);
583+
// The page dimensions are 595x841, so the base canvas is a scale
584+
// version of that but the number of pixels is capped to
585+
// 800x600 = 480000.
586+
expect(after.length)
587+
.withContext(`In ${browserName}, after`)
588+
.toBe(2);
589+
expect(after[0].width)
590+
.withContext(`In ${browserName}`)
591+
.toBe(582 * pixelRatio);
592+
expect(after[0].height)
593+
.withContext(`In ${browserName}`)
594+
.toBe(823 * pixelRatio);
595+
596+
// The dimensions of the detail canvas are capped to 800x600 but
597+
// it depends on the visible area which depends itself of the
598+
// scrollbars dimensions, hence we just check that the canvas
599+
// dimensions are capped.
600+
expect(after[1].width)
601+
.withContext(`In ${browserName}`)
602+
.toBeLessThan(810 * pixelRatio);
603+
expect(after[1].height)
604+
.withContext(`In ${browserName}`)
605+
.toBeLessThan(575 * pixelRatio);
606+
expect(after[1].size)
607+
.withContext(`In ${browserName}`)
608+
.toBeLessThan(800 * 600 * pixelRatio ** 2);
609+
});
610+
});
611+
});
612+
546613
describe("when zooming in past max canvas size", () => {
547-
const forEachPage = setupPages("100%", pixelRatio);
614+
const forEachPage = setupPages("100%", pixelRatio, -1);
548615

549616
it("must render the detail view", async () => {
550617
await forEachPage(async (browserName, page) => {
@@ -616,7 +683,7 @@ describe("PDF viewer", () => {
616683
});
617684

618685
describe("when starting already zoomed in past max canvas size", () => {
619-
const forEachPage = setupPages("300%", pixelRatio);
686+
const forEachPage = setupPages("300%", pixelRatio, -1);
620687

621688
it("must render the detail view", async () => {
622689
await forEachPage(async (browserName, page) => {
@@ -654,7 +721,7 @@ describe("PDF viewer", () => {
654721
});
655722

656723
describe("when scrolling", () => {
657-
const forEachPage = setupPages("300%", pixelRatio);
724+
const forEachPage = setupPages("300%", pixelRatio, -1);
658725

659726
it("must update the detail view", async () => {
660727
await forEachPage(async (browserName, page) => {
@@ -689,7 +756,7 @@ describe("PDF viewer", () => {
689756
});
690757

691758
describe("when scrolling little enough that the existing detail covers the new viewport", () => {
692-
const forEachPage = setupPages("300%", pixelRatio);
759+
const forEachPage = setupPages("300%", pixelRatio, -1);
693760

694761
it("must not re-create the detail canvas", async () => {
695762
await forEachPage(async (browserName, page) => {
@@ -732,7 +799,7 @@ describe("PDF viewer", () => {
732799
});
733800

734801
describe("when scrolling to have two visible pages", () => {
735-
const forEachPage = setupPages("300%", pixelRatio);
802+
const forEachPage = setupPages("300%", pixelRatio, -1);
736803

737804
it("must update the detail view", async () => {
738805
await forEachPage(async (browserName, page) => {
@@ -805,7 +872,7 @@ describe("PDF viewer", () => {
805872
});
806873

807874
describe("pagerendered event", () => {
808-
const forEachPage = setupPages("100%", pixelRatio, {
875+
const forEachPage = setupPages("100%", pixelRatio, -1, {
809876
eventBusSetup: eventBus => {
810877
globalThis.__pageRenderedEvents = [];
811878

@@ -966,7 +1033,7 @@ describe("PDF viewer", () => {
9661033
}
9671034

9681035
describe("when immediately cancelled and re-rendered", () => {
969-
const forEachPage = setupPages("100%", 1, {
1036+
const forEachPage = setupPages("100%", 1, -1, {
9701037
eventBusSetup: eventBus => {
9711038
globalThis.__pageRenderedEvents = [];
9721039
eventBus.on("pagerendered", ({ pageNumber, isDetailView }) => {
@@ -1031,7 +1098,7 @@ describe("PDF viewer", () => {
10311098
});
10321099

10331100
describe("when cancelled and re-rendered after 1 microtick", () => {
1034-
const forEachPage = setupPages("100%", 1, {
1101+
const forEachPage = setupPages("100%", 1, -1, {
10351102
eventBusSetup: eventBus => {
10361103
globalThis.__pageRenderedEvents = [];
10371104
eventBus.on("pagerendered", ({ pageNumber, isDetailView }) => {

web/app.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ const PDFViewerApplication = {
361361
// Set some specific preferences for tests.
362362
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
363363
Object.assign(opts, {
364+
capCanvasAreaFactor: x => parseInt(x),
364365
docBaseUrl: x => x,
365366
enableAltText: x => x === "true",
366367
enableAutoLinking: x => x === "true",
@@ -485,7 +486,8 @@ const PDFViewerApplication = {
485486

486487
const enableHWA = AppOptions.get("enableHWA"),
487488
maxCanvasPixels = AppOptions.get("maxCanvasPixels"),
488-
maxCanvasDim = AppOptions.get("maxCanvasDim");
489+
maxCanvasDim = AppOptions.get("maxCanvasDim"),
490+
capCanvasAreaFactor = AppOptions.get("capCanvasAreaFactor");
489491
const pdfViewer = (this.pdfViewer = new PDFViewer({
490492
container,
491493
viewer,
@@ -515,6 +517,7 @@ const PDFViewerApplication = {
515517
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
516518
maxCanvasPixels,
517519
maxCanvasDim,
520+
capCanvasAreaFactor,
518521
enableDetailCanvas: AppOptions.get("enableDetailCanvas"),
519522
enablePermissions: AppOptions.get("enablePermissions"),
520523
pageColors,

web/app_options.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,11 @@ const defaultOptions = {
168168
value: 2,
169169
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
170170
},
171+
capCanvasAreaFactor: {
172+
/** @type {number} */
173+
value: 200,
174+
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
175+
},
171176
cursorToolOnLoad: {
172177
/** @type {number} */
173178
value: 0,

web/pdf_page_detail_view.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,18 @@ class PDFPageDetailView extends BasePDFPageView {
140140
return;
141141
}
142142

143-
const { viewport, maxCanvasPixels } = this.pageView;
143+
const { viewport, capCanvasAreaFactor } = this.pageView;
144144

145145
const visibleWidth = visibleArea.maxX - visibleArea.minX;
146146
const visibleHeight = visibleArea.maxY - visibleArea.minY;
147+
let { maxCanvasPixels } = this.pageView;
148+
149+
if (capCanvasAreaFactor >= 0) {
150+
maxCanvasPixels = Math.min(
151+
maxCanvasPixels,
152+
OutputScale.getCappedWindowArea(capCanvasAreaFactor)
153+
);
154+
}
147155

148156
// "overflowScale" represents which percentage of the width and of the
149157
// height the detail area extends outside of the visible area. We want to

web/pdf_page_view.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
8181
* @property {number} [maxCanvasDim] - The maximum supported canvas dimension,
8282
* in either width or height. Use `-1` for no limit.
8383
* The default value is 32767.
84+
* @property {number} [capCanvasAreaFactor] - Cap the canvas area to the
85+
* viewport increased by the value in percent. Use `-1` for no limit.
86+
* The default value is 200%.
8487
* @property {boolean} [enableDetailCanvas] - When enabled, if the rendered
8588
* pages would need a canvas that is larger than `maxCanvasPixels` or
8689
* `maxCanvasDim`, it will draw a second canvas on top of the CSS-zoomed one,
@@ -188,6 +191,8 @@ class PDFPageView extends BasePDFPageView {
188191
this.maxCanvasPixels =
189192
options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
190193
this.maxCanvasDim = options.maxCanvasDim || AppOptions.get("maxCanvasDim");
194+
this.capCanvasAreaFactor =
195+
options.capCanvasAreaFactor ?? AppOptions.get("capCanvasAreaFactor");
191196
this.#enableAutoLinking = options.enableAutoLinking !== false;
192197

193198
this.l10n = options.l10n;
@@ -448,7 +453,6 @@ class PDFPageView extends BasePDFPageView {
448453
if (!this.textLayer) {
449454
return;
450455
}
451-
452456
let error = null;
453457
try {
454458
await this.textLayer.render({
@@ -780,7 +784,8 @@ class PDFPageView extends BasePDFPageView {
780784
width,
781785
height,
782786
this.maxCanvasPixels,
783-
this.maxCanvasDim
787+
this.maxCanvasDim,
788+
this.capCanvasAreaFactor
784789
);
785790
}
786791
}

web/pdf_viewer.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ function isValidAnnotationEditorMode(mode) {
122122
* @property {number} [maxCanvasDim] - The maximum supported canvas dimension,
123123
* in either width or height. Use `-1` for no limit.
124124
* The default value is 32767.
125+
* @property {number} [capCanvasAreaFactor] - Cap the canvas area to the
126+
* viewport increased by the value in percent. Use `-1` for no limit.
127+
* The default value is 200%.
125128
* @property {boolean} [enableDetailCanvas] - When enabled, if the rendered
126129
* pages would need a canvas that is larger than `maxCanvasPixels` or
127130
* `maxCanvasDim`, it will draw a second canvas on top of the CSS-zoomed one,
@@ -335,6 +338,7 @@ class PDFViewer {
335338
}
336339
this.maxCanvasPixels = options.maxCanvasPixels;
337340
this.maxCanvasDim = options.maxCanvasDim;
341+
this.capCanvasAreaFactor = options.capCanvasAreaFactor;
338342
this.enableDetailCanvas = options.enableDetailCanvas ?? true;
339343
this.l10n = options.l10n;
340344
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
@@ -1002,6 +1006,7 @@ class PDFViewer {
10021006
imageResourcesPath: this.imageResourcesPath,
10031007
maxCanvasPixels: this.maxCanvasPixels,
10041008
maxCanvasDim: this.maxCanvasDim,
1009+
capCanvasAreaFactor: this.capCanvasAreaFactor,
10051010
enableDetailCanvas: this.enableDetailCanvas,
10061011
pageColors,
10071012
l10n: this.l10n,

0 commit comments

Comments
 (0)