[api-minor] Include line endings in Line/Polyline Annotation-data (issue 14896)

Please refer to:
 - https://web.archive.org/web/20220309040754if_/https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf#G11.2109792
 - https://web.archive.org/web/20220309040754if_/https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf#G11.2096489
 - https://web.archive.org/web/20220309040754if_/https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf#G11.2096447

Note that we still won't attempt to use the /LE-data when creating fallback appearance streams, as mentioned in PR 13448, since custom line endings aren't common enough to warrant the added complexity.
Finally, note that according to the PDF specification we should *potentially* also take the line endings into account for FreeText Annotations. However, in that case their use is conditional on other parameters that we currently don't support.
This commit is contained in:
Jonas Jenwald 2022-05-10 17:36:29 +02:00
parent 8dc836d105
commit 6bcc5b615d
2 changed files with 72 additions and 5 deletions

View File

@ -613,6 +613,39 @@ class Annotation {
this.color = getRgbColor(color);
}
/**
* Set the line endings; should only be used with specific annotation types.
* @param {Array} lineEndings - The line endings array.
*/
setLineEndings(lineEndings) {
this.lineEndings = ["None", "None"]; // The default values.
if (Array.isArray(lineEndings) && lineEndings.length === 2) {
for (let i = 0; i < 2; i++) {
const obj = lineEndings[i];
if (obj instanceof Name) {
switch (obj.name) {
case "None":
continue;
case "Square":
case "Circle":
case "Diamond":
case "OpenArrow":
case "ClosedArrow":
case "Butt":
case "ROpenArrow":
case "RClosedArrow":
case "Slash":
this.lineEndings[i] = obj.name;
continue;
}
}
warn(`Ignoring invalid lineEnding: ${obj}`);
}
}
}
/**
* Set the color for background and border if any.
* The default values are transparent.
@ -2803,22 +2836,26 @@ class LineAnnotation extends MarkupAnnotation {
constructor(parameters) {
super(parameters);
const { dict } = parameters;
this.data.annotationType = AnnotationType.LINE;
const lineCoordinates = parameters.dict.getArray("L");
const lineCoordinates = dict.getArray("L");
this.data.lineCoordinates = Util.normalizeRect(lineCoordinates);
this.setLineEndings(dict.getArray("LE"));
this.data.lineEndings = this.lineEndings;
if (!this.appearance) {
// The default stroke color is black.
const strokeColor = this.color
? Array.from(this.color).map(c => c / 255)
: [0, 0, 0];
const strokeAlpha = parameters.dict.get("CA");
const strokeAlpha = dict.get("CA");
// The default fill color is transparent. Setting the fill colour is
// necessary if/when we want to add support for non-default line endings.
let fillColor = null,
interiorColor = parameters.dict.getArray("IC");
interiorColor = dict.getArray("IC");
if (interiorColor) {
interiorColor = getRgbColor(interiorColor, null);
fillColor = interiorColor
@ -2996,13 +3033,20 @@ class PolylineAnnotation extends MarkupAnnotation {
constructor(parameters) {
super(parameters);
const { dict } = parameters;
this.data.annotationType = AnnotationType.POLYLINE;
this.data.vertices = [];
if (!(this instanceof PolygonAnnotation)) {
// Only meaningful for polyline annotations.
this.setLineEndings(dict.getArray("LE"));
this.data.lineEndings = this.lineEndings;
}
// The vertices array is an array of numbers representing the alternating
// horizontal and vertical coordinates, respectively, of each vertex.
// Convert this to an array of objects with x and y coordinates.
const rawVertices = parameters.dict.getArray("Vertices");
const rawVertices = dict.getArray("Vertices");
if (!Array.isArray(rawVertices)) {
return;
}
@ -3018,7 +3062,7 @@ class PolylineAnnotation extends MarkupAnnotation {
const strokeColor = this.color
? Array.from(this.color).map(c => c / 255)
: [0, 0, 0];
const strokeAlpha = parameters.dict.get("CA");
const strokeAlpha = dict.get("CA");
const borderWidth = this.borderStyle.width || 1,
borderAdjust = 2 * borderWidth;

View File

@ -3618,6 +3618,7 @@ describe("annotation", function () {
lineDict.set("Type", Name.get("Annot"));
lineDict.set("Subtype", Name.get("Line"));
lineDict.set("L", [1, 2, 3, 4]);
lineDict.set("LE", ["Square", "Circle"]);
const lineRef = Ref.get(122, 0);
const xref = new XRefMock([{ ref: lineRef, data: lineDict }]);
@ -3630,6 +3631,28 @@ describe("annotation", function () {
);
expect(data.annotationType).toEqual(AnnotationType.LINE);
expect(data.lineCoordinates).toEqual([1, 2, 3, 4]);
expect(data.lineEndings).toEqual(["None", "None"]);
});
it("should set the line endings", async function () {
const lineDict = new Dict();
lineDict.set("Type", Name.get("Annot"));
lineDict.set("Subtype", Name.get("Line"));
lineDict.set("L", [1, 2, 3, 4]);
lineDict.set("LE", [Name.get("Square"), Name.get("Circle")]);
const lineRef = Ref.get(122, 0);
const xref = new XRefMock([{ ref: lineRef, data: lineDict }]);
const { data } = await AnnotationFactory.create(
xref,
lineRef,
pdfManagerMock,
idFactoryMock
);
expect(data.annotationType).toEqual(AnnotationType.LINE);
expect(data.lineCoordinates).toEqual([1, 2, 3, 4]);
expect(data.lineEndings).toEqual(["Square", "Circle"]);
});
});