Merge pull request #15909 from calixteman/pinch_trackpad

Add support for smooth pinch-to-zoom on a trackpad (bug 1659492)
This commit is contained in:
calixteman 2023-01-11 16:26:21 +01:00 committed by GitHub
commit dafcf82365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -217,7 +217,9 @@ const PDFViewerApplication = {
_contentLength: null, _contentLength: null,
_saveInProgress: false, _saveInProgress: false,
_wheelUnusedTicks: 0, _wheelUnusedTicks: 0,
_wheelUnusedFactor: 1,
_touchUnusedTicks: 0, _touchUnusedTicks: 0,
_touchUnusedFactor: 1,
_PDFBug: null, _PDFBug: null,
_hasAnnotationEditors: false, _hasAnnotationEditors: false,
_title: document.title, _title: document.title,
@ -2058,6 +2060,23 @@ const PDFViewerApplication = {
return wholeTicks; return wholeTicks;
}, },
_accumulateFactor(previousScale, factor, prop) {
if (factor === 1) {
return 1;
}
// If the direction changed, reset the accumulated factor.
if ((this[prop] > 1 && factor < 1) || (this[prop] < 1 && factor > 1)) {
this[prop] = 1;
}
const newFactor =
Math.floor(previousScale * factor * this[prop] * 100) /
(100 * previousScale);
this[prop] = factor / newFactor;
return newFactor;
},
/** /**
* Should be called *after* all pages have loaded, or if an error occurred, * Should be called *after* all pages have loaded, or if an error occurred,
* to unblock the "load" event; see https://bugzilla.mozilla.org/show_bug.cgi?id=1618553 * to unblock the "load" event; see https://bugzilla.mozilla.org/show_bug.cgi?id=1618553
@ -2628,8 +2647,11 @@ function setZoomDisabledTimeout() {
} }
function webViewerWheel(evt) { function webViewerWheel(evt) {
const { pdfViewer, supportedMouseWheelZoomModifierKeys } = const {
PDFViewerApplication; pdfViewer,
supportedMouseWheelZoomModifierKeys,
supportsPinchToZoom,
} = PDFViewerApplication;
if (pdfViewer.isInPresentationMode) { if (pdfViewer.isInPresentationMode) {
return; return;
@ -2653,45 +2675,61 @@ function webViewerWheel(evt) {
return; return;
} }
// It is important that we query deltaMode before delta{X,Y}, so that
// Firefox doesn't switch to DOM_DELTA_PIXEL mode for compat with other
// browsers, see https://bugzilla.mozilla.org/show_bug.cgi?id=1392460.
const deltaMode = evt.deltaMode;
const delta = normalizeWheelEventDirection(evt);
const previousScale = pdfViewer.currentScale; const previousScale = pdfViewer.currentScale;
if (isPinchToZoom && supportsPinchToZoom) {
// The following formula is a bit strange but it comes from:
// https://searchfox.org/mozilla-central/rev/d62c4c4d5547064487006a1506287da394b64724/widget/InputData.cpp#618-626
let scaleFactor = Math.exp(-evt.deltaY / 100);
scaleFactor = PDFViewerApplication._accumulateFactor(
previousScale,
scaleFactor,
"_wheelUnusedFactor"
);
if (scaleFactor < 1) {
PDFViewerApplication.zoomOut(null, scaleFactor);
} else if (scaleFactor > 1) {
PDFViewerApplication.zoomIn(null, scaleFactor);
}
} else {
// It is important that we query deltaMode before delta{X,Y}, so that
// Firefox doesn't switch to DOM_DELTA_PIXEL mode for compat with other
// browsers, see https://bugzilla.mozilla.org/show_bug.cgi?id=1392460.
const deltaMode = evt.deltaMode;
const delta = normalizeWheelEventDirection(evt);
let ticks = 0; let ticks = 0;
if ( if (
deltaMode === WheelEvent.DOM_DELTA_LINE || deltaMode === WheelEvent.DOM_DELTA_LINE ||
deltaMode === WheelEvent.DOM_DELTA_PAGE deltaMode === WheelEvent.DOM_DELTA_PAGE
) { ) {
// For line-based devices, use one tick per event, because different // For line-based devices, use one tick per event, because different
// OSs have different defaults for the number lines. But we generally // OSs have different defaults for the number lines. But we generally
// want one "clicky" roll of the wheel (which produces one event) to // want one "clicky" roll of the wheel (which produces one event) to
// adjust the zoom by one step. // adjust the zoom by one step.
if (Math.abs(delta) >= 1) { if (Math.abs(delta) >= 1) {
ticks = Math.sign(delta); ticks = Math.sign(delta);
} else {
// If we're getting fractional lines (I can't think of a scenario
// this might actually happen), be safe and use the accumulator.
ticks = PDFViewerApplication._accumulateTicks(
delta,
"_wheelUnusedTicks"
);
}
} else { } else {
// If we're getting fractional lines (I can't think of a scenario // pixel-based devices
// this might actually happen), be safe and use the accumulator. const PIXELS_PER_LINE_SCALE = 30;
ticks = PDFViewerApplication._accumulateTicks( ticks = PDFViewerApplication._accumulateTicks(
delta, delta / PIXELS_PER_LINE_SCALE,
"_wheelUnusedTicks" "_wheelUnusedTicks"
); );
} }
} else {
// pixel-based devices
const PIXELS_PER_LINE_SCALE = 30;
ticks = PDFViewerApplication._accumulateTicks(
delta / PIXELS_PER_LINE_SCALE,
"_wheelUnusedTicks"
);
}
if (ticks < 0) { if (ticks < 0) {
PDFViewerApplication.zoomOut(-ticks); PDFViewerApplication.zoomOut(-ticks);
} else if (ticks > 0) { } else if (ticks > 0) {
PDFViewerApplication.zoomIn(ticks); PDFViewerApplication.zoomIn(ticks);
}
} }
const currentScale = pdfViewer.currentScale; const currentScale = pdfViewer.currentScale;
@ -2725,12 +2763,15 @@ function webViewerTouchStart(evt) {
return; return;
} }
const [touch0, touch1] = evt.touches; let [touch0, touch1] = evt.touches;
if (touch0.identifier > touch1.identifier) {
[touch0, touch1] = [touch1, touch0];
}
PDFViewerApplication._touchInfo = { PDFViewerApplication._touchInfo = {
centerX: (touch0.pageX + touch1.pageX) / 2, touch0X: touch0.pageX,
centerY: (touch0.pageY + touch1.pageY) / 2, touch0Y: touch0.pageY,
distance: touch1X: touch1.pageX,
Math.hypot(touch0.pageX - touch1.pageX, touch0.pageY - touch1.pageY) || 1, touch1Y: touch1.pageY,
}; };
} }
@ -2740,42 +2781,88 @@ function webViewerTouchMove(evt) {
} }
const { pdfViewer, _touchInfo, supportsPinchToZoom } = PDFViewerApplication; const { pdfViewer, _touchInfo, supportsPinchToZoom } = PDFViewerApplication;
const [touch0, touch1] = evt.touches; let [touch0, touch1] = evt.touches;
if (touch0.identifier > touch1.identifier) {
[touch0, touch1] = [touch1, touch0];
}
const { pageX: page0X, pageY: page0Y } = touch0; const { pageX: page0X, pageY: page0Y } = touch0;
const { pageX: page1X, pageY: page1Y } = touch1; const { pageX: page1X, pageY: page1Y } = touch1;
const distance = Math.hypot(page0X - page1X, page0Y - page1Y) || 1; const {
const { distance: previousDistance } = _touchInfo; touch0X: pTouch0X,
const scaleFactor = distance / previousDistance; touch0Y: pTouch0Y,
touch1X: pTouch1X,
touch1Y: pTouch1Y,
} = _touchInfo;
if (supportsPinchToZoom && Math.abs(scaleFactor - 1) <= 1e-2) { if (
// Scale increase/decrease isn't significant enough. Math.abs(pTouch0X - page0X) <= 1 &&
Math.abs(pTouch0Y - page0Y) <= 1 &&
Math.abs(pTouch1X - page1X) <= 1 &&
Math.abs(pTouch1Y - page1Y) <= 1
) {
// Touches are really too close and it's hard do some basic
// geometry in order to guess something.
return; return;
} }
const { centerX, centerY } = _touchInfo; _touchInfo.touch0X = page0X;
const diff0X = page0X - centerX; _touchInfo.touch0Y = page0Y;
const diff1X = page1X - centerX; _touchInfo.touch1X = page1X;
const diff0Y = page0Y - centerY; _touchInfo.touch1Y = page1Y;
const diff1Y = page1Y - centerY;
const dotProduct = diff0X * diff1X + diff0Y * diff1Y; if (pTouch0X === page0X && pTouch0Y === page0Y) {
if (dotProduct >= 0) { // First touch is fixed, if the vectors are collinear then we've a pinch.
// The two touches go in almost the same direction. const v1X = pTouch1X - page0X;
return; const v1Y = pTouch1Y - page0Y;
const v2X = page1X - page0X;
const v2Y = page1Y - page0Y;
const det = v1X * v2Y - v1Y * v2X;
// 0.02 is approximatively sin(0.15deg).
if (Math.abs(det) > 0.02 * Math.hypot(v1X, v1Y) * Math.hypot(v2X, v2Y)) {
return;
}
} else if (pTouch1X === page1X && pTouch1Y === page1Y) {
// Second touch is fixed, if the vectors are collinear then we've a pinch.
const v1X = pTouch0X - page1X;
const v1Y = pTouch0Y - page1Y;
const v2X = page0X - page1X;
const v2Y = page0Y - page1Y;
const det = v1X * v2Y - v1Y * v2X;
if (Math.abs(det) > 0.02 * Math.hypot(v1X, v1Y) * Math.hypot(v2X, v2Y)) {
return;
}
} else {
const diff0X = page0X - pTouch0X;
const diff1X = page1X - pTouch1X;
const diff0Y = page0Y - pTouch0Y;
const diff1Y = page1Y - pTouch1Y;
const dotProduct = diff0X * diff1X + diff0Y * diff1Y;
if (dotProduct >= 0) {
// The two touches go in almost the same direction.
return;
}
} }
evt.preventDefault(); evt.preventDefault();
const distance = Math.hypot(page0X - page1X, page0Y - page1Y) || 1;
const pDistance = Math.hypot(pTouch0X - pTouch1X, pTouch0Y - pTouch1Y) || 1;
const previousScale = pdfViewer.currentScale; const previousScale = pdfViewer.currentScale;
if (supportsPinchToZoom) { if (supportsPinchToZoom) {
if (scaleFactor < 1) { const newScaleFactor = PDFViewerApplication._accumulateFactor(
PDFViewerApplication.zoomOut(null, scaleFactor); previousScale,
} else { distance / pDistance,
PDFViewerApplication.zoomIn(null, scaleFactor); "_touchUnusedFactor"
);
if (newScaleFactor < 1) {
PDFViewerApplication.zoomOut(null, newScaleFactor);
} else if (newScaleFactor > 1) {
PDFViewerApplication.zoomIn(null, newScaleFactor);
} }
} else { } else {
const PIXELS_PER_LINE_SCALE = 30; const PIXELS_PER_LINE_SCALE = 30;
const ticks = PDFViewerApplication._accumulateTicks( const ticks = PDFViewerApplication._accumulateTicks(
(distance - previousDistance) / PIXELS_PER_LINE_SCALE, (distance - pDistance) / PIXELS_PER_LINE_SCALE,
"_touchUnusedTicks" "_touchUnusedTicks"
); );
if (ticks < 0) { if (ticks < 0) {
@ -2788,9 +2875,8 @@ function webViewerTouchMove(evt) {
const currentScale = pdfViewer.currentScale; const currentScale = pdfViewer.currentScale;
if (previousScale !== currentScale) { if (previousScale !== currentScale) {
const scaleCorrectionFactor = currentScale / previousScale - 1; const scaleCorrectionFactor = currentScale / previousScale - 1;
const newCenterX = (_touchInfo.centerX = (page0X + page1X) / 2); const newCenterX = (page0X + page1X) / 2;
const newCenterY = (_touchInfo.centerY = (page0Y + page1Y) / 2); const newCenterY = (page0Y + page1Y) / 2;
_touchInfo.distance = distance;
const [top, left] = pdfViewer.containerTopLeft; const [top, left] = pdfViewer.containerTopLeft;
const dx = newCenterX - left; const dx = newCenterX - left;
@ -2807,8 +2893,8 @@ function webViewerTouchEnd(evt) {
evt.preventDefault(); evt.preventDefault();
PDFViewerApplication._touchInfo = null; PDFViewerApplication._touchInfo = null;
PDFViewerApplication.pdfViewer.refresh();
PDFViewerApplication._touchUnusedTicks = 0; PDFViewerApplication._touchUnusedTicks = 0;
PDFViewerApplication._touchUnusedFactor = 1;
} }
function webViewerClick(evt) { function webViewerClick(evt) {