pdf-lite 1.6.1 → 1.6.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/EXAMPLES.md +289 -0
- package/README.md +148 -8
- package/dist/acroform/appearance/pdf-graphics.js +4 -3
- package/dist/acroform/appearance/pdf-text-appearance-stream.js +37 -22
- package/dist/acroform/fields/pdf-button-form-field.js +23 -7
- package/dist/acroform/fields/pdf-default-appearance.js +1 -4
- package/dist/acroform/fields/pdf-form-field.d.ts +10 -0
- package/dist/acroform/fields/pdf-form-field.js +43 -0
- package/dist/acroform/index.d.ts +1 -0
- package/dist/acroform/index.js +1 -0
- package/dist/acroform/js/index.d.ts +5 -0
- package/dist/acroform/js/index.js +5 -0
- package/dist/acroform/js/pdf-field-actions.d.ts +17 -0
- package/dist/acroform/js/pdf-field-actions.js +52 -0
- package/dist/acroform/js/pdf-javascript-action.d.ts +17 -0
- package/dist/acroform/js/pdf-javascript-action.js +38 -0
- package/dist/acroform/js/pdf-js-builtins.d.ts +12 -0
- package/dist/acroform/js/pdf-js-builtins.js +454 -0
- package/dist/acroform/js/pdf-js-engine-impl.d.ts +18 -0
- package/dist/acroform/js/pdf-js-engine-impl.js +22 -0
- package/dist/acroform/js/pdf-js-engine.d.ts +10 -0
- package/dist/acroform/js/pdf-js-engine.js +1 -0
- package/dist/acroform/pdf-acro-form.d.ts +4 -0
- package/dist/acroform/pdf-acro-form.js +58 -2
- package/dist/annotations/pdf-annotation.d.ts +20 -0
- package/dist/core/objects/pdf-dictionary.d.ts +2 -0
- package/dist/core/objects/pdf-dictionary.js +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// util.printd — format a Date as a string using Acrobat date tokens
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
const MONTH_NAMES = [
|
|
5
|
+
'January',
|
|
6
|
+
'February',
|
|
7
|
+
'March',
|
|
8
|
+
'April',
|
|
9
|
+
'May',
|
|
10
|
+
'June',
|
|
11
|
+
'July',
|
|
12
|
+
'August',
|
|
13
|
+
'September',
|
|
14
|
+
'October',
|
|
15
|
+
'November',
|
|
16
|
+
'December',
|
|
17
|
+
];
|
|
18
|
+
const MONTH_ABBR = [
|
|
19
|
+
'Jan',
|
|
20
|
+
'Feb',
|
|
21
|
+
'Mar',
|
|
22
|
+
'Apr',
|
|
23
|
+
'May',
|
|
24
|
+
'Jun',
|
|
25
|
+
'Jul',
|
|
26
|
+
'Aug',
|
|
27
|
+
'Sep',
|
|
28
|
+
'Oct',
|
|
29
|
+
'Nov',
|
|
30
|
+
'Dec',
|
|
31
|
+
];
|
|
32
|
+
const DAY_NAMES = [
|
|
33
|
+
'Sunday',
|
|
34
|
+
'Monday',
|
|
35
|
+
'Tuesday',
|
|
36
|
+
'Wednesday',
|
|
37
|
+
'Thursday',
|
|
38
|
+
'Friday',
|
|
39
|
+
'Saturday',
|
|
40
|
+
];
|
|
41
|
+
const DAY_ABBR = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
42
|
+
function pad(n, len) {
|
|
43
|
+
return String(n).padStart(len, '0');
|
|
44
|
+
}
|
|
45
|
+
export function printd(fmt, date) {
|
|
46
|
+
const y = date.getFullYear();
|
|
47
|
+
const M = date.getMonth(); // 0-based
|
|
48
|
+
const d = date.getDate();
|
|
49
|
+
const dow = date.getDay();
|
|
50
|
+
const H = date.getHours();
|
|
51
|
+
const m = date.getMinutes();
|
|
52
|
+
const s = date.getSeconds();
|
|
53
|
+
const h12 = H % 12 || 12;
|
|
54
|
+
// Replace tokens longest-first to avoid partial matches
|
|
55
|
+
return fmt
|
|
56
|
+
.replace(/yyyy/g, String(y))
|
|
57
|
+
.replace(/yy/g, pad(y % 100, 2))
|
|
58
|
+
.replace(/mmmm/g, MONTH_NAMES[M])
|
|
59
|
+
.replace(/mmm/g, MONTH_ABBR[M])
|
|
60
|
+
.replace(/mm/g, pad(M + 1, 2))
|
|
61
|
+
.replace(/(?<![\w])m(?![\w])/g, String(M + 1))
|
|
62
|
+
.replace(/dddd/g, DAY_NAMES[dow])
|
|
63
|
+
.replace(/ddd/g, DAY_ABBR[dow])
|
|
64
|
+
.replace(/dd/g, pad(d, 2))
|
|
65
|
+
.replace(/(?<![\w])d(?![\w])/g, String(d))
|
|
66
|
+
.replace(/HH/g, pad(H, 2))
|
|
67
|
+
.replace(/hh/g, pad(h12, 2))
|
|
68
|
+
.replace(/(?<![\w])h(?![\w])/g, String(h12))
|
|
69
|
+
.replace(/MM/g, pad(m, 2))
|
|
70
|
+
.replace(/ss/g, pad(s, 2))
|
|
71
|
+
.replace(/tt/g, H < 12 ? 'AM' : 'PM')
|
|
72
|
+
.replace(/(?<![\w])t(?![\w])/g, H < 12 ? 'am' : 'pm');
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// util.scand — parse a date string using an Acrobat format
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
export function scand(fmt, str) {
|
|
78
|
+
// Build a regex from the format, capturing groups for each token
|
|
79
|
+
const tokens = [];
|
|
80
|
+
let pattern = '';
|
|
81
|
+
let i = 0;
|
|
82
|
+
while (i < fmt.length) {
|
|
83
|
+
let matched = false;
|
|
84
|
+
for (const tok of [
|
|
85
|
+
'yyyy',
|
|
86
|
+
'yy',
|
|
87
|
+
'mmmm',
|
|
88
|
+
'mmm',
|
|
89
|
+
'mm',
|
|
90
|
+
'm',
|
|
91
|
+
'dddd',
|
|
92
|
+
'ddd',
|
|
93
|
+
'dd',
|
|
94
|
+
'd',
|
|
95
|
+
'HH',
|
|
96
|
+
'hh',
|
|
97
|
+
'h',
|
|
98
|
+
'MM',
|
|
99
|
+
'ss',
|
|
100
|
+
]) {
|
|
101
|
+
if (fmt.substring(i, i + tok.length) === tok) {
|
|
102
|
+
tokens.push({ token: tok, index: tokens.length });
|
|
103
|
+
switch (tok) {
|
|
104
|
+
case 'yyyy':
|
|
105
|
+
pattern += '(\\d{4})';
|
|
106
|
+
break;
|
|
107
|
+
case 'yy':
|
|
108
|
+
pattern += '(\\d{2})';
|
|
109
|
+
break;
|
|
110
|
+
case 'mmmm':
|
|
111
|
+
pattern += '([A-Za-z]+)';
|
|
112
|
+
break;
|
|
113
|
+
case 'mmm':
|
|
114
|
+
pattern += '([A-Za-z]{3})';
|
|
115
|
+
break;
|
|
116
|
+
case 'mm':
|
|
117
|
+
pattern += '(\\d{2})';
|
|
118
|
+
break;
|
|
119
|
+
case 'm':
|
|
120
|
+
pattern += '(\\d{1,2})';
|
|
121
|
+
break;
|
|
122
|
+
case 'dddd':
|
|
123
|
+
pattern += '([A-Za-z]+)';
|
|
124
|
+
break;
|
|
125
|
+
case 'ddd':
|
|
126
|
+
pattern += '([A-Za-z]{3})';
|
|
127
|
+
break;
|
|
128
|
+
case 'dd':
|
|
129
|
+
pattern += '(\\d{2})';
|
|
130
|
+
break;
|
|
131
|
+
case 'd':
|
|
132
|
+
pattern += '(\\d{1,2})';
|
|
133
|
+
break;
|
|
134
|
+
case 'HH':
|
|
135
|
+
case 'hh':
|
|
136
|
+
pattern += '(\\d{2})';
|
|
137
|
+
break;
|
|
138
|
+
case 'h':
|
|
139
|
+
pattern += '(\\d{1,2})';
|
|
140
|
+
break;
|
|
141
|
+
case 'MM':
|
|
142
|
+
pattern += '(\\d{2})';
|
|
143
|
+
break;
|
|
144
|
+
case 'ss':
|
|
145
|
+
pattern += '(\\d{2})';
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
i += tok.length;
|
|
149
|
+
matched = true;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!matched) {
|
|
154
|
+
pattern += fmt[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
155
|
+
i++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const re = new RegExp('^' + pattern + '$');
|
|
159
|
+
const match = str.match(re);
|
|
160
|
+
if (!match)
|
|
161
|
+
return null;
|
|
162
|
+
let year = 0, month = 0, day = 1, hours = 0, minutes = 0, seconds = 0;
|
|
163
|
+
for (const { token, index } of tokens) {
|
|
164
|
+
const val = match[index + 1];
|
|
165
|
+
switch (token) {
|
|
166
|
+
case 'yyyy':
|
|
167
|
+
year = parseInt(val, 10);
|
|
168
|
+
break;
|
|
169
|
+
case 'yy': {
|
|
170
|
+
const n = parseInt(val, 10);
|
|
171
|
+
year = n < 50 ? 2000 + n : 1900 + n;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case 'mmmm': {
|
|
175
|
+
const idx = MONTH_NAMES.findIndex((m) => m.toLowerCase() === val.toLowerCase());
|
|
176
|
+
if (idx >= 0)
|
|
177
|
+
month = idx;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case 'mmm': {
|
|
181
|
+
const idx = MONTH_ABBR.findIndex((m) => m.toLowerCase() === val.toLowerCase());
|
|
182
|
+
if (idx >= 0)
|
|
183
|
+
month = idx;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case 'mm':
|
|
187
|
+
case 'm':
|
|
188
|
+
month = parseInt(val, 10) - 1;
|
|
189
|
+
break;
|
|
190
|
+
case 'dd':
|
|
191
|
+
case 'd':
|
|
192
|
+
day = parseInt(val, 10);
|
|
193
|
+
break;
|
|
194
|
+
case 'HH':
|
|
195
|
+
case 'hh':
|
|
196
|
+
case 'h':
|
|
197
|
+
hours = parseInt(val, 10);
|
|
198
|
+
break;
|
|
199
|
+
case 'MM':
|
|
200
|
+
minutes = parseInt(val, 10);
|
|
201
|
+
break;
|
|
202
|
+
case 'ss':
|
|
203
|
+
seconds = parseInt(val, 10);
|
|
204
|
+
break;
|
|
205
|
+
// dddd/ddd are day-of-week names — informational only, ignored
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return new Date(year, month, day, hours, minutes, seconds);
|
|
209
|
+
}
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// util.printf — sprintf subset: %d, %f, %s with width/precision/flags
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
export function printf(fmt, ...args) {
|
|
214
|
+
let argIdx = 0;
|
|
215
|
+
return fmt.replace(/%([0 #+-]*)(\d+)?(?:\.(\d+))?([dfsxXo%])/g, (_match, flags, widthStr, precStr, type) => {
|
|
216
|
+
if (type === '%')
|
|
217
|
+
return '%';
|
|
218
|
+
const val = args[argIdx++];
|
|
219
|
+
const width = widthStr ? parseInt(widthStr, 10) : 0;
|
|
220
|
+
const prec = precStr !== undefined ? parseInt(precStr, 10) : undefined;
|
|
221
|
+
const leftAlign = flags.includes('-');
|
|
222
|
+
const zeroPad = flags.includes('0') && !leftAlign;
|
|
223
|
+
const plusSign = flags.includes('+');
|
|
224
|
+
const spaceSign = flags.includes(' ');
|
|
225
|
+
let result;
|
|
226
|
+
switch (type) {
|
|
227
|
+
case 'd': {
|
|
228
|
+
const n = Math.trunc(Number(val));
|
|
229
|
+
const sign = n < 0 ? '-' : plusSign ? '+' : spaceSign ? ' ' : '';
|
|
230
|
+
const digits = String(Math.abs(n));
|
|
231
|
+
result =
|
|
232
|
+
sign +
|
|
233
|
+
(zeroPad
|
|
234
|
+
? digits.padStart(Math.max(0, width - sign.length), '0')
|
|
235
|
+
: digits);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case 'f': {
|
|
239
|
+
const n = Number(val);
|
|
240
|
+
const p = prec !== undefined ? prec : 6;
|
|
241
|
+
const sign = n < 0 ? '-' : plusSign ? '+' : spaceSign ? ' ' : '';
|
|
242
|
+
const formatted = Math.abs(n).toFixed(p);
|
|
243
|
+
result =
|
|
244
|
+
sign +
|
|
245
|
+
(zeroPad
|
|
246
|
+
? formatted.padStart(Math.max(0, width - sign.length), '0')
|
|
247
|
+
: formatted);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case 'x':
|
|
251
|
+
result = (Math.trunc(Number(val)) >>> 0).toString(16);
|
|
252
|
+
break;
|
|
253
|
+
case 'X':
|
|
254
|
+
result = (Math.trunc(Number(val)) >>> 0)
|
|
255
|
+
.toString(16)
|
|
256
|
+
.toUpperCase();
|
|
257
|
+
break;
|
|
258
|
+
case 'o':
|
|
259
|
+
result = (Math.trunc(Number(val)) >>> 0).toString(8);
|
|
260
|
+
break;
|
|
261
|
+
case 's':
|
|
262
|
+
default:
|
|
263
|
+
result = String(val);
|
|
264
|
+
if (prec !== undefined)
|
|
265
|
+
result = result.slice(0, prec);
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
if (result.length < width) {
|
|
269
|
+
result = leftAlign
|
|
270
|
+
? result.padEnd(width)
|
|
271
|
+
: result.padStart(width, zeroPad ? '0' : ' ');
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// util object
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
export const util = { printd, scand, printf };
|
|
280
|
+
function parseNumericValue(str) {
|
|
281
|
+
// Strip currency symbols, spaces, commas
|
|
282
|
+
const cleaned = str.replace(/[^0-9.\-eE]/g, '');
|
|
283
|
+
const n = parseFloat(cleaned);
|
|
284
|
+
return isNaN(n) ? 0 : n;
|
|
285
|
+
}
|
|
286
|
+
function formatNumber(value, nDec, sepStyle, negStyle, _currStyle, strCurrency, bCurrencyPrepend) {
|
|
287
|
+
const absVal = Math.abs(value);
|
|
288
|
+
const fixed = absVal.toFixed(nDec);
|
|
289
|
+
// sepStyle: 0 = 1,234.56 1 = 1234.56 2 = 1.234,56 3 = 1234,56
|
|
290
|
+
let [intPart, decPart] = fixed.split('.');
|
|
291
|
+
const decSep = sepStyle >= 2 ? ',' : '.';
|
|
292
|
+
const thousandSep = sepStyle === 0 ? ',' : sepStyle === 2 ? '.' : '';
|
|
293
|
+
if (thousandSep) {
|
|
294
|
+
intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, thousandSep);
|
|
295
|
+
}
|
|
296
|
+
let formatted = decPart !== undefined ? intPart + decSep + decPart : intPart;
|
|
297
|
+
// Currency
|
|
298
|
+
if (strCurrency) {
|
|
299
|
+
if (bCurrencyPrepend) {
|
|
300
|
+
formatted = strCurrency + formatted;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
formatted = formatted + strCurrency;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Negative style: 0 = -val 1 = red text (we just use -) 2 = (val) 3 = (val) red
|
|
307
|
+
if (value < 0) {
|
|
308
|
+
if (negStyle === 0 || negStyle === 1) {
|
|
309
|
+
formatted = '-' + formatted;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
formatted = '(' + formatted + ')';
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return formatted;
|
|
316
|
+
}
|
|
317
|
+
export function createBuiltins(event, getFieldValue) {
|
|
318
|
+
// --- AF functions that close over event ---
|
|
319
|
+
function AFDate_FormatEx(fmt) {
|
|
320
|
+
if (!event.value)
|
|
321
|
+
return;
|
|
322
|
+
// Try parsing the current value as a date
|
|
323
|
+
const d = new Date(event.value);
|
|
324
|
+
if (isNaN(d.getTime()))
|
|
325
|
+
return;
|
|
326
|
+
event.value = printd(fmt, d);
|
|
327
|
+
}
|
|
328
|
+
function AFDate_KeystrokeEx(fmt) {
|
|
329
|
+
if (event.willCommit) {
|
|
330
|
+
if (!event.value)
|
|
331
|
+
return;
|
|
332
|
+
const d = scand(fmt, event.value);
|
|
333
|
+
if (!d) {
|
|
334
|
+
event.rc = false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// On non-commit keystrokes, allow all input
|
|
338
|
+
}
|
|
339
|
+
function AFNumber_Format(nDec, sepStyle, negStyle, _currStyle, strCurrency, bCurrencyPrepend) {
|
|
340
|
+
if (!event.value && event.value !== '0')
|
|
341
|
+
return;
|
|
342
|
+
const num = parseNumericValue(event.value);
|
|
343
|
+
event.value = formatNumber(num, nDec, sepStyle, negStyle, _currStyle, strCurrency, bCurrencyPrepend);
|
|
344
|
+
}
|
|
345
|
+
function AFNumber_Keystroke(nDec, _sepStyle, _negStyle, _currStyle, _strCurrency, _bCurrencyPrepend) {
|
|
346
|
+
if (event.willCommit) {
|
|
347
|
+
if (!event.value)
|
|
348
|
+
return;
|
|
349
|
+
const num = parseFloat(event.value);
|
|
350
|
+
if (isNaN(num)) {
|
|
351
|
+
event.rc = false;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
// Check decimal places
|
|
355
|
+
const parts = event.value.split('.');
|
|
356
|
+
if (nDec === 0 && parts.length > 1) {
|
|
357
|
+
event.rc = false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function AFSimple_Calculate(op, fields) {
|
|
362
|
+
if (!getFieldValue)
|
|
363
|
+
return;
|
|
364
|
+
const values = fields.map((name) => parseNumericValue(getFieldValue(name)));
|
|
365
|
+
let result;
|
|
366
|
+
switch (op.toUpperCase()) {
|
|
367
|
+
case 'SUM':
|
|
368
|
+
result = values.reduce((a, b) => a + b, 0);
|
|
369
|
+
break;
|
|
370
|
+
case 'AVG':
|
|
371
|
+
result = values.length
|
|
372
|
+
? values.reduce((a, b) => a + b, 0) / values.length
|
|
373
|
+
: 0;
|
|
374
|
+
break;
|
|
375
|
+
case 'PRD':
|
|
376
|
+
result = values.reduce((a, b) => a * b, 1);
|
|
377
|
+
break;
|
|
378
|
+
case 'MIN':
|
|
379
|
+
result = values.length ? Math.min(...values) : 0;
|
|
380
|
+
break;
|
|
381
|
+
case 'MAX':
|
|
382
|
+
result = values.length ? Math.max(...values) : 0;
|
|
383
|
+
break;
|
|
384
|
+
default:
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
event.value = String(result);
|
|
388
|
+
}
|
|
389
|
+
function AFSpecial_Format(psf) {
|
|
390
|
+
if (!event.value)
|
|
391
|
+
return;
|
|
392
|
+
const digits = event.value.replace(/\D/g, '');
|
|
393
|
+
switch (psf) {
|
|
394
|
+
case 0: // zip: 12345
|
|
395
|
+
event.value = digits.slice(0, 5).padEnd(5, '0');
|
|
396
|
+
break;
|
|
397
|
+
case 1: // ssn: 123-45-6789
|
|
398
|
+
event.value =
|
|
399
|
+
digits.slice(0, 3) +
|
|
400
|
+
'-' +
|
|
401
|
+
digits.slice(3, 5) +
|
|
402
|
+
'-' +
|
|
403
|
+
digits.slice(5, 9);
|
|
404
|
+
break;
|
|
405
|
+
case 2: // phone: (123) 456-7890
|
|
406
|
+
event.value =
|
|
407
|
+
'(' +
|
|
408
|
+
digits.slice(0, 3) +
|
|
409
|
+
') ' +
|
|
410
|
+
digits.slice(3, 6) +
|
|
411
|
+
'-' +
|
|
412
|
+
digits.slice(6, 10);
|
|
413
|
+
break;
|
|
414
|
+
case 3: // zip+4: 12345-6789
|
|
415
|
+
event.value = digits.slice(0, 5) + '-' + digits.slice(5, 9);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function AFSpecial_Keystroke(psf) {
|
|
420
|
+
if (!event.willCommit)
|
|
421
|
+
return;
|
|
422
|
+
if (!event.value)
|
|
423
|
+
return;
|
|
424
|
+
const digits = event.value.replace(/\D/g, '');
|
|
425
|
+
switch (psf) {
|
|
426
|
+
case 0: // zip
|
|
427
|
+
if (digits.length !== 5)
|
|
428
|
+
event.rc = false;
|
|
429
|
+
break;
|
|
430
|
+
case 1: // ssn
|
|
431
|
+
if (digits.length !== 9)
|
|
432
|
+
event.rc = false;
|
|
433
|
+
break;
|
|
434
|
+
case 2: // phone
|
|
435
|
+
if (digits.length !== 10 && digits.length !== 7)
|
|
436
|
+
event.rc = false;
|
|
437
|
+
break;
|
|
438
|
+
case 3: // zip+4
|
|
439
|
+
if (digits.length !== 9)
|
|
440
|
+
event.rc = false;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
util,
|
|
446
|
+
AFDate_FormatEx,
|
|
447
|
+
AFDate_KeystrokeEx,
|
|
448
|
+
AFNumber_Format,
|
|
449
|
+
AFNumber_Keystroke,
|
|
450
|
+
AFSimple_Calculate,
|
|
451
|
+
AFSpecial_Format,
|
|
452
|
+
AFSpecial_Keystroke,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PdfJsEngine, PdfJsEvent } from './pdf-js-engine.js';
|
|
2
|
+
export interface PdfJavaScriptEngineOptions {
|
|
3
|
+
getFieldValue?: (name: string) => string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Default JavaScript engine that executes PDF JS actions via `new Function()`.
|
|
7
|
+
*
|
|
8
|
+
* **Security note:** This engine runs PDF-sourced JavaScript with access to the
|
|
9
|
+
* ambient JS environment (e.g. `globalThis`, constructors, network APIs). It is
|
|
10
|
+
* NOT sandboxed and should only be used with trusted PDF documents. For untrusted
|
|
11
|
+
* documents, provide your own `PdfJsEngine` implementation that evaluates code
|
|
12
|
+
* in an isolated context (e.g. Node `vm`, a dedicated realm, or a Web Worker).
|
|
13
|
+
*/
|
|
14
|
+
export declare class PdfJavaScriptEngine implements PdfJsEngine {
|
|
15
|
+
private _getFieldValue?;
|
|
16
|
+
constructor(options?: PdfJavaScriptEngineOptions);
|
|
17
|
+
execute(code: string, event: PdfJsEvent): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createBuiltins } from './pdf-js-builtins.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default JavaScript engine that executes PDF JS actions via `new Function()`.
|
|
4
|
+
*
|
|
5
|
+
* **Security note:** This engine runs PDF-sourced JavaScript with access to the
|
|
6
|
+
* ambient JS environment (e.g. `globalThis`, constructors, network APIs). It is
|
|
7
|
+
* NOT sandboxed and should only be used with trusted PDF documents. For untrusted
|
|
8
|
+
* documents, provide your own `PdfJsEngine` implementation that evaluates code
|
|
9
|
+
* in an isolated context (e.g. Node `vm`, a dedicated realm, or a Web Worker).
|
|
10
|
+
*/
|
|
11
|
+
export class PdfJavaScriptEngine {
|
|
12
|
+
_getFieldValue;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this._getFieldValue = options?.getFieldValue;
|
|
15
|
+
}
|
|
16
|
+
execute(code, event) {
|
|
17
|
+
const builtins = createBuiltins(event, this._getFieldValue);
|
|
18
|
+
const names = Object.keys(builtins);
|
|
19
|
+
const fn = new Function('event', 'app', ...names, code);
|
|
20
|
+
fn(event, Object.freeze({ alert() { } }), ...names.map((k) => builtins[k]));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -12,6 +12,7 @@ import './fields/pdf-choice-form-field.js';
|
|
|
12
12
|
import './fields/pdf-signature-form-field.js';
|
|
13
13
|
import { PdfDefaultResourcesDictionary } from '../annotations/pdf-default-resources.js';
|
|
14
14
|
import { PdfXfaForm } from './xfa/pdf-xfa-form.js';
|
|
15
|
+
import type { PdfJsEngine } from './js/pdf-js-engine.js';
|
|
15
16
|
export declare class PdfAcroForm<T extends Record<string, string> = Record<string, string>> extends PdfIndirectObject<PdfDictionary<{
|
|
16
17
|
Fields: PdfArray<PdfObjectReference> | PdfObjectReference;
|
|
17
18
|
NeedAppearances?: PdfBoolean;
|
|
@@ -38,7 +39,10 @@ export declare class PdfAcroForm<T extends Record<string, string> = Record<strin
|
|
|
38
39
|
set fields(newFields: PdfFormField[]);
|
|
39
40
|
setValues(values: Partial<T>): void;
|
|
40
41
|
importData(fields: T): void;
|
|
42
|
+
private _fireValidate;
|
|
43
|
+
private _fireCalculate;
|
|
41
44
|
exportData(): Partial<T>;
|
|
45
|
+
jsEngine?: PdfJsEngine;
|
|
42
46
|
fontEncodingMaps: Map<string, Map<number, string>>;
|
|
43
47
|
getFontEncodingMap(fontName: string): Map<number, string> | null;
|
|
44
48
|
get xfa(): PdfXfaForm | null;
|
|
@@ -122,15 +122,70 @@ export class PdfAcroForm extends PdfIndirectObject {
|
|
|
122
122
|
for (const field of this.fields) {
|
|
123
123
|
const name = field.name;
|
|
124
124
|
if (name in values && values[name] !== undefined) {
|
|
125
|
-
|
|
125
|
+
const result = this._fireValidate(field, values[name]);
|
|
126
|
+
if (!result.rc)
|
|
127
|
+
continue;
|
|
128
|
+
field.value = result.value;
|
|
126
129
|
}
|
|
127
130
|
}
|
|
131
|
+
this._fireCalculate();
|
|
128
132
|
}
|
|
129
133
|
importData(fields) {
|
|
130
134
|
for (const field of this.fields) {
|
|
131
135
|
const name = field.name;
|
|
132
136
|
if (name && name in fields) {
|
|
133
|
-
|
|
137
|
+
const result = this._fireValidate(field, fields[name]);
|
|
138
|
+
if (!result.rc)
|
|
139
|
+
continue;
|
|
140
|
+
field.value = result.value;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
this._fireCalculate();
|
|
144
|
+
}
|
|
145
|
+
_fireValidate(field, value) {
|
|
146
|
+
const validateAction = field.actions?.validate;
|
|
147
|
+
if (!validateAction?.code)
|
|
148
|
+
return { rc: true, value };
|
|
149
|
+
const event = {
|
|
150
|
+
fieldName: field.name,
|
|
151
|
+
value,
|
|
152
|
+
willCommit: true,
|
|
153
|
+
rc: true,
|
|
154
|
+
};
|
|
155
|
+
validateAction.execute(event);
|
|
156
|
+
return { rc: event.rc, value: event.value };
|
|
157
|
+
}
|
|
158
|
+
_fireCalculate() {
|
|
159
|
+
const co = this.content.get('CO');
|
|
160
|
+
if (!(co instanceof PdfArray))
|
|
161
|
+
return;
|
|
162
|
+
const allFields = this.fields;
|
|
163
|
+
const fieldsByObjNum = new Map();
|
|
164
|
+
for (const field of allFields) {
|
|
165
|
+
if (field.objectNumber != null) {
|
|
166
|
+
fieldsByObjNum.set(field.objectNumber, field);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const item of co.items) {
|
|
170
|
+
if (!(item instanceof PdfObjectReference))
|
|
171
|
+
continue;
|
|
172
|
+
const resolved = item.resolve();
|
|
173
|
+
if (!resolved)
|
|
174
|
+
continue;
|
|
175
|
+
const field = fieldsByObjNum.get(resolved.objectNumber);
|
|
176
|
+
if (!field)
|
|
177
|
+
continue;
|
|
178
|
+
const calcAction = field.actions?.calculate;
|
|
179
|
+
if (!calcAction?.code)
|
|
180
|
+
continue;
|
|
181
|
+
const event = {
|
|
182
|
+
fieldName: field.name,
|
|
183
|
+
value: field.value,
|
|
184
|
+
rc: true,
|
|
185
|
+
};
|
|
186
|
+
calcAction.execute(event);
|
|
187
|
+
if (event.rc && event.value !== field.value) {
|
|
188
|
+
field.value = event.value;
|
|
134
189
|
}
|
|
135
190
|
}
|
|
136
191
|
}
|
|
@@ -144,6 +199,7 @@ export class PdfAcroForm extends PdfIndirectObject {
|
|
|
144
199
|
}
|
|
145
200
|
return result;
|
|
146
201
|
}
|
|
202
|
+
jsEngine;
|
|
147
203
|
fontEncodingMaps = new Map();
|
|
148
204
|
getFontEncodingMap(fontName) {
|
|
149
205
|
if (this.fontEncodingMaps.has(fontName)) {
|
|
@@ -34,6 +34,26 @@ export declare class PdfAnnotation extends PdfIndirectObject<PdfDictionary<{
|
|
|
34
34
|
AP?: PdfAppearanceStreamDictionary;
|
|
35
35
|
P?: PdfObjectReference;
|
|
36
36
|
Parent?: PdfObjectReference<PdfPage>;
|
|
37
|
+
A?: PdfDictionary<{
|
|
38
|
+
Type?: PdfName;
|
|
39
|
+
S?: PdfName;
|
|
40
|
+
D?: PdfArray | PdfDictionary;
|
|
41
|
+
T?: PdfString;
|
|
42
|
+
F?: PdfNumber;
|
|
43
|
+
Win?: PdfDictionary;
|
|
44
|
+
JS?: PdfString;
|
|
45
|
+
}>;
|
|
46
|
+
AA?: PdfDictionary<{
|
|
47
|
+
D?: PdfObjectReference | PdfDictionary;
|
|
48
|
+
U?: PdfObjectReference | PdfDictionary;
|
|
49
|
+
Fo?: PdfObjectReference | PdfDictionary;
|
|
50
|
+
Bl?: PdfObjectReference | PdfDictionary;
|
|
51
|
+
PO?: PdfObjectReference | PdfDictionary;
|
|
52
|
+
K?: PdfObjectReference | PdfDictionary;
|
|
53
|
+
V?: PdfObjectReference | PdfDictionary;
|
|
54
|
+
C?: PdfObjectReference | PdfDictionary;
|
|
55
|
+
F?: PdfObjectReference | PdfDictionary;
|
|
56
|
+
}>;
|
|
37
57
|
}>> {
|
|
38
58
|
private _annotationFlags?;
|
|
39
59
|
private get flags_();
|
|
@@ -22,6 +22,8 @@ export declare class PdfDictionary<T extends PdfDictionaryEntries = PdfDictionar
|
|
|
22
22
|
entries(): IterableIterator<[string, PdfObject | undefined]>;
|
|
23
23
|
get isTrailingDelimited(): boolean;
|
|
24
24
|
protected tokenize(): PdfToken[];
|
|
25
|
+
/** Factory-style type conversion: constructs a new instance passing `this` as the first argument */
|
|
26
|
+
becomes<U extends PdfDictionary, A extends unknown[]>(cls: new (source: PdfDictionary, ...args: A) => U, ...args: A): U;
|
|
25
27
|
copyFrom(other: PdfDictionary<any>): void;
|
|
26
28
|
cloneImpl(): this;
|
|
27
29
|
setModified(modified?: boolean): void;
|
|
@@ -131,6 +131,20 @@ export class PdfDictionary extends PdfObject {
|
|
|
131
131
|
new PdfEndDictionaryToken(),
|
|
132
132
|
];
|
|
133
133
|
}
|
|
134
|
+
/** Factory-style type conversion: constructs a new instance passing `this` as the first argument */
|
|
135
|
+
becomes(cls, ...args) {
|
|
136
|
+
if (this instanceof cls)
|
|
137
|
+
return this;
|
|
138
|
+
const donor = new cls(this, ...args);
|
|
139
|
+
Object.setPrototypeOf(this, cls.prototype);
|
|
140
|
+
for (const key of Object.getOwnPropertyNames(donor)) {
|
|
141
|
+
if (!Object.prototype.hasOwnProperty.call(this, key)) {
|
|
142
|
+
;
|
|
143
|
+
this[key] = donor[key];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
134
148
|
copyFrom(other) {
|
|
135
149
|
for (const [key, value] of other.#entries) {
|
|
136
150
|
this.#entries.set(key, value);
|