diff --git a/src/display/editor/annotation_editor_layer.js b/src/display/editor/annotation_editor_layer.js index e7f404158d5ca..e2d13d65f5e78 100644 --- a/src/display/editor/annotation_editor_layer.js +++ b/src/display/editor/annotation_editor_layer.js @@ -679,14 +679,35 @@ class AnnotationEditorLayer { /** * SelectionChange callback. - * @param {Event} _event + * @param {Event} event */ - selectionStart(_event) { - this.#textLayer?.div.addEventListener( - "pointerup", - this.#boundPointerUpAfterSelection, - { once: true } - ); + selectionStart(event) { + if ( + !this.#textLayer || + event.target.parentElement.closest(".textLayer") !== this.#textLayer.div + ) { + return; + } + + if (this.#uiManager.isShiftKeyDown) { + const keyup = () => { + if (this.#uiManager.isShiftKeyDown) { + return; + } + + window.removeEventListener("keyup", keyup); + window.removeEventListener("blur", keyup); + this.pointerUpAfterSelection({}); + }; + window.addEventListener("keyup", keyup); + window.addEventListener("blur", keyup); + } else { + this.#textLayer.div.addEventListener( + "pointerup", + this.#boundPointerUpAfterSelection, + { once: true } + ); + } } /** diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index ffb7881ca9435..0b787e1d7b8a2 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -581,6 +581,8 @@ class AnnotationEditorUIManager { #boundKeydown = this.keydown.bind(this); + #boundKeyup = this.keyup.bind(this); + #boundOnEditingAction = this.onEditingAction.bind(this); #boundOnPageChanging = this.onPageChanging.bind(this); @@ -765,6 +767,7 @@ class AnnotationEditorUIManager { realScale: PixelsPerInch.PDF_TO_CSS_UNITS, rotation: 0, }; + this.isShiftKeyDown = false; } destroy() { @@ -915,6 +918,7 @@ class AnnotationEditorUIManager { } blur() { + this.isShiftKeyDown = false; if (!this.hasSelection) { return; } @@ -952,10 +956,12 @@ class AnnotationEditorUIManager { // The keyboard events are caught at the container level in order to be able // to execute some callbacks even if the current page doesn't have focus. window.addEventListener("keydown", this.#boundKeydown); + window.addEventListener("keyup", this.#boundKeyup); } #removeKeyboardManager() { window.removeEventListener("keydown", this.#boundKeydown); + window.removeEventListener("keyup", this.#boundKeyup); } #addCopyPasteListeners() { @@ -1084,11 +1090,24 @@ class AnnotationEditorUIManager { * @param {KeyboardEvent} event */ keydown(event) { + if (!this.isShiftKeyDown && event.key === "Shift") { + this.isShiftKeyDown = true; + } if (!this.isEditorHandlingKeyboard) { AnnotationEditorUIManager._keyboardManager.exec(this, event); } } + /** + * Keyup callback. + * @param {KeyboardEvent} event + */ + keyup(event) { + if (this.isShiftKeyDown && event.key === "Shift") { + this.isShiftKeyDown = false; + } + } + /** * Execute an action for a given name. * For example, the user can click on the "Undo" entry in the context menu diff --git a/test/integration/highlight_editor_spec.mjs b/test/integration/highlight_editor_spec.mjs index 6ee7be33ea3a8..b8ad568c73a42 100644 --- a/test/integration/highlight_editor_spec.mjs +++ b/test/integration/highlight_editor_spec.mjs @@ -681,4 +681,64 @@ describe("Highlight Editor", () => { ); }); }); + + describe("Highlight with the keyboard", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer"); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check that some text has been highlighted", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.click("#editorHighlight"); + await page.waitForSelector(".annotationEditorLayer.highlightEditing"); + const sel = getEditorSelector(0); + + const spanRect = await page.evaluate(() => { + const span = document.querySelector( + `.page[data-page-number="1"] > .textLayer > span` + ); + const { x, y, width, height } = span.getBoundingClientRect(); + return { x, y, width, height }; + }); + await page.keyboard.down("Shift"); + await page.mouse.click( + spanRect.x + 1, + spanRect.y + spanRect.height / 2, + { count: 2 } + ); + for (let i = 0; i < 6; i++) { + await page.keyboard.press("ArrowRight"); + } + await page.keyboard.press("ArrowDown"); + await page.keyboard.press("ArrowDown"); + await page.keyboard.up("Shift"); + + const [w, h] = await page.evaluate(s => { + const { + style: { width, height }, + } = document.querySelector(s); + return [parseFloat(width), parseFloat(height)]; + }, sel); + + // w & h are the width and height of the highlight in percent. + // We expect the highlight to be around 73% wide and 9% high. + // We allow a 2% margin of error because of the font used in the text + // layer we can't be sure of the dimensions. + expect(Math.abs(w - 73) <= 2) + .withContext(`In ${browserName}`) + .toBe(true); + expect(Math.abs(h - 9) <= 2) + .withContext(`In ${browserName}`) + .toBe(true); + }) + ); + }); + }); });