[Editor] Add the ability to resize an editor in using a pinch gesture
This commit is contained in:
parent
8fa4398266
commit
4ed7f7f1ee
@ -26,6 +26,7 @@ import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
|
|||||||
import { noContextMenu, stopEvent } from "../display_utils.js";
|
import { noContextMenu, stopEvent } from "../display_utils.js";
|
||||||
import { AltText } from "./alt_text.js";
|
import { AltText } from "./alt_text.js";
|
||||||
import { EditorToolbar } from "./toolbar.js";
|
import { EditorToolbar } from "./toolbar.js";
|
||||||
|
import { TouchManager } from "../touch_manager.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} AnnotationEditorParameters
|
* @typedef {Object} AnnotationEditorParameters
|
||||||
@ -82,6 +83,8 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
#telemetryTimeouts = null;
|
#telemetryTimeouts = null;
|
||||||
|
|
||||||
|
#touchManager = null;
|
||||||
|
|
||||||
_editToolbar = null;
|
_editToolbar = null;
|
||||||
|
|
||||||
_initialOptions = Object.create(null);
|
_initialOptions = Object.create(null);
|
||||||
@ -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) {
|
#resizerPointermove(name, event) {
|
||||||
const [parentWidth, parentHeight] = this.parentDimensions;
|
const [parentWidth, parentHeight] = this.parentDimensions;
|
||||||
const savedX = this.x;
|
const savedX = this.x;
|
||||||
@ -873,10 +883,6 @@ class AnnotationEditor {
|
|||||||
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
|
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
|
||||||
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
|
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 rotationMatrix = this.#getRotationMatrix(this.rotation);
|
||||||
const transf = (x, y) => [
|
const transf = (x, y) => [
|
||||||
rotationMatrix[0] * x + rotationMatrix[2] * y,
|
rotationMatrix[0] * x + rotationMatrix[2] * y,
|
||||||
@ -936,8 +942,8 @@ class AnnotationEditor {
|
|||||||
const point = getPoint(savedWidth, savedHeight);
|
const point = getPoint(savedWidth, savedHeight);
|
||||||
const oppositePoint = getOpposite(savedWidth, savedHeight);
|
const oppositePoint = getOpposite(savedWidth, savedHeight);
|
||||||
let transfOppositePoint = transf(...oppositePoint);
|
let transfOppositePoint = transf(...oppositePoint);
|
||||||
const oppositeX = round(savedX + transfOppositePoint[0]);
|
const oppositeX = AnnotationEditor._round(savedX + transfOppositePoint[0]);
|
||||||
const oppositeY = round(savedY + transfOppositePoint[1]);
|
const oppositeY = AnnotationEditor._round(savedY + transfOppositePoint[1]);
|
||||||
let ratioX = 1;
|
let ratioX = 1;
|
||||||
let ratioY = 1;
|
let ratioY = 1;
|
||||||
|
|
||||||
@ -990,8 +996,8 @@ class AnnotationEditor {
|
|||||||
) / savedHeight;
|
) / savedHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newWidth = round(savedWidth * ratioX);
|
const newWidth = AnnotationEditor._round(savedWidth * ratioX);
|
||||||
const newHeight = round(savedHeight * ratioY);
|
const newHeight = AnnotationEditor._round(savedHeight * ratioY);
|
||||||
transfOppositePoint = transf(...getOpposite(newWidth, newHeight));
|
transfOppositePoint = transf(...getOpposite(newWidth, newHeight));
|
||||||
const newX = oppositeX - transfOppositePoint[0];
|
const newX = oppositeX - transfOppositePoint[0];
|
||||||
const newY = oppositeY - transfOppositePoint[1];
|
const newY = oppositeY - transfOppositePoint[1];
|
||||||
@ -1142,11 +1148,92 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
bindEvents(this, this.div, ["pointerdown"]);
|
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();
|
this._uiManager._editorUndoBar?.hide();
|
||||||
|
|
||||||
return this.div;
|
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.
|
* Onpointerdown callback.
|
||||||
* @param {PointerEvent} event
|
* @param {PointerEvent} event
|
||||||
@ -1158,7 +1245,6 @@ class AnnotationEditor {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#hasBeenClicked = true;
|
this.#hasBeenClicked = true;
|
||||||
|
|
||||||
if (this._isDraggable) {
|
if (this._isDraggable) {
|
||||||
@ -1189,6 +1275,7 @@ class AnnotationEditor {
|
|||||||
#setUpDragSession(event) {
|
#setUpDragSession(event) {
|
||||||
const { isSelected } = this;
|
const { isSelected } = this;
|
||||||
this._uiManager.setUpDragSession();
|
this._uiManager.setUpDragSession();
|
||||||
|
let hasDraggingStarted = false;
|
||||||
|
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
const signal = this._uiManager.combinedSignal(ac);
|
const signal = this._uiManager.combinedSignal(ac);
|
||||||
@ -1201,6 +1288,9 @@ class AnnotationEditor {
|
|||||||
if (!this._uiManager.endDragSession()) {
|
if (!this._uiManager.endDragSession()) {
|
||||||
this.#selectOnPointerEvent(e);
|
this.#selectOnPointerEvent(e);
|
||||||
}
|
}
|
||||||
|
if (hasDraggingStarted) {
|
||||||
|
this._onStopDragging();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
@ -1211,6 +1301,10 @@ class AnnotationEditor {
|
|||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"pointermove",
|
"pointermove",
|
||||||
e => {
|
e => {
|
||||||
|
if (!hasDraggingStarted) {
|
||||||
|
hasDraggingStarted = true;
|
||||||
|
this._onStartDragging();
|
||||||
|
}
|
||||||
const { clientX: x, clientY: y, pointerId } = e;
|
const { clientX: x, clientY: y, pointerId } = e;
|
||||||
if (pointerId !== this.#dragPointerId) {
|
if (pointerId !== this.#dragPointerId) {
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
@ -1235,11 +1329,14 @@ class AnnotationEditor {
|
|||||||
"pointerdown",
|
"pointerdown",
|
||||||
// If the user drags with one finger and then clicks with another.
|
// If the user drags with one finger and then clicks with another.
|
||||||
e => {
|
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.
|
// We cannot have two primaries at the same time.
|
||||||
// It's possible to be in this state with Firefox and Gnome when
|
// It's possible to be in this state with Firefox and Gnome when
|
||||||
// trying to drag with three fingers (see bug 1933716).
|
// trying to drag with three fingers (see bug 1933716).
|
||||||
cancelDrag(e);
|
if (this.#touchManager || e.isPrimary) {
|
||||||
|
cancelDrag(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
},
|
},
|
||||||
@ -1247,12 +1344,9 @@ class AnnotationEditor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._onStartDragging();
|
|
||||||
|
|
||||||
const pointerUpCallback = e => {
|
const pointerUpCallback = e => {
|
||||||
if (!this.#dragPointerId || this.#dragPointerId === e.pointerId) {
|
if (!this.#dragPointerId || this.#dragPointerId === e.pointerId) {
|
||||||
cancelDrag(e);
|
cancelDrag(e);
|
||||||
this._onStopDragging();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
@ -1557,6 +1651,8 @@ class AnnotationEditor {
|
|||||||
this.#telemetryTimeouts = null;
|
this.#telemetryTimeouts = null;
|
||||||
}
|
}
|
||||||
this.parent = null;
|
this.parent = null;
|
||||||
|
this.#touchManager?.destroy();
|
||||||
|
this.#touchManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -834,7 +834,8 @@ class AnnotationEditorUIManager {
|
|||||||
enableUpdatedAddImage,
|
enableUpdatedAddImage,
|
||||||
enableNewAltTextWhenAddingImage,
|
enableNewAltTextWhenAddingImage,
|
||||||
mlManager,
|
mlManager,
|
||||||
editorUndoBar
|
editorUndoBar,
|
||||||
|
supportsPinchToZoom
|
||||||
) {
|
) {
|
||||||
const signal = (this._signal = this.#abortController.signal);
|
const signal = (this._signal = this.#abortController.signal);
|
||||||
this.#container = container;
|
this.#container = container;
|
||||||
@ -870,6 +871,7 @@ class AnnotationEditorUIManager {
|
|||||||
};
|
};
|
||||||
this.isShiftKeyDown = false;
|
this.isShiftKeyDown = false;
|
||||||
this._editorUndoBar = editorUndoBar || null;
|
this._editorUndoBar = editorUndoBar || null;
|
||||||
|
this._supportsPinchToZoom = supportsPinchToZoom !== false;
|
||||||
|
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||||
Object.defineProperty(this, "reset", {
|
Object.defineProperty(this, "reset", {
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { shadow } from "../shared/util.js";
|
import { shadow } from "../shared/util.js";
|
||||||
|
import { stopEvent } from "./display_utils.js";
|
||||||
|
|
||||||
class TouchManager {
|
class TouchManager {
|
||||||
#container;
|
#container;
|
||||||
@ -24,6 +25,8 @@ class TouchManager {
|
|||||||
|
|
||||||
#isPinchingDisabled;
|
#isPinchingDisabled;
|
||||||
|
|
||||||
|
#onPinchStart;
|
||||||
|
|
||||||
#onPinching;
|
#onPinching;
|
||||||
|
|
||||||
#onPinchEnd;
|
#onPinchEnd;
|
||||||
@ -40,6 +43,7 @@ class TouchManager {
|
|||||||
container,
|
container,
|
||||||
isPinchingDisabled = null,
|
isPinchingDisabled = null,
|
||||||
isPinchingStopped = null,
|
isPinchingStopped = null,
|
||||||
|
onPinchStart = null,
|
||||||
onPinching = null,
|
onPinching = null,
|
||||||
onPinchEnd = null,
|
onPinchEnd = null,
|
||||||
signal,
|
signal,
|
||||||
@ -47,6 +51,7 @@ class TouchManager {
|
|||||||
this.#container = container;
|
this.#container = container;
|
||||||
this.#isPinchingStopped = isPinchingStopped;
|
this.#isPinchingStopped = isPinchingStopped;
|
||||||
this.#isPinchingDisabled = isPinchingDisabled;
|
this.#isPinchingDisabled = isPinchingDisabled;
|
||||||
|
this.#onPinchStart = onPinchStart;
|
||||||
this.#onPinching = onPinching;
|
this.#onPinching = onPinching;
|
||||||
this.#onPinchEnd = onPinchEnd;
|
this.#onPinchEnd = onPinchEnd;
|
||||||
this.#touchManagerAC = new AbortController();
|
this.#touchManagerAC = new AbortController();
|
||||||
@ -93,9 +98,10 @@ class TouchManager {
|
|||||||
this.#onTouchEnd.bind(this),
|
this.#onTouchEnd.bind(this),
|
||||||
opt
|
opt
|
||||||
);
|
);
|
||||||
|
this.#onPinchStart?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
evt.preventDefault();
|
stopEvent(evt);
|
||||||
|
|
||||||
if (evt.touches.length !== 2 || this.#isPinchingStopped?.()) {
|
if (evt.touches.length !== 2 || this.#isPinchingStopped?.()) {
|
||||||
this.#touchInfo = null;
|
this.#touchInfo = null;
|
||||||
@ -169,18 +175,15 @@ class TouchManager {
|
|||||||
#onTouchEnd(evt) {
|
#onTouchEnd(evt) {
|
||||||
this.#touchMoveAC.abort();
|
this.#touchMoveAC.abort();
|
||||||
this.#touchMoveAC = null;
|
this.#touchMoveAC = null;
|
||||||
|
this.#onPinchEnd?.();
|
||||||
|
|
||||||
if (!this.#touchInfo) {
|
if (!this.#touchInfo) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#isPinching) {
|
|
||||||
this.#onPinchEnd?.();
|
|
||||||
this.#isPinching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
this.#touchInfo = null;
|
this.#touchInfo = null;
|
||||||
|
this.#isPinching = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|||||||
@ -492,6 +492,7 @@ const PDFViewerApplication = {
|
|||||||
mlManager: this.mlManager,
|
mlManager: this.mlManager,
|
||||||
abortSignal: this._globalAbortController.signal,
|
abortSignal: this._globalAbortController.signal,
|
||||||
enableHWA,
|
enableHWA,
|
||||||
|
supportsPinchToZoom: this.supportsPinchToZoom,
|
||||||
});
|
});
|
||||||
this.pdfViewer = pdfViewer;
|
this.pdfViewer = pdfViewer;
|
||||||
|
|
||||||
|
|||||||
@ -126,6 +126,8 @@ function isValidAnnotationEditorMode(mode) {
|
|||||||
* mode.
|
* mode.
|
||||||
* @property {boolean} [enableHWA] - Enables hardware acceleration for
|
* @property {boolean} [enableHWA] - Enables hardware acceleration for
|
||||||
* rendering. The default value is `false`.
|
* rendering. The default value is `false`.
|
||||||
|
* @property {boolean} [supportsPinchToZoom] - Enable zooming on pinch gesture.
|
||||||
|
* The default value is `true`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class PDFPageViewBuffer {
|
class PDFPageViewBuffer {
|
||||||
@ -248,6 +250,8 @@ class PDFViewer {
|
|||||||
|
|
||||||
#scaleTimeoutId = null;
|
#scaleTimeoutId = null;
|
||||||
|
|
||||||
|
#supportsPinchToZoom = true;
|
||||||
|
|
||||||
#textLayerMode = TextLayerMode.ENABLE;
|
#textLayerMode = TextLayerMode.ENABLE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -316,6 +320,7 @@ class PDFViewer {
|
|||||||
this.pageColors = options.pageColors || null;
|
this.pageColors = options.pageColors || null;
|
||||||
this.#mlManager = options.mlManager || null;
|
this.#mlManager = options.mlManager || null;
|
||||||
this.#enableHWA = options.enableHWA || false;
|
this.#enableHWA = options.enableHWA || false;
|
||||||
|
this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
|
||||||
|
|
||||||
this.defaultRenderingQueue = !options.renderingQueue;
|
this.defaultRenderingQueue = !options.renderingQueue;
|
||||||
if (
|
if (
|
||||||
@ -911,7 +916,8 @@ class PDFViewer {
|
|||||||
this.#enableUpdatedAddImage,
|
this.#enableUpdatedAddImage,
|
||||||
this.#enableNewAltTextWhenAddingImage,
|
this.#enableNewAltTextWhenAddingImage,
|
||||||
this.#mlManager,
|
this.#mlManager,
|
||||||
this.#editorUndoBar
|
this.#editorUndoBar,
|
||||||
|
this.#supportsPinchToZoom
|
||||||
);
|
);
|
||||||
eventBus.dispatch("annotationeditoruimanager", {
|
eventBus.dispatch("annotationeditoruimanager", {
|
||||||
source: this,
|
source: this,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user