diff --git a/gulpfile.mjs b/gulpfile.mjs index 78e902878..067de1c95 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -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), ]) ) diff --git a/package-lock.json b/package-lock.json index 5a1eef363..5c6df4f71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 7b67bb445..6805792b0 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/display/editor/editor.js b/src/display/editor/editor.js index de82156c1..69662510f 100644 --- a/src/display/editor/editor.js +++ b/src/display/editor/editor.js @@ -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]; } } @@ -443,26 +448,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( @@ -470,19 +475,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; @@ -490,204 +529,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(); } /** diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index 147fe72b8..8fe5b175d 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -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. */ diff --git a/test/integration/stamp_editor_spec.js b/test/integration/stamp_editor_spec.js index eb2f3a52f..6fc3bfa44 100644 --- a/test/integration/stamp_editor_spec.js +++ b/test/integration/stamp_editor_spec.js @@ -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; + } + }) + ); + }); + }); }); diff --git a/test/integration/test_utils.js b/test/integration/test_utils.js index 0af11b327..1945b6cf5 100644 --- a/test/integration/test_utils.js +++ b/test/integration/test_utils.js @@ -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; diff --git a/web/annotation_editor_layer_builder.css b/web/annotation_editor_layer_builder.css index be457fc48..95eba5b02 100644 --- a/web/annotation_editor_layer_builder.css +++ b/web/annotation_editor_layer_builder.css @@ -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; + } + } + } }