Merge pull request #16793 from calixteman/editor_resize_rotated

[Editor] Fix the resizing of an editor when it's rotated (bug 1847268)
This commit is contained in:
calixteman 2023-08-08 18:24:20 +02:00 committed by GitHub
commit e914870c14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 448 additions and 313 deletions

View File

@ -27,6 +27,7 @@ import { mkdirp } from "mkdirp";
import path from "path";
import postcss from "gulp-postcss";
import postcssDirPseudoClass from "postcss-dir-pseudo-class";
import postcssNesting from "postcss-nesting";
import { preprocessPDFJSCode } from "./external/builder/preprocessor2.mjs";
import rename from "gulp-rename";
import replace from "gulp-replace";
@ -979,7 +980,11 @@ function buildGeneric(defines, dir) {
preprocessHTML("web/viewer.html", defines).pipe(gulp.dest(dir + "web")),
preprocessCSS("web/viewer.css", defines)
.pipe(
postcss([postcssDirPseudoClass(), autoprefixer(AUTOPREFIXER_CONFIG)])
postcss([
postcssDirPseudoClass(),
postcssNesting(),
autoprefixer(AUTOPREFIXER_CONFIG),
])
)
.pipe(gulp.dest(dir + "web")),
@ -1062,7 +1067,11 @@ function buildComponents(defines, dir) {
gulp.src(COMPONENTS_IMAGES).pipe(gulp.dest(dir + "images")),
preprocessCSS("web/pdf_viewer.css", defines)
.pipe(
postcss([postcssDirPseudoClass(), autoprefixer(AUTOPREFIXER_CONFIG)])
postcss([
postcssDirPseudoClass(),
postcssNesting(),
autoprefixer(AUTOPREFIXER_CONFIG),
])
)
.pipe(gulp.dest(dir)),
]);
@ -1154,7 +1163,11 @@ function buildMinified(defines, dir) {
preprocessHTML("web/viewer.html", defines).pipe(gulp.dest(dir + "web")),
preprocessCSS("web/viewer.css", defines)
.pipe(
postcss([postcssDirPseudoClass(), autoprefixer(AUTOPREFIXER_CONFIG)])
postcss([
postcssDirPseudoClass(),
postcssNesting(),
autoprefixer(AUTOPREFIXER_CONFIG),
])
)
.pipe(gulp.dest(dir + "web")),
@ -1495,6 +1508,7 @@ gulp.task(
.pipe(
postcss([
postcssDirPseudoClass(),
postcssNesting(),
autoprefixer(AUTOPREFIXER_CONFIG),
])
)

27
package-lock.json generated
View File

@ -48,6 +48,7 @@
"pngjs": "^7.0.0",
"postcss": "^8.4.27",
"postcss-dir-pseudo-class": "^8.0.0",
"postcss-nesting": "^12.0.1",
"prettier": "^3.0.1",
"puppeteer": "^21.0.1",
"rimraf": "^3.0.2",
@ -16730,6 +16731,32 @@
}
}
},
"node_modules/postcss-nesting": {
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.1.tgz",
"integrity": "sha512-6LCqCWP9pqwXw/njMvNK0hGY44Fxc4B2EsGbn6xDcxbNRzP8GYoxT7yabVVMLrX3quqOJ9hg2jYMsnkedOf8pA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"dependencies": {
"@csstools/selector-specificity": "^3.0.0",
"postcss-selector-parser": "^6.0.13"
},
"engines": {
"node": "^14 || ^16 || >=18"
},
"peerDependencies": {
"postcss": "^8.4"
}
},
"node_modules/postcss-resolve-nested-selector": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",

View File

@ -41,6 +41,7 @@
"pngjs": "^7.0.0",
"postcss": "^8.4.27",
"postcss-dir-pseudo-class": "^8.0.0",
"postcss-nesting": "^12.0.1",
"prettier": "^3.0.1",
"puppeteer": "^21.0.1",
"rimraf": "^3.0.2",

View File

