Merge pull request #19151 from calixteman/edit_inks
[Editor] Make ink annotation editable
This commit is contained in:
commit
8a2bdb1a05
@ -4382,7 +4382,7 @@ class InkAnnotation extends MarkupAnnotation {
|
|||||||
const { dict, xref } = params;
|
const { dict, xref } = params;
|
||||||
this.data.annotationType = AnnotationType.INK;
|
this.data.annotationType = AnnotationType.INK;
|
||||||
this.data.inkLists = [];
|
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.
|
// We want to be able to add mouse listeners to the annotation.
|
||||||
this.data.noHTML = false;
|
this.data.noHTML = false;
|
||||||
this.data.opacity = dict.get("CA") || 1;
|
this.data.opacity = dict.get("CA") || 1;
|
||||||
@ -4459,17 +4459,30 @@ class InkAnnotation extends MarkupAnnotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static createNewDict(annotation, xref, { apRef, ap }) {
|
static createNewDict(annotation, xref, { apRef, ap }) {
|
||||||
const { color, opacity, paths, outlines, rect, rotation, thickness } =
|
const {
|
||||||
annotation;
|
oldAnnotation,
|
||||||
const ink = new Dict(xref);
|
color,
|
||||||
|
opacity,
|
||||||
|
paths,
|
||||||
|
outlines,
|
||||||
|
rect,
|
||||||
|
rotation,
|
||||||
|
thickness,
|
||||||
|
user,
|
||||||
|
} = annotation;
|
||||||
|
const ink = oldAnnotation || new Dict(xref);
|
||||||
ink.set("Type", Name.get("Annot"));
|
ink.set("Type", Name.get("Annot"));
|
||||||
ink.set("Subtype", Name.get("Ink"));
|
ink.set("Subtype", Name.get("Ink"));
|
||||||
ink.set("CreationDate", `D:${getModificationDate()}`);
|
ink.set(oldAnnotation ? "M" : "CreationDate", `D:${getModificationDate()}`);
|
||||||
ink.set("Rect", rect);
|
ink.set("Rect", rect);
|
||||||
ink.set("InkList", outlines?.points || paths.points);
|
ink.set("InkList", outlines?.points || paths.points);
|
||||||
ink.set("F", 4);
|
ink.set("F", 4);
|
||||||
ink.set("Rotate", rotation);
|
ink.set("Rotate", rotation);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
ink.set("T", stringToAsciiOrUTF16BE(user));
|
||||||
|
}
|
||||||
|
|
||||||
if (outlines) {
|
if (outlines) {
|
||||||
// Free highlight.
|
// Free highlight.
|
||||||
// There's nothing about this in the spec, but it's used when highlighting
|
// There's nothing about this in the spec, but it's used when highlighting
|
||||||
@ -4524,12 +4537,15 @@ class InkAnnotation extends MarkupAnnotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const outline of paths.lines) {
|
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])) {
|
if (isNaN(outline[i])) {
|
||||||
appearanceBuffer.push(
|
appearanceBuffer.push(
|
||||||
`${numberToString(outline[i + 4])} ${numberToString(
|
`${numberToString(outline[i + 4])} ${numberToString(
|
||||||
outline[i + 5]
|
outline[i + 5]
|
||||||
)} m`
|
)} l`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const [c1x, c1y, c2x, c2y, x, y] = outline.slice(i, i + 6);
|
const [c1x, c1y, c2x, c2y, x, y] = outline.slice(i, i + 6);
|
||||||
@ -5006,7 +5022,6 @@ class StampAnnotation extends MarkupAnnotation {
|
|||||||
oldAnnotation ? "M" : "CreationDate",
|
oldAnnotation ? "M" : "CreationDate",
|
||||||
`D:${getModificationDate()}`
|
`D:${getModificationDate()}`
|
||||||
);
|
);
|
||||||
stamp.set("CreationDate", `D:${getModificationDate()}`);
|
|
||||||
stamp.set("Rect", rect);
|
stamp.set("Rect", rect);
|
||||||
stamp.set("F", 4);
|
stamp.set("F", 4);
|
||||||
stamp.set("Border", [0, 0, 0]);
|
stamp.set("Border", [0, 0, 0]);
|
||||||
|
|||||||
@ -2792,6 +2792,8 @@ class CaretAnnotationElement extends AnnotationElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class InkAnnotationElement extends AnnotationElement {
|
class InkAnnotationElement extends AnnotationElement {
|
||||||
|
#polylinesGroupElement = null;
|
||||||
|
|
||||||
#polylines = [];
|
#polylines = [];
|
||||||
|
|
||||||
constructor(parameters) {
|
constructor(parameters) {
|
||||||
@ -2809,6 +2811,38 @@ class InkAnnotationElement extends AnnotationElement {
|
|||||||
: AnnotationEditorType.INK;
|
: AnnotationEditorType.INK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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:
|
||||||
|
return {
|
||||||
|
transform: `rotate(90) translate(${-rect[0]},${rect[1]}) scale(1,-1)`,
|
||||||
|
width: rect[3] - rect[1],
|
||||||
|
height: rect[2] - rect[0],
|
||||||
|
};
|
||||||
|
case 180:
|
||||||
|
return {
|
||||||
|
transform: `rotate(180) translate(${-rect[2]},${rect[1]}) scale(1,-1)`,
|
||||||
|
width: rect[2] - rect[0],
|
||||||
|
height: rect[3] - rect[1],
|
||||||
|
};
|
||||||
|
case 270:
|
||||||
|
return {
|
||||||
|
transform: `rotate(270) translate(${-rect[2]},${rect[3]}) scale(1,-1)`,
|
||||||
|
width: rect[3] - rect[1],
|
||||||
|
height: rect[2] - rect[0],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
transform: `translate(${-rect[0]},${rect[3]}) scale(1,-1)`,
|
||||||
|
width: rect[2] - rect[0],
|
||||||
|
height: rect[3] - rect[1],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.container.classList.add(this.containerClassName);
|
this.container.classList.add(this.containerClassName);
|
||||||
|
|
||||||
@ -2817,47 +2851,31 @@ class InkAnnotationElement extends AnnotationElement {
|
|||||||
const {
|
const {
|
||||||
data: { rect, rotation, inkLists, borderStyle, popupRef },
|
data: { rect, rotation, inkLists, borderStyle, popupRef },
|
||||||
} = this;
|
} = this;
|
||||||
let { width, height } = getRectDims(rect);
|
const { transform, width, height } = this.#getTransform(rotation, rect);
|
||||||
let transform;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
case 180:
|
|
||||||
transform = `rotate(180) translate(${-rect[0] - width},${rect[3] - height}) scale(1,-1)`;
|
|
||||||
break;
|
|
||||||
case 270:
|
|
||||||
transform = `rotate(270) translate(${-rect[0] - width},${rect[3]}) scale(1,-1)`;
|
|
||||||
[width, height] = [height, width];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
transform = `translate(${-rect[0]},${rect[3]}) scale(1,-1)`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const svg = this.svgFactory.create(
|
const svg = this.svgFactory.create(
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
/* skipDimensions = */ true
|
/* 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
|
// 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).
|
// won't be possible to open/close the popup (note e.g. issue 11122).
|
||||||
basePolyline.setAttribute("stroke-width", borderStyle.width || 1);
|
g.setAttribute("stroke-width", borderStyle.width || 1);
|
||||||
basePolyline.setAttribute("stroke", "transparent");
|
g.setAttribute("stroke-linecap", "round");
|
||||||
basePolyline.setAttribute("fill", "transparent");
|
g.setAttribute("stroke-linejoin", "round");
|
||||||
basePolyline.setAttribute("transform", transform);
|
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++) {
|
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);
|
this.#polylines.push(polyline);
|
||||||
polyline.setAttribute("points", inkLists[i].join(","));
|
polyline.setAttribute("points", inkLists[i].join(","));
|
||||||
svg.append(polyline);
|
g.append(polyline);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!popupRef && this.hasPopupData) {
|
if (!popupRef && this.hasPopupData) {
|
||||||
@ -2870,6 +2888,29 @@ class InkAnnotationElement extends AnnotationElement {
|
|||||||
return this.container;
|
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() {
|
getElementsToTriggerPopup() {
|
||||||
return this.#polylines;
|
return this.#polylines;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,14 +108,7 @@ class InkDrawOutliner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.#last.set([x1, y1, x2, y2, x, y], 0);
|
this.#last.set([x1, y1, x2, y2, x, y], 0);
|
||||||
this.#line.push(
|
this.#line.push(...Outline.createBezierPoints(x1, y1, x2, y2, x, y));
|
||||||
(x1 + 5 * x2) / 6,
|
|
||||||
(y1 + 5 * y2) / 6,
|
|
||||||
(5 * x2 + x) / 6,
|
|
||||||
(5 * y2 + y) / 6,
|
|
||||||
(x2 + x) / 2,
|
|
||||||
(y2 + y) / 2
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: {
|
path: {
|
||||||
@ -485,6 +478,51 @@ class InkDrawOutline extends Outline {
|
|||||||
break;
|
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++) {
|
for (let i = 0, ii = lines.length; i < ii; i++) {
|
||||||
newLines.push({
|
newLines.push({
|
||||||
line: rescaleFn(
|
line: rescaleFn(
|
||||||
|
|||||||
@ -97,6 +97,17 @@ class Outline {
|
|||||||
return [x, y];
|
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 };
|
export { Outline };
|
||||||
|
|||||||
@ -66,7 +66,7 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
#hasBeenClicked = false;
|
#hasBeenClicked = false;
|
||||||
|
|
||||||
#initialPosition = null;
|
#initialRect = null;
|
||||||
|
|
||||||
#isEditing = false;
|
#isEditing = false;
|
||||||
|
|
||||||
@ -468,13 +468,13 @@ class AnnotationEditor {
|
|||||||
* @param {number} y - y-translation in page coordinates.
|
* @param {number} y - y-translation in page coordinates.
|
||||||
*/
|
*/
|
||||||
translateInPage(x, y) {
|
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.#translate(this.pageDimensions, x, y);
|
||||||
this.div.scrollIntoView({ block: "nearest" });
|
this.div.scrollIntoView({ block: "nearest" });
|
||||||
}
|
}
|
||||||
|
|
||||||
drag(tx, ty) {
|
drag(tx, ty) {
|
||||||
this.#initialPosition ||= [this.x, this.y];
|
this.#initialRect ||= [this.x, this.y, this.width, this.height];
|
||||||
const {
|
const {
|
||||||
div,
|
div,
|
||||||
parentDimensions: [parentWidth, parentHeight],
|
parentDimensions: [parentWidth, parentHeight],
|
||||||
@ -530,9 +530,16 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
get _hasBeenMoved() {
|
get _hasBeenMoved() {
|
||||||
return (
|
return (
|
||||||
!!this.#initialPosition &&
|
!!this.#initialRect &&
|
||||||
(this.#initialPosition[0] !== this.x ||
|
(this.#initialRect[0] !== this.x || this.#initialRect[1] !== this.y)
|
||||||
this.#initialPosition[1] !== this.y)
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get _hasBeenResized() {
|
||||||
|
return (
|
||||||
|
!!this.#initialRect &&
|
||||||
|
(this.#initialRect[2] !== this.width ||
|
||||||
|
this.#initialRect[3] !== this.height)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -989,6 +996,7 @@ class AnnotationEditor {
|
|||||||
const newX = oppositeX - transfOppositePoint[0];
|
const newX = oppositeX - transfOppositePoint[0];
|
||||||
const newY = oppositeY - transfOppositePoint[1];
|
const newY = oppositeY - transfOppositePoint[1];
|
||||||
|
|
||||||
|
this.#initialRect ||= [this.x, this.y, this.width, this.height];
|
||||||
this.width = newWidth;
|
this.width = newWidth;
|
||||||
this.height = newHeight;
|
this.height = newHeight;
|
||||||
this.x = newX;
|
this.x = newX;
|
||||||
|
|||||||
@ -138,11 +138,44 @@ class InkEditor extends DrawingEditor {
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static async deserialize(data, parent, uiManager) {
|
static async deserialize(data, parent, uiManager) {
|
||||||
|
let initialData = null;
|
||||||
if (data instanceof InkAnnotationElement) {
|
if (data instanceof InkAnnotationElement) {
|
||||||
return null;
|
const {
|
||||||
|
data: {
|
||||||
|
inkLists,
|
||||||
|
rect,
|
||||||
|
rotation,
|
||||||
|
id,
|
||||||
|
color,
|
||||||
|
opacity,
|
||||||
|
borderStyle: { rawWidth: thickness },
|
||||||
|
popupRef,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
page: { pageNumber },
|
||||||
|
},
|
||||||
|
} = data;
|
||||||
|
initialData = data = {
|
||||||
|
annotationType: AnnotationEditorType.INK,
|
||||||
|
color: Array.from(color),
|
||||||
|
thickness,
|
||||||
|
opacity,
|
||||||
|
paths: { points: inkLists },
|
||||||
|
boxes: null,
|
||||||
|
pageIndex: pageNumber - 1,
|
||||||
|
rect: rect.slice(0),
|
||||||
|
rotation,
|
||||||
|
id,
|
||||||
|
deleted: false,
|
||||||
|
popupRef,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.deserialize(data, parent, uiManager);
|
const editor = await super.deserialize(data, parent, uiManager);
|
||||||
|
editor.annotationElementId = data.id || null;
|
||||||
|
editor._initialData = initialData;
|
||||||
|
|
||||||
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
@ -214,9 +247,41 @@ class InkEditor extends DrawingEditor {
|
|||||||
structTreeParentId: this._structTreeParentId,
|
structTreeParentId: this._structTreeParentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isForCopying) {
|
||||||
|
return serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
serialized.id = this.annotationElementId;
|
serialized.id = this.annotationElementId;
|
||||||
return serialized;
|
return serialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#hasElementChanged(serialized) {
|
||||||
|
const { color, thickness, opacity, pageIndex } = this._initialData;
|
||||||
|
return (
|
||||||
|
this._hasBeenMoved ||
|
||||||
|
this._hasBeenResized ||
|
||||||
|
serialized.color.some((c, i) => c !== color[i]) ||
|
||||||
|
serialized.thickness !== thickness ||
|
||||||
|
serialized.opacity !== opacity ||
|
||||||
|
serialized.pageIndex !== pageIndex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
renderAnnotationElement(annotation) {
|
||||||
|
const { points, rect } = this.serializeDraw(/* isForCopying = */ false);
|
||||||
|
annotation.updateEdited({
|
||||||
|
rect,
|
||||||
|
thickness: this._drawingOptions["stroke-width"],
|
||||||
|
points,
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { InkEditor };
|
export { InkEditor };
|
||||||
|
|||||||
@ -921,19 +921,19 @@ class StampEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
#hasElementChanged(serialized) {
|
#hasElementChanged(serialized) {
|
||||||
const {
|
const {
|
||||||
rect,
|
|
||||||
pageIndex,
|
pageIndex,
|
||||||
accessibilityData: { altText },
|
accessibilityData: { altText },
|
||||||
} = this._initialData;
|
} = this._initialData;
|
||||||
|
|
||||||
const isSameRect = serialized.rect.every(
|
|
||||||
(x, i) => Math.abs(x - rect[i]) < 1
|
|
||||||
);
|
|
||||||
const isSamePageIndex = serialized.pageIndex === pageIndex;
|
const isSamePageIndex = serialized.pageIndex === pageIndex;
|
||||||
const isSameAltText = (serialized.accessibilityData?.alt || "") === altText;
|
const isSameAltText = (serialized.accessibilityData?.alt || "") === altText;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSame: isSameRect && isSamePageIndex && isSameAltText,
|
isSame:
|
||||||
|
!this._hasBeenMoved &&
|
||||||
|
!this._hasBeenResized &&
|
||||||
|
isSamePageIndex &&
|
||||||
|
isSameAltText,
|
||||||
isSameAltText,
|
isSameAltText,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -504,14 +504,6 @@ describe("ResetForm action", () => {
|
|||||||
it("must check that the Ink annotation has a popup", async () => {
|
it("must check that the Ink annotation has a popup", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
if (browserName) {
|
|
||||||
// TODO
|
|
||||||
pending(
|
|
||||||
"Re-enable this test when the Ink annotation has been made editable."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
`document.querySelector("[data-annotation-id='25R']").hidden === false`
|
`document.querySelector("[data-annotation-id='25R']").hidden === false`
|
||||||
);
|
);
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import {
|
|||||||
getSelectedEditors,
|
getSelectedEditors,
|
||||||
getSerialized,
|
getSerialized,
|
||||||
hover,
|
hover,
|
||||||
|
isCanvasWhite,
|
||||||
kbBigMoveDown,
|
kbBigMoveDown,
|
||||||
kbBigMoveLeft,
|
kbBigMoveLeft,
|
||||||
kbBigMoveRight,
|
kbBigMoveRight,
|
||||||
@ -988,27 +989,8 @@ describe("FreeText Editor", () => {
|
|||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
await switchToFreeText(page);
|
await switchToFreeText(page);
|
||||||
|
|
||||||
const isEditorWhite = editorRect =>
|
|
||||||
page.evaluate(rect => {
|
|
||||||
const canvas = document.querySelector(".canvasWrapper canvas");
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
rect ||= {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: canvas.width,
|
|
||||||
height: canvas.height,
|
|
||||||
};
|
|
||||||
const { data } = ctx.getImageData(
|
|
||||||
rect.x,
|
|
||||||
rect.y,
|
|
||||||
rect.width,
|
|
||||||
rect.height
|
|
||||||
);
|
|
||||||
return data.every(x => x === 0xff);
|
|
||||||
}, editorRect);
|
|
||||||
|
|
||||||
// The page has been re-rendered but with no freetext annotations.
|
// The page has been re-rendered but with no freetext annotations.
|
||||||
let isWhite = await isEditorWhite();
|
let isWhite = await isCanvasWhite(page, 1);
|
||||||
expect(isWhite).withContext(`In ${browserName}`).toBeTrue();
|
expect(isWhite).withContext(`In ${browserName}`).toBeTrue();
|
||||||
|
|
||||||
let editorIds = await getEditors(page, "freeText");
|
let editorIds = await getEditors(page, "freeText");
|
||||||
@ -1066,7 +1048,7 @@ describe("FreeText Editor", () => {
|
|||||||
editorIds = await getEditors(page, "freeText");
|
editorIds = await getEditors(page, "freeText");
|
||||||
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
|
||||||
|
|
||||||
isWhite = await isEditorWhite(editorRect);
|
isWhite = await isCanvasWhite(page, 1, editorRect);
|
||||||
expect(isWhite).withContext(`In ${browserName}`).toBeTrue();
|
expect(isWhite).withContext(`In ${browserName}`).toBeTrue();
|
||||||
|
|
||||||
// Check we've now a div containing the text.
|
// Check we've now a div containing the text.
|
||||||
|
|||||||
@ -17,9 +17,14 @@ import {
|
|||||||
awaitPromise,
|
awaitPromise,
|
||||||
closePages,
|
closePages,
|
||||||
createPromise,
|
createPromise,
|
||||||
|
dragAndDropAnnotation,
|
||||||
|
getAnnotationSelector,
|
||||||
|
getEditors,
|
||||||
getEditorSelector,
|
getEditorSelector,
|
||||||
getRect,
|
getRect,
|
||||||
getSelectedEditors,
|
getSelectedEditors,
|
||||||
|
getSerialized,
|
||||||
|
isCanvasWhite,
|
||||||
kbRedo,
|
kbRedo,
|
||||||
kbSelectAll,
|
kbSelectAll,
|
||||||
kbUndo,
|
kbUndo,
|
||||||
@ -27,8 +32,10 @@ import {
|
|||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
switchToEditor,
|
switchToEditor,
|
||||||
waitForNoElement,
|
waitForNoElement,
|
||||||
|
waitForSelectedEditor,
|
||||||
waitForSerialized,
|
waitForSerialized,
|
||||||
waitForStorageEntries,
|
waitForStorageEntries,
|
||||||
|
waitForTimeout,
|
||||||
} from "./test_utils.mjs";
|
} from "./test_utils.mjs";
|
||||||
|
|
||||||
const waitForPointerUp = page =>
|
const waitForPointerUp = page =>
|
||||||
@ -580,7 +587,7 @@ describe("Ink Editor", () => {
|
|||||||
await closePages(pages);
|
await closePages(pages);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must check that the color has been changed", async () => {
|
it("must check that the deletion has been undid", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
await switchToInk(page);
|
await switchToInk(page);
|
||||||
@ -669,4 +676,157 @@ describe("Ink Editor", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Ink (update existing)", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
pages = await loadAndWait("inks.pdf", ".annotationEditorLayer");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must update an existing annotation", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
const annotationsRect = await page.evaluate(() => {
|
||||||
|
let xm = Infinity,
|
||||||
|
xM = -Infinity,
|
||||||
|
ym = Infinity,
|
||||||
|
yM = -Infinity;
|
||||||
|
for (const el of document.querySelectorAll(
|
||||||
|
"section.inkAnnotation"
|
||||||
|
)) {
|
||||||
|
const { x, y, width, height } = el.getBoundingClientRect();
|
||||||
|
xm = Math.min(xm, x);
|
||||||
|
xM = Math.max(xM, x + width);
|
||||||
|
ym = Math.min(ym, y);
|
||||||
|
yM = Math.max(yM, y + height);
|
||||||
|
}
|
||||||
|
return { x: xm, y: ym, width: xM - xm, height: yM - ym };
|
||||||
|
});
|
||||||
|
|
||||||
|
await switchToInk(page);
|
||||||
|
|
||||||
|
// The page has been re-rendered but with no ink annotations.
|
||||||
|
let isWhite = await isCanvasWhite(page, 1, annotationsRect);
|
||||||
|
expect(isWhite).withContext(`In ${browserName}`).toBeTrue();
|
||||||
|
|
||||||
|
let editorIds = await getEditors(page, "ink");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(15);
|
||||||
|
|
||||||
|
const pdfjsA = getEditorSelector(0);
|
||||||
|
const editorRect = await getRect(page, pdfjsA);
|
||||||
|
await page.mouse.click(
|
||||||
|
editorRect.x + editorRect.width / 2,
|
||||||
|
editorRect.y + editorRect.height / 2
|
||||||
|
);
|
||||||
|
await waitForSelectedEditor(page, pdfjsA);
|
||||||
|
|
||||||
|
const red = "#ff0000";
|
||||||
|
page.evaluate(value => {
|
||||||
|
window.PDFViewerApplication.eventBus.dispatch(
|
||||||
|
"switchannotationeditorparams",
|
||||||
|
{
|
||||||
|
source: null,
|
||||||
|
type: window.pdfjsLib.AnnotationEditorParamsType.INK_COLOR,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, red);
|
||||||
|
|
||||||
|
const serialized = await getSerialized(page);
|
||||||
|
expect(serialized.length).withContext(`In ${browserName}`).toEqual(1);
|
||||||
|
expect(serialized[0].color).toEqual([255, 0, 0]);
|
||||||
|
|
||||||
|
// Disable editing mode.
|
||||||
|
await switchToInk(page, /* disable = */ true);
|
||||||
|
|
||||||
|
// We want to check that the editor is displayed but not the original
|
||||||
|
// canvas.
|
||||||
|
editorIds = await getEditors(page, "ink");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
|
||||||
|
|
||||||
|
isWhite = await isCanvasWhite(page, 1, editorRect);
|
||||||
|
expect(isWhite).withContext(`In ${browserName}`).toBeTrue();
|
||||||
|
|
||||||
|
// Check we've now a svg with a red stroke.
|
||||||
|
await page.waitForSelector("svg[stroke = '#ff0000']", {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-enable editing mode.
|
||||||
|
await switchToInk(page);
|
||||||
|
await page.focus(".annotationEditorLayer");
|
||||||
|
|
||||||
|
await kbUndo(page);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
editorIds = await getEditors(page, "ink");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(15);
|
||||||
|
|
||||||
|
// Undo again.
|
||||||
|
await kbUndo(page);
|
||||||
|
// Nothing should happen, it's why we can't wait for something
|
||||||
|
// specific!
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
await waitForTimeout(200);
|
||||||
|
|
||||||
|
// We check that the editor hasn't been removed.
|
||||||
|
editorIds = await getEditors(page, "ink");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(15);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Ink (move existing)", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
pages = await loadAndWait("inks.pdf", getAnnotationSelector("277R"));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must move an annotation", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await page.click(getAnnotationSelector("277R"), { count: 2 });
|
||||||
|
const edgeB = getEditorSelector(10);
|
||||||
|
await waitForSelectedEditor(page, edgeB);
|
||||||
|
|
||||||
|
const editorIds = await getEditors(page, "ink");
|
||||||
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(15);
|
||||||
|
|
||||||
|
// All the current annotations should be serialized as null objects
|
||||||
|
// because they haven't been edited yet.
|
||||||
|
const serialized = await getSerialized(page);
|
||||||
|
expect(serialized).withContext(`In ${browserName}`).toEqual([]);
|
||||||
|
|
||||||
|
const editorRect = await page.$eval(edgeB, el => {
|
||||||
|
const { x, y, width, height } = el.getBoundingClientRect();
|
||||||
|
return { x, y, width, height };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select the annotation we want to move.
|
||||||
|
await page.mouse.click(editorRect.x + 2, editorRect.y + 2);
|
||||||
|
await waitForSelectedEditor(page, edgeB);
|
||||||
|
|
||||||
|
await dragAndDropAnnotation(
|
||||||
|
page,
|
||||||
|
editorRect.x + editorRect.width / 2,
|
||||||
|
editorRect.y + editorRect.height / 2,
|
||||||
|
100,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -781,6 +781,28 @@ function waitForNoElement(page, selector) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCanvasWhite(page, pageNumber, rectangle) {
|
||||||
|
return page.evaluate(
|
||||||
|
(rect, pageN) => {
|
||||||
|
const canvas = document.querySelector(
|
||||||
|
`.page[data-page-number = "${pageN}"] .canvasWrapper canvas`
|
||||||
|
);
|
||||||
|
const canvasRect = canvas.getBoundingClientRect();
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
rect ||= canvasRect;
|
||||||
|
const { data } = ctx.getImageData(
|
||||||
|
rect.x - canvasRect.x,
|
||||||
|
rect.y - canvasRect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height
|
||||||
|
);
|
||||||
|
return new Uint32Array(data.buffer).every(x => x === 0xffffffff);
|
||||||
|
},
|
||||||
|
rectangle,
|
||||||
|
pageNumber
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
applyFunctionToEditor,
|
applyFunctionToEditor,
|
||||||
awaitPromise,
|
awaitPromise,
|
||||||
@ -806,6 +828,7 @@ export {
|
|||||||
getSerialized,
|
getSerialized,
|
||||||
getSpanRectFromText,
|
getSpanRectFromText,
|
||||||
hover,
|
hover,
|
||||||
|
isCanvasWhite,
|
||||||
isVisible,
|
isVisible,
|
||||||
kbBigMoveDown,
|
kbBigMoveDown,
|
||||||
kbBigMoveLeft,
|
kbBigMoveLeft,
|
||||||
|
|||||||
2
test/pdfs/.gitignore
vendored
2
test/pdfs/.gitignore
vendored
@ -685,3 +685,5 @@
|
|||||||
!issue19120.pdf
|
!issue19120.pdf
|
||||||
!bug1934157.pdf
|
!bug1934157.pdf
|
||||||
!rotated_ink.pdf
|
!rotated_ink.pdf
|
||||||
|
!inks.pdf
|
||||||
|
!inks_basic.pdf
|
||||||
|
|||||||
BIN
test/pdfs/inks.pdf
Normal file
BIN
test/pdfs/inks.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/inks_basic.pdf
Normal file
BIN
test/pdfs/inks_basic.pdf
Normal file
Binary file not shown.
@ -10872,5 +10872,334 @@
|
|||||||
"talos": false,
|
"talos": false,
|
||||||
"type": "eq",
|
"type": "eq",
|
||||||
"link": true
|
"link": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "inks_basic-editor-save-print",
|
||||||
|
"file": "pdfs/inks_basic.pdf",
|
||||||
|
"md5": "2615610de59b4849993dcc2614ebb266",
|
||||||
|
"rounds": 1,
|
||||||
|
"lastPage": 1,
|
||||||
|
"type": "eq",
|
||||||
|
"save": true,
|
||||||
|
"print": true,
|
||||||
|
"annotationStorage": {
|
||||||
|
"pdfjs_internal_editor_0": {
|
||||||
|
"annotationType": 15,
|
||||||
|
"color": [241, 17, 41],
|
||||||
|
"opacity": 1,
|
||||||
|
"thickness": 20,
|
||||||
|
"paths": {
|
||||||
|
"lines": [
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
114,
|
||||||
|
691.5,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
159.75,
|
||||||
|
631.5
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"points": [[114, 691.5, 159.75, 631.5]]
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [
|
||||||
|
104.0000076523194, 621.5000049701105, 169.7500193485847,
|
||||||
|
701.5000020036331
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "16R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_1": {
|
||||||
|
"id": "17R",
|
||||||
|
"deleted": true,
|
||||||
|
"pageIndex": 0,
|
||||||
|
"popupRef": ""
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_2": {
|
||||||
|
"annotationType": 15,
|
||||||
|
"color": [0, 0, 0],
|
||||||
|
"opacity": 1,
|
||||||
|
"thickness": 3,
|
||||||
|
"paths": {
|
||||||
|
"lines": [
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
475.0961608886719,
|
||||||
|
647.4615478515625,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
520.8461303710938,
|
||||||
|
587.4615478515625
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
475.0961608886719, 647.4615478515625, 520.8461303710938,
|
||||||
|
587.4615478515625
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [
|
||||||
|
473.59618278650134, 585.9615278473268, 522.3461772570244,
|
||||||
|
648.9615135559669
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "18R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_3": {
|
||||||
|
"annotationType": 15,
|
||||||
|
"color": [250, 23, 28],
|
||||||
|
"opacity": 0.55,
|
||||||
|
"thickness": 3,
|
||||||
|
"paths": {
|
||||||
|
"lines": [
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
79.04861450195312,
|
||||||
|
532.353759765625,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
198.18736267089844,
|
||||||
|
378.6045837402344
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
79.04861450195312, 532.353759765625, 198.18736267089844,
|
||||||
|
378.6045837402344
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [
|
||||||
|
77.54861622361037, 377.10462435392236, 199.6873893210521,
|
||||||
|
533.8537948498359
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "19R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_4": {
|
||||||
|
"annotationType": 15,
|
||||||
|
"color": [70, 108, 241],
|
||||||
|
"opacity": 0.7,
|
||||||
|
"thickness": 20,
|
||||||
|
"paths": {
|
||||||
|
"lines": [
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
273.6300048828125,
|
||||||
|
535.47998046875,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
319.3800048828125,
|
||||||
|
475.4800109863281
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
273.6300048828125, 535.47998046875, 319.3800048828125,
|
||||||
|
475.4800109863281
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [
|
||||||
|
263.629992209948, 465.48002000955444, 329.38000390621335,
|
||||||
|
545.4799757370582
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "20R"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "inks_basic-editor-print",
|
||||||
|
"file": "pdfs/inks_basic.pdf",
|
||||||
|
"md5": "2615610de59b4849993dcc2614ebb266",
|
||||||
|
"rounds": 1,
|
||||||
|
"lastPage": 1,
|
||||||
|
"type": "eq",
|
||||||
|
"print": true,
|
||||||
|
"annotationStorage": {
|
||||||
|
"pdfjs_internal_editor_0": {
|
||||||
|
"annotationType": 15,
|
||||||
|
"color": [241, 17, 41],
|
||||||
|
"opacity": 1,
|
||||||
|
"thickness": 20,
|
||||||
|
"paths": {
|
||||||
|
"lines": [
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
114,
|
||||||
|
691.5,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
159.75,
|
||||||
|
631.5
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"points": [[114, 691.5, 159.75, 631.5]]
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [
|
||||||
|
104.0000076523194, 621.5000049701105, 169.7500193485847,
|
||||||
|
701.5000020036331
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "16R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_1": {
|
||||||
|
"id": "17R",
|
||||||
|
"deleted": true,
|
||||||
|
"pageIndex": 0,
|
||||||
|
"popupRef": ""
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_2": {
|
||||||
|
"annotationType": 15,
|
||||||
|
"color": [0, 0, 0],
|
||||||
|
"opacity": 1,
|
||||||
|
"thickness": 3,
|
||||||
|
"paths": {
|
||||||
|
"lines": [
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
475.0961608886719,
|
||||||
|
647.4615478515625,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
520.8461303710938,
|
||||||
|
587.4615478515625
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
475.0961608886719, 647.4615478515625, 520.8461303710938,
|
||||||
|
587.4615478515625
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [
|
||||||
|
473.59618278650134, 585.9615278473268, 522.3461772570244,
|
||||||
|
648.9615135559669
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "18R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_3": {
|
||||||
|
"annotationType": 15,
|
||||||
|
"color": [250, 23, 28],
|
||||||
|
"opacity": 0.55,
|
||||||
|
"thickness": 3,
|
||||||
|
"paths": {
|
||||||
|
"lines": [
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
79.04861450195312,
|
||||||
|
532.353759765625,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
198.18736267089844,
|
||||||
|
378.6045837402344
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
79.04861450195312, 532.353759765625, 198.18736267089844,
|
||||||
|
378.6045837402344
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [
|
||||||
|
77.54861622361037, 377.10462435392236, 199.6873893210521,
|
||||||
|
533.8537948498359
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "19R"
|
||||||
|
},
|
||||||
|
"pdfjs_internal_editor_4": {
|
||||||
|
"annotationType": 15,
|
||||||
|
"color": [70, 108, 241],
|
||||||
|
"opacity": 0.7,
|
||||||
|
"thickness": 20,
|
||||||
|
"paths": {
|
||||||
|
"lines": [
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
273.6300048828125,
|
||||||
|
535.47998046875,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
319.3800048828125,
|
||||||
|
475.4800109863281
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
273.6300048828125, 535.47998046875, 319.3800048828125,
|
||||||
|
475.4800109863281
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pageIndex": 0,
|
||||||
|
"rect": [
|
||||||
|
263.629992209948, 465.48002000955444, 329.38000390621335,
|
||||||
|
545.4799757370582
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"structTreeParentId": null,
|
||||||
|
"id": "20R"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user