Skip to content

Commit

Permalink
Merge pull request #17914 from calixteman/freetext_edit
Browse files Browse the repository at this point in the history
[Editor] Provide an element to render in the annotation layer after a freetext has been edited (bug 1890535)
  • Loading branch information
calixteman authored Apr 18, 2024
2 parents 7290faf + 71ea849 commit 4866686
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 42 deletions.
115 changes: 86 additions & 29 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
// eslint-disable-next-line max-len
/** @typedef {import("../../web/interfaces").IDownloadManager} IDownloadManager */
/** @typedef {import("../../web/interfaces").IPDFLinkService} IPDFLinkService */
// eslint-disable-next-line max-len
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */

import {
AnnotationBorderStyleType,
Expand Down Expand Up @@ -157,6 +159,8 @@ class AnnotationElementFactory {
}

class AnnotationElement {
#updates = null;

#hasBorder = false;

constructor(
Expand Down Expand Up @@ -197,6 +201,52 @@ class AnnotationElement {
return AnnotationElement._hasPopupData(this.data);
}

updateEdited(params) {
if (!this.container) {
return;
}

this.#updates ||= {
rect: this.data.rect.slice(0),
};

const { rect } = params;

if (rect) {
this.#setRectEdited(rect);
}
}

resetEdited() {
if (!this.#updates) {
return;
}
this.#setRectEdited(this.#updates.rect);
this.#updates = null;
}

#setRectEdited(rect) {
const {
container: { style },
data: { rect: currentRect, rotation },
parent: {
viewport: {
rawDims: { pageWidth, pageHeight, pageX, pageY },
},
},
} = this;
currentRect?.splice(0, 4, ...rect);
const { width, height } = getRectDims(rect);
style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
style.top = `${(100 * (pageHeight - rect[3] + pageY)) / pageHeight}%`;
if (rotation === 0) {
style.width = `${(100 * width) / pageWidth}%`;
style.height = `${(100 * height) / pageHeight}%`;
} else {
this.setRotation(rotation);
}
}

/**
* Create an empty container for the annotation's HTML element.
*
Expand All @@ -216,13 +266,14 @@ class AnnotationElement {
if (!(this instanceof WidgetAnnotationElement)) {
container.tabIndex = DEFAULT_TAB_INDEX;
}
const { style } = container;

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

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

const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims;

if (!data.rect || this instanceof PopupAnnotationElement) {
const { rotation } = data;
if (!data.hasOwnCanvas && rotation !== 0) {
Expand All @@ -248,35 +297,26 @@ class AnnotationElement {

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

// Do *not* modify `data.rect`, since that will corrupt the annotation
// position on subsequent calls to `_createContainer` (see issue 6804).
const rect = Util.normalizeRect([
data.rect[0],
page.view[3] - data.rect[1] + page.view[1],
data.rect[2],
page.view[3] - data.rect[3] + page.view[1],
]);

if (!ignoreBorder && data.borderStyle.width > 0) {
container.style.borderWidth = `${data.borderStyle.width}px`;
style.borderWidth = `${data.borderStyle.width}px`;

const horizontalRadius = data.borderStyle.horizontalCornerRadius;
const verticalRadius = data.borderStyle.verticalCornerRadius;
if (horizontalRadius > 0 || verticalRadius > 0) {
const radius = `calc(${horizontalRadius}px * var(--scale-factor)) / calc(${verticalRadius}px * var(--scale-factor))`;
container.style.borderRadius = radius;
style.borderRadius = radius;
} else if (this instanceof RadioButtonWidgetAnnotationElement) {
const radius = `calc(${width}px * var(--scale-factor)) / calc(${height}px * var(--scale-factor))`;
container.style.borderRadius = radius;
style.borderRadius = radius;
}

switch (data.borderStyle.style) {
case AnnotationBorderStyleType.SOLID:
container.style.borderStyle = "solid";
style.borderStyle = "solid";
break;

case AnnotationBorderStyleType.DASHED:
container.style.borderStyle = "dashed";
style.borderStyle = "dashed";
break;

case AnnotationBorderStyleType.BEVELED:
Expand All @@ -288,7 +328,7 @@ class AnnotationElement {
break;

case AnnotationBorderStyleType.UNDERLINE:
container.style.borderBottomStyle = "solid";
style.borderBottomStyle = "solid";
break;

default:
Expand All @@ -298,24 +338,34 @@ class AnnotationElement {
const borderColor = data.borderColor || null;
if (borderColor) {
this.#hasBorder = true;
container.style.borderColor = Util.makeHexColor(
style.borderColor = Util.makeHexColor(
borderColor[0] | 0,
borderColor[1] | 0,
borderColor[2] | 0
);
} else {
// Transparent (invisible) border, so do not draw it at all.
container.style.borderWidth = 0;
style.borderWidth = 0;
}
}

container.style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
container.style.top = `${(100 * (rect[1] - pageY)) / pageHeight}%`;
// Do *not* modify `data.rect`, since that will corrupt the annotation
// position on subsequent calls to `_createContainer` (see issue 6804).
const rect = Util.normalizeRect([
data.rect[0],
page.view[3] - data.rect[1] + page.view[1],
data.rect[2],
page.view[3] - data.rect[3] + page.view[1],
]);
const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims;

style.left = `${(100 * (rect[0] - pageX)) / pageWidth}%`;
style.top = `${(100 * (rect[1] - pageY)) / pageHeight}%`;

const { rotation } = data;
if (data.hasOwnCanvas || rotation === 0) {
container.style.width = `${(100 * width) / pageWidth}%`;
container.style.height = `${(100 * height) / pageHeight}%`;
style.width = `${(100 * width) / pageWidth}%`;
style.height = `${(100 * height) / pageHeight}%`;
} else {
this.setRotation(rotation, container);
}
Expand Down Expand Up @@ -2897,6 +2947,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
* @property {Object<string, Array<Object>> | null} [fieldObjects]
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
* @property {TextAccessibilityManager} [accessibilityManager]
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
*/

/**
Expand All @@ -2913,6 +2964,7 @@ class AnnotationLayer {
div,
accessibilityManager,
annotationCanvasMap,
annotationEditorUIManager,
page,
viewport,
}) {
Expand All @@ -2922,6 +2974,7 @@ class AnnotationLayer {
this.page = page;
this.viewport = viewport;
this.zIndex = 0;
this._annotationEditorUIManager = annotationEditorUIManager;

if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
// For testing purposes.
Expand Down Expand Up @@ -3011,15 +3064,16 @@ class AnnotationLayer {
}
}

if (element.annotationEditorType > 0) {
this.#editableAnnotations.set(element.data.id, element);
}

const rendered = element.render();
if (data.hidden) {
rendered.style.visibility = "hidden";
}
this.#appendElement(rendered, data.id);

if (element.annotationEditorType > 0) {
this.#editableAnnotations.set(element.data.id, element);
this._annotationEditorUIManager?.renderAnnotationElement(element);
}
}

this.#setAnnotationCanvasMap();
Expand Down Expand Up @@ -3051,13 +3105,16 @@ class AnnotationLayer {
continue;
}

canvas.className = "annotationContent";
const { firstChild } = element;
if (!firstChild) {
element.append(canvas);
} else if (firstChild.nodeName === "CANVAS") {
firstChild.replaceWith(canvas);
} else {
} else if (!firstChild.classList.contains("annotationContent")) {
firstChild.before(canvas);
} else {
firstChild.after(canvas);
}
}
this.#annotationCanvasMap.clear();
Expand Down
35 changes: 27 additions & 8 deletions src/display/editor/annotation_editor_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ class AnnotationEditorLayer {
const annotationElementIds = new Set();
for (const editor of this.#editors.values()) {
editor.enableEditing();
editor.show(true);
if (editor.annotationElementId) {
this.#uiManager.removeChangedExistingAnnotation(editor);
annotationElementIds.add(editor.annotationElementId);
}
}
Expand Down Expand Up @@ -283,13 +285,19 @@ class AnnotationEditorLayer {
this.#isDisabling = true;
this.div.tabIndex = -1;
this.togglePointerEvents(false);
const hiddenAnnotationIds = new Set();
const changedAnnotations = new Map();
const resetAnnotations = new Map();
for (const editor of this.#editors.values()) {
editor.disableEditing();
if (!editor.annotationElementId || editor.serialize() !== null) {
hiddenAnnotationIds.add(editor.annotationElementId);
if (!editor.annotationElementId) {
continue;
}
if (editor.serialize() !== null) {
changedAnnotations.set(editor.annotationElementId, editor);
continue;
} else {
resetAnnotations.set(editor.annotationElementId, editor);
}
this.getEditableAnnotation(editor.annotationElementId)?.show();
editor.remove();
}
Expand All @@ -299,12 +307,23 @@ class AnnotationEditorLayer {
const editables = this.#annotationLayer.getEditableAnnotations();
for (const editable of editables) {
const { id } = editable.data;
if (
hiddenAnnotationIds.has(id) ||
this.#uiManager.isDeletedAnnotationElement(id)
) {
if (this.#uiManager.isDeletedAnnotationElement(id)) {
continue;
}
let editor = resetAnnotations.get(id);
if (editor) {
editor.resetAnnotationElement(editable);
editor.show(false);
editable.show();
continue;
}

editor = changedAnnotations.get(id);
if (editor) {
this.#uiManager.addChangedExistingAnnotation(editor);
editor.renderAnnotationElement(editable);
editor.show(false);
}
editable.show();
}
}
Expand Down Expand Up @@ -461,7 +480,7 @@ class AnnotationEditorLayer {
return;
}

if (editor.annotationElementId) {
if (editor.parent && editor.annotationElementId) {
this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId);
AnnotationEditor.deleteAnnotationElement(editor);
editor.annotationElementId = null;
Expand Down
42 changes: 42 additions & 0 deletions src/display/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,17 @@ class AnnotationEditor {
return editor;
}

/**
* Check if an existing annotation associated with this editor has been
* modified.
* @returns {boolean}
*/
get hasBeenModified() {
return (
!!this.annotationElementId && (this.deleted || this.serialize() !== null)
);
}

/**
* Remove this editor.
* It's used on ctrl+backspace action.
Expand Down Expand Up @@ -1710,6 +1721,37 @@ class AnnotationEditor {
}
this.#disabled = true;
}

/**
* Render an annotation in the annotation layer.
* @param {Object} annotation
* @returns {HTMLElement}
*/
renderAnnotationElement(annotation) {
let content = annotation.container.querySelector(".annotationContent");
if (!content) {
content = document.createElement("div");
content.classList.add("annotationContent", this.editorType);
annotation.container.prepend(content);
} else if (content.nodeName === "CANVAS") {
const canvas = content;
content = document.createElement("div");
content.classList.add("annotationContent", this.editorType);
canvas.before(content);
}

return content;
}

resetAnnotationElement(annotation) {
const { firstChild } = annotation.container;
if (
firstChild.nodeName === "DIV" &&
firstChild.classList.contains("annotationContent")
) {
firstChild.remove();
}
}
}

// This class is used to fake an editor which has been deleted.
Expand Down
Loading

0 comments on commit 4866686

Please sign in to comment.