Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Editor] Make ink annotation editable #19151

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4382,7 +4382,7 @@ 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.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;
Expand Down Expand Up @@ -4459,17 +4459,30 @@ class InkAnnotation extends MarkupAnnotation {
}

static createNewDict(annotation, xref, { apRef, ap }) {
const { color, opacity, paths, outlines, rect, rotation, thickness } =
annotation;
const ink = new Dict(xref);
const {
oldAnnotation,
color,
opacity,
paths,
outlines,
rect,
rotation,
thickness,
user,
} = annotation;
const ink = oldAnnotation || new Dict(xref);
ink.set("Type", Name.get("Annot"));
ink.set("Subtype", Name.get("Ink"));
ink.set("CreationDate", `D:${getModificationDate()}`);
ink.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`);
ink.set("Rect", rect);
ink.set("InkList", outlines?.points || paths.points);
ink.set("F", 4);
ink.set("Rotate", rotation);

if (user) {
ink.set("T", stringToAsciiOrUTF16BE(user));
}

if (outlines) {
// Free highlight.
// There's nothing about this in the spec, but it's used when highlighting
Expand Down Expand Up @@ -4524,12 +4537,15 @@ class InkAnnotation extends MarkupAnnotation {
}

for (const outline of paths.lines) {
for (let i = 0, ii = outline.length; i < ii; i += 6) {
appearanceBuffer.push(
`${numberToString(outline[4])} ${numberToString(outline[5])} m`
);
for (let i = 6, ii = outline.length; i < ii; i += 6) {
if (isNaN(outline[i])) {
appearanceBuffer.push(
`${numberToString(outline[i + 4])} ${numberToString(
outline[i + 5]
)} m`
)} l`
);
} else {
const [c1x, c1y, c2x, c2y, x, y] = outline.slice(i, i + 6);
Expand Down Expand Up @@ -5006,7 +5022,6 @@ class StampAnnotation extends MarkupAnnotation {
oldAnnotation ? "M" : "CreationDate",
`D:${getModificationDate()}`
);
stamp.set("CreationDate", `D:${getModificationDate()}`);
stamp.set("Rect", rect);
stamp.set("F", 4);
stamp.set("Border", [0, 0, 0]);
Expand Down
97 changes: 69 additions & 28 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2792,6 +2792,8 @@ class CaretAnnotationElement extends AnnotationElement {
}

class InkAnnotationElement extends AnnotationElement {
#polylinesGroupElement = null;

#polylines = [];

constructor(parameters) {
Expand All @@ -2809,55 +2811,71 @@ class InkAnnotationElement extends AnnotationElement {
: AnnotationEditorType.INK;
}

render() {
this.container.classList.add(this.containerClassName);

// Create an invisible polyline with the same points that acts as the
// trigger for the popup.
const {
data: { rect, rotation, inkLists, borderStyle, popupRef },
} = this;
let { width, height } = getRectDims(rect);
let transform;

#getTransform(rotation, rect) {
// PDF coordinates are calculated from a bottom left origin, so
// transform the polyline coordinates to a top left origin for the
// SVG element.
switch (rotation) {
case 90:
transform = `rotate(90) translate(${-rect[0]},${rect[3] - height}) scale(1,-1)`;
[width, height] = [height, width];
break;
return {
transform: `rotate(90) translate(${-rect[0]},${rect[1]}) scale(1,-1)`,
width: rect[3] - rect[1],
height: rect[2] - rect[0],
};
case 180:
transform = `rotate(180) translate(${-rect[0] - width},${rect[3] - height}) scale(1,-1)`;
break;
return {
transform: `rotate(180) translate(${-rect[2]},${rect[1]}) scale(1,-1)`,
width: rect[2] - rect[0],
height: rect[3] - rect[1],
};
case 270:
transform = `rotate(270) translate(${-rect[0] - width},${rect[3]}) scale(1,-1)`;
[width, height] = [height, width];
break;
return {
transform: `rotate(270) translate(${-rect[2]},${rect[3]}) scale(1,-1)`,
width: rect[3] - rect[1],
height: rect[2] - rect[0],
};
default:
transform = `translate(${-rect[0]},${rect[3]}) scale(1,-1)`;
break;
return {
transform: `translate(${-rect[0]},${rect[3]}) scale(1,-1)`,
width: rect[2] - rect[0],
height: rect[3] - rect[1],
};
}
}

render() {
this.container.classList.add(this.containerClassName);

// Create an invisible polyline with the same points that acts as the
// trigger for the popup.
const {
data: { rect, rotation, inkLists, borderStyle, popupRef },
} = this;
const { transform, width, height } = this.#getTransform(rotation, rect);

const svg = this.svgFactory.create(
width,
height,
/* skipDimensions = */ true
);
const basePolyline = this.svgFactory.createElement(this.svgElementName);
const g = (this.#polylinesGroupElement =
this.svgFactory.createElement("svg:g"));
svg.append(g);
// Ensure that the 'stroke-width' is always non-zero, since otherwise it
// won't be possible to open/close the popup (note e.g. issue 11122).
basePolyline.setAttribute("stroke-width", borderStyle.width || 1);
basePolyline.setAttribute("stroke", "transparent");
basePolyline.setAttribute("fill", "transparent");
basePolyline.setAttribute("transform", transform);
g.setAttribute("stroke-width", borderStyle.width || 1);
g.setAttribute("stroke-linecap", "round");
g.setAttribute("stroke-linejoin", "round");
g.setAttribute("stroke-miterlimit", 10);
g.setAttribute("stroke", "transparent");
g.setAttribute("fill", "transparent");
g.setAttribute("transform", transform);

for (let i = 0, ii = inkLists.length; i < ii; i++) {
const polyline = i < ii - 1 ? basePolyline.cloneNode() : basePolyline;
const polyline = this.svgFactory.createElement(this.svgElementName);
this.#polylines.push(polyline);
polyline.setAttribute("points", inkLists[i].join(","));
svg.append(polyline);
g.append(polyline);
}

if (!popupRef && this.hasPopupData) {
Expand All @@ -2870,6 +2888,29 @@ class InkAnnotationElement extends AnnotationElement {
return this.container;
}

updateEdited(params) {
super.updateEdited(params);
const { thickness, points, rect } = params;
const g = this.#polylinesGroupElement;
if (thickness >= 0) {
g.setAttribute("stroke-width", thickness || 1);
}
if (points) {
for (let i = 0, ii = this.#polylines.length; i < ii; i++) {
this.#polylines[i].setAttribute("points", points[i].join(","));
}
}
if (rect) {
const { transform, width, height } = this.#getTransform(
this.data.rotation,
rect
);
const root = g.parentElement;
root.setAttribute("viewBox", `0 0 ${width} ${height}`);
g.setAttribute("transform", transform);
}
}

getElementsToTriggerPopup() {
return this.#polylines;
}
Expand Down
54 changes: 46 additions & 8 deletions src/display/editor/drawers/inkdraw.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,7 @@ class InkDrawOutliner {
}

this.#last.set([x1, y1, x2, y2, x, y], 0);
this.#line.push(
(x1 + 5 * x2) / 6,
(y1 + 5 * y2) / 6,
(5 * x2 + x) / 6,
(5 * y2 + y) / 6,
(x2 + x) / 2,
(y2 + y) / 2
);
this.#line.push(...Outline.createBezierPoints(x1, y1, x2, y2, x, y));

return {
path: {
Expand Down Expand Up @@ -485,6 +478,51 @@ class InkDrawOutline extends Outline {
break;
}

if (!lines) {
lines = [];
for (const point of points) {
const len = point.length;
if (len === 2) {
lines.push(
new Float32Array([NaN, NaN, NaN, NaN, point[0], point[1]])
);
continue;
}
if (len === 4) {
lines.push(
new Float32Array([
NaN,
NaN,
NaN,
NaN,
point[0],
point[1],
NaN,
NaN,
NaN,
NaN,
point[2],
point[3],
])
);
continue;
}
const line = new Float32Array(3 * (len - 2));
lines.push(line);
let [x1, y1, x2, y2] = point.subarray(0, 4);
line.set([NaN, NaN, NaN, NaN, x1, y1], 0);
for (let i = 4; i < len; i += 2) {
const x = point[i];
const y = point[i + 1];
line.set(
Outline.createBezierPoints(x1, y1, x2, y2, x, y),
(i - 2) * 3
);
[x1, y1, x2, y2] = [x2, y2, x, y];
}
}
}

for (let i = 0, ii = lines.length; i < ii; i++) {
newLines.push({
line: rescaleFn(
Expand Down
11 changes: 11 additions & 0 deletions src/display/editor/drawers/outline.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ class Outline {
return [x, y];
}
}

static createBezierPoints(x1, y1, x2, y2, x3, y3) {
calixteman marked this conversation as resolved.
Show resolved Hide resolved
return [
(x1 + 5 * x2) / 6,
(y1 + 5 * y2) / 6,
(5 * x2 + x3) / 6,
(5 * y2 + y3) / 6,
(x2 + x3) / 2,
(y2 + y3) / 2,
];
}
}

export { Outline };
20 changes: 14 additions & 6 deletions src/display/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class AnnotationEditor {

#hasBeenClicked = false;

#initialPosition = null;
#initialRect = null;

#isEditing = false;

Expand Down Expand Up @@ -468,13 +468,13 @@ class AnnotationEditor {
* @param {number} y - y-translation in page coordinates.
*/
translateInPage(x, y) {
this.#initialPosition ||= [this.x, this.y];
this.#initialRect ||= [this.x, this.y, this.width, this.height];
this.#translate(this.pageDimensions, x, y);
this.div.scrollIntoView({ block: "nearest" });
}

drag(tx, ty) {
this.#initialPosition ||= [this.x, this.y];
this.#initialRect ||= [this.x, this.y, this.width, this.height];
const {
div,
parentDimensions: [parentWidth, parentHeight],
Expand Down Expand Up @@ -530,9 +530,16 @@ class AnnotationEditor {

get _hasBeenMoved() {
return (
!!this.#initialPosition &&
(this.#initialPosition[0] !== this.x ||
this.#initialPosition[1] !== this.y)
!!this.#initialRect &&
(this.#initialRect[0] !== this.x || this.#initialRect[1] !== this.y)
);
}

get _hasBeenResized() {
return (
!!this.#initialRect &&
(this.#initialRect[2] !== this.width ||
this.#initialRect[3] !== this.height)
);
}

Expand Down Expand Up @@ -989,6 +996,7 @@ class AnnotationEditor {
const newX = oppositeX - transfOppositePoint[0];
const newY = oppositeY - transfOppositePoint[1];

this.#initialRect ||= [this.x, this.y, this.width, this.height];
this.width = newWidth;
this.height = newHeight;
this.x = newX;
Expand Down
Loading