: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
this.y = (y + ty) / height;
this.fixAndSetPosition();
#translate([width, height], x, y) {
[x, y] = this.screenToPageTranslation(x, y);
this.fixAndSetPosition();
this.#translate(this.parentDimensions, x, y);
this.#initialPosition ||= [this.x, this.y];
this.#translate(this.pageDimensions, x, y);
this.div.scrollIntoView({
this.#initialPosition ||= [this.x, this.y];
const [parentWidth, parentHeight] = this.parentDimensions;
this.x += tx / parentWidth;
this.y += ty / parentHeight;
if (this.parent && (this.x < 0 || this.x > 1 || this.y < 0 || this.y > 1)) {
} = this.div.getBoundingClientRect();
if (this.parent.findNewParent(this, x, y)) {
this.x -= Math.floor(this.x);
this.y -= Math.floor(this.y);
const [bx, by] = this.getBaseTranslation();
this.div.style.left = `${(100 * x).toFixed(2)}%`;
this.div.style.top = `${(100 * y).toFixed(2)}%`;
this.div.scrollIntoView({
return !!this.#initialPosition && (this.#initialPosition[0] !== this.x || this.#initialPosition[1] !== this.y);
const [parentWidth, parentHeight] = this.parentDimensions;
const x = _borderLineWidth / parentWidth;
const y = _borderLineWidth / parentHeight;
fixAndSetPosition(rotation = this.rotation) {
const [pageWidth, pageHeight] = this.pageDimensions;
if (this._mustFixPosition) {
x = Math.max(0, Math.min(pageWidth - width, x));
y = Math.max(0, Math.min(pageHeight - height, y));
x = Math.max(0, Math.min(pageWidth - height, x));
y = Math.min(pageHeight, Math.max(width, y));
x = Math.min(pageWidth, Math.max(width, x));
y = Math.min(pageHeight, Math.max(height, y));
x = Math.min(pageWidth, Math.max(height, x));
y = Math.max(0, Math.min(pageHeight - width, y));
this.y = y /= pageHeight;
const [bx, by] = this.getBaseTranslation();
style.left = `${(100 * x).toFixed(2)}%`;
style.top = `${(100 * y).toFixed(2)}%`;
static #rotatePoint(x, y, angle) {
screenToPageTranslation(x, y) {
return AnnotationEditor.#rotatePoint(x, y, this.parentRotation);
pageTranslationToScreen(x, y) {
return AnnotationEditor.#rotatePoint(x, y, 360 - this.parentRotation);
#getRotationMatrix(rotation) {
const [pageWidth, pageHeight] = this.pageDimensions;
return [0, -pageWidth / pageHeight, pageHeight / pageWidth, 0];
const [pageWidth, pageHeight] = this.pageDimensions;
return [0, pageWidth / pageHeight, -pageHeight / pageWidth, 0];
return this._uiManager.viewParameters.realScale;
return (this._uiManager.viewParameters.rotation + this.pageRotation) % 360;
pageDimensions: [pageWidth, pageHeight]
const scaledWidth = pageWidth * parentScale;
const scaledHeight = pageHeight * parentScale;
return util_FeatureTest.isCSSRoundSupported ? [Math.round(scaledWidth), Math.round(scaledHeight)] : [scaledWidth, scaledHeight];
const [parentWidth, parentHeight] = this.parentDimensions;
this.div.style.width = `${(100 * width / parentWidth).toFixed(2)}%`;
if (!this.#keepAspectRatio) {
this.div.style.height = `${(100 * height / parentHeight).toFixed(2)}%`;
const widthPercent = width.endsWith("%");
const heightPercent = !this.#keepAspectRatio && height.endsWith("%");
if (widthPercent && heightPercent) {
const [parentWidth, parentHeight] = this.parentDimensions;
style.width = `${(100 * parseFloat(width) / parentWidth).toFixed(2)}%`;
if (!this.#keepAspectRatio && !heightPercent) {
style.height = `${(100 * parseFloat(height) / parentHeight).toFixed(2)}%`;
getInitialTranslation() {
this.#resizersDiv = document.createElement("div");
this.#resizersDiv.classList.add("resizers");
const classes = this._willKeepAspectRatio ? ["topLeft", "topRight", "bottomRight", "bottomLeft"] : ["topLeft", "topMiddle", "topRight", "middleRight", "bottomRight", "bottomMiddle", "bottomLeft", "middleLeft"];
for (const name of classes) {
const div = document.createElement("div");
this.#resizersDiv.append(div);
div.classList.add("resizer", name);
div.setAttribute("data-resizer-name", name);
div.addEventListener("pointerdown", this.#resizerPointerdown.bind(this, name));
div.addEventListener("contextmenu", noContextMenu);
this.div.prepend(this.#resizersDiv);
#resizerPointerdown(name, event) {
} = util_FeatureTest.platform;
if (event.button !== 0 || event.ctrlKey && isMac) {
this.#altText?.toggle(false);
const boundResizerPointermove = this.#resizerPointermove.bind(this, name);
const savedDraggable = this._isDraggable;
this._isDraggable = false;
const pointerMoveOptions = {
this.parent.togglePointerEvents(false);
window.addEventListener("pointermove", boundResizerPointermove, pointerMoveOptions);
window.addEventListener("contextmenu", noContextMenu);
const savedWidth = this.width;
const savedHeight = this.height;
const savedParentCursor = this.parent.div.style.cursor;
const savedCursor = this.div.style.cursor;
this.div.style.cursor = this.parent.div.style.cursor = window.getComputedStyle(event.target).cursor;
const pointerUpCallback = () => {
this.parent.togglePointerEvents(true);
this.#altText?.toggle(true);
this._isDraggable = savedDraggable;
window.removeEventListener("pointerup", pointerUpCallback);
window.removeEventListener("blur", pointerUpCallback);
window.removeEventListener("pointermove", boundResizerPointermove, pointerMoveOptions);
window.removeEventListener("contextmenu", noContextMenu);
this.parent.div.style.cursor = savedParentCursor;
this.div.style.cursor = savedCursor;
this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight);
window.addEventListener("pointerup", pointerUpCallback);
window.addEventListener("blur", pointerUpCallback);
#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight) {
const newWidth = this.width;
const newHeight = this.height;
if (newX === savedX && newY === savedY && newWidth === savedWidth && newHeight === savedHeight) {
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(parentWidth * newWidth, parentHeight * newHeight);
this.fixAndSetPosition();
this.height = savedHeight;
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(parentWidth * savedWidth, parentHeight * savedHeight);
this.fixAndSetPosition();
#resizerPointermove(name, event) {
const [parentWidth, parentHeight] = this.parentDimensions;
const savedWidth = this.width;
const savedHeight = this.height;
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
const round = x => Math.round(x * 10000) / 10000;
const rotationMatrix = this.#getRotationMatrix(this.rotation);
const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y];
const invRotationMatrix = this.#getRotationMatrix(360 - this.rotation);
const invTransf = (x, y) => [invRotationMatrix[0] * x + invRotationMatrix[2] * y, invRotationMatrix[1] * x + invRotationMatrix[3] * y];
let isHorizontal = false;
getPoint = (w, h) => [0, 0];
getOpposite = (w, h) => [w, h];
getPoint = (w, h) => [w / 2, 0];
getOpposite = (w, h) => [w / 2, h];
getPoint = (w, h) => [w, 0];
getOpposite = (w, h) => [0, h];
getPoint = (w, h) => [w, h / 2];
getOpposite = (w, h) => [0, h / 2];
getPoint = (w, h) => [w, h];
getOpposite = (w, h) => [0, 0];
getPoint = (w, h) => [w / 2, h];
getOpposite = (w, h) => [w / 2, 0];
getPoint = (w, h) => [0, h];
getOpposite = (w, h) => [w, 0];
getPoint = (w, h) => [0, h / 2];
getOpposite = (w, h) => [w, h / 2];
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]);
let [deltaX, deltaY] = this.screenToPageTranslation(event.movementX, event.movementY);
[deltaX, deltaY] = invTransf(deltaX / parentWidth, deltaY / parentHeight);
const oldDiag = Math.hypot(savedWidth, savedHeight);
ratioX = ratioY = Math.max(Math.min(Math.hypot(oppositePoint[0] - point[0] - deltaX, oppositePoint[1] - point[1] - deltaY) / oldDiag, 1 / savedWidth, 1 / savedHeight), minWidth / savedWidth, minHeight / savedHeight);
} else if (isHorizontal) {
ratioX = Math.max(minWidth, Math.min(1, Math.abs(oppositePoint[0] - point[0] - deltaX))) / savedWidth;
ratioY = Math.max(minHeight, Math.min(1, Math.abs(oppositePoint[1] - point[1] - deltaY))) / savedHeight;
const newWidth = round(savedWidth * ratioX);
const newHeight = round(savedHeight * ratioY);
transfOppositePoint = transf(...getOpposite(newWidth, newHeight));
const newX = oppositeX - transfOppositePoint[0];
const newY = oppositeY - transfOppositePoint[1];
this.setDims(parentWidth * newWidth, parentHeight * newHeight);
this.fixAndSetPosition();
if (this.#editToolbar || this.#isInEditMode) {
return this.#editToolbar;
this.#editToolbar = new EditorToolbar(this);
this.div.append(this.#editToolbar.render());
this.#editToolbar.addAltTextButton(await this.#altText.render());
return this.#editToolbar;
if (!this.#editToolbar) {
this.#editToolbar.remove();
this.#editToolbar = null;
this.#altText?.destroy();
return this.div.getBoundingClientRect();
async addAltTextButton() {
AltText.initialize(AnnotationEditor._l10nPromise);
this.#altText = new AltText(this);
await this.addEditToolbar();
return this.#altText?.data;
this.#altText.data = data;
return !this.#altText?.isEmpty();
this.div = document.createElement("div");
this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
this.div.className = this.name;
this.div.setAttribute("id", this.id);
this.div.tabIndex = this.#disabled ? -1 : 0;
this.div.classList.add("hidden");
this.div.addEventListener("focusin", this.#boundFocusin);
this.div.addEventListener("focusout", this.#boundFocusout);
const [parentWidth, parentHeight] = this.parentDimensions;
if (this.parentRotation % 180 !== 0) {
this.div.style.maxWidth = `${(100 * parentHeight / parentWidth).toFixed(2)}%`;
this.div.style.maxHeight = `${(100 * parentWidth / parentHeight).toFixed(2)}%`;
const [tx, ty] = this.getInitialTranslation();
bindEvents(this, this.div, ["pointerdown"]);
} = util_FeatureTest.platform;
if (event.button !== 0 || event.ctrlKey && isMac) {
this.#hasBeenClicked = true;
this.#setUpDragSession(event);
this.#selectOnPointerEvent(event);
#selectOnPointerEvent(event) {
} = util_FeatureTest.platform;
if (event.ctrlKey && !isMac || event.shiftKey || event.metaKey && isMac) {
this.parent.toggleSelected(this);
this.parent.setSelected(this);
#setUpDragSession(event) {
const isSelected = this._uiManager.isSelected(this);
this._uiManager.setUpDragSession();
let pointerMoveOptions, pointerMoveCallback;
this.div.classList.add("moving");
this.#prevDragX = event.clientX;
this.#prevDragY = event.clientY;
pointerMoveCallback = e => {
const [tx, ty] = this.screenToPageTranslation(x - this.#prevDragX, y - this.#prevDragY);
this._uiManager.dragSelectedEditors(tx, ty);
window.addEventListener("pointermove", pointerMoveCallback, pointerMoveOptions);
const pointerUpCallback = () => {
window.removeEventListener("pointerup", pointerUpCallback);
window.removeEventListener("blur", pointerUpCallback);
this.div.classList.remove("moving");
window.removeEventListener("pointermove", pointerMoveCallback, pointerMoveOptions);
this.#hasBeenClicked = false;
if (!this._uiManager.endDragSession()) {
this.#selectOnPointerEvent(event);