@embedpdf/plugin-form 3.0.0-next.14 → 3.0.0-next.16
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/contract/host.cjs +29 -2
- package/dist/contract/host.cjs.map +1 -0
- package/dist/contract/host.d.cts +3 -2
- package/dist/contract/host.d.ts +3 -2
- package/dist/contract/host.js +14 -2
- package/dist/contract/host.js.map +1 -0
- package/dist/contract-BwcgtCOD.d.ts +566 -0
- package/dist/contract-CU0jrI_D.cjs +56 -0
- package/dist/contract-CU0jrI_D.cjs.map +1 -0
- package/dist/contract-D-yQxw3H.d.cts +566 -0
- package/dist/contract-DcncvPcF.js +45 -0
- package/dist/contract-DcncvPcF.js.map +1 -0
- package/dist/contract.cjs +17 -6
- package/dist/contract.d.cts +2 -8
- package/dist/contract.d.ts +2 -8
- package/dist/contract.js +2 -7
- package/dist/controller-CS6tV2m1.cjs +514 -0
- package/dist/controller-CS6tV2m1.cjs.map +1 -0
- package/dist/controller-Sr5M_8G8.js +497 -0
- package/dist/controller-Sr5M_8G8.js.map +1 -0
- package/dist/host-contract-1epLfK1S.d.cts +87 -0
- package/dist/host-contract-BRYUbpT_.d.ts +87 -0
- package/dist/index.cjs +2917 -1205
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -17
- package/dist/index.d.ts +46 -17
- package/dist/index.js +2896 -1206
- package/dist/index.js.map +1 -1
- package/dist/scripting.cjs +3 -484
- package/dist/scripting.d.cts +58 -22
- package/dist/scripting.d.ts +58 -22
- package/dist/scripting.js +1 -482
- package/package.json +15 -14
- package/dist/contract.cjs.map +0 -1
- package/dist/contract.js.map +0 -1
- package/dist/internal.cjs +0 -4
- package/dist/internal.d.cts +0 -3
- package/dist/internal.d.ts +0 -3
- package/dist/internal.js +0 -3
- package/dist/scripting.cjs.map +0 -1
- package/dist/scripting.js.map +0 -1
- package/dist/types-Bpc32Qmx.d.cts +0 -309
- package/dist/types-C1uIIYfn.cjs +0 -16
- package/dist/types-C1uIIYfn.cjs.map +0 -1
- package/dist/types-DRirhZKr.d.ts +0 -309
- package/dist/types-DkQmmcTG.js +0 -12
- package/dist/types-DkQmmcTG.js.map +0 -1
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { DEFAULT_SCRIPT_BUDGET, javaScriptProgramFromActionTree, scriptFieldsFromSnapshot } from "@embedpdf/core-acrojs";
|
|
2
|
+
//#region src/scripting/overlay.ts
|
|
3
|
+
const refKey = (ref) => ref.kind === "objectNumber" ? `obj:${ref.objectNumber}` : `fqn:${ref.name}`;
|
|
4
|
+
const sameRef = (left, right) => refKey(left) === refKey(right);
|
|
5
|
+
const cloneValue = (value) => Array.isArray(value) ? [...value] : value;
|
|
6
|
+
const sameValue = (left, right) => Array.isArray(left) && Array.isArray(right) ? left.length === right.length && left.every((value, index) => value === right[index]) : left === right;
|
|
7
|
+
function cloneFields(fields) {
|
|
8
|
+
return fields.map((field) => ({
|
|
9
|
+
...field,
|
|
10
|
+
ref: { ...field.ref },
|
|
11
|
+
value: cloneValue(field.value),
|
|
12
|
+
defaultValue: cloneValue(field.defaultValue),
|
|
13
|
+
...field.options ? { options: field.options.map((option) => ({ ...option })) } : {}
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
function fieldByRef(fields, ref) {
|
|
17
|
+
return fields.find((field) => sameRef(field.ref, ref));
|
|
18
|
+
}
|
|
19
|
+
/** The field a ref names: by its own ref, or by full name for `toFieldRef(name)`. */
|
|
20
|
+
function snapshotField(snapshot, ref) {
|
|
21
|
+
return snapshot.fields.find((field) => sameRef(field.ref, ref) || ref.kind === "fqn" && field.name === ref.name);
|
|
22
|
+
}
|
|
23
|
+
/** A written value as scripts see it: a toggle's export value (`'Off'` when clear). */
|
|
24
|
+
function scriptValueFromFormValue(field, value) {
|
|
25
|
+
switch (field.family) {
|
|
26
|
+
case "text":
|
|
27
|
+
case "combobox":
|
|
28
|
+
if ("value" in value) return value.value ?? "";
|
|
29
|
+
break;
|
|
30
|
+
case "radio":
|
|
31
|
+
if ("value" in value) return value.value ?? "Off";
|
|
32
|
+
break;
|
|
33
|
+
case "checkbox":
|
|
34
|
+
if ("value" in value) return value.value ?? "Off";
|
|
35
|
+
if ("checked" in value) {
|
|
36
|
+
if (!value.checked) return "Off";
|
|
37
|
+
const first = field.exportValues?.[0];
|
|
38
|
+
if (first === void 0) throw new Error(`Form field '${field.name}' has no widget to check`);
|
|
39
|
+
return first;
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case "listbox":
|
|
43
|
+
if ("selectedValues" in value) return [...value.selectedValues];
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Form field '${field.name}' (${field.family}) does not take this value`);
|
|
47
|
+
}
|
|
48
|
+
/** A script value as the engine writes it, or null for a family that holds none. */
|
|
49
|
+
function formValueFromScriptValue(field) {
|
|
50
|
+
const value = field.value;
|
|
51
|
+
switch (field.family) {
|
|
52
|
+
case "text": return { value: String(value ?? "") };
|
|
53
|
+
case "checkbox":
|
|
54
|
+
case "radio": return { value: value === null || String(value) === "Off" ? null : String(value) };
|
|
55
|
+
case "combobox": {
|
|
56
|
+
const selected = Array.isArray(value) ? value[0] : value;
|
|
57
|
+
const text = selected === null || selected === void 0 ? "" : String(selected);
|
|
58
|
+
return { value: text === "" ? null : text };
|
|
59
|
+
}
|
|
60
|
+
case "listbox":
|
|
61
|
+
if (Array.isArray(value)) return { selectedValues: [...value] };
|
|
62
|
+
return { selectedValues: value === null || value === "" ? [] : [String(value)] };
|
|
63
|
+
default: return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function applyEffects(overlay, effects) {
|
|
67
|
+
for (const effect of effects) {
|
|
68
|
+
if (effect.kind === "reset") {
|
|
69
|
+
for (const ref of effect.refs) {
|
|
70
|
+
const field = fieldByRef(overlay.fields, ref);
|
|
71
|
+
if (!field) continue;
|
|
72
|
+
field.value = cloneValue(field.defaultValue);
|
|
73
|
+
overlay.resetKeys.add(refKey(ref));
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const field = fieldByRef(overlay.fields, effect.ref);
|
|
78
|
+
if (!field) continue;
|
|
79
|
+
const key = refKey(effect.ref);
|
|
80
|
+
if (effect.kind === "setValue") {
|
|
81
|
+
field.value = scriptValueFromFormValue(field, effect.value);
|
|
82
|
+
overlay.resetKeys.delete(key);
|
|
83
|
+
} else if (effect.kind === "setDisplay") field.display = effect.display;
|
|
84
|
+
else overlay.appearances.set(key, {
|
|
85
|
+
ref: effect.ref,
|
|
86
|
+
text: effect.text
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function canonicalEffects(overlay) {
|
|
91
|
+
const effects = [];
|
|
92
|
+
const resets = [];
|
|
93
|
+
for (const field of overlay.fields) {
|
|
94
|
+
const original = fieldByRef(overlay.original, field.ref);
|
|
95
|
+
if (!original) continue;
|
|
96
|
+
if (!sameValue(original.value, field.value)) {
|
|
97
|
+
const key = refKey(field.ref);
|
|
98
|
+
if (overlay.resetKeys.has(key) && sameValue(field.value, field.defaultValue)) resets.push(field.ref);
|
|
99
|
+
else {
|
|
100
|
+
const value = formValueFromScriptValue(field);
|
|
101
|
+
if (value) effects.push({
|
|
102
|
+
kind: "setValue",
|
|
103
|
+
ref: field.ref,
|
|
104
|
+
value
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (original.display !== field.display) effects.push({
|
|
109
|
+
kind: "setDisplay",
|
|
110
|
+
ref: field.ref,
|
|
111
|
+
display: field.display
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (resets.length > 0) effects.unshift({
|
|
115
|
+
kind: "reset",
|
|
116
|
+
refs: resets
|
|
117
|
+
});
|
|
118
|
+
effects.push(...Array.from(overlay.appearances.values(), ({ ref, text }) => ({
|
|
119
|
+
kind: "setAppearanceText",
|
|
120
|
+
ref,
|
|
121
|
+
text
|
|
122
|
+
})));
|
|
123
|
+
return effects;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/scripting/results.ts
|
|
127
|
+
function scriptError(message) {
|
|
128
|
+
return {
|
|
129
|
+
kind: "invalid-output",
|
|
130
|
+
message
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function utf8Length(value) {
|
|
134
|
+
let length = 0;
|
|
135
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
136
|
+
const code = value.charCodeAt(index);
|
|
137
|
+
if (code < 128) length += 1;
|
|
138
|
+
else if (code < 2048) length += 2;
|
|
139
|
+
else if (code >= 55296 && code <= 56319 && index + 1 < value.length) {
|
|
140
|
+
length += 4;
|
|
141
|
+
index += 1;
|
|
142
|
+
} else length += 3;
|
|
143
|
+
}
|
|
144
|
+
return length;
|
|
145
|
+
}
|
|
146
|
+
function pageNumberFor(meta, field) {
|
|
147
|
+
const placed = field.widgets.find((widget) => widget.page !== null)?.page;
|
|
148
|
+
if (!meta || !placed) return 0;
|
|
149
|
+
const index = meta.pages.findIndex((page) => page.ref.objectNumber === placed.objectNumber);
|
|
150
|
+
return Math.max(0, index);
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/scripting/controller.ts
|
|
154
|
+
var FormScriptingController = class {
|
|
155
|
+
constructor(options) {
|
|
156
|
+
this.options = options;
|
|
157
|
+
this.disposed = false;
|
|
158
|
+
this.transactionBudget = options.budget;
|
|
159
|
+
}
|
|
160
|
+
/** The realm belongs to its owner (the actions plugin, or the caller that
|
|
161
|
+
* minted a detached one) — this only fences further transactions. */
|
|
162
|
+
dispose() {
|
|
163
|
+
this.disposed = true;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* One realm transaction: `body` runs its scripts and writes what they changed before the
|
|
167
|
+
* next transaction starts, so that one reads the form with it.
|
|
168
|
+
*/
|
|
169
|
+
transaction(body) {
|
|
170
|
+
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Form scripting controller is disposed"));
|
|
171
|
+
return this.options.transaction((txn) => body({
|
|
172
|
+
commit: (form, ref, value) => this.run(txn, form, ref, value),
|
|
173
|
+
activate: (form, ref, action) => this.run(txn, form, ref, void 0, action),
|
|
174
|
+
recalculate: (form) => this.run(txn, form, void 0)
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Load the realm before the first write needs it: the scripts' engine starts, and the
|
|
179
|
+
* document's open scripts run, their effects kept for the first write as without warming.
|
|
180
|
+
* Nothing is written.
|
|
181
|
+
*/
|
|
182
|
+
async warm(form) {
|
|
183
|
+
if (this.disposed) return;
|
|
184
|
+
const budget = this.transactionBudget ?? DEFAULT_SCRIPT_BUDGET;
|
|
185
|
+
await this.options.transaction((txn) => txn.run("", {
|
|
186
|
+
fields: scriptFieldsFromSnapshot(form),
|
|
187
|
+
event: { kind: "name-tree-boot" }
|
|
188
|
+
}, budget));
|
|
189
|
+
}
|
|
190
|
+
/** {@link FormScripts.commit} in a transaction of its own. */
|
|
191
|
+
commit(form, ref, value) {
|
|
192
|
+
return this.transaction((scripts) => scripts.commit(form, ref, value));
|
|
193
|
+
}
|
|
194
|
+
/** {@link FormScripts.activate} in a transaction of its own. */
|
|
195
|
+
activate(form, ref, action) {
|
|
196
|
+
return this.transaction((scripts) => scripts.activate(form, ref, action));
|
|
197
|
+
}
|
|
198
|
+
/** {@link FormScripts.recalculate} in a transaction of its own. */
|
|
199
|
+
recalculate(form) {
|
|
200
|
+
return this.transaction((scripts) => scripts.recalculate(form));
|
|
201
|
+
}
|
|
202
|
+
async run(txn, snapshot, refInput, proposed, activation) {
|
|
203
|
+
const asked = refInput ?? snapshot.calculationOrder.find((candidate) => candidate !== null && snapshotField(snapshot, candidate) !== void 0) ?? snapshot.fields[0]?.ref;
|
|
204
|
+
if (!asked) return {
|
|
205
|
+
status: "accepted",
|
|
206
|
+
effects: [],
|
|
207
|
+
bootEffects: [],
|
|
208
|
+
uiEffects: [],
|
|
209
|
+
diagnostics: []
|
|
210
|
+
};
|
|
211
|
+
const target = snapshotField(snapshot, asked);
|
|
212
|
+
if (!target) return this.failed([], [], scriptError(`Form field '${refKey(asked)}' no longer exists`));
|
|
213
|
+
const ref = target.ref;
|
|
214
|
+
const original = scriptFieldsFromSnapshot(snapshot);
|
|
215
|
+
const overlay = {
|
|
216
|
+
original,
|
|
217
|
+
fields: cloneFields(original),
|
|
218
|
+
resetKeys: /* @__PURE__ */ new Set(),
|
|
219
|
+
appearances: /* @__PURE__ */ new Map()
|
|
220
|
+
};
|
|
221
|
+
const targetInput = fieldByRef(overlay.fields, ref);
|
|
222
|
+
if (!targetInput) return this.failed([], [], scriptError(`Form field '${refKey(ref)}' has no script view`));
|
|
223
|
+
const hasProposedValue = proposed !== void 0;
|
|
224
|
+
let proposedValue = cloneValue(targetInput.value);
|
|
225
|
+
if (proposed) try {
|
|
226
|
+
proposedValue = scriptValueFromFormValue(targetInput, proposed);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
return this.failed([], [], scriptError(error instanceof Error ? error.message : String(error)));
|
|
229
|
+
}
|
|
230
|
+
const uiEffects = [];
|
|
231
|
+
const diagnostics = [];
|
|
232
|
+
const pageNumber = pageNumberFor(this.options.document(), target);
|
|
233
|
+
const budget = this.transactionBudget ?? DEFAULT_SCRIPT_BUDGET;
|
|
234
|
+
let executionMs = 0;
|
|
235
|
+
let aggregateOutputSize = 0;
|
|
236
|
+
const consume = (output, phase) => {
|
|
237
|
+
aggregateOutputSize += utf8Length(JSON.stringify(output));
|
|
238
|
+
uiEffects.push(...output.uiEffects.map((effect) => ({
|
|
239
|
+
...effect,
|
|
240
|
+
phase
|
|
241
|
+
})));
|
|
242
|
+
diagnostics.push(...output.diagnostics);
|
|
243
|
+
if (aggregateOutputSize > budget.maxOutputBytes) return {
|
|
244
|
+
kind: "budget",
|
|
245
|
+
message: `Form script transaction output exceeded ${budget.maxOutputBytes} bytes`
|
|
246
|
+
};
|
|
247
|
+
return output.error ?? null;
|
|
248
|
+
};
|
|
249
|
+
const remainingBudget = () => {
|
|
250
|
+
const remaining = budget.maxExecutionMs - executionMs;
|
|
251
|
+
return remaining <= 0 ? null : {
|
|
252
|
+
...budget,
|
|
253
|
+
maxExecutionMs: remaining
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
const world = (fields, event) => ({
|
|
257
|
+
fields,
|
|
258
|
+
pageNumber,
|
|
259
|
+
event
|
|
260
|
+
});
|
|
261
|
+
const run = async (source, input) => {
|
|
262
|
+
const remaining = remainingBudget();
|
|
263
|
+
if (!remaining) return { error: {
|
|
264
|
+
kind: "budget",
|
|
265
|
+
message: "Form script transaction timed out"
|
|
266
|
+
} };
|
|
267
|
+
let output;
|
|
268
|
+
try {
|
|
269
|
+
const startedAt = Date.now();
|
|
270
|
+
output = await txn.run(source, input, remaining);
|
|
271
|
+
executionMs += Date.now() - startedAt;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
return { error: scriptError(error instanceof Error ? error.message : String(error)) };
|
|
274
|
+
}
|
|
275
|
+
const error = consume(output, "user");
|
|
276
|
+
return error ? { error } : { output };
|
|
277
|
+
};
|
|
278
|
+
{
|
|
279
|
+
let boot = null;
|
|
280
|
+
try {
|
|
281
|
+
boot = await txn.boot(world(overlay.fields, { kind: "name-tree-boot" }), budget);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
diagnostics.push({
|
|
284
|
+
code: "script-error",
|
|
285
|
+
message: `Document boot script failed (continuing without it): ${error instanceof Error ? error.message : String(error)}`
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (boot) {
|
|
289
|
+
const bootError = consume(boot, "boot");
|
|
290
|
+
if (!bootError) applyEffects(overlay, boot.formEffects);
|
|
291
|
+
else diagnostics.push({
|
|
292
|
+
code: "script-error",
|
|
293
|
+
message: `Document boot script failed (continuing without it): ${bootError.message}`
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const bootEffects = canonicalEffects(overlay);
|
|
298
|
+
if (activation) {
|
|
299
|
+
let program;
|
|
300
|
+
try {
|
|
301
|
+
program = javaScriptProgramFromActionTree(activation);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
304
|
+
}
|
|
305
|
+
if (program) {
|
|
306
|
+
const activated = await run(program, world(overlay.fields, {
|
|
307
|
+
kind: "widget-activate",
|
|
308
|
+
target: ref,
|
|
309
|
+
source: ref,
|
|
310
|
+
value: targetInput.value
|
|
311
|
+
}));
|
|
312
|
+
if (activated.error) return this.failed(uiEffects, diagnostics, activated.error);
|
|
313
|
+
applyEffects(overlay, activated.output.formEffects);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!activation && hasProposedValue && target.actions?.keystroke && target.family === "text") {
|
|
317
|
+
let program;
|
|
318
|
+
try {
|
|
319
|
+
program = javaScriptProgramFromActionTree(target.actions.keystroke);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
322
|
+
}
|
|
323
|
+
if (program) {
|
|
324
|
+
const oldValue = String(targetInput.value ?? "");
|
|
325
|
+
const typing = await run(program, world(overlay.fields, {
|
|
326
|
+
kind: "field-keystroke",
|
|
327
|
+
target: ref,
|
|
328
|
+
source: ref,
|
|
329
|
+
value: oldValue,
|
|
330
|
+
change: String(proposedValue ?? ""),
|
|
331
|
+
selStart: 0,
|
|
332
|
+
selEnd: oldValue.length,
|
|
333
|
+
willCommit: false
|
|
334
|
+
}));
|
|
335
|
+
if (typing.error && typing.error.kind !== "exception") return this.failed(uiEffects, diagnostics, typing.error);
|
|
336
|
+
if (typing.error) diagnostics.push({
|
|
337
|
+
code: "script-error",
|
|
338
|
+
message: `Keystroke script failed (kept the typed value): ${typing.error.message}`
|
|
339
|
+
});
|
|
340
|
+
else {
|
|
341
|
+
if (!typing.output.event.rc) return this.rejected(bootEffects, uiEffects, diagnostics);
|
|
342
|
+
applyEffects(overlay, typing.output.formEffects);
|
|
343
|
+
const event = typing.output.event;
|
|
344
|
+
const base = String(event.value ?? "");
|
|
345
|
+
proposedValue = base.slice(0, event.selStart) + event.change + base.slice(Math.max(event.selStart, event.selEnd));
|
|
346
|
+
}
|
|
347
|
+
const commit = await run(program, world(overlay.fields, {
|
|
348
|
+
kind: "field-keystroke",
|
|
349
|
+
target: ref,
|
|
350
|
+
source: ref,
|
|
351
|
+
value: proposedValue,
|
|
352
|
+
change: "",
|
|
353
|
+
selStart: 0,
|
|
354
|
+
selEnd: 0,
|
|
355
|
+
willCommit: true
|
|
356
|
+
}));
|
|
357
|
+
if (commit.error && commit.error.kind !== "exception") return this.failed(uiEffects, diagnostics, commit.error);
|
|
358
|
+
if (commit.error) diagnostics.push({
|
|
359
|
+
code: "script-error",
|
|
360
|
+
message: `Keystroke commit script failed (kept the typed value): ${commit.error.message}`
|
|
361
|
+
});
|
|
362
|
+
else {
|
|
363
|
+
if (!commit.output.event.rc) return this.rejected(bootEffects, uiEffects, diagnostics);
|
|
364
|
+
applyEffects(overlay, commit.output.formEffects);
|
|
365
|
+
proposedValue = cloneValue(commit.output.event.value);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (!activation && hasProposedValue) {
|
|
370
|
+
targetInput.value = cloneValue(proposedValue);
|
|
371
|
+
overlay.resetKeys.delete(refKey(ref));
|
|
372
|
+
}
|
|
373
|
+
if (!activation && hasProposedValue && target.actions?.validate) {
|
|
374
|
+
let program;
|
|
375
|
+
try {
|
|
376
|
+
program = javaScriptProgramFromActionTree(target.actions.validate);
|
|
377
|
+
} catch (error) {
|
|
378
|
+
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
379
|
+
}
|
|
380
|
+
if (program) {
|
|
381
|
+
const validation = await run(program, world(overlay.fields, {
|
|
382
|
+
kind: "field-validate",
|
|
383
|
+
target: ref,
|
|
384
|
+
source: ref,
|
|
385
|
+
value: proposedValue,
|
|
386
|
+
willCommit: true
|
|
387
|
+
}));
|
|
388
|
+
if (validation.error && validation.error.kind !== "exception") return this.failed(uiEffects, diagnostics, validation.error);
|
|
389
|
+
if (validation.error) diagnostics.push({
|
|
390
|
+
code: "script-error",
|
|
391
|
+
message: `Validate script failed (accepted the value): ${validation.error.message}`
|
|
392
|
+
});
|
|
393
|
+
else {
|
|
394
|
+
if (!validation.output.event.rc) return this.rejected(bootEffects, uiEffects, diagnostics);
|
|
395
|
+
targetInput.value = cloneValue(validation.output.event.value);
|
|
396
|
+
applyEffects(overlay, validation.output.formEffects);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const formatRefs = !activation && hasProposedValue ? [ref] : [];
|
|
401
|
+
for (const calculationRef of activation ? [] : snapshot.calculationOrder) {
|
|
402
|
+
if (!calculationRef) continue;
|
|
403
|
+
const calculated = snapshotField(snapshot, calculationRef);
|
|
404
|
+
const tree = calculated?.actions?.calculate;
|
|
405
|
+
if (!calculated || !tree) continue;
|
|
406
|
+
let program;
|
|
407
|
+
try {
|
|
408
|
+
program = javaScriptProgramFromActionTree(tree);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
411
|
+
}
|
|
412
|
+
const calculatedInput = fieldByRef(overlay.fields, calculationRef);
|
|
413
|
+
if (program && calculatedInput) {
|
|
414
|
+
const calculation = await run(program, world(overlay.fields, {
|
|
415
|
+
kind: "field-calculate",
|
|
416
|
+
target: calculationRef,
|
|
417
|
+
...hasProposedValue ? { source: ref } : {},
|
|
418
|
+
value: calculatedInput.value
|
|
419
|
+
}));
|
|
420
|
+
if (calculation.error && calculation.error.kind !== "exception") return this.failed(uiEffects, diagnostics, calculation.error);
|
|
421
|
+
if (calculation.error) diagnostics.push({
|
|
422
|
+
code: "script-error",
|
|
423
|
+
message: `Calculate script failed (field left unchanged): ${calculation.error.message}`
|
|
424
|
+
});
|
|
425
|
+
else applyEffects(overlay, calculation.output.formEffects);
|
|
426
|
+
}
|
|
427
|
+
if (!formatRefs.some((candidate) => sameRef(candidate, calculationRef))) formatRefs.push(calculationRef);
|
|
428
|
+
}
|
|
429
|
+
for (const formatRef of formatRefs) {
|
|
430
|
+
const field = snapshotField(snapshot, formatRef);
|
|
431
|
+
const tree = field?.actions?.format;
|
|
432
|
+
const overlayField = fieldByRef(overlay.fields, formatRef);
|
|
433
|
+
if (!field || !tree || !overlayField) continue;
|
|
434
|
+
let program;
|
|
435
|
+
try {
|
|
436
|
+
program = javaScriptProgramFromActionTree(tree);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
return this.failed(uiEffects, diagnostics, scriptError(error instanceof Error ? error.message : String(error)));
|
|
439
|
+
}
|
|
440
|
+
if (!program) continue;
|
|
441
|
+
const format = await run(program, world(overlay.fields, {
|
|
442
|
+
kind: "field-format",
|
|
443
|
+
target: formatRef,
|
|
444
|
+
...hasProposedValue ? { source: ref } : {},
|
|
445
|
+
value: overlayField.value
|
|
446
|
+
}));
|
|
447
|
+
if (format.error && format.error.kind !== "exception") return this.failed(uiEffects, diagnostics, format.error);
|
|
448
|
+
if (format.error) {
|
|
449
|
+
diagnostics.push({
|
|
450
|
+
code: "script-error",
|
|
451
|
+
message: `Format script failed (kept the raw value): ${format.error.message}`
|
|
452
|
+
});
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
applyEffects(overlay, format.output.formEffects);
|
|
456
|
+
}
|
|
457
|
+
const effects = canonicalEffects(overlay);
|
|
458
|
+
if (effects.length + uiEffects.length > budget.maxEffects) return this.failed(uiEffects, diagnostics, {
|
|
459
|
+
kind: "budget",
|
|
460
|
+
message: `Form script transaction exceeded ${budget.maxEffects} effects`
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
status: "accepted",
|
|
464
|
+
effects,
|
|
465
|
+
bootEffects,
|
|
466
|
+
uiEffects,
|
|
467
|
+
diagnostics
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
failed(uiEffects, diagnostics, error) {
|
|
471
|
+
return {
|
|
472
|
+
status: "failed",
|
|
473
|
+
effects: [],
|
|
474
|
+
bootEffects: [],
|
|
475
|
+
uiEffects,
|
|
476
|
+
diagnostics,
|
|
477
|
+
error
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
/** The value is refused; the open scripts' effects are still written. */
|
|
481
|
+
rejected(bootEffects, uiEffects, diagnostics) {
|
|
482
|
+
return {
|
|
483
|
+
status: "rejected",
|
|
484
|
+
effects: [],
|
|
485
|
+
bootEffects,
|
|
486
|
+
uiEffects,
|
|
487
|
+
diagnostics
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
function createFormScriptingController(options) {
|
|
492
|
+
return new FormScriptingController(options);
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
495
|
+
export { createFormScriptingController as n, scriptError as r, FormScriptingController as t };
|
|
496
|
+
|
|
497
|
+
//# sourceMappingURL=controller-Sr5M_8G8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller-Sr5M_8G8.js","names":[],"sources":["../src/scripting/overlay.ts","../src/scripting/results.ts","../src/scripting/controller.ts"],"sourcesContent":["/**\n * The transaction overlay: a working copy of the script field view that\n * every pass mutates, and the canonical effect list diffed from it at the\n * end (values, display, appearance text; resets folded back to one effect).\n */\nimport type { ScriptFieldInput, ScriptValue } from '@embedpdf/core-acrojs';\nimport type {\n FormEffect,\n FormFieldDTO,\n FormFieldRef,\n FormFieldValue,\n FormSnapshot,\n} from '@embedpdf/engine-core/runtime';\n\nexport interface Overlay {\n original: ScriptFieldInput[];\n fields: ScriptFieldInput[];\n resetKeys: Set<string>;\n appearances: Map<string, { ref: FormFieldRef; text: string }>;\n}\n\nexport const refKey = (ref: FormFieldRef): string =>\n ref.kind === 'objectNumber' ? `obj:${ref.objectNumber}` : `fqn:${ref.name}`;\n\nexport const sameRef = (left: FormFieldRef, right: FormFieldRef): boolean =>\n refKey(left) === refKey(right);\n\nexport const cloneValue = (value: ScriptValue): ScriptValue =>\n Array.isArray(value) ? [...value] : value;\n\nexport const sameValue = (left: ScriptValue, right: ScriptValue): boolean =>\n Array.isArray(left) && Array.isArray(right)\n ? left.length === right.length && left.every((value, index) => value === right[index])\n : left === right;\n\nexport function cloneFields(fields: ScriptFieldInput[]): ScriptFieldInput[] {\n return fields.map((field) => ({\n ...field,\n ref: { ...field.ref },\n value: cloneValue(field.value),\n defaultValue: cloneValue(field.defaultValue),\n ...(field.options ? { options: field.options.map((option) => ({ ...option })) } : {}),\n }));\n}\n\nexport function fieldByRef<T extends { ref: FormFieldRef }>(\n fields: T[],\n ref: FormFieldRef,\n): T | undefined {\n return fields.find((field) => sameRef(field.ref, ref));\n}\n\n/** The field a ref names: by its own ref, or by full name for `toFieldRef(name)`. */\nexport function snapshotField(snapshot: FormSnapshot, ref: FormFieldRef): FormFieldDTO | undefined {\n return snapshot.fields.find(\n (field) => sameRef(field.ref, ref) || (ref.kind === 'fqn' && field.name === ref.name),\n );\n}\n\n/** A written value as scripts see it: a toggle's export value (`'Off'` when clear). */\nexport function scriptValueFromFormValue(\n field: ScriptFieldInput,\n value: FormFieldValue,\n): ScriptValue {\n switch (field.family) {\n case 'text':\n case 'combobox':\n if ('value' in value) return value.value ?? '';\n break;\n case 'radio':\n if ('value' in value) return value.value ?? 'Off';\n break;\n case 'checkbox':\n if ('value' in value) return value.value ?? 'Off';\n if ('checked' in value) {\n if (!value.checked) return 'Off';\n const first = field.exportValues?.[0];\n if (first === undefined)\n throw new Error(`Form field '${field.name}' has no widget to check`);\n return first;\n }\n break;\n case 'listbox':\n if ('selectedValues' in value) return [...value.selectedValues];\n break;\n }\n throw new Error(`Form field '${field.name}' (${field.family}) does not take this value`);\n}\n\n/** A script value as the engine writes it, or null for a family that holds none. */\nexport function formValueFromScriptValue(field: ScriptFieldInput): FormFieldValue | null {\n const value = field.value;\n switch (field.family) {\n case 'text':\n return { value: String(value ?? '') };\n case 'checkbox':\n case 'radio':\n return { value: value === null || String(value) === 'Off' ? null : String(value) };\n case 'combobox': {\n const selected = Array.isArray(value) ? value[0] : value;\n const text = selected === null || selected === undefined ? '' : String(selected);\n return { value: text === '' ? null : text };\n }\n case 'listbox':\n if (Array.isArray(value)) return { selectedValues: [...value] };\n return { selectedValues: value === null || value === '' ? [] : [String(value)] };\n default:\n return null;\n }\n}\n\nexport function applyEffects(overlay: Overlay, effects: FormEffect[]): void {\n for (const effect of effects) {\n if (effect.kind === 'reset') {\n for (const ref of effect.refs) {\n const field = fieldByRef(overlay.fields, ref);\n if (!field) continue;\n field.value = cloneValue(field.defaultValue);\n overlay.resetKeys.add(refKey(ref));\n }\n continue;\n }\n\n const field = fieldByRef(overlay.fields, effect.ref);\n if (!field) continue;\n const key = refKey(effect.ref);\n if (effect.kind === 'setValue') {\n field.value = scriptValueFromFormValue(field, effect.value);\n overlay.resetKeys.delete(key);\n } else if (effect.kind === 'setDisplay') {\n field.display = effect.display;\n } else {\n overlay.appearances.set(key, { ref: effect.ref, text: effect.text });\n }\n }\n}\n\nexport function canonicalEffects(overlay: Overlay): FormEffect[] {\n const effects: FormEffect[] = [];\n const resets: FormFieldRef[] = [];\n for (const field of overlay.fields) {\n const original = fieldByRef(overlay.original, field.ref);\n if (!original) continue;\n if (!sameValue(original.value, field.value)) {\n const key = refKey(field.ref);\n if (overlay.resetKeys.has(key) && sameValue(field.value, field.defaultValue)) {\n resets.push(field.ref);\n } else {\n const value = formValueFromScriptValue(field);\n if (value) effects.push({ kind: 'setValue', ref: field.ref, value });\n }\n }\n if (original.display !== field.display) {\n effects.push({ kind: 'setDisplay', ref: field.ref, display: field.display });\n }\n }\n if (resets.length > 0) effects.unshift({ kind: 'reset', refs: resets });\n effects.push(\n ...Array.from(overlay.appearances.values(), ({ ref, text }) => ({\n kind: 'setAppearanceText' as const,\n ref,\n text,\n })),\n );\n return effects;\n}\n","/** Result shaping for the scripting transaction: errors, budgets, page numbers. */\nimport type { DocumentMeta } from '@embedpdf/core';\nimport type { ScriptExecutionError } from '@embedpdf/core-acrojs';\nimport type { FormFieldDTO } from '@embedpdf/engine-core/runtime';\n\nexport function scriptError(message: string): ScriptExecutionError {\n return { kind: 'invalid-output', message };\n}\n\nexport function utf8Length(value: string): number {\n let length = 0;\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code < 0x80) length += 1;\n else if (code < 0x800) length += 2;\n else if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {\n length += 4;\n index += 1;\n } else length += 3;\n }\n return length;\n}\n\nexport function pageNumberFor(\n meta: Pick<DocumentMeta, 'pages'> | null,\n field: FormFieldDTO,\n): number {\n const placed = field.widgets.find((widget) => widget.page !== null)?.page;\n if (!meta || !placed) return 0;\n const index = meta.pages.findIndex((page) => page.ref.objectNumber === placed.objectNumber);\n return Math.max(0, index);\n}\n","/**\n * The K/V/C/F passes: keystroke, validate, calculate and format scripts over\n * one working copy of the form (`overlay.ts`), inside one realm transaction.\n * The controller reads the form it is given and returns what the scripts\n * changed. Writing it is the caller's, inside the same transaction\n * (`transaction`), so the next transaction reads the form with it.\n */\nimport type { DocumentMeta } from '@embedpdf/core';\nimport {\n DEFAULT_SCRIPT_BUDGET,\n javaScriptProgramFromActionTree,\n scriptFieldsFromSnapshot,\n type ScriptBudget,\n type ScriptDiagnostic,\n type ScriptExecutionError,\n type ScriptFieldInput,\n type ScriptOutput,\n type ScriptTransaction,\n type ScriptValue,\n type ScriptWorldInput,\n} from '@embedpdf/core-acrojs';\nimport type {\n FormEffect,\n FormFieldRef,\n FormFieldValue,\n FormSnapshot,\n PdfActionTree,\n} from '@embedpdf/engine-core/runtime';\n\nimport type { FormUiEffect } from '../contract';\nimport {\n applyEffects,\n canonicalEffects,\n cloneFields,\n cloneValue,\n fieldByRef,\n refKey,\n sameRef,\n scriptValueFromFormValue,\n snapshotField,\n type Overlay,\n} from './overlay';\nimport { pageNumberFor, scriptError, utf8Length } from './results';\n\n/**\n * The controller never owns a realm. It rides a realm port — this document's\n * own (the actions plugin's per-document host) or a detached one minted by\n * the actions plugin for a document that is not displayed (stamp's\n * asset documents). Either way the owner disposes the realm; `dispose()`\n * here only fences further transactions.\n */\nexport interface FormScriptingControllerOptions {\n /** What the scripts read of the document: its name, page count and pages. */\n document(): Pick<DocumentMeta, 'name' | 'pageCount' | 'pages'> | null;\n /** The realm's transaction port ({@link ScriptTransaction}): the realm runs one at a time. */\n transaction<T>(body: (txn: ScriptTransaction) => Promise<T>): Promise<T>;\n /** The transaction aggregate (output bytes, wall-clock across passes);\n * the realm owner hands over the same budget its runs enforce. */\n budget?: ScriptBudget;\n}\n\n/** What the form's scripts made of one write, before anything is written. */\nexport interface FormScriptResult {\n /**\n * `accepted`: write `effects`. `rejected`: a keystroke or validate script refused the value,\n * so only `bootEffects` are written. `failed`: the transaction broke (a budget, a script that\n * couldn't run), so nothing is.\n */\n readonly status: 'accepted' | 'rejected' | 'failed';\n /** What the scripts changed in the form, in the order to write it (`formEffectOps`). */\n readonly effects: FormEffect[];\n /** What the document's open scripts changed: written even when the value is refused. */\n readonly bootEffects: FormEffect[];\n readonly uiEffects: FormUiEffect[];\n readonly diagnostics: ScriptDiagnostic[];\n readonly error?: ScriptExecutionError;\n}\n\n/** The scripts one transaction can run, each against the form it is given. */\nexport interface FormScripts {\n /**\n * A field's new value through its keystroke and validate scripts, then the calculations and\n * the formats. `form` is the form without the value.\n */\n commit(form: FormSnapshot, ref: FormFieldRef, value: FormFieldValue): Promise<FormScriptResult>;\n /** One widget's activation action (`/A`, with `/Next`). Only its scripts run here. */\n activate(\n form: FormSnapshot,\n ref: FormFieldRef,\n action: PdfActionTree<unknown>,\n ): Promise<FormScriptResult>;\n /** The document's open scripts (once per realm) and the calculations, without a field change. */\n recalculate(form: FormSnapshot): Promise<FormScriptResult>;\n}\n\nexport class FormScriptingController {\n private disposed = false;\n private readonly transactionBudget: ScriptBudget | undefined;\n\n constructor(private readonly options: FormScriptingControllerOptions) {\n this.transactionBudget = options.budget;\n }\n\n /** The realm belongs to its owner (the actions plugin, or the caller that\n * minted a detached one) — this only fences further transactions. */\n dispose(): void {\n this.disposed = true;\n }\n\n /**\n * One realm transaction: `body` runs its scripts and writes what they changed before the\n * next transaction starts, so that one reads the form with it.\n */\n transaction<T>(body: (scripts: FormScripts) => Promise<T>): Promise<T> {\n if (this.disposed) return Promise.reject(new Error('Form scripting controller is disposed'));\n return this.options.transaction((txn) =>\n body({\n commit: (form, ref, value) => this.run(txn, form, ref, value),\n activate: (form, ref, action) => this.run(txn, form, ref, undefined, action),\n recalculate: (form) => this.run(txn, form, undefined),\n }),\n );\n }\n\n /**\n * Load the realm before the first write needs it: the scripts' engine starts, and the\n * document's open scripts run, their effects kept for the first write as without warming.\n * Nothing is written.\n */\n async warm(form: FormSnapshot): Promise<void> {\n if (this.disposed) return;\n const budget = this.transactionBudget ?? DEFAULT_SCRIPT_BUDGET;\n await this.options.transaction((txn) =>\n txn.run(\n '',\n { fields: scriptFieldsFromSnapshot(form), event: { kind: 'name-tree-boot' } },\n budget,\n ),\n );\n }\n\n /** {@link FormScripts.commit} in a transaction of its own. */\n commit(form: FormSnapshot, ref: FormFieldRef, value: FormFieldValue): Promise<FormScriptResult> {\n return this.transaction((scripts) => scripts.commit(form, ref, value));\n }\n\n /** {@link FormScripts.activate} in a transaction of its own. */\n activate(\n form: FormSnapshot,\n ref: FormFieldRef,\n action: PdfActionTree<unknown>,\n ): Promise<FormScriptResult> {\n return this.transaction((scripts) => scripts.activate(form, ref, action));\n }\n\n /** {@link FormScripts.recalculate} in a transaction of its own. */\n recalculate(form: FormSnapshot): Promise<FormScriptResult> {\n return this.transaction((scripts) => scripts.recalculate(form));\n }\n\n private async run(\n txn: ScriptTransaction,\n snapshot: FormSnapshot,\n refInput: FormFieldRef | undefined,\n proposed?: FormFieldValue,\n activation?: PdfActionTree<unknown>,\n ): Promise<FormScriptResult> {\n // Recalculate's anchor resolves here (first live /CO field, else the\n // first field); a zero-field document is an honest no-op.\n const asked =\n refInput ??\n snapshot.calculationOrder.find(\n (candidate): candidate is FormFieldRef =>\n candidate !== null && snapshotField(snapshot, candidate) !== undefined,\n ) ??\n snapshot.fields[0]?.ref;\n if (!asked) {\n return { status: 'accepted', effects: [], bootEffects: [], uiEffects: [], diagnostics: [] };\n }\n const target = snapshotField(snapshot, asked);\n if (!target) {\n return this.failed([], [], scriptError(`Form field '${refKey(asked)}' no longer exists`));\n }\n // The scripts know the field by its own ref, whichever ref asked for it.\n const ref = target.ref;\n\n const original = scriptFieldsFromSnapshot(snapshot);\n const overlay: Overlay = {\n original,\n fields: cloneFields(original),\n resetKeys: new Set(),\n appearances: new Map(),\n };\n const targetInput = fieldByRef(overlay.fields, ref);\n if (!targetInput) {\n return this.failed([], [], scriptError(`Form field '${refKey(ref)}' has no script view`));\n }\n\n const hasProposedValue = proposed !== undefined;\n let proposedValue: ScriptValue = cloneValue(targetInput.value);\n if (proposed) {\n try {\n proposedValue = scriptValueFromFormValue(targetInput, proposed);\n } catch (error) {\n return this.failed(\n [],\n [],\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n }\n\n const uiEffects: FormUiEffect[] = [];\n const diagnostics: ScriptDiagnostic[] = [];\n const meta = this.options.document();\n // Document/identity/environment are host-owned now; the world carries the\n // per-field current page for `this.pageNum`.\n const pageNumber = pageNumberFor(meta, target);\n const budget = this.transactionBudget ?? DEFAULT_SCRIPT_BUDGET;\n let executionMs = 0;\n let aggregateOutputSize = 0;\n\n const consume = (\n output: ScriptOutput,\n phase: FormUiEffect['phase'],\n ): ScriptExecutionError | null => {\n aggregateOutputSize += utf8Length(JSON.stringify(output));\n // Tag every UI request with who asked — embedders suppress boot-phase\n // nags (Adobe's version-check alert) but show user-phase validation.\n uiEffects.push(...output.uiEffects.map((effect) => ({ ...effect, phase })));\n diagnostics.push(...output.diagnostics);\n if (aggregateOutputSize > budget.maxOutputBytes) {\n return {\n kind: 'budget',\n message: `Form script transaction output exceeded ${budget.maxOutputBytes} bytes`,\n };\n }\n return output.error ?? null;\n };\n const remainingBudget = (): ScriptBudget | null => {\n const remaining = budget.maxExecutionMs - executionMs;\n return remaining <= 0 ? null : { ...budget, maxExecutionMs: remaining };\n };\n const world = (\n fields: ScriptFieldInput[],\n event: ScriptWorldInput['event'],\n ): ScriptWorldInput => ({\n fields,\n pageNumber,\n event,\n });\n const run = async (\n source: string,\n input: ScriptWorldInput,\n ): Promise<{ output?: ScriptOutput; error?: ScriptExecutionError }> => {\n const remaining = remainingBudget();\n if (!remaining) {\n return { error: { kind: 'budget', message: 'Form script transaction timed out' } };\n }\n let output: ScriptOutput;\n try {\n const startedAt = Date.now();\n output = await txn.run(source, input, remaining);\n executionMs += Date.now() - startedAt;\n } catch (error) {\n return {\n error: scriptError(error instanceof Error ? error.message : String(error)),\n };\n }\n const error = consume(output, 'user');\n return error ? { error } : { output };\n };\n\n {\n // Boot runs once per realm (host-owned latch) and can only ever\n // degrade, never brick — a failure surfaces a diagnostic and this\n // transaction continues from pristine state.\n let boot: ScriptOutput | null = null;\n try {\n boot = await txn.boot(world(overlay.fields, { kind: 'name-tree-boot' }), budget);\n } catch (error) {\n diagnostics.push({\n code: 'script-error',\n message: `Document boot script failed (continuing without it): ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n if (boot) {\n const bootError = consume(boot, 'boot');\n if (!bootError) applyEffects(overlay, boot.formEffects);\n else {\n diagnostics.push({\n code: 'script-error',\n message: `Document boot script failed (continuing without it): ${bootError.message}`,\n });\n }\n }\n }\n\n const bootEffects = canonicalEffects(overlay);\n\n if (activation) {\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(activation);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n if (program) {\n const activated = await run(\n program,\n world(overlay.fields, {\n kind: 'widget-activate',\n target: ref,\n source: ref,\n value: targetInput.value,\n }),\n );\n if (activated.error) return this.failed(uiEffects, diagnostics, activated.error);\n applyEffects(overlay, activated.output!.formEffects);\n }\n }\n\n if (!activation && hasProposedValue && target.actions?.keystroke && target.family === 'text') {\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(target.actions.keystroke);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n if (program) {\n // Acrobat fires per-typing keystroke events plus one final commit\n // event. This pipeline compresses typing into one paste-shaped\n // replacement (Acrobat's own paste contract: the whole string rides\n // `event.change`, willCommit=false) so transform/filter scripts run,\n // then fires the Acrobat-faithful willCommit pass (the full value on\n // `event.value`, empty `change`) so AF* and commit validators see\n // what Adobe's library expects.\n const oldValue = String(targetInput.value ?? '');\n const typing = await run(\n program,\n world(overlay.fields, {\n kind: 'field-keystroke',\n target: ref,\n source: ref,\n value: oldValue,\n change: String(proposedValue ?? ''),\n selStart: 0,\n selEnd: oldValue.length,\n willCommit: false,\n }),\n );\n if (typing.error && typing.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, typing.error);\n }\n if (typing.error) {\n // Fault ladder: an exception is not a rejection. Keep the typed\n // value, surface a diagnostic, and let the transaction continue.\n diagnostics.push({\n code: 'script-error',\n message: `Keystroke script failed (kept the typed value): ${typing.error.message}`,\n });\n } else {\n if (!typing.output!.event.rc) {\n return this.rejected(bootEffects, uiEffects, diagnostics);\n }\n applyEffects(overlay, typing.output!.formEffects);\n const event = typing.output!.event;\n const base = String(event.value ?? '');\n proposedValue =\n base.slice(0, event.selStart) +\n event.change +\n base.slice(Math.max(event.selStart, event.selEnd));\n }\n const commit = await run(\n program,\n world(overlay.fields, {\n kind: 'field-keystroke',\n target: ref,\n source: ref,\n value: proposedValue,\n change: '',\n selStart: 0,\n selEnd: 0,\n willCommit: true,\n }),\n );\n if (commit.error && commit.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, commit.error);\n }\n if (commit.error) {\n diagnostics.push({\n code: 'script-error',\n message: `Keystroke commit script failed (kept the typed value): ${commit.error.message}`,\n });\n } else {\n if (!commit.output!.event.rc) {\n return this.rejected(bootEffects, uiEffects, diagnostics);\n }\n applyEffects(overlay, commit.output!.formEffects);\n proposedValue = cloneValue(commit.output!.event.value);\n }\n }\n }\n\n if (!activation && hasProposedValue) {\n targetInput.value = cloneValue(proposedValue);\n overlay.resetKeys.delete(refKey(ref));\n }\n\n if (!activation && hasProposedValue && target.actions?.validate) {\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(target.actions.validate);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n if (program) {\n const validation = await run(\n program,\n world(overlay.fields, {\n kind: 'field-validate',\n target: ref,\n source: ref,\n value: proposedValue,\n willCommit: true,\n }),\n );\n if (validation.error && validation.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, validation.error);\n }\n if (validation.error) {\n // Fault ladder: a broken validator must not reject the value.\n diagnostics.push({\n code: 'script-error',\n message: `Validate script failed (accepted the value): ${validation.error.message}`,\n });\n } else {\n if (!validation.output!.event.rc) {\n return this.rejected(bootEffects, uiEffects, diagnostics);\n }\n targetInput.value = cloneValue(validation.output!.event.value);\n applyEffects(overlay, validation.output!.formEffects);\n }\n }\n }\n\n const formatRefs: FormFieldRef[] = !activation && hasProposedValue ? [ref] : [];\n for (const calculationRef of activation ? [] : snapshot.calculationOrder) {\n if (!calculationRef) continue;\n const calculated = snapshotField(snapshot, calculationRef);\n const tree = calculated?.actions?.calculate;\n if (!calculated || !tree) continue;\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(tree);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n const calculatedInput = fieldByRef(overlay.fields, calculationRef);\n if (program && calculatedInput) {\n const calculation = await run(\n program,\n world(overlay.fields, {\n kind: 'field-calculate',\n target: calculationRef,\n ...(hasProposedValue ? { source: ref } : {}),\n value: calculatedInput.value,\n }),\n );\n if (calculation.error && calculation.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, calculation.error);\n }\n if (calculation.error) {\n // Fault ladder: this field's calculation is skipped; the /CO chain continues.\n diagnostics.push({\n code: 'script-error',\n message: `Calculate script failed (field left unchanged): ${calculation.error.message}`,\n });\n } else {\n applyEffects(overlay, calculation.output!.formEffects);\n }\n }\n if (!formatRefs.some((candidate) => sameRef(candidate, calculationRef))) {\n formatRefs.push(calculationRef);\n }\n }\n\n for (const formatRef of formatRefs) {\n const field = snapshotField(snapshot, formatRef);\n const tree = field?.actions?.format;\n const overlayField = fieldByRef(overlay.fields, formatRef);\n if (!field || !tree || !overlayField) continue;\n let program: string;\n try {\n program = javaScriptProgramFromActionTree(tree);\n } catch (error) {\n return this.failed(\n uiEffects,\n diagnostics,\n scriptError(error instanceof Error ? error.message : String(error)),\n );\n }\n if (!program) continue;\n const format = await run(\n program,\n world(overlay.fields, {\n kind: 'field-format',\n target: formatRef,\n ...(hasProposedValue ? { source: ref } : {}),\n value: overlayField.value,\n }),\n );\n if (format.error && format.error.kind !== 'exception') {\n return this.failed(uiEffects, diagnostics, format.error);\n }\n if (format.error) {\n // Fault ladder: formatting is cosmetic — skip it, keep the raw value.\n diagnostics.push({\n code: 'script-error',\n message: `Format script failed (kept the raw value): ${format.error.message}`,\n });\n continue;\n }\n applyEffects(overlay, format.output!.formEffects);\n }\n\n const effects = canonicalEffects(overlay);\n if (effects.length + uiEffects.length > budget.maxEffects) {\n return this.failed(uiEffects, diagnostics, {\n kind: 'budget',\n message: `Form script transaction exceeded ${budget.maxEffects} effects`,\n });\n }\n return { status: 'accepted', effects, bootEffects, uiEffects, diagnostics };\n }\n\n private failed(\n uiEffects: FormUiEffect[],\n diagnostics: ScriptDiagnostic[],\n error: ScriptExecutionError,\n ): FormScriptResult {\n return { status: 'failed', effects: [], bootEffects: [], uiEffects, diagnostics, error };\n }\n\n /** The value is refused; the open scripts' effects are still written. */\n private rejected(\n bootEffects: FormEffect[],\n uiEffects: FormUiEffect[],\n diagnostics: ScriptDiagnostic[],\n ): FormScriptResult {\n return { status: 'rejected', effects: [], bootEffects, uiEffects, diagnostics };\n }\n}\n\nexport function createFormScriptingController(\n options: FormScriptingControllerOptions,\n): FormScriptingController {\n return new FormScriptingController(options);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { N as FormSetValueResult, Y as Box, Z as ShownWidget, a as FormCommitResult, i as FormCapability } from "./contract-D-yQxw3H.cjs";
|
|
2
|
+
import "@embedpdf/core";
|
|
3
|
+
import { AnnotationRef, FormEffect, FormFieldDTO, FormFieldRef, PageRef, PdfActionTargetRef, WidgetAnnotation } from "@embedpdf/engine-core/runtime";
|
|
4
|
+
import { ActionContext, ActionDiagnostic, ActionOrigin, ActionSubmitRequest, PdfAnnotationEventKind, SubmitIntent } from "@embedpdf/plugin-actions/contract";
|
|
5
|
+
import { TextEditorLayout, TextEditorLayout as TextEditorLayout$1 } from "@embedpdf/core-form";
|
|
6
|
+
import { FormCommitSinkResult } from "@embedpdf/plugin-actions/contract/host";
|
|
7
|
+
import { AppearancePicture } from "@embedpdf/plugin-render/contract/host";
|
|
8
|
+
import { SceneNode } from "@embedpdf/core-annotation";
|
|
9
|
+
//#region src/services/looks.d.ts
|
|
10
|
+
/** A widget as the painter draws it: its scene on the page (its text carries the faces it needs). */
|
|
11
|
+
interface WidgetPainting {
|
|
12
|
+
readonly scene: readonly SceneNode[];
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/host-contract.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* The host lens: members for the framework layers and sibling plugins (the
|
|
18
|
+
* widget event feed, the text being typed, and the actions plugin's
|
|
19
|
+
* executors and sinks). Application code uses the public capability.
|
|
20
|
+
*/
|
|
21
|
+
interface FormHostCapability extends FormCapability {
|
|
22
|
+
/** Load a page's widgets (one annotation read per page), so `listWidgets(page)` has them. */
|
|
23
|
+
ensureLoaded(page: PageRef): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* The widgets a page shows, in the order the page draws them, each with the appearance state it
|
|
26
|
+
* shows and whether its look is `pending` (the view's isn't the confirmed one yet); hidden ones
|
|
27
|
+
* are left out. The form layer paints their pictures from it, and the painter's drawing
|
|
28
|
+
* (`getWidgetPainting`) for a pending one or one whose picture is older than its look. In design
|
|
29
|
+
* mode, while the widgets are lent to the annotation plugin, none: the annotation layer draws
|
|
30
|
+
* them.
|
|
31
|
+
*/
|
|
32
|
+
listShownWidgets(page: PageRef): readonly ShownWidget[];
|
|
33
|
+
/**
|
|
34
|
+
* What a page's widget pictures depend on: its widgets at their picture
|
|
35
|
+
* versions (`5@2|6@7`). Load the pictures again when it changes, or when
|
|
36
|
+
* the appearance scale does. A checkbox that only switches its state
|
|
37
|
+
* doesn't move it.
|
|
38
|
+
*/
|
|
39
|
+
getAppearanceEpoch(page: PageRef): string;
|
|
40
|
+
/**
|
|
41
|
+
* Every picture of a page's widgets, at rest, in every state, each with the
|
|
42
|
+
* picture version of its widget (`ShownWidget.pictureVersion`), through the
|
|
43
|
+
* render plugin; none without it. The form layer and design mode share them.
|
|
44
|
+
*/
|
|
45
|
+
renderAppearances(page: PageRef, scale: number, signal?: AbortSignal): Promise<readonly AppearancePicture[]>;
|
|
46
|
+
/**
|
|
47
|
+
* What the widget looks like as the view has it, drawn by the painter (`@embedpdf/core-form`): its
|
|
48
|
+
* scene in page space, its text in the engine's own faces (each glyphs node carries the program
|
|
49
|
+
* to mount). The same object while nothing it shows changes; `null` for a widget the painter
|
|
50
|
+
* doesn't draw (a signature).
|
|
51
|
+
*/
|
|
52
|
+
getWidgetPainting(widget: AnnotationRef): WidgetPainting | null;
|
|
53
|
+
/**
|
|
54
|
+
* Where a text field's editor shows `text` (what is typed in it), so it sits where the engine
|
|
55
|
+
* will draw it: the face, the size the engine draws at (a fitted size resolved for `text`),
|
|
56
|
+
* the text's box inside the widget's, one line's height and a comb's spacing, in page points.
|
|
57
|
+
* `null` for a widget that isn't a text field's.
|
|
58
|
+
*/
|
|
59
|
+
getTextEditorLayout(widget: AnnotationRef, text: string): TextEditorLayout | null;
|
|
60
|
+
/** The page box in page space, for keeping a placement on the page; `null` for a page that isn't there. */
|
|
61
|
+
getPageBox(page: PageRef): Box | null;
|
|
62
|
+
/** Send a widget's pointer or focus event to the actions plugin, which runs its `/AA` actions. */
|
|
63
|
+
notifyWidgetEvent(field: FormFieldRef, widget: AnnotationRef, event: PdfAnnotationEventKind): void;
|
|
64
|
+
/** Run a ResetForm action: reset the fields it names (or all but them), then recalculate. */
|
|
65
|
+
resetFormAction(fields: PdfActionTargetRef[] | null, exclude: boolean, origin?: ActionOrigin): Promise<FormCommitResult>;
|
|
66
|
+
/** The fields a SubmitForm action sends: the form as the user sees it, once every fill on its way is answered. */
|
|
67
|
+
resolveSubmitDataset(intent: SubmitIntent, actionContext: ActionContext, diagnose: (diagnostic: ActionDiagnostic) => void): Promise<ActionSubmitRequest>;
|
|
68
|
+
/**
|
|
69
|
+
* Write what a document script changed in the fields, with the calculations a value causes,
|
|
70
|
+
* as one change: shown at once, sent once the script's transaction ends. Never rejects; a
|
|
71
|
+
* change it can't stage reports why, and the engine's refusal comes through the change queue.
|
|
72
|
+
*/
|
|
73
|
+
commitScriptFormEffects(effects: FormEffect[]): Promise<FormCommitSinkResult>;
|
|
74
|
+
/**
|
|
75
|
+
* The text someone is typing in a field, before it's written (write/typing.ts): a keystroke
|
|
76
|
+
* calls `draftText`, blur or Enter `commitDraftText` (the field's scripts run then), Escape
|
|
77
|
+
* `discardDraftText`. A download writes a draft first, like Acrobat commits the field being
|
|
78
|
+
* edited before it saves. `commitDraftText` resolves `null` when there's nothing to write.
|
|
79
|
+
*/
|
|
80
|
+
draftText(field: FormFieldRef, text: string): void;
|
|
81
|
+
commitDraftText(field: FormFieldRef): Promise<FormSetValueResult | null>;
|
|
82
|
+
discardDraftText(field: FormFieldRef): void;
|
|
83
|
+
}
|
|
84
|
+
declare const FormToken: import("@embedpdf/core").CapabilityToken<FormHostCapability>;
|
|
85
|
+
//#endregion
|
|
86
|
+
export { WidgetPainting as i, FormToken as n, TextEditorLayout$1 as r, FormHostCapability as t };
|
|
87
|
+
//# sourceMappingURL=host-contract-1epLfK1S.d.cts.map
|