Skip to content

Commit

Permalink
Add the pinch-to-zoom feature (bug 1677933)
Browse files Browse the repository at this point in the history
  • Loading branch information
calixteman committed Jan 3, 2023
1 parent da1365a commit 57284ba
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 28 deletions.
153 changes: 143 additions & 10 deletions web/app.js
Expand Up @@ -122,6 +122,10 @@ class DefaultExternalServices {
throw new Error("Not implemented: createScripting");
}

static get supportsPinchToZoom() {
return shadow(this, "supportsPinchToZoom", true);
}

static get supportsIntegratedFind() {
return shadow(this, "supportsIntegratedFind", false);
}
Expand Down Expand Up @@ -213,10 +217,12 @@ const PDFViewerApplication = {
_contentLength: null,
_saveInProgress: false,
_wheelUnusedTicks: 0,
_touchUnusedTicks: 0,
_PDFBug: null,
_hasAnnotationEditors: false,
_title: document.title,
_printAnnotationStoragePromise: null,
_touchInfo: null,

// Called once when the document is loaded.
async initialize(appConfig) {
Expand Down Expand Up @@ -655,17 +661,19 @@ const PDFViewerApplication = {
if (this.pdfViewer.isInPresentationMode) {
return;
}
this.pdfViewer.increaseScale(steps, {
this.pdfViewer.increaseScale({
drawingDelay: AppOptions.get("defaultZoomDelay"),
steps,
});
},

zoomOut(steps) {
if (this.pdfViewer.isInPresentationMode) {
return;
}
this.pdfViewer.decreaseScale(steps, {
this.pdfViewer.decreaseScale({
drawingDelay: AppOptions.get("defaultZoomDelay"),
steps,
});
},

Expand All @@ -676,6 +684,23 @@ const PDFViewerApplication = {
this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
},

rescale(scaleFactor) {
if (this.pdfViewer.isInPresentationMode || scaleFactor === 1) {
return;
}
if (scaleFactor < 1) {
this.pdfViewer.decreaseScale({
drawingDelay: AppOptions.get("defaultZoomDelay"),
scaleFactor,
});
} else {
this.pdfViewer.increaseScale({
drawingDelay: AppOptions.get("defaultZoomDelay"),
scaleFactor,
});
}
},

get pagesCount() {
return this.pdfDocument ? this.pdfDocument.numPages : 0;
},
Expand All @@ -696,6 +721,10 @@ const PDFViewerApplication = {
return shadow(this, "supportsFullscreen", document.fullscreenEnabled);
},

get supportsPinchToZoom() {
return this.externalServices.supportsPinchToZoom;
},

get supportsIntegratedFind() {
return this.externalServices.supportsIntegratedFind;
},
Expand Down Expand Up @@ -1915,6 +1944,12 @@ const PDFViewerApplication = {
window.addEventListener("touchstart", webViewerTouchStart, {
passive: false,
});
window.addEventListener("touchmove", webViewerTouchMove, {
passive: false,
});
window.addEventListener("touchend", webViewerTouchEnd, {
passive: false,
});
window.addEventListener("click", webViewerClick);
window.addEventListener("keydown", webViewerKeyDown);
window.addEventListener("resize", _boundEvents.windowResize);
Expand Down Expand Up @@ -1997,6 +2032,12 @@ const PDFViewerApplication = {
window.removeEventListener("touchstart", webViewerTouchStart, {
passive: false,
});
window.removeEventListener("touchmove", webViewerTouchMove, {
passive: false,
});
window.removeEventListener("touchend", webViewerTouchEnd, {
passive: false,
});
window.removeEventListener("click", webViewerClick);
window.removeEventListener("keydown", webViewerKeyDown);
window.removeEventListener("resize", _boundEvents.windowResize);
Expand Down Expand Up @@ -2030,6 +2071,20 @@ const PDFViewerApplication = {
return wholeTicks;
},

accumulateTouchTicks(ticks) {
// If the scroll direction changed, reset the accumulated touch ticks.
if (
(this._touchUnusedTicks > 0 && ticks < 0) ||
(this._touchUnusedTicks < 0 && ticks > 0)
) {
this._touchUnusedTicks = 0;
}
this._touchUnusedTicks += ticks;
const wholeTicks = Math.trunc(this._touchUnusedTicks);
this._touchUnusedTicks -= wholeTicks;
return wholeTicks;
},

/**
* 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
Expand Down Expand Up @@ -2674,18 +2729,96 @@ function webViewerWheel(evt) {

function webViewerTouchStart(evt) {
if (evt.touches.length > 1) {
// Disable touch-based zooming, because the entire UI bits gets zoomed and
// that doesn't look great. If we do want to have a good touch-based
// zooming experience, we need to implement smooth zoom capability (probably
// using a CSS transform for faster visual response, followed by async
// re-rendering at the final zoom level) and do gesture detection on the
// touchmove events to drive it. Or if we want to settle for a less good
// experience we can make the touchmove events drive the existing step-zoom
// behaviour that the ctrl+mousewheel path takes.
evt.preventDefault();

if (evt.touches.length !== 2) {
PDFViewerApplication._touchInfo = null;
return;
}

const [touch0, touch1] = evt.touches;
PDFViewerApplication._touchInfo = {
centerX: (touch0.pageX + touch1.pageX) / 2,
centerY: (touch0.pageY + touch1.pageY) / 2,
distance:
Math.hypot(touch0.pageX - touch1.pageX, touch0.pageY - touch1.pageY) ||
1,
};
}
}

function webViewerTouchMove(evt) {
if (!PDFViewerApplication._touchInfo || evt.touches.length !== 2) {
return;
}

const { pdfViewer, _touchInfo, supportsPinchToZoom } = PDFViewerApplication;
const [touch0, touch1] = evt.touches;
const { pageX: page0X, pageY: page0Y } = touch0;
const { pageX: page1X, pageY: page1Y } = touch1;
const distance = Math.hypot(page0X - page1X, page0Y - page1Y) || 1;
const { distance: previousDistance } = _touchInfo;
const scaleFactor = distance / previousDistance;

if (supportsPinchToZoom && Math.abs(scaleFactor - 1) <= 1e-2) {
// Scale increase/decrease isn't significant enough.
return;
}

const { centerX, centerY } = _touchInfo;
const diff0X = page0X - centerX;
const diff1X = page1X - centerX;
const diff0Y = page0Y - centerY;
const diff1Y = page1Y - centerY;
const dotProduct = diff0X * diff1X + diff0Y * diff1Y;
if (dotProduct >= 0) {
// The two touches go in almost the same direction.
return;
}

evt.preventDefault();

const previousScale = pdfViewer.currentScale;

if (supportsPinchToZoom) {
PDFViewerApplication.rescale(scaleFactor);
} else {
const PIXELS_PER_LINE_SCALE = 30;
const delta = (distance - previousDistance) / PIXELS_PER_LINE_SCALE;
const ticks = PDFViewerApplication.accumulateTouchTicks(delta);
if (ticks < 0) {
PDFViewerApplication.zoomOut(-ticks);
} else if (ticks > 0) {
PDFViewerApplication.zoomIn(ticks);
}
}

const currentScale = pdfViewer.currentScale;
if (previousScale !== currentScale) {
const scaleCorrectionFactor = currentScale / previousScale - 1;
const newCenterX = (_touchInfo.centerX = (page0X + page1X) / 2);
const newCenterY = (_touchInfo.centerY = (page0Y + page1Y) / 2);
_touchInfo.distance = distance;

const [top, left] = pdfViewer.containerTopLeft;
const dx = newCenterX - left;
const dy = newCenterY - top;
pdfViewer.container.scrollLeft += dx * scaleCorrectionFactor;
pdfViewer.container.scrollTop += dy * scaleCorrectionFactor;
}
}

function webViewerTouchEnd(evt) {
if (!PDFViewerApplication._touchInfo) {
return;
}

evt.preventDefault();
PDFViewerApplication._touchInfo = null;
PDFViewerApplication.pdfViewer.refresh();
PDFViewerApplication._touchUnusedTicks = 0;
}

function webViewerClick(evt) {
if (!PDFViewerApplication.secondaryToolbar?.isOpen) {
return;
Expand Down
5 changes: 5 additions & 0 deletions web/firefoxcom.js
Expand Up @@ -410,6 +410,11 @@ class FirefoxExternalServices extends DefaultExternalServices {
return FirefoxScripting;
}

static get supportsPinchToZoom() {
const support = FirefoxCom.requestSync("supportsPinchToZoom");
return shadow(this, "supportsPinchToZoom", support);
}

static get supportsIntegratedFind() {
const support = FirefoxCom.requestSync("supportsIntegratedFind");
return shadow(this, "supportsIntegratedFind", support);
Expand Down
56 changes: 38 additions & 18 deletions web/pdf_viewer.js
Expand Up @@ -1992,43 +1992,63 @@ class PDFViewer {

/**
* Increase the current zoom level one, or more, times.
* @param {number} [steps] - Defaults to zooming once.
* @param {Object|null} [options]
*/
increaseScale(steps = 1, options = null) {
increaseScale(options = null) {
if (!this.pdfDocument) {
return;
}
let newScale = this._currentScale;
do {
newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.ceil(newScale * 10) / 10;
newScale = Math.min(MAX_SCALE, newScale);
} while (--steps > 0 && newScale < MAX_SCALE);

options ||= Object.create(null);
options.noScroll = false;

let newScale;
if (options.scaleFactor) {
newScale = Math.min(
MAX_SCALE,
Math.round(this._currentScale * options.scaleFactor * 100) / 100
);
} else {
let steps = options.steps ?? 1;
newScale = this._currentScale;
do {
newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.ceil(newScale * 10) / 10;
newScale = Math.min(MAX_SCALE, newScale);
} while (--steps > 0 && newScale < MAX_SCALE);

options.noScroll = false;
}
this._setScale(newScale, options);
}

/**
* Decrease the current zoom level one, or more, times.
* @param {number} [steps] - Defaults to zooming once.
* @param {Object|null} [options]
*/
decreaseScale(steps = 1, options = null) {
decreaseScale(options = null) {
if (!this.pdfDocument) {
return;
}
let newScale = this._currentScale;
do {
newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.floor(newScale * 10) / 10;
newScale = Math.max(MIN_SCALE, newScale);
} while (--steps > 0 && newScale > MIN_SCALE);

options ||= Object.create(null);
options.noScroll = false;

let newScale;
if (options.scaleFactor) {
newScale = Math.max(
MIN_SCALE,
Math.round(this._currentScale * options.scaleFactor * 100) / 100
);
} else {
let steps = options.steps ?? 1;
newScale = this._currentScale;
do {
newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
newScale = Math.floor(newScale * 10) / 10;
newScale = Math.max(MIN_SCALE, newScale);
} while (--steps > 0 && newScale > MIN_SCALE);

options.noScroll = false;
}
this._setScale(newScale, options);
}

Expand Down

0 comments on commit 57284ba

Please sign in to comment.