@embedpdf/core-acrojs 3.0.0-next.13 → 3.0.0-next.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  //#region src/actions.ts
2
2
  /**
3
3
  * Return executable JavaScript in PDF action order (root, then `/Next`
4
- * depth-first). A poisoned native snapshot is never partially executed.
4
+ * depth-first). A poisoned native snapshot is never partially executed. Any
5
+ * kind of destination will do: only the scripts are read.
5
6
  */
6
7
  function javaScriptSourcesFromActionTree(tree) {
7
8
  if (tree.incomplete) throw new Error("Refusing to execute an incomplete PDF action tree");
@@ -22,7 +23,7 @@ function javaScriptProgramFromActionTree(tree) {
22
23
  /**
23
24
  * Host-side Acrobat color-array conversions — the commit sinks and world
24
25
  * builders cross the Acrobat-array ↔ renderer-color boundary through these.
25
- * The PRELUDE inlines its own identical formulas (it must stay
26
+ * The prelude inlines its own identical formulas (it must stay
26
27
  * self-contained); `prelude.test.ts` pins the two implementations together.
27
28
  *
28
29
  * Conversion vectors (the documented Acrobat semantics):
@@ -46,11 +47,11 @@ function scriptColorToRgb(color) {
46
47
  b: color[3]
47
48
  };
48
49
  case "CMYK": {
49
- const [, c, m, y, k] = color;
50
+ const [, cyan, magenta, yellow, black] = color;
50
51
  return {
51
- r: 1 - Math.min(1, c + k),
52
- g: 1 - Math.min(1, m + k),
53
- b: 1 - Math.min(1, y + k)
52
+ r: 1 - Math.min(1, cyan + black),
53
+ g: 1 - Math.min(1, magenta + black),
54
+ b: 1 - Math.min(1, yellow + black)
54
55
  };
55
56
  }
56
57
  }
@@ -91,10 +92,10 @@ function isScriptColorArray(value) {
91
92
  //#endregion
92
93
  //#region src/types.ts
93
94
  /**
94
- * The writable-property validity matrix, keyed by engine subtype — the ONE
95
+ * The writable-property validity matrix, keyed by engine subtype — the one
95
96
  * exported table (the prelude inlines an identical copy; a parity test pins
96
97
  * them together, and plugin-annotation drift-guards this against the kind
97
- * registry's PropSpecs). Flags + contents are writable everywhere
98
+ * registry's FieldSpecs). Flags + contents are writable everywhere
98
99
  * script-addressable; appearance keys vary by kind.
99
100
  */
100
101
  const ANNOT_WRITABLE_KEYS = Object.freeze({
@@ -218,16 +219,16 @@ function seedFrom(documentId, sequence) {
218
219
  }
219
220
  return hash >>> 0;
220
221
  }
221
- /** Engine JWT/session identity composed under embedder overrides — the
222
- * standard `ScriptHostOptions.identity` builder. */
222
+ /** The session's identity, as Acrobat JavaScript's `identity` object, under
223
+ * embedder overrides — the standard `ScriptHostOptions.identity` builder. */
223
224
  function resolveScriptIdentity(doc, overrides) {
224
- const claims = doc.security.identity;
225
+ const identity = doc.security.identity;
225
226
  const supplied = typeof overrides === "function" ? overrides() : overrides ?? {};
226
227
  return {
227
- name: supplied.name ?? claims?.display_name ?? claims?.user_id ?? "",
228
- loginName: supplied.loginName ?? claims?.user_id ?? "",
229
- corporation: supplied.corporation ?? claims?.group_id ?? "",
230
- email: supplied.email ?? ""
228
+ name: supplied.name ?? identity?.displayName ?? identity?.userId ?? "",
229
+ loginName: supplied.loginName ?? identity?.userId ?? "",
230
+ corporation: supplied.corporation ?? identity?.organization ?? "",
231
+ email: supplied.email ?? identity?.email ?? ""
231
232
  };
232
233
  }
233
234
  function createScriptHost(options) {
@@ -261,7 +262,7 @@ function createScriptHost(options) {
261
262
  ...world.annotsCoverDocument !== void 0 ? { annotsCoverDocument: world.annotsCoverDocument } : {},
262
263
  event: world.event
263
264
  });
264
- /** A resource fault poisons the realm: rebuild + re-boot lazily NEXT time. */
265
+ /** A resource fault poisons the realm: rebuild + re-boot lazily next time. */
265
266
  const noteFault = (sandbox) => {
266
267
  if (!sandbox.disposed) return;
267
268
  sandboxPromise = null;
@@ -345,25 +346,42 @@ function valueFromEntry(entry) {
345
346
  case "unsupported": return null;
346
347
  }
347
348
  }
348
- function fieldValueFromEntry(family, entry) {
349
- if ((family === "checkbox" || family === "radio") && entry.kind === "none") return "Off";
350
- return valueFromEntry(entry);
349
+ /**
350
+ * A field value as scripts see it. A checkbox or radio group shows the export
351
+ * value of its checked widget, as Acrobat's `field.value` does, or `'Off'`.
352
+ */
353
+ function fieldValueFromEntry(field, entry) {
354
+ if (field.family !== "checkbox" && field.family !== "radio") return valueFromEntry(entry);
355
+ if (entry.kind !== "scalar") return "Off";
356
+ return field.widgets.find((widget) => widget.onState === entry.value)?.exportValue ?? entry.value;
357
+ }
358
+ /**
359
+ * How a widget shows, as Acrobat's `field.display` reads its flags: hidden;
360
+ * on screen but not printed; printed but not on screen; or visible.
361
+ */
362
+ function displayOf(row) {
363
+ if (!row) return "visible";
364
+ if (row.hidden) return "hidden";
365
+ if (row.noView) return "noView";
366
+ return row.print ? "visible" : "noPrint";
351
367
  }
352
368
  /** Build the VM's detached field view from the engine's lossless snapshot. */
353
369
  function scriptFieldsFromSnapshot(snapshot) {
370
+ const rows = new Map(snapshot.widgets.map((row) => [row.ref.kind === "objectNumber" ? row.ref.objectNumber : 0, row]));
354
371
  return snapshot.fields.map((field) => ({
355
372
  ref: field.ref,
356
373
  name: field.name,
357
374
  family: field.family,
358
- value: fieldValueFromEntry(field.family, field.valueEntry),
359
- defaultValue: fieldValueFromEntry(field.family, field.defaultValueEntry),
360
- display: "visible",
361
- readOnly: field.flags.readOnly,
362
- required: field.flags.required,
375
+ value: fieldValueFromEntry(field, field.valueEntry),
376
+ defaultValue: fieldValueFromEntry(field, field.defaultValueEntry),
377
+ display: displayOf(rows.get(field.widgets?.[0]?.objectNumber ?? -1)),
378
+ readOnly: field.readOnly,
379
+ required: field.required,
363
380
  ..."options" in field ? { options: field.options.map((option) => ({
364
381
  label: option.label,
365
382
  value: option.value
366
- })) } : {}
383
+ })) } : {},
384
+ ...field.family === "checkbox" || field.family === "radio" ? { exportValues: field.widgets.map((widget) => widget.exportValue) } : {}
367
385
  }));
368
386
  }