@ -18,13 +18,8 @@
// eslint-disable-next-line max-len
/** @typedef {import("./tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
import {
AnnotationEditorParamsType,
FeatureTest,
shadow,
unreachable,
} from "../../shared/util.js";
import { bindEvents, ColorManager } from "./tools.js";
import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
/**
* @typedef {Object} AnnotationEditorParameters
@ -43,8 +38,6 @@ class AnnotationEditor {
#resizersDiv = null;
#resizePosition = null;
#boundFocusin = this.focusin.bind(this);
#boundFocusout = this.focusout.bind(this);
@ -328,13 +321,8 @@ class AnnotationEditor {
this.div.style.top = `${(100 * this.y).toFixed(2)}%`;
}
/**
* Convert a screen translation into a page one.
* @param {number} x
* @param {number} y
*/
screenToPageTranslation(x, y) {
switch (this.parentRotation) {
static #rotatePoint(x, y, angle) {
switch (angle) {
case 90:
return [y, -x];
case 180:
@ -346,21 +334,38 @@ class AnnotationEditor {
}
}
/**
* Convert a screen translation into a page one.
* @param {number} x
* @param {number} y
*/
screenToPageTranslation(x, y) {
return AnnotationEditor.#rotatePoint(x, y, this.parentRotation);
}
/**
* Convert a page translation into a screen one.
* @param {number} x
* @param {number} y
*/
pageTranslationToScreen(x, y) {
switch (this.parentRotation) {
case 90:
return [-y, x];
return AnnotationEditor.#rotatePoint(x, y, 360 - this.parentRotation);
}
#getRotationMatrix(rotation) {
switch (rotation) {
case 90: {
const [pageWidth, pageHeight] = this.pageDimensions;
return [0, -pageWidth / pageHeight, pageHeight / pageWidth, 0];
}
case 180:
return [-x, -y];
case 270:
return [y, -x];
return [-1, 0, 0, -1];
case 270: {
const [pageWidth, pageHeight] = this.pageDimensions;
return [0, pageWidth / pageHeight, -pageHeight / pageWidth, 0];
}
default:
return [x, y];
return [1, 0, 0, 1];
}
}
@ -449,26 +454,26 @@ class AnnotationEditor {
#resizerPointerdown(name, event) {
event.preventDefault();
this.#resizePosition = [event.clientX, event.clientY];
const boundResizerPointermove = this.#resizerPointermove.bind(this, name);
const savedDraggable = this._isDraggable;
this._isDraggable = false;
const resizingClassName = `resizing${name
.charAt(0)
.toUpperCase()}${name.slice(1)}`;
this.parent.div.classList.add(resizingClassName);
const pointerMoveOptions = { passive: true, capture: true };
window.addEventListener(
"pointermove",
boundResizerPointermove,
pointerMoveOptions
);
const savedX = this.x;
const savedY = this.y;
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 = () => {
// Stop the undo accumulation in order to have an undo action for each
// resize session.
this._uiManager.stopUndoAccumulation();
this._isDraggable = savedDraggable;
this.parent.div.classList.remove(resizingClassName);
window.removeEventListener("pointerup", pointerUpCallback);
window.removeEventListener("blur", pointerUpCallback);
window.removeEventListener(
@ -476,19 +481,53 @@ class AnnotationEditor {
boundResizerPointermove,
pointerMoveOptions
);
this.parent.div.style.cursor = savedParentCursor;
this.div.style.cursor = savedCursor;
const newX = this.x;
const newY = this.y;
const newWidth = this.width;
const newHeight = this.height;
if (
newX === savedX &&
newY === savedY &&
newWidth === savedWidth &&
newHeight === savedHeight
) {
return;
}
this.addCommands({
cmd: () => {
this.width = newWidth;
this.height = newHeight;
this.x = newX;
this.y = newY;
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(parentWidth * newWidth, parentHeight * newHeight);
this.fixAndSetPosition();
this.parent.moveEditorInDOM(this);
},
undo: () => {
this.width = savedWidth;
this.height = savedHeight;
this.x = savedX;
this.y = savedY;
const [parentWidth, parentHeight] = this.parentDimensions;
this.setDims(parentWidth * savedWidth, parentHeight * savedHeight);
this.fixAndSetPosition();
this.parent.moveEditorInDOM(this);
},
mustExec: true,
});
};
window.addEventListener("pointerup", pointerUpCallback);
// If the user switch to another window (with alt+tab), then we end the
// If the user switches to another window (with alt+tab), then we end the
// resize session.
window.addEventListener("blur", pointerUpCallback);
}
#resizerPointermove(name, event) {
const { clientX, clientY } = event;
const deltaX = clientX - this.#resizePosition[0];
const deltaY = clientY - this.#resizePosition[1];
this.#resizePosition[0] = clientX;
this.#resizePosition[1] = clientY;
const [parentWidth, parentHeight] = this.parentDimensions;
const savedX = this.x;
const savedY = this.y;
@ -496,204 +535,124 @@ class AnnotationEditor {
const savedHeight = this.height;
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
let cmd;
// 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 updatePosition = (width, height) => {
// We must take the parent dimensions as they are when undo/redo.
const [pWidth, pHeight] = this.parentDimensions;
this.setDims(pWidth * width, pHeight * height);
this.fixAndSetPosition();
};
const undo = () => {
this.width = savedWidth;
this.height = savedHeight;
this.x = savedX;
this.y = savedY;
updatePosition(savedWidth, savedHeight);
};
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 getPoint;
let getOpposite;
let isDiagonal = false;
let isHorizontal = false;
switch (name) {
case "topLeft": {
if (Math.sign(deltaX) * Math.sign(deltaY) < 0) {
return;
}
const dist = Math.hypot(deltaX, deltaY);
const oldDiag = Math.hypot(
savedWidth * parentWidth,
savedHeight * parentHeight
);
const brX = round(savedX + savedWidth);
const brY = round(savedY + savedHeight);
const ratio = Math.max(
Math.min(
1 - Math.sign(deltaX) * (dist / oldDiag),
// Avoid the editor to be larger than the page.
1 / savedWidth,
1 / savedHeight
),
// Avoid the editor to be smaller than the minimum size.
minWidth / savedWidth,
minHeight / savedHeight
);
const newWidth = round(savedWidth * ratio);
const newHeight = round(savedHeight * ratio);
const newX = brX - newWidth;
const newY = brY - newHeight;
cmd = () => {
this.width = newWidth;
this.height = newHeight;
this.x = newX;
this.y = newY;
updatePosition(newWidth, newHeight);
};
case "topLeft":
isDiagonal = true;
getPoint = (w, h) => [0, 0];
getOpposite = (w, h) => [w, h];
break;
}
case "topMiddle": {
const bmY = round(this.y + savedHeight);
const newHeight = round(
Math.max(minHeight, Math.min(1, savedHeight - deltaY / parentHeight))
);
const newY = bmY - newHeight;
cmd = () => {
this.height = newHeight;
this.y = newY;
updatePosition(savedWidth, newHeight);
};
case "topMiddle":
getPoint = (w, h) => [w / 2, 0];
getOpposite = (w, h) => [w / 2, h];
break;
}
case "topRight": {
if (Math.sign(deltaX) * Math.sign(deltaY) > 0) {
return;
}
const dist = Math.hypot(deltaX, deltaY);
const oldDiag = Math.hypot(
this.width * parentWidth,
this.height * parentHeight
);
const blY = round(savedY + this.height);
const ratio = Math.max(
Math.min(
1 + Math.sign(deltaX) * (dist / oldDiag),
1 / savedWidth,
1 / savedHeight
),
minWidth / savedWidth,
minHeight / savedHeight
);
const newWidth = round(savedWidth * ratio);
const newHeight = round(savedHeight * ratio);
const newY = blY - newHeight;
cmd = () => {
this.width = newWidth;
this.height = newHeight;
this.y = newY;
updatePosition(newWidth, newHeight);
};
case "topRight":
isDiagonal = true;
getPoint = (w, h) => [w, 0];
getOpposite = (w, h) => [0, h];
break;
}
case "middleRight": {
const newWidth = round(
Math.max(minWidth, Math.min(1, savedWidth + deltaX / parentWidth))
);
cmd = () => {
this.width = newWidth;
updatePosition(newWidth, savedHeight);
};
case "middleRight":
isHorizontal = true;
getPoint = (w, h) => [w, h / 2];
getOpposite = (w, h) => [0, h / 2];
break;
}
case "bottomRight": {
if (Math.sign(deltaX) * Math.sign(deltaY) < 0) {
return;
}
const dist = Math.hypot(deltaX, deltaY);
const oldDiag = Math.hypot(
this.width * parentWidth,
this.height * parentHeight
);
const ratio = Math.max(
Math.min(
1 + Math.sign(deltaX) * (dist / oldDiag),
1 / savedWidth,
1 / savedHeight
),
minWidth / savedWidth,
minHeight / savedHeight
);
const newWidth = round(savedWidth * ratio);
const newHeight = round(savedHeight * ratio);
cmd = () => {
this.width = newWidth;
this.height = newHeight;
updatePosition(newWidth, newHeight);
};
case "bottomRight":
isDiagonal = true;
getPoint = (w, h) => [w, h];
getOpposite = (w, h) => [0, 0];
break;
}
case "bottomMiddle": {
const newHeight = round(
Math.max(minHeight, Math.min(1, savedHeight + deltaY / parentHeight))
);
cmd = () => {
this.height = newHeight;
updatePosition(savedWidth, newHeight);
};
case "bottomMiddle":
getPoint = (w, h) => [w / 2, h];
getOpposite = (w, h) => [w / 2, 0];
break;
}
case "bottomLeft": {
if (Math.sign(deltaX) * Math.sign(deltaY) > 0) {
return;
}
const dist = Math.hypot(deltaX, deltaY);
const oldDiag = Math.hypot(
this.width * parentWidth,
this.height * parentHeight
);
const trX = round(savedX + this.width);
const ratio = Math.max(
Math.min(
1 - Math.sign(deltaX) * (dist / oldDiag),
1 / savedWidth,
1 / savedHeight
),
minWidth / savedWidth,
minHeight / savedHeight
);
const newWidth = round(savedWidth * ratio);
const newHeight = round(savedHeight * ratio);
const newX = trX - newWidth;
cmd = () => {
this.width = newWidth;
this.height = newHeight;
this.x = newX;
updatePosition(newWidth, newHeight);
};
case "bottomLeft":
isDiagonal = true;
getPoint = (w, h) => [0, h];
getOpposite = (w, h) => [w, 0];
break;
}
case "middleLeft": {
const mrX = round(savedX + savedWidth);
const newWidth = round(
Math.max(minWidth, Math.min(1, savedWidth - deltaX / parentWidth))
);
const newX = mrX - newWidth;
cmd = () => {
this.width = newWidth;
this.x = newX;
updatePosition(newWidth, savedHeight);
};
case "middleLeft":
isHorizontal = true;
getPoint = (w, h) => [0, h / 2];
getOpposite = (w, h) => [w, h / 2];
break;
}
}
this.addCommands({
cmd,
undo,
mustExec: true,
type: AnnotationEditorParamsType.RESIZE,
overwriteIfSameType: true,
keepUndo: true,
});
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 ratioX = 1;
let ratioY = 1;
let [deltaX, deltaY] = this.screenToPageTranslation(
event.movementX,
event.movementY
);
[deltaX, deltaY] = invTransf(deltaX / parentWidth, deltaY / parentHeight);
if (isDiagonal) {
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,
// Avoid the editor to be larger than the page.
1 / savedWidth,
1 / savedHeight
),
// Avoid the editor to be smaller than the minimum size.
minWidth / savedWidth,
minHeight / savedHeight
);
} else if (isHorizontal) {
ratioX =
Math.max(
minWidth,
Math.min(1, Math.abs(oppositePoint[0] - point[0] - deltaX))
) / savedWidth;
} else {
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.width = newWidth;
this.height = newHeight;
this.x = newX;
this.y = newY;
this.setDims(parentWidth * newWidth, parentHeight * newHeight);
this.fixAndSetPosition();
}
/**

View File

@ -305,12 +305,6 @@ class CommandManager {
this.#commands.push(save);
}
stopUndoAccumulation() {
if (this.#position !== -1) {
this.#commands[this.#position].type = NaN;
}
}
/**
* Undo the last command.
*/
@ -1294,10 +1288,6 @@ class AnnotationEditorUIManager {
return this.#selectedEditors.size !== 0;
}
stopUndoAccumulation() {
this.#commandManager.stopUndoAccumulation();
}
/**
* Undo the last command.
*/

View File

@ -18,6 +18,7 @@ const {
getEditorDimensions,
loadAndWait,
serializeBitmapDimensions,
waitForAnnotationEditorLayer,
} = require("./test_utils.js");
const path = require("path");
@ -37,9 +38,8 @@ describe("Stamp Editor", () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
if (browserName === "firefox") {
pending(
"Disabled in Firefox, because of https://bugzilla.mozilla.org/1553847."
);
// Disabled in Firefox, because of https://bugzilla.mozilla.org/1553847.
return;
}
await page.click("#editorStamp");
@ -59,12 +59,11 @@ describe("Stamp Editor", () => {
await page.waitForTimeout(300);
const { width, height } = await getEditorDimensions(page, 0);
const { width } = await getEditorDimensions(page, 0);
// The image is bigger than the page, so it has been scaled down to
// 75% of the page width.
expect(width).toEqual("75%");
expect(height).toEqual("auto");
const [bitmap] = await serializeBitmapDimensions(page);
expect(bitmap.width).toEqual(512);
@ -84,9 +83,8 @@ describe("Stamp Editor", () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
if (browserName === "firefox") {
pending(
"Disabled in Firefox, because of https://bugzilla.mozilla.org/1553847."
);
// Disabled in Firefox, because of https://bugzilla.mozilla.org/1553847.
return;
}
const rect = await page.$eval(".annotationEditorLayer", el => {
@ -104,10 +102,9 @@ describe("Stamp Editor", () => {
await page.waitForTimeout(300);
const { width, height } = await getEditorDimensions(page, 1);
const { width } = await getEditorDimensions(page, 1);
expect(Math.round(parseFloat(width))).toEqual(40);
expect(height).toEqual("auto");
const [bitmap] = await serializeBitmapDimensions(page);
// The original size is 80x242 but to increase the resolution when it
@ -144,9 +141,8 @@ describe("Stamp Editor", () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
if (browserName === "firefox") {
pending(
"Disabled in Firefox, because of https://bugzilla.mozilla.org/1553847."
);
// Disabled in Firefox, because of https://bugzilla.mozilla.org/1553847.
return;
}
await page.click("#editorStamp");
@ -175,4 +171,98 @@ describe("Stamp Editor", () => {
);
});
});
describe("Resize", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer", 50);
});
afterAll(async () => {
await closePages(pages);
});
it("must check that an added image stay within the page", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
if (browserName === "firefox") {
// Disabled in Firefox, because of https://bugzilla.mozilla.org/1553847.
return;
}
await page.click("#editorStamp");
const names = ["bottomLeft", "bottomRight", "topRight", "topLeft"];
for (let i = 0; i < 4; i++) {
if (i !== 0) {
await page.keyboard.down("Control");
await page.keyboard.press("a");
await page.keyboard.up("Control");
await page.waitForTimeout(10);
await page.keyboard.press("Backspace");
await page.waitForTimeout(10);
}
const rect = await page.$eval(".annotationEditorLayer", el => {
// With Chrome something is wrong when serializing a DomRect,
// hence we extract the values and just return them.
const { x, y } = el.getBoundingClientRect();
return { x, y };
});
await page.mouse.click(rect.x + 10, rect.y + 10);
await page.waitForTimeout(10);
const input = await page.$("#stampEditorFileInput");
await input.uploadFile(
`${path.join(__dirname, "../images/firefox_logo.png")}`
);
await page.waitForTimeout(300);
for (let j = 0; j < 4; j++) {
await page.keyboard.press("Escape");
await page.waitForFunction(
`getComputedStyle(document.querySelector(".resizers")).display === "none"`
);
const promise = waitForAnnotationEditorLayer(page);
await page.evaluate(() => {
window.PDFViewerApplication.rotatePages(90);
});
await promise;
await page.focus(".stampEditor");
await page.waitForFunction(
`getComputedStyle(document.querySelector(".resizers")).display === "block"`
);
await page.waitForTimeout(10);
const [name, cursor] = await page.evaluate(() => {
const { x, y } = document
.querySelector(".stampEditor")
.getBoundingClientRect();
const el = document.elementFromPoint(x, y);
const cornerName = Array.from(el.classList).find(
c => c !== "resizer"
);
return [cornerName, window.getComputedStyle(el).cursor];
});
expect(name).withContext(`In ${browserName}`).toEqual(names[j]);
expect(cursor)
.withContext(`In ${browserName}`)
.toEqual("nwse-resize");
}
const promise = waitForAnnotationEditorLayer(page);
await page.evaluate(() => {
window.PDFViewerApplication.rotatePages(90);
});
await promise;
}
})
);
});
});
});

