mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
[Editor] Add few more info when saving ink data (thickness, opacity, ...)
Fix the InkList entry: the coordinates were relative to the page and not to the bounding box of the annotation.
This commit is contained in:
parent
071e6bc7e7
commit
133d103186
4 changed files with 181 additions and 176 deletions
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue