@syncfusion/ej2-pdf
Version:
Feature-rich JavaScript PDF library with built-in support for loading and manipulating PDF document.
2,235 lines • 94.7 kB
JavaScript
import { _PdfDictionary, _PdfName, _PdfReference } from './../pdf-primitives';
import { PdfField, PdfTextBoxField, PdfButtonField, PdfCheckBoxField, PdfRadioButtonListField, PdfComboBoxField, PdfListBoxField, PdfSignatureField } from './field';
import { _getInheritableProperty, _getPageIndex, _isNullOrUndefined } from './../utils';
import { PdfFormFieldsTabOrder, PdfRotationAngle, _FieldFlag, _SignatureFlag } from './../enumerator';
import { PdfPage } from './../pdf-page';
import { PdfAnnotationCollection } from './../annotations/annotation-collection';
import { PdfWidgetAnnotation } from './../annotations/annotation';
import { initializeTelemetryFeature } from '@syncfusion/ej2-base';
/**
* Represents a PDF form.
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Access the form of the PDF document
* let form: PdfForm = document.form;
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*/
var PdfForm = /** @class */ (function () {
/**
* Represents a loaded from the PDF document.
*
* @private
* @param {_PdfDictionary} dictionary Form dictionary.
* @param {_PdfCrossReference} crossReference Cross reference object.
*/
function PdfForm(dictionary, crossReference) {
/**
* Indicates use of a default appearance for widgets.
*
* @private
*/
this._isDefaultAppearance = false;
/**
* Indicates whether the form contains fields with kids.
*
* @private
*/
this._hasKids = false;
/**
* Indicates whether to generate appearance streams for fields.
*
* @private
*/
this._setAppearance = false;
/**
* Exports fields even if they are empty.
*
* @private
*/
this._exportEmptyFields = false;
/**
* Collection of parsed field instances.
*
* @private
*/
this._fieldCollection = [];
/**
* Signature flag indicating required usage or certification.
*
* @private
*/
this._signFlag = _SignatureFlag.none;
/**
* Cached indicator for NeedAppearances usage.
*
* @private
*/
this._isNeedAppearances = false;
/**
* List of form names in document order.
*
* @private
*/
this._formNames = [];
/**
* Enables automatic naming for newly added fields.
*
* @private
*/
this._fieldAutoNaming = false;
/**
* Generated or user-specified field names.
*
* @private
*/
this._fieldName = [];
/**
* Cache of fonts used across fields keyed by font name.
*
* @private
*/
this._fontCache = new Map();
/**
* Indicates whether additional post-processing is required.
*
* @private
*/
this._requiresPostProcessing = false;
/**
* Indicates whether the kids are valid or not.
*
* @private
*/
this._isValidKids = false;
this._dictionary = dictionary;
this._crossReference = crossReference;
this._parsedFields = new Map();
this._fields = [];
this._addedFieldNames = new Set();
this._createFields();
}
Object.defineProperty(PdfForm.prototype, "count", {
/**
* Gets the fields count (Read only).
*
* @returns {number} Fields count.
*
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Access loaded form
* let form: PdfForm = document.form;
* // Gets the fields count
* let count: number = form.count;
* // Destroy the document
* document.destroy();
* ```
*/
get: function () {
return this._fields.length;
},
enumerable: true,
configurable: true
});
Object.defineProperty(PdfForm.prototype, "needAppearances", {
/**
* Gets a value indicating whether need appearances (Read only).
*
* @returns {boolean} Need appearances.
*
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Access loaded form
* let form: PdfForm = document.form;
* // Gets the boolean flag indicating need appearances
* let needAppearances: number = form.needAppearances;
* // Destroy the document
* document.destroy();
* ```
*/
get: function () {
if (this._dictionary.has('NeedAppearances')) {
this._needAppearances = this._dictionary.get('NeedAppearances');
}
return this._needAppearances;
},
enumerable: true,
configurable: true
});
Object.defineProperty(PdfForm.prototype, "exportEmptyFields", {
/**
* Gets a value indicating whether allow to export empty fields or not.
*
* @returns {boolean} Export empty fields.
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Access loaded form
* let form: PdfForm = document.form;
* // Gets a value indicating whether allow to export empty fields or not.
* let exportEmptyFields: boolean = form.exportEmptyFields;
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*/
get: function () {
return this._exportEmptyFields;
},
/**
* Sets a value indicating whether allow to export empty fields or not.
*
* @param {boolean} value Export empty fields.
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Access loaded form
* let form: PdfForm = document.form;
* // Sets a value indicating whether allow to export empty fields or not.
* form.exportEmptyFields = false;
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*/
set: function (value) {
this._exportEmptyFields = value;
},
enumerable: true,
configurable: true
});
Object.defineProperty(PdfForm.prototype, "_signatureFlag", {
/**
* Gets the current signature flags of the form (`SigFlags`).
*
* @private
* @returns {_SignatureFlag} The active signature flags bitmask.
*/
get: function () {
return this._signFlag;
},
/**
* Sets the form's signature flags and updates the `SigFlags` entry in the AcroForm dictionary.
*
* @private
* @param {_SignatureFlag} value The signature flags bitmask to set.
* @returns {void}
*/
set: function (value) {
if (value !== this._signFlag) {
this._signFlag = value;
this._dictionary.update('SigFlags', value);
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(PdfForm.prototype, "fieldAutoNaming", {
/**
* Gets a value indicating whether the automatic field naming is enabled for form fields.
*
* @returns {boolean} Indicates if field auto naming is enabled.
*
* ```typescript
* // Create new document.
* let document: PdfDocument = new PdfDocument();
* // Access loaded form
* let form: PdfForm = document.form;
* // Gets the value indicating if automatic field naming is enabled
* let fieldAutoNaming: boolean = form.fieldAutoNaming;
* // Destroy the document
* document.destroy();
* ```
*/
get: function () {
return this._fieldAutoNaming;
},
/**
* Sets a value indicating whether field auto-naming is enabled for form fields.
*
* @param {boolean} value Enable or disable field auto naming. The default value is false.
* ```typescript
* // Create a new document
* let document: PdfDocument = new PdfDocument();
* // Access loaded form
* let form: PdfForm = document.form;
* // Enable automatic field naming for new form fields.
* form.fieldAutoNaming = true;
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*/
set: function (value) {
this._fieldAutoNaming = value;
},
enumerable: true,
configurable: true
});
/**
* Gets the `PdfField` at the specified index.
*
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Access the loaded form field
* let field: PdfField = document.form.fieldAt(0);
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*
* @param {number} index Field index.
* @returns {PdfField} Loaded PDF form field at the specified index.
*/
PdfForm.prototype.fieldAt = function (index) {
if (index < 0 || index >= this._fields.length) {
throw Error('Index out of range.');
}
var field;
if (this._parsedFields.has(index)) {
field = this._parsedFields.get(index);
this._isNeedAppearances = true;
}
else {
var dictionary = void 0;
var ref = this._fields[index]; // eslint-disable-line
if (ref && ref instanceof _PdfReference) {
dictionary = this._crossReference._fetch(ref);
}
if (dictionary) {
field = this._parseFields(dictionary, ref);
this._parsedFields.set(index, field);
if (field && field instanceof PdfField) {
field._annotationIndex = index;
}
}
}
return field;
};
/**
* Builds a map of widget annotation references from all pages in the document.
* This collection is used to associate terminal fields with their corresponding
* page-level widget annotations, enabling proper field-to-page linkage.
*
* @private
* @returns {void}
*/
PdfForm.prototype._getPageWidgetCollection = function () {
this._pageWidgetReference = new Map();
var document = this._crossReference._document;
if (!document) {
return;
}
for (var i = 0; i < document.pageCount; i++) {
var page = document.getPage(i);
if (!page || !page._pageDictionary) {
continue;
}
var widgetAnnots = [];
if (page._pageDictionary.has('Annots')) {
widgetAnnots = page._pageDictionary.getRaw('Annots');
if (widgetAnnots instanceof _PdfReference) {
widgetAnnots = this._crossReference._fetch(widgetAnnots);
}
}
if (widgetAnnots && Array.isArray(widgetAnnots)) {
for (var j = 0; j < widgetAnnots.length; j++) {
var ref = widgetAnnots[j];
if (ref && ref instanceof _PdfReference) {
var annotDictionary = this._crossReference._fetch(ref);
if (annotDictionary && annotDictionary.has('Subtype') && annotDictionary.get('Subtype') &&
annotDictionary.get('Subtype').name === 'Widget') {
this._pageWidgetReference.set(ref, annotDictionary);
}
}
}
}
}
};
/**
* Retrieves a form field by its index within the terminal fields collection.
* Resolves the field dictionary with its associated page widget reference when available.
*
* @private
* @param {number} index The index of the field in the terminal fields array.
* @returns {PdfField} The parsed PDF form field, or `null` if not found or out of range.
*/
PdfForm.prototype._getField = function (index) {
var _this = this;
if (!this._terminalFields || index < 0 || index >= this._terminalFields.length) {
return null;
}
if (!this._pageWidgetReference) {
this._getPageWidgetCollection();
}
var dictionary = this._terminalFields[index];
var ref = dictionary._reference;
var acroFields = this._dictionary ? this._dictionary.get('Fields') : null; // eslint-disable-line
if (acroFields && this._pageWidgetReference && this._pageWidgetReference.size > 0) {
var _found_1 = false;
this._pageWidgetReference.forEach(function (wdict, wref) {
if (_found_1) {
return;
}
if (dictionary && _this._compareWidgets(wdict, dictionary)) {
if (!dictionary.has('P')) {
dictionary = wdict;
if (_this._fieldsMap && _this._fieldsMap.has(wdict)) {
ref = _this._fieldsMap.get(wdict);
}
}
_found_1 = true;
}
});
}
var field = this._parseFields(dictionary, ref);
if (field) {
field._form = this;
}
return field;
};
/**
* Parses a form field from a given field dictionary, resolving page widget associations
* and constructing the appropriate PDF field type via `_parseFields`.
*
* @private
* @param {_PdfDictionary} fieldDictionary The field dictionary to parse.
* @returns {PdfField} The constructed PDF form field, or `null` if parsing fails.
*/
PdfForm.prototype._getFieldFromDictionary = function (fieldDictionary) {
var _this = this;
if (!this._pageWidgetReference) {
this._getPageWidgetCollection();
}
var dictionary = fieldDictionary;
var ref = dictionary._reference;
var acroFields = this._dictionary ? this._dictionary.get('Fields') : null; // eslint-disable-line
if (acroFields && this._pageWidgetReference && this._pageWidgetReference.size > 0) {
var found_1 = false;
this._pageWidgetReference.forEach(function (wdict, wref) {
if (found_1) {
return;
}
if (dictionary && _this._compareWidgets(wdict, dictionary)) {
if (!dictionary.has('P')) {
dictionary = wdict;
if (_this._fieldsMap && _this._fieldsMap.has(wdict)) {
ref = _this._fieldsMap.get(wdict);
}
}
found_1 = true;
}
});
}
var field = this._parseFields(dictionary, ref);
if (field) {
field._form = this;
}
return field;
};
/**
* Parses a terminal form field from its dictionary and reference, instantiating the appropriate
* field type based on `FT` and `Ff` (e.g., text, button, choice, signature).
*
* @private
* @param {_PdfDictionary} dictionary The field dictionary to parse.
* @param {_PdfReference} reference The indirect reference of the field.
* @returns {PdfField} The constructed field instance.
*/
PdfForm.prototype._parseFields = function (dictionary, reference) {
var field;
if (dictionary) {
var key = _getInheritableProperty(dictionary, 'FT', false, true, 'Parent');
var fieldFlags = 0;
var flag = _getInheritableProperty(dictionary, 'Ff', false, true, 'Parent');
if (typeof flag !== 'undefined') {
fieldFlags = flag;
}
if (key) {
switch (key.name.toLowerCase()) {
case 'tx':
field = PdfTextBoxField._load(this, dictionary, this._crossReference, reference);
break;
case 'btn':
if ((fieldFlags & _FieldFlag.pushButton) !== 0) {
field = PdfButtonField._load(this, dictionary, this._crossReference, reference);
}
else if ((fieldFlags & _FieldFlag.radio) !== 0) {
field = PdfRadioButtonListField._load(this, dictionary, this._crossReference, reference);
}
else {
field = PdfCheckBoxField._load(this, dictionary, this._crossReference, reference);
}
break;
case 'ch':
if ((fieldFlags & _FieldFlag.combo) !== 0) {
field = PdfComboBoxField._load(this, dictionary, this._crossReference, reference);
}
else {
field = PdfListBoxField._load(this, dictionary, this._crossReference, reference);
}
break;
case 'sig':
field = PdfSignatureField._load(this, dictionary, this._crossReference, reference);
break;
}
}
}
return field;
};
/**
* Add a new `PdfField`.
*
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Add a new form field
* let index: number = document.form.add(field);
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*
* @param {PdfField} field Field object to add.
* @returns {number} Field index.
*/
PdfForm.prototype.add = function (field) {
initializeTelemetryFeature('AcroForm', 'PDFLibrary');
if (this._fields.length > 0) {
var fieldsCollection = this._getFields();
var old = fieldsCollection.find(function (oldField) { return oldField.name === field.name; }); // eslint-disable-line
if (old && this._fieldAutoNaming) {
var newName = this._getCorrectName(field._name);
field._name = newName;
field._dictionary.update('T', newName);
this._fieldName.push(field._name);
return this._doAdd(field);
}
else if (old && ((old instanceof PdfSignatureField && (old._isLoaded
|| old._crossReference._document._isLoaded)) ||
!(old instanceof PdfSignatureField)) && this._checkType(old, field)) {
this._fieldName.push(field._name);
return this._groupingFormFields(field, old);
}
}
this._requiresPostProcessing = true;
this._fieldName.push(field._name);
return this._doAdd(field);
};
/**
* Adds a field to the form, updates the AcroForm `Fields` array, caches the parsed field,
* and sets appearance/signature flags when applicable.
*
* @private
* @param {PdfField} field The field to add to the form.
* @returns {number} The index of the added field in the form.
*/
PdfForm.prototype._doAdd = function (field) {
if (this._fields.indexOf(field._ref) === -1) {
this._fields.push(field._ref);
this._dictionary.update('Fields', this._fields);
this._parsedFields.set(this._fields.length - 1, field);
field._form = this;
this._crossReference._root._updated = true;
if (field instanceof PdfSignatureField) {
field._form._signatureFlag = _SignatureFlag.signatureExists | _SignatureFlag.appendOnly;
}
this._isNeedAppearances = true;
}
return (this._fields.length - 1);
};
/**
* Groups a new field with an existing field having the same name by wiring widget parents,
* merging kids, syncing flags/appearance, and handling radio options/selection where needed.
*
* @private
* @param {PdfField} field The new field to group.
* @param {PdfField} oldField The existing field with the same name.
* @returns {number} The index of the grouped field (existing field’s index).
*/
PdfForm.prototype._groupingFormFields = function (field, oldField) {
if (oldField._name === field._name) {
if (oldField.flatten || field.flatten) {
field.flatten = true;
oldField.flatten = true;
}
}
if (!(field instanceof PdfRadioButtonListField && oldField instanceof PdfRadioButtonListField)) {
var widgetDictionary = field.itemAt(0)._dictionary;
if (widgetDictionary && widgetDictionary.has('Parent')) {
delete widgetDictionary._map.Parent;
}
field.itemAt(0)._dictionary.set('Parent', oldField._ref);
var fieldKidRef = field.itemAt(0)._ref;
var oldFieldKids = oldField._dictionary.get('Kids'); // eslint-disable-line
if (oldFieldKids && oldFieldKids.length > 0) {
oldFieldKids.push(fieldKidRef);
oldField._dictionary.update('Kids', oldFieldKids);
oldField._dictionary._updated = true;
}
else {
this._updateFieldsKids(oldField, field);
}
if (field instanceof PdfCheckBoxField && oldField instanceof PdfCheckBoxField) {
var newItem = field.itemAt(0);
var newValue = void 0;
if (newItem && typeof newItem.exportValue === 'string') {
newValue = newItem.exportValue;
}
var appendedIndex = oldField.itemsCount - 1;
oldField._parsedItems.set(appendedIndex, newItem);
newItem._field = oldField;
newItem._index = appendedIndex;
if (!newValue && newValue !== '') {
return this._fields.length - 1;
}
var matchIndex = this._findFirstByExportValue(oldField, newValue);
var groupHasSameExportValue = matchIndex >= 0;
var oldSelectedValue = this._getSelectedExportValue(oldField);
if (newItem.checked) {
if (groupHasSameExportValue) {
var matched = oldField.itemAt(matchIndex);
if (matched && !matched.checked) {
matched._field._isUpdating = false;
matched.checked = true;
}
if (!newItem.checked) {
newItem.checked = true;
}
}
else {
newItem.checked = true;
}
}
else {
if (oldSelectedValue && oldSelectedValue === newValue) {
newItem.checked = true;
}
else if (groupHasSameExportValue) {
var matched = oldField.itemAt(matchIndex);
if (matched && matched.checked && !newItem.checked) {
newItem.checked = true;
}
}
}
return this._fields.length - 1;
}
if ((field instanceof PdfButtonField && oldField instanceof PdfButtonField)) {
if (!oldField._setAppearance) {
oldField._setAppearance = true;
}
}
return this._fields.length - 1;
}
else {
var baseDictionary = oldField._dictionary;
if (baseDictionary && baseDictionary.has('Opt')) {
delete baseDictionary._map.Opt;
}
var _radioButtonGroupingFields = [];
_radioButtonGroupingFields.push(oldField);
_radioButtonGroupingFields.push(field);
var itemCount_1 = 0;
var globalSelectedIndex_1 = -1;
_radioButtonGroupingFields.forEach(function (field) {
if (field.selectedIndex >= 0) {
var selectedItemIndex = itemCount_1 + field.selectedIndex;
if (selectedItemIndex > globalSelectedIndex_1) {
globalSelectedIndex_1 = selectedItemIndex;
}
}
itemCount_1 += field._kids.length;
});
if (oldField._kids.length !== oldField._parsedItems.size) {
for (var j = 0; j < oldField._kids.length; j++) {
var existingItem = oldField._parsedItems.get(j);
if (!existingItem || existingItem.value !== oldField.itemAt(j).value) {
oldField._parsedItems.set(j, oldField.itemAt(j));
}
}
}
var fieldKids = field._dictionary.get('Kids'); // eslint-disable-line
if (fieldKids.length > 0) {
var oldFieldKids = oldField._dictionary.get('Kids'); // eslint-disable-line
var itemsCount = oldField.itemsCount;
for (var i = 0; i < field.itemsCount; i++) {
var widgetDictionary = field.itemAt(i)._dictionary;
if (widgetDictionary && widgetDictionary.has('Parent')) {
delete widgetDictionary._map.Parent;
}
widgetDictionary.set('Parent', oldField._ref);
field.itemAt(i)._field = oldField;
field.itemAt(i)._index = itemsCount++;
itemCount_1++;
var fieldKidRef = field.itemAt(i)._ref;
oldFieldKids.push(fieldKidRef);
}
oldField._dictionary.update('Kids', oldFieldKids);
oldField._dictionary._updated = true;
}
oldField.allowUnisonSelection = field.allowUnisonSelection;
var count = oldField._parsedItems.size;
for (var j = 0; j < field.itemsCount; j++) {
oldField._parsedItems.set(count, field.itemAt(j));
count++;
}
if (!field.allowUnisonSelection) {
this._addItemsToOptionsArray(oldField);
}
if (_radioButtonGroupingFields.length > 0 && globalSelectedIndex_1 >= 0 && globalSelectedIndex_1 < itemCount_1) {
_radioButtonGroupingFields[0].selectedIndex = globalSelectedIndex_1;
}
return this._fields.length - 1;
}
};
/**
* Finds the first item index within a checkbox field that has an `exportValue`
* matching the provided `value`.
*
* @private
* @param {PdfCheckBoxField} field The checkbox field to search.
* @param {string} value The export value to match.
* @returns {number} The index of the first matching item, or `-1` when not found.
*/
PdfForm.prototype._findFirstByExportValue = function (field, value) {
if (!field || field.itemsCount <= 0 || value == null) {
return -1;
}
for (var i = 0; i < field.itemsCount; i++) {
var item = field.itemAt(i);
if (item && item.exportValue === value) {
return i;
}
}
return -1;
};
/**
* Returns the export value of the currently selected item in a checkbox field.
* If the field dictionary contains a `/V` entry it is returned first; otherwise
* the checked state of individual `PdfStateItem`s is consulted.
*
* @private
* @param {PdfCheckBoxField} field The checkbox field to query.
* @returns {string} The selected export value, or `undefined` if none.
*/
PdfForm.prototype._getSelectedExportValue = function (field) {
var exportValue;
if (!field) {
return undefined;
}
if (field._dictionary && field._dictionary.has('V')) {
var v = field._dictionary.get('V');
if (v && typeof v.name === 'string') {
exportValue = v.name;
}
}
for (var i = 0; i < field.itemsCount; i++) {
var item = field.itemAt(i);
if (item && item.checked) {
exportValue = item.exportValue;
}
}
return exportValue;
};
/**
* Converts a standalone field into a parent with `Kids` by creating a new parent dictionary,
* moving relevant entries, and attaching both the old and new field widgets under it.
*
* @private
* @param {PdfField} oldField The existing field that will become the parent.
* @param {PdfField} newField The new field whose first widget is added as a kid.
* @returns {void}
*/
PdfForm.prototype._updateFieldsKids = function (oldField, newField) {
var oldFieldDict = oldField._dictionary;
var newDict = new _PdfDictionary(this._crossReference);
var newFieldRef = this._crossReference._getNextReference();
var fieldKeys = ['FT', 'T', 'V', 'Ff', 'Opt', 'I', 'TU'];
fieldKeys.forEach(function (key) {
if (oldFieldDict.has(key)) {
newDict.update(key, oldFieldDict.get(key));
delete oldFieldDict._map[key]; // eslint-disable-line
}
});
oldField._dictionary._updated = true;
oldFieldDict.set('Parent', newFieldRef);
newField.itemAt(0)._dictionary.set('Parent', newFieldRef);
var kidElements = [];
kidElements.push(oldField._ref);
var fieldKidRef = newField.itemAt(0)._ref;
kidElements.push(fieldKidRef);
newDict.update('Kids', kidElements);
newDict._updated = true;
this._crossReference._cacheMap.set(newFieldRef, newDict);
var acroForm = this._crossReference._document._catalog._catalogDictionary.get('AcroForm'); // eslint-disable-line
var fields = acroForm.get('Fields'); // eslint-disable-line
var index = fields.indexOf(oldField._ref);
if (index !== -1) {
fields[index] = newFieldRef;
}
oldField._ref = newFieldRef;
oldField._dictionary = newDict;
oldField._kids = newDict.get('Kids');
oldField._dictionary._updated = true;
};
/**
* Populates the `Opt` array for a radio button group when duplicate export values are present,
* ensuring unique option entries for appearance resolution.
*
* @private
* @param {PdfField} baseField The radio button field used to derive option values.
* @returns {void}
*/
PdfForm.prototype._addItemsToOptionsArray = function (baseField) {
var seenValues = new Set();
var duplicateValues = new Set();
var allValues = [];
var radioField = baseField;
for (var i = 0; i < radioField.itemsCount; i++) {
var value = radioField.itemAt(i).value;
allValues.push(value);
if (seenValues.has(value)) {
duplicateValues.add(value);
}
else {
seenValues.add(value);
}
}
if (duplicateValues.size > 0) {
baseField._dictionary.set('Opt', allValues);
}
};
/**
* Computes a unique field name by appending a generated identifier when the base name already exists.
*
* @private
* @param {string} name The proposed field name.
* @returns {string} A unique field name derived from the input.
*/
PdfForm.prototype._getCorrectName = function (name) {
var correctName = name;
var existingIndex = this._fieldName.indexOf(name);
if (existingIndex !== -1) {
var uid = this._generateUniqueIdentifier();
correctName = name + "_" + uid;
}
return correctName;
};
/**
* Generates a simple pseudo-random identifier string for field auto-naming.
*
* @private
* @returns {string} The generated identifier.
*/
PdfForm.prototype._generateUniqueIdentifier = function () {
return Math.floor(Math.random() * 10000).toString();
};
/**
* Remove the specified PDF form field.
*
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Access the loaded form field
* let field: PdfField = document.form.fieldAt(3);
* // Remove the form field
* document.form.removeField(field);
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*
* @param {PdfField} field Field object to remove.
* @returns {void} Nothing.
*/
PdfForm.prototype.removeField = function (field) {
var index = this._fields.indexOf(field._ref);
if (index >= 0) {
this.removeFieldAt(index);
}
};
/**
* Remove the PDF form field from specified index.
*
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Remove the form field from the specified index
* document.form.removeFieldAt(3);
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*
* @param {number} index Field index to remove.
* @returns {void} Nothing.
*/
PdfForm.prototype.removeFieldAt = function (index) {
var field = this.fieldAt(index);
if (field) {
if (field._kidsCount > 0) {
for (var i = field._kidsCount - 1; i >= 0; i--) {
var item = field.itemAt(i);
var page = void 0;
if (item) {
page = item._getPage();
if (page) {
page._removeAnnotation(item._ref);
}
}
}
}
else if (field._dictionary.has('Subtype') && field._dictionary.get('Subtype').name === 'Widget') {
var page = field.page;
if (page) {
page._removeAnnotation(field._ref);
}
}
this._parsedFields.delete(index);
this._reorderParsedAnnotations(index);
}
this._fields.splice(index, 1);
var document = this._crossReference._document;
var catalog = document._catalog;
if (this._fields.length === 0 && document && catalog && catalog._catalogDictionary) {
catalog._catalogDictionary._updated = true;
this._crossReference._allowCatalog = true;
}
this._dictionary.set('Fields', this._fields);
this._dictionary._updated = true;
};
/**
* Rebuilds the parsed fields cache after a removal, compacting indices above the removed position.
*
* @private
* @param {number} index The removed field index.
* @returns {void}
*/
PdfForm.prototype._reorderParsedAnnotations = function (index) {
var result = new Map();
this._parsedFields.forEach(function (value, key) {
if (key > index) {
result.set(key - 1, value);
}
else {
result.set(key, value);
}
});
this._parsedFields = result;
};
/**
* Sets the flag to indicate the new appearance creation
* If true, appearance will not be created. Default appearance has been considered.
* If false, new appearance stream has been created from field values and updated as normal appearance.
*
* ```typescript
* // Load an existing PDF document
* let document: PdfDocument = new PdfDocument(data, password);
* // Set boolean flag to create a new appearance stream for form fields.
* document.form.setDefaultAppearance(false);
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*
* @param {boolean} value Set default appearance.
* @returns {void} Nothing.
*/
PdfForm.prototype.setDefaultAppearance = function (value) {
this._setAppearance = !value;
this._needAppearances = value;
this._isDefaultAppearance = value;
};
PdfForm.prototype.orderFormFields = function (tabOrder) {
var _this = this;
if (tabOrder === null || typeof tabOrder === 'undefined') {
this.orderFormFields(new Map());
}
else {
var tab_1;
var document_1 = this._crossReference._document;
var value_1;
if (tabOrder && tabOrder instanceof Map) {
var setTabOrder_1 = true;
if (tabOrder.size > 0) {
this._tabCollection = tabOrder;
}
else {
setTabOrder_1 = false;
this._tabCollection = tabOrder;
}
var fieldCollection_1 = new Map();
this._fieldCollection = this._getFields();
if (_isNullOrUndefined(this._fieldCollection) && this._fieldCollection.length > 0) {
var page = this._fieldCollection[0].page;
if (page && document_1) {
this._fieldCollection.forEach(function (field) {
if (field.page) {
var index = _getPageIndex(document_1, _this._sortItemByPageIndex(field, true)._pageDictionary);
if (index >= 0) {
if (fieldCollection_1.has(index)) {
value_1 = fieldCollection_1.get(index);
value_1.push(field);
}
else {
value_1 = [];
value_1.push(field);
fieldCollection_1.set(index, value_1);
}
var page_1 = document_1.getPage(index);
if (!_this._tabCollection.has(index)) {
_this._tabCollection.set(index, page_1.tabOrder);
}
if (setTabOrder_1) {
page_1.tabOrder = _this._tabCollection.get(index);
}
}
}
});
var fieldsCount_1 = 0;
fieldCollection_1.forEach(function (value, key) {
_this._tabOrder = _this._tabCollection.get(key);
if (_this._tabOrder !== PdfFormFieldsTabOrder.structure) {
var fields = value;
fields.sort(function (pdfField1, pdfField2) {
return _this._compareFields(pdfField1, pdfField2);
});
fields.forEach(function (field, j) {
var fieldIndex = _this._fieldCollection.indexOf(field);
if (fieldIndex !== -1 && fieldIndex !== fieldsCount_1 + j) {
var fieldToMove = _this._fieldCollection[fieldIndex];
_this._fieldCollection.splice(fieldIndex, 1);
_this._fieldCollection.splice(fieldsCount_1 + j, 0, fieldToMove);
}
});
}
fieldsCount_1 += value.length;
});
}
}
}
else {
this._tabOrder = tabOrder;
tab_1 = this._getOrder(this._tabOrder);
this._fieldCollection = this._getFields();
this._fieldCollection.sort(function (pdfField1, pdfField2) {
return _this._compareFields(pdfField1, pdfField2);
});
}
this._parsedFields.clear();
this._fieldCollection.forEach(function (field, i) {
_this._parsedFields.set(i, field);
_this._fields[i] = field._ref;
if (tab_1) {
field.page._pageDictionary.update('Tabs', tab_1);
}
});
this._dictionary.update('Fields', this._fields);
}
};
/**
* Traverses the AcroForm field tree, collects terminal fields, repairs missing `Parent` links,
* and initializes the form's field list and known form names.
*
* @private
* @returns {void}
*/
PdfForm.prototype._createFields = function () {
var _this = this;
var fields; // eslint-disable-line
var fieldsMap = new Map();
if (this._dictionary && this._dictionary.has('Fields')) {
fields = this._dictionary.get('Fields');
}
var count = 0;
var nodes = []; // eslint-disable-line
var terminalFields = [];
while (fields && fields.length > 0) {
var _loop_1 = function () {
var ref = fields[count]; // eslint-disable-line
var fieldDictionary = void 0;
if (ref && ref instanceof _PdfReference) {
fieldDictionary = this_1._crossReference._fetch(ref);
if (fieldDictionary && fieldDictionary instanceof _PdfDictionary) {
fieldsMap.set(fieldDictionary, ref);
}
}
var fieldKids = void 0;
if (fieldDictionary && fieldDictionary instanceof _PdfDictionary && fieldDictionary.has('Kids')) {
fieldKids = fieldDictionary.get('Kids');
if (fieldKids && fieldKids.length > 0) {
fieldKids.forEach(function (reference) {
if (reference instanceof _PdfReference) {
var kidsDict = _this._crossReference._fetch(reference);
if (kidsDict) {
fieldsMap.set(kidsDict, reference);
if (!kidsDict.has('Parent')) {
kidsDict.update('Parent', ref);
}
}
}
});
}
}
if (!fieldKids) {
if (fieldDictionary && fieldDictionary instanceof _PdfDictionary) {
if (terminalFields.indexOf(fieldDictionary) === -1) {
terminalFields.push(fieldDictionary);
if (fieldDictionary.has('T')) {
var fieldName = fieldDictionary.get('T');
if (this_1._formNames.indexOf(fieldName) === -1) {
this_1._formNames.push(fieldName);
}
}
}
}
}
else {
var isNode = (!fieldDictionary.has('FT')) || this_1._isNode(fieldKids);
if (isNode) {
nodes.push({ fields: fields, count: count });
this_1._hasKids = true;
count = -1;
fields = fieldKids;
}
else {
terminalFields.push(fieldDictionary);
if (fieldDictionary.has('T')) {
var fieldName = fieldDictionary.get('T');
if (this_1._formNames.indexOf(fieldName) === -1) {
this_1._formNames.push(fieldName);
}
}
}
}
};
var this_1 = this;
for (; count < fields.length; count++) {
_loop_1();
}
if (nodes.length === 0) {
break;
}
var entry = nodes.pop(); // eslint-disable-line
fields = entry.fields;
count = entry.count + 1;
}
this._terminalFields = terminalFields;
this._fieldsMap = fieldsMap;
this._createFieldCollection(terminalFields, fieldsMap);
};
PdfForm.prototype._createFieldCollection = function (terminalFields, fieldsMap) {
var pageWidgets = new Map();
var document = this._crossReference._document;
var widgetCollection = [];
this._widgetDictionary = new Map();
if (document) {
for (var i = 0; i < document.pageCount; i++) {
var page = document.getPage(i);
if (!page || !page._pageDictionary) {
continue;
}
var pageDictionary = page._pageDictionary;
var widgetAnnots = [];
if (pageDictionary && pageDictionary.has('Annots')) {
widgetAnnots = pageDictionary.getRaw('Annots');
if (widgetAnnots instanceof _PdfReference) {
widgetAnnots = this._crossReference._fetch(widgetAnnots);
}
}
var widgets = [];
if (widgetAnnots && Array.isArray(widgetAnnots) && widgetAnnots.length > 0) {
for (var j = 0; j < widgetAnnots.length; j++) {
var ref = widgetAnnots[j];
if (ref && ref instanceof _PdfReference) {
widgetCollection.push(ref);
var annotDictionary = this._crossReference._fetch(ref);
if (annotDictionary && annotDictionary.has('Subtype') &&
annotDictionary.get('Subtype').name === 'Widget') {
annotDictionary._reference = ref;
if (annotDictionary.has('T')) {
var tname = annotDictionary.get('T');
if (this._formNames.indexOf(tname) === -1) {
if (tname) {
var arr = this._widgetDictionary.get(tname);
if (!arr) {
arr = [];
this._widgetDictionary.set(tname, arr);
}
arr.push(annotDictionary);
}
}
}
widgets.push(annotDictionary);
}
}
}
pageWidgets.set(i, widgets);
}
}
}
for (var i = 0; i < terminalFields.length; i++) {
var fieldDictionary = terminalFields[i];
var fieldRef = fieldsMap.get(fieldDictionary);
if (fieldRef && this._removeInvalidFields(fieldDictionary, pageWidgets, fieldRef, widgetCollection)) {
if (this._fields.indexOf(fieldRef) === -1) {
this._fields.push(fieldRef);
}
}
}
if (terminalFields.length > 0) {
this._processRemainingWidgets();
}
this._createFormFieldsFromWidgets(terminalFields.length);
};
PdfForm.prototype._processRemainingWidgets = function () {
var document = this._crossReference._document;
for (var i = 0; i < document.pageCount; i++) {
var page = document.getPage(i);
if (!page || !page._pageDictionary) {
continue;
}
var pageDictionary = page._pageDictionary;
var widgetAnnots = [];
if (pageDictionary.has('Annots')) {
widgetAnnots = pageDictionary.getRaw('Annots');
if (widgetAnnots instanceof _PdfReference) {
widgetAnnots = this._crossReference._fetch(widgetAnnots);
}
}
for (var j = 0; j < widgetAnnots.length; j++) {
var ref = widgetAnnots[j];
if (ref instanceof _PdfReference && this._fields.indexOf(ref) === -1) {
var annotDictionary = this._crossReference._fetch(ref);
if (annotDictionary &&
annotDictionary.has('Subtype') &&
annotDictionary.get('Subtype') &&
annotDictionary.get('Subtype').name === 'Widget' &&
annotDictionary.has('P')) {
if (annotDictionary.has('Parent')) {
var parentRef = annotDictionary.getRaw('Parent');
var annotationParentDictionary = void 0;
if (parentRef && parentRef instanceof _PdfReference) {
annotationParentDictionary = this._crossReference._fetch(parentRef);
if (annotationParentDictionary && annotationParentDictionary.has('T')) {
var parentValue = annotationParentDictionary.get('T');
if (this._formNames.indexOf(parentValue) === -1 && this._fields.indexOf(parentRef) === -1) {
this._fields.push(parentRef);
}
}
}
}
else if (annotDictionary.has('FT') && annotDictionary.has('T') && this._formNames.indexOf(annotDictionary.get('T')) === -1) {
this._fields.push(ref);
}
}
}
}
}
};
/**
* Checks whether a field dictionary contains a non-empty `Kids` array.
*
* @private
* @param {_PdfDictionary} dictionary The field dictionary to inspect.
* @returns {boolean} Returns `true` if `Kids` exists and is non-empty; otherwise, `false`.
*/
PdfForm.prototype._hasValidKids = function (dictionary) {
var kidsArray = dictionary.get('Kids'); // eslint-disable-line
return kidsArray && kidsArray.length > 0;
};
/**
* Validates a field (and its descendants) against the page widget lists, removing any invalid
* kid references and determining whether the field has at least one valid descendant.
*
* @private
* @param {_PdfDictionary} dictionary The field or node dictionary.
* @param {Map<number, _PdfDictionary[]>} pageWidgets A mapping of page index to widget dictionaries.
* @param {_PdfReference} ref The reference of the current field/widget.
* @param {_PdfReference[]} widgetCollection Flat list of widget references found in pages.
* @returns {boolean} Returns `true` if the field or any child remains valid; otherwise, `false`.
*/
PdfForm.prototype._removeInvalidFields = function (dictionary, pageWidgets, ref, widgetCollection) {
if (!dictionary) {
return false;
}
if (dictionary.has('Kids') && this._hasValidKids(dictionary)) {
this._isValidKids = true;
var kidsArray = dictionary.get('Kids'); // eslint-disable-line
var invalidKids = [];
var hasValidChild = false;
for (var i = 0; i < kidsArray.length; i++) {
var childDictionary = void 0;
var kidRef = kidsArray[i]; // eslint-disable-line
if (kidRef instanceof _PdfReference) {
childDictionary = this._crossReference._fetch(kidRef);
}
else if (kidRef instanceof _PdfDictionary) {
childDictionary = kidRef;
}
if (childDictionary) {
if (childDictionary.has('P')) {
var pageRef = childDictionary.get('P'); // eslint-disable-line
var pageDictionary = void 0;
if (pageRef instanceof _PdfReference) {
pageDictionary = this._crossReference._fetch(pageRef);
}
else if (pageRef instanceof _PdfDictionary) {
pageDictionary = pageRef;
}
if (pageDictionary && pageDictionary.has('Annots')) {
var annots = pageDictionary.get('Annots'); // eslint-disable-line
if (annots instanceof _PdfReference) {
annots = this._crossReference._fetch(annots);
}
if (annots && Array.isArray(annots)) {
var kidExists = annots.indexOf(kidRef) !== -1;
if (!kidExists) {
invalidKids.push(i);
continue;
}
}
}
}
var isChildValid = this._removeInvalidFields(childDictionary, pageWidgets, kidRef, widgetCollection);
if (isChildValid) {
hasValidChild = true;
}
else {
invalidKids.push(i);
}
}
else {
invalidKids.push(i);
}
}
for (var i = invalidKids.length - 1; i >= 0; i--) {
kidsArray.splice(invalidKids[i], 1);
}
this._isValidKids = false;
if (kidsArray.length > 0 && hasValidChild) {
dictionary.update('Kids', kidsArray);
return true;
}
return false;
}
else {
return this._validateField(dictionary, pageWidgets, ref, widgetCollection);
}
};
/**
* Determines whether a terminal field dictionary represents a valid widget by checking for
* page and rectangle entries or by matching against known page widgets.
*
* @private
* @param {_PdfDictionary} fieldDictionary The terminal field dictionary to validate.
* @param {Map<number, _PdfDictionary[]>} pageWidgets A mapping of page index to widget dictionaries.
* @param {_PdfReference} ref The reference of the field/widget under validation.
* @param {_PdfReference[]} widgetCollection Flat list of widget references found in pages.
* @returns {boolean} Returns `true` if the field is valid; otherwise, `false`.
*/
PdfForm.prototype._validateField = function (fieldDictionary, pageWidgets, ref, widgetCollection) {
var _this = this;
if (!fieldDictionary || !pageWidgets) {
return false;
}
if (fieldDictionary.has('P') && fieldDictionary.has('Rect')) {
return true;
}
if (widgetCollection && widgetCollection.indexOf(ref) !== -1) {
return true;
}
var isValid = false;
pageWidgets.forEach(function (widgets, pageIndex) {
if (widgets && widgets.length > 0) {
for (var j = 0; j < widgets.length; j++) {
var widget = widgets[j];
if (widget && _this._compareWidgets(widget, fieldDictionary)) {
if (_this._isValidKids) {
isValid = true;
if (!fieldDictionary.has('P') && !widget.has('P')) {
var page = widget._crossReference._document.getPage(pageIndex);
fieldDictionary.update('P', page._ref);
}
return;
}
else if (_this._fields.indexOf(widget._reference) === -1) {
if (widget.has('FT') && widget.has('Type') && widget.get('Type').name === 'Annot' &&
widget.has('Subtype') && widget.get('Subtype').name === 'Widget' && !widget.has('P')) {
var page = widget._crossReference._document.getPage(pageIndex);
widget.update('P', page._ref);
}
_this._fields.push(widget._reference);
return;
}
}
}
}
});
return isValid;
};
PdfForm.prototype._compareWidgets = function (widget, annotDictionary) {
var isSame = false;
if (widget && annotDictionary) {
var wCount = widget.size;
var aCount = annotDictionary.size;
if (wCount === aCount || wCount + 1 === aCount || wCount === aCount + 1) {
var widgetType = widget.get('FT');
var annotType = annotDictionary.get('FT');
if (widgetType && annotType && widgetType.name === annotType.name) {
var widgetName = widget.get('T');
var annotName = annotDictionary.get('T');
if (widgetName && annotName && widgetName === annotName) {
var widgetValue = widget.get('V');
var annotValue = annotDictionary.get('V');
if (widgetValue && annotValue && widgetValue === annotValue) {
isSame = true;
}
else {
if (widget.has('Parent') && annotDictionary.has('Parent')) {
var widgetHolder = widget.getRaw('Parent');
var annotHolder = annotDictionary.getRaw('Parent');
if (widgetHolder && annotHolder &&
widgetHolder === annotHolder) {
isSame = true;
}
}
else if (widget.has('TU') && annotDictionary.has('TU')) {
var widgetTU = widget.get('TU');
var annotTU = annotDictionary.get('TU');
if (widgetTU && annotTU && widgetTU === annotTU) {
isSame = true;
}
}
else {
if (widget.has('Rect') && annotDictionary.has('Rect')) {
var widgetArray = widget.get('Rect');
var annotArray = annotDictionary.get('Rect');
if (Array.isArray(widgetArray) && Array.isArray(annotArray) &&
widgetArray.length === annotArray.length) {
var w1 = widgetArray[0];
var a1 = annotArray[0];
var w2 = widgetArray[1];
var a2 = annotArray[1];
if (typeof w1 === 'number' && typeof a1 === 'number' &&
typeof w2 === 'number' && typeof a2 === 'number') {
if (w1 === a1 && w2 === a2) {
isSame = true;
}
}
}
}
}
}
}
}
else if (!widgetType && !annotType) {
if (widget.has('TU') && annotDictionary.has('TU')) {
var widgetTU = widget.get('TU');
var annotTU = annotDictionary.get('TU');
if (widgetTU && annotTU && widgetTU === annotTU) {
isSame = true;
}
else {
isSame = false;
}
}
if (widget.has('Rect') && annotDictionary.has('Rect')) {
var widgetArray = widget.get('Rect');
var annotArray = annotDictionary.get('Rect');
if (Array.isArray(widgetArray) && Array.isArray(annotArray) &&
widgetArray.length === annotArray.length) {
var w1 = widgetArray[0];
var a1 = annotArray[0];
var w2 = widgetArray[1];
var a2 = annotArray[1];
if (typeof w1 === 'number' && typeof a1 === 'number' &&
typeof w2 === 'number' && typeof a2 === 'number') {
if (w1 === a1 && w2 === a2) {
isSame = true;
}
else {
isSame = false;
}
}
}
}
}
}
}
return isSame;
};
/**
* Create form fields from previously discovered terminal widgets starting at index.
* Mirrors the C# CreateFormFieldsFromWidgets logic: add terminal fields not yet added
* and merge radio-group items when multiple widgets share the same name.
*
* @private
* @param {number} startIndex The starting index into terminal fields.
* @returns {void}
*/
PdfForm.prototype._createFormFieldsFromWidgets = function (startIndex) {
if (!this._terminalFields) {
return;
}
this._processTerminalFields(startIndex);
this._processWidgetDictionary();
};
/**
* Process terminal fields starting from the specified index.
* Adds fields to the form, tracking added field names to avoid duplicates.
*
* @private
* @param {number} startIndex The starting index into terminal fields.
* @returns {void}
*/
PdfForm.prototype._processTerminalFields = function (startIndex) {
for (var i = startIndex; i < this._terminalFields.length; i++) {
var field = this.fieldAt(i);
if (!field) {
continue;
}
var name_1 = field.name;
if (name_1 && !this._addedFieldNames.has(name_1)) {
this._doAdd(field);
this._addedFieldNames.add(name_1);
}
else if (!name_1) {
this._doAdd(field);
}
}
};
/**
* Process widget dictionary to handle radio button groups and single widgets.
* Merges radio button items when multiple widgets share the same name.
*
* @private
* @returns {void}
*/
PdfForm.prototype._processWidgetDictionary = function () {
var _this = this;
if (!this._widgetDictionary) {
return;
}
this._widgetDictionary.forEach(function (list) {
if (!list || list.length === 0) {
return;
}
if (list.length > 1) {
_this._processMultipleWidgets(list);
}
else {
_this._processSingleWidget(list[0]);
}
});
};
/**
* Process multiple widgets with the same name, merging radio button items.
*
* @private
* @param {_PdfDictionary[]} list The list of widget dictionaries with the same name.
* @returns {void}
*/
PdfForm.prototype._processMultipleWidgets = function (list) {
var firstDict = list[0];
var baseField = this._getFieldFromDictionary(firstDict);
if (baseField) {
this._terminalFields.push(baseField._dictionary);
this._doAdd(baseField);
}
var radioField = baseField instanceof PdfRadioButtonListField ? baseField : undefined;
for (var k = 1; k < list.length; k++) {
var dict = list[k];
var field = this._getFieldFromDictionary(dict);
if (!field) {
continue;
}
if (radioField && field instanceof PdfRadioButtonListField) {
this._mergeRadioButtonItems(radioField, field);
}
if (field.name) {
this._handleFieldNaming(field);
}
this._terminalFields.push(field._dictionary);
this._doAdd(field);
}
};
/**
* Merge radio button items from one field into another.
*
* @private
* @param {PdfRadioButtonListField} targetField The target radio button field to merge into.
* @param {PdfRadioButtonListField} sourceField The source radio button field to merge from.
* @returns {void}
*/
PdfForm.prototype._mergeRadioButtonItems = function (targetField, sourceField) {
for (var j = 0; j < sourceField.itemsCount; j++) {
var item = sourceField.itemAt(j);
if (item) {
var insertIndex = targetField._parsedItems.size;
targetField._parsedItems.set(insertIndex, item);
}
}
};
/**
* Handle field naming by either correcting duplicate names or adding to the field name list.
*
* @private
* @param {PdfField} field The field to process for naming.
* @returns {void}
*/
PdfForm.prototype._handleFieldNaming = function (field) {
if (!this._validFieldName(field)) {
field._dictionary.update('T', this._getCorrectName(field.name));
}
else {
this._fieldName.push(field.name);
}
};
/**
* Process a single widget dictionary entry.
*
* @private
* @param {_PdfDictionary} dict The widget dictionary to process.
* @returns {void}
*/
PdfForm.prototype._processSingleWidget = function (dict) {
if (!dict) {
return;
}
if (this._terminalFields.indexOf(dict) === -1) {
this._terminalFields.push(dict);
}
var idx = this._terminalFields.length - 1;
var field = idx >= 0 ? this._getField(idx) : undefined;
if (field) {
this._doAdd(field);
}
};
/**
* Validates whether a field name is unique within the form.
*
* @private
* @param {PdfField} field The field to validate.
* @returns {boolean} Returns `true` if the field name is valid (unique); otherwise, `false`.
*/
PdfForm.prototype._validFieldName = function (field) {
return this._fieldName.indexOf(field.name) === -1;
};
/**
* Determines whether the provided `Kids` collection represents a non widget node
* (i.e., its first child is not a `Widget` subtype).
*
* @private
* @param {any[]} kids The array of kid dictionaries or references.
* @returns {boolean} Returns `true` if the entry is a non terminal node; otherwise, `false`.
*/
PdfForm.prototype._isNode = function (kids) {
var isNode = false;
if (_isNullOrUndefined(kids) && kids.length > 0) {
var entry = kids[0]; // eslint-disable-line
var dictionary = void 0;
if (_isNullOrUndefined(entry)) {
if (entry instanceof _PdfDictionary) {
dictionary = entry;
}
else if (entry instanceof _PdfReference) {
dictionary = this._crossReference._fetch(entry);
}
}
if (dictionary && dictionary.has('Subtype')) {
var subtype = dictionary.get('Subtype');
if (subtype && subtype.name !== 'Widget') {
isNode = true;
}
}
}
return isNode;
};
/**
* Enumerates and collects all widget annotation references for the form's fields,
* traversing each field’s `Kids` or the field itself when no children are present.
*
* @private
* @returns {Array<_PdfReference>} The array of widget references.
*/
PdfForm.prototype._parseWidgetReferences = function () {
var _this = this;
if (typeof this._widgetReferences === 'undefined' && this.count > 0) {
this._widgetReferences = [];
this._fields.forEach(function (fieldReference) {
var dictionary = _this._crossReference._fetch(fieldReference);
if (dictionary) {
if (dictionary.has('Kids')) {
var fieldKids = dictionary.get('Kids');
if (fieldKids && fieldKids.length > 0) {
fieldKids.forEach(function (kidReference) {
var kidDictionary;
if (kidReference && kidReference instanceof _PdfDictionary) {
kidDictionary = kidReference;
}
else if (kidReference && kidReference instanceof _PdfReference) {
kidDictionary = _this._crossReference._fetch(kidReference);
}
if (kidDictionary && kidDictionary.has('Subtype')) {
var subtype = kidDictionary.get('Subtype');
if (subtype && subtype.name === 'Widget') {
_this._widgetReferences.push(kidReference);
}
}
});
}
}
else {
_this._widgetReferences.push(fieldReference);
}
}
});
}
return this._widgetReferences;
};
/**
* Performs post-processing for all fields: applies tab order re arrangement (when manual),
* generates appearances or flattens as required, and removes fields flattened for the specified page.
*
* @private
* @param {boolean} isFlatten When `true`, flatten field appearances into the page content.
* @param {PdfPage} [pageToImport] Optional page context to restrict processing/removal.
* @returns {void}
*/
PdfForm.prototype._doPostProcess = function (isFlatten, pageToImport) {
for (var i = this.count - 1; i >= 0; i--) {
var field = this.fieldAt(i);
if (field && !field._isLoaded && typeof field._tabIndex !== 'undefined' && field._tabIndex >= 0) {
var page = field._page;
if (page &&
page._pageDictionary.has('Annots') &&
(page.tabOrder === PdfFormFieldsTabOrder.manual || this._tabOrder === PdfFormFieldsTabOrder.manual)) {
var annots = page._pageDictionary.get('Annots');
var annotationCollection = new PdfAnnotationCollection(annots, this._crossReference, page);
page._annotations = annotationCollection;
for (var i_1 = 0; i_1 < field.itemsCount; i_1++) {
var item = field.itemAt(i_1);
if (item && item instanceof PdfWidgetAnnotation) {
var index = annots.indexOf(item._ref);
if (index < 0) {
index = field._annotationIndex;
}
if (index >= 0) {
var annotations = page.annotations._reArrange(field._ref, field._tabIndex, index);
page._pageDictionary.update('Annots', annotations);
page._pageDictionary._updated = true;
}
}
}
}
}
if (field && ((pageToImport && field.page === pageToImport) || !pageToImport)) {
if (pageToImport) {
field._isImport = true;
}
field._doPostProcess(isFlatten || field.flatten);
if (!isFlatten && field.flatten || (isFlatten && pageToImport && field.page === pageToImport)) {
this.removeFieldAt(i);
}
}
}
};
/**
* Resolves a field's index by matching against stored names, indexed names, actual names,
* and indexed actual names.
*
* @private
* @param {string} name The field name to locate.
* @returns {number} The matching field index, or `-1` if not found.
*/
PdfForm.prototype._getFieldIndex = function (name) {
var index = -1;
if (this.count > 0) {
if (!this._fieldNames) {
this._fieldNames = [];
}
if (!this._indexedFieldNames) {
this._indexedFieldNames = [];
}
if (!this._actualFieldNames) {
this._actualFieldNames = [];
}
if (!this._indexedActualFieldNames) {
this._indexedActualFieldNames = [];
}
for (var i = 0; i < this.count; i++) {
var field = this.fieldAt(i);
if (field) {
var fieldName = field.name;
if (fieldName) {
this._fieldNames.push(fieldName);
this._indexedFieldNames.push(fieldName.split('[')[0]);
}
var actualName = field.actualName;
if (actualName) {
this._actualFieldNames.push(actualName);
this._indexedActualFieldNames.push(actualName.split('[')[0]);
}
}
}
var nameIndex = this._fieldNames.indexOf(name);
if (nameIndex !== -1) {
index = nameIndex;
}
else {
nameIndex = this._indexedFieldNames.indexOf(name);
if (nameIndex !== -1) {
index = nameIndex;
}
else {
nameIndex = this._actualFieldNames.indexOf(name);
if (nameIndex !== -1) {
index = nameIndex;
}
else {
nameIndex = this._indexedActualFieldNames.indexOf(name);
if (nameIndex !== -1) {
index = nameIndex;
}
}
}
}
}
return index;
};
/**
* Materializes and returns all parsed `PdfField` instances for the current form.
*
* @private
* @returns {PdfField[]} The array of loaded fields.
*/
PdfForm.prototype._getFields = function () {
var fields = [];
for (var i = 0; i < this._fields.length; i++) {
var field = this.fieldAt(i);
if (field && field instanceof PdfField) {
fields.push(field);
}
}
return fields;
};
/**
* Maps a tab order enumeration to its corresponding name object (`'R'`, `'C'`, `'S'`), or `null` for `none`.
*
* @private
* @param {PdfFormFieldsTabOrder} tabOrder The tab order mode.
* @returns {_PdfName} The corresponding name entry, or `null` if none.
*/
PdfForm.prototype._getOrder = function (tabOrder) {
if (tabOrder !== PdfFormFieldsTabOrder.none) {
var tabs = '';
if (tabOrder === PdfFormFieldsTabOrder.row) {
tabs = 'R';
}
else if (tabOrder === PdfFormFieldsTabOrder.column) {
tabs = 'C';
}
else if (tabOrder === PdfFormFieldsTabOrder.structure) {
tabs = 'S';
}
return _PdfName.get(tabs);
}
return null;
};
/**
* Compares two fields for ordering based on page index and the current tab order mode
* (`row`, `column`, `manual`, `none`, `structure`, `widget`).
*
* @private
* @param {any} field1 The first field to compare.
* @param {any} field2 The second field to compare.
* @returns {number} A negative value if `field1` precedes `field2`, positive if after, or `0` if equal.
*/
PdfForm.prototype._compareFields = function (field1, field2) {
var result = 0;
var xdiff;
var index;
var page1 = field1.page;
var page2 = field2.page;
var rotation = page1.rotation;
if (page1 && !page1._isNew && page1 instanceof PdfPage && page2 && !page2._isNew && page2 instanceof PdfPage) {
var page1Index = this._sortItemByPageIndex(field1, false)._pageIndex;
var page2Index = this._sortItemByPageIndex(field2, false)._pageIndex;
var rectangle1 = void 0;
if (field1._dictionary.has('Kids')) {
rectangle1 = this._getItemRectangle(field1);
}
else {
rectangle1 = this._getRectangle(field1._dictionary);
}
var rectangle2 = void 0;
if (field2._dictionary.has('Kids')) {
rectangle2 = this._getItemRectangle(field2);
}
else {
rectangle2 = this._getRectangle(field2._dictionary);
}
var firstHeight = rectangle1[3] - rectangle1[1];
var secondHeight = rectangle2[3] - rectangle2[1];
if (rectangle1 && rectangle1.length >= 2 && rectangle2 && rectangle2.length >= 2) {
var x1 = rectangle1[0];
var y1 = rectangle1[1];
var x2 = rectangle2[0];
var y2 = rectangle2[1];
if (typeof x1 === 'number' && typeof x2 === 'number' &&
typeof y1 === 'number' && typeof y2 === 'number') {
index = page1Index - page2Index;
var columnTolerance = 2;
if (this._tabOrder === PdfFormFieldsTabOrder.row) {
if (rotation === PdfRotationAngle.angle0) {
xdiff = this._compare(y2, y1);
if (xdiff !== 0) {
var isValid = xdiff === -1 && y1 > y2 && (y1 - firstHeight / 2) < y2;
isValid = isValid || (xdiff === 1 && y2 > y1 && (y2 - secondHeight / 2) < y1);
if (isValid) {
xdiff = 0;
}
}
if (index !== 0) {
result = index;
}
else if (xdiff !== 0) {
result = xdiff;
}
else {
result = this._compare(x1, x2);
}
}
else if (rotation === PdfRotationAngle.angle270) {
var xDistance = Math.abs(x1 - x2);
if (index !== 0) {
result = index;
}
else if (xDistance <= columnTolerance) {
result = this._compare(y2, y1);
}
else {
result = this._compare(x2, x1);
}
}
}
else if (this._tabOrder === PdfFormFieldsTabOrder.column) {
xdiff = this._compare(x1, x2);
if (index !== 0) {
result = index;
}
else if (xdiff !== 0) {
result = xdiff;
}
else {
result = this._compare(y2, y1);
}
}
else if (this._tabOrder === PdfFormFieldsTabOrder.manual ||
this._tabOrder === PdfFormFieldsTabOrder.none ||
this._tabOrder === PdfFormFieldsTabOrder.structure ||
this._tabOrder === PdfFormFieldsTabOrder.widget) {
if (field1 instanceof PdfField && field2 instanceof PdfField) {
var field1Index = field1.tabIndex;
var field2Index = field2.tabIndex;
xdiff = this._compare(field1Index, field2Index);
if (index !== 0) {
result = index;
}
else {
result = xdiff;
}
}
}
}
}
}
return result;
};
/**
* Retrieves the `Rect` array from the specified dictionary.
*
* @private
* @param {_PdfDictionary} dictionary The dictionary containing a `Rect` entry.
* @returns {number[]} The rectangle `[x1, y1, x2, y2]`, or `undefined` if absent.
*/
PdfForm.prototype._getRectangle = function (dictionary) {
var rect;
if (dictionary && dictionary.has('Rect')) {
rect = dictionary.getArray('Rect');
}
return rect;
};
/**
* Gets a representative widget rectangle for a field with kids, preferring the parent field's
* rectangle first.
*
* @private
* @param {PdfField} field The field whose widget rectangle is requested.
* @returns {number[]} The widget rectangle `[x1, y1, x2, y2]`, or `undefined` if unavailable.
*/
PdfForm.prototype._getItemRectangle = function (field) {
var result;
var dictionary = field._dictionary;
if (!dictionary.has('Kids')) {
result = this._getRectangle(dictionary);
}
if (dictionary.has('Kids')) {
var kids = dictionary.getArray('Kids');
if (!kids || kids.length === 0) {
result = this._getRectangle(dictionary);
}
if (_isNullOrUndefined(kids) && kids.length >= 1) {
if (kids.length === 1) {
result = this._getRectangle(kids[0]);
}
else {
if (field && field.itemsCount > 1) {
result = this._getRectangle(field.itemAt(0)._dictionary);
}
else {
var kids_1 = dictionary.getArray('Kids');
var minX = Number.MAX_VALUE;
var maxY = Number.MIN_VALUE;
var maxX = Number.MIN_VALUE;
var minY = Number.MAX_VALUE;
var hasRect = false;
for (var i = 0; i < kids_1.length; i++) {
var kidRect = void 0;
if (field && field.itemsCount > i && field.itemAt(i)) {
kidRect = this._getRectangle(field.itemAt(i)._dictionary);
}
else {
kidRect = this._getRectangle(kids_1[i]);
}
if (kidRect && kidRect.length === 4) {
hasRect = true;
minX = Math.min(minX, kidRect[0]);
minY = Math.min(minY, kidRect[1]);
maxX = Math.max(maxX, kidRect[2]);
maxY = Math.max(maxY, kidRect[3]);
}
}
return hasRect ? [minX, minY, maxX, maxY] : this._getRectangle(dictionary);
}
}
}
}
return result;
};
/**
* Compares two numeric values.
*
* @private
* @param {number} x The first number.
* @param {number} y The second number.
* @returns {number} Returns `1` if `x > y`, `-1` if `x < y`, otherwise `0`.
*/
PdfForm.prototype._compare = function (x, y) {
if (x > y) {
return 1;
}
else if (x < y) {
return -1;
}
else {
return 0;
}
};
/**
* Compares two widget references by their rectangles according to the current tab order.
*
* @private
* @param {_PdfReference} x The first widget reference.
* @param {_PdfReference} y The second widget reference.
* @returns {number} A negative, positive, or zero value indicating relative order.
*/
PdfForm.prototype._compareKidsElement = function (x, y) {
var xDictionary = this._crossReference._fetch(x);
var yDictionary = this._crossReference._fetch(y);
var xRect = this._getRectangle(xDictionary);
var yRect = this._getRectangle(yDictionary);
var result;
if (xRect && xRect.length >= 2 && yRect && yRect.length >= 2) {
var x1 = xRect[0];
var y1 = xRect[1];
var x2 = yRect[0];
var y2 = yRect[1];
if (typeof x1 === 'number' && typeof x2 === 'number' &&
typeof y1 === 'number' && typeof y2 === 'number') {
var xdiff = void 0;
if (this._tabOrder === PdfFormFieldsTabOrder.row) {
xdiff = this._compare(y2, y1);
if (xdiff !== 0) {
result = xdiff;
}
else {
result = this._compare(x1, x2);
}
}
else if (this._tabOrder === PdfFormFieldsTabOrder.column) {
xdiff = this._compare(x1, x2);
if (xdiff !== 0) {
result = xdiff;
}
else {
result = this._compare(y2, y1);
}
}
else {
result = 0;
}
return result;
}
}
return result;
};
/**
* Sorts a field's items by page/tab order and returns the effective page used for ordering.
*
* @private
* @param {PdfField} field The field whose items should be sorted.
* @param {boolean} hasPageTabOrder When `true`, uses the page's tab order for sorting.
* @returns {PdfPage} The page used to determine ordering.
*/
PdfForm.prototype._sortItemByPageIndex = function (field, hasPageTabOrder) {
var page = field.page;
var tabOrder = this._tabOrder;
this._tabOrder = hasPageTabOrder ? field.page.tabOrder : tabOrder;
this._sortFieldItems(field);
if (field._isLoaded && field._kidsCount > 1) {
page = field.itemAt(0).page;
}
this._tabOrder = tabOrder;
if (typeof page === 'undefined') {
page = field.page;
}
return page;
};
/**
* Sorts the parsed items of supported fields (text, list box, checkbox, radio) using
* the current tab order comparator.
*
* @private
* @param {PdfField} field The field whose items are to be sorted.
* @returns {void}
*/
PdfForm.prototype._sortFieldItems = function (field) {
var _this = this;
if (field._isLoaded && (field instanceof PdfTextBoxField ||
field instanceof PdfListBoxField ||
field instanceof PdfCheckBoxField ||
field instanceof PdfRadioButtonListField)) {
var collection = field._parseItems(); // eslint-disable-line
collection.sort(function (item1, item2) {
return _this._compareFieldItem(item1, item2);
});
field._parsedItems.clear();
collection.forEach(function (item, i) {
field._parsedItems.set(i, item);
});
}
};
/**
* Compares two field items by page index and rectangle, honoring the current tab order
* `row` or `column`.
*
* @private
* @param {any} item1 The first item to compare.
* @param {any} item2 The second item to compare.
* @returns {number} A negative, positive, or zero value indicating relative order.
*/
PdfForm.prototype._compareFieldItem = function (item1, item2) {
var result = 0;
if (typeof item1 !== 'undefined' && typeof item2 !== 'undefined') {
var page1 = item1.page;
var page2 = item2.page;
var array1 = this._getRectangle(item1._dictionary);
var array2 = this._getRectangle(item2._dictionary);
if (array1 && array2) {
var x1 = array1[0];
var y1 = array1[1];
var x2 = array2[0];
var y2 = array2[1];
var xdiff = void 0;
if (this._tabOrder === PdfFormFieldsTabOrder.row) {
xdiff = this._compare(page1._pageIndex, page2._pageIndex);
if (xdiff !== 0) {
result = xdiff;
}
else {
xdiff = this._compare(y2, y1);
if (xdiff !== 0) {
result = xdiff;
}
else {
result = this._compare(x1, x2);
}
}
}
else if (this._tabOrder === PdfFormFieldsTabOrder.column) {
xdiff = this._compare(page1._pageIndex, page2._pageIndex);
if (xdiff !== 0) {
result = xdiff;
}
else {
xdiff = this._compare(x1, x2);
if (xdiff !== 0) {
result = xdiff;
}
else {
result = this._compare(y2, y1);
}
}
}
}
}
return result;
};
/**
* Clears the form’s field reference list and the parsed field cache.
*
* @private
* @returns {void}
*/
PdfForm.prototype._clear = function () {
this._fields = [];
this._parsedFields = new Map();
};
/**
* Checks whether two fields are of the same field class (e.g., both text, both radio).
*
* @private
* @param {PdfField} field1 The first field.
* @param {PdfField} field2 The second field.
* @returns {boolean} Returns `true` if the field types are compatible; otherwise, `false`.
*/
PdfForm.prototype._checkType = function (field1, field2) {
return (field1 instanceof PdfTextBoxField && field2 instanceof PdfTextBoxField) ||
(field1 instanceof PdfButtonField && field2 instanceof PdfButtonField) ||
(field1 instanceof PdfCheckBoxField && field2 instanceof PdfCheckBoxField) ||
(field1 instanceof PdfComboBoxField && field2 instanceof PdfComboBoxField) ||
(field1 instanceof PdfListBoxField && field2 instanceof PdfListBoxField) ||
(field1 instanceof PdfRadioButtonListField && field2 instanceof PdfRadioButtonListField) ||
(field1 instanceof PdfSignatureField && field2 instanceof PdfSignatureField);
};
return PdfForm;
}());
export { PdfForm };