From bf75b0881aeff7bc5d4e274f8b71c5fb1246d66a Mon Sep 17 00:00:00 2001 From: Jim Pravetz Date: Sun, 21 Jul 2019 12:19:08 -0700 Subject: [PATCH] Added shortcut support for formatting form text inputs. Fixed annotations to not automatically add Border and C for Widget annotations. More documentation. --- docs/forms.md | 119 +++++++++++++++++++++++++------------- lib/mixins/acroform.js | 48 ++++++++++++++- lib/mixins/annotations.js | 32 +++++----- 3 files changed, 142 insertions(+), 57 deletions(-) diff --git a/docs/forms.md b/docs/forms.md index 041b06a..456a0fb 100644 --- a/docs/forms.md +++ b/docs/forms.md @@ -2,12 +2,16 @@ AcroForms are interactive features of the PDF format, and they make it possible to include things like text fields, buttons and actions. To include AcroForms -you must call the `document.initAcroForm()` method. +you must call the document `initAcroForm()` method. -AcroForm elements are _widget_ annotations and are added using the -`widgetAnnotation` method. Other methods are shortcut methods that call the -`widgetAnnotation` method. Here is a list of the available _Widget Annotation_ -methods: +* `initAcroform()` - Must be called when using AcroForms + +## AcroForm Methods + +AcroForm elements are _Widget Annotations_ and are added using the +`widgetAnnotation` method. Additional methods listed below are shortcut methods +that call the `widgetAnnotation` method. The list of the available _Widget +Annotation_ document methods is: * `widgetAnnotation( name, x, y, width, height, options)` * `formText( name, x, y, width, height, options)` @@ -16,29 +20,54 @@ methods: * `formNoToggleToOffButton( name, x, y, width, height, options)` * `formChoice( name, x, y, width, height, options)` -Some Widget Annotations have a `color` option that you can specify. You can use +### Options Parameter + +Some Widget Annotations have a `color` option that can be specified. You can use an array of RGB values, a hex color, or a named CSS color value for that option. -For example, form buttons can have a `backgroundColor` and `borderColor`. + +* `backgroundColor` - button background color +* `borderColor` - button border color Other `options` conveniences include: -* `label` - set button text labels (MK.CA) -* `align` - set to `left`, `center` or `right` for text within the Widget Annotation -* Field flags to set `Ff` - * readyOnly: 1, - * required: 2, - * noExport: 4, - * multiline: 0x1000, - * password: 0x2000, - * toggleToOffButton: 0x4000, - * radioButton: 0x8000, (also set when calling `formRadioButton`) - * pushButton: 0x10000 (also set when calling `formPushButton`) - * toggleToOffButton: 0x10000 (also set when calling `formNoToggleToOffButton`) - * combo: 0x20000, - * edit: 0x40000, - * sort: 0x80000 +* `label` - set button text labels (will set <> >>) +* `align` - set to `left`, `center` or `right` for text within the + Widget Annotation +* Field flags that will set bits in `Ff` + * `readyOnly`: 1, + * `required`: 2, + * `noExport`: 4, + * `multiline`: 0x1000, + * `password`: 0x2000, + * `toggleToOffButton`: 0x4000, + * `radioButton`: 0x8000, (will be set when calling the `formRadioButton` method) + * `pushButton`: 0x10000 (will be set when calling the `formPushButton` method) + * `toggleToOffButton`: 0x10000 (will be set when calling the `formNoToggleToOffButton` method) + * `combo`: 0x20000, + * `edit`: 0x40000, + * `sort`: 0x80000 -When using `formChoice`, set `options.Opt` to the array of choices. +When using the `formChoice` method, set `options.Opt` to the array of choices. + +When needing to format the text value of a Widget Annotation, the following +`options` shortcuts are available to implement predefined JavaScript actions. +Refer to the Acrobat SDK documentation for the [Acrobat Forms +Plugin](https://help.adobe.com/en_US/acrobat/acrobat_dc_sdk/2015/HTMLHelp/#t=Acro12_MasterBook%2FIAC_API_FormsIntro%2FMethods1.htm) for more information. + +* `format` - object + * `type` - value is a string with one of the following values: + * `time` + * `date` + * `percent` + * `number` + * `special` + * `zip` + * `zipPlus4` + * `phone` + * `ssn` + * `params` - value is a string, number or array of strings and numbers + +## Other Methods The font used for a Widget Annotation is set using the `document.font` method. @@ -48,32 +77,40 @@ _shipping.address.street_. You can either set the `name` of each Widget Annotation with the full name (e.g. _shipping.address.street_) or you can create parent Fields. In this example you might have a _shipping_ field that is added to the AcroForm Fields array, an _address_ field that refers to the _shipping_ -Field as it's parent, and a _street_ Widget Annotation which would refer to the -_address_ field as it's parent. To create a field use: +Field as it's parent, and a _street_ Widget Annotation that would refer to the +_address_ field as it's parent. To create a field use the document method: -* `field( name, options )` returns a reference to the field +* `field( name, options )` - returns a reference to the field To specify the parent of a _Field_ or _Widget Annotation_, set the `parent` -option to the field reference. +options to the field reference. -In support of Widget Annotations that execute PDF JavaScript, you can call the following method: +```js +var shippingRef = doc.field( 'shipping' ); +var addressRef = doc.field( 'address', shippingRef ); +doc.formText('street`,10,10,100,20,{parent:addressRef}); +``` + +In support of Widget Annotations that execute PDF JavaScript, you can call the following document method: * `addNamedJavaScript( name, buffer )` -There is an important caveat when using AcroForms with PDFKit. Form elements -must each have an _appearance_ set using the _AP_ attribute of the annotation. -If this attribute is not set, the form element _may_ not be visible. Because -appearances can be complex to generate, Adobe Acrobat has an option to build -these apperances when a PDF is opened. To do this PDFKit sets the AcroForm -dictionary's _NeedAppearances_ attribute to true. This could mean that the PDF -will be _dirty_ upon open, meaning it will need to be saved. It is also -important to realize that the _NeedAppearances_ flag may not be honored by PDF -viewers that don't implement this aspect of the PDF Reference. +## Limitations -A final note on _NeedAppearances_ is that for some form documents you may not -need to generate appearances. We believe this to be the case for text widget -annotations that are initially blank. It is not true for push button widget -annotations. +An important caveat when using AcroForms with PDFKit is that form elements must +each have an _appearance_ set using the `AP` attribute of the annotation. If +this attribute is not set, the form element _may_ not be visible. Because +appearances can be complex to generate, Adobe Acrobat has an option to build +these apperances from form values and Widget Annotation attributes when a PDF is +opened. To do this PDFKit sets the AcroForm dictionary's `NeedAppearances` +attribute to true. This could mean that the PDF will be _dirty_ upon open, +meaning it will need to be saved. It is also important to realize that the +`NeedAppearances` flag may not be honored by PDF viewers that do not implement +this aspect of the PDF Reference. + +A final note on `NeedAppearances` is that for some form documents you may not +need to generate appearances. This may be the case for text Widget Annotations +that are initially blank. This is not true for push button widget annotations. * * * diff --git a/lib/mixins/acroform.js b/lib/mixins/acroform.js index 1afd6ce..c23d095 100644 --- a/lib/mixins/acroform.js +++ b/lib/mixins/acroform.js @@ -20,6 +20,13 @@ const FIELD_JUSTIFY = { center: 1, right: 2 }; +const FORMAT_SPECIAL = { + zip: '0', + zipPlus4: '1', + zip4: '1', + phone: '2', + ssn: '3' +}; export default { @@ -117,12 +124,52 @@ export default { opts = this._resolveFont(opts); opts = this._resolveStrings(opts); opts = this._resolveColors(opts); + opts = this._resolveFormat(opts); opts = Object.assign(opts, { T: new String(name), }); return opts; }, + _resolveFormat (opts) { + if (opts.format && opts.format.type) { + let fnKeystroke; + let fnFormat; + let params; + if (FORMAT_SPECIAL[opts.format.type] !== undefined) { + fnKeystroke = `AFSpecial_Keystroke`; + fnFormat = `AFSpecial_Format`; + params = FORMAT_SPECIAL[opts.format.type] + } else { + let format = opts.format.type.charAt(0).toUpperCase() + opts.format.type.slice(1); + fnKeystroke = `AF${format}_Keystroke`; + fnFormat = `AF${format}_Format`; + params = this._formatParamsAsString(opts.format.params); + if (opts.format.type === 'date') { + fnKeystroke += 'Ex'; + } + } + opts.AA = opts.AA ? opts.AA : {}; + opts.AA.K = { + S: "JavaScript", + JS: new String(`${fnKeystroke}(${params});`) + }; + opts.AA.F = { + S: "JavaScript", + JS: new String(`${fnFormat}(${params});`) + }; + } + delete opts.format; + return opts; + }, + + _formatParamsAsString (params) { + if (Array.isArray(params)) { + return JSON.stringify(params).replace('[', '').replace(']', '') + } + return String(params); + }, + _resolveColors (opts) { let color = this._normalizeColor(opts.backgroundColor); if (color) { @@ -185,7 +232,6 @@ export default { } else { arr.push(options.Opt[idx]) } - arr.push(new String(options.Opt[idx])); } options.Opt = arr; } diff --git a/lib/mixins/annotations.js b/lib/mixins/annotations.js index f670e04..b406e93 100644 --- a/lib/mixins/annotations.js +++ b/lib/mixins/annotations.js @@ -1,9 +1,11 @@ export default { - annotate(x, y, w, h, options) { + annotate (x, y, w, h, options) { options.Type = 'Annot'; options.Rect = this._convertRect(x, y, w, h); - options.Border = [0, 0, 0]; - if (options.Subtype !== 'Link') { + if (options.Subtype !== 'Widget') { + options.Border = [0, 0, 0]; + } + if (options.Subtype !== 'Link' && options.Subtype !== 'Widget') { if (options.C == null) { options.C = this._normalizeColor(options.color || [0, 0, 0]); } @@ -26,7 +28,7 @@ export default { return this; }, - note(x, y, w, h, contents, options = {}) { + note (x, y, w, h, contents, options = {}) { options.Subtype = 'Text'; options.Contents = new String(contents); options.Name = 'Comment'; @@ -36,7 +38,7 @@ export default { return this.annotate(x, y, w, h, options); }, - goTo(x, y, w, h, name, options = {}) { + goTo (x, y, w, h, name, options = {}) { options.Subtype = 'Link'; options.A = this.ref({ S: 'GoTo', @@ -46,7 +48,7 @@ export default { return this.annotate(x, y, w, h, options); }, - link(x, y, w, h, url, options = {}) { + link (x, y, w, h, url, options = {}) { options.Subtype = 'Link'; if (typeof url === 'number') { @@ -73,14 +75,14 @@ export default { return this.annotate(x, y, w, h, options); }, - _markup(x, y, w, h, options = {}) { + _markup (x, y, w, h, options = {}) { const [x1, y1, x2, y2] = this._convertRect(x, y, w, h); options.QuadPoints = [x1, y2, x2, y2, x1, y1, x2, y1]; options.Contents = new String(); return this.annotate(x, y, w, h, options); }, - highlight(x, y, w, h, options = {}) { + highlight (x, y, w, h, options = {}) { options.Subtype = 'Highlight'; if (options.color == null) { options.color = [241, 238, 148]; @@ -88,43 +90,43 @@ export default { return this._markup(x, y, w, h, options); }, - underline(x, y, w, h, options = {}) { + underline (x, y, w, h, options = {}) { options.Subtype = 'Underline'; return this._markup(x, y, w, h, options); }, - strike(x, y, w, h, options = {}) { + strike (x, y, w, h, options = {}) { options.Subtype = 'StrikeOut'; return this._markup(x, y, w, h, options); }, - lineAnnotation(x1, y1, x2, y2, options = {}) { + lineAnnotation (x1, y1, x2, y2, options = {}) { options.Subtype = 'Line'; options.Contents = new String(); options.L = [x1, this.page.height - y1, x2, this.page.height - y2]; return this.annotate(x1, y1, x2, y2, options); }, - rectAnnotation(x, y, w, h, options = {}) { + rectAnnotation (x, y, w, h, options = {}) { options.Subtype = 'Square'; options.Contents = new String(); return this.annotate(x, y, w, h, options); }, - ellipseAnnotation(x, y, w, h, options = {}) { + ellipseAnnotation (x, y, w, h, options = {}) { options.Subtype = 'Circle'; options.Contents = new String(); return this.annotate(x, y, w, h, options); }, - textAnnotation(x, y, w, h, text, options = {}) { + textAnnotation (x, y, w, h, text, options = {}) { options.Subtype = 'FreeText'; options.Contents = new String(text); options.DA = new String(); return this.annotate(x, y, w, h, options); }, - _convertRect(x1, y1, w, h) { + _convertRect (x1, y1, w, h) { // flip y1 and y2 let y2 = y1; y1 += h;