Skip to content

Commit

Permalink
[Editor] Make highlight annotations editable (bug 1883884)
Browse files Browse the repository at this point in the history
The goal of this patch is to be able to edit existing highlight annotations.
  • Loading branch information
calixteman committed Aug 3, 2024
1 parent c60c0d1 commit 6c5fcca
Show file tree
Hide file tree
Showing 11 changed files with 554 additions and 25 deletions.
20 changes: 18 additions & 2 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,11 @@ class Annotation {
this.data.pageIndex = params.pageIndex;
}

const it = dict.get("IT");
if (it) {
this.data.it = it.name;
}

this._isOffscreenCanvasSupported =
params.evaluatorOptions.isOffscreenCanvasSupported;
this._fallbackFontDict = null;
Expand Down Expand Up @@ -1377,6 +1382,7 @@ class Annotation {
class AnnotationBorderStyle {
constructor() {
this.width = 1;
this.thickness = 1;
this.style = AnnotationBorderStyleType.SOLID;
this.dashArray = [3];
this.horizontalCornerRadius = 0;
Expand Down Expand Up @@ -1407,6 +1413,7 @@ class AnnotationBorderStyle {
}
if (typeof width === "number") {
if (width > 0) {
this.thickness = width;
const maxWidth = (rect[2] - rect[0]) / 2;
const maxHeight = (rect[3] - rect[1]) / 2;

Expand Down Expand Up @@ -4283,6 +4290,8 @@ class InkAnnotation extends MarkupAnnotation {
const { dict, xref } = params;
this.data.annotationType = AnnotationType.INK;
this.data.inkLists = [];
this.data.isEditable = !this.data.noHTML && this.data.it === "InkHighlight";
this.data.noHTML = false;

const rawInkLists = dict.getArray("InkList");
if (!Array.isArray(rawInkLists)) {
Expand Down Expand Up @@ -4534,6 +4543,9 @@ class HighlightAnnotation extends MarkupAnnotation {

const { dict, xref } = params;
this.data.annotationType = AnnotationType.HIGHLIGHT;
this.data.isEditable = !this.data.noHTML;
// We want to be able to add mouse listeners to the annotation.
this.data.noHTML = false;

const quadPoints = (this.data.quadPoints = getQuadPoints(dict, null));
if (quadPoints) {
Expand Down Expand Up @@ -4573,11 +4585,15 @@ class HighlightAnnotation extends MarkupAnnotation {
}
}

static createNewDict(annotation, xref, { apRef, ap }) {
static createNewDict(annotation, xref, { apRef, ap, oldAnnotation }) {
const { color, opacity, rect, rotation, user, quadPoints } = annotation;
const highlight = new Dict(xref);
const highlight = oldAnnotation || new Dict(xref);
highlight.set("Type", Name.get("Annot"));
highlight.set("Subtype", Name.get("Highlight"));
highlight.set(
oldAnnotation ? "M" : "CreationDate",
`D:${getModificationDate()}`
);
highlight.set("CreationDate", `D:${getModificationDate()}`);
highlight.set("Rect", rect);
highlight.set("F", 4);
Expand Down
18 changes: 17 additions & 1 deletion src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2810,7 +2810,15 @@ class InkAnnotationElement extends AnnotationElement {
// Use the polyline SVG element since it allows us to use coordinates
// directly and to draw both straight lines and curves.
this.svgElementName = "svg:polyline";
this.annotationEditorType = AnnotationEditorType.INK;

this.annotationEditorType =
this.data.it === "InkHighlight"
? AnnotationEditorType.HIGHLIGHT
: AnnotationEditorType.INK;
}

get _isEditable() {
return this.data.isEditable;
}

render() {
Expand Down Expand Up @@ -2860,6 +2868,10 @@ class InkAnnotationElement extends AnnotationElement {
}

this.container.append(svg);

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

Expand All @@ -2879,6 +2891,7 @@ class HighlightAnnotationElement extends AnnotationElement {
ignoreBorder: true,
createQuadrilaterals: true,
});
this.annotationEditorType = AnnotationEditorType.HIGHLIGHT;
}

render() {
Expand All @@ -2887,6 +2900,8 @@ class HighlightAnnotationElement extends AnnotationElement {
}

this.container.classList.add("highlightAnnotation");
this._editOnDoubleClick();

return this.container;
}
}
Expand Down Expand Up @@ -3250,6 +3265,7 @@ class AnnotationLayer {
export {
AnnotationLayer,
FreeTextAnnotationElement,
HighlightAnnotationElement,
InkAnnotationElement,
StampAnnotationElement,
};
4 changes: 4 additions & 0 deletions src/display/draw_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ class DrawLayer {
this.#mapping.get(id).classList.remove(className);
}

getSVGRoot(id) {
return this.#mapping.get(id);
}

remove(id) {
if (this.#parent === null) {
return;
Expand Down
6 changes: 4 additions & 2 deletions src/display/editor/annotation_editor_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,10 @@ class AnnotationEditorLayer {
editor = changedAnnotations.get(id);
if (editor) {
this.#uiManager.addChangedExistingAnnotation(editor);
editor.renderAnnotationElement(editable);
editor.show(false);
if (editor.renderAnnotationElement(editable)) {
// Content has changed, so we need to hide the editor.
editor.show(false);
}
}
editable.show();
}
Expand Down
3 changes: 2 additions & 1 deletion src/display/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,7 @@ class AnnotationEditor {
data.rect,
pageHeight
);

editor.x = x / pageWidth;
editor.y = y / pageHeight;
editor.width = width / pageWidth;
Expand Down Expand Up @@ -1779,7 +1780,7 @@ class AnnotationEditor {
/**
* Render an annotation in the annotation layer.
* @param {Object} annotation
* @returns {HTMLElement}
* @returns {HTMLElement|null}
*/
renderAnnotationElement(annotation) {
let content = annotation.container.querySelector(".annotationContent");
Expand Down
159 changes: 140 additions & 19 deletions src/display/editor/highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import {
} from "../../shared/util.js";
import { bindEvents, KeyboardManager } from "./tools.js";
import { FreeOutliner, Outliner } from "./outliner.js";
import {
HighlightAnnotationElement,
InkAnnotationElement,
} from "../annotation_layer.js";
import { AnnotationEditor } from "./editor.js";
import { ColorPicker } from "./color_picker.js";
import { noContextMenu } from "../display_utils.js";
Expand Down Expand Up @@ -51,6 +55,8 @@ class HighlightEditor extends AnnotationEditor {

#id = null;

#initialData = null;

#isFreeHighlight = false;

#boundKeydown = this.#keydown.bind(this);
Expand Down Expand Up @@ -113,7 +119,7 @@ class HighlightEditor extends AnnotationEditor {
this.#isFreeHighlight = true;
this.#createFreeOutlines(params);
this.#addToDrawLayer();
} else {
} else if (this.#boxes) {
this.#anchorNode = params.anchorNode;
this.#anchorOffset = params.anchorOffset;
this.#focusNode = params.focusNode;
Expand Down Expand Up @@ -412,7 +418,9 @@ class HighlightEditor extends AnnotationEditor {

/** @inheritdoc */
onceAdded() {
this.parent.addUndoableEditor(this);
if (!this.annotationElementId) {
this.parent.addUndoableEditor(this);
}
this.div.focus();
}

Expand Down Expand Up @@ -772,29 +780,112 @@ class HighlightEditor extends AnnotationEditor {

/** @inheritdoc */
static deserialize(data, parent, uiManager) {
let initialData = null;
if (data instanceof HighlightAnnotationElement) {
const {
data: { quadPoints, rect, rotation, id, color },
parent: {
page: { pageNumber },
},
} = data;
initialData = data = {
annotationType: AnnotationEditorType.HIGHLIGHT,
color: Array.from(color),
quadPoints,
boxes: null,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
id,
deleted: false,
};
} else if (data instanceof InkAnnotationElement) {
const {
data: {
inkLists,
rect,
rotation,
id,
color,
borderStyle: { thickness },
},
parent: {
page: { pageNumber },
},
} = data;
initialData = data = {
annotationType: AnnotationEditorType.HIGHLIGHT,
color: Array.from(color),
thickness,
inkLists,
boxes: null,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
id,
deleted: false,
};
}

const { color, quadPoints, inkLists } = data;
const editor = super.deserialize(data, parent, uiManager);

const {
rect: [blX, blY, trX, trY],
color,
quadPoints,
} = data;
editor.color = Util.makeHexColor(...color);
editor.#opacity = data.opacity;
if (inkLists) {
editor.#thickness = data.thickness;
}
editor.annotationElementId = data.id || null;
editor.#initialData = initialData;

const [pageWidth, pageHeight] = editor.pageDimensions;
editor.width = (trX - blX) / pageWidth;
editor.height = (trY - blY) / pageHeight;
const boxes = (editor.#boxes = []);
for (let i = 0; i < quadPoints.length; i += 8) {
boxes.push({
x: (quadPoints[4] - trX) / pageWidth,
y: (trY - (1 - quadPoints[i + 5])) / pageHeight,
width: (quadPoints[i + 2] - quadPoints[i]) / pageWidth,
height: (quadPoints[i + 5] - quadPoints[i + 1]) / pageHeight,
const [pageX, pageY] = editor.pageTranslation;

if (quadPoints) {
const boxes = (editor.#boxes = []);
for (let i = 0; i < quadPoints.length; i += 8) {
boxes.push({
x: (quadPoints[i] - pageX) / pageWidth,
y: 1 - (quadPoints[i + 1] - pageY) / pageHeight,
width: (quadPoints[i + 2] - quadPoints[i]) / pageWidth,
height: (quadPoints[i + 1] - quadPoints[i + 5]) / pageHeight,
});
}
editor.#createOutlines();
editor.#addToDrawLayer();
editor.rotate(editor.rotation);
} else if (inkLists) {
editor.#isFreeHighlight = true;
const points = inkLists[0];
const point = {
x: points[0] - pageX,
y: pageHeight - (points[1] - pageY),
};
const outliner = new FreeOutliner(
point,
[0, 0, pageWidth, pageHeight],
1,
editor.#thickness / 2,
true,
0.001
);
for (let i = 0, ii = points.length; i < ii; i += 2) {
point.x = points[i] - pageX;
point.y = pageHeight - (points[i + 1] - pageY);
outliner.add(point);
}
const { id, clipPathId } = parent.drawLayer.highlight(
outliner,
editor.color,
editor._defaultOpacity,
/* isPathUpdatable = */ true
);
editor.#createFreeOutlines({
highlightOutlines: outliner.getOutlines(),
highlightId: id,
clipPathId,
});
editor.#addToDrawLayer();
}
editor.#createOutlines();

return editor;
}
Expand All @@ -806,10 +897,18 @@ class HighlightEditor extends AnnotationEditor {
return null;
}

if (this.deleted) {
return {
pageIndex: this.pageIndex,
id: this.annotationElementId,
deleted: true,
};
}

const rect = this.getRect(0, 0);
const color = AnnotationEditor._colorManager.convert(this.color);

return {
const serialized = {
annotationType: AnnotationEditorType.HIGHLIGHT,
color,
opacity: this.#opacity,
Expand All @@ -821,6 +920,28 @@ class HighlightEditor extends AnnotationEditor {
rotation: this.#getRotation(),
structTreeParentId: this._structTreeParentId,
};

if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
return null;
}

serialized.id = this.annotationElementId;
console.log(serialized);
return serialized;
}

#hasElementChanged(serialized) {
const { color } = this.#initialData;
return serialized.color.some((c, i) => c !== color[i]);
}

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

return null;
}

static canCreateNewEmptyEditor() {
Expand Down
Loading

0 comments on commit 6c5fcca

Please sign in to comment.