diff --git a/src/core/annotation.js b/src/core/annotation.js index f92dddb82..7930e14ff 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -410,6 +410,11 @@ class AnnotationFactory { }) ); break; + case AnnotationEditorType.SIGNATURE: + promises.push( + StampAnnotation.createNewAnnotation(xref, annotation, changes, {}) + ); + break; } } @@ -511,6 +516,18 @@ class AnnotationFactory { ) ); break; + case AnnotationEditorType.SIGNATURE: + promises.push( + StampAnnotation.createNewPrintAnnotation( + annotationGlobals, + xref, + annotation, + { + evaluatorOptions: options, + } + ) + ); + break; } } @@ -5025,11 +5042,61 @@ class StampAnnotation extends MarkupAnnotation { return stamp; } + static async #createNewAppearanceStreamForDrawing(annotation, xref) { + const { areContours, color, rect, lines, thickness } = annotation; + + const appearanceBuffer = [ + `${thickness} w 1 J 1 j`, + `${getPdfColor(color, /* isFill */ areContours)}`, + ]; + + for (const line of lines) { + appearanceBuffer.push( + `${numberToString(line[4])} ${numberToString(line[5])} m` + ); + for (let i = 6, ii = line.length; i < ii; i += 6) { + if (isNaN(line[i])) { + appearanceBuffer.push( + `${numberToString(line[i + 4])} ${numberToString(line[i + 5])} l` + ); + } else { + const [c1x, c1y, c2x, c2y, x, y] = line.slice(i, i + 6); + appearanceBuffer.push( + [c1x, c1y, c2x, c2y, x, y].map(numberToString).join(" ") + " c" + ); + } + } + if (line.length === 6) { + appearanceBuffer.push( + `${numberToString(line[4])} ${numberToString(line[5])} l` + ); + } + } + appearanceBuffer.push(areContours ? "F" : "S"); + + const appearance = appearanceBuffer.join("\n"); + + const appearanceStreamDict = new Dict(xref); + appearanceStreamDict.set("FormType", 1); + appearanceStreamDict.set("Subtype", Name.get("Form")); + appearanceStreamDict.set("Type", Name.get("XObject")); + appearanceStreamDict.set("BBox", rect); + appearanceStreamDict.set("Length", appearance.length); + + const ap = new StringStream(appearance); + ap.dict = appearanceStreamDict; + + return ap; + } + static async createNewAppearanceStream(annotation, xref, params) { if (annotation.oldAnnotation) { // We'll use the AP we already have. return null; } + if (annotation.isSignature) { + return this.#createNewAppearanceStreamForDrawing(annotation, xref); + } const { rotation } = annotation; const { imageRef, width, height } = params.image; diff --git a/src/display/editor/draw.js b/src/display/editor/draw.js index 52d0a3a47..8e9e9641a 100644 --- a/src/display/editor/draw.js +++ b/src/display/editor/draw.js @@ -30,7 +30,9 @@ class DrawingOptions { return; } for (const [name, value] of Object.entries(properties)) { - this.updateProperty(name, value); + if (!name.startsWith("_")) { + this.updateProperty(name, value); + } } } diff --git a/src/display/editor/signature.js b/src/display/editor/signature.js index e3f5ba6f8..2c5c830b9 100644 --- a/src/display/editor/signature.js +++ b/src/display/editor/signature.js @@ -18,6 +18,7 @@ import { DrawingEditor, DrawingOptions } from "./draw.js"; import { AnnotationEditor } from "./editor.js"; import { ContourDrawOutline } from "./drawers/contour.js"; import { InkDrawingOptions } from "./ink.js"; +import { InkDrawOutline } from "./drawers/inkdraw.js"; import { SignatureExtractor } from "./drawers/signaturedraw.js"; class SignatureOptions extends DrawingOptions { @@ -70,6 +71,7 @@ class SignatureEditor extends DrawingEditor { constructor(params) { super({ ...params, mustBeCommitted: true, name: "signatureEditor" }); this._willKeepAspectRatio = true; + this._description = ""; } /** @inheritdoc */ @@ -122,17 +124,20 @@ class SignatureEditor extends DrawingEditor { } super.render(); - this.div.hidden = true; this.div.setAttribute("role", "figure"); - this._uiManager.getSignature(this); + if (this._drawId === null) { + this.div.hidden = true; + this._uiManager.getSignature(this); + } return this.div; } - addSignature(outline, heightInPage) { + addSignature(outline, heightInPage, description) { const { x: savedX, y: savedY } = this; this.#isExtracted = outline instanceof ContourDrawOutline; + this._description = description; let drawingOptions; if (this.#isExtracted) { drawingOptions = SignatureEditor.getDefaultDrawingOptions(); @@ -210,6 +215,87 @@ class SignatureEditor extends DrawingEditor { areContours: false, }); } + + /** @inheritdoc */ + createDrawingOptions({ areContours, thickness }) { + if (areContours) { + this._drawingOptions = SignatureEditor.getDefaultDrawingOptions(); + } else { + this._drawingOptions = + SignatureEditor._defaultDrawnSignatureOptions.clone(); + this._drawingOptions.updateProperties({ "stroke-width": thickness }); + } + } + + /** @inheritdoc */ + serialize(isForCopying = false) { + if (this.isEmpty()) { + return null; + } + + const { lines, points, rect } = this.serializeDraw(isForCopying); + const { + _drawingOptions: { "stroke-width": thickness }, + } = this; + const serialized = { + annotationType: AnnotationEditorType.SIGNATURE, + isSignature: true, + areContours: this.#isExtracted, + color: [0, 0, 0], + thickness: this.#isExtracted ? 0 : thickness, + pageIndex: this.pageIndex, + rect, + rotation: this.rotation, + structTreeParentId: this._structTreeParentId, + }; + if (isForCopying) { + serialized.paths = { lines, points }; + } else { + serialized.lines = lines; + } + if (this._description) { + serialized.accessibilityData = { type: "Figure", alt: this._description }; + } + return serialized; + } + + /** @inheritdoc */ + static deserializeDraw( + pageX, + pageY, + pageWidth, + pageHeight, + innerMargin, + data + ) { + if (data.areContours) { + return ContourDrawOutline.deserialize( + pageX, + pageY, + pageWidth, + pageHeight, + innerMargin, + data + ); + } + + return InkDrawOutline.deserialize( + pageX, + pageY, + pageWidth, + pageHeight, + innerMargin, + data + ); + } + + /** @inheritdoc */ + static async deserialize(data, parent, uiManager) { + const editor = await super.deserialize(data, parent, uiManager); + editor.#isExtracted = data.areContours; + editor._description = data.accessibilityData?.alt || ""; + return editor; + } } export { SignatureEditor }; diff --git a/test/test_manifest.json b/test/test_manifest.json index dda23d1d9..9247b545b 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -11301,5 +11301,580 @@ "md5": "b2de376f7e96fa2b6afc00dac016c40a", "rounds": 1, "type": "eq" + }, + { + "id": "signature-rotation-print", + "file": "pdfs/tracemonkey.pdf", + "md5": "9a192d8b1a7dc652a19835f6f08098bd", + "rounds": 1, + "lastPage": 1, + "type": "eq", + "print": true, + "annotationStorage": { + "pdfjs_internal_editor_0": { + "annotationType": 101, + "isSignature": true, + "areContours": true, + "color": [0, 0, 0], + "thickness": 0, + "pageIndex": 0, + "rect": [ + 30.33709076317874, 528.4258915511044, 92.2465096088973, + 605.8765082034198 + ], + "rotation": 0, + "structTreeParentId": null, + "lines": [ + [ + null, + null, + null, + null, + 30.337093353271484, + 605.8765258789062, + 30.337093353271484, + 598.2304077148438, + 34.63264083862305, + 596.6561889648438, + 43.22381591796875, + 596.5662841796875, + 51.8149528503418, + 596.476318359375, + 56.11055374145508, + 585.0970458984375, + 56.11055374145508, + 562.4286499023438, + 56.11055374145508, + 539.7601928710938, + 57.83761978149414, + 528.4259643554688, + 61.29179763793945, + 528.4259643554688, + 64.74597930908203, + 528.4259643554688, + 66.4730453491211, + 539.7601928710938, + 66.4730453491211, + 562.4286499023438, + 66.4730453491211, + 585.0970458984375, + 70.76863098144531, + 596.476318359375, + 79.35980224609375, + 596.5662841796875, + 87.9509048461914, + 596.6561889648438, + 92.24649810791016, + 598.2304077148438, + 92.24649810791016, + 601.288818359375, + 92.24649810791016, + 604.3472900390625, + 81.9282455444336, + 605.8765258789062, + 61.29179763793945, + 605.8765258789062 + ] + ] + }, + "pdfjs_internal_editor_1": { + "annotationType": 101, + "isSignature": true, + "areContours": true, + "color": [0, 0, 0], + "thickness": 0, + "pageIndex": 0, + "rect": [ + 115.2826878157529, 438.45786562832916, 250.9453054937449, + 540.3920820626346 + ], + "rotation": 90, + "structTreeParentId": null, + "lines": [ + [ + null, + null, + null, + null, + 115.2826919555664, + 438.4578552246094, + 128.67564392089844, + 438.4578552246094, + 131.4329833984375, + 445.5304870605469, + 131.59056091308594, + 459.6759033203125, + 131.7481231689453, + 473.8213806152344, + 151.6800079345703, + 480.8940734863281, + 191.38607788085938, + 480.8940734863281, + 231.09228515625, + 480.8940734863281, + 250.94525146484375, + 483.7376708984375, + 250.94525146484375, + 489.425048828125, + 250.94525146484375, + 495.1123046875, + 231.09228515625, + 497.955810546875, + 191.38607788085938, + 497.955810546875, + 151.6800079345703, + 497.955810546875, + 131.7481231689453, + 505.0286865234375, + 131.59056091308594, + 519.1740112304688, + 131.4329833984375, + 533.3192749023438, + 128.67564392089844, + 540.3919067382812, + 123.31848907470703, + 540.3919067382812, + 117.96131134033203, + 540.3919067382812, + 115.2826919555664, + 523.4029541015625, + 115.2826919555664, + 489.425048828125 + ] + ], + "accessibilityData": { + "type": "Figure", + "alt": "T" + } + }, + "pdfjs_internal_editor_2": { + "annotationType": 101, + "isSignature": true, + "areContours": true, + "color": [0, 0, 0], + "thickness": 0, + "pageIndex": 0, + "rect": [ + 394.41588899764145, 521.6146706667813, 429.2137094410983, + 565.6428880366412 + ], + "rotation": 180, + "structTreeParentId": null, + "lines": [ + [ + null, + null, + null, + null, + 429.2137145996094, + 521.6146850585938, + 429.2137145996094, + 525.9612426757812, + 426.7992858886719, + 526.8561401367188, + 421.97039794921875, + 526.9072265625, + 417.1415100097656, + 526.9583740234375, + 414.72705078125, + 533.4271240234375, + 414.72705078125, + 546.3134155273438, + 414.72705078125, + 559.1997680664062, + 413.7563171386719, + 565.6429443359375, + 411.8147888183594, + 565.6429443359375, + 409.873291015625, + 565.6429443359375, + 408.9025573730469, + 559.1997680664062, + 408.9025573730469, + 546.3134155273438, + 408.9025573730469, + 533.4271240234375, + 406.48809814453125, + 526.9583740234375, + 401.6592102050781, + 526.9072265625, + 396.8303527832031, + 526.8561401367188, + 394.4158935546875, + 525.9612426757812, + 394.4158935546875, + 524.2225952148438, + 394.4158935546875, + 522.4840087890625, + 400.2155456542969, + 521.6146850585938, + 411.8147888183594, + 521.6146850585938 + ] + ], + "accessibilityData": { + "type": "Figure", + "alt": "T" + } + }, + "pdfjs_internal_editor_3": { + "annotationType": 101, + "isSignature": true, + "areContours": true, + "color": [0, 0, 0], + "thickness": 0, + "pageIndex": 0, + "rect": [ + 527.772727521983, 549.8299978104504, 563.1363680037585, + 575.0790592106906 + ], + "rotation": 270, + "structTreeParentId": null, + "lines": [ + [ + null, + null, + null, + null, + 563.1363525390625, + 575.0791015625, + 559.6452026367188, + 575.0791015625, + 558.9263916015625, + 573.3272094726562, + 558.8853149414062, + 569.8233642578125, + 558.8442993164062, + 566.319580078125, + 553.6485595703125, + 564.5676879882812, + 543.2982177734375, + 564.5676879882812, + 532.9478759765625, + 564.5676879882812, + 527.772705078125, + 563.86328125, + 527.772705078125, + 562.4545288085938, + 527.772705078125, + 561.0458374023438, + 532.9478759765625, + 560.3414306640625, + 543.2982177734375, + 560.3414306640625, + 553.6485595703125, + 560.3414306640625, + 558.8442993164062, + 558.5894775390625, + 558.8853149414062, + 555.085693359375, + 558.9263916015625, + 551.5819091796875, + 559.6452026367188, + 549.8300170898438, + 561.0416259765625, + 549.8300170898438, + 562.4381103515625, + 549.8300170898438, + 563.1363525390625, + 554.0382080078125, + 563.1363525390625, + 562.4545288085938 + ] + ], + "accessibilityData": { + "type": "Figure", + "alt": "T" + } + } + } + }, + { + "id": "signature-rotation-save", + "file": "pdfs/tracemonkey.pdf", + "md5": "9a192d8b1a7dc652a19835f6f08098bd", + "rounds": 1, + "lastPage": 1, + "type": "eq", + "print": true, + "save": true, + "annotationStorage": { + "pdfjs_internal_editor_0": { + "annotationType": 101, + "isSignature": true, + "areContours": true, + "color": [0, 0, 0], + "thickness": 0, + "pageIndex": 0, + "rect": [ + 30.33709076317874, 528.4258915511044, 92.2465096088973, + 605.8765082034198 + ], + "rotation": 0, + "structTreeParentId": null, + "lines": [ + [ + null, + null, + null, + null, + 30.337093353271484, + 605.8765258789062, + 30.337093353271484, + 598.2304077148438, + 34.63264083862305, + 596.6561889648438, + 43.22381591796875, + 596.5662841796875, + 51.8149528503418, + 596.476318359375, + 56.11055374145508, + 585.0970458984375, + 56.11055374145508, + 562.4286499023438, + 56.11055374145508, + 539.7601928710938, + 57.83761978149414, + 528.4259643554688, + 61.29179763793945, + 528.4259643554688, + 64.74597930908203, + 528.4259643554688, + 66.4730453491211, + 539.7601928710938, + 66.4730453491211, + 562.4286499023438, + 66.4730453491211, + 585.0970458984375, + 70.76863098144531, + 596.476318359375, + 79.35980224609375, + 596.5662841796875, + 87.9509048461914, + 596.6561889648438, + 92.24649810791016, + 598.2304077148438, + 92.24649810791016, + 601.288818359375, + 92.24649810791016, + 604.3472900390625, + 81.9282455444336, + 605.8765258789062, + 61.29179763793945, + 605.8765258789062 + ] + ] + }, + "pdfjs_internal_editor_1": { + "annotationType": 101, + "isSignature": true, + "areContours": true, + "color": [0, 0, 0], + "thickness": 0, + "pageIndex": 0, + "rect": [ + 115.2826878157529, 438.45786562832916, 250.9453054937449, + 540.3920820626346 + ], + "rotation": 90, + "structTreeParentId": null, + "lines": [ + [ + null, + null, + null, + null, + 115.2826919555664, + 438.4578552246094, + 128.67564392089844, + 438.4578552246094, + 131.4329833984375, + 445.5304870605469, + 131.59056091308594, + 459.6759033203125, + 131.7481231689453, + 473.8213806152344, + 151.6800079345703, + 480.8940734863281, + 191.38607788085938, + 480.8940734863281, + 231.09228515625, + 480.8940734863281, + 250.94525146484375, + 483.7376708984375, + 250.94525146484375, + 489.425048828125, + 250.94525146484375, + 495.1123046875, + 231.09228515625, + 497.955810546875, + 191.38607788085938, + 497.955810546875, + 151.6800079345703, + 497.955810546875, + 131.7481231689453, + 505.0286865234375, + 131.59056091308594, + 519.1740112304688, + 131.4329833984375, + 533.3192749023438, + 128.67564392089844, + 540.3919067382812, + 123.31848907470703, + 540.3919067382812, + 117.96131134033203, + 540.3919067382812, + 115.2826919555664, + 523.4029541015625, + 115.2826919555664, + 489.425048828125 + ] + ], + "accessibilityData": { + "type": "Figure", + "alt": "T" + } + }, + "pdfjs_internal_editor_2": { + "annotationType": 101, + "isSignature": true, + "areContours": true, + "color": [0, 0, 0], + "thickness": 0, + "pageIndex": 0, + "rect": [ + 394.41588899764145, 521.6146706667813, 429.2137094410983, + 565.6428880366412 + ], + "rotation": 180, + "structTreeParentId": null, + "lines": [ + [ + null, + null, + null, + null, + 429.2137145996094, + 521.6146850585938, + 429.2137145996094, + 525.9612426757812, + 426.7992858886719, + 526.8561401367188, + 421.97039794921875, + 526.9072265625, + 417.1415100097656, + 526.9583740234375, + 414.72705078125, + 533.4271240234375, + 414.72705078125, + 546.3134155273438, + 414.72705078125, + 559.1997680664062, + 413.7563171386719, + 565.6429443359375, + 411.8147888183594, + 565.6429443359375, + 409.873291015625, + 565.6429443359375, + 408.9025573730469, + 559.1997680664062, + 408.9025573730469, + 546.3134155273438, + 408.9025573730469, + 533.4271240234375, + 406.48809814453125, + 526.9583740234375, + 401.6592102050781, + 526.9072265625, + 396.8303527832031, + 526.8561401367188, + 394.4158935546875, + 525.9612426757812, + 394.4158935546875, + 524.2225952148438, + 394.4158935546875, + 522.4840087890625, + 400.2155456542969, + 521.6146850585938, + 411.8147888183594, + 521.6146850585938 + ] + ], + "accessibilityData": { + "type": "Figure", + "alt": "T" + } + }, + "pdfjs_internal_editor_3": { + "annotationType": 101, + "isSignature": true, + "areContours": true, + "color": [0, 0, 0], + "thickness": 0, + "pageIndex": 0, + "rect": [ + 527.772727521983, 549.8299978104504, 563.1363680037585, + 575.0790592106906 + ], + "rotation": 270, + "structTreeParentId": null, + "lines": [ + [ + null, + null, + null, + null, + 563.1363525390625, + 575.0791015625, + 559.6452026367188, + 575.0791015625, + 558.9263916015625, + 573.3272094726562, + 558.8853149414062, + 569.8233642578125, + 558.8442993164062, + 566.319580078125, + 553.6485595703125, + 564.5676879882812, + 543.2982177734375, + 564.5676879882812, + 532.9478759765625, + 564.5676879882812, + 527.772705078125, + 563.86328125, + 527.772705078125, + 562.4545288085938, + 527.772705078125, + 561.0458374023438, + 532.9478759765625, + 560.3414306640625, + 543.2982177734375, + 560.3414306640625, + 553.6485595703125, + 560.3414306640625, + 558.8442993164062, + 558.5894775390625, + 558.8853149414062, + 555.085693359375, + 558.9263916015625, + 551.5819091796875, + 559.6452026367188, + 549.8300170898438, + 561.0416259765625, + 549.8300170898438, + 562.4381103515625, + 549.8300170898438, + 563.1363525390625, + 554.0382080078125, + 563.1363525390625, + 562.4545288085938 + ] + ], + "accessibilityData": { + "type": "Figure", + "alt": "T" + } + } + } } ] diff --git a/test/unit/annotation_spec.js b/test/unit/annotation_spec.js index a7c968644..8124fe6cf 100644 --- a/test/unit/annotation_spec.js +++ b/test/unit/annotation_spec.js @@ -5090,4 +5090,55 @@ describe("annotation", function () { ); }); }); + + describe("StampAnnotation for signatures", function () { + it("should create a new Stamp annotation", async function () { + const xref = (partialEvaluator.xref = new XRefMock()); + const changes = new RefSetCache(); + const task = new WorkerTask("test Stamp creation"); + await AnnotationFactory.saveNewAnnotations( + partialEvaluator, + task, + [ + { + annotationType: 101, + isSignature: true, + areContours: true, + color: [0, 0, 0], + thickness: 0, + pageIndex: 0, + rect: [12, 34, 56, 78], + rotation: 0, + structTreeParentId: null, + lines: [[NaN, NaN, NaN, NaN, 1, 2, 3, 4, 5, 6, 7, 8]], + }, + ], + null, + changes + ); + const data = await writeChanges(changes, xref); + + const base = data[0].data.replace(/\(D:\d+\)/, "(date)"); + expect(base).toEqual( + "1 0 obj\n" + + "<< /Type /Annot /Subtype /Stamp /CreationDate (date) /Rect [12 34 56 78] " + + "/F 4 /Border [0 0 0] " + + "/Rotate 0 /AP << /N 2 0 R>>>>\n" + + "endobj\n" + ); + + const appearance = data[1].data; + expect(appearance).toEqual( + "2 0 obj\n" + + "<< /FormType 1 /Subtype /Form /Type /XObject /BBox [12 34 56 78] /Length 37>> stream\n" + + "0 w 1 J 1 j\n" + + "0 g\n" + + "1 2 m\n" + + "3 4 5 6 7 8 c\n" + + "F\n" + + "endstream\n" + + "endobj\n" + ); + }); + }); }); diff --git a/web/signature_manager.js b/web/signature_manager.js index 0578c7eb1..945282cf9 100644 --- a/web/signature_manager.js +++ b/web/signature_manager.js @@ -666,7 +666,11 @@ class SignatureManager { data = this.#extractedSignatureData; break; } - this.#currentEditor.addSignature(data, /* heightInPage */ 40); + this.#currentEditor.addSignature( + data, + /* heightInPage */ 40, + this.#description.value + ); if (this.#saveCheckbox.checked) { // TODO }