mirror of
https://github.com/zen-browser/pdf.js.git
synced 2025-07-08 09:20:06 +02:00
Merge pull request #18218 from nicolo-ribaudo/test-maxCanvasPixels
Respect `maxCanvasPixels` when computing canvas dimensions
This commit is contained in:
commit
3e1d779859
4 changed files with 187 additions and 62 deletions
|
@ -38,8 +38,13 @@ function loadAndWait(filename, selector, zoom, pageSetup, options) {
|
||||||
|
|
||||||
let app_options = "";
|
let app_options = "";
|
||||||
if (options) {
|
if (options) {
|
||||||
|
const optionsObject =
|
||||||
|
typeof options === "function"
|
||||||
|
? await options(page, session.name)
|
||||||
|
: options;
|
||||||
|
|
||||||
// Options must be handled in app.js::_parseHashParams.
|
// Options must be handled in app.js::_parseHashParams.
|
||||||
for (const [key, value] of Object.entries(options)) {
|
for (const [key, value] of Object.entries(optionsObject)) {
|
||||||
app_options += `&${key}=${encodeURIComponent(value)}`;
|
app_options += `&${key}=${encodeURIComponent(value)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,24 +174,6 @@ describe("PDF viewer", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("CSS-only zoom", () => {
|
describe("CSS-only zoom", () => {
|
||||||
let pages;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
pages = await loadAndWait(
|
|
||||||
"tracemonkey.pdf",
|
|
||||||
".textLayer .endOfContent",
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
maxCanvasPixels: 0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await closePages(pages);
|
|
||||||
});
|
|
||||||
|
|
||||||
function createPromiseForFirstPageRendered(page) {
|
function createPromiseForFirstPageRendered(page) {
|
||||||
return createPromise(page, (resolve, reject) => {
|
return createPromise(page, (resolve, reject) => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
@ -209,50 +191,184 @@ describe("PDF viewer", () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
it("respects drawing delay when zooming out", async () => {
|
describe("forced (maxCanvasPixels: 0)", () => {
|
||||||
await Promise.all(
|
let pages;
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
const promise = await createPromiseForFirstPageRendered(page);
|
|
||||||
|
|
||||||
const start = await page.evaluate(() => {
|
beforeAll(async () => {
|
||||||
const startTime = performance.now();
|
pages = await loadAndWait(
|
||||||
window.PDFViewerApplication.pdfViewer.decreaseScale({
|
"tracemonkey.pdf",
|
||||||
drawingDelay: 100,
|
".textLayer .endOfContent",
|
||||||
scaleFactor: 0.9,
|
null,
|
||||||
|
null,
|
||||||
|
{ maxCanvasPixels: 0 }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("respects drawing delay when zooming out", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
const promise = await createPromiseForFirstPageRendered(page);
|
||||||
|
|
||||||
|
const start = await page.evaluate(() => {
|
||||||
|
const startTime = performance.now();
|
||||||
|
window.PDFViewerApplication.pdfViewer.decreaseScale({
|
||||||
|
drawingDelay: 100,
|
||||||
|
scaleFactor: 0.9,
|
||||||
|
});
|
||||||
|
return startTime;
|
||||||
});
|
});
|
||||||
return startTime;
|
|
||||||
});
|
|
||||||
|
|
||||||
const end = await awaitPromise(promise);
|
const end = await awaitPromise(promise);
|
||||||
|
|
||||||
expect(end - start)
|
expect(end - start)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toBeGreaterThanOrEqual(100);
|
.toBeGreaterThanOrEqual(100);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("respects drawing delay when zooming in", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
const promise = await createPromiseForFirstPageRendered(page);
|
||||||
|
|
||||||
|
const start = await page.evaluate(() => {
|
||||||
|
const startTime = performance.now();
|
||||||
|
window.PDFViewerApplication.pdfViewer.increaseScale({
|
||||||
|
drawingDelay: 100,
|
||||||
|
scaleFactor: 1.1,
|
||||||
|
});
|
||||||
|
return startTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
const end = await awaitPromise(promise);
|
||||||
|
|
||||||
|
expect(end - start)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeGreaterThanOrEqual(100);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("respects drawing delay when zooming in", async () => {
|
describe("triggers when going bigger than maxCanvasPixels", () => {
|
||||||
await Promise.all(
|
let pages;
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
const promise = await createPromiseForFirstPageRendered(page);
|
|
||||||
|
|
||||||
const start = await page.evaluate(() => {
|
const MAX_CANVAS_PIXELS = new Map();
|
||||||
const startTime = performance.now();
|
|
||||||
window.PDFViewerApplication.pdfViewer.increaseScale({
|
beforeAll(async () => {
|
||||||
drawingDelay: 100,
|
pages = await loadAndWait(
|
||||||
scaleFactor: 1.1,
|
"tracemonkey.pdf",
|
||||||
|
".textLayer .endOfContent",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
async (page, browserName) => {
|
||||||
|
const ratio = await page.evaluate(() => window.devicePixelRatio);
|
||||||
|
const maxCanvasPixels = 1_000_000 * ratio ** 2;
|
||||||
|
MAX_CANVAS_PIXELS.set(browserName, maxCanvasPixels);
|
||||||
|
|
||||||
|
return { maxCanvasPixels };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.PDFViewerApplication.pdfViewer.currentScale = 0.5;
|
||||||
});
|
});
|
||||||
return startTime;
|
})
|
||||||
});
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const end = await awaitPromise(promise);
|
afterAll(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
expect(end - start)
|
function getCanvasSize(page) {
|
||||||
.withContext(`In ${browserName}`)
|
return page.evaluate(() => {
|
||||||
.toBeGreaterThanOrEqual(100);
|
const canvas = window.document.querySelector(".canvasWrapper canvas");
|
||||||
})
|
return canvas.width * canvas.height;
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAX_CANVAS_PIXELS must be big enough that the originally rendered
|
||||||
|
// canvas still has enough space to grow before triggering CSS-only zoom
|
||||||
|
it("test correctly configured", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
const canvasSize = await getCanvasSize(page);
|
||||||
|
|
||||||
|
expect(canvasSize)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeLessThan(MAX_CANVAS_PIXELS.get(browserName) / 4);
|
||||||
|
expect(canvasSize)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeGreaterThan(MAX_CANVAS_PIXELS.get(browserName) / 16);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not trigger CSS-only zoom below maxCanvasPixels", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
const originalCanvasSize = await getCanvasSize(page);
|
||||||
|
const factor = 2;
|
||||||
|
|
||||||
|
await page.evaluate(scaleFactor => {
|
||||||
|
window.PDFViewerApplication.pdfViewer.increaseScale({
|
||||||
|
drawingDelay: 0,
|
||||||
|
scaleFactor,
|
||||||
|
});
|
||||||
|
}, factor);
|
||||||
|
|
||||||
|
const canvasSize = await getCanvasSize(page);
|
||||||
|
|
||||||
|
expect(canvasSize)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBe(originalCanvasSize * factor ** 2);
|
||||||
|
|
||||||
|
expect(canvasSize)
|
||||||
|
.withContext(`In ${browserName}, MAX_CANVAS_PIXELS`)
|
||||||
|
.toBeLessThan(MAX_CANVAS_PIXELS.get(browserName));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("triggers CSS-only zoom above maxCanvasPixels", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
const originalCanvasSize = await getCanvasSize(page);
|
||||||
|
const factor = 4;
|
||||||
|
|
||||||
|
await page.evaluate(scaleFactor => {
|
||||||
|
window.PDFViewerApplication.pdfViewer.increaseScale({
|
||||||
|
drawingDelay: 0,
|
||||||
|
scaleFactor,
|
||||||
|
});
|
||||||
|
}, factor);
|
||||||
|
|
||||||
|
const canvasSize = await getCanvasSize(page);
|
||||||
|
|
||||||
|
expect(canvasSize)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeLessThan(originalCanvasSize * factor ** 2);
|
||||||
|
|
||||||
|
expect(canvasSize)
|
||||||
|
.withContext(`In ${browserName}, <= MAX_CANVAS_PIXELS`)
|
||||||
|
.toBeLessThanOrEqual(MAX_CANVAS_PIXELS.get(browserName));
|
||||||
|
|
||||||
|
expect(canvasSize)
|
||||||
|
.withContext(`In ${browserName}, > MAX_CANVAS_PIXELS * 0.99`)
|
||||||
|
.toBeGreaterThan(MAX_CANVAS_PIXELS.get(browserName) * 0.99);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,9 +34,9 @@ import {
|
||||||
import {
|
import {
|
||||||
approximateFraction,
|
approximateFraction,
|
||||||
DEFAULT_SCALE,
|
DEFAULT_SCALE,
|
||||||
|
floorToDivide,
|
||||||
OutputScale,
|
OutputScale,
|
||||||
RenderingStates,
|
RenderingStates,
|
||||||
roundToDivide,
|
|
||||||
TextLayerMode,
|
TextLayerMode,
|
||||||
} from "./ui_utils.js";
|
} from "./ui_utils.js";
|
||||||
import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
|
import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
|
||||||
|
@ -1016,11 +1016,11 @@ class PDFPageView {
|
||||||
const sfx = approximateFraction(outputScale.sx);
|
const sfx = approximateFraction(outputScale.sx);
|
||||||
const sfy = approximateFraction(outputScale.sy);
|
const sfy = approximateFraction(outputScale.sy);
|
||||||
|
|
||||||
canvas.width = roundToDivide(width * outputScale.sx, sfx[0]);
|
canvas.width = floorToDivide(width * outputScale.sx, sfx[0]);
|
||||||
canvas.height = roundToDivide(height * outputScale.sy, sfy[0]);
|
canvas.height = floorToDivide(height * outputScale.sy, sfy[0]);
|
||||||
const { style } = canvas;
|
const { style } = canvas;
|
||||||
style.width = roundToDivide(width, sfx[1]) + "px";
|
style.width = floorToDivide(width, sfx[1]) + "px";
|
||||||
style.height = roundToDivide(height, sfy[1]) + "px";
|
style.height = floorToDivide(height, sfy[1]) + "px";
|
||||||
|
|
||||||
// Add the viewport so it's known what it was originally drawn with.
|
// Add the viewport so it's known what it was originally drawn with.
|
||||||
this.#viewportMap.set(canvas, viewport);
|
this.#viewportMap.set(canvas, viewport);
|
||||||
|
|
|
@ -263,6 +263,7 @@ function binarySearchFirstItem(items, condition, start = 0) {
|
||||||
* @param {number} x - Positive float number.
|
* @param {number} x - Positive float number.
|
||||||
* @returns {Array} Estimated fraction: the first array item is a numerator,
|
* @returns {Array} Estimated fraction: the first array item is a numerator,
|
||||||
* the second one is a denominator.
|
* the second one is a denominator.
|
||||||
|
* They are both natural numbers.
|
||||||
*/
|
*/
|
||||||
function approximateFraction(x) {
|
function approximateFraction(x) {
|
||||||
// Fast paths for int numbers or their inversions.
|
// Fast paths for int numbers or their inversions.
|
||||||
|
@ -309,9 +310,12 @@ function approximateFraction(x) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function roundToDivide(x, div) {
|
/**
|
||||||
const r = x % div;
|
* @param {number} x - A positive number to round to a multiple of `div`.
|
||||||
return r === 0 ? x : Math.round(x - r + div);
|
* @param {number} div - A natural number.
|
||||||
|
*/
|
||||||
|
function floorToDivide(x, div) {
|
||||||
|
return x - (x % div);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -866,6 +870,7 @@ export {
|
||||||
DEFAULT_SCALE_DELTA,
|
DEFAULT_SCALE_DELTA,
|
||||||
DEFAULT_SCALE_VALUE,
|
DEFAULT_SCALE_VALUE,
|
||||||
docStyle,
|
docStyle,
|
||||||
|
floorToDivide,
|
||||||
getActiveOrFocusedElement,
|
getActiveOrFocusedElement,
|
||||||
getPageSizeInches,
|
getPageSizeInches,
|
||||||
getVisibleElements,
|
getVisibleElements,
|
||||||
|
@ -884,7 +889,6 @@ export {
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
removeNullCharacters,
|
removeNullCharacters,
|
||||||
RenderingStates,
|
RenderingStates,
|
||||||
roundToDivide,
|
|
||||||
SCROLLBAR_PADDING,
|
SCROLLBAR_PADDING,
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
ScrollMode,
|
ScrollMode,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue