Merge pull request #16494 from calixteman/save_more_info_for_ink

[Editor] Add few more info when saving ink data (thickness, opacity, …)
This commit is contained in:
calixteman 2023-05-31 15:56:45 +02:00 committed by GitHub
commit a2118f52b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 181 additions and 176 deletions

View File

@ -4056,7 +4056,7 @@ class InkAnnotation extends MarkupAnnotation {
}
static createNewDict(annotation, xref, { apRef, ap }) {
const { paths, rect, rotation } = annotation;
const { color, opacity, paths, rect, rotation, thickness } = annotation;
const ink = new Dict(xref);
ink.set("Type", Name.get("Annot"));
ink.set("Subtype", Name.get("Ink"));
@ -4067,9 +4067,22 @@ class InkAnnotation extends MarkupAnnotation {
paths.map(p => p.points)
);
ink.set("F", 4);
ink.set("Border", [0, 0, 0]);
ink.set("Rotate", rotation);
// Line thickness.
const bs = new Dict(xref);
ink.set("BS", bs);
bs.set("W", thickness);
// Color.
ink.set(
"C",
Array.from(color, c => c / 255)
);
// Opacity.
ink.set("CA", opacity);
const n = new Dict(xref);
ink.set("AP", n);
@ -4123,14 +4136,9 @@ class InkAnnotation extends MarkupAnnotation {
appearanceStreamDict.set("FormType", 1);
appearanceStreamDict.set("Subtype", Name.get("Form"));
appearanceStreamDict.set("Type", Name.get("XObject"));
appearanceStreamDict.set("BBox", [0, 0, w, h]);
appearanceStreamDict.set("BBox", rect);
appearanceStreamDict.set("Length", appearance.length);
if (rotation) {
const matrix = getRotationMatrix(rotation, w, h);
appearanceStreamDict.set("Matrix", matrix);
}
if (opacity !== 1) {
const resources = new Dict(xref);
const extGState = new Dict(xref);

View File

@ -910,137 +910,123 @@ class InkEditor extends AnnotationEditor {
return path2D;
}
static #toPDFCoordinates(points, rect, rotation) {
const [blX, blY, trX, trY] = rect;
switch (rotation) {
case 0:
for (let i = 0, ii = points.length; i < ii; i += 2) {
points[i] += blX;
points[i + 1] = trY - points[i + 1];
}
break;
case 90:
for (let i = 0, ii = points.length; i < ii; i += 2) {
const x = points[i];
points[i] = points[i + 1] + blX;
points[i + 1] = x + blY;
}
break;
case 180:
for (let i = 0, ii = points.length; i < ii; i += 2) {
points[i] = trX - points[i];
points[i + 1] += blY;
}
break;
case 270:
for (let i = 0, ii = points.length; i < ii; i += 2) {
const x = points[i];
points[i] = trX - points[i + 1];
points[i + 1] = trY - x;
}
break;
default:
throw new Error("Invalid rotation");
}
return points;
}
static #fromPDFCoordinates(points, rect, rotation) {
const [blX, blY, trX, trY] = rect;
switch (rotation) {
case 0:
for (let i = 0, ii = points.length; i < ii; i += 2) {
points[i] -= blX;
points[i + 1] = trY - points[i + 1];
}
break;
case 90:
for (let i = 0, ii = points.length; i < ii; i += 2) {
const x = points[i];
points[i] = points[i + 1] - blY;
points[i + 1] = x - blX;
}
break;
case 180:
for (let i = 0, ii = points.length; i < ii; i += 2) {
points[i] = trX - points[i];
points[i + 1] -= blY;
}
break;
case 270:
for (let i = 0, ii = points.length; i < ii; i += 2) {
const x = points[i];
points[i] = trY - points[i + 1];
points[i + 1] = trX - x;
}
break;
default:
throw new Error("Invalid rotation");
}
return points;
}
/**
* Transform and serialize the paths.
* @param {number} s - scale factor
* @param {number} tx - abscissa of the translation
* @param {number} ty - ordinate of the translation
* @param {number} h - height of the bounding box
* @param {Array<number>} rect - the bounding box of the annotation
*/
#serializePaths(s, tx, ty, h) {
const NUMBER_OF_POINTS_ON_BEZIER_CURVE = 4;
#serializePaths(s, tx, ty, rect) {
const paths = [];
const padding = this.thickness / 2;
let buffer, points;
const shiftX = s * tx + padding;
const shiftY = s * ty + padding;
for (const bezier of this.paths) {
buffer = [];
points = [];
for (let i = 0, ii = bezier.length; i < ii; i++) {
const [first, control1, control2, second] = bezier[i];
const p10 = s * (first[0] + tx) + padding;
const p11 = h - s * (first[1] + ty) - padding;
const p20 = s * (control1[0] + tx) + padding;
const p21 = h - s * (control1[1] + ty) - padding;
const p30 = s * (control2[0] + tx) + padding;
const p31 = h - s * (control2[1] + ty) - padding;
const p40 = s * (second[0] + tx) + padding;
const p41 = h - s * (second[1] + ty) - padding;
const buffer = [];
const points = [];
for (let j = 0, jj = bezier.length; j < jj; j++) {
const [first, control1, control2, second] = bezier[j];
const p10 = s * first[0] + shiftX;
const p11 = s * first[1] + shiftY;
const p20 = s * control1[0] + shiftX;
const p21 = s * control1[1] + shiftY;
const p30 = s * control2[0] + shiftX;
const p31 = s * control2[1] + shiftY;
const p40 = s * second[0] + shiftX;
const p41 = s * second[1] + shiftY;
if (i === 0) {
if (j === 0) {
buffer.push(p10, p11);
points.push(p10, p11);
}
buffer.push(p20, p21, p30, p31, p40, p41);
this.#extractPointsOnBezier(
p10,
p11,
p20,
p21,
p30,
p31,
p40,
p41,
NUMBER_OF_POINTS_ON_BEZIER_CURVE,
points
);
points.push(p20, p21);
if (j === jj - 1) {
points.push(p40, p41);
}
}
paths.push({ bezier: buffer, points });
paths.push({
bezier: InkEditor.#toPDFCoordinates(buffer, rect, this.rotation),
points: InkEditor.#toPDFCoordinates(points, rect, this.rotation),
});
}
return paths;
}
/**
* Extract n-1 points from the cubic Bezier curve.
* @param {number} p10
* @param {number} p11
* @param {number} p20
* @param {number} p21
* @param {number} p30
* @param {number} p31
* @param {number} p40
* @param {number} p41
* @param {number} n
* @param {Array<number>} points
* @returns {undefined}
*/
#extractPointsOnBezier(p10, p11, p20, p21, p30, p31, p40, p41, n, points) {
// If we can save few points thanks to the flatness we must do it.
if (this.#isAlmostFlat(p10, p11, p20, p21, p30, p31, p40, p41)) {
points.push(p40, p41);
return;
}
// Apply the de Casteljau's algorithm in order to get n points belonging
// to the Bezier's curve:
// https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
// The first point is the last point of the previous Bezier curve
// so no need to push the first point.
for (let i = 1; i < n - 1; i++) {
const t = i / n;
const mt = 1 - t;
let q10 = t * p10 + mt * p20;
let q11 = t * p11 + mt * p21;
let q20 = t * p20 + mt * p30;
let q21 = t * p21 + mt * p31;
const q30 = t * p30 + mt * p40;
const q31 = t * p31 + mt * p41;
q10 = t * q10 + mt * q20;
q11 = t * q11 + mt * q21;
q20 = t * q20 + mt * q30;
q21 = t * q21 + mt * q31;
q10 = t * q10 + mt * q20;
q11 = t * q11 + mt * q21;
points.push(q10, q11);
}
points.push(p40, p41);
}
/**
* Check if a cubic Bezier curve is almost flat.
* @param {number} p10
* @param {number} p11
* @param {number} p20
* @param {number} p21
* @param {number} p30
* @param {number} p31
* @param {number} p40
* @param {number} p41
* @returns {boolean}
*/
#isAlmostFlat(p10, p11, p20, p21, p30, p31, p40, p41) {
// For reference:
// https://jeremykun.com/tag/bezier-curves/
const tol = 10;
const ax = (3 * p20 - 2 * p10 - p40) ** 2;
const ay = (3 * p21 - 2 * p11 - p41) ** 2;
const bx = (3 * p30 - p10 - 2 * p40) ** 2;
const by = (3 * p31 - p11 - 2 * p41) ** 2;
return Math.max(ax, bx) + Math.max(ay, by) <= tol;
}
/**
* Get the bounding box containing all the paths.
* @returns {Array<number>}
@ -1161,18 +1147,21 @@ class InkEditor extends AnnotationEditor {
editor.#realWidth = Math.round(width);
editor.#realHeight = Math.round(height);
for (const { bezier } of data.paths) {
const { paths, rect, rotation } = data;
for (let { bezier } of paths) {
bezier = InkEditor.#fromPDFCoordinates(bezier, rect, rotation);
const path = [];
editor.paths.push(path);
let p0 = scaleFactor * (bezier[0] - padding);
let p1 = scaleFactor * (height - bezier[1] - padding);
let p1 = scaleFactor * (bezier[1] - padding);
for (let i = 2, ii = bezier.length; i < ii; i += 6) {
const p10 = scaleFactor * (bezier[i] - padding);
const p11 = scaleFactor * (height - bezier[i + 1] - padding);
const p11 = scaleFactor * (bezier[i + 1] - padding);
const p20 = scaleFactor * (bezier[i + 2] - padding);
const p21 = scaleFactor * (height - bezier[i + 3] - padding);
const p21 = scaleFactor * (bezier[i + 3] - padding);
const p30 = scaleFactor * (bezier[i + 4] - padding);
const p31 = scaleFactor * (height - bezier[i + 5] - padding);
const p31 = scaleFactor * (bezier[i + 5] - padding);
path.push([
[p0, p1],
[p10, p11],
@ -1201,9 +1190,6 @@ class InkEditor extends AnnotationEditor {
}
const rect = this.getRect(0, 0);
const height =
this.rotation % 180 === 0 ? rect[3] - rect[1] : rect[2] - rect[0];
const color = AnnotationEditor._colorManager.convert(this.ctx.strokeStyle);
return {
@ -1215,7 +1201,7 @@ class InkEditor extends AnnotationEditor {
this.scaleFactor / this.parentScale,
this.translationX,
this.translationY,
height
rect
),
pageIndex: this.pageIndex,
rect,

View File

@ -6978,18 +6978,17 @@
"opacity": 1,
"paths": [{
"bezier": [
1.5, 25.727771084724367, 2.8040804485100495, 27.031851533234402,
5.396811581133676, 23.25556095123241, 6, 22.727771084724367,
10.45407020558315, 18.830459654839103, 15.981183968598401,
16.364531104350363, 21, 13.227771084724367, 25.88795894206055,
10.172796745936523, 37.988543516372076, 5.739227568352277, 42,
1.7277710847243668
73, 560.2277710847244, 74.30408044851005, 561.5318515332344,
76.89681158113368, 557.7555609512324, 77.5, 557.2277710847244,
81.95407020558315, 553.3304596548392, 87.4811839685984, 550.8645311043504,
92.5, 547.7277710847244, 97.38795894206055, 544.6727967459365,
109.48854351637208, 540.2392275683522, 113.5, 536.2277710847244
],
"points": [
1.5, 25.727771084724367, 5.225791198862495, 23.602568747729173,
4.012834511116397, 24.914722452856147, 6, 22.727771084724367, 21,
13.227771084724367, 37.71378602219673, 4.78737352236285,
31.828688421912233, 7.836451889039392, 42, 1.7277710847243668
73, 560.2277710847244, 76.7257911988625, 558.1025687477292,
75.5128345111164, 559.4147224528562, 77.5, 557.2277710847244,
92.5, 547.7277710847244, 109.21378602219673, 539.2873735223628,
103.32868842191223, 542.3364518890394, 113.5, 536.2277710847244
]
}],
"pageIndex": 0,
@ -7057,101 +7056,113 @@
},
"pdfjs_internal_editor_21": {
"annotationType": 15,
"color": [0, 0, 0],
"color": [255, 0, 0],
"thickness": 1,
"opacity": 1,
"paths": [
{
"bezier": [
0.5, 15.653846153846189, 0.5, 10.612792605955292,
2.221156193659856, 5.960961418318131, 2.221156193659856,
0.7371591421274406
417.61538461538464, 520.3461538461538, 419.15384615384613,
520.3461538461538, 421.0769230769231, 520.3461538461538,
423.38461538461536, 520.3461538461538, 425.6923076923077,
520.3461538461538, 429.15384615384613, 519.9615384615385,
433.7692307692308, 519.1923076923076
],
"points": [
0.5, 15.653846153846189, 2.221156193659856, 0.7371591421274406
417.61538461538464, 520.3461538461538, 419.15384615384613,
520.3461538461538, 425.6923076923077, 520.3461538461538,
433.7692307692308, 519.1923076923076
]
}
],
"pageIndex": 0,
"rect": [
416.53846153846155, 561.8076923076923, 419.41346388596753,
577.9615384615385
417.11538461538464, 510.46153846153845, 434.42307692307696,
520.8461538461538
],
"rotation": 0
},
"pdfjs_internal_editor_23": {
"annotationType": 15,
"color": [0, 0, 0],
"color": [0, 255, 0],
"thickness": 1,
"opacity": 1,
"paths": [
{
"bezier": [
0.5, 18.538461538461547, 0.5, 12.869221974582576,
3.9307267310416893, 5.207607308237302, 1.6538461538461537,
0.653846153846164
449.92307692307696, 526.6538461538462, 449.92307692307696,
527.423076923077, 449.6346153846154, 528.8653846153846,
449.0576923076924, 530.9807692307693, 448.4807692307693,
533.0961538461539, 447.8076923076924, 536.6538461538462,
447.0384615384616, 541.6538461538462
],
"points": [
0.5, 18.538461538461547, 2.434116685812059, 4.572198481030599,
1.9307532933714027, 9.17784944259592, 1.6538461538461537,
0.653846153846164
449.92307692307696, 526.6538461538462, 449.92307692307696,
527.423076923077, 448.4807692307693, 533.0961538461539,
447.0384615384616, 541.6538461538462
]
}
],
"pageIndex": 0,
"rect": [
390.00000000000006, 543.4615384615386, 409.0384615384616,
546.9134638859676
446.5384615384616, 526.1538461538462, 456.92307692307696,
542.3076923076924
],
"rotation": 90
},
"pdfjs_internal_editor_25": {
"annotationType": 15,
"color": [0, 0, 0],
"color": [0, 0, 255],
"thickness": 1,
"opacity": 1,
"paths": [
{
"bezier": [
0.5, 24.307692307692264, 0.5, 16.218230266280443,
1.6442331167367787, 8.976323168614734, 1.6442331167367787,
0.8509134145882982
482.8461538461538, 511.6538461538462, 482.07692307692304,
511.6538461538462, 480.53846153846155, 511.6538461538462,
478.23076923076917, 511.6538461538462, 475.9230769230769,
511.6538461538462, 472.46153846153845, 511.6538461538462,
467.8461538461538, 511.6538461538462
],
"points": [
0.5, 24.307692307692264, 1.6442331167367787, 0.8509134145882982
482.8461538461538, 511.6538461538462, 482.07692307692304,
511.6538461538462, 475.9230769230769, 511.6538461538462,
467.8461538461538, 511.6538461538462
]
}
],
"pageIndex": 0,
"rect": [
422.7788438063401, 515.7692307692307, 425.07692307692304,
540.576923076923
467.1923076923077, 511.1538461538462, 483.3461538461538,
521.5384615384615
],
"rotation": 180
},
"pdfjs_internal_editor_27": {
"annotationType": 15,
"color": [0, 0, 0],
"color": [0, 255, 255],
"thickness": 1,
"opacity": 1,
"paths": [
{
"bezier": [
0.5, 32.96153846153845, 4.262222952239026, 32.96153846153845,
2.8076923076923075, 4.355429108316972, 2.8076923076923075,
0.6538461538461462
445.9230769230769, 509.3846153846154, 445.5384615384615,
509.3846153846154, 445.15384615384613, 508.1346153846154,
444.7692307692307, 505.6346153846154, 444.38461538461536,
503.1346153846154, 443.23076923076917, 499.00000000000006,
441.30769230769226, 493.2307692307693
],
"points": [
0.5, 32.96153846153845, 2.9761779872739975, 7.263528385840449,
3.0646797609357885, 18.195785915618856, 2.8076923076923075,
0.6538461538461462
445.9230769230769, 509.3846153846154, 445.5384615384615,
509.3846153846154, 444.38461538461536, 503.1346153846154,
441.30769230769226, 493.2307692307693
]
}
],
"pageIndex": 0,
"rect": [
425.6538461538462, 553.7403822678785, 459.11538461538464,
557.7692307692307
436.03846153846155, 492.5769230769231, 446.4230769230769,
509.8846153846154
],
"rotation": 270
}

View File

@ -4293,15 +4293,15 @@ describe("annotation", function () {
expect(base).toEqual(
"1 0 obj\n" +
"<< /Type /Annot /Subtype /Ink /CreationDate (date) /Rect [12 34 56 78] " +
"/InkList [[1 2 3 4 5 6 7 8] [91 92 93 94 95 96 97 98]] /F 4 /Border [0 0 0] " +
"/Rotate 0 /AP << /N 2 0 R>>>>\n" +
"/InkList [[1 2 3 4 5 6 7 8] [91 92 93 94 95 96 97 98]] /F 4 " +
"/Rotate 0 /BS << /W 1>> /C [0 0 0] /CA 1 /AP << /N 2 0 R>>>>\n" +
"endobj\n"
);
const appearance = data.dependencies[0].data;
expect(appearance).toEqual(
"2 0 obj\n" +
"<< /FormType 1 /Subtype /Form /Type /XObject /BBox [0 0 44 44] /Length 129>> stream\n" +
"<< /FormType 1 /Subtype /Form /Type /XObject /BBox [12 34 56 78] /Length 129>> stream\n" +
"1 w 1 J 1 j\n" +
"0 G\n" +
"10 11 m\n" +
@ -4354,15 +4354,15 @@ describe("annotation", function () {
expect(base).toEqual(
"1 0 obj\n" +
"<< /Type /Annot /Subtype /Ink /CreationDate (date) /Rect [12 34 56 78] " +
"/InkList [[1 2 3 4 5 6 7 8] [91 92 93 94 95 96 97 98]] /F 4 /Border [0 0 0] " +
"/Rotate 0 /AP << /N 2 0 R>>>>\n" +
"/InkList [[1 2 3 4 5 6 7 8] [91 92 93 94 95 96 97 98]] /F 4 " +
"/Rotate 0 /BS << /W 1>> /C [0 0 0] /CA 0.12 /AP << /N 2 0 R>>>>\n" +
"endobj\n"
);
const appearance = data.dependencies[0].data;
expect(appearance).toEqual(
"2 0 obj\n" +
"<< /FormType 1 /Subtype /Form /Type /XObject /BBox [0 0 44 44] /Length 136 /Resources " +
"<< /FormType 1 /Subtype /Form /Type /XObject /BBox [12 34 56 78] /Length 136 /Resources " +
"<< /ExtGState << /R0 << /CA 0.12 /Type /ExtGState>>>>>>>> stream\n" +
"1 w 1 J 1 j\n" +
"0 G\n" +