From ca3e45755cfa336b7be77eb8d386ebf931085beb Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Fri, 16 Jun 2023 13:00:00 +0200 Subject: [PATCH] [Editor] Avoid an exception when copying an existing editor --- src/display/editor/editor.js | 3 ++- src/display/editor/freetext.js | 11 ++++++-- src/display/editor/tools.js | 7 ++--- test/integration/freetext_editor_spec.js | 34 ++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/display/editor/editor.js b/src/display/editor/editor.js index 3052d6466..3751d8244 100644 --- a/src/display/editor/editor.js +++ b/src/display/editor/editor.js @@ -493,8 +493,9 @@ class AnnotationEditor { * new annotation to add to the pdf document. * * To implement in subclasses. + * @param {boolean} isForCopying */ - serialize() { + serialize(_isForCopying = false) { unreachable("An editor must be serializable"); } diff --git a/src/display/editor/freetext.js b/src/display/editor/freetext.js index 2eecb6080..53439912f 100644 --- a/src/display/editor/freetext.js +++ b/src/display/editor/freetext.js @@ -567,7 +567,7 @@ class FreeTextEditor extends AnnotationEditor { } /** @inheritdoc */ - serialize() { + serialize(isForCopying = false) { if (this.isEmpty()) { return null; } @@ -596,13 +596,20 @@ class FreeTextEditor extends AnnotationEditor { pageIndex: this.pageIndex, rect, rotation: this.rotation, - id: this.annotationElementId, }; + if (isForCopying) { + // Don't add the id when copying because the pasted editor mustn't be + // linked to an existing annotation. + return serialized; + } + if (this.annotationElementId && !this.#hasElementChanged(serialized)) { return null; } + serialized.id = this.annotationElementId; + return serialized; } diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index b9df8bb55..3bab75ca4 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -555,11 +555,8 @@ class AnnotationEditorUIManager { const editors = []; for (const editor of this.#selectedEditors) { - if (!editor.isEmpty()) { - const serialized = editor.serialize(); - // Remove the id from the serialized data because it mustn't be linked - // to an existing annotation. - delete serialized.id; + const serialized = editor.serialize(/* isForCopying = */ true); + if (serialized) { editors.push(serialized); } } diff --git a/test/integration/freetext_editor_spec.js b/test/integration/freetext_editor_spec.js index 373bd203c..2172cfd4c 100644 --- a/test/integration/freetext_editor_spec.js +++ b/test/integration/freetext_editor_spec.js @@ -1082,4 +1082,38 @@ describe("FreeText Editor", () => { ); }); }); + + describe("FreeText (copy/paste existing)", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("freetexts.pdf", ".annotationEditorLayer"); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must copy and paste an existing annotation", async () => { + // Run sequentially to avoid clipboard issues. + for (const [browserName, page] of pages) { + await page.click("#editorFreeText"); + + const editorIds = await getEditors(page, "freeText"); + expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6); + + const editorRect = await page.$eval(getEditorSelector(1), el => { + const { x, y, width, height } = el.getBoundingClientRect(); + return { x, y, width, height }; + }); + await page.mouse.click( + editorRect.x + editorRect.width / 2, + editorRect.y + editorRect.height / 2 + ); + + await copyPaste(page); + await waitForStorageEntries(page, 7); + } + }); + }); });