Skip to content

Commit

Permalink
[Editor] Add the ability to resize an editor in using a pinch gesture
Browse files Browse the repository at this point in the history
  • Loading branch information
calixteman committed Dec 12, 2024
1 parent 8fa4398 commit c7a00c2
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 19 deletions.
124 changes: 109 additions & 15 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,90 @@ class AnnotationEditor {

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

if (this.isResizable && this._uiManager._supportsPinchToZoom) {
this.#touchManager ||= new TouchManager({
container: this.div,
isPinchingDisabled: () => false, // !this.isSelected,
isPinchingStopped: () => false,
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) {
const controlFactor = 0.7;
let factor = controlFactor * (distance / prevDistance) + 1 - controlFactor;
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 +1243,6 @@ class AnnotationEditor {
event.preventDefault();
return;
}

this.#hasBeenClicked = true;

if (this._isDraggable) {
Expand Down Expand Up @@ -1189,6 +1273,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 @@ -1198,9 +1283,12 @@ class AnnotationEditor {

this.#dragPointerId = null;
this.#hasBeenClicked = false;
if (!this._uiManager.endDragSession()) {
if (!this._uiManager.endDragSession() && !this.isSelected) {
this.#selectOnPointerEvent(e);
}
if (hasDraggingStarted) {
this._onStopDragging();
}
};

if (isSelected) {
Expand All @@ -1211,6 +1299,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 +1327,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 +1649,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
10 changes: 8 additions & 2 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,13 +175,13 @@ class TouchManager {
#onTouchEnd(evt) {
this.#touchMoveAC.abort();
this.#touchMoveAC = null;
this.#onPinchEnd?.();

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

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

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 `false`.
*/

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

#scaleTimeoutId = null;

#supportsPinchToZoom = false;

#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

0 comments on commit c7a00c2

Please sign in to comment.