mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
Merge pull request #18427 from calixteman/edit_highlight
[Editor] Make highlight annotations editable (bug 1883884)
This commit is contained in:
commit
a61f4b0303
12 changed files with 574 additions and 28 deletions
|
@ -705,6 +705,11 @@ class Annotation {
|
|||
this.data.pageIndex = params.pageIndex;
|
||||
}
|
||||
|
||||
const it = dict.get("IT");
|
||||
if (it instanceof Name) {
|
||||
this.data.it = it.name;
|
||||
}
|
||||
|
||||
this._isOffscreenCanvasSupported =
|
||||
params.evaluatorOptions.isOffscreenCanvasSupported;
|
||||
this._fallbackFontDict = null;
|
||||
|
@ -1377,6 +1382,7 @@ class Annotation {
|
|||
class AnnotationBorderStyle {
|
||||
constructor() {
|
||||
this.width = 1;
|
||||
this.rawWidth = 1;
|
||||
this.style = AnnotationBorderStyleType.SOLID;
|
||||
this.dashArray = [3];
|
||||
this.horizontalCornerRadius = 0;
|
||||
|
@ -1407,6 +1413,7 @@ class AnnotationBorderStyle {
|
|||
}
|
||||
if (typeof width === "number") {
|
||||
if (width > 0) {
|
||||
this.rawWidth = width;
|
||||
const maxWidth = (rect[2] - rect[0]) / 2;
|
||||
const maxHeight = (rect[3] - rect[1]) / 2;
|
||||
|
||||
|
@ -4283,6 +4290,10 @@ 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";
|
||||
// We want to be able to add mouse listeners to the annotation.
|
||||
this.data.noHTML = false;
|
||||
this.data.opacity = dict.get("CA") || 1;
|
||||
|
||||
const rawInkLists = dict.getArray("InkList");
|
||||
if (!Array.isArray(rawInkLists)) {
|
||||
|
@ -4534,6 +4545,10 @@ 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;
|
||||
this.data.opacity = dict.get("CA") || 1;
|
||||
|
||||
const quadPoints = (this.data.quadPoints = getQuadPoints(dict, null));
|
||||
if (quadPoints) {
|
||||
|
@ -4573,11 +4588,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);
|
||||
|
|
|
@ -2807,7 +2807,11 @@ 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;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -2857,6 +2861,10 @@ class InkAnnotationElement extends AnnotationElement {
|
|||
}
|
||||
|
||||
this.container.append(svg);
|
||||
|
||||
if (this._isEditable) {
|
||||
this._editOnDoubleClick();
|
||||
}
|
||||
return this.container;
|
||||
}
|
||||
|
||||
|
@ -2876,6 +2884,7 @@ class HighlightAnnotationElement extends AnnotationElement {
|
|||
ignoreBorder: true,
|
||||
createQuadrilaterals: true,
|
||||
});
|
||||
this.annotationEditorType = AnnotationEditorType.HIGHLIGHT;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -2884,6 +2893,8 @@ class HighlightAnnotationElement extends AnnotationElement {
|
|||
}
|
||||
|
||||
this.container.classList.add("highlightAnnotation");
|
||||
this._editOnDoubleClick();
|
||||
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
|
@ -3247,6 +3258,7 @@ class AnnotationLayer {
|
|||
export {
|
||||
AnnotationLayer,
|
||||
FreeTextAnnotationElement,
|
||||
HighlightAnnotationElement,
|
||||
InkAnnotationElement,
|
||||
StampAnnotationElement,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -323,8 +323,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();
|
||||
}
|
||||
|
|
|
@ -1376,6 +1376,7 @@ class AnnotationEditor {
|
|||
data.rect,
|
||||
pageHeight
|
||||
);
|
||||
|
||||
editor.x = x / pageWidth;
|
||||
editor.y = y / pageHeight;
|
||||
editor.width = width / pageWidth;
|
||||
|
@ -1774,7 +1775,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");
|
||||
|
|
|
@ -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";
|
||||
|
@ -51,6 +55,8 @@ class HighlightEditor extends AnnotationEditor {
|
|||
|
||||
#id = null;
|
||||
|
||||
#initialData = null;
|
||||
|
||||
#isFreeHighlight = false;
|
||||
|
||||
#lastPoint = null;
|
||||
|
@ -111,7 +117,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;
|
||||
|
@ -316,15 +322,22 @@ class HighlightEditor extends AnnotationEditor {
|
|||
* @param {string} color
|
||||
*/
|
||||
#updateColor(color) {
|
||||
const setColor = col => {
|
||||
const setColorAndOpacity = (col, opa) => {
|
||||
this.color = col;
|
||||
this.parent?.drawLayer.changeColor(this.#id, col);
|
||||
this.#colorPicker?.updateColor(col);
|
||||
this.#opacity = opa;
|
||||
this.parent?.drawLayer.changeOpacity(this.#id, opa);
|
||||
};
|
||||
const savedColor = this.color;
|
||||
const savedOpacity = this.#opacity;
|
||||
this.addCommands({
|
||||
cmd: setColor.bind(this, color),
|
||||
undo: setColor.bind(this, savedColor),
|
||||
cmd: setColorAndOpacity.bind(
|
||||
this,
|
||||
color,
|
||||
HighlightEditor._defaultOpacity
|
||||
),
|
||||
undo: setColorAndOpacity.bind(this, savedColor, savedOpacity),
|
||||
post: this._uiManager.updateUI.bind(this._uiManager, this),
|
||||
mustExec: true,
|
||||
type: AnnotationEditorParamsType.HIGHLIGHT_COLOR,
|
||||
|
@ -410,7 +423,9 @@ class HighlightEditor extends AnnotationEditor {
|
|||
|
||||
/** @inheritdoc */
|
||||
onceAdded() {
|
||||
this.parent.addUndoableEditor(this);
|
||||
if (!this.annotationElementId) {
|
||||
this.parent.addUndoableEditor(this);
|
||||
}
|
||||
this.div.focus();
|
||||
}
|
||||
|
||||
|
@ -769,29 +784,114 @@ class HighlightEditor extends AnnotationEditor {
|
|||
|
||||
/** @inheritdoc */
|
||||
static deserialize(data, parent, uiManager) {
|
||||
let initialData = null;
|
||||
if (data instanceof HighlightAnnotationElement) {
|
||||
const {
|
||||
data: { quadPoints, rect, rotation, id, color, opacity },
|
||||
parent: {
|
||||
page: { pageNumber },
|
||||
},
|
||||
} = data;
|
||||
initialData = data = {
|
||||
annotationType: AnnotationEditorType.HIGHLIGHT,
|
||||
color: Array.from(color),
|
||||
opacity,
|
||||
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: { rawWidth: 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, opacity } = 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;
|
||||
editor.#opacity = opacity || 1;
|
||||
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;
|
||||
}
|
||||
|
@ -803,10 +903,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,
|
||||
|
@ -818,6 +926,27 @@ class HighlightEditor extends AnnotationEditor {
|
|||
rotation: this.#getRotation(),
|
||||
structTreeParentId: this._structTreeParentId,
|
||||
};
|
||||
|
||||
if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
serialized.id = this.annotationElementId;
|
||||
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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue