Skip to content

Commit 5c1e234

Browse files
committed
[Editor] Provide an element to render in the annotation layer after a freetext has been edited (bug 1890535)
1 parent 7290faf commit 5c1e234

11 files changed

+379
-42
lines changed

src/display/annotation_layer.js

+86-29
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
// eslint-disable-next-line max-len
2121
/** @typedef {import("../../web/interfaces").IDownloadManager} IDownloadManager */
2222
/** @typedef {import("../../web/interfaces").IPDFLinkService} IPDFLinkService */
23+
// eslint-disable-next-line max-len
24+
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
2325

2426
import {
2527
AnnotationBorderStyleType,
@@ -157,6 +159,8 @@ class AnnotationElementFactory {
157159
}
158160

159161
class AnnotationElement {
162+
#updates = null;
163+
160164
#hasBorder = false;
161165

162166
constructor(
@@ -197,6 +201,52 @@ class AnnotationElement {
197201
return AnnotationElement._hasPopupData(this.data);
198202
}
199203

204+
updateEdited(params) {
205+
if (!this.container) {
206+
return;
207+
}
208+
209+
this.#updates ||= {
210+
rect: this.data.rect.slice(0),
211+
};
212+
213+
const { rect } = params;
214+
215+
if (rect) {
216+
this.#setRectEdited(rect);
217+
}
218+
}
219+
220+
resetEdited() {
221+
if (!this.#updates) {
222+
return;
223+
}
224+
this.#setRectEdited(this.#updates.rect);
225+
this.#updates = null;
226+
}
227+
228+
#setRectEdited(rect) {
229+
const {
230+
container: { style },
231+
data: { rect: currentRect, rotation },
232+
parent: {
233+
viewport: {
234+
rawDims: { pageWidth, pageHeight, pageX, pageY },
235+
},
236+
},
237+
} = this;
238+
currentRect?.splice(0, 4, ...rect);
239+
const { width, height } = getRectDims(rect);
240+
style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
241+
style.top = `${(100 * (pageHeight - rect[3] + pageY)) / pageHeight}%`;
242+
if (rotation === 0) {
243+
style.width = `${(100 * width) / pageWidth}%`;
244+
style.height = `${(100 * height) / pageHeight}%`;
245+
} else {
246+
this.setRotation(rotation);
247+
}
248+
}
249+
200250
/**
201251
* Create an empty container for the annotation's HTML element.
202252
*
@@ -216,13 +266,14 @@ class AnnotationElement {
216266
if (!(this instanceof WidgetAnnotationElement)) {
217267
container.tabIndex = DEFAULT_TAB_INDEX;
218268
}
269+
const { style } = container;
219270

220271
// The accessibility manager will move the annotation in the DOM in
221272
// order to match the visual ordering.
222273
// But if an annotation is above an other one, then we must draw it
223274
// after the other one whatever the order is in the DOM, hence the
224275
// use of the z-index.
225-
container.style.zIndex = this.parent.zIndex++;
276+
style.zIndex = this.parent.zIndex++;
226277

227278
if (data.popupRef) {
228279
container.setAttribute("aria-haspopup", "dialog");
@@ -236,8 +287,6 @@ class AnnotationElement {
236287
container.classList.add("norotate");
237288
}
238289

239-
const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims;
240-
241290
if (!data.rect || this instanceof PopupAnnotationElement) {
242291
const { rotation } = data;
243292
if (!data.hasOwnCanvas && rotation !== 0) {
@@ -248,35 +297,26 @@ class AnnotationElement {
248297

249298
const { width, height } = getRectDims(data.rect);
250299

251-
// Do *not* modify `data.rect`, since that will corrupt the annotation
252-
// position on subsequent calls to `_createContainer` (see issue 6804).
253-
const rect = Util.normalizeRect([
254-
data.rect[0],
255-
page.view[3] - data.rect[1] + page.view[1],
256-
data.rect[2],
257-
page.view[3] - data.rect[3] + page.view[1],
258-
]);
259-
260300
if (!ignoreBorder && data.borderStyle.width > 0) {
261-
container.style.borderWidth = `${data.borderStyle.width}px`;
301+
style.borderWidth = `${data.borderStyle.width}px`;
262302

263303
const horizontalRadius = data.borderStyle.horizontalCornerRadius;
264304
const verticalRadius = data.borderStyle.verticalCornerRadius;
265305
if (horizontalRadius > 0 || verticalRadius > 0) {
266306
const radius = `calc(${horizontalRadius}px * var(--scale-factor)) / calc(${verticalRadius}px * var(--scale-factor))`;
267-
container.style.borderRadius = radius;
307+
style.borderRadius = radius;
268308
} else if (this instanceof RadioButtonWidgetAnnotationElement) {
269309
const radius = `calc(${width}px * var(--scale-factor)) / calc(${height}px * var(--scale-factor))`;
270-
container.style.borderRadius = radius;
310+
style.borderRadius = radius;
271311
}
272312

273313
switch (data.borderStyle.style) {
274314
case AnnotationBorderStyleType.SOLID:
275-
container.style.borderStyle = "solid";
315+
style.borderStyle = "solid";
276316
break;
277317

278318
case AnnotationBorderStyleType.DASHED:
279-
container.style.borderStyle = "dashed";
319+
style.borderStyle = "dashed";
280320
break;
281321

282322
case AnnotationBorderStyleType.BEVELED:
@@ -288,7 +328,7 @@ class AnnotationElement {
288328
break;
289329

290330
case AnnotationBorderStyleType.UNDERLINE:
291-
container.style.borderBottomStyle = "solid";
331+
style.borderBottomStyle = "solid";
292332
break;
293333

294334
default:
@@ -298,24 +338,34 @@ class AnnotationElement {
298338
const borderColor = data.borderColor || null;
299339
if (borderColor) {
300340
this.#hasBorder = true;
301-
container.style.borderColor = Util.makeHexColor(
341+
style.borderColor = Util.makeHexColor(
302342
borderColor[0] | 0,
303343
borderColor[1] | 0,
304344
borderColor[2] | 0
305345
);
306346
} else {
307347
// Transparent (invisible) border, so do not draw it at all.
308-
container.style.borderWidth = 0;
348+
style.borderWidth = 0;
309349
}
310350
}
311351

312-
container.style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
313-
container.style.top = `${(100 * (rect[1] - pageY)) / pageHeight}%`;
352+
// Do *not* modify `data.rect`, since that will corrupt the annotation
353+
// position on subsequent calls to `_createContainer` (see issue 6804).
354+
const rect = Util.normalizeRect([
355+
data.rect[0],
356+
page.view[3] - data.rect[1] + page.view[1],
357+
data.rect[2],
358+
page.view[3] - data.rect[3] + page.view[1],
359+
]);
360+
const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims;
361+
362+
style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
363+
style.top = `${(100 * (rect[1] - pageY)) / pageHeight}%`;
314364

315365
const { rotation } = data;
316366
if (data.hasOwnCanvas || rotation === 0) {
317-
container.style.width = `${(100 * width) / pageWidth}%`;
318-
container.style.height = `${(100 * height) / pageHeight}%`;
367+
style.width = `${(100 * width) / pageWidth}%`;
368+
style.height = `${(100 * height) / pageHeight}%`;
319369
} else {
320370
this.setRotation(rotation, container);
321371
}
@@ -2897,6 +2947,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
28972947
* @property {Object<string, Array<Object>> | null} [fieldObjects]
28982948
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
28992949
* @property {TextAccessibilityManager} [accessibilityManager]
2950+
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
29002951
*/
29012952

29022953
/**
@@ -2913,6 +2964,7 @@ class AnnotationLayer {
29132964
div,
29142965
accessibilityManager,
29152966
annotationCanvasMap,
2967+
annotationEditorUIManager,
29162968
page,
29172969
viewport,
29182970
}) {
@@ -2922,6 +2974,7 @@ class AnnotationLayer {
29222974
this.page = page;
29232975
this.viewport = viewport;
29242976
this.zIndex = 0;
2977+
this._annotationEditorUIManager = annotationEditorUIManager;
29252978

29262979
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
29272980
// For testing purposes.
@@ -3011,15 +3064,16 @@ class AnnotationLayer {
30113064
}
30123065
}
30133066

3014-
if (element.annotationEditorType > 0) {
3015-
this.#editableAnnotations.set(element.data.id, element);
3016-
}
3017-
30183067
const rendered = element.render();
30193068
if (data.hidden) {
30203069
rendered.style.visibility = "hidden";
30213070
}
30223071
this.#appendElement(rendered, data.id);
3072+
3073+
if (element.annotationEditorType > 0) {
3074+
this.#editableAnnotations.set(element.data.id, element);
3075+
this._annotationEditorUIManager?.renderAnnotationElement(element);
3076+
}
30233077
}
30243078

30253079
this.#setAnnotationCanvasMap();
@@ -3051,13 +3105,16 @@ class AnnotationLayer {
30513105
continue;
30523106
}
30533107

3108+
canvas.className = "annotationContent";
30543109
const { firstChild } = element;
30553110
if (!firstChild) {
30563111
element.append(canvas);
30573112
} else if (firstChild.nodeName === "CANVAS") {
30583113
firstChild.replaceWith(canvas);
3059-
} else {
3114+
} else if (!firstChild.classList.contains("annotationContent")) {
30603115
firstChild.before(canvas);
3116+
} else {
3117+
firstChild.after(canvas);
30613118
}
30623119
}
30633120
this.#annotationCanvasMap.clear();

src/display/editor/annotation_editor_layer.js

+27-8
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,9 @@ class AnnotationEditorLayer {
248248
const annotationElementIds = new Set();
249249
for (const editor of this.#editors.values()) {
250250
editor.enableEditing();
251+
editor.show(true);
251252
if (editor.annotationElementId) {
253+
this.#uiManager.removeChangedExistingAnnotation(editor);
252254
annotationElementIds.add(editor.annotationElementId);
253255
}
254256
}
@@ -283,13 +285,19 @@ class AnnotationEditorLayer {
283285
this.#isDisabling = true;
284286
this.div.tabIndex = -1;
285287
this.togglePointerEvents(false);
286-
const hiddenAnnotationIds = new Set();
288+
const changedAnnotations = new Map();
289+
const resetAnnotations = new Map();
287290
for (const editor of this.#editors.values()) {
288291
editor.disableEditing();
289-
if (!editor.annotationElementId || editor.serialize() !== null) {
290-
hiddenAnnotationIds.add(editor.annotationElementId);
292+
if (!editor.annotationElementId) {
291293
continue;
292294
}
295+
if (editor.serialize() !== null) {
296+
changedAnnotations.set(editor.annotationElementId, editor);
297+
continue;
298+
} else {
299+
resetAnnotations.set(editor.annotationElementId, editor);
300+
}
293301
this.getEditableAnnotation(editor.annotationElementId)?.show();
294302
editor.remove();
295303
}
@@ -299,12 +307,23 @@ class AnnotationEditorLayer {
299307
const editables = this.#annotationLayer.getEditableAnnotations();
300308
for (const editable of editables) {
301309
const { id } = editable.data;
302-
if (
303-
hiddenAnnotationIds.has(id) ||
304-
this.#uiManager.isDeletedAnnotationElement(id)
305-
) {
310+
if (this.#uiManager.isDeletedAnnotationElement(id)) {
311+
continue;
312+
}
313+
let editor = resetAnnotations.get(id);
314+
if (editor) {
315+
editor.resetAnnotationElement(editable);
316+
editor.show(false);
317+
editable.show();
306318
continue;
307319
}
320+
321+
editor = changedAnnotations.get(id);
322+
if (editor) {
323+
this.#uiManager.addChangedExistingAnnotation(editor);
324+
editor.renderAnnotationElement(editable);
325+
editor.show(false);
326+
}
308327
editable.show();
309328
}
310329
}
@@ -461,7 +480,7 @@ class AnnotationEditorLayer {
461480
return;
462481
}
463482

464-
if (editor.annotationElementId) {
483+
if (editor.parent && editor.annotationElementId) {
465484
this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId);
466485
AnnotationEditor.deleteAnnotationElement(editor);
467486
editor.annotationElementId = null;

src/display/editor/editor.js

+42
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,17 @@ class AnnotationEditor {
13361336
return editor;
13371337
}
13381338

1339+
/**
1340+
* Check if an existing annotation associated with this editor has been
1341+
* modified.
1342+
* @returns {boolean}
1343+
*/
1344+
get hasBeenModified() {
1345+
return (
1346+
!!this.annotationElementId && (this.deleted || this.serialize() !== null)
1347+
);
1348+
}
1349+
13391350
/**
13401351
* Remove this editor.
13411352
* It's used on ctrl+backspace action.
@@ -1710,6 +1721,37 @@ class AnnotationEditor {
17101721
}
17111722
this.#disabled = true;
17121723
}
1724+
1725+
/**
1726+
* Render an annotation in the annotation layer.
1727+
* @param {Object} annotation
1728+
* @returns {HTMLElement}
1729+
*/
1730+
renderAnnotationElement(annotation) {
1731+
let content = annotation.container.querySelector(".annotationContent");
1732+
if (!content) {
1733+
content = document.createElement("div");
1734+
content.classList.add("annotationContent", this.editorType);
1735+
annotation.container.prepend(content);
1736+
} else if (content.nodeName === "CANVAS") {
1737+
const canvas = content;
1738+
content = document.createElement("div");
1739+
content.classList.add("annotationContent", this.editorType);
1740+
canvas.before(content);
1741+
}
1742+
1743+
return content;
1744+
}
1745+
1746+
resetAnnotationElement(annotation) {
1747+
const { firstChild } = annotation.container;
1748+
if (
1749+
firstChild.nodeName === "DIV" &&
1750+
firstChild.classList.contains("annotationContent")
1751+
) {
1752+
firstChild.remove();
1753+
}
1754+
}
17131755
}
17141756

17151757
// This class is used to fake an editor which has been deleted.

0 commit comments

Comments
 (0)