pdf-lite 1.3.1 → 1.3.3

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/README.md CHANGED
@@ -13,10 +13,10 @@ PRs and issues are welcome!
13
13
 
14
14
  ## Features
15
15
 
16
- - **Zero dependencies**: No external libraries are required, making it lightweight and easy to integrate.
17
16
  - **Type-safe**: Built with TypeScript, ensuring type safety and reducing runtime errors.
18
17
  - **Browser and Node.js support**: Works seamlessly in both environments, allowing for versatile usage.
19
18
  - **Low-level API**: Provides a low-level API for advanced users who want to manipulate PDF files directly, as well as a higher-level API for easier usage.
19
+ - **Minimal dependencies**: A small number external libraries are required, making it lightweight and easy to integrate.
20
20
 
21
21
  ## Installation
22
22
 
@@ -62,7 +62,7 @@ export declare class PdfAcroFormField extends PdfIndirectObject<PdfDictionary<{
62
62
  MaxLen?: PdfNumber;
63
63
  Opt?: PdfArray<PdfString>;
64
64
  }>> {
65
- parent?: PdfAcroFormField;
65
+ private _parent?;
66
66
  defaultGenerateAppearance: boolean;
67
67
  private _appearanceStream?;
68
68
  private _appearanceStreamYes?;
@@ -71,6 +71,11 @@ export declare class PdfAcroFormField extends PdfIndirectObject<PdfDictionary<{
71
71
  other?: PdfIndirectObject;
72
72
  form?: PdfAcroForm;
73
73
  });
74
+ get parent(): PdfAcroFormField | undefined;
75
+ set parent(field: PdfAcroFormField | undefined);
76
+ get children(): PdfAcroFormField[];
77
+ set children(fields: PdfAcroFormField[]);
78
+ get siblings(): PdfAcroFormField[];
74
79
  get encodingMap(): Map<number, string> | undefined;
75
80
  /**
76
81
  * Convenience method to check if field dictionary is modified
@@ -18,7 +18,7 @@ export const PdfFieldType = {
18
18
  Signature: 'Sig',
19
19
  };
20
20
  export class PdfAcroFormField extends PdfIndirectObject {
21
- parent;
21
+ _parent;
22
22
  defaultGenerateAppearance = true;
23
23
  _appearanceStream;
24
24
  _appearanceStreamYes; // For button fields: checked state
@@ -28,6 +28,50 @@ export class PdfAcroFormField extends PdfIndirectObject {
28
28
  new PdfIndirectObject({ content: new PdfDictionary() }));
29
29
  this.form = options?.form;
30
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
+ }
31
75
  get encodingMap() {
32
76
  const fontName = this.fontName;
33
77
  if (!fontName)
@@ -44,7 +88,8 @@ export class PdfAcroFormField extends PdfIndirectObject {
44
88
  * Gets the field type
45
89
  */
