diff --git a/base/shared/annotation.js b/base/shared/annotation.js index 9064e77d..8eb04215 100755 --- a/base/shared/annotation.js +++ b/base/shared/annotation.js @@ -372,10 +372,10 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { var parent = Annotation.prototype; Util.inherit(WidgetAnnotation, Annotation, { isViewable: function WidgetAnnotation_isViewable() { - if (this.data.fieldType === 'Sig') { - TODO('unimplemented annotation type: Widget signature'); - return false; - } +// if (this.data.fieldType === 'Sig') { +// TODO('unimplemented annotation type: Widget signature'); +// return false; +// } return parent.isViewable.call(this); } diff --git a/lib/pdfanno.js b/lib/pdfanno.js index b610278d..9709c48e 100644 --- a/lib/pdfanno.js +++ b/lib/pdfanno.js @@ -148,6 +148,25 @@ function processFieldAttribute(jsFuncName, item) { } } +function setupSignature(annotation, item) { + //PDF Spec p.695: field value is signature dict if signed + let sig = annotation.get('V'); + if (!sig) return; + + //PDF Spec p.728: get signature information + item.Sig = {}; + let name = sig.get('Name'); + if (name) item.Sig.Name = name; + let time = sig.get('M'); + if (time) item.Sig.M = time; + let location = sig.get('Location'); + if (location) item.Sig.Location = location; + let reason = sig.get('Reason'); + if (reason) item.Sig.Reason = reason; + let contactInfo = sig.get('ContactInfo'); + if (contactInfo) item.Sig.ContactInfo = contactInfo; +} + //END - MQZ 9/19/2012. Helper functions to parse acroForm elements class PDFAnno { @@ -169,6 +188,9 @@ class PDFAnno { else if (item.fieldType == 'Tx') { setupFieldAttributes(annotation, item); } + else if (item.fieldType === 'Sig') { + setupSignature(annotation, item); + } else { nodeUtil.p2jwarn("Unknown fieldType: ", item); } diff --git a/lib/pdffield.js b/lib/pdffield.js index c704efc3..8849c819 100644 --- a/lib/pdffield.js +++ b/lib/pdffield.js @@ -26,6 +26,7 @@ class PDFField { retVal = true; break; case 'Ch': retVal = true; break; //drop down + case 'Sig': retVal = true; break; //signature default: nodeUtil.p2jwarn("Unsupported: field.fieldType of " + field.fieldType); break; @@ -222,6 +223,27 @@ class PDFField { this.Fields.push(anData); }; + #addSignature(field) { + const anData = Object.assign({ + style: 48, + T: { + Name: "signature", + TypeInfo: {} + } + }, this.#getFieldBaseData(field)); + + if (field.Sig) { + anData.Sig = {}; + if (field.Sig.Name) anData.Sig.Name = field.Sig.Name; + if (field.Sig.M) anData.Sig.M = PDFUnit.dateToIso8601(field.Sig.M); + if (field.Sig.Location) anData.Sig.Location = field.Sig.Location; + if (field.Sig.Reason) anData.Sig.Reason = field.Sig.Reason; + if (field.Sig.ContactInfo) anData.Sig.ContactInfo = field.Sig.ContactInfo; + } + + this.Fields.push(anData); + } + // public instance methods processField() { this.field.TI = PDFField.tabIndex++; @@ -232,6 +254,7 @@ class PDFField { case 'Rd': this.#addRadioButton(this.field);break; case 'Btn':this.#addLinkButton(this.field); break; case 'Ch': this.#addSelect(this.field); break; + case 'Sig': this.#addSignature(this.field); break; } this.clean(); diff --git a/lib/pdfunit.js b/lib/pdfunit.js index 2b198f60..69633116 100644 --- a/lib/pdfunit.js +++ b/lib/pdfunit.js @@ -51,6 +51,29 @@ class PDFUnit { //MQZ. 07/29/2013: if color is not in dictionary, just return -1. The caller (pdffont, pdffill) will set the actual color return kColors.indexOf(color); } + + static dateToIso8601(date) { + // PDF spec p.160 + if (date.slice(0, 2) === 'D:') { // D: prefix is optional + date = date.slice(2); + } + let tz = 'Z'; + let idx = date.search(/[Z+-]/); // timezone is optional + if (idx >= 0) { + tz = date.slice(idx); + if (tz !== 'Z') { // timezone format OHH'mm' + tz = tz.slice(0, 3) + ':' + tz.slice(4, 6); + } + date = date.slice(0, idx); + } + let yr = date.slice(0, 4); // everything after year is optional + let mth = date.slice(4, 6) || '01'; + let day = date.slice(6, 8) || '01'; + let hr = date.slice(8, 10) || '00'; + let min = date.slice(10, 12) || '00'; + let sec = date.slice(12, 14) || '00'; + return yr + '-' + mth + '-' + day + 'T' + hr + ':' + min + ':' + sec + tz; + } } module.exports = PDFUnit; diff --git a/readme.md b/readme.md index d65e776c..53ecd4a6 100644 --- a/readme.md +++ b/readme.md @@ -566,6 +566,37 @@ Another supported field attributes is "required": when form author mark a field h: 0.85 } +v2.X.X added support for the signature form element (Name: 'signature'). If the field has been signed, the 'Sig' property will be present, and will contain any of the following signature details if available: +- 'Name' - Signer's name +- 'M' - Time of signing in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format +- 'Location' - Location of signing +- 'Reason' - Reason for signing +- 'ContactInfo' - Signer's contact information + +Signature example: + + { + style: 48, + T: { + Name: "signature", + TypeInfo: {} + }, + id: { + Id: "SignatureFormField_1", + EN: 0 + }, + TI: 0, + AM: 16, + x: 5.506, + y: 31.394, + w: 14.367, + h: 4.241, + Sig: { + Name: "Signer Name", + M: "2022-03-15T19:17:34-04:00" + } + } + ## Text Input Field Formatter Types v0.1.8 added text input field formatter types detection for