369
387
  //#endregion
@@ -375,18 +393,18 @@ function scriptFieldsFromSnapshot(snapshot) {
375
393
  * values. The sandbox ships `installAcroJs.toString()` into QuickJS, while
376
394
  * tests execute the same source in a Node VM. Types disappear at build time.
377
395
  */
378
- function installAcroJs(g) {
379
- const host = g;
396
+ function installAcroJs(globalObject) {
397
+ const host = globalObject;
380
398
  const NativeDate = Date;
381
399
  let state = null;
382
400
  const cloneValue = (value) => Array.isArray(value) ? value.map((item) => String(item)) : value;
383
- const sameValue = (a, b) => {
384
- if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((item, index) => item === b[index]);
385
- return a === b;
401
+ const sameValue = (left, right) => {
402
+ if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((item, index) => item === right[index]);
403
+ return left === right;
386
404
  };
387
405
  const refKey = (ref) => {
388
406
  const value = ref;
389
- return value?.kind === "objectNumber" ? `obj:${String(value.fieldObjectNumber)}` : `fqn:${String(value?.name ?? "")}`;
407
+ return value?.kind === "objectNumber" ? `obj:${String(value.objectNumber)}` : `fqn:${String(value?.name ?? "")}`;
390
408
  };
391
409
  const diagnostic = (code, message) => {
392
410
  state?.diagnostics.push({
@@ -583,19 +601,19 @@ function installAcroJs(g) {
583
601
  Number(color[2]),
584
602
  Number(color[3])
585
603
  ];
586
- const c = Number(color[1]);
587
- const m = Number(color[2]);
588
- const y = Number(color[3]);
589
- const k = Number(color[4]);
604
+ const cyan = Number(color[1]);
605
+ const magenta = Number(color[2]);
606
+ const yellow = Number(color[3]);
607
+ const black = Number(color[4]);
590
608
  return [
591
- 1 - Math.min(1, c + k),
592
- 1 - Math.min(1, m + k),
593
- 1 - Math.min(1, y + k)
609
+ 1 - Math.min(1, cyan + black),
610
+ 1 - Math.min(1, magenta + black),
611
+ 1 - Math.min(1, yellow + black)
594
612
  ];
595
613
  };
596
- const sameAnnotValue = (a, b) => {
597
- if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((item, index) => item === b[index]);
598
- return a === b;
614
+ const sameAnnotValue = (left, right) => {
615
+ if (Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((item, index) => item === right[index]);
616
+ return left === right;
599
617
  };
600
618
  const cloneAnnotValue = (value) => Array.isArray(value) ? [...value] : value;
601
619
  const makeAnnot = (record) => {
@@ -623,8 +641,8 @@ function installAcroJs(g) {
623
641
  get: () => cloneAnnotValue(record.current[key]),
624
642
  set: (value) => stage(key, value, validate)
625
643
  });
626
- const isNum = (v) => typeof v === "number" && isFinite(v);
627
- const isBool = (v) => typeof v === "boolean";
644
+ const isNum = (value) => typeof value === "number" && isFinite(value);
645
+ const isBool = (value) => typeof value === "boolean";
628
646
  Object.defineProperties(wrapper, {
629
647
  name: {
630
648
  enumerable: true,
@@ -648,12 +666,12 @@ function installAcroJs(g) {
648
666
  },
649
667
  strokeColor: prop("strokeColor", isColorArray),
650
668
  fillColor: prop("fillColor", isColorArray),
651
- opacity: prop("opacity", (v) => isNum(v) && Number(v) >= 0 && Number(v) <= 1),
652
- width: prop("width", (v) => isNum(v) && Number(v) >= 0),
653
- style: prop("borderStyle", (v) => v === "S" || v === "D"),
654
- dash: prop("dash", (v) => Array.isArray(v) && v.every(isNum)),
655
- rect: prop("rect", (v) => Array.isArray(v) && v.length === 4 && v.every(isNum)),
656
- contents: prop("contents", (v) => typeof v === "string"),
669
+ opacity: prop("opacity", (value) => isNum(value) && Number(value) >= 0 && Number(value) <= 1),
670
+ width: prop("width", (value) => isNum(value) && Number(value) >= 0),
671
+ style: prop("borderStyle", (value) => value === "S" || value === "D"),
672
+ dash: prop("dash", (value) => Array.isArray(value) && value.every(isNum)),
673
+ rect: prop("rect", (value) => Array.isArray(value) && value.length === 4 && value.every(isNum)),
674
+ contents: prop("contents", (value) => typeof value === "string"),
657
675
  hidden: prop("hidden", isBool),
658
676
  print: prop("print", isBool),
659
677
  readOnly: prop("readOnly", isBool),
@@ -871,30 +889,30 @@ function installAcroJs(g) {
871
889
  });
872
890
  const fieldsByName = new Map(fields.map((field) => [String(field.input.name), field]));
873
891
  const fieldsByRef = new Map(fields.map((field) => [refKey(field.input.ref), field]));
874
- const annots = (input.annots ?? []).map((annot) => {
892
+ const annots = (input.annots ?? []).map((annotation) => {
875
893
  const writableSnapshot = () => ({
876
- strokeColor: cloneAnnotValue(annot.strokeColor ?? ["T"]),
877
- fillColor: cloneAnnotValue(annot.fillColor ?? ["T"]),
878
- opacity: Number(annot.opacity ?? 1),
879
- width: Number(annot.width ?? 1),
880
- borderStyle: String(annot.borderStyle ?? "S"),
881
- dash: cloneAnnotValue(annot.dash ?? []),
882
- rect: cloneAnnotValue(annot.rect ?? [
894
+ strokeColor: cloneAnnotValue(annotation.strokeColor ?? ["T"]),
895
+ fillColor: cloneAnnotValue(annotation.fillColor ?? ["T"]),
896
+ opacity: Number(annotation.opacity ?? 1),
897
+ width: Number(annotation.width ?? 1),
898
+ borderStyle: String(annotation.borderStyle ?? "S"),
899
+ dash: cloneAnnotValue(annotation.dash ?? []),
900
+ rect: cloneAnnotValue(annotation.rect ?? [
883
901
  0,
884
902
  0,
885
903
  0,
886
904
  0
887
905
  ]),
888
- contents: String(annot.contents ?? ""),
889
- hidden: Boolean(annot.hidden),
890
- print: Boolean(annot.print),
891
- readOnly: Boolean(annot.readOnly),
892
- locked: Boolean(annot.locked),
893
- noView: Boolean(annot.noView),
894
- toggleNoView: Boolean(annot.toggleNoView)
906
+ contents: String(annotation.contents ?? ""),
907
+ hidden: Boolean(annotation.hidden),
908
+ print: Boolean(annotation.print),
909
+ readOnly: Boolean(annotation.readOnly),
910
+ locked: Boolean(annotation.locked),
911
+ noView: Boolean(annotation.noView),
912
+ toggleNoView: Boolean(annotation.toggleNoView)
895
913
  });
896
914
  const record = {
897
- input: annot,
915
+ input: annotation,
898
916
  original: writableSnapshot(),
899
917
  current: writableSnapshot(),
900
918
  wrapper: {}
@@ -951,22 +969,17 @@ function installAcroJs(g) {
951
969
  };
952
970
  const formValue = (field, value) => {
953
971
  const family = String(field.input.family);
954
- if (family === "text") return {
955
- type: "text",
956
- value: String(value ?? "")
957
- };
958
- if (family === "checkbox" || family === "radio") return {
959
- type: "toggle",
960
- state: value === null || value === void 0 || String(value) === "Off" ? null : String(value)
961
- };
962
- if (family === "combobox") return {
963
- type: "choice",
964
- values: Array.isArray(value) ? value.slice(0, 1).map(String) : [String(value ?? "")]
965
- };
966
- if (family === "listbox") return {
967
- type: "choice",
968
- values: Array.isArray(value) ? value.map(String) : [String(value ?? "")]
969
- };
972
+ if (family === "text") return { value: String(value ?? "") };
973
+ if (family === "checkbox" || family === "radio") return { value: value === null || value === void 0 || String(value) === "Off" ? null : String(value) };
974
+ if (family === "combobox") {
975
+ const selected = Array.isArray(value) ? value[0] : value;
976
+ const text = selected === null || selected === void 0 ? "" : String(selected);
977
+ return { value: text === "" ? null : text };
978
+ }
979
+ if (family === "listbox") {
980
+ if (Array.isArray(value)) return { selectedValues: value.map(String) };
981
+ return { selectedValues: value === null || value === void 0 || value === "" ? [] : [String(value)] };
982
+ }
970
983
  diagnostic("invalid-field-value", `field '${String(field.input.name)}' cannot accept a scripted value`);
971
984
  return null;
972
985
  };
@@ -1157,23 +1170,23 @@ function installAcroJs(g) {
1157
1170
  rgb[2]
1158
1171
  ];
1159
1172
  if (target === "CMYK") {
1160
- const k = 1 - Math.max(rgb[0], rgb[1], rgb[2]);
1173
+ const black = 1 - Math.max(rgb[0], rgb[1], rgb[2]);
1161
1174
  return [
1162
1175
  "CMYK",
1163
- 1 - rgb[0] - k,
1164
- 1 - rgb[1] - k,
1165
- 1 - rgb[2] - k,
1166
- k
1176
+ 1 - rgb[0] - black,
1177
+ 1 - rgb[1] - black,
1178
+ 1 - rgb[2] - black,
1179
+ black
1167
1180
  ];
1168
1181
  }
1169
1182
  return ["T"];
1170
1183
  },
1171
- equal: (a, b) => {
1172
- if (!isColorArray(a) || !isColorArray(b)) return false;
1173
- const left = colorToRgbTriple(a);
1174
- const right = colorToRgbTriple(b);
1175
- if (left === null || right === null) return left === right;
1176
- return left.every((component, index) => Math.abs(component - right[index]) < 1e-6);
1184
+ equal: (left, right) => {
1185
+ if (!isColorArray(left) || !isColorArray(right)) return false;
1186
+ const leftRgb = colorToRgbTriple(left);
1187
+ const rightRgb = colorToRgbTriple(right);
1188
+ if (leftRgb === null || rightRgb === null) return leftRgb === rightRgb;
1189
+ return leftRgb.every((component, index) => Math.abs(component - rightRgb[index]) < 1e-6);
1177
1190
  }
1178
1191
  });
1179
1192
  host.getField = getField;
@@ -1221,7 +1234,7 @@ function installAcroJs(g) {
1221
1234
  host.AFSimple_Calculate = (operation, names) => {
1222
1235
  const values = (Array.isArray(names) ? names : String(names).split(",")).map((name) => numberValue(getField(String(name).trim())?.value));
1223
1236
  const op = String(operation).toUpperCase();
1224
- const value = values.length === 0 ? 0 : op === "PRD" ? values.reduce((a, b) => a * b, 1) : op === "AVG" ? values.reduce((a, b) => a + b, 0) / values.length : op === "MIN" ? Math.min(...values) : op === "MAX" ? Math.max(...values) : values.reduce((a, b) => a + b, 0);
1237
+ const value = values.length === 0 ? 0 : op === "PRD" ? values.reduce((left, right) => left * right, 1) : op === "AVG" ? values.reduce((left, right) => left + right, 0) / values.length : op === "MIN" ? Math.min(...values) : op === "MAX" ? Math.max(...values) : values.reduce((left, right) => left + right, 0);
1225
1238
  if (state) state.event.value = value;
1226
1239
  };
1227
1240
  host.AFNumber_Format = (decimals, separatorStyle, _negativeStyle, _currencyStyle, currency, prepend) => {
@@ -1274,12 +1287,12 @@ function installAcroJs(g) {
1274
1287
  const formatMismatch = () => rejectWithAlert(`The value entered does not match the format of the field [ ${String(state?.event.targetName ?? "")} ]`);
1275
1288
  const dateMismatch = (format) => rejectWithAlert(`Invalid date/time: please ensure that the date/time exists. Field [ ${String(state?.event.targetName ?? "")} ] should match format ${String(format ?? "")}`);
1276
1289
  const mergeChange = (eventObject) => {
1277
- const e = eventObject ?? state?.event ?? {};
1278
- const value = String(e.value ?? "");
1279
- if (e.willCommit) return value;
1280
- const start = Math.max(0, Math.trunc(Number(e.selStart ?? 0)));
1281
- const end = Math.max(start, Math.trunc(Number(e.selEnd ?? 0)));
1282
- return value.slice(0, start) + String(e.change ?? "") + value.slice(end);
1290
+ const event = eventObject ?? state?.event ?? {};
1291
+ const value = String(event.value ?? "");
1292
+ if (event.willCommit) return value;
1293
+ const start = Math.max(0, Math.trunc(Number(event.selStart ?? 0)));
1294
+ const end = Math.max(start, Math.trunc(Number(event.selEnd ?? 0)));
1295
+ return value.slice(0, start) + String(event.change ?? "") + value.slice(end);
1283
1296
  };
1284
1297
  host.AFMergeChange = mergeChange;
1285
1298
  const makeNumber = (value) => {
@@ -1374,17 +1387,17 @@ function installAcroJs(g) {
1374
1387
  break;
1375
1388
  }
1376
1389
  const timeMatch = /(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(am|pm)?/i.exec(text);
1377
- let H = 0;
1378
- let M = 0;
1379
- let S = 0;
1390
+ let hours = 0;
1391
+ let minutes = 0;
1392
+ let seconds = 0;
1380
1393
  if (timeMatch) {
1381
- H = Number(timeMatch[1]);
1382
- M = Number(timeMatch[2]);
1383
- S = Number(timeMatch[3] ?? 0);
1394
+ hours = Number(timeMatch[1]);
1395
+ minutes = Number(timeMatch[2]);
1396
+ seconds = Number(timeMatch[3] ?? 0);
1384
1397
  const marker = String(timeMatch[4] ?? "").toLowerCase();
1385
- if (marker === "pm" && H < 12) H += 12;
1386
- if (marker === "am" && H === 12) H = 0;
1387
- if (H > 23 || M > 59 || S > 59) return null;
1398
+ if (marker === "pm" && hours < 12) hours += 12;
1399
+ if (marker === "am" && hours === 12) hours = 0;
1400
+ if (hours > 23 || minutes > 59 || seconds > 59) return null;
1388
1401
  }
1389
1402
  const numbers = ((timeMatch ? text.slice(0, timeMatch.index) + text.slice(timeMatch.index + timeMatch[0].length) : text).match(/\d+/g) ?? []).map((token) => Number(token));
1390
1403
  const order = [];
@@ -1392,35 +1405,35 @@ function installAcroJs(g) {
1392
1405
  const kind = token[0] === "y" ? "y" : token[0] === "m" ? "m" : "d";
1393
1406
  if (order.indexOf(kind) < 0) order.push(kind);
1394
1407
  }
1395
- let y = null;
1396
- let m = namedMonth;
1397
- let d = null;
1408
+ let year = null;
1409
+ let month = namedMonth;
1410
+ let day = null;
1398
1411
  let cursor = 0;
1399
1412
  for (const kind of order) {
1400
1413
  if (kind === "m" && namedMonth !== null) continue;
1401
1414
  if (cursor >= numbers.length) break;
1402
1415
  const value = numbers[cursor];
1403
1416
  cursor += 1;
1404
- if (kind === "y") y = value;
1405
- else if (kind === "m") m = value;
1406
- else d = value;
1417
+ if (kind === "y") year = value;
1418
+ else if (kind === "m") month = value;
1419
+ else day = value;
1407
1420
  }
1408
- if (y === null && m === null && d === null && !timeMatch) return null;
1421
+ if (year === null && month === null && day === null && !timeMatch) return null;
1409
1422
  const today = nowWall();
1410
- if (y === null) y = today.y;
1411
- else if (y < 100) y = y < 50 ? 2e3 + y : 1900 + y;
1412
- if (m === null) m = today.m;
1413
- if (d === null) d = 1;
1414
- if (m < 1 || m > 12 || d < 1 || d > 31) return null;
1415
- const probe = new NativeDate(NativeDate.UTC(y, m - 1, d));
1416
- if (probe.getUTCFullYear() !== y || probe.getUTCMonth() !== m - 1 || probe.getUTCDate() !== d) return null;
1423
+ if (year === null) year = today.y;
1424
+ else if (year < 100) year = year < 50 ? 2e3 + year : 1900 + year;
1425
+ if (month === null) month = today.m;
1426
+ if (day === null) day = 1;
1427
+ if (month < 1 || month > 12 || day < 1 || day > 31) return null;
1428
+ const probe = new NativeDate(NativeDate.UTC(year, month - 1, day));
1429
+ if (probe.getUTCFullYear() !== year || probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return null;
1417
1430
  return {
1418
- y,
1419
- m,
1420
- d,
1421
- H,
1422
- M,
1423
- S
1431
+ y: year,
1432
+ m: month,
1433
+ d: day,
1434
+ H: hours,
1435
+ M: minutes,
1436
+ S: seconds
1424
1437
  };
1425
1438
  };
1426
1439
  const afDateFormatEx = (format) => {