Skip to content

Commit

Permalink
Merge pull request #19151 from calixteman/edit_inks
Browse files Browse the repository at this point in the history
[Editor] Make ink annotation editable
  • Loading branch information
calixteman authored Dec 2, 2024
2 parents 97c7a8e + 7e02c77 commit 8a2bdb1
Show file tree
Hide file tree
Showing 15 changed files with 753 additions and 87 deletions.
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) {
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

0 comments on commit 8a2bdb1

Please sign in to comment.