From 6bcc5b615d225a3cf3159007bc0ff4db4d8cb443 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Tue, 10 May 2022 17:36:29 +0200 Subject: [PATCH] [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. --- src/core/annotation.js | 54 ++++++++++++++++++++++++++++++++---- test/unit/annotation_spec.js | 23 +++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/core/annotation.js b/src/core/annotation.js index 710551639..4012868c8 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -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; diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index c7effe3f1..a3accfca4 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -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"]); }); });