Skip to content

Commit

Permalink
Merge pull request #18834 from calixteman/edit_stamp1
Browse files Browse the repository at this point in the history
[Editor] Make stamp annotations editable (bug 1921291)
  • Loading branch information
calixteman authored Oct 4, 2024
2 parents ebbd019 + 8410252 commit e9c81a6
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 18 deletions.
22 changes: 21 additions & 1 deletion src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4859,14 +4859,34 @@ class StrikeOutAnnotation extends MarkupAnnotation {
}

class StampAnnotation extends MarkupAnnotation {
#savedHasOwnCanvas;

constructor(params) {
super(params);

this.data.annotationType = AnnotationType.STAMP;
this.data.hasOwnCanvas = this.data.noRotate;
this.#savedHasOwnCanvas = this.data.hasOwnCanvas = this.data.noRotate;
this.data.isEditable = !this.data.noHTML;
// We want to be able to add mouse listeners to the annotation.
this.data.noHTML = false;
}

mustBeViewedWhenEditing(isEditing, modifiedIds = null) {
if (isEditing) {
if (!this.data.isEditable) {
return false;
}
// When we're editing, we want to ensure that the stamp annotation is
// drawn on a canvas in order to use it in the annotation editor layer.
this.#savedHasOwnCanvas = this.data.hasOwnCanvas;
this.data.hasOwnCanvas = true;
return true;
}
this.data.hasOwnCanvas = this.#savedHasOwnCanvas;

return !modifiedIds?.has(this.data.id);
}

static async createImage(bitmap, xref) {
// TODO: when printing, we could have a specific internal colorspace
// (e.g. something like DeviceRGBA) in order avoid any conversion (i.e. no
Expand Down
7 changes: 4 additions & 3 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2863,10 +2863,8 @@ class InkAnnotationElement extends AnnotationElement {
}

this.container.append(svg);
this._editOnDoubleClick();

if (this._isEditable) {
this._editOnDoubleClick();
}
return this.container;
}

Expand Down Expand Up @@ -2961,6 +2959,7 @@ class StrikeOutAnnotationElement extends AnnotationElement {
class StampAnnotationElement extends AnnotationElement {
constructor(parameters) {
super(parameters, { isRenderable: true, ignoreBorder: true });
this.annotationEditorType = AnnotationEditorType.STAMP;
}

render() {
Expand All @@ -2970,6 +2969,8 @@ class StampAnnotationElement extends AnnotationElement {
if (!this.data.popupRef && this.hasPopupData) {
this._createPopup();
}
this._editOnDoubleClick();

return this.container;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/display/editor/annotation_editor_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
// eslint-disable-next-line max-len
/** @typedef {import("../annotation_layer.js").AnnotationLayer} AnnotationLayer */
/** @typedef {import("../draw_layer.js").DrawLayer} DrawLayer */
// eslint-disable-next-line max-len
/** @typedef {import("../src/display/struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */

import { AnnotationEditorType, FeatureTest } from "../../shared/util.js";
import { AnnotationEditor } from "./editor.js";
Expand All @@ -35,6 +37,7 @@ import { StampEditor } from "./stamp.js";
* @typedef {Object} AnnotationEditorLayerOptions
* @property {Object} mode
* @property {HTMLDivElement} div
* @property {StructTreeLayerBuilder} structTreeLayer
* @property {AnnotationEditorUIManager} uiManager
* @property {boolean} enabled
* @property {TextAccessibilityManager} [accessibilityManager]
Expand Down Expand Up @@ -95,6 +98,7 @@ class AnnotationEditorLayer {
uiManager,
pageIndex,
div,
structTreeLayer,
accessibilityManager,
annotationLayer,
drawLayer,
Expand All @@ -119,6 +123,7 @@ class AnnotationEditorLayer {
this.viewport = viewport;
this.#textLayer = textLayer;
this.drawLayer = drawLayer;
this._structTree = structTreeLayer;

this.#uiManager.addLayer(this);
}
Expand Down
105 changes: 100 additions & 5 deletions src/display/editor/stamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
* limitations under the License.
*/

import { AnnotationEditorType, shadow } from "../../shared/util.js";
import {
AnnotationEditorType,
AnnotationPrefix,
shadow,
} from "../../shared/util.js";
import { OutputScale, PixelsPerInch } from "../display_utils.js";
import { AnnotationEditor } from "./editor.js";
import { StampAnnotationElement } from "../annotation_layer.js";
Expand Down Expand Up @@ -383,7 +387,7 @@ class StampEditor extends AnnotationEditor {
this.#getBitmap();
}

if (this.width) {
if (this.width && !this.annotationElementId) {
// This editor was created in using copy (ctrl+c).
const [parentWidth, parentHeight] = this.parentDimensions;
this.setAt(
Expand Down Expand Up @@ -431,7 +435,8 @@ class StampEditor extends AnnotationEditor {

if (
!this._uiManager.useNewAltTextWhenAddingImage ||
!this._uiManager.useNewAltTextFlow
!this._uiManager.useNewAltTextFlow ||
this.annotationElementId
) {
div.hidden = false;
}
Expand Down Expand Up @@ -769,13 +774,55 @@ class StampEditor extends AnnotationEditor {

/** @inheritdoc */
static async deserialize(data, parent, uiManager) {
let initialData = null;
if (data instanceof StampAnnotationElement) {
return null;
const {
data: { rect, rotation, id, structParent, popupRef },
container,
parent: {
page: { pageNumber },
},
} = data;
const canvas = container.querySelector("canvas");
const imageData = uiManager.imageManager.getFromCanvas(
container.id,
canvas
);
canvas.remove();

// When switching to edit mode, we wait for the structure tree to be
// ready (see pdf_viewer.js), so it's fine to use getAriaAttributesSync.
const altText =
(
await parent._structTree.getAriaAttributes(`${AnnotationPrefix}${id}`)
)?.get("aria-label") || "";

initialData = data = {
annotationType: AnnotationEditorType.STAMP,
bitmapId: imageData.id,
bitmap: imageData.bitmap,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
id,
deleted: false,
accessibilityData: {
decorative: false,
altText,
},
isSvg: false,
structParent,
popupRef,
};
}
const editor = await super.deserialize(data, parent, uiManager);
const { rect, bitmapUrl, bitmapId, isSvg, accessibilityData } = data;
const { rect, bitmap, bitmapUrl, bitmapId, isSvg, accessibilityData } =
data;
if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) {
editor.#bitmapId = bitmapId;
if (bitmap) {
editor.#bitmap = bitmap;
}
} else {
editor.#bitmapUrl = bitmapUrl;
}
Expand All @@ -785,9 +832,11 @@ class StampEditor extends AnnotationEditor {
editor.width = (rect[2] - rect[0]) / parentWidth;
editor.height = (rect[3] - rect[1]) / parentHeight;

editor.annotationElementId = data.id || null;
if (accessibilityData) {
editor.altTextData = accessibilityData;
}
editor._initialData = initialData;

return editor;
}
Expand All @@ -798,6 +847,10 @@ class StampEditor extends AnnotationEditor {
return null;
}

if (this.deleted) {
return this.serializeDeleted();
}

const serialized = {
annotationType: AnnotationEditorType.STAMP,
bitmapId: this.#bitmapId,
Expand All @@ -821,6 +874,20 @@ class StampEditor extends AnnotationEditor {
if (!decorative && altText) {
serialized.accessibilityData = { type: "Figure", alt: altText };
}
if (this.annotationElementId) {
const changes = this.#hasElementChanged(serialized);
if (changes.isSame) {
// Nothing has been changed.
return null;
}
if (changes.isSameAltText) {
delete serialized.accessibilityData;
} else {
serialized.accessibilityData.structParent =
this._initialData.structParent ?? -1;
}
}
serialized.id = this.annotationElementId;

if (context === null) {
return serialized;
Expand Down Expand Up @@ -848,6 +915,34 @@ class StampEditor extends AnnotationEditor {
}
return serialized;
}

#hasElementChanged(serialized) {
const {
rect,
pageIndex,
accessibilityData: { altText },
} = this._initialData;

const isSameRect = serialized.rect.every(
(x, i) => Math.abs(x - rect[i]) < 1
);
const isSamePageIndex = serialized.pageIndex === pageIndex;
const isSameAltText = (serialized.accessibilityData?.alt || "") === altText;

return {
isSame: isSameRect && isSamePageIndex && isSameAltText,
isSameAltText,
};
}

/** @inheritdoc */
renderAnnotationElement(annotation) {
annotation.updateEdited({
rect: this.getRect(0, 0),
});

return null;
}
}

export { StampEditor };
2 changes: 1 addition & 1 deletion src/display/editor/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class EditorToolbar {

render() {
const editToolbar = (this.#toolbar = document.createElement("div"));
editToolbar.className = "editToolbar";
editToolbar.classList.add("editToolbar", "hidden");
editToolbar.setAttribute("role", "toolbar");
const signal = this.#editor._uiManager._signal;
editToolbar.addEventListener("contextmenu", noContextMenu, { signal });
Expand Down
25 changes: 24 additions & 1 deletion src/display/editor/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,27 @@ class ImageManager {
return this.getFromUrl(data.url);
}

getFromCanvas(id, canvas) {
this.#cache ||= new Map();
let data = this.#cache.get(id);
if (data?.bitmap) {
data.refCounter += 1;
return data;
}
const offscreen = new OffscreenCanvas(canvas.width, canvas.height);
const ctx = offscreen.getContext("2d");
ctx.drawImage(canvas, 0, 0);
data = {
bitmap: offscreen.transferToImageBitmap(),
id: `image_${this.#baseId}_${this.#id++}`,
refCounter: 1,
isSvg: false,
};
this.#cache.set(id, data);
this.#cache.set(data.id, data);
return data;
}

getSvgUrl(id) {
const data = this.#cache.get(id);
if (!data?.isSvg) {
Expand All @@ -218,6 +239,7 @@ class ImageManager {
if (data.refCounter !== 0) {
return;
}
data.bitmap.close?.();
data.bitmap = null;
}

Expand Down Expand Up @@ -1619,7 +1641,8 @@ class AnnotationEditorUIManager {
if (editor.annotationElementId === editId) {
this.setSelected(editor);
editor.enterInEditMode();
break;
} else {
editor.unselect();
}
}

Expand Down
Loading

0 comments on commit e9c81a6

Please sign in to comment.