diff --git a/src/display/editor/annotation_editor_layer.js b/src/display/editor/annotation_editor_layer.js index 1cb53569e..c25b4a0a5 100644 --- a/src/display/editor/annotation_editor_layer.js +++ b/src/display/editor/annotation_editor_layer.js @@ -464,6 +464,31 @@ class AnnotationEditorLayer { return null; } + /** + * Paste some content into a new editor. + * @param {number} mode + * @param {Object} params + */ + pasteEditor(mode, params) { + this.#uiManager.updateToolbar(mode); + this.#uiManager.updateMode(mode); + + const { offsetX, offsetY } = this.#getCenterPoint(); + const id = this.getNextId(); + const editor = this.#createNewEditor({ + parent: this, + id, + x: offsetX, + y: offsetY, + uiManager: this.#uiManager, + isCentered: true, + ...params, + }); + if (editor) { + this.add(editor); + } + } + /** * Create a new editor * @param {Object} data @@ -504,10 +529,7 @@ class AnnotationEditorLayer { return editor; } - /** - * Create and add a new editor. - */ - addNewEditor() { + #getCenterPoint() { const { x, y, width, height } = this.div.getBoundingClientRect(); const tlX = Math.max(0, x); const tlY = Math.max(0, y); @@ -520,11 +542,15 @@ class AnnotationEditorLayer { ? [centerX, centerY] : [centerY, centerX]; + return { offsetX, offsetY }; + } + + /** + * Create and add a new editor. + */ + addNewEditor() { this.#createAndAddNewEditor( - { - offsetX, - offsetY, - }, + this.#getCenterPoint(), /* isCentered = */ true ); } diff --git a/src/display/editor/editor.js b/src/display/editor/editor.js index 45895b26a..55d531c52 100644 --- a/src/display/editor/editor.js +++ b/src/display/editor/editor.js @@ -140,6 +140,26 @@ class AnnotationEditor { return []; } + /** + * Check if this kind of editor is able to handle the given mime type for + * pasting. + * @param {string} mime + * @returns {boolean} + */ + static isHandlingMimeForPasting(_mime) { + return false; + } + + /** + * Extract the data from the clipboard item and delegate the creation of the + * editor to the parent. + * @param {DataTransferItem} item + * @param {AnnotationEditorLayer} parent + */ + static paste(item, parent) { + unreachable("Not implemented"); + } + /** * Get the properties to update in the UI for this editor. * @returns {Array} diff --git a/src/display/editor/stamp.js b/src/display/editor/stamp.js index ffb4a99ee..00352c72f 100644 --- a/src/display/editor/stamp.js +++ b/src/display/editor/stamp.js @@ -30,6 +30,8 @@ class StampEditor extends AnnotationEditor { #bitmapUrl = null; + #bitmapFile = null; + #canvas = null; #observer = null; @@ -45,6 +47,7 @@ class StampEditor extends AnnotationEditor { constructor(params) { super({ ...params, name: "stampEditor" }); this.#bitmapUrl = params.bitmapUrl; + this.#bitmapFile = params.bitmapFile; } static get supportedTypes() { @@ -64,10 +67,26 @@ class StampEditor extends AnnotationEditor { return shadow( this, "supportedTypes", - types.map(type => `image/${type}`).join(",") + types.map(type => `image/${type}`) ); } + static get supportedTypesStr() { + return shadow(this, "supportedTypesStr", this.supportedTypes.join(",")); + } + + /** @inheritdoc */ + static isHandlingMimeForPasting(mime) { + return this.supportedTypes.includes(mime); + } + + /** @inheritdoc */ + static paste(item, parent) { + parent.pasteEditor(AnnotationEditorType.STAMP, { + bitmapFile: item.getAsFile(), + }); + } + #getBitmapDone() { this._uiManager.enableWaiting(false); if (this.#canvas) { @@ -115,6 +134,29 @@ class StampEditor extends AnnotationEditor { return; } + if (this.#bitmapFile) { + const file = this.#bitmapFile; + this.#bitmapFile = null; + this._uiManager.enableWaiting(true); + this.#bitmapPromise = this._uiManager.imageManager + .getFromFile(file) + .then(data => { + this.#bitmapPromise = null; + if (!data) { + this.remove(); + return; + } + ({ + bitmap: this.#bitmap, + id: this.#bitmapId, + isSvg: this.#isSvg, + } = data); + this.#createCanvas(); + }) + .finally(() => this.#getBitmapDone()); + return; + } + const input = document.createElement("input"); if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) { input.hidden = true; @@ -122,7 +164,7 @@ class StampEditor extends AnnotationEditor { document.body.append(input); } input.type = "file"; - input.accept = StampEditor.supportedTypes; + input.accept = StampEditor.supportedTypesStr; this.#bitmapPromise = new Promise(resolve => { input.addEventListener("change", async () => { this.#bitmapPromise = null; diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index 37b05861c..f0e3ade4e 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -919,8 +919,17 @@ class AnnotationEditorUIManager { */ paste(event) { event.preventDefault(); + const { clipboardData } = event; + for (const item of clipboardData.items) { + for (const editorType of this.#editorTypes) { + if (editorType.isHandlingMimeForPasting(item.type)) { + editorType.paste(item, this.currentLayer); + return; + } + } + } - let data = event.clipboardData.getData("application/pdfjs"); + let data = clipboardData.getData("application/pdfjs"); if (!data) { return; } @@ -1099,6 +1108,9 @@ class AnnotationEditorUIManager { * @param {string|null} editId */ updateMode(mode, editId = null) { + if (this.#mode === mode) { + return; + } this.#mode = mode; if (mode === AnnotationEditorType.NONE) { this.setEditingState(false);