mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-02 04:17:24 +02:00
Use the client coordinates for the pinch-zoom origin
`TouchManager` computes the pinch distances with the screen coordinates, but it was passing the pinch center in that space too, while the viewer expects a client coordinate, like the origin coming from a wheel event. Since a two-finger gesture can be synthesized with WebDriver BiDi, the pinch-zoom test doesn't have to rely on CDP anymore and can run in Chrome, where it fails without the above fix.
This commit is contained in:
parent
61acf93172
commit
8960eddfc1
@ -189,7 +189,7 @@ class TouchManager {
|
|||||||
const pDistance = Math.hypot(prevGapX, prevGapY) || 1;
|
const pDistance = Math.hypot(prevGapX, prevGapY) || 1;
|
||||||
if (
|
if (
|
||||||
!this.#isPinching &&
|
!this.#isPinching &&
|
||||||
Math.abs(pDistance - distance) <= TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH
|
Math.abs(pDistance - distance) <= this.MIN_TOUCH_DISTANCE_TO_PINCH
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -207,7 +207,12 @@ class TouchManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const origin = [(screen0X + screen1X) / 2, (screen0Y + screen1Y) / 2];
|
// The distances are in screen CSS pixels, but the origin must be in client
|
||||||
|
// coordinates, like the one coming from a wheel event.
|
||||||
|
const origin = [
|
||||||
|
(touch0.clientX + touch1.clientX) / 2,
|
||||||
|
(touch0.clientY + touch1.clientY) / 2,
|
||||||
|
];
|
||||||
this.#onPinching?.(origin, pDistance, distance);
|
this.#onPinching?.(origin, pDistance, distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import {
|
|||||||
kbUndo,
|
kbUndo,
|
||||||
loadAndWait,
|
loadAndWait,
|
||||||
moveEditor,
|
moveEditor,
|
||||||
|
pinch,
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
selectEditor,
|
selectEditor,
|
||||||
selectEditors,
|
selectEditors,
|
||||||
@ -1419,30 +1420,9 @@ describe("Pinch to resize a drawing", () => {
|
|||||||
await closePages(pages);
|
await closePages(pages);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Spread two fingers apart, centered on the editor.
|
|
||||||
async function pinchOut(page, selector) {
|
|
||||||
const { x, y, width, height } = await getRect(page, selector);
|
|
||||||
const centerX = x + width / 2;
|
|
||||||
const centerY = y + height / 2;
|
|
||||||
const finger0 = await page.touchscreen.touchStart(centerX - 25, centerY);
|
|
||||||
const finger1 = await page.touchscreen.touchStart(centerX + 25, centerY);
|
|
||||||
for (let i = 1; i <= 12; i++) {
|
|
||||||
const gap = 25 + i * 12;
|
|
||||||
await finger0.move(centerX - gap, centerY);
|
|
||||||
await finger1.move(centerX + gap, centerY);
|
|
||||||
}
|
|
||||||
await finger0.end();
|
|
||||||
await finger1.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
it("must keep resizing a drawing which came back with an undo", async () => {
|
it("must keep resizing a drawing which came back with an undo", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
if (browserName === "firefox") {
|
|
||||||
pending(
|
|
||||||
"Touch events are not supported on devices without touch screen in Firefox."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await switchToInk(page);
|
await switchToInk(page);
|
||||||
|
|
||||||
const rect = await getRect(page, ".annotationEditorLayer");
|
const rect = await getRect(page, ".annotationEditorLayer");
|
||||||
@ -1462,11 +1442,21 @@ describe("Pinch to resize a drawing", () => {
|
|||||||
await page.waitForSelector(editorSelector);
|
await page.waitForSelector(editorSelector);
|
||||||
await selectEditor(page, editorSelector);
|
await selectEditor(page, editorSelector);
|
||||||
|
|
||||||
const { width: before } = await getRect(page, editorSelector);
|
const before = await getRect(page, editorSelector);
|
||||||
await pinchOut(page, editorSelector);
|
const startGap = Math.min(before.width, before.height) * 0.2;
|
||||||
|
const endGap = Math.max(before.width, before.height) * 1.8;
|
||||||
|
await pinch(
|
||||||
|
page,
|
||||||
|
before.x + before.width / 2,
|
||||||
|
before.y + before.height / 2,
|
||||||
|
startGap,
|
||||||
|
endGap
|
||||||
|
);
|
||||||
const { width: after } = await getRect(page, editorSelector);
|
const { width: after } = await getRect(page, editorSelector);
|
||||||
|
|
||||||
expect(after).withContext(`In ${browserName}`).toBeGreaterThan(before);
|
expect(after)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeGreaterThan(before.width);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -600,6 +600,31 @@ async function dragAndDrop(page, selector, translations, steps = 1) {
|
|||||||
await page.waitForSelector("#viewer:not(.noUserSelect)");
|
await page.waitForSelector("#viewer:not(.noUserSelect)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move two fingers, horizontally centered on (centerX, centerY), from startGap
|
||||||
|
// to endGap: it's a pinch out when endGap is larger than startGap.
|
||||||
|
// Keep in mind that `TouchManager` starts to pinch only once the distance
|
||||||
|
// between the two fingers changed by more than `MIN_TOUCH_DISTANCE_TO_PINCH`
|
||||||
|
// (35 CSS pixels), hence the first moves are swallowed and the resulting zoom
|
||||||
|
// factor is smaller than endGap / startGap.
|
||||||
|
async function pinch(page, centerX, centerY, startGap, endGap, steps = 12) {
|
||||||
|
const finger0 = await page.touchscreen.touchStart(
|
||||||
|
centerX - startGap,
|
||||||
|
centerY
|
||||||
|
);
|
||||||
|
const finger1 = await page.touchscreen.touchStart(
|
||||||
|
centerX + startGap,
|
||||||
|
centerY
|
||||||
|
);
|
||||||
|
const delta = (endGap - startGap) / steps;
|
||||||
|
for (let i = 1; i <= steps; i++) {
|
||||||
|
const gap = startGap + i * delta;
|
||||||
|
await finger0.move(centerX - gap, centerY);
|
||||||
|
await finger1.move(centerX + gap, centerY);
|
||||||
|
}
|
||||||
|
await finger0.end();
|
||||||
|
await finger1.end();
|
||||||
|
}
|
||||||
|
|
||||||
function waitForPageChanging(page) {
|
function waitForPageChanging(page) {
|
||||||
return createPromise(page, resolve => {
|
return createPromise(page, resolve => {
|
||||||
window.PDFViewerApplication.eventBus.on("pagechanging", resolve, {
|
window.PDFViewerApplication.eventBus.on("pagechanging", resolve, {
|
||||||
@ -1163,6 +1188,7 @@ export {
|
|||||||
paste,
|
paste,
|
||||||
pasteFromClipboard,
|
pasteFromClipboard,
|
||||||
PDI,
|
PDI,
|
||||||
|
pinch,
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
selectEditor,
|
selectEditor,
|
||||||
selectEditors,
|
selectEditors,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
getRect,
|
getRect,
|
||||||
getSpanRectFromText,
|
getSpanRectFromText,
|
||||||
loadAndWait,
|
loadAndWait,
|
||||||
|
pinch,
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
showViewsManager,
|
showViewsManager,
|
||||||
waitAndClick,
|
waitAndClick,
|
||||||
@ -1527,7 +1528,11 @@ describe("PDF viewer", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
pages = await loadAndWait(
|
pages = await loadAndWait(
|
||||||
"tracemonkey.pdf",
|
"tracemonkey.pdf",
|
||||||
`.page[data-page-number = "1"] .endOfContent`
|
`.page[data-page-number = "1"] .endOfContent`,
|
||||||
|
// Pin the zoom: the drift checked below is proportional to the zoom
|
||||||
|
// level reached at the end of the pinch, and the default `page-fit`
|
||||||
|
// depends on the size of the window.
|
||||||
|
50
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1535,20 +1540,9 @@ describe("PDF viewer", () => {
|
|||||||
await closePages(pages);
|
await closePages(pages);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the content under the pinch centre fixed on the screen", async () => {
|
it("keeps the content under the pinch center fixed on the screen", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
if (browserName === "firefox") {
|
|
||||||
pending(
|
|
||||||
"Touch events are not supported on devices without touch screen in Firefox."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (browserName === "chrome") {
|
|
||||||
pending(
|
|
||||||
"Pinch zoom emulation is not supported for WebDriver BiDi in Chrome."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = await getSpanRectFromText(page, 1, "type-stable");
|
const rect = await getSpanRectFromText(page, 1, "type-stable");
|
||||||
const originX = rect.x + rect.width / 2;
|
const originX = rect.x + rect.width / 2;
|
||||||
const originY = rect.y + rect.height / 2;
|
const originY = rect.y + rect.height / 2;
|
||||||
@ -1564,14 +1558,12 @@ describe("PDF viewer", () => {
|
|||||||
};
|
};
|
||||||
window.PDFViewerApplication.eventBus.on("textlayerrendered", cb);
|
window.PDFViewerApplication.eventBus.on("textlayerrendered", cb);
|
||||||
});
|
});
|
||||||
const client = await page.target().createCDPSession();
|
// Spread the two fingers from 50 to 200 pixels apart: the first
|
||||||
await client.send("Input.synthesizePinchGesture", {
|
// moves are swallowed until the distance between them changed by
|
||||||
x: originX,
|
// more than 35 pixels, hence a zoom factor of about 200/85 = 2.4.
|
||||||
y: originY,
|
await pinch(page, originX, originY, 25, 100);
|
||||||
scaleFactor: 3,
|
|
||||||
gestureSourceType: "touch",
|
|
||||||
});
|
|
||||||
await awaitPromise(rendered);
|
await awaitPromise(rendered);
|
||||||
|
|
||||||
const spanHandle = await page.evaluateHandle(() =>
|
const spanHandle = await page.evaluateHandle(() =>
|
||||||
Array.from(
|
Array.from(
|
||||||
document.querySelectorAll(
|
document.querySelectorAll(
|
||||||
@ -1579,7 +1571,21 @@ describe("PDF viewer", () => {
|
|||||||
)
|
)
|
||||||
).find(span => span.textContent.includes("type-stable"))
|
).find(span => span.textContent.includes("type-stable"))
|
||||||
);
|
);
|
||||||
expect(await spanHandle.isIntersectingViewport()).toBeTrue();
|
expect(await spanHandle.isIntersectingViewport())
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeTrue();
|
||||||
|
|
||||||
|
// The text which was under the fingers must still be at the same
|
||||||
|
// height: only vertically because a page which is larger than its
|
||||||
|
// container isn't centered in it anymore.
|
||||||
|
// A few pixels are tolerated because the origin is preserved by
|
||||||
|
// scrolling: Chrome snaps the scroll offsets to the device pixels and
|
||||||
|
// the discarded fractions show up as a small drift. It's exact in
|
||||||
|
// Firefox, which keeps them.
|
||||||
|
const newRect = await getSpanRectFromText(page, 1, "type-stable");
|
||||||
|
expect(Math.abs(newRect.y + newRect.height / 2 - originY))
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeLessThan(5);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1890,19 +1890,20 @@ class PDFViewer {
|
|||||||
container.scrollLeft - firstPage.x,
|
container.scrollLeft - firstPage.x,
|
||||||
container.scrollTop - firstPage.y
|
container.scrollTop - firstPage.y
|
||||||
);
|
);
|
||||||
const intLeft = Math.round(topLeft[0]);
|
const [left, top] = topLeft;
|
||||||
const intTop = Math.round(topLeft[1]);
|
|
||||||
|
|
||||||
let pdfOpenParams = `#page=${pageNumber}`;
|
let pdfOpenParams = `#page=${pageNumber}`;
|
||||||
if (!this.isInPresentationMode) {
|
if (!this.isInPresentationMode) {
|
||||||
pdfOpenParams += `&zoom=${normalizedScaleValue},${intLeft},${intTop}`;
|
pdfOpenParams +=
|
||||||
|
`&zoom=${normalizedScaleValue},` +
|
||||||
|
`${Math.round(left)},${Math.round(top)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._location = {
|
this._location = {
|
||||||
pageNumber,
|
pageNumber,
|
||||||
scale: normalizedScaleValue,
|
scale: normalizedScaleValue,
|
||||||
top: intTop,
|
top,
|
||||||
left: intLeft,
|
left,
|
||||||
rotation: this._pagesRotation,
|
rotation: this._pagesRotation,
|
||||||
pdfOpenParams,
|
pdfOpenParams,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user