@embedpdf/plugin-signature 2.15.1 → 3.0.0-next.13

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.
Files changed (92) hide show
  1. package/LICENSE +192 -21
  2. package/README.md +38 -0
  3. package/dist/contract-BvHa9Zl_.d.cts +176 -0
  4. package/dist/contract-C3nQtwGf.js +14 -0
  5. package/dist/contract-C3nQtwGf.js.map +1 -0
  6. package/dist/contract-CojqGdMH.cjs +43 -0
  7. package/dist/contract-CojqGdMH.cjs.map +1 -0
  8. package/dist/contract-Dg50puf7.d.ts +176 -0
  9. package/dist/contract.cjs +7 -0
  10. package/dist/contract.d.cts +2 -0
  11. package/dist/contract.d.ts +2 -0
  12. package/dist/contract.js +2 -0
  13. package/dist/index.cjs +498 -2
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +35 -0
  16. package/dist/index.d.ts +35 -1
  17. package/dist/index.js +450 -511
  18. package/dist/index.js.map +1 -1
  19. package/package.json +28 -58
  20. package/dist/lib/actions.d.ts +0 -14
  21. package/dist/lib/handlers/signature-ink.handler.d.ts +0 -4
  22. package/dist/lib/handlers/signature-stamp.handler.d.ts +0 -4
  23. package/dist/lib/index.d.ts +0 -12
  24. package/dist/lib/manifest.d.ts +0 -4
  25. package/dist/lib/reducer.d.ts +0 -5
  26. package/dist/lib/serialization.d.ts +0 -30
  27. package/dist/lib/signature-plugin.d.ts +0 -27
  28. package/dist/lib/tools.d.ts +0 -113
  29. package/dist/lib/types.d.ts +0 -114
  30. package/dist/preact/adapter.d.ts +0 -5
  31. package/dist/preact/annotation.d.ts +0 -1
  32. package/dist/preact/core.d.ts +0 -1
  33. package/dist/preact/index.cjs +0 -2
  34. package/dist/preact/index.cjs.map +0 -1
  35. package/dist/preact/index.d.ts +0 -1
  36. package/dist/preact/index.js +0 -464
  37. package/dist/preact/index.js.map +0 -1
  38. package/dist/react/adapter.d.ts +0 -2
  39. package/dist/react/annotation.d.ts +0 -1
  40. package/dist/react/core.d.ts +0 -1
  41. package/dist/react/index.cjs +0 -2
  42. package/dist/react/index.cjs.map +0 -1
  43. package/dist/react/index.d.ts +0 -1
  44. package/dist/react/index.js +0 -463
  45. package/dist/react/index.js.map +0 -1
  46. package/dist/shared/components/crop-canvas.d.ts +0 -14
  47. package/dist/shared/components/index.d.ts +0 -2
  48. package/dist/shared/components/signature-draw-pad.d.ts +0 -12
  49. package/dist/shared/components/signature-type-pad.d.ts +0 -14
  50. package/dist/shared/hooks/index.d.ts +0 -2
  51. package/dist/shared/hooks/use-signature-upload.d.ts +0 -21
  52. package/dist/shared/hooks/use-signature.d.ts +0 -16
  53. package/dist/shared/index.d.ts +0 -4
  54. package/dist/shared-preact/components/crop-canvas.d.ts +0 -14
  55. package/dist/shared-preact/components/index.d.ts +0 -2
  56. package/dist/shared-preact/components/signature-draw-pad.d.ts +0 -12
  57. package/dist/shared-preact/components/signature-type-pad.d.ts +0 -14
  58. package/dist/shared-preact/hooks/index.d.ts +0 -2
  59. package/dist/shared-preact/hooks/use-signature-upload.d.ts +0 -21
  60. package/dist/shared-preact/hooks/use-signature.d.ts +0 -16
  61. package/dist/shared-preact/index.d.ts +0 -4
  62. package/dist/shared-react/components/crop-canvas.d.ts +0 -14
  63. package/dist/shared-react/components/index.d.ts +0 -2
  64. package/dist/shared-react/components/signature-draw-pad.d.ts +0 -12
  65. package/dist/shared-react/components/signature-type-pad.d.ts +0 -14
  66. package/dist/shared-react/hooks/index.d.ts +0 -2
  67. package/dist/shared-react/hooks/use-signature-upload.d.ts +0 -21
  68. package/dist/shared-react/hooks/use-signature.d.ts +0 -16
  69. package/dist/shared-react/index.d.ts +0 -4
  70. package/dist/shared-vue/components/crop-canvas.d.ts +0 -14
  71. package/dist/svelte/components/SignatureDrawPad.svelte.d.ts +0 -12
  72. package/dist/svelte/components/SignatureTypePad.svelte.d.ts +0 -14
  73. package/dist/svelte/components/index.d.ts +0 -2
  74. package/dist/svelte/hooks/index.d.ts +0 -2
  75. package/dist/svelte/hooks/use-signature-upload.svelte.d.ts +0 -21
  76. package/dist/svelte/hooks/use-signature.svelte.d.ts +0 -17
  77. package/dist/svelte/index.cjs +0 -2
  78. package/dist/svelte/index.cjs.map +0 -1
  79. package/dist/svelte/index.d.ts +0 -4
  80. package/dist/svelte/index.js +0 -415
  81. package/dist/svelte/index.js.map +0 -1
  82. package/dist/vue/components/SignatureDrawPad.vue.d.ts +0 -15
  83. package/dist/vue/components/SignatureTypePad.vue.d.ts +0 -19
  84. package/dist/vue/components/index.d.ts +0 -2
  85. package/dist/vue/hooks/index.d.ts +0 -2
  86. package/dist/vue/hooks/use-signature-upload.d.ts +0 -20
  87. package/dist/vue/hooks/use-signature.d.ts +0 -176
  88. package/dist/vue/index.cjs +0 -2
  89. package/dist/vue/index.cjs.map +0 -1
  90. package/dist/vue/index.d.ts +0 -4
  91. package/dist/vue/index.js +0 -453
  92. package/dist/vue/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,516 +1,455 @@
