From 659fbc50201aa18d8f503e26a90786bff1881ecf Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Wed, 9 Aug 2023 14:07:15 +0200 Subject: [PATCH] [Editor] Add a button to explicitly add an image (bug 1848108) The main stamp button will be used to just enter in a add/edit image mode: - the user can add a new image in using the new button. - the user can edit an image in resizing, moving it. In image mode, when the user clicks outside on the page but not on an editor, then all the selected editors will be unselected. --- l10n/en-US/viewer.properties | 6 ++- src/display/editor/annotation_editor_layer.js | 41 ++++++++++++++-- src/display/editor/editor.js | 26 ++++++++++ src/display/editor/freetext.js | 4 ++ src/display/editor/stamp.js | 7 ++- src/display/editor/tools.js | 5 ++ src/shared/util.js | 1 + test/integration/stamp_editor_spec.js | 47 +++++++++---------- web/annotation_editor_params.js | 4 ++ web/toolbar.js | 7 ++- web/viewer.css | 10 ++++ web/viewer.html | 12 ++++- web/viewer.js | 4 ++ 13 files changed, 140 insertions(+), 34 deletions(-) diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index c820d8068..db404c896 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -245,8 +245,8 @@ editor_free_text2.title=Text editor_free_text2_label=Text editor_ink2.title=Draw editor_ink2_label=Draw -editor_stamp.title=Add an image -editor_stamp_label=Add an image +editor_stamp1.title=Add or edit images +editor_stamp1_label=Add or edit images free_text2_default_content=Start typing… @@ -256,6 +256,8 @@ editor_free_text_size=Size editor_ink_color=Color editor_ink_thickness=Thickness editor_ink_opacity=Opacity +editor_stamp_add_image_label=Add image +editor_stamp_add_image.title=Add image # Editor aria editor_free_text2_aria_label=Text Editor diff --git a/src/display/editor/annotation_editor_layer.js b/src/display/editor/annotation_editor_layer.js index 7c4121440..1cb53569e 100644 --- a/src/display/editor/annotation_editor_layer.js +++ b/src/display/editor/annotation_editor_layer.js @@ -166,7 +166,10 @@ class AnnotationEditorLayer { } } - const editor = this.#createAndAddNewEditor({ offsetX: 0, offsetY: 0 }); + const editor = this.#createAndAddNewEditor( + { offsetX: 0, offsetY: 0 }, + /* isCentered = */ false + ); editor.setInBackground(); } @@ -481,9 +484,10 @@ class AnnotationEditorLayer { /** * Create and add a new editor. * @param {PointerEvent} event + * @param {boolean} isCentered * @returns {AnnotationEditor} */ - #createAndAddNewEditor(event) { + #createAndAddNewEditor(event, isCentered) { const id = this.getNextId(); const editor = this.#createNewEditor({ parent: this, @@ -491,6 +495,7 @@ class AnnotationEditorLayer { x: event.offsetX, y: event.offsetY, uiManager: this.#uiManager, + isCentered, }); if (editor) { this.add(editor); @@ -499,6 +504,31 @@ class AnnotationEditorLayer { return editor; } + /** + * Create and add a new editor. + */ + addNewEditor() { + const { x, y, width, height } = this.div.getBoundingClientRect(); + const tlX = Math.max(0, x); + const tlY = Math.max(0, y); + const brX = Math.min(window.innerWidth, x + width); + const brY = Math.min(window.innerHeight, y + height); + const centerX = (tlX + brX) / 2 - x; + const centerY = (tlY + brY) / 2 - y; + const [offsetX, offsetY] = + this.viewport.rotation % 180 === 0 + ? [centerX, centerY] + : [centerY, centerX]; + + this.#createAndAddNewEditor( + { + offsetX, + offsetY, + }, + /* isCentered = */ true + ); + } + /** * Set the last selected editor. * @param {AnnotationEditor} editor @@ -560,7 +590,12 @@ class AnnotationEditorLayer { return; } - this.#createAndAddNewEditor(event); + if (this.#uiManager.getMode() === AnnotationEditorType.STAMP) { + this.#uiManager.unselectAll(); + return; + } + + this.#createAndAddNewEditor(event, /* isCentered = */ false); } /** diff --git a/src/display/editor/editor.js b/src/display/editor/editor.js index 3c3cae425..45895b26a 100644 --- a/src/display/editor/editor.js +++ b/src/display/editor/editor.js @@ -48,6 +48,8 @@ class AnnotationEditor { #isInEditMode = false; + _initialOptions = Object.create(null); + _uiManager = null; _focusEventsAllowed = true; @@ -77,6 +79,7 @@ class AnnotationEditor { this._uiManager = parameters.uiManager; this.annotationElementId = null; this._willKeepAspectRatio = false; + this._initialOptions.isCentered = parameters.isCentered; const { rotation, @@ -154,6 +157,29 @@ class AnnotationEditor { this.div?.classList.toggle("draggable", value); } + center() { + const [pageWidth, pageHeight] = this.pageDimensions; + switch (this.parentRotation) { + case 90: + this.x -= (this.height * pageHeight) / (pageWidth * 2); + this.y += (this.width * pageWidth) / (pageHeight * 2); + break; + case 180: + this.x += this.width / 2; + this.y += this.height / 2; + break; + case 270: + this.x += (this.height * pageHeight) / (pageWidth * 2); + this.y -= (this.width * pageWidth) / (pageHeight * 2); + break; + default: + this.x -= this.width / 2; + this.y -= this.height / 2; + break; + } + this.fixAndSetPosition(); + } + /** * Add some commands into the CommandManager (undo/redo stuff). * @param {Object} params diff --git a/src/display/editor/freetext.js b/src/display/editor/freetext.js index 08dd5f362..7e96d518c 100644 --- a/src/display/editor/freetext.js +++ b/src/display/editor/freetext.js @@ -363,6 +363,10 @@ class FreeTextEditor extends AnnotationEditor { } this.enableEditMode(); this.editorDiv.focus(); + if (this._initialOptions?.isCentered) { + this.center(); + } + this._initialOptions = null; } /** @inheritdoc */ diff --git a/src/display/editor/stamp.js b/src/display/editor/stamp.js index a9a55eaa2..d121724ed 100644 --- a/src/display/editor/stamp.js +++ b/src/display/editor/stamp.js @@ -290,7 +290,12 @@ class StampEditor extends AnnotationEditor { this.width = width / parentWidth; this.height = height / parentHeight; this.setDims(width, height); - this.fixAndSetPosition(); + if (this._initialOptions?.isCentered) { + this.center(); + } else { + this.fixAndSetPosition(); + } + this._initialOptions = null; if (this.#resizeTimeoutId !== null) { clearTimeout(this.#resizeTimeoutId); } diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index 380c12e69..7d041412e 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -18,6 +18,7 @@ /** @typedef {import("./annotation_editor_layer.js").AnnotationEditorLayer} AnnotationEditorLayer */ import { + AnnotationEditorParamsType, AnnotationEditorPrefix, AnnotationEditorType, FeatureTest, @@ -1144,6 +1145,10 @@ class AnnotationEditorUIManager { if (!this.#editorTypes) { return; } + if (type === AnnotationEditorParamsType.CREATE) { + this.currentLayer.addNewEditor(type); + return; + } for (const editor of this.#selectedEditors) { editor.updateParams(type, value); diff --git a/src/shared/util.js b/src/shared/util.js index 607fd2508..adce62c1d 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -78,6 +78,7 @@ const AnnotationEditorType = { const AnnotationEditorParamsType = { RESIZE: 1, + CREATE: 2, FREETEXT_SIZE: 11, FREETEXT_COLOR: 12, FREETEXT_OPACITY: 13, diff --git a/test/integration/stamp_editor_spec.js b/test/integration/stamp_editor_spec.js index 6fc3bfa44..228a2019f 100644 --- a/test/integration/stamp_editor_spec.js +++ b/test/integration/stamp_editor_spec.js @@ -15,7 +15,9 @@ const { closePages, + dragAndDropAnnotation, getEditorDimensions, + getEditorSelector, loadAndWait, serializeBitmapDimensions, waitForAnnotationEditorLayer, @@ -43,15 +45,8 @@ describe("Stamp Editor", () => { } await page.click("#editorStamp"); + await page.click("#editorStampAddImage"); - 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 + 100, rect.y + 100); const input = await page.$("#stampEditorFileInput"); await input.uploadFile( `${path.join(__dirname, "../images/firefox_logo.png")}` @@ -87,14 +82,7 @@ describe("Stamp Editor", () => { return; } - 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 + 100, rect.y + 100); + await page.click("#editorStampAddImage"); const input = await page.$("#stampEditorFileInput"); await input.uploadFile( `${path.join(__dirname, "../images/firefox_logo.svg")}` @@ -146,6 +134,7 @@ describe("Stamp Editor", () => { } await page.click("#editorStamp"); + await page.click("#editorStampAddImage"); const rect = await page.$eval(".annotationEditorLayer", el => { // With Chrome something is wrong when serializing a DomRect, @@ -154,7 +143,13 @@ describe("Stamp Editor", () => { return { x: right, y: bottom }; }); - await page.mouse.click(rect.x - 10, rect.y - 10); + const editorRect = await page.$eval(getEditorSelector(0), 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 }; + }); + const input = await page.$("#stampEditorFileInput"); await input.uploadFile( `${path.join(__dirname, "../images/firefox_logo.png")}` @@ -162,6 +157,15 @@ describe("Stamp Editor", () => { await page.waitForTimeout(300); + await dragAndDropAnnotation( + page, + editorRect.x + 10, + editorRect.y + 10, + rect.x - 10, + rect.y - 10 + ); + await page.waitForTimeout(10); + const { left } = await getEditorDimensions(page, 0); // The image is bigger than the page, so it has been scaled down to @@ -204,14 +208,7 @@ describe("Stamp Editor", () => { 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.click("#editorStampAddImage"); await page.waitForTimeout(10); const input = await page.$("#stampEditorFileInput"); await input.uploadFile( diff --git a/web/annotation_editor_params.js b/web/annotation_editor_params.js index 60e3560f6..ddfc17d1a 100644 --- a/web/annotation_editor_params.js +++ b/web/annotation_editor_params.js @@ -31,6 +31,7 @@ class AnnotationEditorParams { editorInkColor, editorInkThickness, editorInkOpacity, + editorStampAddImage, }) { const dispatchEvent = (typeStr, value) => { this.eventBus.dispatch("switchannotationeditorparams", { @@ -54,6 +55,9 @@ class AnnotationEditorParams { editorInkOpacity.addEventListener("input", function () { dispatchEvent("INK_OPACITY", this.valueAsNumber); }); + editorStampAddImage.addEventListener("click", () => { + dispatchEvent("CREATE"); + }); this.eventBus._on("annotationeditorparamschanged", evt => { for (const [type, value] of evt.details) { diff --git a/web/toolbar.js b/web/toolbar.js index 826f59733..051fabecc 100644 --- a/web/toolbar.js +++ b/web/toolbar.js @@ -218,6 +218,7 @@ class Toolbar { editorInkButton, editorInkParamsToolbar, editorStampButton, + editorStampParamsToolbar, }) { const editorModeChanged = ({ mode }) => { toggleCheckedBtn( @@ -230,7 +231,11 @@ class Toolbar { mode === AnnotationEditorType.INK, editorInkParamsToolbar ); - toggleCheckedBtn(editorStampButton, mode === AnnotationEditorType.STAMP); + toggleCheckedBtn( + editorStampButton, + mode === AnnotationEditorType.STAMP, + editorStampParamsToolbar + ); const isDisable = mode === AnnotationEditorType.DISABLE; editorFreeTextButton.disabled = isDisable; diff --git a/web/viewer.css b/web/viewer.css index 570d6c186..0caed2f1e 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -118,6 +118,7 @@ --secondaryToolbarButton-spreadOdd-icon: url(images/secondaryToolbarButton-spreadOdd.svg); --secondaryToolbarButton-spreadEven-icon: url(images/secondaryToolbarButton-spreadEven.svg); --secondaryToolbarButton-documentProperties-icon: url(images/secondaryToolbarButton-documentProperties.svg); + --editorParams-stampAddImage-icon: url(images/toolbarButton-zoomIn.svg); } :root:dir(rtl) { @@ -576,6 +577,11 @@ body { margin-bottom: -4px; } +#editorStampParamsToolbar { + inset-inline-end: 40px; + background-color: var(--toolbar-bg-color); +} + #editorInkParamsToolbar { inset-inline-end: 68px; background-color: var(--toolbar-bg-color); @@ -586,6 +592,10 @@ body { background-color: var(--toolbar-bg-color); } +#editorStampAddImage::before { + mask-image: var(--editorParams-stampAddImage-icon); +} + .doorHanger, .doorHangerRight { border-radius: 2px; diff --git a/web/viewer.html b/web/viewer.html index 09a5726bc..cfb73d529 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -198,6 +198,14 @@ See https://github.com/adobe-type-tools/cmap-resources + +