@embedpdf/plugin-annotation 3.0.0-next.12 → 3.0.0-next.14

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 (39) hide show
  1. package/dist/{capability-R7dgogJs.cjs → capability-Bt320iJP.cjs} +713 -597
  2. package/dist/capability-Bt320iJP.cjs.map +1 -0
  3. package/dist/{capability-Bh_1PL5v.js → capability-CoL4na7g.js} +712 -572
  4. package/dist/capability-CoL4na7g.js.map +1 -0
  5. package/dist/contract/host.d.cts +2 -2
  6. package/dist/contract/host.d.ts +2 -2
  7. package/dist/contract-C7rcuNp_.d.cts +34 -0
  8. package/dist/contract-CM2Ovdq_.d.ts +34 -0
  9. package/dist/contract-DtXE1hjY.js +32 -0
  10. package/dist/contract-DtXE1hjY.js.map +1 -0
  11. package/dist/contract-a4eaQWga.cjs +67 -0
  12. package/dist/contract-a4eaQWga.cjs.map +1 -0
  13. package/dist/contract.cjs +9 -7
  14. package/dist/contract.d.cts +3 -10
  15. package/dist/contract.d.ts +3 -10
  16. package/dist/contract.js +4 -8
  17. package/dist/index.cjs +58 -7
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +3 -3
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.js +51 -6
  22. package/dist/index.js.map +1 -1
  23. package/dist/internal.cjs +3 -2
  24. package/dist/internal.d.cts +2 -2
  25. package/dist/internal.d.ts +2 -2
  26. package/dist/internal.js +2 -1
  27. package/dist/tools-B3i1gjRs.cjs +589 -0
  28. package/dist/tools-B3i1gjRs.cjs.map +1 -0
  29. package/dist/tools-DsfqBn_a.js +566 -0
  30. package/dist/tools-DsfqBn_a.js.map +1 -0
  31. package/dist/types-BvB1vUKb.js.map +1 -1
  32. package/dist/{types-BgetZ4BK.d.ts → types-ChRnKozg.d.cts} +131 -14
  33. package/dist/types-Dlhdw2k_.cjs.map +1 -1
  34. package/dist/{types-BAPdbfPT.d.cts → types-Dmz3eRSG.d.ts} +131 -14
  35. package/package.json +9 -9
  36. package/dist/capability-Bh_1PL5v.js.map +0 -1
  37. package/dist/capability-R7dgogJs.cjs.map +0 -1
  38. package/dist/contract.cjs.map +0 -1
  39. package/dist/contract.js.map +0 -1