1
- import { BasePlugin, createEmitter, createScopedEmitter } from "@embedpdf/core";
2
- import { PdfAnnotationSubtype, uuidV4, PdfAnnotationName, expandRect } from "@embedpdf/models";
3
- import { applyInsertUpright, clampAnnotationToPage, defineAnnotationTool, patching } from "@embedpdf/plugin-annotation";
4
- var SignatureMode = /* @__PURE__ */ ((SignatureMode2) => {
5
- SignatureMode2["SignatureOnly"] = "signature-only";
6
- SignatureMode2["SignatureAndInitials"] = "signature-and-initials";
7
- return SignatureMode2;
8
- })(SignatureMode || {});
9
- var SignatureCreationType = /* @__PURE__ */ ((SignatureCreationType2) => {
10
- SignatureCreationType2["Draw"] = "draw";
11
- SignatureCreationType2["Type"] = "type";
12
- SignatureCreationType2["Upload"] = "upload";
13
- return SignatureCreationType2;
14
- })(SignatureCreationType || {});
15
- var SignatureFieldKind = /* @__PURE__ */ ((SignatureFieldKind2) => {
16
- SignatureFieldKind2["Signature"] = "signature";
17
- SignatureFieldKind2["Initials"] = "initials";
18
- return SignatureFieldKind2;
19
- })(SignatureFieldKind || {});
20
- const SIGNATURE_PLUGIN_ID = "signature";
21
- const manifest = {
22
- id: SIGNATURE_PLUGIN_ID,
23
- name: "Signature Plugin",
24
- version: "1.0.0",
25
- provides: ["signature"],
26
- requires: ["annotation"],
27
- optional: [],
28
- defaultConfig: {
29
- mode: SignatureMode.SignatureOnly
30
- }
31
- };
32
- const ADD_SIGNATURE_ENTRY = "SIGNATURE/ADD_ENTRY";
33
- const REMOVE_SIGNATURE_ENTRY = "SIGNATURE/REMOVE_ENTRY";
34
- function addSignatureEntry(id) {
35
- return { type: ADD_SIGNATURE_ENTRY, payload: id };
1
+ import { a as markRoleOf, i as SignatureToken, n as SIGNATURES_LIBRARY_KIND, r as SIGNATURE_MARK_NAME, t as INITIALS_MARK_NAME } from "./contract-C3nQtwGf.js";
2
+ import { createEventHook, definePlugin } from "@embedpdf/core";
3
+ import { FormToken } from "@embedpdf/plugin-form/contract";
4
+ import { InteractionToken } from "@embedpdf/plugin-interaction/contract";
5
+ import { StampToken } from "@embedpdf/plugin-stamp/contract";
6
+ import { ANNOTATION_EDIT_PRIORITY, ARMED_STAMP_TOOL_ID, AnnotationToken } from "@embedpdf/plugin-annotation/contract";
7
+ import { certificateCommonName, createTestSigner, indexedDbKeyStore, memoryKeyStore, personalSigner, remoteSigner, sign, validateSignatures, webCryptoSigner } from "@embedpdf/core-signature";
8
+ import { resolveBinarySource } from "@embedpdf/engine-core/runtime";
9
+ //#region src/blank-page.ts
10
+ const OBJECTS = [
11
+ "<< /Type /Catalog /Pages 2 0 R >>",
12
+ "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
13
+ "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 60] /Contents 4 0 R /Resources << >> >>",
14
+ `<< /Length 3 >>\nstream\nq Q\nendstream`
15
+ ];
16
+ let cached = null;
17
+ function blankPagePdf() {
18
+ if (cached) return new Uint8Array(cached);
19
+ let body = "%PDF-1.7\n";
20
+ const offsets = [];
21
+ OBJECTS.forEach((object, index) => {
22
+ offsets.push(body.length);
23
+ body += `${index + 1} 0 obj\n${object}\nendobj\n`;
24
+ });
25
+ const xref = body.length;
26
+ body += `xref\n0 ${OBJECTS.length + 1}\n0000000000 65535 f \n`;
27
+ for (const offset of offsets) body += `${String(offset).padStart(10, "0")} 00000 n \n`;
28
+ body += `trailer\n<< /Size ${OBJECTS.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`;
29
+ cached = new TextEncoder().encode(body);
30
+ return new Uint8Array(cached);
36
31
  }
37
- function removeSignatureEntry(id) {
38
- return { type: REMOVE_SIGNATURE_ENTRY, payload: id };
32
+ //#endregion
33
+ //#region src/capability.ts
34
+ const sameRef = (a, b) => a.kind === "objectNumber" && b.kind === "objectNumber" ? a.fieldObjectNumber === b.fieldObjectNumber : a.kind === "fqn" && b.kind === "fqn" ? a.name === b.name : false;
35
+ const sameProtection = (a, b) => JSON.stringify(a) === JSON.stringify(b);
36
+ function createSignatureCapability(ctx, config = {}) {
37
+ const form = ctx.get(FormToken);
38
+ const changed = createEventHook((error) => globalThis.console?.error("[signature] listener failed:", error));
39
+ ctx.cleanup(() => changed.dispose());
40
+ const state = () => ctx.getState();
41
+ const requireDoc = () => {
42
+ const doc = ctx.doc;
43
+ if (!doc) throw new Error("[signature] no document bound");
44
+ return doc;
45
+ };
46
+ const requireSignatures = () => {
47
+ const doc = requireDoc();
48
+ if (!doc.signatures) throw new Error("[signature] this engine does not implement signatures");
49
+ return {
50
+ doc,
51
+ signatures: doc.signatures
52
+ };
53
+ };
54
+ const stamp = () => ctx.tryGet(StampToken);
55
+ const documentId = () => {
56
+ const id = ctx.documentId;
57
+ if (!id) throw new Error("[signature] no document id");
58
+ return id;
59
+ };
60
+ const mode = () => config.mode ?? (config.signer ? "sign" : "visual");
61
+ const resolveSigner = async () => {
62
+ const signer = config.signer;
63
+ if (!signer) throw new Error("[signature] no signer configured (mode \"sign\" needs one)");
64
+ return typeof signer === "function" ? signer() : signer;
65
+ };
66
+ const allows = (cap) => ctx.doc?.security.allows(cap) ?? false;
67
+ const refresh = async () => {
68
+ const doc = ctx.doc;
69
+ if (!doc?.signatures) {
70
+ ctx.dispatch({
71
+ type: "SNAPSHOT",
72
+ snapshot: null
73
+ });
74
+ return null;
75
+ }
76
+ const before = state().snapshot?.protection ?? null;
77
+ const snapshot = await doc.signatures.list();
78
+ ctx.dispatch({
79
+ type: "SNAPSHOT",
80
+ snapshot
81
+ });
82
+ if (!sameProtection(before, snapshot.protection)) changed.emit({
83
+ type: "protectionChanged",
84
+ protection: snapshot.protection
85
+ });
86
+ return snapshot;
87
+ };
88
+ const validate = async (opts) => {
89
+ const doc = ctx.doc;
90
+ if (!doc?.signatures) return [];
91
+ const verdicts = await validateSignatures(doc, {
92
+ trust: config.trust ?? null,
93
+ at: opts?.at,
94
+ until: opts?.until ?? "working-copy"
95
+ });
96
+ const before = state().verdicts;
97
+ ctx.dispatch({
98
+ type: "VERDICTS",
99
+ verdicts
100
+ });
101
+ changed.emit({
102
+ type: "validated",
103
+ verdicts
104
+ });
105
+ for (const v of verdicts) {
106
+ if (v.modifications.basis !== "working-copy" || v.summary !== "invalid") continue;
107
+ const was = before?.find((b) => b.signature.index === v.signature.index);
108
+ if (was && was.summary === "invalid") continue;
109
+ changed.emit({
110
+ type: "invalidating",
111
+ field: v.signature.field,
112
+ detail: v.modifications.detail ?? ""
113
+ });
114
+ }
115
+ return verdicts;
116
+ };
117
+ let revalidateTimer = null;
118
+ const revalidateSoon = () => {
119
+ if (!state().snapshot?.signatures.some((s) => s.signed)) return;
120
+ if (revalidateTimer) clearTimeout(revalidateTimer);
121
+ revalidateTimer = setTimeout(() => {
122
+ revalidateTimer = null;
123
+ validate().catch((error) => globalThis.console?.error("[signature] re-validation failed:", error));
124
+ }, 300);
125
+ };
126
+ ctx.cleanup(() => {
127
+ if (revalidateTimer) clearTimeout(revalidateTimer);
128
+ });
129
+ const analyze = (input) => requireSignatures().signatures.analyze(input);
130
+ const signatureOf = (field) => {
131
+ const snapshot = state().snapshot;
132
+ if (!snapshot) return null;
133
+ if ("annotObjectNumber" in field) {
134
+ const byWidget = snapshot.signatures.find((s) => s.widget?.annotObjectNumber === field.annotObjectNumber);
135
+ if (byWidget) return byWidget;
136
+ const owner = form.fieldForWidget(field.annotObjectNumber);
137
+ return owner ? signatureOf(owner.ref) : null;
138
+ }
139
+ return snapshot.signatures.find((s) => field.kind === "fqn" ? s.fieldName === field.name : sameRef(s.field, field)) ?? null;
140
+ };
141
+ const verdictOf = (field) => {
142
+ const signature = signatureOf(field);
143
+ if (!signature) return null;
144
+ return state().verdicts?.find((v) => v.signature.index === signature.index) ?? null;
145
+ };
146
+ const bytesOf = async (source) => new Uint8Array((await resolveBinarySource(source)).bytes);
147
+ const markBytes = async (mark) => {
148
+ if ("assetId" in mark) {
149
+ const bytes = stamp()?.assetBytes(mark.assetId);
150
+ if (!bytes) throw new Error(`[signature] unknown mark '${mark.assetId}'`);
151
+ return bytes;
152
+ }
153
+ return bytesOf(mark.source);
154
+ };
155
+ const withBusy = async (work) => {
156
+ ctx.dispatch({
157
+ type: "BUSY",
158
+ busy: true
159
+ });
160
+ try {
161
+ return await work();
162
+ } finally {
163
+ ctx.dispatch({
164
+ type: "BUSY",
165
+ busy: false
166
+ });
167
+ }
168
+ };
169
+ const signField = (input) => withBusy(async () => {
170
+ const { doc } = requireSignatures();
171
+ const signer = await resolveSigner();
172
+ const subject = signer.kind === "raw" && signer.certificateChain[0] ? certificateCommonName(signer.certificateChain[0]) : null;
173
+ const attribution = {
174
+ ...subject ? { name: subject } : {},
175
+ ...input.attribution
176
+ };
177
+ const appearance = input.appearance ? await bytesOf(input.appearance) : await markBytes(input.mark);
178
+ const result = await sign(doc, {
179
+ field: input.field,
180
+ signer,
181
+ attribution,
182
+ certify: input.certify,
183
+ lock: input.lock,
184
+ appearance: {
185
+ pdf: appearance,
186
+ pageIndex: 0
187
+ }
188
+ });
189
+ if (state().target && sameRef(state().target, input.field)) setTarget(null);
190
+ await refresh();
191
+ validate().catch((error) => globalThis.console?.error("[signature] validation after signing failed:", error));
192
+ changed.emit({
193
+ type: "signed",
194
+ field: input.field,
195
+ result
196
+ });
197
+ return result;
198
+ });
199
+ const setAppearance = async (field, pdf) => {
200
+ const doc = requireDoc();
201
+ if (!doc.forms.setSignatureAppearance) throw new Error("[signature] this engine cannot draw into a signature field");
202
+ const signature = signatureOf(field);
203
+ if (signature?.signed) throw new Error(`[signature] '${signature.fieldName}' is signed`);
204
+ await doc.forms.setSignatureAppearance(field, {
205
+ pdf,
206
+ pageIndex: 0
207
+ });
208
+ await form.refresh();
209
+ };
210
+ const fillField = (field, mark) => withBusy(async () => {
211
+ await setAppearance(field, await markBytes(mark));
212
+ if (state().target && sameRef(state().target, field)) setTarget(null);
213
+ changed.emit({
214
+ type: "filled",
215
+ field
216
+ });
217
+ });
218
+ const clearField = (field) => withBusy(async () => {
219
+ await setAppearance(field, blankPagePdf());
220
+ changed.emit({
221
+ type: "cleared",
222
+ field
223
+ });
224
+ });
225
+ const placeMark = async (mark, target) => {
226
+ if (!("field" in target)) {
227
+ if ("assetId" in mark) {
228
+ const library = stamp();
229
+ if (!library) throw new Error("[signature] no stamp plugin to place an asset");
230
+ await library.placeAsset(documentId(), mark.assetId, target);
231
+ } else await ctx.get(AnnotationToken).placeStamp({ source: mark.source }, target);
232
+ return;
233
+ }
234
+ switch (mode()) {
235
+ case "sign":
236
+ if (config.allowCertify && !state().snapshot?.signatures.some((s) => s.signed)) {
237
+ changed.emit({
238
+ type: "ask",
239
+ field: target.field,
240
+ mark
241
+ });
242
+ return;
243
+ }
244
+ await signField({
245
+ field: target.field,
246
+ mark
247
+ });
248
+ return;
249
+ case "visual":
250
+ await fillField(target.field, mark);
251
+ return;
252
+ case "ask":
253
+ changed.emit({
254
+ type: "ask",
255
+ field: target.field,
256
+ mark
257
+ });
258
+ return;
259
+ }
260
+ };
261
+ const setTarget = (field) => {
262
+ const current = state().target;
263
+ if (current === field || current && field && sameRef(current, field)) return;
264
+ ctx.dispatch({
265
+ type: "TARGET",
266
+ field
267
+ });
268
+ changed.emit({
269
+ type: "target",
270
+ field
271
+ });
272
+ };
273
+ const doc = ctx.doc;
274
+ if (doc) {
275
+ const unsubscribe = doc.events.subscribe((event) => {
276
+ switch (event.type) {
277
+ case "signature.prepared":
278
+ ctx.dispatch({
279
+ type: "PENDING",
280
+ pending: {
281
+ signingId: event.signingId,
282
+ field: event.field
283
+ }
284
+ });
285
+ return;
286
+ case "signature.aborted":
287
+ ctx.dispatch({
288
+ type: "PENDING",
289
+ pending: null
290
+ });
291
+ return;
292
+ case "signature.completed":
293
+ ctx.dispatch({
294
+ type: "PENDING",
295
+ pending: null
296
+ });
297
+ return;
298
+ case "document.versioned":
299
+ case "stream.desynced":
300
+ refresh().then(() => validate()).catch((error) => globalThis.console?.error("[signature] refresh failed:", error));
301
+ return;
302
+ case "form.fieldCreated":
303
+ case "form.fieldDeleted":
304
+ case "form.widgetAttached":
305
+ case "form.widgetDetached":
306
+ case "form.imported":
307
+ case "form.repaired":
308
+ refresh().then(() => revalidateSoon()).catch((error) => globalThis.console?.error("[signature] refresh failed:", error));
309
+ return;
310
+ case "annotation.created":
311
+ case "annotation.updated":
312
+ case "annotation.deleted":
313
+ case "annotation.moved":
314
+ case "form.valueChanged":
315
+ case "form.fieldUpdated":
316
+ case "form.effectsApplied":
317
+ revalidateSoon();
318
+ return;
319
+ default: return;
320
+ }
321
+ });
322
+ ctx.cleanup(unsubscribe);
323
+ }
324
+ return {
325
+ snapshot: () => state().snapshot,
326
+ verdicts: () => state().verdicts,
327
+ protection: () => state().snapshot?.protection ?? null,
328
+ pending: () => state().pending,
329
+ target: () => state().target,
330
+ busy: () => state().busy,
331
+ mode,
332
+ signatureOf,
333
+ verdictOf,
334
+ canSign: () => allows("doc.sign") && config.signer != null,
335
+ canFill: () => allows("doc.forms.fill") && ctx.doc?.forms.setSignatureAppearance != null,
336
+ canCertify: () => config.allowCertify === true && allows("doc.sign.certify"),
337
+ signField,
338
+ fillField,
339
+ clearField,
340
+ placeMark,
341
+ setTarget,
342
+ inspect: (field) => changed.emit({
343
+ type: "inspect",
344
+ field
345
+ }),
346
+ refresh,
347
+ validate,
348
+ analyze,
349
+ revisionBytes: (revisionIndex) => requireSignatures().signatures.revisionBytes(revisionIndex),
350
+ onChanged: changed.on
351
+ };
39
352
  }
40
- const signatureStampHandlerFactory = {
41
- annotationType: PdfAnnotationSubtype.STAMP,
42
- create(context) {
43
- const { onPreview, onCommit, getTool, pageSize, pageRotation, getToolContext } = context;
44
- return {
45
- onPointerMove: (pos) => {
46
- const ctx = getToolContext();
47
- if (!(ctx == null ? void 0 : ctx.targetSize)) return;
48
- const { width, height } = ctx.targetSize;
49
- onPreview({
50
- type: PdfAnnotationSubtype.STAMP,
51
- bounds: {
52
- origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
53
- size: { width, height }
54
- },
55
- data: {
56
- rect: {
57
- origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
58
- size: { width, height }
59
- },
60
- ghostUrl: ctx.ghostUrl,
61
- pageRotation
62
- }
63
- });
64
- },
65
- onPointerDown: (pos) => {
66
- var _a;
67
- const tool = getTool();
68
- const ctx = getToolContext();
69
- if (!tool || !(ctx == null ? void 0 : ctx.targetSize)) return;
70
- const { width, height } = ctx.targetSize;
71
- const rect = {
72
- origin: { x: pos.x - width / 2, y: pos.y - height / 2 },
73
- size: { width, height }
74
- };
75
- let anno = {
76
- ...tool.defaults,
77
- rect,
78
- type: PdfAnnotationSubtype.STAMP,
79
- name: PdfAnnotationName.Custom,
80
- subject: "Signature",
81
- flags: tool.defaults.flags ?? ["print"],
82
- pageIndex: context.pageIndex,
83
- id: uuidV4(),
84
- created: /* @__PURE__ */ new Date()
85
- };
86
- if ((_a = tool.behavior) == null ? void 0 : _a.insertUpright) {
87
- anno = applyInsertUpright(anno, pageRotation, false);
88
- }
89
- anno = clampAnnotationToPage(anno, pageSize);
90
- onCommit(anno, { data: ctx.imageData });
91
- onPreview(null);
92
- },
93
- onPointerLeave: () => {
94
- onPreview(null);
95
- }
96
- };
97
- }
98
- };
99
- function translateAndScaleInk(inkData, targetSize, centerPos) {
100
- const scaleX = targetSize.width / inkData.size.width;
101
- const scaleY = targetSize.height / inkData.size.height;
102
- const scale = Math.min(scaleX, scaleY);
103
- const scaledW = inkData.size.width * scale;
104
- const scaledH = inkData.size.height * scale;
105
- const offsetX = centerPos.x - scaledW / 2;
106
- const offsetY = centerPos.y - scaledH / 2;
107
- const inkList = inkData.inkList.map((stroke) => ({
108
- points: stroke.points.map((p) => ({
109
- x: p.x * scale + offsetX,
110
- y: p.y * scale + offsetY
111
- }))
112
- }));
113
- return { inkList, strokeWidth: Math.round(inkData.strokeWidth * scale * 10) / 10 };
353
+ //#endregion
354
+ //#region src/handler.ts
355
+ /**
356
+ * The armed mark over a field. The annotation plugin places an armed stamp
357
+ * on click (`annotation-place`, priority 95) and selects an existing
358
+ * annotation under the pointer before that (`annotation-edit`, 100 — a
359
+ * widget is an annotation). This handler sits above BOTH, enabled for the
360
+ * armed stamp's tool only, and captures exactly when the armed asset is a
361
+ * person's mark (a library of kind `signatures`) and the pointer is over a
362
+ * signature field: then the destination decides — the mark goes INTO the
363
+ * field (`placeMark` by mode) instead of onto the page. Everywhere else it
364
+ * declines and the click stays a stamp placement, as in Preview.
365
+ */
366
+ function createArmedMarkHandler(documentId, signature, form, stamp) {
367
+ return {
368
+ id: "signature:armed-mark-on-field",
369
+ priority: ANNOTATION_EDIT_PRIORITY + 1,
370
+ enabledFor: (tool) => tool.id === ARMED_STAMP_TOOL_ID,
371
+ onDown: (sample) => {
372
+ if (!sample.page) return false;
373
+ const armed = stamp.armedAsset(documentId);
374
+ if (!armed || stamp.library(armed.libraryId)?.kind !== "signatures") return false;
375
+ const hit = form.widgetAt(sample.page.pon, sample.page.point);
376
+ if (!hit || hit.field.family !== "signature") return false;
377
+ if (signature.signatureOf(hit.field.ref)?.signed || hit.field.valueEntry.kind !== "none") return true;
378
+ stamp.disarm(documentId);
379
+ signature.placeMark({ assetId: armed.id }, { field: hit.field.ref }).catch((error) => globalThis.console?.error("[signature] placing the mark failed:", error));
380
+ return true;
381
+ }
382
+ };
114
383
  }
115
- const signatureInkHandlerFactory = {
116
- annotationType: PdfAnnotationSubtype.INK,
117
- create(context) {
118
- const { onPreview, onCommit, getTool, pageSize, getToolContext } = context;
119
- return {
120
- onPointerMove: (pos) => {
121
- const ctx = getToolContext();
122
- if (!(ctx == null ? void 0 : ctx.inkData) || !(ctx == null ? void 0 : ctx.targetSize)) return;
123
- const { inkList, strokeWidth } = translateAndScaleInk(ctx.inkData, ctx.targetSize, pos);
124
- const allPoints = inkList.flatMap((s) => s.points);
125
- if (allPoints.length === 0) return;
126
- const rect = expandRect(
127
- {
128
- origin: {
129
- x: Math.min(...allPoints.map((p) => p.x)),
130
- y: Math.min(...allPoints.map((p) => p.y))
131
- },
132
- size: {
133
- width: Math.max(...allPoints.map((p) => p.x)) - Math.min(...allPoints.map((p) => p.x)),
134
- height: Math.max(...allPoints.map((p) => p.y)) - Math.min(...allPoints.map((p) => p.y))
135
- }
136
- },
137
- strokeWidth / 2
138
- );
139
- onPreview({
140
- type: PdfAnnotationSubtype.INK,
141
- bounds: rect,
142
- data: {
143
- rect,
144
- inkList,
145
- strokeWidth,
146
- strokeColor: ctx.inkData.strokeColor,
147
- opacity: 0.5
148
- }
149
- });
150
- },
151
- onPointerDown: (pos) => {
152
- const tool = getTool();
153
- const ctx = getToolContext();
154
- if (!tool || !(ctx == null ? void 0 : ctx.inkData) || !(ctx == null ? void 0 : ctx.targetSize)) return;
155
- const { inkList, strokeWidth } = translateAndScaleInk(ctx.inkData, ctx.targetSize, pos);
156
- const allPoints = inkList.flatMap((s) => s.points);
157
- if (allPoints.length === 0) return;
158
- const rect = expandRect(
159
- {
160
- origin: {
161
- x: Math.min(...allPoints.map((p) => p.x)),
162
- y: Math.min(...allPoints.map((p) => p.y))
163
- },
164
- size: {
165
- width: Math.max(...allPoints.map((p) => p.x)) - Math.min(...allPoints.map((p) => p.x)),
166
- height: Math.max(...allPoints.map((p) => p.y)) - Math.min(...allPoints.map((p) => p.y))
167
- }
168
- },
169
- strokeWidth / 2
170
- );
171
- let anno = {
172
- ...tool.defaults,
173
- inkList,
174
- rect,
175
- strokeWidth,
176
- strokeColor: ctx.inkData.strokeColor,
177
- opacity: 1,
178
- type: PdfAnnotationSubtype.INK,
179
- subject: "Signature",
180
- flags: tool.defaults.flags ?? ["print"],
181
- pageIndex: context.pageIndex,
182
- id: uuidV4(),
183
- created: /* @__PURE__ */ new Date()
184
- };
185
- const unclamped = anno.rect;
186
- anno = clampAnnotationToPage(anno, pageSize);
187
- const dx = anno.rect.origin.x - unclamped.origin.x;
188
- const dy = anno.rect.origin.y - unclamped.origin.y;
189
- if (dx !== 0 || dy !== 0) {
190
- anno = {
191
- ...anno,
192
- inkList: anno.inkList.map((s) => ({
193
- points: s.points.map((p) => ({ x: p.x + dx, y: p.y + dy }))
194
- }))
195
- };
196
- }
197
- onCommit(anno);
198
- onPreview(null);
199
- },
200
- onPointerLeave: () => {
201
- onPreview(null);
202
- }
203
- };
204
- }
205
- };
206
- const SIGNATURE_STAMP_TOOL_ID = "signatureStamp";
207
- const SIGNATURE_INK_TOOL_ID = "signatureInk";
208
- const signatureStampTool = defineAnnotationTool({
209
- id: SIGNATURE_STAMP_TOOL_ID,
210
- name: "Signature Stamp",
211
- labelKey: "signature.stamp",
212
- categories: ["annotation", "signature", "insert"],
213
- matchScore: () => 0,
214
- interaction: {
215
- exclusive: true,
216
- cursor: "copy",
217
- isDraggable: true,
218
- isResizable: true,
219
- isRotatable: false,
220
- lockAspectRatio: true,
221
- isGroupDraggable: true,
222
- isGroupResizable: false,
223
- isGroupRotatable: false
224
- },
225
- defaults: {
226
- type: PdfAnnotationSubtype.STAMP
227
- },
228
- behavior: {
229
- deactivateToolAfterCreate: true,
230
- selectAfterCreate: true,
231
- insertUpright: true,
232
- useAppearanceStream: true
233
- },
234
- pointerHandler: signatureStampHandlerFactory
384
+ //#endregion
385
+ //#region src/reducer.ts
386
+ const initialSignatureState = () => ({
387
+ snapshot: null,
388
+ verdicts: null,
389
+ target: null,
390
+ pending: null,
391
+ busy: false
235
392
  });
236
- const signatureInkTool = defineAnnotationTool({
237
- id: SIGNATURE_INK_TOOL_ID,
238
- name: "Signature Ink",
239
- labelKey: "signature.ink",
240
- categories: ["annotation", "signature", "insert"],
241
- matchScore: (a) => a.type === PdfAnnotationSubtype.INK && a.subject === "Signature" ? 20 : 0,
242
- interaction: {
243
- exclusive: true,
244
- cursor: "copy",
245
- isDraggable: true,
246
- isResizable: true,
247
- isRotatable: false,
248
- lockAspectRatio: true,
249
- isGroupDraggable: true,
250
- isGroupResizable: false,
251
- isGroupRotatable: false
252
- },
253
- defaults: {
254
- type: PdfAnnotationSubtype.INK
255
- },
256
- behavior: {
257
- deactivateToolAfterCreate: true,
258
- selectAfterCreate: true
259
- },
260
- transform: patching.patchInk,
261
- pointerHandler: signatureInkHandlerFactory
262
- });
263
- const signatureTools = [signatureStampTool, signatureInkTool];
264
- const _SignaturePlugin = class _SignaturePlugin extends BasePlugin {
265
- constructor(id, registry, config) {
266
- var _a;
267
- super(id, registry);
268
- this.config = config;
269
- this.entries = /* @__PURE__ */ new Map();
270
- this.entriesChange$ = createEmitter();
271
- this.activePlacement$ = createScopedEmitter((documentId, activePlacement) => ({ documentId, activePlacement }));
272
- this.annotation = null;
273
- this.toolChangeUnsubscribe = null;
274
- this.currentGhostUrl = null;
275
- this.annotation = ((_a = registry.getPlugin("annotation")) == null ? void 0 : _a.provides()) ?? null;
276
- if (this.annotation) {
277
- for (const tool of signatureTools) {
278
- this.annotation.addTool(tool);
279
- }
280
- this.toolChangeUnsubscribe = this.annotation.onActiveToolChange(({ documentId, tool }) => {
281
- if ((tool == null ? void 0 : tool.id) !== SIGNATURE_STAMP_TOOL_ID && (tool == null ? void 0 : tool.id) !== SIGNATURE_INK_TOOL_ID) {
282
- this.revokeGhostUrl();
283
- this.activePlacement$.emit(documentId, null);
284
- }
285
- });
286
- }
287
- }
288
- async initialize() {
289
- }
290
- buildCapability() {
291
- return {
292
- mode: this.config.mode,
293
- getEntries: () => this.getEntries(),
294
- addEntry: (entry) => this.addEntry(entry),
295
- removeEntry: (id) => this.removeEntry(id),
296
- loadEntries: (entries) => this.loadEntries(entries),
297
- exportEntries: () => this.exportEntries(),
298
- onEntriesChange: this.entriesChange$.on,
299
- forDocument: (documentId) => this.createSignatureScope(documentId),
300
- onActivePlacementChange: this.activePlacement$.onGlobal
301
- };
302
- }
303
- getEntries() {
304
- return Array.from(this.entries.values());
305
- }
306
- addEntry(entry) {
307
- const id = uuidV4();
308
- const fullEntry = {
309
- ...entry,
310
- id,
311
- createdAt: Date.now()
312
- };
313
- this.entries.set(id, fullEntry);
314
- this.dispatch(addSignatureEntry(id));
315
- this.emitEntriesChange();
316
- return id;
317
- }
318
- removeEntry(id) {
319
- if (!this.entries.has(id)) return;
320
- this.entries.delete(id);
321
- this.dispatch(removeSignatureEntry(id));
322
- this.emitEntriesChange();
323
- }
324
- loadEntries(entries) {
325
- for (const entry of entries) {
326
- this.entries.set(entry.id, entry);
327
- this.dispatch(addSignatureEntry(entry.id));
328
- }
329
- this.emitEntriesChange();
330
- }
331
- exportEntries() {
332
- return this.getEntries();
333
- }
334
- createSignatureScope(documentId) {
335
- return {
336
- activateSignaturePlacement: (entryId) => this.activatePlacement(documentId, entryId, SignatureFieldKind.Signature),
337
- activateInitialsPlacement: (entryId) => this.activatePlacement(documentId, entryId, SignatureFieldKind.Initials),
338
- deactivatePlacement: () => this.deactivatePlacement(documentId),
339
- getActivePlacement: () => this.activePlacement$.getValue(documentId) ?? null,
340
- onActivePlacementChange: this.activePlacement$.forScope(documentId)
341
- };
342
- }
343
- activatePlacement(documentId, entryId, kind) {
344
- const entry = this.entries.get(entryId);
345
- if (!entry || !this.annotation) return;
346
- const field = kind === SignatureFieldKind.Initials ? entry.initials : entry.signature;
347
- if (!field) return;
348
- this.revokeGhostUrl();
349
- if (field.creationType === SignatureCreationType.Draw) {
350
- if (entry.signature.creationType !== SignatureCreationType.Draw) return;
351
- const defaultSize = this.config.defaultSize ?? { width: 150, height: 50 };
352
- const referenceSize = entry.signature.inkData.size;
353
- const scale = Math.min(
354
- defaultSize.width / referenceSize.width,
355
- defaultSize.height / referenceSize.height
356
- );
357
- const targetSize = {
358
- width: field.inkData.size.width * scale,
359
- height: field.inkData.size.height * scale
360
- };
361
- this.annotation.setActiveTool(SIGNATURE_INK_TOOL_ID, {
362
- inkData: field.inkData,
363
- targetSize,
364
- entryId,
365
- kind
366
- });
367
- } else {
368
- if (!field.imageData) return;
369
- const ghostUrl = URL.createObjectURL(
370
- new Blob([field.imageData], { type: field.imageMimeType ?? "image/png" })
371
- );
372
- this.currentGhostUrl = ghostUrl;
373
- const defaultSize = this.config.defaultSize ?? { width: 150, height: 50 };
374
- let targetSize = defaultSize;
375
- if (field.imageSize) {
376
- const referenceSize = entry.signature.creationType !== SignatureCreationType.Draw ? entry.signature.imageSize ?? field.imageSize : field.imageSize;
377
- const scale = Math.min(
378
- defaultSize.width / referenceSize.width,
379
- defaultSize.height / referenceSize.height
380
- );
381
- targetSize = {
382
- width: field.imageSize.width * scale,
383
- height: field.imageSize.height * scale
384
- };
385
- }
386
- this.annotation.setActiveTool(SIGNATURE_STAMP_TOOL_ID, {
387
- imageData: field.imageData,
388
- ghostUrl,
389
- targetSize,
390
- entryId,
391
- kind
392
- });
393
- }
394
- this.activePlacement$.emit(documentId, { entryId, kind });
395
- }
396
- deactivatePlacement(documentId) {
397
- if (!this.annotation) return;
398
- this.annotation.setActiveTool(null);
399
- this.revokeGhostUrl();
400
- this.activePlacement$.emit(documentId, null);
401
- }
402
- revokeGhostUrl() {
403
- if (this.currentGhostUrl) {
404
- URL.revokeObjectURL(this.currentGhostUrl);
405
- this.currentGhostUrl = null;
406
- }
407
- }
408
- emitEntriesChange() {
409
- this.entriesChange$.emit(this.getEntries());
410
- }
411
- async destroy() {
412
- var _a;
413
- this.revokeGhostUrl();
414
- (_a = this.toolChangeUnsubscribe) == null ? void 0 : _a.call(this);
415
- this.entries.clear();
416
- }
417
- };
418
- _SignaturePlugin.id = SIGNATURE_PLUGIN_ID;
419
- let SignaturePlugin = _SignaturePlugin;
420
- const initialState = {
421
- entryIds: []
422
- };
423
- const signatureReducer = (state = initialState, action) => {
424
- switch (action.type) {
425
- case ADD_SIGNATURE_ENTRY:
426
- return {
427
- ...state,
428
- entryIds: [...state.entryIds, action.payload]
429
- };
430
- case REMOVE_SIGNATURE_ENTRY:
431
- return {
432
- ...state,
433
- entryIds: state.entryIds.filter((id) => id !== action.payload)
434
- };
435
- default:
436
- return state;
437
- }
438
- };
439
- function arrayBufferToBase64(buffer) {
440
- const bytes = new Uint8Array(buffer);
441
- let binary = "";
442
- for (let i = 0; i < bytes.length; i++) {
443
- binary += String.fromCharCode(bytes[i]);
444
- }
445
- return btoa(binary);
446
- }
447
- function base64ToArrayBuffer(base64) {
448
- const binary = atob(base64);
449
- const bytes = new Uint8Array(binary.length);
450
- for (let i = 0; i < binary.length; i++) {
451
- bytes[i] = binary.charCodeAt(i);
452
- }
453
- return bytes.buffer;
454
- }
455
- function serializeField(field) {
456
- if (field.creationType === SignatureCreationType.Draw) {
457
- return field;
458
- }
459
- const { imageData, ...rest } = field;
460
- return {
461
- ...rest,
462
- imageData: imageData ? arrayBufferToBase64(imageData) : void 0
463
- };
464
- }
465
- function deserializeField(field) {
466
- if (field.creationType === SignatureCreationType.Draw) {
467
- return field;
468
- }
469
- const { imageData, ...rest } = field;
470
- return {
471
- ...rest,
472
- imageData: imageData ? base64ToArrayBuffer(imageData) : void 0
473
- };
393
+ function signatureReducer(state, action) {
394
+ switch (action.type) {
395
+ case "SNAPSHOT": return {
396
+ ...state,
397
+ snapshot: action.snapshot
398
+ };
399
+ case "VERDICTS": return {
400
+ ...state,
401
+ verdicts: action.verdicts
402
+ };
403
+ case "TARGET": return {
404
+ ...state,
405
+ target: action.field
406
+ };
407
+ case "PENDING": return {
408
+ ...state,
409
+ pending: action.pending
410
+ };
411
+ case "BUSY": return {
412
+ ...state,
413
+ busy: action.busy
414
+ };
415
+ default: return state;
416
+ }
474
417
  }
475
- function serializeEntries(entries) {
476
- return entries.map((entry) => ({
477
- id: entry.id,
478
- createdAt: entry.createdAt,
479
- signature: serializeField(entry.signature),
480
- ...entry.initials && { initials: serializeField(entry.initials) }
481
- }));
482
- }
483
- function deserializeEntries(data) {
484
- return data.map((entry) => ({
485
- id: entry.id,
486
- createdAt: entry.createdAt,
487
- signature: deserializeField(entry.signature),
488
- ...entry.initials && { initials: deserializeField(entry.initials) }
489
- }));
490
- }
491
- const SignaturePluginPackage = {
492
- manifest,
493
- create: (registry, config) => new SignaturePlugin(SIGNATURE_PLUGIN_ID, registry, config),
494
- reducer: signatureReducer,
495
- initialState
496
- };
497
- export {
498
- ADD_SIGNATURE_ENTRY,
499
- REMOVE_SIGNATURE_ENTRY,
500
- SIGNATURE_INK_TOOL_ID,
501
- SIGNATURE_PLUGIN_ID,
502
- SIGNATURE_STAMP_TOOL_ID,
503
- SignatureCreationType,
504
- SignatureFieldKind,
505
- SignatureMode,
506
- SignaturePlugin,
507
- SignaturePluginPackage,
508
- addSignatureEntry,
509
- deserializeEntries,
510
- initialState,
511
- manifest,
512
- removeSignatureEntry,
513
- serializeEntries,
514
- signatureReducer
515
- };
516
- //# sourceMappingURL=index.js.map
418
+ //#endregion
419
+ //#region src/signature.plugin.ts
420
+ /**
421
+ * The signature plugin — the ACT of signing, document-scoped. It owns no
422
+ * mark: marks are stamp-library assets (or bytes the embedder brings), and
423
+ * the mark IS the appearance the engine draws into the field. Signing goes
424
+ * through `@embedpdf/core-signature` with the configured signer port; the
425
+ * engine seals; on the cloud the server publishes the new version.
426
+ *
427
+ * With the interaction hub and the stamp plugin present, an armed mark from
428
+ * a `signatures` library dropped over an unsigned signature field goes into
429
+ * the field (sign / visual fill / ask, by mode) instead of onto the page.
430
+ */
431
+ const signaturePlugin = (config = {}) => definePlugin({
432
+ id: "signature",
433
+ token: SignatureToken,
434
+ scope: "document",
435
+ requires: [FormToken],
436
+ optional: [
437
+ InteractionToken,
438
+ StampToken,
439
+ AnnotationToken
440
+ ],
441
+ initialState: initialSignatureState,
442
+ reduce: signatureReducer,
443
+ capability: (ctx) => createSignatureCapability(ctx, config),
444
+ init: (ctx) => {
445
+ const signature = ctx.get(SignatureToken);
446
+ signature.refresh().then((snapshot) => snapshot?.signatures.some((s) => s.signed) ? signature.validate() : null).catch((error) => globalThis.console?.error("[signature] initial read failed:", error));
447
+ const interaction = ctx.tryGet(InteractionToken);
448
+ const stamp = ctx.tryGet(StampToken);
449
+ if (interaction && stamp && ctx.documentId) ctx.cleanup(interaction.registerHandler(createArmedMarkHandler(ctx.documentId, signature, ctx.get(FormToken), stamp)));
450
+ }
451
+ });
452
+ //#endregion
453
+ export { INITIALS_MARK_NAME, SIGNATURES_LIBRARY_KIND, SIGNATURE_MARK_NAME, SignatureToken, createArmedMarkHandler, createTestSigner, indexedDbKeyStore, markRoleOf, memoryKeyStore, personalSigner, remoteSigner, signaturePlugin, webCryptoSigner };
454
+
455
+ //# sourceMappingURL=index.js.map