Skip to content

Commit 1225c1e

Browse files
committed
Add a pref in order to cap the canvas area to a factor of the window one (bug 1958015)
This way it helps to reduce the overall canvas dimensions and make the rendering faster. The drawback is that when scrolling, the page can be blurry in waiting for the rendering. The default value is 200% on desktop and will be 100% for GeckoView.
1 parent 6f05231 commit 1225c1e

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)