46
90
  get fieldType() {
47
- const ft = this.content.get('FT')?.value;
91
+ const ft = this.content.get('FT')?.value ??
92
+ this.parent?.content.get('FT')?.value;
48
93
  switch (ft) {
49
94
  case 'Tx':
50
95
  return 'Text';
@@ -128,7 +173,7 @@ export class PdfAcroFormField extends PdfIndirectObject {
128
173
  * Gets the default value
129
174
  */
130
175
  get defaultValue() {
131
- const dv = this.content.get('DV');
176
+ const dv = this.content.get('DV') ?? this.parent?.content.get('DV');
132
177
  if (dv instanceof PdfString) {
133
178
  return dv.value;
134
179
  }
@@ -150,7 +195,8 @@ export class PdfAcroFormField extends PdfIndirectObject {
150
195
  }
151
196
  }
152
197
  get value() {
153
- const v = this.content.get('V');
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');
154
200
  if (v instanceof PdfString) {
155
201
  // UTF-16BE strings should always use UTF-16BE decoding regardless of font encoding
156
202
  if (v.isUTF16BE) {
@@ -170,45 +216,56 @@ export class PdfAcroFormField extends PdfIndirectObject {
170
216
  if (this.value === val) {
171
217
  return;
172
218
  }
219
+ // In a parent/kids split, V should be set on the parent field
220
+ const target = this.parent ?? this;
173
221
  const fieldType = this.fieldType;
174
222
  if (fieldType === 'Button') {
175
223
  val = val instanceof PdfString ? val.value : val;
176
224
  if (val.trim() === '') {
177
- this.content.delete('V');
225
+ target.content.delete('V');
178
226
  this.content.delete('AS');
179
227
  return;
180
228
  }
181
- this.content.set('V', new PdfName(val));
229
+ target.content.set('V', new PdfName(val));
182
230
  this.content.set('AS', new PdfName(val));
183
231
  }
184
232
  else {
185
- this.content.set('V', val instanceof PdfString ? val : new PdfString(val));
233
+ target.content.set('V', val instanceof PdfString ? val : new PdfString(val));
186
234
  }
187
235
  if (this.defaultGenerateAppearance) {
188
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
+ }
189
245
  }
190
246
  }
191
247
  get checked() {
192
248
  if (this.fieldType === 'Button') {
193
- const v = this.content.get('V');
249
+ const v = this.content.get('V') ?? this.parent?.content.get('V');
194
250
  return v instanceof PdfName && v.value === 'Yes';
195
251
  }
196
252
  return false;
197
253
  }
198
254
  set checked(isChecked) {
199
255
  if (this.fieldType === 'Button') {
256
+ const target = this.parent ?? this;
200
257
  if (isChecked) {
201
- this.content.set('V', new PdfName('Yes'));
258
+ target.content.set('V', new PdfName('Yes'));
202
259
  this.content.set('AS', new PdfName('Yes'));
203
260
  }
204
261
  else {
205
- this.content.set('V', new PdfName('Off'));
262
+ target.content.set('V', new PdfName('Off'));
206
263
  this.content.set('AS', new PdfName('Off'));
207
264
  }
208
265
  }
209
266
  }
210
267
  get fontSize() {
211
- const da = this.content.get('DA')?.as(PdfString)?.value || '';
268
+ const da = this.defaultAppearance || '';
212
269
  const match = da.match(/\/[A-Za-z0-9_-]+\s+([\d.]+)\s+Tf/);
213
270
  if (match) {
214
271
  return parseFloat(match[1]);
@@ -216,7 +273,7 @@ export class PdfAcroFormField extends PdfIndirectObject {
216
273
  return null;
217
274
  }
218
275
  set fontSize(size) {
219
- const da = this.content.get('DA')?.as(PdfString)?.value || '';
276
+ const da = this.defaultAppearance || '';
220
277
  if (!da) {
221
278
  this.content.set('DA', new PdfString(`/F1 ${size} Tf 0 g`));
222
279
  return;
@@ -225,7 +282,7 @@ export class PdfAcroFormField extends PdfIndirectObject {
225
282
  this.content.set('DA', new PdfString(updatedDa));
226
283
  }
227
284
  get fontName() {
228
- const da = this.content.get('DA')?.as(PdfString)?.value || '';
285
+ const da = this.defaultAppearance || '';
229
286
  const match = da.match(/\/([A-Za-z0-9_-]+)\s+[\d.]+\s+Tf/);
230
287
  if (match) {
231
288
  return match[1];
@@ -233,7 +290,7 @@ export class PdfAcroFormField extends PdfIndirectObject {
233
290
  return null;
234
291
  }
235
292
  set fontName(fontName) {
236
- const da = this.content.get('DA')?.as(PdfString)?.value || '';
293
+ const da = this.defaultAppearance || '';
237
294
  if (!da) {
238
295
  this.content.set('DA', new PdfString(`/${fontName} 12 Tf 0 g`));
239
296
  return;
@@ -253,7 +310,7 @@ export class PdfAcroFormField extends PdfIndirectObject {
253
310
  }
254
311
  const resourceName = font.resourceName;
255
312
  const currentSize = this.fontSize ?? 12;
256
- const da = this.content.get('DA')?.as(PdfString)?.value || '';
313
+ const da = this.defaultAppearance || '';
257
314
  if (!da) {
258
315
  this.content.set('DA', new PdfString(`/${resourceName} ${currentSize} Tf 0 g`));
259
316
  return;
@@ -265,7 +322,9 @@ export class PdfAcroFormField extends PdfIndirectObject {
265
322
  * Gets field flags (bitwise combination of field attributes)
266
323
  */
267
324
  get flags() {
268
- return this.content.get('Ff')?.as(PdfNumber)?.value ?? 0;
325
+ return (this.content.get('Ff')?.as(PdfNumber)?.value ??
326
+ this.parent?.content.get('Ff')?.as(PdfNumber)?.value ??
327
+ 0);
269
328
  }
270
329
  /**
271
330
  * Sets field flags
@@ -364,7 +423,9 @@ export class PdfAcroFormField extends PdfIndirectObject {
364
423
  * 0 = left-justified, 1 = centered, 2 = right-justified
365
424
  */
366
425
  get quadding() {
367
- return this.content.get('Q')?.as(PdfNumber)?.value ?? 0;
426
+ return (this.content.get('Q')?.as(PdfNumber)?.value ??
427
+ this.parent?.content.get('Q')?.as(PdfNumber)?.value ??
428
+ 0);
368
429
  }
369
430
  /**
370
431
  * Sets the quadding (text alignment) for this field.
@@ -378,7 +439,8 @@ export class PdfAcroFormField extends PdfIndirectObject {
378
439
  * Returns an array of option strings.
379
440
  */
380
441
  get options() {
381
- const opt = this.content.get('Opt')?.as((PdfArray));
442
+ const opt = this.content.get('Opt')?.as((PdfArray)) ??
443
+ this.parent?.content.get('Opt')?.as((PdfArray));
382
444
  if (!opt)
383
445
  return [];
384
446
  return opt.items.map((item) => item.value);
@@ -396,7 +458,9 @@ export class PdfAcroFormField extends PdfIndirectObject {
396
458
  this.content.set('Opt', optArray);
397
459
  }
398
460
  get defaultAppearance() {
399
- return this.content.get('DA')?.as(PdfString)?.value ?? null;
461
+ return (this.content.get('DA')?.as(PdfString)?.value ??
462
+ this.parent?.content.get('DA')?.as(PdfString)?.value ??
463
+ null);
400
464
  }
401
465
  set defaultAppearance(da) {
402
466
  this.content.set('DA', new PdfString(da));
@@ -747,8 +811,9 @@ export class PdfAcroFormField extends PdfIndirectObject {
747
811
  const [x1, y1, x2, y2] = rect;
748
812
  const width = x2 - x1;
749
813
  const height = y2 - y1;
750
- // Get the default appearance string
751
- const da = this.content.get('DA')?.as(PdfString)?.value;
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;
752
817
  if (!da)
753
818
  return false;
754
819
  // Get the field value
@@ -858,6 +923,25 @@ EMC
858
923
  new PdfNumber(width),
859
924
  new PdfNumber(height),
860
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
+ }
861
945
  const stream = new PdfStream({
862
946
  header: appearanceDict,
863
947
  original: contentStream,
@@ -978,8 +1062,9 @@ Q
978
1062
  const [x1, y1, x2, y2] = rect;
979
1063
  const width = x2 - x1;
980
1064
  const height = y2 - y1;
981
- // Get the default appearance string
982
- const da = this.content.get('DA')?.as(PdfString)?.value;
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;
983
1068
  if (!da)
984
1069
  return false;
985
1070
  const value = this.value;
@@ -1045,6 +1130,23 @@ EMC
1045
1130
  new PdfNumber(width),
1046
1131
  new PdfNumber(height),
1047
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
+ }
1048
1150
  const stream = new PdfStream({
1049
1151
  header: appearanceDict,
1050
1152
  original: contentStream,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-lite",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "exports": {