@@ -0,0 +1,589 @@
1
+ //#region src/tools.ts
2
+ /**
3
+ * The tool `armStamp` activates — and the only tool an armed payload survives
4
+ * onto (plus the legacy `annotation-stamp` tag, honoured for embedder configs
5
+ * written before the tags were unified). Placement consults the armed payload
6
+ * BEFORE the active tool's own `source` spec, so a payload must not outlive
7
+ * its tool onto a sibling stamp preset: an `image` preset's click has to open
8
+ * its own picker, not silently place whatever the stamp panel last armed.
9
+ */
10
+ const ARMED_STAMP_TOOL_ID = "stamp";
11
+ /**
12
+ * The creation properties each known authoring kind actually consumes. This is
13
+ * deliberately separate from the flat internal patch vocabulary: it gives tool
14
+ * configuration precise compile-time and runtime validation while mixed-selection
15
+ * edits can keep using `AnnotationPropsPatch`.
16
+ */
17
+ const TOOL_DEFAULT_KEYS = {
18
+ square: [
19
+ "color",
20
+ "interiorColor",
21
+ "opacity",
22
+ "strokeWidth",
23
+ "border"
24
+ ],
25
+ circle: [
26
+ "color",
27
+ "interiorColor",
28
+ "opacity",
29
+ "strokeWidth",
30
+ "border"
31
+ ],
32
+ line: [
33
+ "color",
34
+ "interiorColor",
35
+ "opacity",
36
+ "strokeWidth",
37
+ "border",
38
+ "lineEndings"
39
+ ],
40
+ polygon: [
41
+ "color",
42
+ "interiorColor",
43
+ "opacity",
44
+ "strokeWidth",
45
+ "border"
46
+ ],
47
+ polyline: [
48
+ "color",
49
+ "interiorColor",
50
+ "opacity",
51
+ "strokeWidth",
52
+ "border",
53
+ "lineEndings"
54
+ ],
55
+ ink: [
56
+ "color",
57
+ "opacity",
58
+ "strokeWidth",
59
+ "blendMode"
60
+ ],
61
+ "free-text": [
62
+ "fontFamily",
63
+ "fontSize",
64
+ "fontColor",
65
+ "bold",
66
+ "italic",
67
+ "underline",
68
+ "textAlign",
69
+ "opacity",
70
+ "interiorColor",
71
+ "color",
72
+ "strokeWidth",
73
+ "border"
74
+ ],
75
+ "free-text-callout": [
76
+ "fontFamily",
77
+ "fontSize",
78
+ "fontColor",
79
+ "bold",
80
+ "italic",
81
+ "underline",
82
+ "textAlign",
83
+ "opacity",
84
+ "interiorColor",
85
+ "color",
86
+ "strokeWidth",
87
+ "border",
88
+ "lineEndings"
89
+ ],
90
+ highlight: [
91
+ "color",
92
+ "opacity",
93
+ "blendMode"
94
+ ],
95
+ underline: [
96
+ "color",
97
+ "opacity",
98
+ "blendMode"
99
+ ],
100
+ strikeout: [
101
+ "color",
102
+ "opacity",
103
+ "blendMode"
104
+ ],
105
+ squiggly: [
106
+ "color",
107
+ "opacity",
108
+ "blendMode"
109
+ ],
110
+ caret: ["color", "opacity"],
111
+ redact: [
112
+ "color",
113
+ "interiorColor",
114
+ "opacity",
115
+ "fontFamily",
116
+ "fontSize",
117
+ "fontColor",
118
+ "textAlign"
119
+ ],
120
+ stamp: [],
121
+ link: ["link"],
122
+ text: [
123
+ "icon",
124
+ "color",
125
+ "opacity"
126
+ ],
127
+ "file-attachment": [
128
+ "icon",
129
+ "color",
130
+ "opacity"
131
+ ],
132
+ "widget-text": [
133
+ "color",
134
+ "interiorColor",
135
+ "strokeWidth",
136
+ "border",
137
+ "fontFamily",
138
+ "fontSize",
139
+ "fontColor",
140
+ "textAlign"
141
+ ],
142
+ "widget-choice": [
143
+ "color",
144
+ "interiorColor",
145
+ "strokeWidth",
146
+ "border",
147
+ "fontFamily",
148
+ "fontSize",
149
+ "fontColor",
150
+ "textAlign"
151
+ ],
152
+ "widget-toggle": [
153
+ "color",
154
+ "interiorColor",
155
+ "strokeWidth",
156
+ "border"
157
+ ],
158
+ "widget-box": [
159
+ "color",
160
+ "interiorColor",
161
+ "strokeWidth",
162
+ "border"
163
+ ]
164
+ };
165
+ const DRAW_TAGS = ["annotation-draw", "annotation-edit"];
166
+ const MARKUP_TAGS = ["text-select", "annotation-edit"];
167
+ /**
168
+ * Whether arming this ANNOTATION tool is touch consent (`Tool.touchDirect`):
169
+ * drag-CREATE tools — shape/ink draw and text-markup select — take
170
+ * single-finger touch wholesale (finger draws/marks, two fingers navigate:
171
+ * the drawing-app convention). Click-to-PLACE tools (stamp, note) do not: a
172
+ * tap places without any claim, and drags keep scrolling. Derived from the
173
+ * gesture tags, so custom registered tools inherit the right behavior. Only
174
+ * annotation-owned tools run through this — the built-in `pointer` tool also
175
+ * enables `text-select`, but it is registered by the hub, not here.
176
+ */
177
+ const isTouchDirect = (enables) => enables.has("annotation-draw") || enables.has("text-select");
178
+ /**
179
+ * The built-in tools — a data mirror of v2's registrations (shapes + lines + ink
180
+ * + free text in the draw channel, text markup + caret behind text selection, and
181
+ * the click-to-place stamp). Order is display-neutral; the toolbar owns layout.
182
+ */
183
+ const DEFAULT_TOOLS = [
184
+ {
185
+ id: "square",
186
+ subtype: "square",
187
+ cursor: "crosshair",
188
+ enables: DRAW_TAGS,
189
+ defaults: { strokeWidth: 6 },
190
+ clickCreate: {
191
+ width: 80,
192
+ height: 60
193
+ }
194
+ },
195
+ {
196
+ id: "circle",
197
+ subtype: "circle",
198
+ cursor: "crosshair",
199
+ enables: DRAW_TAGS,
200
+ defaults: { strokeWidth: 6 },
201
+ clickCreate: {
202
+ width: 80,
203
+ height: 60
204
+ }
205
+ },
206
+ {
207
+ id: "line",
208
+ subtype: "line",
209
+ cursor: "crosshair",
210
+ enables: DRAW_TAGS,
211
+ defaults: { strokeWidth: 6 },
212
+ clickCreate: { length: 80 }
213
+ },
214
+ {
215
+ id: "distance",
216
+ extends: "line",
217
+ intent: "LineDimension",
218
+ clickCreate: false,
219
+ defaults: {
220
+ strokeWidth: 1,
221
+ lineEndings: {
222
+ start: "closed-arrow",
223
+ end: "closed-arrow"
224
+ }
225
+ },
226
+ measurement: {
227
+ caption: {
228
+ enabled: true,
229
+ position: "inline"
230
+ },
231
+ leader: {
232
+ length: 12,
233
+ extension: 5,
234
+ offset: 0
235
+ }
236
+ }
237
+ },
238
+ {
239
+ id: "calibrate",
240
+ extends: "line",
241
+ enables: ["annotation-draw"],
242
+ defaults: { strokeWidth: 1 },
243
+ clickCreate: false,
244
+ meta: { capture: true }
245
+ },
246
+ {
247
+ id: "polygon",
248
+ subtype: "polygon",
249
+ cursor: "crosshair",
250
+ enables: DRAW_TAGS,
251
+ defaults: { strokeWidth: 6 }
252
+ },
253
+ {
254
+ id: "polyline",
255
+ subtype: "polyline",
256
+ cursor: "crosshair",
257
+ enables: DRAW_TAGS,
258
+ defaults: { strokeWidth: 6 }
259
+ },
260
+ {
261
+ id: "perimeter",
262
+ extends: "polyline",
263
+ intent: "PolyLineDimension",
264
+ defaults: {
265
+ strokeWidth: 1,
266
+ lineEndings: {
267
+ start: "none",
268
+ end: "none"
269
+ }
270
+ },
271
+ measurement: { caption: { enabled: true } }
272
+ },
273
+ {
274
+ id: "area",
275
+ extends: "polygon",
276
+ intent: "PolygonDimension",
277
+ defaults: { strokeWidth: 1 },
278
+ measurement: { caption: { enabled: true } }
279
+ },
280
+ {
281
+ id: "ink",
282
+ subtype: "ink",
283
+ cursor: "crosshair",
284
+ enables: DRAW_TAGS,
285
+ defaults: {
286
+ color: "#ef4444",
287
+ strokeWidth: 6,
288
+ blendMode: "normal"
289
+ },
290
+ ink: { groupStrokesMs: 800 }
291
+ },
292
+ {
293
+ id: "ink-highlight",
294
+ extends: "ink",
295
+ intent: "ink-highlight",
296
+ defaults: {
297
+ color: "#ffcd45",
298
+ strokeWidth: 14,
299
+ blendMode: "multiply"
300
+ },
301
+ ink: { straighten: {
302
+ deviationThreshold: .15,
303
+ axisSnapDegrees: 15
304
+ } }
305
+ },
306
+ {
307
+ id: "free-text",
308
+ subtype: "free-text",
309
+ cursor: "crosshair",
310
+ enables: DRAW_TAGS,
311
+ defaults: { fontColor: "#ef4444" },
312
+ upright: true,
313
+ clickCreate: {
314
+ width: 180,
315
+ height: 40,
316
+ anchor: "top-left"
317
+ }
318
+ },
319
+ {
320
+ id: "free-text-callout",
321
+ subtype: "free-text-callout",
322
+ propsKind: "free-text",
323
+ cursor: "crosshair",
324
+ enables: DRAW_TAGS,
325
+ defaults: {
326
+ strokeWidth: 6,
327
+ lineEndings: { end: "open-arrow" }
328
+ },
329
+ upright: true
330
+ },
331
+ {
332
+ id: "highlight",
333
+ subtype: "highlight",
334
+ cursor: "default",
335
+ enables: MARKUP_TAGS,
336
+ defaults: {
337
+ color: "#ffe16a",
338
+ blendMode: "multiply"
339
+ },
340
+ selection: { kind: "markup" }
341
+ },
342
+ {
343
+ id: "redact",
344
+ subtype: "redact",
345
+ cursor: "crosshair",
346
+ enables: [
347
+ "text-select",
348
+ "annotation-draw",
349
+ "annotation-edit"
350
+ ],
351
+ defaults: {
352
+ color: "#e44234",
353
+ interiorColor: "#000000",
354
+ fontColor: "#ffffff",
355
+ opacity: 1
356
+ },
357
+ selection: { kind: "markup" }
358
+ },
359
+ {
360
+ id: "underline",
361
+ subtype: "underline",
362
+ cursor: "default",
363
+ enables: MARKUP_TAGS,
364
+ defaults: { color: "#ef4444" },
365
+ selection: { kind: "markup" }
366
+ },
367
+ {
368
+ id: "strikeout",
369
+ subtype: "strikeout",
370
+ cursor: "default",
371
+ enables: MARKUP_TAGS,
372
+ defaults: { color: "#ef4444" },
373
+ selection: { kind: "markup" }
374
+ },
375
+ {
376
+ id: "squiggly",
377
+ subtype: "squiggly",
378
+ cursor: "default",
379
+ enables: MARKUP_TAGS,
380
+ defaults: { color: "#ef4444" },
381
+ selection: { kind: "markup" }
382
+ },
383
+ {
384
+ id: "insert-text",
385
+ subtype: "caret",
386
+ propsKind: "caret",
387
+ preset: "caret",
388
+ cursor: "default",
389
+ enables: MARKUP_TAGS,
390
+ defaults: { color: "#ef4444" },
391
+ selection: {
392
+ kind: "text-edit",
393
+ operation: "insert"
394
+ }
395
+ },
396
+ {
397
+ id: "replace-text",
398
+ subtype: "strikeout",
399
+ propsKind: "strikeout",
400
+ cursor: "default",
401
+ enables: MARKUP_TAGS,
402
+ defaults: { color: "#ef4444" },
403
+ selection: {
404
+ kind: "text-edit",
405
+ operation: "replace"
406
+ }
407
+ },
408
+ {
409
+ id: ARMED_STAMP_TOOL_ID,
410
+ subtype: "stamp",
411
+ cursor: "copy",
412
+ enables: ["annotation-place", "annotation-edit"],
413
+ source: {
414
+ kind: "prompt",
415
+ accept: "image/png,image/jpeg,application/pdf"
416
+ },
417
+ upright: true,
418
+ ghost: { mode: "footprint" }
419
+ },
420
+ {
421
+ id: "link",
422
+ subtype: "link",
423
+ cursor: "crosshair",
424
+ enables: DRAW_TAGS
425
+ },
426
+ {
427
+ id: "note",
428
+ subtype: "text",
429
+ cursor: "crosshair",
430
+ enables: ["annotation-place", "annotation-edit"],
431
+ defaults: {
432
+ icon: "comment",
433
+ color: "#facc15"
434
+ },
435
+ flags: {
436
+ noZoom: true,
437
+ noRotate: true
438
+ },
439
+ upright: true,
440
+ ghost: { mode: "footprint" }
441
+ },
442
+ {
443
+ id: "attachment",
444
+ subtype: "file-attachment",
445
+ cursor: "copy",
446
+ enables: ["annotation-place", "annotation-edit"],
447
+ source: { kind: "prompt" },
448
+ defaults: {
449
+ icon: "paperclip",
450
+ color: "#facc15"
451
+ },
452
+ flags: {
453
+ noZoom: true,
454
+ noRotate: true
455
+ },
456
+ upright: true,
457
+ ghost: { mode: "footprint" }
458
+ }
459
+ ];
460
+ /** Merge two default patches, `b` over `a`, with line endings merged per side. */
461
+ function mergeDefaults(a, b) {
462
+ if (!a) return b;
463
+ if (!b) return a;
464
+ const merged = {
465
+ ...a,
466
+ ...b
467
+ };
468
+ if (a.lineEndings || b.lineEndings) merged.lineEndings = {
469
+ ...a.lineEndings,
470
+ ...b.lineEndings
471
+ };
472
+ return merged;
473
+ }
474
+ /** Overlay a same-id override onto a base definition (configure a built-in). */
475
+ function mergeDef(base, over) {
476
+ return {
477
+ ...base,
478
+ ...over,
479
+ enables: over.enables ?? base.enables,
480
+ meta: base.meta || over.meta ? {
481
+ ...base.meta,
482
+ ...over.meta
483
+ } : void 0,
484
+ defaults: mergeDefaults(base.defaults, over.defaults),
485
+ flags: base.flags || over.flags ? {
486
+ ...base.flags,
487
+ ...over.flags
488
+ } : void 0,
489
+ ink: base.ink || over.ink ? {
490
+ ...base.ink,
491
+ ...over.ink
492
+ } : void 0
493
+ };
494
+ }
495
+ function validateDefaults(tool) {
496
+ if (!tool.defaults) return;
497
+ const allowed = TOOL_DEFAULT_KEYS[tool.subtype];
498
+ if (!allowed) return;
499
+ const keys = new Set(allowed);
500
+ for (const key of Object.keys(tool.defaults)) if (!keys.has(key)) throw new Error(`[annotation] tool '${tool.id}' does not support default '${key}' for '${tool.subtype}'`);
501
+ }
502
+ /**
503
+ * Resolve {@link DEFAULT_TOOLS} + embedder overrides into the ready-to-use table.
504
+ * A same-id override merges over the built-in; a new id adds a tool; `extends`
505
+ * inherits from an already-defined tool. Keyed by id, so lookups are O(1).
506
+ */
507
+ function buildToolRegistry(overrides = []) {
508
+ const defs = /* @__PURE__ */ new Map();
509
+ for (const d of DEFAULT_TOOLS) defs.set(d.id, d);
510
+ for (const o of overrides) {
511
+ const prev = defs.get(o.id);
512
+ defs.set(o.id, prev ? mergeDef(prev, o) : o);
513
+ }
514
+ const out = /* @__PURE__ */ new Map();
515
+ const resolving = /* @__PURE__ */ new Set();
516
+ const resolve = (id) => {
517
+ const cached = out.get(id);
518
+ if (cached) return cached;
519
+ const def = defs.get(id);
520
+ if (!def) throw new Error(`[annotation] tool '${id}' extends an unknown tool`);
521
+ let base;
522
+ if (def.extends && def.extends !== id && !resolving.has(def.extends)) {
523
+ resolving.add(id);
524
+ base = resolve(def.extends);
525
+ resolving.delete(id);
526
+ }
527
+ const subtype = def.subtype ?? base?.subtype ?? def.id;
528
+ const resolved = {
529
+ id: def.id,
530
+ subtype,
531
+ preset: def.preset ?? def.id,
532
+ propsKind: def.propsKind ?? base?.propsKind ?? subtype,
533
+ cursor: def.cursor ?? base?.cursor ?? "crosshair",
534
+ enables: new Set(def.enables ?? (base ? [...base.enables] : [])),
535
+ defaults: mergeDefaults(base?.defaults, def.defaults),
536
+ flags: base?.flags || def.flags ? {
537
+ ...base?.flags,
538
+ ...def.flags
539
+ } : void 0,
540
+ source: def.source ?? base?.source,
541
+ selection: def.selection ?? base?.selection,
542
+ intent: def.intent ?? base?.intent,
543
+ measurement: def.measurement ?? base?.measurement,
544
+ ink: base?.ink || def.ink ? {
545
+ ...base?.ink,
546
+ ...def.ink
547
+ } : void 0,
548
+ upright: def.upright ?? base?.upright ?? false,
549
+ clickCreate: def.clickCreate ?? base?.clickCreate ?? false,
550
+ ghost: def.ghost ?? base?.ghost ?? false,
551
+ meta: base?.meta || def.meta ? {
552
+ ...base?.meta,
553
+ ...def.meta
554
+ } : void 0
555
+ };
556
+ validateDefaults(resolved);
557
+ out.set(id, resolved);
558
+ return resolved;
559
+ };
560
+ for (const id of defs.keys()) resolve(id);
561
+ return out;
562
+ }
563
+ //#endregion
564
+ Object.defineProperty(exports, "ARMED_STAMP_TOOL_ID", {
565
+ enumerable: true,
566
+ get: function() {
567
+ return ARMED_STAMP_TOOL_ID;
568
+ }
569
+ });
570
+ Object.defineProperty(exports, "DEFAULT_TOOLS", {
571
+ enumerable: true,
572
+ get: function() {
573
+ return DEFAULT_TOOLS;
574
+ }
575
+ });
576
+ Object.defineProperty(exports, "buildToolRegistry", {
577
+ enumerable: true,
578
+ get: function() {
579
+ return buildToolRegistry;
580
+ }
581
+ });
582
+ Object.defineProperty(exports, "isTouchDirect", {
583
+ enumerable: true,
584
+ get: function() {
585
+ return isTouchDirect;
586
+ }
587
+ });
588
+
589
+ //# sourceMappingURL=tools-B3i1gjRs.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-B3i1gjRs.cjs","names":[],"sources":["../src/tools.ts"],"sourcesContent":["/**\n * The annotation TOOL registry — the one place that says which tools exist and\n * what each one authors. A tool is a named authoring PRESET, not a new kind: it\n * binds an `id` to a PDF `subtype`, a bag of per-tool `defaults`, a cursor, and\n * the interaction TAGS it turns on. Two tools can share a subtype but differ in\n * defaults (an \"arrow\" is a `line` with an arrowhead default), which is exactly\n * why defaults are keyed by the tool id (the `preset`), never by the subtype.\n *\n * The built-in {@link DEFAULT_TOOLS} reproduce v2's tool set; an embedder tweaks\n * or extends them with `annotationPlugin({ tools: [...] })`. Same-id entries MERGE\n * over the built-in (configure it); a new id ADDS a tool; `extends` inherits an\n * existing tool's subtype/cursor/tags so a preset is one line.\n */\nimport type {\n AnnotationFlags,\n AnnotationPropsPatch,\n ClickCreate,\n InkStraightenOptions,\n PropKey,\n Subtype,\n} from '@embedpdf/core-annotation';\nimport type {\n BinarySource,\n InkIntent,\n LineDimensionCaption,\n LineLeader,\n ShapeDimensionCaption,\n} from '@embedpdf/engine-core/runtime';\n\n/**\n * How a stamp-family tool resolves the image bytes it places — pure DATA, so a\n * tool table stays JSON-serializable (config can come from a file, a DB, or the\n * server). Evaluated at CLICK time, so the user picks the spot first, then the\n * source:\n * - `{ kind: 'bytes' }` — fixed bytes: a company rubber-stamp.\n * - `{ kind: 'prompt' }` — ask the environment for bytes. The plugin does NOT\n * know how (a file dialog is DOM); the file-picker port (`FilePickerProvider`),\n * installed by the framework adapter, fulfils it. Resolve `null` there to cancel.\n */\nexport type StampSourceSpec = { kind: 'bytes'; source: BinarySource } | PromptSourceSpec;\n\n/**\n * The ask-the-environment resolution, shared by every click-then-pick tool\n * (a stamp's `'prompt'` source, the file-attachment tool). `accept` is the\n * file-dialog filter the picker request carries — a UX hint only, the engine\n * sniffs/validates the bytes for real. Unset = any file.\n */\nexport interface PromptSourceSpec {\n kind: 'prompt';\n accept?: string;\n}\n\n/**\n * The tool `armStamp` activates — and the only tool an armed payload survives\n * onto (plus the legacy `annotation-stamp` tag, honoured for embedder configs\n * written before the tags were unified). Placement consults the armed payload\n * BEFORE the active tool's own `source` spec, so a payload must not outlive\n * its tool onto a sibling stamp preset: an `image` preset's click has to open\n * its own picker, not silently place whatever the stamp panel last armed.\n */\nexport const ARMED_STAMP_TOOL_ID = 'stamp';\n\n/** Declarative result of committing a text selection while a tool is active. */\nexport type SelectionAuthoring =\n | { kind: 'markup' }\n | { kind: 'text-edit'; operation: 'insert' | 'replace' };\n\n/**\n * The armed tool's IN-PAGE preview:\n * - `footprint` — the EXACT box a click would place, in the page (content\n * space, page-clamped, WYSIWYG). Meaningful only when the box is\n * determinable: an armed stamp payload, or a `clickCreate` size.\n * - `false` — no preview.\n * WHICH tool is armed is the CURSOR's job, not a ghost's: give the tool an\n * image cursor built from your toolbar icon (the hub's `setToolCursor` + the\n * web package's `svgCursor`), and the hub's claim/gap arbitration keeps it\n * honest over annotations, form fields, and page gaps.\n */\nexport type GhostPolicy = false | { mode: 'footprint' };\n\n/** Time/geometry policy for freehand ink authoring. */\nexport interface InkAuthoringOptions {\n /** Group strokes made within this interval into one `/InkList`; 0 commits immediately. */\n groupStrokesMs?: number;\n /** Optional pure straight-line recognition applied to each completed stroke. */\n straighten?: InkStraightenOptions;\n}\n\n/**\n * The creation properties each known authoring kind actually consumes. This is\n * deliberately separate from the flat internal patch vocabulary: it gives tool\n * configuration precise compile-time and runtime validation while mixed-selection\n * edits can keep using `AnnotationPropsPatch`.\n */\nexport const TOOL_DEFAULT_KEYS = {\n square: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border'],\n circle: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border'],\n line: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border', 'lineEndings'],\n polygon: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border'],\n polyline: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border', 'lineEndings'],\n ink: ['color', 'opacity', 'strokeWidth', 'blendMode'],\n 'free-text': [\n 'fontFamily',\n 'fontSize',\n 'fontColor',\n 'bold',\n 'italic',\n 'underline',\n 'textAlign',\n 'opacity',\n 'interiorColor',\n 'color',\n 'strokeWidth',\n 'border',\n ],\n 'free-text-callout': [\n 'fontFamily',\n 'fontSize',\n 'fontColor',\n 'bold',\n 'italic',\n 'underline',\n 'textAlign',\n 'opacity',\n 'interiorColor',\n 'color',\n 'strokeWidth',\n 'border',\n 'lineEndings',\n ],\n highlight: ['color', 'opacity', 'blendMode'],\n underline: ['color', 'opacity', 'blendMode'],\n strikeout: ['color', 'opacity', 'blendMode'],\n squiggly: ['color', 'opacity', 'blendMode'],\n caret: ['color', 'opacity'],\n redact: ['color', 'interiorColor', 'opacity', 'fontFamily', 'fontSize', 'fontColor', 'textAlign'],\n stamp: [],\n // A link preset may carry a FIXED target ('docs-link' style one-click links).\n link: ['link'],\n text: ['icon', 'color', 'opacity'],\n 'file-attachment': ['icon', 'color', 'opacity'],\n // Widget CLIENT kinds (the form plugin's palette tools): the same key sets\n // the kind table declares — box styling for every family, /DA text styling\n // for the text-bearing ones. The engine maps them onto /MK //BS //DA //Q.\n 'widget-text': [\n 'color',\n 'interiorColor',\n 'strokeWidth',\n 'border',\n 'fontFamily',\n 'fontSize',\n 'fontColor',\n 'textAlign',\n ],\n 'widget-choice': [\n 'color',\n 'interiorColor',\n 'strokeWidth',\n 'border',\n 'fontFamily',\n 'fontSize',\n 'fontColor',\n 'textAlign',\n ],\n 'widget-toggle': ['color', 'interiorColor', 'strokeWidth', 'border'],\n // The bare box every other widget family ingests as (a signature field's\n // widget among them): box styling only — no text to style.\n 'widget-box': ['color', 'interiorColor', 'strokeWidth', 'border'],\n} as const satisfies Record<string, readonly PropKey[]>;\n\nexport type ToolAuthoringKind = keyof typeof TOOL_DEFAULT_KEYS;\ntype ToolDefaultKey<K extends ToolAuthoringKind> = (typeof TOOL_DEFAULT_KEYS)[K][number];\nexport type ToolDefaultsFor<K extends ToolAuthoringKind> = [ToolDefaultKey<K>] extends [never]\n ? never\n : Pick<AnnotationPropsPatch, ToolDefaultKey<K>>;\n\n/**\n * A tool definition — the public config vocabulary. Every field except `id` is\n * optional: the common cases are \"configure a built-in\" (`{ id, defaults }`) and\n * \"add a preset\" (`{ id, extends, defaults }`).\n */\nexport interface AnnotationToolDef<K extends ToolAuthoringKind = ToolAuthoringKind> {\n /** Stable tool id — the value passed to `activateTool` and the `defaults` key. */\n id: string;\n /** Inherit `subtype` / `propsKind` / `cursor` / `enables` / `source` /\n * `selection` / `intent` / `ink` / `meta`\n * from an existing tool id (a built-in or another entry). Own fields win. */\n extends?: string;\n /** The ROUTING KIND this tool authors — the core's client kind (the geometry\n * it draws + the props key). Usually the PDF subtype, but not always:\n * `free-text-callout` routes the callout gesture onto a free-text\n * annotation, and `widget-text`/`widget-choice`/`widget-toggle` are client\n * views of the ONE PDF `widget` subtype (a form tool's commit goes through\n * `doc.forms`, never this plugin — see the form plugin's tool table).\n * Defaults to the inherited kind, or the id when neither is given. */\n subtype?: Subtype;\n /** The kind whose editable-property specs a style panel shows for this tool.\n * Defaults to `subtype` (a callout authors `free-text` props, for example). */\n propsKind?: string;\n /** ADVANCED: the `defaults` key this tool reads/writes. Defaults to the id, and\n * that is almost always right — override it only to alias a shared defaults bag\n * (the built-in insert-caret tool points its preset at the `caret` key). */\n preset?: string;\n /** Seed defaults for newly drawn annotations — the flat AnnotationProps patch,\n * merged over any inherited defaults (line endings merge per side). */\n defaults?: ToolDefaultsFor<K>;\n /**\n * `/F` annotation flags seeded on everything this tool creates, merged over\n * the drawn default (`print` set — Acrobat parity) and any inherited seed.\n * A note tool passes `{ noZoom: true, noRotate: true }` so its icons stay\n * screen-sized and upright (the spec's rule for Text annotations).\n */\n flags?: Partial<AnnotationFlags>;\n /** The pointer cursor while this tool is active. Defaults inherited / `crosshair`. */\n cursor?: string;\n /** Interaction capability tags this tool enables (which handlers wake up). */\n enables?: string[];\n /**\n * How the click-to-place payload resolves. Stamps take the full spec\n * ({@link StampSourceSpec}: fixed bytes or prompt); file attachments are\n * prompt-only ({@link PromptSourceSpec} — the file always comes from the\n * environment). Other kinds carry no payload.\n */\n source?: K extends 'stamp'\n ? StampSourceSpec\n : K extends 'file-attachment'\n ? PromptSourceSpec\n : never;\n /** What a committed text selection authors. Omit for pointer/click tools. */\n selection?: SelectionAuthoring;\n /** PDF `/IT` authored by an intent-bearing ink preset. */\n intent?: K extends 'ink'\n ? InkIntent\n : K extends 'line'\n ? 'LineDimension'\n : K extends 'polygon'\n ? 'PolygonDimension'\n : K extends 'polyline'\n ? 'PolyLineDimension'\n : never;\n /** Caption defaults for a measurement preset. Shape centers use absolute PDF coordinates. */\n measurement?: K extends 'line'\n ? { caption: LineDimensionCaption; leader?: LineLeader }\n : K extends 'polygon' | 'polyline'\n ? { caption: ShapeDimensionCaption }\n : never;\n /** Ink-only stroke grouping and straightening policy. */\n ink?: K extends 'ink' ? InkAuthoringOptions : never;\n /**\n * Place annotations UPRIGHT: counter-rotate what this tool creates against the\n * page's TOTAL display rotation (document /Rotate + any stage view rotation),\n * so it reads horizontally exactly as the author saw it — Adobe's behaviour\n * for stamps and text on rotated pages. WYSIWYG at authoring time: the\n * rotation is baked into the annotation, so a save keeps what the author saw\n * (other viewers apply only /Rotate — document that when view rotation is in\n * play). Box kinds only (stamp / free-text, where reading orientation is\n * meaningful); ignored by vertex kinds and callouts. Default: on for the\n * built-in `stamp` and `free-text` tools, off elsewhere.\n */\n upright?: boolean;\n /**\n * What a bare CLICK creates (v2's `clickBehavior`): a default-size shape\n * centred on the point / a default-length line from it / free-text's\n * type-here box — page-clamped, in PDF pt. `false` = drag-only. Defaults:\n * on for the built-in square/circle/line/free-text, off elsewhere.\n */\n clickCreate?: ClickCreate | false;\n /**\n * The armed-tool in-page preview ({@link GhostPolicy}). Defaults: inherited,\n * else off (the built-in stamp declares `footprint`).\n */\n ghost?: GhostPolicy;\n /** Opaque presentation hints (label/icon…) for a toolbar or cursor that builds\n * itself from the tool table. `capture: true` captures a line gesture without\n * creating an annotation (used by calibration). */\n meta?: Record<string, unknown>;\n}\n\nexport interface BuiltinToolKindMap {\n square: 'square';\n circle: 'circle';\n line: 'line';\n distance: 'line';\n perimeter: 'polyline';\n area: 'polygon';\n calibrate: 'line';\n polygon: 'polygon';\n polyline: 'polyline';\n ink: 'ink';\n 'ink-highlight': 'ink';\n 'free-text': 'free-text';\n 'free-text-callout': 'free-text-callout';\n highlight: 'highlight';\n underline: 'underline';\n strikeout: 'strikeout';\n squiggly: 'squiggly';\n 'insert-text': 'caret';\n 'replace-text': 'strikeout';\n redact: 'redact';\n stamp: 'stamp';\n note: 'text';\n attachment: 'file-attachment';\n link: 'link';\n}\n\ntype DirectToolDef = {\n [K in ToolAuthoringKind]: Omit<AnnotationToolDef<K>, 'subtype'> & { subtype: K };\n}[ToolAuthoringKind];\ntype BuiltinToolOverride = {\n [I in keyof BuiltinToolKindMap]: Omit<\n AnnotationToolDef<BuiltinToolKindMap[I]>,\n 'id' | 'extends' | 'subtype'\n > & {\n id: I;\n extends?: never;\n subtype?: never;\n };\n}[keyof BuiltinToolKindMap];\ntype ExtendedBuiltinToolDef = {\n [I in keyof BuiltinToolKindMap]: Omit<\n AnnotationToolDef<BuiltinToolKindMap[I]>,\n 'extends' | 'subtype'\n > & {\n extends: I;\n subtype?: never;\n };\n}[keyof BuiltinToolKindMap];\n\n/** Public tool input: known overrides, known-base presets, or an explicit subtype. */\nexport type AnnotationToolInput = DirectToolDef | BuiltinToolOverride | ExtendedBuiltinToolDef;\n\n/** A fully-resolved tool — what the plugin, handlers, and registration loop read. */\nexport interface ResolvedTool {\n id: string;\n /** Routing token for the draw core + the created annotation's PDF subtype. */\n subtype: Subtype;\n /** The `defaults` key (always the tool id) — keeps same-subtype tools apart. */\n preset: string;\n /** The `propsFor` key for the style panel. */\n propsKind: string;\n cursor: string;\n enables: ReadonlySet<string>;\n defaults?: AnnotationPropsPatch;\n /** `/F` seed for created annotations (see {@link AnnotationToolDef.flags}). */\n flags?: Partial<AnnotationFlags>;\n source?: StampSourceSpec;\n selection?: SelectionAuthoring;\n intent?: InkIntent | 'LineDimension' | 'PolyLineDimension' | 'PolygonDimension';\n measurement?: { caption: LineDimensionCaption | ShapeDimensionCaption; leader?: LineLeader };\n ink?: InkAuthoringOptions;\n /** Counter-rotate creations against the page's display rotation (see\n * {@link AnnotationToolDef.upright}). */\n upright: boolean;\n /** What a bare click creates, or `false` for drag-only. */\n clickCreate: ClickCreate | false;\n /** The armed-tool in-page preview ({@link GhostPolicy}). */\n ghost: GhostPolicy;\n meta?: Record<string, unknown>;\n}\n\n// ── field groups shared by the built-ins (keeps the table readable) ──────────\nconst DRAW_TAGS = ['annotation-draw', 'annotation-edit'];\nconst MARKUP_TAGS = ['text-select', 'annotation-edit'];\n\n/**\n * Whether arming this ANNOTATION tool is touch consent (`Tool.touchDirect`):\n * drag-CREATE tools — shape/ink draw and text-markup select — take\n * single-finger touch wholesale (finger draws/marks, two fingers navigate:\n * the drawing-app convention). Click-to-PLACE tools (stamp, note) do not: a\n * tap places without any claim, and drags keep scrolling. Derived from the\n * gesture tags, so custom registered tools inherit the right behavior. Only\n * annotation-owned tools run through this — the built-in `pointer` tool also\n * enables `text-select`, but it is registered by the hub, not here.\n */\nexport const isTouchDirect = (enables: ReadonlySet<string>): boolean =>\n enables.has('annotation-draw') || enables.has('text-select');\n\n/**\n * The built-in tools — a data mirror of v2's registrations (shapes + lines + ink\n * + free text in the draw channel, text markup + caret behind text selection, and\n * the click-to-place stamp). Order is display-neutral; the toolbar owns layout.\n */\nexport const DEFAULT_TOOLS: AnnotationToolInput[] = [\n // shapes / lines / ink / free text — the `annotation-draw` gesture.\n // Draw tools carry v2's click-create defaults (a bare click places a\n // default-size annotation); armed-tool identity rides the cursor, so none\n // of them needs a ghost.\n {\n id: 'square',\n subtype: 'square',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n clickCreate: { width: 80, height: 60 },\n },\n {\n id: 'circle',\n subtype: 'circle',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n clickCreate: { width: 80, height: 60 },\n },\n {\n id: 'line',\n subtype: 'line',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n clickCreate: { length: 80 },\n },\n {\n id: 'distance',\n extends: 'line',\n intent: 'LineDimension',\n clickCreate: false,\n defaults: { strokeWidth: 1, lineEndings: { start: 'closed-arrow', end: 'closed-arrow' } },\n measurement: {\n caption: { enabled: true, position: 'inline' },\n leader: { length: 12, extension: 5, offset: 0 },\n },\n },\n {\n id: 'calibrate',\n extends: 'line',\n enables: ['annotation-draw'],\n defaults: { strokeWidth: 1 },\n clickCreate: false,\n meta: { capture: true },\n },\n {\n id: 'polygon',\n subtype: 'polygon',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n },\n {\n id: 'polyline',\n subtype: 'polyline',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n },\n {\n id: 'perimeter',\n extends: 'polyline',\n intent: 'PolyLineDimension',\n defaults: { strokeWidth: 1, lineEndings: { start: 'none', end: 'none' } },\n measurement: { caption: { enabled: true } },\n },\n {\n id: 'area',\n extends: 'polygon',\n intent: 'PolygonDimension',\n defaults: { strokeWidth: 1 },\n measurement: { caption: { enabled: true } },\n },\n {\n id: 'ink',\n subtype: 'ink',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { color: '#ef4444', strokeWidth: 6, blendMode: 'normal' },\n ink: { groupStrokesMs: 800 },\n },\n {\n id: 'ink-highlight',\n extends: 'ink',\n intent: 'ink-highlight',\n defaults: { color: '#ffcd45', strokeWidth: 14, blendMode: 'multiply' },\n ink: {\n straighten: { deviationThreshold: 0.15, axisSnapDegrees: 15 },\n },\n },\n {\n id: 'free-text',\n subtype: 'free-text',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { fontColor: '#ef4444' },\n upright: true,\n // Top-left anchored: the box hangs where you'll type (the kind's reading\n // feel); shapes default to `center`. Anchoring is explicit policy data.\n clickCreate: { width: 180, height: 40, anchor: 'top-left' },\n },\n {\n // Routes on the `free-text-callout` subtype token but authors a `free-text`\n // annotation (leader + box). Its leader defaults to an open arrowhead.\n // `upright` applies to the text BOX only: on a rotated page it commits\n // counter-rotated (readable), while the leader tip/knee stay page-space\n // anchors — see the core's `calloutPointer`.\n id: 'free-text-callout',\n subtype: 'free-text-callout',\n propsKind: 'free-text',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6, lineEndings: { end: 'open-arrow' } },\n upright: true,\n },\n // text markup — the `text-select` gesture (inert without a selection plugin).\n // Base cursor is the plain arrow: the I-beam appears only where the action is\n // possible, via the selection handler's over-text claim (which an app can\n // reskin with the tool's icon — see the hub's ToolCursorSkin).\n {\n id: 'highlight',\n subtype: 'highlight',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ffe16a', blendMode: 'multiply' },\n selection: { kind: 'markup' },\n },\n // Redaction marking — the COMPOSED tool: over text it rides the selection\n // gesture (per-line quad marks, like a highlight); anywhere else it drag-\n // draws an area mark. One tool, both modes; the destructive apply lives in\n // plugin-redaction.\n {\n id: 'redact',\n subtype: 'redact',\n cursor: 'crosshair',\n enables: ['text-select', 'annotation-draw', 'annotation-edit'],\n defaults: {\n color: '#e44234',\n interiorColor: '#000000',\n fontColor: '#ffffff',\n opacity: 1,\n },\n selection: { kind: 'markup' },\n },\n {\n id: 'underline',\n subtype: 'underline',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'markup' },\n },\n {\n id: 'strikeout',\n subtype: 'strikeout',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'markup' },\n },\n {\n id: 'squiggly',\n subtype: 'squiggly',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'markup' },\n },\n {\n // The insert-caret tool authors a `caret` from a text selection. Its defaults\n // live under the `caret` preset — the key `createCaret` resolves.\n id: 'insert-text',\n subtype: 'caret',\n propsKind: 'caret',\n preset: 'caret',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'text-edit', operation: 'insert' },\n },\n {\n id: 'replace-text',\n subtype: 'strikeout',\n propsKind: 'strikeout',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'text-edit', operation: 'replace' },\n },\n // stamp — click-to-place; 'prompt' asks the environment through the ONE\n // file-picker port (the React adapter wires a file dialog by default; an\n // embedder can pass fixed bytes instead). `accept` narrows the dialog to\n // what the engine's stamp sniffer takes anyway.\n {\n id: ARMED_STAMP_TOOL_ID,\n subtype: 'stamp',\n cursor: 'copy',\n enables: ['annotation-place', 'annotation-edit'],\n source: { kind: 'prompt', accept: 'image/png,image/jpeg,application/pdf' },\n upright: true,\n ghost: { mode: 'footprint' },\n },\n // link — drag an invisible hit rectangle; the target is set afterwards\n // through the selection editor (create-then-edit), unless a preset carries\n // a fixed one (`{ id: 'docs-link', extends: 'link', defaults: { link: … } }`).\n // While this tool is active the navigation plane stands down (no `link-nav`),\n // so existing links become plain editable rects.\n {\n id: 'link',\n subtype: 'link',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n },\n // sticky note (\"comment\") — click-to-place, no payload: each click drops a\n // fixed 20×20 icon (engine-baked /AP from /C + /Name), screen-sized and\n // upright per the spec's Text-icon rule (the noZoom/noRotate seed).\n {\n id: 'note',\n subtype: 'text',\n cursor: 'crosshair',\n enables: ['annotation-place', 'annotation-edit'],\n defaults: { icon: 'comment', color: '#facc15' },\n flags: { noZoom: true, noRotate: true },\n upright: true,\n ghost: { mode: 'footprint' },\n },\n // file attachment — click-to-place with the spot-first-file-second rule:\n // the click opens the installed file-picker port (a file dialog by\n // default; `accept` unset = any file — attaching any format is the point);\n // the picked file embeds at the clicked point.\n {\n id: 'attachment',\n subtype: 'file-attachment',\n cursor: 'copy',\n enables: ['annotation-place', 'annotation-edit'],\n source: { kind: 'prompt' },\n defaults: { icon: 'paperclip', color: '#facc15' },\n flags: { noZoom: true, noRotate: true },\n upright: true,\n ghost: { mode: 'footprint' },\n },\n];\n\n/** Merge two default patches, `b` over `a`, with line endings merged per side. */\nfunction mergeDefaults(\n a?: AnnotationPropsPatch,\n b?: AnnotationPropsPatch,\n): AnnotationPropsPatch | undefined {\n if (!a) return b;\n if (!b) return a;\n const merged: AnnotationPropsPatch = { ...a, ...b };\n if (a.lineEndings || b.lineEndings) merged.lineEndings = { ...a.lineEndings, ...b.lineEndings };\n return merged;\n}\n\n/** Overlay a same-id override onto a base definition (configure a built-in). */\nfunction mergeDef(base: AnnotationToolDef, over: AnnotationToolDef): AnnotationToolDef {\n return {\n ...base,\n ...over,\n enables: over.enables ?? base.enables,\n meta: base.meta || over.meta ? { ...base.meta, ...over.meta } : undefined,\n defaults: mergeDefaults(base.defaults, over.defaults),\n flags: base.flags || over.flags ? { ...base.flags, ...over.flags } : undefined,\n ink: base.ink || over.ink ? { ...base.ink, ...over.ink } : undefined,\n };\n}\n\nfunction validateDefaults(tool: ResolvedTool): void {\n if (!tool.defaults) return;\n const allowed = TOOL_DEFAULT_KEYS[tool.subtype as ToolAuthoringKind];\n // Unknown/custom routing kinds remain extensible; known built-ins are strict.\n if (!allowed) return;\n const keys = new Set<string>(allowed);\n for (const key of Object.keys(tool.defaults)) {\n if (!keys.has(key)) {\n throw new Error(\n `[annotation] tool '${tool.id}' does not support default '${key}' for '${tool.subtype}'`,\n );\n }\n }\n}\n\n/**\n * Resolve {@link DEFAULT_TOOLS} + embedder overrides into the ready-to-use table.\n * A same-id override merges over the built-in; a new id adds a tool; `extends`\n * inherits from an already-defined tool. Keyed by id, so lookups are O(1).\n */\nexport function buildToolRegistry(\n overrides: AnnotationToolInput[] = [],\n): Map<string, ResolvedTool> {\n const defs = new Map<string, AnnotationToolDef>();\n for (const d of DEFAULT_TOOLS) defs.set(d.id, d);\n for (const o of overrides) {\n const prev = defs.get(o.id);\n defs.set(o.id, prev ? mergeDef(prev, o) : o);\n }\n\n const out = new Map<string, ResolvedTool>();\n const resolving = new Set<string>();\n const resolve = (id: string): ResolvedTool => {\n const cached = out.get(id);\n if (cached) return cached;\n const def = defs.get(id);\n if (!def) throw new Error(`[annotation] tool '${id}' extends an unknown tool`);\n // Inherit from the base first (guarding self / cyclic extends), then own fields win.\n let base: ResolvedTool | undefined;\n if (def.extends && def.extends !== id && !resolving.has(def.extends)) {\n resolving.add(id);\n base = resolve(def.extends);\n resolving.delete(id);\n }\n const subtype = (def.subtype ?? base?.subtype ?? def.id) as Subtype;\n const resolved: ResolvedTool = {\n id: def.id,\n subtype,\n preset: def.preset ?? def.id,\n propsKind: def.propsKind ?? base?.propsKind ?? subtype,\n cursor: def.cursor ?? base?.cursor ?? 'crosshair',\n enables: new Set(def.enables ?? (base ? [...base.enables] : [])),\n defaults: mergeDefaults(base?.defaults, def.defaults),\n flags: base?.flags || def.flags ? { ...base?.flags, ...def.flags } : undefined,\n source: def.source ?? base?.source,\n selection: def.selection ?? base?.selection,\n intent: def.intent ?? base?.intent,\n measurement: def.measurement ?? base?.measurement,\n ink: base?.ink || def.ink ? { ...base?.ink, ...def.ink } : undefined,\n upright: def.upright ?? base?.upright ?? false,\n clickCreate: def.clickCreate ?? base?.clickCreate ?? false,\n ghost: def.ghost ?? base?.ghost ?? false,\n meta: base?.meta || def.meta ? { ...base?.meta, ...def.meta } : undefined,\n };\n validateDefaults(resolved);\n out.set(id, resolved);\n return resolved;\n };\n for (const id of defs.keys()) resolve(id);\n return out;\n}\n"],"mappings":"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"}