Added shortcut support for formatting form text inputs.

Fixed annotations to not automatically add Border and C for Widget annotations.
More documentation.
This commit is contained in:
Jim Pravetz 2019-07-21 12:19:08 -07:00
parent 9f6c5fb825
commit bf75b0881a
3 changed files with 142 additions and 57 deletions

View File

@ -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 <<MK <<CA (label)>> >>)
* `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.
* * *

View File

@ -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;
}

View File

@ -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;