pdf-lite 1.3.3 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acroform/acroform.d.ts +7 -454
- package/dist/acroform/acroform.js +5 -1664
- package/dist/acroform/appearance/index.d.ts +4 -0
- package/dist/acroform/appearance/index.js +4 -0
- package/dist/acroform/appearance/pdf-appearance-stream.d.ts +21 -0
- package/dist/acroform/appearance/pdf-appearance-stream.js +41 -0
- package/dist/acroform/appearance/pdf-button-appearance-stream.d.ts +13 -0
- package/dist/acroform/appearance/pdf-button-appearance-stream.js +54 -0
- package/dist/acroform/appearance/pdf-choice-appearance-stream.d.ts +22 -0
- package/dist/acroform/appearance/pdf-choice-appearance-stream.js +75 -0
- package/dist/acroform/appearance/pdf-graphics.d.ts +51 -0
- package/dist/acroform/appearance/pdf-graphics.js +239 -0
- package/dist/acroform/appearance/pdf-text-appearance-stream.d.ts +22 -0
- package/dist/acroform/appearance/pdf-text-appearance-stream.js +104 -0
- package/dist/acroform/fields/index.d.ts +8 -0
- package/dist/acroform/fields/index.js +8 -0
- package/dist/acroform/fields/pdf-button-form-field.d.ts +23 -0
- package/dist/acroform/fields/pdf-button-form-field.js +102 -0
- package/dist/acroform/fields/pdf-choice-form-field.d.ts +18 -0
- package/dist/acroform/fields/pdf-choice-form-field.js +131 -0
- package/dist/acroform/fields/pdf-default-appearance.d.ts +23 -0
- package/dist/acroform/fields/pdf-default-appearance.js +68 -0
- package/dist/acroform/fields/pdf-form-field-flags.d.ts +45 -0
- package/dist/acroform/fields/pdf-form-field-flags.js +122 -0
- package/dist/acroform/fields/pdf-form-field.d.ts +123 -0
- package/dist/acroform/fields/pdf-form-field.js +433 -0
- package/dist/acroform/fields/pdf-signature-form-field.d.ts +7 -0
- package/dist/acroform/fields/pdf-signature-form-field.js +12 -0
- package/dist/acroform/fields/pdf-text-form-field.d.ts +10 -0
- package/dist/acroform/fields/pdf-text-form-field.js +77 -0
- package/dist/acroform/fields/types.d.ts +26 -0
- package/dist/acroform/fields/types.js +9 -0
- package/dist/acroform/index.d.ts +5 -1
- package/dist/acroform/index.js +5 -1
- package/dist/acroform/manager.d.ts +12 -1
- package/dist/acroform/manager.js +20 -2
- package/dist/acroform/pdf-acro-form.d.ts +69 -0
- package/dist/acroform/pdf-acro-form.js +293 -0
- package/dist/acroform/pdf-font-encoding-cache.d.ts +27 -0
- package/dist/acroform/pdf-font-encoding-cache.js +188 -0
- package/dist/acroform/xfa/index.d.ts +3 -0
- package/dist/acroform/xfa/index.js +2 -0
- package/dist/acroform/xfa/pdf-xfa-data.d.ts +20 -0
- package/dist/acroform/xfa/pdf-xfa-data.js +68 -0
- package/dist/acroform/xfa/pdf-xfa-form.d.ts +11 -0
- package/dist/acroform/xfa/pdf-xfa-form.js +56 -0
- package/dist/annotations/index.d.ts +4 -0
- package/dist/annotations/index.js +4 -0
- package/dist/annotations/pdf-annotation-flags.d.ts +24 -0
- package/dist/annotations/pdf-annotation-flags.js +93 -0
- package/dist/annotations/pdf-annotation-writer.d.ts +20 -0
- package/dist/annotations/pdf-annotation-writer.js +76 -0
- package/dist/annotations/pdf-annotation.d.ts +61 -0
- package/dist/annotations/pdf-annotation.js +106 -0
- package/dist/annotations/pdf-widget-annotation.d.ts +15 -0
- package/dist/annotations/pdf-widget-annotation.js +37 -0
- package/dist/core/objects/pdf-array.d.ts +1 -1
- package/dist/core/objects/pdf-array.js +3 -2
- package/dist/core/objects/pdf-boolean.d.ts +1 -1
- package/dist/core/objects/pdf-boolean.js +3 -2
- package/dist/core/objects/pdf-comment.d.ts +1 -1
- package/dist/core/objects/pdf-comment.js +1 -1
- package/dist/core/objects/pdf-dictionary.d.ts +1 -1
- package/dist/core/objects/pdf-dictionary.js +3 -2
- package/dist/core/objects/pdf-hexadecimal.d.ts +1 -1
- package/dist/core/objects/pdf-hexadecimal.js +3 -2
- package/dist/core/objects/pdf-indirect-object.d.ts +1 -1
- package/dist/core/objects/pdf-indirect-object.js +1 -1
- package/dist/core/objects/pdf-name.d.ts +1 -1
- package/dist/core/objects/pdf-name.js +3 -2
- package/dist/core/objects/pdf-null.d.ts +1 -1
- package/dist/core/objects/pdf-null.js +3 -2
- package/dist/core/objects/pdf-number.d.ts +1 -1
- package/dist/core/objects/pdf-number.js +3 -2
- package/dist/core/objects/pdf-object-reference.d.ts +1 -1
- package/dist/core/objects/pdf-object-reference.js +3 -2
- package/dist/core/objects/pdf-object.d.ts +3 -1
- package/dist/core/objects/pdf-object.js +6 -0
- package/dist/core/objects/pdf-start-xref.d.ts +1 -1
- package/dist/core/objects/pdf-start-xref.js +3 -2
- package/dist/core/objects/pdf-stream.d.ts +4 -3
- package/dist/core/objects/pdf-stream.js +45 -16
- package/dist/core/objects/pdf-string.d.ts +2 -1
- package/dist/core/objects/pdf-string.js +17 -2
- package/dist/core/objects/pdf-trailer.d.ts +1 -1
- package/dist/core/objects/pdf-trailer.js +3 -2
- package/dist/core/objects/pdf-xref-table.d.ts +3 -3
- package/dist/core/objects/pdf-xref-table.js +3 -3
- package/dist/core/parser/incremental-parser.d.ts +0 -13
- package/dist/core/parser/incremental-parser.js +1 -18
- package/dist/core/streams/object-stream.d.ts +1 -1
- package/dist/core/streams/object-stream.js +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +24 -0
- package/dist/fonts/index.d.ts +1 -1
- package/dist/fonts/index.js +1 -1
- package/dist/fonts/pdf-font.d.ts +64 -7
- package/dist/fonts/pdf-font.js +188 -8
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/pdf/index.d.ts +0 -1
- package/dist/pdf/index.js +0 -1
- package/dist/pdf/pdf-document.d.ts +16 -12
- package/dist/pdf/pdf-document.js +51 -37
- package/dist/pdf/pdf-revision.d.ts +1 -1
- package/dist/pdf/pdf-revision.js +1 -1
- package/dist/pdf/pdf-xref-lookup.d.ts +8 -0
- package/dist/pdf/pdf-xref-lookup.js +12 -0
- package/dist/security/handlers/base.js +3 -0
- package/dist/utils/encodePdfText.d.ts +17 -0
- package/dist/utils/encodePdfText.js +61 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/dist/pdf/errors.d.ts +0 -6
- package/dist/pdf/errors.js +0 -6
- package/dist/xfa/index.d.ts +0 -1
- package/dist/xfa/index.js +0 -1
- package/dist/xfa/manager.d.ts +0 -44
- package/dist/xfa/manager.js +0 -136
- /package/dist/fonts/{font-manager.d.ts → manager.d.ts} +0 -0
- /package/dist/fonts/{font-manager.js → manager.js} +0 -0
- /package/dist/utils/{IterableReadableStream.d.ts → iterable-readable-stream.d.ts} +0 -0
- /package/dist/utils/{IterableReadableStream.js → iterable-readable-stream.js} +0 -0
|
@@ -1,1666 +1,7 @@
|
|
|
1
|
-
import { PdfDictionary } from '../core/objects/pdf-dictionary.js';
|
|
2
|
-
import { PdfArray } from '../core/objects/pdf-array.js';
|
|
3
|
-
import { PdfString } from '../core/objects/pdf-string.js';
|
|
4
|
-
import { PdfObjectReference } from '../core/objects/pdf-object-reference.js';
|
|
5
|
-
import { PdfIndirectObject } from '../core/objects/pdf-indirect-object.js';
|
|
6
|
-
import { PdfName } from '../core/objects/pdf-name.js';
|
|
7
|
-
import { PdfBoolean } from '../core/objects/pdf-boolean.js';
|
|
8
|
-
import { PdfNumber } from '../core/objects/pdf-number.js';
|
|
9
|
-
import { PdfStream } from '../core/objects/pdf-stream.js';
|
|
10
|
-
import { buildEncodingMap, decodeWithFontEncoding, } from '../utils/decodeWithFontEncoding.js';
|
|
11
1
|
/**
|
|
12
|
-
*
|
|
2
|
+
* Backward-compatible re-export shim.
|
|
3
|
+
* All classes have been moved to dedicated modules.
|
|
13
4
|
*/
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Choice: 'Ch',
|
|
18
|
-
Signature: 'Sig',
|
|
19
|
-
};
|
|
20
|
-
export class PdfAcroFormField extends PdfIndirectObject {
|
|
21
|
-
_parent;
|
|
22
|
-
defaultGenerateAppearance = true;
|
|
23
|
-
_appearanceStream;
|
|
24
|
-
_appearanceStreamYes; // For button fields: checked state
|
|
25
|
-
form;
|
|
26
|
-
constructor(options) {
|
|
27
|
-
super(options?.other ??
|
|
28
|
-
new PdfIndirectObject({ content: new PdfDictionary() }));
|
|
29
|
-
this.form = options?.form;
|
|
30
|
-
}
|
|
31
|
-
get parent() {
|
|
32
|
-
if (this._parent)
|
|
33
|
-
return this._parent;
|
|
34
|
-
if (!this.form)
|
|
35
|
-
return undefined;
|
|
36
|
-
return this.form.fields.find((f) => f !== this &&
|
|
37
|
-
f.kids.some((k) => k.objectNumber === this.objectNumber &&
|
|
38
|
-
k.generationNumber === this.generationNumber));
|
|
39
|
-
}
|
|
40
|
-
set parent(field) {
|
|
41
|
-
// Remove from old parent's Kids
|
|
42
|
-
if (this._parent) {
|
|
43
|
-
this._parent.kids = this._parent.kids.filter((k) => k.objectNumber !== this.objectNumber ||
|
|
44
|
-
k.generationNumber !== this.generationNumber);
|
|
45
|
-
}
|
|
46
|
-
this._parent = field;
|
|
47
|
-
// Add to new parent's Kids
|
|
48
|
-
if (field) {
|
|
49
|
-
const alreadyInKids = field.kids.some((k) => k.objectNumber === this.objectNumber &&
|
|
50
|
-
k.generationNumber === this.generationNumber);
|
|
51
|
-
if (!alreadyInKids) {
|
|
52
|
-
field.kids = [...field.kids, this.reference];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
get children() {
|
|
57
|
-
if (!this.form)
|
|
58
|
-
return [];
|
|
59
|
-
return this.form.fields.filter((f) => f.parent === this);
|
|
60
|
-
}
|
|
61
|
-
set children(fields) {
|
|
62
|
-
// Clear existing children's parent
|
|
63
|
-
for (const child of this.children) {
|
|
64
|
-
child._parent = undefined;
|
|
65
|
-
}
|
|
66
|
-
// Set new children and update Kids array
|
|
67
|
-
this.kids = fields.map((f) => f.reference);
|
|
68
|
-
for (const child of fields) {
|
|
69
|
-
child._parent = this;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
get siblings() {
|
|
73
|
-
return this.parent?.children ?? [];
|
|
74
|
-
}
|
|
75
|
-
get encodingMap() {
|
|
76
|
-
const fontName = this.fontName;
|
|
77
|
-
if (!fontName)
|
|
78
|
-
return undefined;
|
|
79
|
-
return this.form?.fontEncodingMaps?.get(fontName);
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Convenience method to check if field dictionary is modified
|
|
83
|
-
*/
|
|
84
|
-
isModified() {
|
|
85
|
-
return this.content.isModified();
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Gets the field type
|
|
89
|
-
*/
|
|
90
|
-
get fieldType() {
|
|
91
|
-
const ft = this.content.get('FT')?.value ??
|
|
92
|
-
this.parent?.content.get('FT')?.value;
|
|
93
|
-
switch (ft) {
|
|
94
|
-
case 'Tx':
|
|
95
|
-
return 'Text';
|
|
96
|
-
case 'Btn':
|
|
97
|
-
return 'Button';
|
|
98
|
-
case 'Ch':
|
|
99
|
-
return 'Choice';
|
|
100
|
-
case 'Sig':
|
|
101
|
-
return 'Signature';
|
|
102
|
-
default:
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
set fieldType(type) {
|
|
107
|
-
if (type === null) {
|
|
108
|
-
this.content.delete('FT');
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
this.content.set('FT', new PdfName(PdfFieldType[type]));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
get rect() {
|
|
115
|
-
const rectArray = this.content.get('Rect')?.as((PdfArray));
|
|
116
|
-
if (!rectArray)
|
|
117
|
-
return null;
|
|
118
|
-
return rectArray.items.map((num) => num.value);
|
|
119
|
-
}
|
|
120
|
-
set rect(rect) {
|
|
121
|
-
if (rect === null) {
|
|
122
|
-
this.content.delete('Rect');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const rectArray = new PdfArray(rect.map((num) => new PdfNumber(num)));
|
|
126
|
-
this.content.set('Rect', rectArray);
|
|
127
|
-
}
|
|
128
|
-
get parentRef() {
|
|
129
|
-
const ref = this.content.get('P')?.as(PdfObjectReference);
|
|
130
|
-
return ref ?? null;
|
|
131
|
-
}
|
|
132
|
-
set parentRef(ref) {
|
|
133
|
-
if (ref === null) {
|
|
134
|
-
this.content.delete('P');
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
this.content.set('P', ref);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
get isWidget() {
|
|
141
|
-
const type = this.content.get('Type')?.as(PdfName)?.value;
|
|
142
|
-
const subtype = this.content.get('Subtype')?.as(PdfName)?.value;
|
|
143
|
-
return type === 'Annot' && subtype === 'Widget';
|
|
144
|
-
}
|
|
145
|
-
set isWidget(isWidget) {
|
|
146
|
-
if (isWidget) {
|
|
147
|
-
this.content.set('Type', new PdfName('Annot'));
|
|
148
|
-
this.content.set('Subtype', new PdfName('Widget'));
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
this.content.delete('Type');
|
|
152
|
-
this.content.delete('Subtype');
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Gets the field name
|
|
157
|
-
*/
|
|
158
|
-
get name() {
|
|
159
|
-
const parentName = this.parent?.name ?? '';
|
|
160
|
-
const ownName = this.content.get('T')?.as(PdfString)?.value ?? '';
|
|
161
|
-
if (parentName && ownName) {
|
|
162
|
-
return `${parentName}.${ownName}`;
|
|
163
|
-
}
|
|
164
|
-
return parentName || ownName;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Sets the field name
|
|
168
|
-
*/
|
|
169
|
-
set name(name) {
|
|
170
|
-
this.content.set('T', new PdfString(name));
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Gets the default value
|
|
174
|
-
*/
|
|
175
|
-
get defaultValue() {
|
|
176
|
-
const dv = this.content.get('DV') ?? this.parent?.content.get('DV');
|
|
177
|
-
if (dv instanceof PdfString) {
|
|
178
|
-
return dv.value;
|
|
179
|
-
}
|
|
180
|
-
else if (dv instanceof PdfName) {
|
|
181
|
-
return dv.value;
|
|
182
|
-
}
|
|
183
|
-
return '';
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Sets the default value
|
|
187
|
-
*/
|
|
188
|
-
set defaultValue(val) {
|
|
189
|
-
const fieldType = this.fieldType;
|
|
190
|
-
if (fieldType === 'Button') {
|
|
191
|
-
this.content.set('DV', new PdfName(val));
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
this.content.set('DV', new PdfString(val));
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
get value() {
|
|
198
|
-
// V may be on this field or inherited from parent (parent/kids split)
|
|
199
|
-
const v = this.content.get('V') ?? this.parent?.content.get('V');
|
|
200
|
-
if (v instanceof PdfString) {
|
|
201
|
-
// UTF-16BE strings should always use UTF-16BE decoding regardless of font encoding
|
|
202
|
-
if (v.isUTF16BE) {
|
|
203
|
-
return v.value; // Use PdfString's built-in UTF-16BE decoder
|
|
204
|
-
}
|
|
205
|
-
if (this.encodingMap) {
|
|
206
|
-
return decodeWithFontEncoding(v.raw, this.encodingMap);
|
|
207
|
-
}
|
|
208
|
-
return v.value;
|
|
209
|
-
}
|
|
210
|
-
else if (v instanceof PdfName) {
|
|
211
|
-
return v.value;
|
|
212
|
-
}
|
|
213
|
-
return '';
|
|
214
|
-
}
|
|
215
|
-
set value(val) {
|
|
216
|
-
if (this.value === val) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
// In a parent/kids split, V should be set on the parent field
|
|
220
|
-
const target = this.parent ?? this;
|
|
221
|
-
const fieldType = this.fieldType;
|
|
222
|
-
if (fieldType === 'Button') {
|
|
223
|
-
val = val instanceof PdfString ? val.value : val;
|
|
224
|
-
if (val.trim() === '') {
|
|
225
|
-
target.content.delete('V');
|
|
226
|
-
this.content.delete('AS');
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
target.content.set('V', new PdfName(val));
|
|
230
|
-
this.content.set('AS', new PdfName(val));
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
target.content.set('V', val instanceof PdfString ? val : new PdfString(val));
|
|
234
|
-
}
|
|
235
|
-
if (this.defaultGenerateAppearance) {
|
|
236
|
-
this.generateAppearance();
|
|
237
|
-
// If this is a child widget with siblings, regenerate their appearances too
|
|
238
|
-
for (const sibling of this.siblings) {
|
|
239
|
-
if (sibling !== this &&
|
|
240
|
-
sibling.rect &&
|
|
241
|
-
sibling.defaultGenerateAppearance) {
|
|
242
|
-
sibling.generateAppearance();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
get checked() {
|
|
248
|
-
if (this.fieldType === 'Button') {
|
|
249
|
-
const v = this.content.get('V') ?? this.parent?.content.get('V');
|
|
250
|
-
return v instanceof PdfName && v.value === 'Yes';
|
|
251
|
-
}
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
set checked(isChecked) {
|
|
255
|
-
if (this.fieldType === 'Button') {
|
|
256
|
-
const target = this.parent ?? this;
|
|
257
|
-
if (isChecked) {
|
|
258
|
-
target.content.set('V', new PdfName('Yes'));
|
|
259
|
-
this.content.set('AS', new PdfName('Yes'));
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
target.content.set('V', new PdfName('Off'));
|
|
263
|
-
this.content.set('AS', new PdfName('Off'));
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
get fontSize() {
|
|
268
|
-
const da = this.defaultAppearance || '';
|
|
269
|
-
const match = da.match(/\/[A-Za-z0-9_-]+\s+([\d.]+)\s+Tf/);
|
|
270
|
-
if (match) {
|
|
271
|
-
return parseFloat(match[1]);
|
|
272
|
-
}
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
set fontSize(size) {
|
|
276
|
-
const da = this.defaultAppearance || '';
|
|
277
|
-
if (!da) {
|
|
278
|
-
this.content.set('DA', new PdfString(`/F1 ${size} Tf 0 g`));
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
const updatedDa = da.replace(/(\/[A-Za-z0-9_-]+)\s+[\d.]+\s+Tf/g, `$1 ${size} Tf`);
|
|
282
|
-
this.content.set('DA', new PdfString(updatedDa));
|
|
283
|
-
}
|
|
284
|
-
get fontName() {
|
|
285
|
-
const da = this.defaultAppearance || '';
|
|
286
|
-
const match = da.match(/\/([A-Za-z0-9_-]+)\s+[\d.]+\s+Tf/);
|
|
287
|
-
if (match) {
|
|
288
|
-
return match[1];
|
|
289
|
-
}
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
set fontName(fontName) {
|
|
293
|
-
const da = this.defaultAppearance || '';
|
|
294
|
-
if (!da) {
|
|
295
|
-
this.content.set('DA', new PdfString(`/${fontName} 12 Tf 0 g`));
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const updatedDa = da.replace(/\/[A-Za-z0-9_-]+(\s+[\d.]+\s+Tf)/g, `/${fontName}$1`);
|
|
299
|
-
this.content.set('DA', new PdfString(updatedDa));
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Sets the font using a PdfFont object.
|
|
303
|
-
* Pass null to clear the font.
|
|
304
|
-
*/
|
|
305
|
-
set font(font) {
|
|
306
|
-
if (font === null) {
|
|
307
|
-
// Clear font - set to empty or default
|
|
308
|
-
this.content.set('DA', new PdfString(''));
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
const resourceName = font.resourceName;
|
|
312
|
-
const currentSize = this.fontSize ?? 12;
|
|
313
|
-
const da = this.defaultAppearance || '';
|
|
314
|
-
if (!da) {
|
|
315
|
-
this.content.set('DA', new PdfString(`/${resourceName} ${currentSize} Tf 0 g`));
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
const updatedDa = da.replace(/\/[A-Za-z0-9_-]+(\s+[\d.]+\s+Tf)/g, `/${resourceName}$1`);
|
|
319
|
-
this.content.set('DA', new PdfString(updatedDa));
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Gets field flags (bitwise combination of field attributes)
|
|
323
|
-
*/
|
|
324
|
-
get flags() {
|
|
325
|
-
return (this.content.get('Ff')?.as(PdfNumber)?.value ??
|
|
326
|
-
this.parent?.content.get('Ff')?.as(PdfNumber)?.value ??
|
|
327
|
-
0);
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Sets field flags
|
|
331
|
-
*/
|
|
332
|
-
set flags(flags) {
|
|
333
|
-
this.content.set('Ff', new PdfNumber(flags));
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Gets annotation flags (for visual appearance and behavior)
|
|
337
|
-
*/
|
|
338
|
-
get annotationFlags() {
|
|
339
|
-
return this.content.get('F')?.as(PdfNumber)?.value ?? 0;
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Sets annotation flags
|
|
343
|
-
*/
|
|
344
|
-
set annotationFlags(flags) {
|
|
345
|
-
this.content.set('F', new PdfNumber(flags));
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Checks if the field is read-only (Ff bit 1)
|
|
349
|
-
*/
|
|
350
|
-
get readOnly() {
|
|
351
|
-
return (this.flags & 1) !== 0;
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Sets the field as read-only or editable (Ff bit 1)
|
|
355
|
-
*/
|
|
356
|
-
set readOnly(isReadOnly) {
|
|
357
|
-
if (isReadOnly) {
|
|
358
|
-
this.flags = this.flags | 1;
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
this.flags = this.flags & ~1;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Checks if the field is required
|
|
366
|
-
*/
|
|
367
|
-
get required() {
|
|
368
|
-
return (this.flags & 2) !== 0;
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Sets the field as required or optional
|
|
372
|
-
*/
|
|
373
|
-
set required(isRequired) {
|
|
374
|
-
if (isRequired) {
|
|
375
|
-
this.flags = this.flags | 2;
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
this.flags = this.flags & ~2;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Checks if the field is multiline (for text fields)
|
|
383
|
-
*/
|
|
384
|
-
get multiline() {
|
|
385
|
-
return (this.flags & 4096) !== 0;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Sets the field as multiline (for text fields)
|
|
389
|
-
*/
|
|
390
|
-
set multiline(isMultiline) {
|
|
391
|
-
if (isMultiline) {
|
|
392
|
-
this.flags = this.flags | 4096;
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
this.flags = this.flags & ~4096;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Checks if the field is a password field (for text fields)
|
|
400
|
-
*/
|
|
401
|
-
get password() {
|
|
402
|
-
return (this.flags & 8192) !== 0;
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Sets the field as a password field (for text fields)
|
|
406
|
-
*/
|
|
407
|
-
set password(isPassword) {
|
|
408
|
-
if (isPassword) {
|
|
409
|
-
this.flags = this.flags | 8192;
|
|
410
|
-
}
|
|
411
|
-
else {
|
|
412
|
-
this.flags = this.flags & ~8192;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Checks if the field is a comb field (characters distributed evenly across cells)
|
|
417
|
-
*/
|
|
418
|
-
get comb() {
|
|
419
|
-
return (this.flags & 16777216) !== 0;
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Gets the quadding (text alignment) for this field.
|
|
423
|
-
* 0 = left-justified, 1 = centered, 2 = right-justified
|
|
424
|
-
*/
|
|
425
|
-
get quadding() {
|
|
426
|
-
return (this.content.get('Q')?.as(PdfNumber)?.value ??
|
|
427
|
-
this.parent?.content.get('Q')?.as(PdfNumber)?.value ??
|
|
428
|
-
0);
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Sets the quadding (text alignment) for this field.
|
|
432
|
-
* 0 = left-justified, 1 = centered, 2 = right-justified
|
|
433
|
-
*/
|
|
434
|
-
set quadding(q) {
|
|
435
|
-
this.content.set('Q', new PdfNumber(q));
|
|
436
|
-
}
|
|
437
|
-
/**
|
|
438
|
-
* Gets the options for choice fields (dropdowns, list boxes).
|
|
439
|
-
* Returns an array of option strings.
|
|
440
|
-
*/
|
|
441
|
-
get options() {
|
|
442
|
-
const opt = this.content.get('Opt')?.as((PdfArray)) ??
|
|
443
|
-
this.parent?.content.get('Opt')?.as((PdfArray));
|
|
444
|
-
if (!opt)
|
|
445
|
-
return [];
|
|
446
|
-
return opt.items.map((item) => item.value);
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Sets the options for choice fields (dropdowns, list boxes).
|
|
450
|
-
* Pass an array of strings.
|
|
451
|
-
*/
|
|
452
|
-
set options(options) {
|
|
453
|
-
if (options.length === 0) {
|
|
454
|
-
this.content.delete('Opt');
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
const optArray = new PdfArray(options.map((opt) => new PdfString(opt)));
|
|
458
|
-
this.content.set('Opt', optArray);
|
|
459
|
-
}
|
|
460
|
-
get defaultAppearance() {
|
|
461
|
-
return (this.content.get('DA')?.as(PdfString)?.value ??
|
|
462
|
-
this.parent?.content.get('DA')?.as(PdfString)?.value ??
|
|
463
|
-
null);
|
|
464
|
-
}
|
|
465
|
-
set defaultAppearance(da) {
|
|
466
|
-
this.content.set('DA', new PdfString(da));
|
|
467
|
-
}
|
|
468
|
-
set combo(isCombo) {
|
|
469
|
-
if (isCombo) {
|
|
470
|
-
this.flags = this.flags | 131072;
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
this.flags = this.flags & ~131072;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
get combo() {
|
|
477
|
-
return (this.flags & 131072) !== 0;
|
|
478
|
-
}
|
|
479
|
-
get radio() {
|
|
480
|
-
return (this.flags & 32768) !== 0;
|
|
481
|
-
}
|
|
482
|
-
set radio(isRadio) {
|
|
483
|
-
if (isRadio) {
|
|
484
|
-
this.flags = this.flags | 32768;
|
|
485
|
-
}
|
|
486
|
-
else {
|
|
487
|
-
this.flags = this.flags & ~32768;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
get noToggleToOff() {
|
|
491
|
-
return (this.flags & 16384) !== 0;
|
|
492
|
-
}
|
|
493
|
-
set noToggleToOff(noToggle) {
|
|
494
|
-
if (noToggle) {
|
|
495
|
-
this.flags = this.flags | 16384;
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
this.flags = this.flags & ~16384;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
get combField() {
|
|
502
|
-
return (this.flags & 16777216) !== 0;
|
|
503
|
-
}
|
|
504
|
-
set combField(isComb) {
|
|
505
|
-
if (isComb) {
|
|
506
|
-
this.flags = this.flags | 16777216;
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
this.flags = this.flags & ~16777216;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
get maxLen() {
|
|
513
|
-
return this.content.get('MaxLen')?.as(PdfNumber)?.value ?? null;
|
|
514
|
-
}
|
|
515
|
-
set maxLen(maxLen) {
|
|
516
|
-
if (maxLen === null) {
|
|
517
|
-
this.content.delete('MaxLen');
|
|
518
|
-
}
|
|
519
|
-
else {
|
|
520
|
-
this.content.set('MaxLen', new PdfNumber(maxLen));
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
// ============================================
|
|
524
|
-
// Annotation Flags (F field)
|
|
525
|
-
// ============================================
|
|
526
|
-
/**
|
|
527
|
-
* If true, the annotation is invisible (F bit 1)
|
|
528
|
-
*/
|
|
529
|
-
get invisible() {
|
|
530
|
-
return (this.annotationFlags & 1) !== 0;
|
|
531
|
-
}
|
|
532
|
-
set invisible(value) {
|
|
533
|
-
if (value) {
|
|
534
|
-
this.annotationFlags = this.annotationFlags | 1;
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
this.annotationFlags = this.annotationFlags & ~1;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* If true, the annotation is hidden (F bit 2)
|
|
542
|
-
*/
|
|
543
|
-
get hidden() {
|
|
544
|
-
return (this.annotationFlags & 2) !== 0;
|
|
545
|
-
}
|
|
546
|
-
set hidden(value) {
|
|
547
|
-
if (value) {
|
|
548
|
-
this.annotationFlags = this.annotationFlags | 2;
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
this.annotationFlags = this.annotationFlags & ~2;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* If true, print the annotation when printing (F bit 3)
|
|
556
|
-
*/
|
|
557
|
-
get print() {
|
|
558
|
-
return (this.annotationFlags & 4) !== 0;
|
|
559
|
-
}
|
|
560
|
-
set print(value) {
|
|
561
|
-
if (value) {
|
|
562
|
-
this.annotationFlags = this.annotationFlags | 4;
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
this.annotationFlags = this.annotationFlags & ~4;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* If true, do not zoom annotation when zooming (F bit 4)
|
|
570
|
-
*/
|
|
571
|
-
get noZoom() {
|
|
572
|
-
return (this.annotationFlags & 8) !== 0;
|
|
573
|
-
}
|
|
574
|
-
set noZoom(value) {
|
|
575
|
-
if (value) {
|
|
576
|
-
this.annotationFlags = this.annotationFlags | 8;
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
this.annotationFlags = this.annotationFlags & ~8;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* If true, do not rotate annotation when rotating (F bit 5)
|
|
584
|
-
*/
|
|
585
|
-
get noRotate() {
|
|
586
|
-
return (this.annotationFlags & 16) !== 0;
|
|
587
|
-
}
|
|
588
|
-
set noRotate(value) {
|
|
589
|
-
if (value) {
|
|
590
|
-
this.annotationFlags = this.annotationFlags | 16;
|
|
591
|
-
}
|
|
592
|
-
else {
|
|
593
|
-
this.annotationFlags = this.annotationFlags & ~16;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* If true, do not display annotation on screen (F bit 6)
|
|
598
|
-
*/
|
|
599
|
-
get noView() {
|
|
600
|
-
return (this.annotationFlags & 32) !== 0;
|
|
601
|
-
}
|
|
602
|
-
set noView(value) {
|
|
603
|
-
if (value) {
|
|
604
|
-
this.annotationFlags = this.annotationFlags | 32;
|
|
605
|
-
}
|
|
606
|
-
else {
|
|
607
|
-
this.annotationFlags = this.annotationFlags & ~32;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* If true, annotation is locked (F bit 8)
|
|
612
|
-
*/
|
|
613
|
-
get locked() {
|
|
614
|
-
return (this.annotationFlags & 128) !== 0;
|
|
615
|
-
}
|
|
616
|
-
set locked(value) {
|
|
617
|
-
if (value) {
|
|
618
|
-
this.annotationFlags = this.annotationFlags | 128;
|
|
619
|
-
}
|
|
620
|
-
else {
|
|
621
|
-
this.annotationFlags = this.annotationFlags & ~128;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
// ============================================
|
|
625
|
-
// Field Flags (Ff field) - Additional
|
|
626
|
-
// ============================================
|
|
627
|
-
/**
|
|
628
|
-
* If true, field value should not be exported (Ff bit 3)
|
|
629
|
-
*/
|
|
630
|
-
get noExport() {
|
|
631
|
-
return (this.flags & 4) !== 0;
|
|
632
|
-
}
|
|
633
|
-
set noExport(value) {
|
|
634
|
-
if (value) {
|
|
635
|
-
this.flags = this.flags | 4;
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
this.flags = this.flags & ~4;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* If true, field is a pushbutton (Ff bit 17)
|
|
643
|
-
*/
|
|
644
|
-
get pushButton() {
|
|
645
|
-
return (this.flags & 65536) !== 0;
|
|
646
|
-
}
|
|
647
|
-
set pushButton(value) {
|
|
648
|
-
if (value) {
|
|
649
|
-
this.flags = this.flags | 65536;
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
this.flags = this.flags & ~65536;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* If true, text field allows editing (Ff bit 19)
|
|
657
|
-
*/
|
|
658
|
-
get edit() {
|
|
659
|
-
return (this.flags & 262144) !== 0;
|
|
660
|
-
}
|
|
661
|
-
set edit(value) {
|
|
662
|
-
if (value) {
|
|
663
|
-
this.flags = this.flags | 262144;
|
|
664
|
-
}
|
|
665
|
-
else {
|
|
666
|
-
this.flags = this.flags & ~262144;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
/**
|
|
670
|
-
* If true, choice options should be sorted alphabetically (Ff bit 20)
|
|
671
|
-
*/
|
|
672
|
-
get sort() {
|
|
673
|
-
return (this.flags & 524288) !== 0;
|
|
674
|
-
}
|
|
675
|
-
set sort(value) {
|
|
676
|
-
if (value) {
|
|
677
|
-
this.flags = this.flags | 524288;
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
this.flags = this.flags & ~524288;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* If true, allows multiple selections in choice field (Ff bit 22)
|
|
685
|
-
*/
|
|
686
|
-
get multiSelect() {
|
|
687
|
-
return (this.flags & 2097152) !== 0;
|
|
688
|
-
}
|
|
689
|
-
set multiSelect(value) {
|
|
690
|
-
if (value) {
|
|
691
|
-
this.flags = this.flags | 2097152;
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
this.flags = this.flags & ~2097152;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* If true, do not spell check this field (Ff bit 23)
|
|
699
|
-
*/
|
|
700
|
-
get doNotSpellCheck() {
|
|
701
|
-
return (this.flags & 4194304) !== 0;
|
|
702
|
-
}
|
|
703
|
-
set doNotSpellCheck(value) {
|
|
704
|
-
if (value) {
|
|
705
|
-
this.flags = this.flags | 4194304;
|
|
706
|
-
}
|
|
707
|
-
else {
|
|
708
|
-
this.flags = this.flags & ~4194304;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* If true, do not scroll text field (Ff bit 24)
|
|
713
|
-
*/
|
|
714
|
-
get doNotScroll() {
|
|
715
|
-
return (this.flags & 8388608) !== 0;
|
|
716
|
-
}
|
|
717
|
-
set doNotScroll(value) {
|
|
718
|
-
if (value) {
|
|
719
|
-
this.flags = this.flags | 8388608;
|
|
720
|
-
}
|
|
721
|
-
else {
|
|
722
|
-
this.flags = this.flags & ~8388608;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* If true, commit field value immediately on selection change (Ff bit 27)
|
|
727
|
-
*/
|
|
728
|
-
get commitOnSelChange() {
|
|
729
|
-
return (this.flags & 67108864) !== 0;
|
|
730
|
-
}
|
|
731
|
-
set commitOnSelChange(value) {
|
|
732
|
-
if (value) {
|
|
733
|
-
this.flags = this.flags | 67108864;
|
|
734
|
-
}
|
|
735
|
-
else {
|
|
736
|
-
this.flags = this.flags & ~67108864;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
get kids() {
|
|
740
|
-
const kidsArray = this.content
|
|
741
|
-
.get('Kids')
|
|
742
|
-
?.as((PdfArray));
|
|
743
|
-
if (!kidsArray)
|
|
744
|
-
return [];
|
|
745
|
-
return kidsArray.items;
|
|
746
|
-
}
|
|
747
|
-
set kids(kids) {
|
|
748
|
-
if (kids.length === 0) {
|
|
749
|
-
this.content.delete('Kids');
|
|
750
|
-
return;
|
|
751
|
-
}
|
|
752
|
-
const kidsArray = new PdfArray(kids);
|
|
753
|
-
this.content.set('Kids', kidsArray);
|
|
754
|
-
}
|
|
755
|
-
get appearanceStreamDict() {
|
|
756
|
-
const apDict = this.content.get('AP')?.as(PdfDictionary);
|
|
757
|
-
if (!apDict)
|
|
758
|
-
return null;
|
|
759
|
-
return apDict;
|
|
760
|
-
}
|
|
761
|
-
set appearanceStreamDict(dict) {
|
|
762
|
-
if (dict === null) {
|
|
763
|
-
this.content.delete('AP');
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
this.content.set('AP', dict);
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Generates an appearance stream for a text field using iText's approach.
|
|
770
|
-
*
|
|
771
|
-
* This generates an appearance with text using the same positioning formula as iText:
|
|
772
|
-
* - textY = (height - fontSize) / 2 + fontSize * 0.2
|
|
773
|
-
* - Wrapped in marked content blocks (/Tx BMC ... EMC)
|
|
774
|
-
* - Field remains editable unless makeReadOnly is set
|
|
775
|
-
*
|
|
776
|
-
* For editable fields (default, no options):
|
|
777
|
-
* - Text visible immediately
|
|
778
|
-
* - Field remains fully editable
|
|
779
|
-
* - No save dialog (needAppearances = false)
|
|
780
|
-
* - Text positioning matches iText
|
|
781
|
-
*
|
|
782
|
-
* For read-only fields (makeReadOnly: true):
|
|
783
|
-
* - Same appearance generation
|
|
784
|
-
* - Field is set as read-only
|
|
785
|
-
*
|
|
786
|
-
* @param options.makeReadOnly - If true, sets field as read-only
|
|
787
|
-
* @returns true if appearance was generated successfully
|
|
788
|
-
*/
|
|
789
|
-
generateAppearance(options) {
|
|
790
|
-
const fieldType = this.fieldType;
|
|
791
|
-
// Route to appropriate generation method based on field type
|
|
792
|
-
if (fieldType === 'Text') {
|
|
793
|
-
return this.generateTextAppearance(options);
|
|
794
|
-
}
|
|
795
|
-
else if (fieldType === 'Button') {
|
|
796
|
-
return this.generateButtonAppearance(options);
|
|
797
|
-
}
|
|
798
|
-
else if (fieldType === 'Choice') {
|
|
799
|
-
return this.generateChoiceAppearance(options);
|
|
800
|
-
}
|
|
801
|
-
return false;
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* Generates appearance for text fields
|
|
805
|
-
* @internal
|
|
806
|
-
*/
|
|
807
|
-
generateTextAppearance(options) {
|
|
808
|
-
const rect = this.rect;
|
|
809
|
-
if (!rect || rect.length !== 4)
|
|
810
|
-
return false;
|
|
811
|
-
const [x1, y1, x2, y2] = rect;
|
|
812
|
-
const width = x2 - x1;
|
|
813
|
-
const height = y2 - y1;
|
|
814
|
-
// Get the default appearance string (may be inherited from parent)
|
|
815
|
-
const da = this.content.get('DA')?.as(PdfString)?.value ??
|
|
816
|
-
this.parent?.content.get('DA')?.as(PdfString)?.value;
|
|
817
|
-
if (!da)
|
|
818
|
-
return false;
|
|
819
|
-
// Get the field value
|
|
820
|
-
const value = this.value;
|
|
821
|
-
// Parse font name and size from DA
|
|
822
|
-
const fontMatch = da.match(/\/(\w+)\s+([\d.]+)\s+Tf/);
|
|
823
|
-
if (!fontMatch)
|
|
824
|
-
return false;
|
|
825
|
-
const fontName = fontMatch[1];
|
|
826
|
-
let fontSize = parseFloat(fontMatch[2]);
|
|
827
|
-
// If font size is 0 or invalid, use a default size
|
|
828
|
-
if (!fontSize || fontSize <= 0) {
|
|
829
|
-
fontSize = 12; // Default to 12pt
|
|
830
|
-
}
|
|
831
|
-
// Parse color from DA (format: "r g b rg" or "g g")
|
|
832
|
-
let colorOp = '0 g'; // default to black
|
|
833
|
-
const rgMatch = da.match(/([\d.]+\s+[\d.]+\s+[\d.]+)\s+rg/);
|
|
834
|
-
const gMatch = da.match(/([\d.]+)\s+g/);
|
|
835
|
-
if (rgMatch) {
|
|
836
|
-
colorOp = `${rgMatch[1]} rg`;
|
|
837
|
-
}
|
|
838
|
-
else if (gMatch) {
|
|
839
|
-
colorOp = `${gMatch[1]} g`;
|
|
840
|
-
}
|
|
841
|
-
// Reconstruct the DA string with the correct font size
|
|
842
|
-
const reconstructedDA = `/${fontName} ${fontSize} Tf ${colorOp}`;
|
|
843
|
-
// Calculate text position using Adobe Acrobat's positioning formula
|
|
844
|
-
// After testing, this formula matches Acrobat's rendering most closely
|
|
845
|
-
const padding = 2;
|
|
846
|
-
// Vertical positioning: Position baseline to match viewer behavior
|
|
847
|
-
// This accounts for the font's typical metrics (cap height, descenders, etc.)
|
|
848
|
-
const textY = (height - fontSize) / 2 + fontSize * 0.2;
|
|
849
|
-
// Escape special characters in the text value
|
|
850
|
-
const escapedValue = value
|
|
851
|
-
.replace(/\\/g, '\\\\')
|
|
852
|
-
.replace(/\(/g, '\\(')
|
|
853
|
-
.replace(/\)/g, '\\)')
|
|
854
|
-
.replace(/\r/g, '\\r')
|
|
855
|
-
.replace(/\n/g, '\\n');
|
|
856
|
-
// Generate text positioning based on field type
|
|
857
|
-
let textContent;
|
|
858
|
-
if (this.multiline) {
|
|
859
|
-
// Multiline text field: handle line breaks
|
|
860
|
-
const lines = value.split('\n');
|
|
861
|
-
const lineHeight = fontSize * 1.2;
|
|
862
|
-
const startY = height - padding - fontSize;
|
|
863
|
-
textContent = 'BT\n';
|
|
864
|
-
textContent += `${reconstructedDA}\n`;
|
|
865
|
-
textContent += `${padding} ${startY} Td\n`;
|
|
866
|
-
for (let i = 0; i < lines.length; i++) {
|
|
867
|
-
const line = lines[i]
|
|
868
|
-
.replace(/\\/g, '\\\\')
|
|
869
|
-
.replace(/\(/g, '\\(')
|
|
870
|
-
.replace(/\)/g, '\\)')
|
|
871
|
-
.replace(/\r/g, '');
|
|
872
|
-
if (i > 0) {
|
|
873
|
-
textContent += `0 ${-lineHeight} Td\n`;
|
|
874
|
-
}
|
|
875
|
-
textContent += `(${line}) Tj\n`;
|
|
876
|
-
}
|
|
877
|
-
textContent += 'ET\n';
|
|
878
|
-
}
|
|
879
|
-
else if (this.comb && this.maxLen) {
|
|
880
|
-
// Comb field: position each character in its own cell
|
|
881
|
-
const cellWidth = width / this.maxLen;
|
|
882
|
-
const chars = value.split('');
|
|
883
|
-
textContent = 'BT\n';
|
|
884
|
-
textContent += `${reconstructedDA}\n`;
|
|
885
|
-
for (let i = 0; i < chars.length && i < this.maxLen; i++) {
|
|
886
|
-
// Center each character in its cell
|
|
887
|
-
const cellX = cellWidth * i + cellWidth / 2 - fontSize * 0.3;
|
|
888
|
-
const escapedChar = chars[i]
|
|
889
|
-
.replace(/\\/g, '\\\\')
|
|
890
|
-
.replace(/\(/g, '\\(')
|
|
891
|
-
.replace(/\)/g, '\\)');
|
|
892
|
-
textContent += `${cellX} ${textY} Td\n`;
|
|
893
|
-
textContent += `(${escapedChar}) Tj\n`;
|
|
894
|
-
textContent += `${-cellX} ${-textY} Td\n`; // Reset position
|
|
895
|
-
}
|
|
896
|
-
textContent += 'ET\n';
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
// Regular text field
|
|
900
|
-
const textX = padding;
|
|
901
|
-
textContent = `BT
|
|
902
|
-
${reconstructedDA}
|
|
903
|
-
${textX} ${textY} Td
|
|
904
|
-
(${escapedValue}) Tj
|
|
905
|
-
ET
|
|
906
|
-
`;
|
|
907
|
-
}
|
|
908
|
-
// Generate appearance with text (iText approach)
|
|
909
|
-
// Use marked content to properly tag the text field content
|
|
910
|
-
const contentStream = `/Tx BMC
|
|
911
|
-
q
|
|
912
|
-
${textContent}Q
|
|
913
|
-
EMC
|
|
914
|
-
`;
|
|
915
|
-
// Create the appearance stream
|
|
916
|
-
const appearanceDict = new PdfDictionary();
|
|
917
|
-
appearanceDict.set('Type', new PdfName('XObject'));
|
|
918
|
-
appearanceDict.set('Subtype', new PdfName('Form'));
|
|
919
|
-
appearanceDict.set('FormType', new PdfNumber(1));
|
|
920
|
-
appearanceDict.set('BBox', new PdfArray([
|
|
921
|
-
new PdfNumber(0),
|
|
922
|
-
new PdfNumber(0),
|
|
923
|
-
new PdfNumber(width),
|
|
924
|
-
new PdfNumber(height),
|
|
925
|
-
]));
|
|
926
|
-
// Add font resources so Acrobat can resolve the font name.
|
|
927
|
-
// Prefer the field's own DR (which has correctly resolved refs in incremental updates),
|
|
928
|
-
// then fall back to the AcroForm-level DR.
|
|
929
|
-
const fieldDR = this.content
|
|
930
|
-
.get('DR')
|
|
931
|
-
?.as(PdfDictionary);
|
|
932
|
-
const acroformDR = this.form?.defaultResources;
|
|
933
|
-
const fontSource = fieldDR?.get('Font')?.as(PdfDictionary) ??
|
|
934
|
-
acroformDR?.get('Font')?.as(PdfDictionary);
|
|
935
|
-
if (fontSource && fontName) {
|
|
936
|
-
const fontRef = fontSource.get(fontName);
|
|
937
|
-
if (fontRef) {
|
|
938
|
-
const resourceFontDict = new PdfDictionary();
|
|
939
|
-
resourceFontDict.set(fontName, fontRef);
|
|
940
|
-
const resourcesDict = new PdfDictionary();
|
|
941
|
-
resourcesDict.set('Font', resourceFontDict);
|
|
942
|
-
appearanceDict.set('Resources', resourcesDict);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
const stream = new PdfStream({
|
|
946
|
-
header: appearanceDict,
|
|
947
|
-
original: contentStream,
|
|
948
|
-
});
|
|
949
|
-
// Store the appearance stream for later writing
|
|
950
|
-
this._appearanceStream = stream;
|
|
951
|
-
// Configure field flags based on options
|
|
952
|
-
if (options?.makeReadOnly) {
|
|
953
|
-
// Set the read-only flag (Ff bit 0)
|
|
954
|
-
this.readOnly = true;
|
|
955
|
-
// Ensure the annotation is visible and printable
|
|
956
|
-
this.print = true;
|
|
957
|
-
this.noZoom = true;
|
|
958
|
-
}
|
|
959
|
-
else {
|
|
960
|
-
// For editable fields, just ensure print flag is set
|
|
961
|
-
this.print = true;
|
|
962
|
-
}
|
|
963
|
-
return true;
|
|
964
|
-
}
|
|
965
|
-
/**
|
|
966
|
-
* Generates appearance for button fields (checkboxes, radio buttons)
|
|
967
|
-
* @internal
|
|
968
|
-
*/
|
|
969
|
-
generateButtonAppearance(options) {
|
|
970
|
-
const rect = this.rect;
|
|
971
|
-
if (!rect || rect.length !== 4)
|
|
972
|
-
return false;
|
|
973
|
-
const [x1, y1, x2, y2] = rect;
|
|
974
|
-
const width = x2 - x1;
|
|
975
|
-
const height = y2 - y1;
|
|
976
|
-
const size = Math.min(width, height);
|
|
977
|
-
// Check if this is a radio button by looking at parent/siblings
|
|
978
|
-
// Radio buttons typically have Ff bit 15 (Radio) set
|
|
979
|
-
const isRadio = (this.flags & 32768) !== 0;
|
|
980
|
-
// Helper to create appearance stream dictionary
|
|
981
|
-
const createAppearanceStream = (content) => {
|
|
982
|
-
const appearanceDict = new PdfDictionary();
|
|
983
|
-
appearanceDict.set('Type', new PdfName('XObject'));
|
|
984
|
-
appearanceDict.set('Subtype', new PdfName('Form'));
|
|
985
|
-
appearanceDict.set('FormType', new PdfNumber(1));
|
|
986
|
-
appearanceDict.set('BBox', new PdfArray([
|
|
987
|
-
new PdfNumber(0),
|
|
988
|
-
new PdfNumber(0),
|
|
989
|
-
new PdfNumber(width),
|
|
990
|
-
new PdfNumber(height),
|
|
991
|
-
]));
|
|
992
|
-
// Add ZapfDingbats font for checkmarks
|
|
993
|
-
const resources = new PdfDictionary();
|
|
994
|
-
const fonts = new PdfDictionary();
|
|
995
|
-
const zapfFont = new PdfDictionary();
|
|
996
|
-
zapfFont.set('Type', new PdfName('Font'));
|
|
997
|
-
zapfFont.set('Subtype', new PdfName('Type1'));
|
|
998
|
-
zapfFont.set('BaseFont', new PdfName('ZapfDingbats'));
|
|
999
|
-
fonts.set('ZaDb', zapfFont);
|
|
1000
|
-
resources.set('Font', fonts);
|
|
1001
|
-
appearanceDict.set('Resources', resources);
|
|
1002
|
-
return new PdfStream({
|
|
1003
|
-
header: appearanceDict,
|
|
1004
|
-
original: content,
|
|
1005
|
-
});
|
|
1006
|
-
};
|
|
1007
|
-
// Generate "Off" state appearance (unchecked/empty)
|
|
1008
|
-
const offStream = createAppearanceStream('');
|
|
1009
|
-
// Generate "Yes" state appearance (checked)
|
|
1010
|
-
let yesContent;
|
|
1011
|
-
if (isRadio) {
|
|
1012
|
-
// Radio button: filled circle using 4 Bezier curves to approximate a circle
|
|
1013
|
-
const center = size / 2;
|
|
1014
|
-
const radius = size * 0.35;
|
|
1015
|
-
const k = 0.5522847498; // Magic number for circular Bezier curves (4/3 * tan(π/8))
|
|
1016
|
-
const kRadius = k * radius;
|
|
1017
|
-
// Draw a filled circle using 4 cubic Bezier curves
|
|
1018
|
-
yesContent = `q
|
|
1019
|
-
0 0 0 rg
|
|
1020
|
-
${center} ${center + radius} m
|
|
1021
|
-
${center + kRadius} ${center + radius} ${center + radius} ${center + kRadius} ${center + radius} ${center} c
|
|
1022
|
-
${center + radius} ${center - kRadius} ${center + kRadius} ${center - radius} ${center} ${center - radius} c
|
|
1023
|
-
${center - kRadius} ${center - radius} ${center - radius} ${center - kRadius} ${center - radius} ${center} c
|
|
1024
|
-
${center - radius} ${center + kRadius} ${center - kRadius} ${center + radius} ${center} ${center + radius} c
|
|
1025
|
-
f
|
|
1026
|
-
Q
|
|
1027
|
-
`;
|
|
1028
|
-
}
|
|
1029
|
-
else {
|
|
1030
|
-
// Checkbox: checkmark (using ZapfDingbats character)
|
|
1031
|
-
const checkSize = size * 0.8;
|
|
1032
|
-
const offset = (size - checkSize) / 2;
|
|
1033
|
-
yesContent = `q
|
|
1034
|
-
BT
|
|
1035
|
-
/ZaDb ${checkSize} Tf
|
|
1036
|
-
${offset} ${offset} Td
|
|
1037
|
-
(4) Tj
|
|
1038
|
-
ET
|
|
1039
|
-
Q
|
|
1040
|
-
`;
|
|
1041
|
-
}
|
|
1042
|
-
const yesStream = createAppearanceStream(yesContent);
|
|
1043
|
-
// Store both appearance streams in a state dictionary
|
|
1044
|
-
// We'll use a special structure to hold both states
|
|
1045
|
-
this._appearanceStream = offStream; // Store Off as default
|
|
1046
|
-
this._appearanceStreamYes = yesStream; // Store Yes state separately
|
|
1047
|
-
if (options?.makeReadOnly) {
|
|
1048
|
-
this.readOnly = true;
|
|
1049
|
-
this.print = true;
|
|
1050
|
-
this.noZoom = true;
|
|
1051
|
-
}
|
|
1052
|
-
return true;
|
|
1053
|
-
}
|
|
1054
|
-
/**
|
|
1055
|
-
* Generates appearance for choice fields (dropdowns, list boxes)
|
|
1056
|
-
* @internal
|
|
1057
|
-
*/
|
|
1058
|
-
generateChoiceAppearance(options) {
|
|
1059
|
-
const rect = this.rect;
|
|
1060
|
-
if (!rect || rect.length !== 4)
|
|
1061
|
-
return false;
|
|
1062
|
-
const [x1, y1, x2, y2] = rect;
|
|
1063
|
-
const width = x2 - x1;
|
|
1064
|
-
const height = y2 - y1;
|
|
1065
|
-
// Get the default appearance string (may be inherited from parent)
|
|
1066
|
-
const da = this.content.get('DA')?.as(PdfString)?.value ??
|
|
1067
|
-
this.parent?.content.get('DA')?.as(PdfString)?.value;
|
|
1068
|
-
if (!da)
|
|
1069
|
-
return false;
|
|
1070
|
-
const value = this.value;
|
|
1071
|
-
if (!value)
|
|
1072
|
-
return false;
|
|
1073
|
-
// Parse font and size from DA
|
|
1074
|
-
const fontMatch = da.match(/\/(\w+)\s+([\d.]+)\s+Tf/);
|
|
1075
|
-
if (!fontMatch)
|
|
1076
|
-
return false;
|
|
1077
|
-
const fontName = fontMatch[1];
|
|
1078
|
-
let fontSize = parseFloat(fontMatch[2]);
|
|
1079
|
-
if (!fontSize || fontSize <= 0) {
|
|
1080
|
-
fontSize = 12;
|
|
1081
|
-
}
|
|
1082
|
-
const colorOp = '0 g';
|
|
1083
|
-
const reconstructedDA = `/${fontName} ${fontSize} Tf ${colorOp}`;
|
|
1084
|
-
const padding = 2;
|
|
1085
|
-
const textY = (height - fontSize) / 2 + fontSize * 0.2;
|
|
1086
|
-
const textX = padding;
|
|
1087
|
-
const escapedValue = value
|
|
1088
|
-
.replace(/\\/g, '\\\\')
|
|
1089
|
-
.replace(/\(/g, '\\(')
|
|
1090
|
-
.replace(/\)/g, '\\)');
|
|
1091
|
-
// Check if this is a combo box (dropdown) - Ff bit 17 (131072)
|
|
1092
|
-
const isCombo = (this.flags & 131072) !== 0;
|
|
1093
|
-
// Draw dropdown arrow for combo boxes
|
|
1094
|
-
let arrowGraphics = '';
|
|
1095
|
-
if (isCombo) {
|
|
1096
|
-
// Reserve space for the arrow on the right
|
|
1097
|
-
const arrowWidth = height * 0.8; // Arrow area width
|
|
1098
|
-
const arrowX = width - arrowWidth - 2; // X position for arrow
|
|
1099
|
-
const arrowY = height / 2; // Y center
|
|
1100
|
-
const arrowSize = height * 0.3; // Triangle size
|
|
1101
|
-
// Draw a small downward-pointing triangle
|
|
1102
|
-
arrowGraphics = `
|
|
1103
|
-
q
|
|
1104
|
-
0.5 0.5 0.5 rg
|
|
1105
|
-
${arrowX + arrowWidth / 2} ${arrowY - arrowSize / 3} m
|
|
1106
|
-
${arrowX + arrowWidth / 2 - arrowSize / 2} ${arrowY + arrowSize / 3} l
|
|
1107
|
-
${arrowX + arrowWidth / 2 + arrowSize / 2} ${arrowY + arrowSize / 3} l
|
|
1108
|
-
f
|
|
1109
|
-
Q
|
|
1110
|
-
`;
|
|
1111
|
-
}
|
|
1112
|
-
// Generate appearance with text and optional dropdown arrow
|
|
1113
|
-
const contentStream = `/Tx BMC
|
|
1114
|
-
q
|
|
1115
|
-
BT
|
|
1116
|
-
${reconstructedDA}
|
|
1117
|
-
${textX} ${textY} Td
|
|
1118
|
-
(${escapedValue}) Tj
|
|
1119
|
-
ET
|
|
1120
|
-
${arrowGraphics}Q
|
|
1121
|
-
EMC
|
|
1122
|
-
`;
|
|
1123
|
-
const appearanceDict = new PdfDictionary();
|
|
1124
|
-
appearanceDict.set('Type', new PdfName('XObject'));
|
|
1125
|
-
appearanceDict.set('Subtype', new PdfName('Form'));
|
|
1126
|
-
appearanceDict.set('FormType', new PdfNumber(1));
|
|
1127
|
-
appearanceDict.set('BBox', new PdfArray([
|
|
1128
|
-
new PdfNumber(0),
|
|
1129
|
-
new PdfNumber(0),
|
|
1130
|
-
new PdfNumber(width),
|
|
1131
|
-
new PdfNumber(height),
|
|
1132
|
-
]));
|
|
1133
|
-
// Add font resources so Acrobat can resolve the font name.
|
|
1134
|
-
const fieldDR = this.content
|
|
1135
|
-
.get('DR')
|
|
1136
|
-
?.as(PdfDictionary);
|
|
1137
|
-
const acroformDR = this.form?.defaultResources;
|
|
1138
|
-
const fontSource = fieldDR?.get('Font')?.as(PdfDictionary) ??
|
|
1139
|
-
acroformDR?.get('Font')?.as(PdfDictionary);
|
|
1140
|
-
if (fontSource && fontName) {
|
|
1141
|
-
const fontRef = fontSource.get(fontName);
|
|
1142
|
-
if (fontRef) {
|
|
1143
|
-
const resourceFontDict = new PdfDictionary();
|
|
1144
|
-
resourceFontDict.set(fontName, fontRef);
|
|
1145
|
-
const resourcesDict = new PdfDictionary();
|
|
1146
|
-
resourcesDict.set('Font', resourceFontDict);
|
|
1147
|
-
appearanceDict.set('Resources', resourcesDict);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
const stream = new PdfStream({
|
|
1151
|
-
header: appearanceDict,
|
|
1152
|
-
original: contentStream,
|
|
1153
|
-
});
|
|
1154
|
-
this._appearanceStream = stream;
|
|
1155
|
-
if (options?.makeReadOnly) {
|
|
1156
|
-
this.readOnly = true;
|
|
1157
|
-
this.print = true;
|
|
1158
|
-
this.noZoom = true;
|
|
1159
|
-
}
|
|
1160
|
-
return true;
|
|
1161
|
-
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Gets the stored appearance stream if one has been generated.
|
|
1164
|
-
* For button fields, returns the appropriate stream based on the current state.
|
|
1165
|
-
* @internal
|
|
1166
|
-
*/
|
|
1167
|
-
getAppearanceStream() {
|
|
1168
|
-
// For button fields, return the appropriate stream based on state
|
|
1169
|
-
if (this.fieldType === 'Button') {
|
|
1170
|
-
if (this.checked && this._appearanceStreamYes) {
|
|
1171
|
-
return this._appearanceStreamYes;
|
|
1172
|
-
}
|
|
1173
|
-
return this._appearanceStream; // Return "Off" state
|
|
1174
|
-
}
|
|
1175
|
-
return this._appearanceStream;
|
|
1176
|
-
}
|
|
1177
|
-
/**
|
|
1178
|
-
* Gets all appearance streams for writing to PDF.
|
|
1179
|
-
* For button fields, returns both Off and Yes states.
|
|
1180
|
-
* For other fields, returns just the primary appearance.
|
|
1181
|
-
* @internal
|
|
1182
|
-
*/
|
|
1183
|
-
getAppearanceStreamsForWriting() {
|
|
1184
|
-
if (!this._appearanceStream)
|
|
1185
|
-
return undefined;
|
|
1186
|
-
return {
|
|
1187
|
-
primary: this._appearanceStream,
|
|
1188
|
-
secondary: this.fieldType === 'Button'
|
|
1189
|
-
? this._appearanceStreamYes
|
|
1190
|
-
: undefined,
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
/**
|
|
1194
|
-
* Sets the appearance dictionary reference for this field.
|
|
1195
|
-
* @internal - This is called automatically by PdfAcroForm.write()
|
|
1196
|
-
*/
|
|
1197
|
-
setAppearanceReference(appearanceStreamRef, appearanceStreamYesRef) {
|
|
1198
|
-
let apDict = this.appearanceStreamDict;
|
|
1199
|
-
if (!apDict) {
|
|
1200
|
-
apDict = new PdfDictionary();
|
|
1201
|
-
this.appearanceStreamDict = apDict;
|
|
1202
|
-
}
|
|
1203
|
-
// For button fields with multiple states, create a state dictionary
|
|
1204
|
-
if (appearanceStreamYesRef && this.fieldType === 'Button') {
|
|
1205
|
-
const stateDict = new PdfDictionary();
|
|
1206
|
-
stateDict.set('Off', appearanceStreamRef);
|
|
1207
|
-
stateDict.set('Yes', appearanceStreamYesRef);
|
|
1208
|
-
apDict.set('N', stateDict);
|
|
1209
|
-
}
|
|
1210
|
-
else {
|
|
1211
|
-
// For other fields, set the appearance stream directly
|
|
1212
|
-
apDict.set('N', appearanceStreamRef);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
export class PdfAcroForm extends PdfIndirectObject {
|
|
1217
|
-
fields;
|
|
1218
|
-
fontEncodingMaps = new Map();
|
|
1219
|
-
document;
|
|
1220
|
-
constructor(options) {
|
|
1221
|
-
super(options?.other ??
|
|
1222
|
-
new PdfIndirectObject({
|
|
1223
|
-
content: new PdfDictionary(),
|
|
1224
|
-
}));
|
|
1225
|
-
this.fields = options?.fields ?? [];
|
|
1226
|
-
this.document = options?.document;
|
|
1227
|
-
}
|
|
1228
|
-
/**
|
|
1229
|
-
* Convenience method to get a value from the form dictionary
|
|
1230
|
-
*/
|
|
1231
|
-
get(key) {
|
|
1232
|
-
return this.content.get(key);
|
|
1233
|
-
}
|
|
1234
|
-
/**
|
|
1235
|
-
* Convenience method to set a value in the form dictionary
|
|
1236
|
-
*/
|
|
1237
|
-
set(key, value) {
|
|
1238
|
-
this.content.set(key, value);
|
|
1239
|
-
}
|
|
1240
|
-
/**
|
|
1241
|
-
* Convenience method to delete a key from the form dictionary
|
|
1242
|
-
*/
|
|
1243
|
-
delete(key) {
|
|
1244
|
-
this.content.delete(key);
|
|
1245
|
-
}
|
|
1246
|
-
/**
|
|
1247
|
-
* Convenience method to check if form dictionary is modified
|
|
1248
|
-
*/
|
|
1249
|
-
isModified() {
|
|
1250
|
-
return this.content.isModified();
|
|
1251
|
-
}
|
|
1252
|
-
/**
|
|
1253
|
-
* Gets the NeedAppearances flag
|
|
1254
|
-
*/
|
|
1255
|
-
get needAppearances() {
|
|
1256
|
-
return (this.content.get('NeedAppearances')?.as(PdfBoolean)?.value ?? false);
|
|
1257
|
-
}
|
|
1258
|
-
/**
|
|
1259
|
-
* Sets the NeedAppearances flag to indicate that appearance streams need to be regenerated
|
|
1260
|
-
*/
|
|
1261
|
-
set needAppearances(value) {
|
|
1262
|
-
this.content.set('NeedAppearances', new PdfBoolean(value));
|
|
1263
|
-
}
|
|
1264
|
-
/**
|
|
1265
|
-
* Gets the signature flags
|
|
1266
|
-
*/
|
|
1267
|
-
get signatureFlags() {
|
|
1268
|
-
return this.content.get('SigFlags')?.as(PdfNumber)?.value ?? 0;
|
|
1269
|
-
}
|
|
1270
|
-
/**
|
|
1271
|
-
* Sets the signature flags
|
|
1272
|
-
*/
|
|
1273
|
-
set signatureFlags(flags) {
|
|
1274
|
-
this.content.set('SigFlags', new PdfNumber(flags));
|
|
1275
|
-
}
|
|
1276
|
-
/**
|
|
1277
|
-
* Gets the default appearance string for the form
|
|
1278
|
-
*/
|
|
1279
|
-
get defaultAppearance() {
|
|
1280
|
-
return this.content.get('DA')?.as(PdfString)?.value ?? null;
|
|
1281
|
-
}
|
|
1282
|
-
/**
|
|
1283
|
-
* Sets the default appearance string for the form
|
|
1284
|
-
*/
|
|
1285
|
-
set defaultAppearance(da) {
|
|
1286
|
-
this.content.set('DA', new PdfString(da));
|
|
1287
|
-
}
|
|
1288
|
-
/**
|
|
1289
|
-
* Gets the default quadding (alignment) for the form
|
|
1290
|
-
* 0 = left, 1 = center, 2 = right
|
|
1291
|
-
*/
|
|
1292
|
-
get defaultQuadding() {
|
|
1293
|
-
return this.content.get('Q')?.as(PdfNumber)?.value ?? 0;
|
|
1294
|
-
}
|
|
1295
|
-
/**
|
|
1296
|
-
* Sets the default quadding (alignment) for the form
|
|
1297
|
-
*/
|
|
1298
|
-
set defaultQuadding(q) {
|
|
1299
|
-
this.content.set('Q', new PdfNumber(q));
|
|
1300
|
-
}
|
|
1301
|
-
/**
|
|
1302
|
-
* Gets the default resources dictionary for the form
|
|
1303
|
-
*/
|
|
1304
|
-
get defaultResources() {
|
|
1305
|
-
return this.content.get('DR')?.as(PdfDictionary) ?? null;
|
|
1306
|
-
}
|
|
1307
|
-
/**
|
|
1308
|
-
* Sets the default resources dictionary for the form
|
|
1309
|
-
*/
|
|
1310
|
-
set defaultResources(resources) {
|
|
1311
|
-
if (resources === null) {
|
|
1312
|
-
this.content.delete('DR');
|
|
1313
|
-
}
|
|
1314
|
-
else {
|
|
1315
|
-
this.content.set('DR', resources);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
/**
|
|
1319
|
-
* Sets multiple field values by field name.
|
|
1320
|
-
* @param values Object with field names as keys and values to set
|
|
1321
|
-
* */
|
|
1322
|
-
setValues(values) {
|
|
1323
|
-
for (const field of this.fields) {
|
|
1324
|
-
const name = field.name;
|
|
1325
|
-
if (name in values && values[name] !== undefined) {
|
|
1326
|
-
field.value = values[name];
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
importData(fields) {
|
|
1331
|
-
for (const field of this.fields) {
|
|
1332
|
-
const name = field.name;
|
|
1333
|
-
if (name && name in fields) {
|
|
1334
|
-
field.value = fields[name];
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
exportData() {
|
|
1339
|
-
const result = {};
|
|
1340
|
-
for (const field of this.fields) {
|
|
1341
|
-
const name = field.name;
|
|
1342
|
-
if (name) {
|
|
1343
|
-
result[name] = field.value;
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
return result;
|
|
1347
|
-
}
|
|
1348
|
-
/**
|
|
1349
|
-
* Gets the encoding map for a specific font in the form's resources.
|
|
1350
|
-
* Returns null if no custom encoding is found.
|
|
1351
|
-
* Results are cached for performance.
|
|
1352
|
-
*/
|
|
1353
|
-
async getFontEncodingMap(fontName) {
|
|
1354
|
-
// Check cache first
|
|
1355
|
-
if (this.fontEncodingMaps.has(fontName)) {
|
|
1356
|
-
return this.fontEncodingMaps.get(fontName);
|
|
1357
|
-
}
|
|
1358
|
-
// Get the font from default resources
|
|
1359
|
-
const dr = this.defaultResources;
|
|
1360
|
-
if (!dr) {
|
|
1361
|
-
return null;
|
|
1362
|
-
}
|
|
1363
|
-
const fonts = dr.get('Font')?.as(PdfDictionary);
|
|
1364
|
-
if (!fonts) {
|
|
1365
|
-
return null;
|
|
1366
|
-
}
|
|
1367
|
-
const fontRef = fonts.get(fontName)?.as(PdfObjectReference);
|
|
1368
|
-
if (!fontRef || !this.document) {
|
|
1369
|
-
return null;
|
|
1370
|
-
}
|
|
1371
|
-
// Read the font object
|
|
1372
|
-
const fontObj = await this.document.readObject({
|
|
1373
|
-
objectNumber: fontRef.objectNumber,
|
|
1374
|
-
generationNumber: fontRef.generationNumber,
|
|
1375
|
-
});
|
|
1376
|
-
if (!fontObj) {
|
|
1377
|
-
return null;
|
|
1378
|
-
}
|
|
1379
|
-
const fontDict = fontObj.content.as(PdfDictionary);
|
|
1380
|
-
const encoding = fontDict.get('Encoding');
|
|
1381
|
-
// Handle encoding reference
|
|
1382
|
-
let encodingDict = null;
|
|
1383
|
-
if (encoding instanceof PdfObjectReference) {
|
|
1384
|
-
const encodingObj = await this.document.readObject({
|
|
1385
|
-
objectNumber: encoding.objectNumber,
|
|
1386
|
-
generationNumber: encoding.generationNumber,
|
|
1387
|
-
});
|
|
1388
|
-
encodingDict = encodingObj?.content.as(PdfDictionary) ?? null;
|
|
1389
|
-
}
|
|
1390
|
-
else if (encoding instanceof PdfDictionary) {
|
|
1391
|
-
encodingDict = encoding;
|
|
1392
|
-
}
|
|
1393
|
-
if (!encodingDict) {
|
|
1394
|
-
return null;
|
|
1395
|
-
}
|
|
1396
|
-
// Parse the Differences array
|
|
1397
|
-
const differences = encodingDict.get('Differences')?.as(PdfArray);
|
|
1398
|
-
if (!differences) {
|
|
1399
|
-
return null;
|
|
1400
|
-
}
|
|
1401
|
-
const encodingMap = buildEncodingMap(differences);
|
|
1402
|
-
if (!encodingMap) {
|
|
1403
|
-
return null;
|
|
1404
|
-
}
|
|
1405
|
-
this.fontEncodingMaps.set(fontName, encodingMap);
|
|
1406
|
-
return encodingMap;
|
|
1407
|
-
}
|
|
1408
|
-
static async fromDocument(document) {
|
|
1409
|
-
const catalog = document.root;
|
|
1410
|
-
if (!catalog)
|
|
1411
|
-
return null;
|
|
1412
|
-
const acroFormRef = catalog.content.get('AcroForm');
|
|
1413
|
-
if (!acroFormRef)
|
|
1414
|
-
return null;
|
|
1415
|
-
let acroFormDict;
|
|
1416
|
-
let acroFormContainer;
|
|
1417
|
-
if (acroFormRef instanceof PdfObjectReference) {
|
|
1418
|
-
const acroFormObject = await document.readObject({
|
|
1419
|
-
objectNumber: acroFormRef.objectNumber,
|
|
1420
|
-
generationNumber: acroFormRef.generationNumber,
|
|
1421
|
-
});
|
|
1422
|
-
if (!acroFormObject)
|
|
1423
|
-
return null;
|
|
1424
|
-
if (!(acroFormObject.content instanceof PdfDictionary))
|
|
1425
|
-
throw new Error('AcroForm content must be a dictionary');
|
|
1426
|
-
acroFormDict = acroFormObject.content;
|
|
1427
|
-
acroFormContainer = acroFormObject;
|
|
1428
|
-
}
|
|
1429
|
-
else if (acroFormRef instanceof PdfDictionary) {
|
|
1430
|
-
acroFormDict = acroFormRef;
|
|
1431
|
-
acroFormContainer = new PdfIndirectObject({ content: acroFormDict });
|
|
1432
|
-
}
|
|
1433
|
-
else {
|
|
1434
|
-
return null;
|
|
1435
|
-
}
|
|
1436
|
-
const acroForm = new PdfAcroForm({ other: acroFormContainer, document });
|
|
1437
|
-
// Pre-cache font encoding maps for all fonts used in fields
|
|
1438
|
-
await acroForm.cacheAllFontEncodings();
|
|
1439
|
-
const fields = new Map();
|
|
1440
|
-
const getFields = async (fieldRefs, parent) => {
|
|
1441
|
-
for (const fieldRef of fieldRefs) {
|
|
1442
|
-
const refKey = fieldRef.toString().trim();
|
|
1443
|
-
if (fields.has(refKey)) {
|
|
1444
|
-
fields.get(refKey).parent = parent;
|
|
1445
|
-
continue;
|
|
1446
|
-
}
|
|
1447
|
-
const fieldObject = await document.readObject({
|
|
1448
|
-
objectNumber: fieldRef.objectNumber,
|
|
1449
|
-
generationNumber: fieldRef.generationNumber,
|
|
1450
|
-
});
|
|
1451
|
-
if (!fieldObject)
|
|
1452
|
-
continue;
|
|
1453
|
-
if (!(fieldObject.content instanceof PdfDictionary))
|
|
1454
|
-
continue;
|
|
1455
|
-
const field = new PdfAcroFormField({
|
|
1456
|
-
other: fieldObject,
|
|
1457
|
-
form: acroForm,
|
|
1458
|
-
});
|
|
1459
|
-
field.parent = parent;
|
|
1460
|
-
// Process child fields (Kids) before adding the parent
|
|
1461
|
-
const kids = field.kids;
|
|
1462
|
-
if (kids.length > 0) {
|
|
1463
|
-
await getFields(kids, field);
|
|
1464
|
-
}
|
|
1465
|
-
acroForm.fields.push(field);
|
|
1466
|
-
fields.set(refKey, field);
|
|
1467
|
-
}
|
|
1468
|
-
};
|
|
1469
|
-
const fieldsArray = new PdfArray();
|
|
1470
|
-
if (acroForm.content.get('Fields') instanceof PdfArray) {
|
|
1471
|
-
fieldsArray.items.push(...acroForm.content
|
|
1472
|
-
.get('Fields')
|
|
1473
|
-
.as((PdfArray)).items);
|
|
1474
|
-
}
|
|
1475
|
-
else if (acroForm.content.get('Fields') instanceof PdfObjectReference) {
|
|
1476
|
-
const fieldsObj = await document.readObject({
|
|
1477
|
-
objectNumber: acroForm.content
|
|
1478
|
-
.get('Fields')
|
|
1479
|
-
.as(PdfObjectReference).objectNumber,
|
|
1480
|
-
generationNumber: acroForm.content
|
|
1481
|
-
.get('Fields')
|
|
1482
|
-
.as(PdfObjectReference).generationNumber,
|
|
1483
|
-
});
|
|
1484
|
-
if (fieldsObj && fieldsObj.content instanceof PdfArray) {
|
|
1485
|
-
fieldsArray.items.push(...fieldsObj.content.as((PdfArray)).items);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
await getFields(fieldsArray.items);
|
|
1489
|
-
return acroForm;
|
|
1490
|
-
}
|
|
1491
|
-
/**
|
|
1492
|
-
* Pre-caches encoding maps for all fonts used in the form fields.
|
|
1493
|
-
* This makes subsequent field value access faster and synchronous.
|
|
1494
|
-
*/
|
|
1495
|
-
async cacheAllFontEncodings() {
|
|
1496
|
-
const fontNames = new Set();
|
|
1497
|
-
// Collect all font names from field DA strings
|
|
1498
|
-
for (const field of this.fields) {
|
|
1499
|
-
const da = field.content.get('DA')?.as(PdfString)?.value;
|
|
1500
|
-
if (da) {
|
|
1501
|
-
const fontMatch = da.match(/\/(\w+)\s+[\d.]+\s+Tf/);
|
|
1502
|
-
if (fontMatch) {
|
|
1503
|
-
fontNames.add(fontMatch[1]);
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
// Pre-cache encoding for each font
|
|
1508
|
-
for (const fontName of fontNames) {
|
|
1509
|
-
await this.getFontEncodingMap(fontName);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* Gets or creates the Annots array for a page.
|
|
1514
|
-
* Returns the array and metadata about whether it's an indirect object.
|
|
1515
|
-
*/
|
|
1516
|
-
async getPageAnnotsArray(document, pageDict) {
|
|
1517
|
-
const annotsRef = pageDict.get('Annots');
|
|
1518
|
-
if (annotsRef instanceof PdfObjectReference) {
|
|
1519
|
-
const annotsObj = await document.readObject({
|
|
1520
|
-
objectNumber: annotsRef.objectNumber,
|
|
1521
|
-
generationNumber: annotsRef.generationNumber,
|
|
1522
|
-
});
|
|
1523
|
-
return {
|
|
1524
|
-
annotsArray: annotsObj.content
|
|
1525
|
-
.as((PdfArray))
|
|
1526
|
-
.clone(),
|
|
1527
|
-
isIndirect: true,
|
|
1528
|
-
objectNumber: annotsRef.objectNumber,
|
|
1529
|
-
generationNumber: annotsRef.generationNumber,
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
else if (annotsRef instanceof PdfArray) {
|
|
1533
|
-
return {
|
|
1534
|
-
annotsArray: annotsRef.as((PdfArray)).clone(),
|
|
1535
|
-
isIndirect: false,
|
|
1536
|
-
};
|
|
1537
|
-
}
|
|
1538
|
-
else {
|
|
1539
|
-
const newArray = new PdfArray();
|
|
1540
|
-
pageDict.set('Annots', newArray);
|
|
1541
|
-
return {
|
|
1542
|
-
annotsArray: newArray,
|
|
1543
|
-
isIndirect: false,
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
/**
|
|
1548
|
-
* Adds field references to a page's Annots array, avoiding duplicates.
|
|
1549
|
-
*/
|
|
1550
|
-
addFieldsToAnnots(annotsArray, fieldRefs) {
|
|
1551
|
-
for (const fieldRef of fieldRefs) {
|
|
1552
|
-
const exists = annotsArray.items.some((ref) => ref.equals(fieldRef));
|
|
1553
|
-
if (!exists) {
|
|
1554
|
-
annotsArray.push(fieldRef);
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
/**
|
|
1559
|
-
* Updates page annotations to include new form field references.
|
|
1560
|
-
*/
|
|
1561
|
-
async updatePageAnnotations(document, fieldsByPage) {
|
|
1562
|
-
for (const { pageRef, fieldRefs } of fieldsByPage.values()) {
|
|
1563
|
-
const pageObj = await document.readObject({
|
|
1564
|
-
objectNumber: pageRef.objectNumber,
|
|
1565
|
-
generationNumber: pageRef.generationNumber,
|
|
1566
|
-
});
|
|
1567
|
-
if (!pageObj)
|
|
1568
|
-
continue;
|
|
1569
|
-
const pageDict = pageObj.content.as(PdfDictionary).clone();
|
|
1570
|
-
const annotsInfo = await this.getPageAnnotsArray(document, pageDict);
|
|
1571
|
-
this.addFieldsToAnnots(annotsInfo.annotsArray, fieldRefs);
|
|
1572
|
-
// Write the Annots array if it's an indirect object
|
|
1573
|
-
if (annotsInfo.isIndirect &&
|
|
1574
|
-
annotsInfo.objectNumber !== undefined) {
|
|
1575
|
-
const annotsIndirect = new PdfIndirectObject({
|
|
1576
|
-
objectNumber: annotsInfo.objectNumber,
|
|
1577
|
-
generationNumber: annotsInfo.generationNumber,
|
|
1578
|
-
content: annotsInfo.annotsArray,
|
|
1579
|
-
});
|
|
1580
|
-
document.add(annotsIndirect);
|
|
1581
|
-
}
|
|
1582
|
-
// Write the modified page
|
|
1583
|
-
const pageIndirect = new PdfIndirectObject({
|
|
1584
|
-
objectNumber: pageRef.objectNumber,
|
|
1585
|
-
generationNumber: pageRef.generationNumber,
|
|
1586
|
-
content: pageDict,
|
|
1587
|
-
});
|
|
1588
|
-
document.add(pageIndirect);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
async write(document) {
|
|
1592
|
-
const catalog = document.root;
|
|
1593
|
-
const isIncremental = document.isIncremental();
|
|
1594
|
-
document.setIncremental(true);
|
|
1595
|
-
const fieldsArray = new PdfArray();
|
|
1596
|
-
this.content.set('Fields', fieldsArray);
|
|
1597
|
-
// Track fields that need to be added to page annotations
|
|
1598
|
-
const fieldsByPage = new Map();
|
|
1599
|
-
for (const field of this.fields) {
|
|
1600
|
-
let fieldReference;
|
|
1601
|
-
if (field.isModified()) {
|
|
1602
|
-
// If the field has generated appearance streams, create them as indirect objects
|
|
1603
|
-
const appearances = field.getAppearanceStreamsForWriting();
|
|
1604
|
-
if (appearances) {
|
|
1605
|
-
// Create the primary appearance stream
|
|
1606
|
-
const primaryAppearanceObj = new PdfIndirectObject({
|
|
1607
|
-
content: appearances.primary,
|
|
1608
|
-
});
|
|
1609
|
-
document.add(primaryAppearanceObj);
|
|
1610
|
-
// Create the secondary appearance stream if present (for button fields)
|
|
1611
|
-
let secondaryAppearanceRef;
|
|
1612
|
-
if (appearances.secondary) {
|
|
1613
|
-
const secondaryAppearanceObj = new PdfIndirectObject({
|
|
1614
|
-
content: appearances.secondary,
|
|
1615
|
-
});
|
|
1616
|
-
document.add(secondaryAppearanceObj);
|
|
1617
|
-
secondaryAppearanceRef =
|
|
1618
|
-
secondaryAppearanceObj.reference;
|
|
1619
|
-
}
|
|
1620
|
-
// Set the appearance references on the field
|
|
1621
|
-
field.setAppearanceReference(primaryAppearanceObj.reference, secondaryAppearanceRef);
|
|
1622
|
-
// Ensure field has the Print flag set
|
|
1623
|
-
// This ensures the appearance is used for display and printing
|
|
1624
|
-
if (!field.print) {
|
|
1625
|
-
field.print = true;
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
document.add(field);
|
|
1629
|
-
// Create a proper PdfObjectReference (not the proxy from .reference)
|
|
1630
|
-
fieldReference = new PdfObjectReference(field.objectNumber, field.generationNumber);
|
|
1631
|
-
// Track if this field needs to be added to a page's Annots
|
|
1632
|
-
const parentRef = field.parentRef;
|
|
1633
|
-
const isWidget = field.isWidget;
|
|
1634
|
-
if (parentRef && isWidget) {
|
|
1635
|
-
const pageKey = `${parentRef.objectNumber}_${parentRef.generationNumber}`;
|
|
1636
|
-
if (!fieldsByPage.has(pageKey)) {
|
|
1637
|
-
fieldsByPage.set(pageKey, {
|
|
1638
|
-
pageRef: parentRef,
|
|
1639
|
-
fieldRefs: [],
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
fieldsByPage.get(pageKey).fieldRefs.push(fieldReference);
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
else {
|
|
1646
|
-
fieldReference = field.reference;
|
|
1647
|
-
}
|
|
1648
|
-
fieldsArray.push(fieldReference);
|
|
1649
|
-
}
|
|
1650
|
-
// Add field references to page annotations
|
|
1651
|
-
await this.updatePageAnnotations(document, fieldsByPage);
|
|
1652
|
-
if (this.isModified()) {
|
|
1653
|
-
document.add(this);
|
|
1654
|
-
if (!catalog.content.has('AcroForm')) {
|
|
1655
|
-
let updatableCatalog = catalog;
|
|
1656
|
-
if (catalog.isImmutable()) {
|
|
1657
|
-
updatableCatalog = catalog.clone();
|
|
1658
|
-
document.add(updatableCatalog);
|
|
1659
|
-
}
|
|
1660
|
-
updatableCatalog.content.set('AcroForm', this.reference);
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
await document.commit();
|
|
1664
|
-
document.setIncremental(isIncremental);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
5
|
+
export { PdfFormField as PdfAcroFormField } from './fields/pdf-form-field.js';
|
|
6
|
+
export { PdfAcroForm } from './pdf-acro-form.js';
|
|
7
|
+
export { PdfFieldType } from './fields/types.js';
|