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.
Files changed (124) hide show
  1. package/dist/acroform/acroform.d.ts +7 -454
  2. package/dist/acroform/acroform.js +5 -1664
  3. package/dist/acroform/appearance/index.d.ts +4 -0
  4. package/dist/acroform/appearance/index.js +4 -0
  5. package/dist/acroform/appearance/pdf-appearance-stream.d.ts +21 -0
  6. package/dist/acroform/appearance/pdf-appearance-stream.js +41 -0
  7. package/dist/acroform/appearance/pdf-button-appearance-stream.d.ts +13 -0
  8. package/dist/acroform/appearance/pdf-button-appearance-stream.js +54 -0
  9. package/dist/acroform/appearance/pdf-choice-appearance-stream.d.ts +22 -0
  10. package/dist/acroform/appearance/pdf-choice-appearance-stream.js +75 -0
  11. package/dist/acroform/appearance/pdf-graphics.d.ts +51 -0
  12. package/dist/acroform/appearance/pdf-graphics.js +239 -0
  13. package/dist/acroform/appearance/pdf-text-appearance-stream.d.ts +22 -0
  14. package/dist/acroform/appearance/pdf-text-appearance-stream.js +104 -0
  15. package/dist/acroform/fields/index.d.ts +8 -0
  16. package/dist/acroform/fields/index.js +8 -0
  17. package/dist/acroform/fields/pdf-button-form-field.d.ts +23 -0
  18. package/dist/acroform/fields/pdf-button-form-field.js +102 -0
  19. package/dist/acroform/fields/pdf-choice-form-field.d.ts +18 -0
  20. package/dist/acroform/fields/pdf-choice-form-field.js +131 -0
  21. package/dist/acroform/fields/pdf-default-appearance.d.ts +23 -0
  22. package/dist/acroform/fields/pdf-default-appearance.js +68 -0
  23. package/dist/acroform/fields/pdf-form-field-flags.d.ts +45 -0
  24. package/dist/acroform/fields/pdf-form-field-flags.js +122 -0
  25. package/dist/acroform/fields/pdf-form-field.d.ts +123 -0
  26. package/dist/acroform/fields/pdf-form-field.js +433 -0
  27. package/dist/acroform/fields/pdf-signature-form-field.d.ts +7 -0
  28. package/dist/acroform/fields/pdf-signature-form-field.js +12 -0
  29. package/dist/acroform/fields/pdf-text-form-field.d.ts +10 -0
  30. package/dist/acroform/fields/pdf-text-form-field.js +77 -0
  31. package/dist/acroform/fields/types.d.ts +26 -0
  32. package/dist/acroform/fields/types.js +9 -0
  33. package/dist/acroform/index.d.ts +5 -1
  34. package/dist/acroform/index.js +5 -1
  35. package/dist/acroform/manager.d.ts +12 -1
  36. package/dist/acroform/manager.js +20 -2
  37. package/dist/acroform/pdf-acro-form.d.ts +69 -0
  38. package/dist/acroform/pdf-acro-form.js +293 -0
  39. package/dist/acroform/pdf-font-encoding-cache.d.ts +27 -0
  40. package/dist/acroform/pdf-font-encoding-cache.js +188 -0
  41. package/dist/acroform/xfa/index.d.ts +3 -0
  42. package/dist/acroform/xfa/index.js +2 -0
  43. package/dist/acroform/xfa/pdf-xfa-data.d.ts +20 -0
  44. package/dist/acroform/xfa/pdf-xfa-data.js +68 -0
  45. package/dist/acroform/xfa/pdf-xfa-form.d.ts +11 -0
  46. package/dist/acroform/xfa/pdf-xfa-form.js +56 -0
  47. package/dist/annotations/index.d.ts +4 -0
  48. package/dist/annotations/index.js +4 -0
  49. package/dist/annotations/pdf-annotation-flags.d.ts +24 -0
  50. package/dist/annotations/pdf-annotation-flags.js +93 -0
  51. package/dist/annotations/pdf-annotation-writer.d.ts +20 -0
  52. package/dist/annotations/pdf-annotation-writer.js +76 -0
  53. package/dist/annotations/pdf-annotation.d.ts +61 -0
  54. package/dist/annotations/pdf-annotation.js +106 -0
  55. package/dist/annotations/pdf-widget-annotation.d.ts +15 -0
  56. package/dist/annotations/pdf-widget-annotation.js +37 -0
  57. package/dist/core/objects/pdf-array.d.ts +1 -1
  58. package/dist/core/objects/pdf-array.js +3 -2
  59. package/dist/core/objects/pdf-boolean.d.ts +1 -1
  60. package/dist/core/objects/pdf-boolean.js +3 -2
  61. package/dist/core/objects/pdf-comment.d.ts +1 -1
  62. package/dist/core/objects/pdf-comment.js +1 -1
  63. package/dist/core/objects/pdf-dictionary.d.ts +1 -1
  64. package/dist/core/objects/pdf-dictionary.js +3 -2
  65. package/dist/core/objects/pdf-hexadecimal.d.ts +1 -1
  66. package/dist/core/objects/pdf-hexadecimal.js +3 -2
  67. package/dist/core/objects/pdf-indirect-object.d.ts +1 -1
  68. package/dist/core/objects/pdf-indirect-object.js +1 -1
  69. package/dist/core/objects/pdf-name.d.ts +1 -1
  70. package/dist/core/objects/pdf-name.js +3 -2
  71. package/dist/core/objects/pdf-null.d.ts +1 -1
  72. package/dist/core/objects/pdf-null.js +3 -2
  73. package/dist/core/objects/pdf-number.d.ts +1 -1
  74. package/dist/core/objects/pdf-number.js +3 -2
  75. package/dist/core/objects/pdf-object-reference.d.ts +1 -1
  76. package/dist/core/objects/pdf-object-reference.js +3 -2
  77. package/dist/core/objects/pdf-object.d.ts +3 -1
  78. package/dist/core/objects/pdf-object.js +6 -0
  79. package/dist/core/objects/pdf-start-xref.d.ts +1 -1
  80. package/dist/core/objects/pdf-start-xref.js +3 -2
  81. package/dist/core/objects/pdf-stream.d.ts +4 -3
  82. package/dist/core/objects/pdf-stream.js +45 -16
  83. package/dist/core/objects/pdf-string.d.ts +2 -1
  84. package/dist/core/objects/pdf-string.js +17 -2
  85. package/dist/core/objects/pdf-trailer.d.ts +1 -1
  86. package/dist/core/objects/pdf-trailer.js +3 -2
  87. package/dist/core/objects/pdf-xref-table.d.ts +3 -3
  88. package/dist/core/objects/pdf-xref-table.js +3 -3
  89. package/dist/core/parser/incremental-parser.d.ts +0 -13
  90. package/dist/core/parser/incremental-parser.js +1 -18
  91. package/dist/core/streams/object-stream.d.ts +1 -1
  92. package/dist/core/streams/object-stream.js +1 -1
  93. package/dist/errors.d.ts +22 -0
  94. package/dist/errors.js +24 -0
  95. package/dist/fonts/index.d.ts +1 -1
  96. package/dist/fonts/index.js +1 -1
  97. package/dist/fonts/pdf-font.d.ts +64 -7
  98. package/dist/fonts/pdf-font.js +188 -8
  99. package/dist/index.d.ts +2 -0
  100. package/dist/index.js +2 -0
  101. package/dist/pdf/index.d.ts +0 -1
  102. package/dist/pdf/index.js +0 -1
  103. package/dist/pdf/pdf-document.d.ts +16 -12
  104. package/dist/pdf/pdf-document.js +51 -37
  105. package/dist/pdf/pdf-revision.d.ts +1 -1
  106. package/dist/pdf/pdf-revision.js +1 -1
  107. package/dist/pdf/pdf-xref-lookup.d.ts +8 -0
  108. package/dist/pdf/pdf-xref-lookup.js +12 -0
  109. package/dist/security/handlers/base.js +3 -0
  110. package/dist/utils/encodePdfText.d.ts +17 -0
  111. package/dist/utils/encodePdfText.js +61 -0
  112. package/dist/utils/index.d.ts +1 -1
  113. package/dist/utils/index.js +1 -1
  114. package/package.json +1 -1
  115. package/dist/pdf/errors.d.ts +0 -6
  116. package/dist/pdf/errors.js +0 -6
  117. package/dist/xfa/index.d.ts +0 -1
  118. package/dist/xfa/index.js +0 -1
  119. package/dist/xfa/manager.d.ts +0 -44
  120. package/dist/xfa/manager.js +0 -136
  121. /package/dist/fonts/{font-manager.d.ts → manager.d.ts} +0 -0
  122. /package/dist/fonts/{font-manager.js → manager.js} +0 -0
  123. /package/dist/utils/{IterableReadableStream.d.ts → iterable-readable-stream.d.ts} +0 -0
  124. /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
- * Field types for AcroForm fields
2
+ * Backward-compatible re-export shim.
3
+ * All classes have been moved to dedicated modules.
13
4
  */
14
- export const PdfFieldType = {
15
- Text: 'Tx',
16
- Button: 'Btn',
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';