mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
[Editor] Make stamp annotations editable (bug 1921291)
This commit is contained in:
parent
ebbd019d7d
commit
8410252eb8
11 changed files with 281 additions and 18 deletions
|
@ -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
|
||||
|
|
|
@ -2863,10 +2863,8 @@ class InkAnnotationElement extends AnnotationElement {
|
|||
}
|
||||
|
||||
this.container.append(svg);
|
||||
this._editOnDoubleClick();
|
||||
|
||||
if (this._isEditable) {
|
||||
this._editOnDoubleClick();
|
||||
}
|
||||
return this.container;
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -2970,6 +2969,8 @@ class StampAnnotationElement extends AnnotationElement {
|
|||
if (!this.data.popupRef && this.hasPopupData) {
|
||||
this._createPopup();
|
||||
}
|
||||
this._editOnDoubleClick();
|
||||
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
@ -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]
|
||||
|
@ -95,6 +98,7 @@ class AnnotationEditorLayer {
|
|||
uiManager,
|
||||
pageIndex,
|
||||
div,
|
||||
structTreeLayer,
|
||||
accessibilityManager,
|
||||
annotationLayer,
|
||||
drawLayer,
|
||||
|
@ -119,6 +123,7 @@ class AnnotationEditorLayer {
|
|||
this.viewport = viewport;
|
||||
this.#textLayer = textLayer;
|
||||
this.drawLayer = drawLayer;
|
||||
this._structTree = structTreeLayer;
|
||||
|
||||
this.#uiManager.addLayer(this);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
@ -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(
|
||||
|
@ -431,7 +435,8 @@ class StampEditor extends AnnotationEditor {
|
|||
|
||||
if (
|
||||
!this._uiManager.useNewAltTextWhenAddingImage ||
|
||||
!this._uiManager.useNewAltTextFlow
|
||||
!this._uiManager.useNewAltTextFlow ||
|
||||
this.annotationElementId
|
||||
) {
|
||||
div.hidden = false;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -798,6 +847,10 @@ class StampEditor extends AnnotationEditor {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (this.deleted) {
|
||||
return this.serializeDeleted();
|
||||
}
|
||||
|
||||
const serialized = {
|
||||
annotationType: AnnotationEditorType.STAMP,
|
||||
bitmapId: this.#bitmapId,
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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) {
|
||||
|
@ -218,6 +239,7 @@ class ImageManager {
|
|||
if (data.refCounter !== 0) {
|
||||
return;
|
||||
}
|
||||
data.bitmap.close?.();
|
||||
data.bitmap = null;
|
||||
}
|
||||
|
||||
|
@ -1619,7 +1641,8 @@ class AnnotationEditorUIManager {
|
|||
if (editor.annotationElementId === editId) {
|
||||
this.setSelected(editor);
|
||||
editor.enterInEditMode();
|
||||
break;
|
||||
} else {
|
||||
editor.unselect();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue