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