Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Editor] Add the ability to resize an editor in using a pinch gesture #19219

Merged
merged 1 commit into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 110 additions & 14 deletions src/display/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
import { noContextMenu, stopEvent } from "../display_utils.js";
import { AltText } from "./alt_text.js";
import { EditorToolbar } from "./toolbar.js";
import { TouchManager } from "../touch_manager.js";

/**
* @typedef {Object} AnnotationEditorParameters
Expand Down Expand Up @@ -82,6 +83,8 @@ class AnnotationEditor {

#telemetryTimeouts = null;

#touchManager = null;

_editToolbar = null;

_initialOptions = Object.create(null);
Expand Down Expand Up @@ -864,6 +867,13 @@ class AnnotationEditor {
});
}

static _round(x) {
// 10000 because we multiply by 100 and use toFixed(2) in fixAndSetPosition.
// Without rounding, the positions of the corners other than the top left
// one can be slightly wrong.
return Math.round(x * 10000) / 10000;
}

#resizerPointermove(name, event) {
const [parentWidth, parentHeight] = this.parentDimensions;
const savedX = this.x;
Expand All @@ -873,10 +883,6 @@ class AnnotationEditor {
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;

// 10000 because we multiply by 100 and use toFixed(2) in fixAndSetPosition.
// Without rounding, the positions of the corners other than the top left
// one can be slightly wrong.
const round = x => Math.round(x * 10000) / 10000;
const rotationMatrix = this.#getRotationMatrix(this.rotation);
const transf = (x, y) => [
rotationMatrix[0] * x + rotationMatrix[2] * y,
Expand Down Expand Up @@ -936,8 +942,8 @@ class AnnotationEditor {
const point = getPoint(savedWidth, savedHeight);
const oppositePoint = getOpposite(savedWidth, savedHeight);
let transfOppositePoint = transf(...oppositePoint);
const oppositeX = round(savedX + transfOppositePoint[0]);
const oppositeY = round(savedY + transfOppositePoint[1]);
const oppositeX = AnnotationEditor._round(savedX + transfOppositePoint[0]);
const oppositeY = AnnotationEditor._round(savedY + transfOppositePoint[1]);
let ratioX = 1;
let ratioY = 1;

Expand Down Expand Up @@ -990,8 +996,8 @@ class AnnotationEditor {
) / savedHeight;
}

const newWidth = round(savedWidth * ratioX);
const newHeight = round(savedHeight * ratioY);
const newWidth = AnnotationEditor._round(savedWidth * ratioX);
const newHeight = AnnotationEditor._round(savedHeight * ratioY);
transfOppositePoint = transf(...getOpposite(newWidth, newHeight));
const newX = oppositeX - transfOppositePoint[0];
const newY = oppositeY - transfOppositePoint[1];
Expand Down Expand Up @@ -1142,11 +1148,92 @@ class AnnotationEditor {

bindEvents(this, this.div, ["pointerdown"]);

if (this.isResizable && this._uiManager._supportsPinchToZoom) {
this.#touchManager ||= new TouchManager({
container: this.div,
isPinchingDisabled: () => !this.isSelected,
onPinchStart: this.#touchPinchStartCallback.bind(this),
onPinching: this.#touchPinchCallback.bind(this),
onPinchEnd: this.#touchPinchEndCallback.bind(this),
signal: this._uiManager._signal,
});
}

this._uiManager._editorUndoBar?.hide();

return this.div;
}

#touchPinchStartCallback() {
this.#savedDimensions = {
savedX: this.x,
savedY: this.y,
savedWidth: this.width,
savedHeight: this.height,
};
this.#altText?.toggle(false);
this.parent.togglePointerEvents(false);
}

#touchPinchCallback(_origin, prevDistance, distance) {
// Slightly slow down the zooming because the editor could be small and the
// user could have difficulties to rescale it as they want.
const slowDownFactor = 0.7;
let factor =
slowDownFactor * (distance / prevDistance) + 1 - slowDownFactor;
if (factor === 1) {
return;
}

const rotationMatrix = this.#getRotationMatrix(this.rotation);
const transf = (x, y) => [
rotationMatrix[0] * x + rotationMatrix[2] * y,
rotationMatrix[1] * x + rotationMatrix[3] * y,
];

// The center of the editor is the fixed point.
const [parentWidth, parentHeight] = this.parentDimensions;
const savedX = this.x;
const savedY = this.y;
const savedWidth = this.width;
const savedHeight = this.height;

const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
factor = Math.max(
Math.min(factor, 1 / savedWidth, 1 / savedHeight),
minWidth / savedWidth,
minHeight / savedHeight
);
const newWidth = AnnotationEditor._round(savedWidth * factor);
const newHeight = AnnotationEditor._round(savedHeight * factor);
if (newWidth === savedWidth && newHeight === savedHeight) {
return;
}

this.#initialRect ||= [savedX, savedY, savedWidth, savedHeight];
const transfCenterPoint = transf(savedWidth / 2, savedHeight / 2);
const centerX = AnnotationEditor._round(savedX + transfCenterPoint[0]);
const centerY = AnnotationEditor._round(savedY + transfCenterPoint[1]);
const newTransfCenterPoint = transf(newWidth / 2, newHeight / 2);

this.x = centerX - newTransfCenterPoint[0];
this.y = centerY - newTransfCenterPoint[1];
this.width = newWidth;
this.height = newHeight;

this.setDims(parentWidth * newWidth, parentHeight * newHeight);
this.fixAndSetPosition();

this._onResizing();
}

#touchPinchEndCallback() {
this.#altText?.toggle(true);
this.parent.togglePointerEvents(true);
this.#addResizeToUndoStack();
}

/**
* Onpointerdown callback.
* @param {PointerEvent} event
Expand All @@ -1158,7 +1245,6 @@ class AnnotationEditor {
event.preventDefault();
return;
}

this.#hasBeenClicked = true;

if (this._isDraggable) {
Expand Down Expand Up @@ -1189,6 +1275,7 @@ class AnnotationEditor {
#setUpDragSession(event) {
const { isSelected } = this;
this._uiManager.setUpDragSession();
let hasDraggingStarted = false;

const ac = new AbortController();
const signal = this._uiManager.combinedSignal(ac);
Expand All @@ -1201,6 +1288,9 @@ class AnnotationEditor {
if (!this._uiManager.endDragSession()) {
this.#selectOnPointerEvent(e);
}
if (hasDraggingStarted) {
this._onStopDragging();
}
};

if (isSelected) {
Expand All @@ -1211,6 +1301,10 @@ class AnnotationEditor {
window.addEventListener(
"pointermove",
e => {
if (!hasDraggingStarted) {
hasDraggingStarted = true;
this._onStartDragging();
}
const { clientX: x, clientY: y, pointerId } = e;
if (pointerId !== this.#dragPointerId) {
stopEvent(e);
Expand All @@ -1235,24 +1329,24 @@ class AnnotationEditor {
"pointerdown",
// If the user drags with one finger and then clicks with another.
e => {
if (e.isPrimary && e.pointerType === this.#dragPointerType) {
if (e.pointerType === this.#dragPointerType) {
// We've a pinch to zoom session.
// We cannot have two primaries at the same time.
// It's possible to be in this state with Firefox and Gnome when
// trying to drag with three fingers (see bug 1933716).
cancelDrag(e);
if (this.#touchManager || e.isPrimary) {
cancelDrag(e);
}
}
stopEvent(e);
},
opts
);
}

this._onStartDragging();

const pointerUpCallback = e => {
if (!this.#dragPointerId || this.#dragPointerId === e.pointerId) {
cancelDrag(e);
this._onStopDragging();
return;
}
stopEvent(e);
Expand Down Expand Up @@ -1557,6 +1651,8 @@ class AnnotationEditor {
this.#telemetryTimeouts = null;
}
this.parent = null;
this.#touchManager?.destroy();
this.#touchManager = null;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion src/display/editor/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,8 @@ class AnnotationEditorUIManager {
enableUpdatedAddImage,
enableNewAltTextWhenAddingImage,
mlManager,
editorUndoBar
editorUndoBar,
supportsPinchToZoom
) {
const signal = (this._signal = this.#abortController.signal);
this.#container = container;
Expand Down Expand Up @@ -870,6 +871,7 @@ class AnnotationEditorUIManager {
};
this.isShiftKeyDown = false;
this._editorUndoBar = editorUndoBar || null;
this._supportsPinchToZoom = supportsPinchToZoom !== false;

if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
Object.defineProperty(this, "reset", {
Expand Down
15 changes: 9 additions & 6 deletions src/display/touch_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

import { shadow } from "../shared/util.js";
import { stopEvent } from "./display_utils.js";

class TouchManager {
#container;
Expand All @@ -24,6 +25,8 @@ class TouchManager {

#isPinchingDisabled;

#onPinchStart;

#onPinching;

#onPinchEnd;
Expand All @@ -40,13 +43,15 @@ class TouchManager {
container,
isPinchingDisabled = null,
isPinchingStopped = null,
onPinchStart = null,
onPinching = null,
onPinchEnd = null,
signal,
}) {
this.#container = container;
this.#isPinchingStopped = isPinchingStopped;
this.#isPinchingDisabled = isPinchingDisabled;
this.#onPinchStart = onPinchStart;
this.#onPinching = onPinching;
this.#onPinchEnd = onPinchEnd;
this.#touchManagerAC = new AbortController();
Expand Down Expand Up @@ -93,9 +98,10 @@ class TouchManager {
this.#onTouchEnd.bind(this),
opt
);
this.#onPinchStart?.();
}

evt.preventDefault();
stopEvent(evt);

if (evt.touches.length !== 2 || this.#isPinchingStopped?.()) {
this.#touchInfo = null;
Expand Down Expand Up @@ -169,18 +175,15 @@ class TouchManager {
#onTouchEnd(evt) {
this.#touchMoveAC.abort();
this.#touchMoveAC = null;
this.#onPinchEnd?.();

if (!this.#touchInfo) {
return;
}

if (this.#isPinching) {
this.#onPinchEnd?.();
this.#isPinching = false;
}

evt.preventDefault();
this.#touchInfo = null;
this.#isPinching = false;
}

destroy() {
Expand Down
1 change: 1 addition & 0 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ const PDFViewerApplication = {
mlManager: this.mlManager,
abortSignal: this._globalAbortController.signal,
enableHWA,
supportsPinchToZoom: this.supportsPinchToZoom,
});
this.pdfViewer = pdfViewer;

Expand Down
8 changes: 7 additions & 1 deletion web/pdf_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ function isValidAnnotationEditorMode(mode) {
* mode.
* @property {boolean} [enableHWA] - Enables hardware acceleration for
* rendering. The default value is `false`.
* @property {boolean} [supportsPinchToZoom] - Enable zooming on pinch gesture.
* The default value is `true`.
*/

class PDFPageViewBuffer {
Expand Down Expand Up @@ -248,6 +250,8 @@ class PDFViewer {

#scaleTimeoutId = null;

#supportsPinchToZoom = true;

#textLayerMode = TextLayerMode.ENABLE;

/**
Expand Down Expand Up @@ -316,6 +320,7 @@ class PDFViewer {
this.pageColors = options.pageColors || null;
this.#mlManager = options.mlManager || null;
this.#enableHWA = options.enableHWA || false;
this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;

this.defaultRenderingQueue = !options.renderingQueue;
if (
Expand Down Expand Up @@ -911,7 +916,8 @@ class PDFViewer {
this.#enableUpdatedAddImage,
this.#enableNewAltTextWhenAddingImage,
this.#mlManager,
this.#editorUndoBar
this.#editorUndoBar,
this.#supportsPinchToZoom
);
eventBus.dispatch("annotationeditoruimanager", {
source: this,
Expand Down
Loading