View File

@ -202,3 +202,15 @@ async function dragAndDropAnnotation(page, startX, startY, tX, tY) {
await page.mouse.up();
}
exports.dragAndDropAnnotation = dragAndDropAnnotation;
async function waitForAnnotationEditorLayer(page) {
return page.evaluate(() => {
return new Promise(resolve => {
window.PDFViewerApplication.eventBus.on(
"annotationeditorlayerrendered",
resolve
);
});
});
}
exports.waitForAnnotationEditorLayer = waitForAnnotationEditorLayer;

View File

@ -186,86 +186,128 @@
height: 100%;
}
.annotationEditorLayer .resizers {
width: 100%;
height: 100%;
position: absolute;
inset: 0;
}
.annotationEditorLayer {
:is(.freeTextEditor, .inkEditor, .stampEditor) {
& > .resizers {
width: 100%;
height: 100%;
position: absolute;
inset: 0;
.annotationEditorLayer .resizers.hidden {
display: none;
}
&.hidden {
display: none;
}
.annotationEditorLayer .resizer {
width: var(--resizer-size);
height: var(--resizer-size);
border-radius: 50%;
background: var(--resizer-color);
border: var(--focus-outline);
position: absolute;
}
& > .resizer {
width: var(--resizer-size);
height: var(--resizer-size);
border-radius: 50%;
background: var(--resizer-color);
border: var(--focus-outline);
position: absolute;
.annotationEditorLayer .resizer.topLeft {
cursor: nwse-resize;
top: var(--resizer-shift);
left: var(--resizer-shift);
}
&.topLeft {
top: var(--resizer-shift);
left: var(--resizer-shift);
}
.annotationEditorLayer .resizer.topMiddle {
cursor: ns-resize;
top: var(--resizer-shift);
left: calc(50% + var(--resizer-shift));
}
&.topMiddle {
top: var(--resizer-shift);
left: calc(50% + var(--resizer-shift));
}
.annotationEditorLayer .resizer.topRight {
cursor: nesw-resize;
top: var(--resizer-shift);
right: var(--resizer-shift);
}
&.topRight {
top: var(--resizer-shift);
right: var(--resizer-shift);
}
.annotationEditorLayer .resizer.middleRight {
cursor: ew-resize;
top: calc(50% + var(--resizer-shift));
right: var(--resizer-shift);
}
&.middleRight {
top: calc(50% + var(--resizer-shift));
right: var(--resizer-shift);
}
.annotationEditorLayer .resizer.bottomRight {
cursor: nwse-resize;
bottom: var(--resizer-shift);
right: var(--resizer-shift);
}
&.bottomRight {
bottom: var(--resizer-shift);
right: var(--resizer-shift);
}
.annotationEditorLayer .resizer.bottomMiddle {
cursor: ns-resize;
bottom: var(--resizer-shift);
left: calc(50% + var(--resizer-shift));
}
&.bottomMiddle {
bottom: var(--resizer-shift);
left: calc(50% + var(--resizer-shift));
}
.annotationEditorLayer .resizer.bottomLeft {
cursor: nesw-resize;
bottom: var(--resizer-shift);
left: var(--resizer-shift);
}
&.bottomLeft {
bottom: var(--resizer-shift);
left: var(--resizer-shift);
}
.annotationEditorLayer .resizer.middleLeft {
cursor: ew-resize;
top: calc(50% + var(--resizer-shift));
left: var(--resizer-shift);
}
&.middleLeft {
top: calc(50% + var(--resizer-shift));
left: var(--resizer-shift);
}
}
}
}
.annotationEditorLayer:is(.resizingTopLeft, .resizingBottomRight) {
cursor: nwse-resize;
}
&[data-main-rotation="0"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]),
&[data-main-rotation="90"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]),
&[data-main-rotation="180"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]),
&[data-main-rotation="270"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]) {
& > .resizers > .resizer {
&.topLeft,
&.bottomRight {
cursor: nwse-resize;
}
.annotationEditorLayer:is(.resizingTopMiddle, .resizingBottomMiddle) {
cursor: ns-resize;
}
&.topMiddle,
&.bottomMiddle {
cursor: ns-resize;
}
.annotationEditorLayer:is(.resizingTopRight, .resizingBottomLeft) {
cursor: nesw-resize;
}
&.topRight,
&.bottomLeft {
cursor: nesw-resize;
}
.annotationEditorLayer:is(.resizingMiddleRight, .resizingMiddleLeft) {
cursor: ew-resize;
&.middleRight,
&.middleLeft {
cursor: ew-resize;
}
}
}
&[data-main-rotation="0"]
:is([data-editor-rotation="90"], [data-editor-rotation="270"]),
&[data-main-rotation="90"]
:is([data-editor-rotation="0"], [data-editor-rotation="180"]),
&[data-main-rotation="180"]
:is([data-editor-rotation="270"], [data-editor-rotation="90"]),
&[data-main-rotation="270"]
:is([data-editor-rotation="180"], [data-editor-rotation="0"]) {
& > .resizers > .resizer {
&.topLeft,
&.bottomRight {
cursor: nesw-resize;
}
&.topMiddle,
&.bottomMiddle {
cursor: ew-resize;
}
&.topRight,
&.bottomLeft {
cursor: nwse-resize;
}
&.middleRight,
&.middleLeft {
cursor: ns-resize;
}
}
}
}