@embedpdf/plugin-annotation 3.0.0-next.1 → 3.0.0-next.11

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 (43) hide show
  1. package/dist/authoring-BhtyfsMx.d.cts +37 -0
  2. package/dist/authoring-R4ezdnXd.d.ts +37 -0
  3. package/dist/authoring.cjs +16 -0
  4. package/dist/authoring.d.cts +2 -0
  5. package/dist/authoring.d.ts +2 -0
  6. package/dist/authoring.js +3 -0
  7. package/dist/{capability-hb2AU0f6.cjs → capability-Csb9WMm-.cjs} +816 -482
  8. package/dist/capability-Csb9WMm-.cjs.map +1 -0
  9. package/dist/{capability-fghl0JmE.js → capability-DBsr2D-L.js} +772 -420
  10. package/dist/capability-DBsr2D-L.js.map +1 -0
  11. package/dist/contract/host.cjs +3 -0
  12. package/dist/contract/host.d.cts +2 -0
  13. package/dist/contract/host.d.ts +2 -0
  14. package/dist/contract/host.js +2 -0
  15. package/dist/contract.cjs +8 -0
  16. package/dist/contract.cjs.map +1 -0
  17. package/dist/contract.d.cts +10 -0
  18. package/dist/contract.d.ts +10 -0
  19. package/dist/contract.js +8 -0
  20. package/dist/contract.js.map +1 -0
  21. package/dist/index.cjs +104 -104
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +5 -47
  24. package/dist/index.d.ts +5 -47
  25. package/dist/index.js +96 -96
  26. package/dist/index.js.map +1 -1
  27. package/dist/internal.cjs +4 -2
  28. package/dist/internal.d.cts +2 -1
  29. package/dist/internal.d.ts +2 -1
  30. package/dist/internal.js +3 -1
  31. package/dist/seam-6pJeKOOJ.cjs +296 -0
  32. package/dist/seam-6pJeKOOJ.cjs.map +1 -0
  33. package/dist/seam-C4L_7rDt.js +225 -0
  34. package/dist/seam-C4L_7rDt.js.map +1 -0
  35. package/dist/types-CwytT-TI.js +13 -0
  36. package/dist/types-CwytT-TI.js.map +1 -0
  37. package/dist/types-DimcLIic.cjs +17 -0
  38. package/dist/types-DimcLIic.cjs.map +1 -0
  39. package/dist/{types-Da6JRmhw.d.ts → types-eLTpocXz.d.cts} +232 -25
  40. package/dist/{types-BhWgqgbX.d.cts → types-otwv-Cdl.d.ts} +232 -25
  41. package/package.json +21 -7
  42. package/dist/capability-fghl0JmE.js.map +0 -1
  43. package/dist/capability-hb2AU0f6.cjs.map +0 -1
@@ -1,236 +1,12 @@
1
+ const require_seam = require("./seam-6pJeKOOJ.cjs");
1
2
  let _embedpdf_core = require("@embedpdf/core");
2
- let _embedpdf_plugin_interaction = require("@embedpdf/plugin-interaction");
3
+ let _embedpdf_plugin_actions_contract = require("@embedpdf/plugin-actions/contract");
4
+ let _embedpdf_plugin_interaction_contract = require("@embedpdf/plugin-interaction/contract");
3
5
  let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
6
+ let _embedpdf_core_acrojs = require("@embedpdf/core-acrojs");
7
+ let _embedpdf_plugin_selection_contract = require("@embedpdf/plugin-selection/contract");
4
8
  let _embedpdf_core_annotation = require("@embedpdf/core-annotation");
5
- //#region src/types.ts
6
- /**
7
- * The annotation capability token. Typed to the full {@link AnnotationHostCapability}
8
- * here (the package internals + the `/internal` entry use this view). The package
9
- * root re-exports the SAME token narrowed to {@link AnnotationCapability}.
10
- */
11
- const AnnotationToken = (0, _embedpdf_core.createCapabilityToken)("annotation");
12
- //#endregion
13
- //#region src/repository/seam.ts
14
- function refKey(ref) {
15
- return ref.kind === "objectNumber" ? `obj:${ref.annotObjectNumber}` : ref.kind === "nm" ? `nm:${ref.nm}` : `idx:${ref.pageObjectNumber}:${ref.index}`;
16
- }
17
- const h2 = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
18
- const colorToCss = (c) => `#${h2(c.r)}${h2(c.g)}${h2(c.b)}`;
19
- function cssToColor(css) {
20
- const s = css.trim();
21
- const m6 = /^#?([0-9a-f]{6})$/i.exec(s);
22
- if (m6) {
23
- const n = parseInt(m6[1], 16);
24
- return {
25
- r: n >> 16 & 255,
26
- g: n >> 8 & 255,
27
- b: n & 255
28
- };
29
- }
30
- const m3 = /^#?([0-9a-f]{3})$/i.exec(s);
31
- if (m3) {
32
- const [a, b, c] = m3[1];
33
- return {
34
- r: parseInt(a + a, 16),
35
- g: parseInt(b + b, 16),
36
- b: parseInt(c + c, 16)
37
- };
38
- }
39
- return {
40
- r: 0,
41
- g: 0,
42
- b: 0
43
- };
44
- }
45
- const toPdfRotation = (rotCW) => (0, _embedpdf_core_annotation.normalizeDeg)(-rotCW);
46
- const fromPdfRotation = (rotPdf) => (0, _embedpdf_core_annotation.normalizeDeg)(-rotPdf);
47
- /** Advisory `rot` for a vertex geom, from a DTO's (PDF-convention) `rotation`.
48
- * Absent → no `rot` key (kept off the geom so unrotated shapes stay clean). */
49
- const rotFromDTO = (rotation) => rotation ? { rot: fromPdfRotation(rotation) } : {};
50
- /**
51
- * Geometry/rotation fields for a BOX kind (square/circle/plain free-text). The
52
- * model's `rect` is the UNROTATED logical box and `rot` the applied tilt, so:
53
- * - rot == 0 → `/Rect` IS the box; the transform pair is stated as `null`
54
- * (TOTAL projection — the engine's tri-state writes preserve omissions, so
55
- * an omitted field would keep a stale rotation instead of flattening it).
56
- * - rot != 0 → `/Rect` is the rotated visual AABB (PDFium clips the baked /AP
57
- * to it), `unrotatedRect` the logical box, `rotation` the PDF angle.
58
- */
59
- function boxGeomFields(rect, rot, crop) {
60
- if (!rot) return {
61
- rect: (0, _embedpdf_core_annotation.contentToPdfRect)(rect, crop),
62
- rotation: null,
63
- unrotatedRect: null
64
- };
65
- return {
66
- rect: (0, _embedpdf_core_annotation.contentToPdfRect)((0, _embedpdf_core_annotation.rotatedAabb)(rect, rot), crop),
67
- unrotatedRect: (0, _embedpdf_core_annotation.contentToPdfRect)(rect, crop),
68
- rotation: toPdfRotation(rot)
69
- };
70
- }
71
- /** A box geom (square/circle/stamp) from its DTO: when rotated, the LOCAL box
72
- * is the stored `unrotatedRect` (the AABB `/Rect` is the rendered envelope)
73
- * and `rot` the converted tilt; unrotated, `/Rect` IS the box. */
74
- function boxGeomFromDTO(dto, rotation, unrotatedRect, crop, ellipse) {
75
- const rot = rotation ? fromPdfRotation(rotation) : 0;
76
- return {
77
- t: "rect",
78
- rect: (0, _embedpdf_core_annotation.pdfToContentRect)(rot && unrotatedRect ? unrotatedRect : dto.rect, crop),
79
- ellipse,
80
- ...rot ? { rot } : {}
81
- };
82
- }
83
- /** Inset a PdfRect by a `/RD` (PDF user space, y-up: all four are non-negative
84
- * insets from the matching `/Rect` edge). Used to recover the callout text box. */
85
- const insetPdfRectByRD = (r, rd) => rd ? {
86
- left: r.left + rd.left,
87
- bottom: r.bottom + rd.bottom,
88
- right: r.right - rd.right,
89
- top: r.top - rd.top
90
- } : r;
91
- /** Engine border fields (`/BS /S`, `/BS /D`, `/BE /I`) → the `Border` union. A
92
- * cloudy effect wins over the underlying border style (which stays solid). */
93
- function borderFromDTO(d) {
94
- if ((d.cloudyIntensity ?? 0) > 0) return {
95
- kind: "cloudy",
96
- intensity: d.cloudyIntensity
97
- };
98
- if (d.borderStyle === "dashed") return {
99
- kind: "dashed",
100
- dash: d.dashArray?.length ? d.dashArray : [3, 3]
101
- };
102
- return { kind: "solid" };
103
- }
104
- /** The writable projection of a `link` value: `goto`/`uri` pass through,
105
- * read-only arms (`javascript`, `named`, `goto-remote`, `launch`,
106
- * `unsupported`) yield `null` — they can be carried, never (re)written. */
107
- function writableTarget(t) {
108
- return t && (t.kind === "goto" || t.kind === "uri") ? t : null;
109
- }
110
- const TEXT_MARKUP = /* @__PURE__ */ new Set([
111
- "highlight",
112
- "underline",
113
- "squiggly",
114
- "strikeout"
115
- ]);
116
- const STROKE_KINDS = /* @__PURE__ */ new Set([
117
- "square",
118
- "circle",
119
- "line",
120
- "polygon",
121
- "polyline",
122
- "ink"
123
- ]);
124
- /** Engine DTO → content-space `Style` (CSS colours, `Border` union). Exported so
125
- * selection-aware UIs can read display values straight off a {@link AnnotationDTO}
126
- * without re-deriving the colour/border mapping. */
127
- function styleFromDTO(dto) {
128
- if (dto.subtype === "widget") return {
129
- color: dto.color ? colorToCss(dto.color) : "#1a1a1a",
130
- interiorColor: dto.interiorColor ? colorToCss(dto.interiorColor) : null,
131
- strokeWidth: dto.strokeWidth,
132
- opacity: 1,
133
- blendMode: dto.blendMode,
134
- border: dto.borderStyle === "dashed" ? {
135
- kind: "dashed",
136
- dash: [3, 3]
137
- } : { kind: "solid" }
138
- };
139
- if (STROKE_KINDS.has(dto.subtype)) {
140
- const d = dto;
141
- return {
142
- color: colorToCss(d.color),
143
- interiorColor: d.interiorColor ? colorToCss(d.interiorColor) : null,
144
- strokeWidth: d.strokeWidth,
145
- opacity: d.opacity,
146
- blendMode: dto.blendMode,
147
- border: borderFromDTO(d)
148
- };
149
- }
150
- if (TEXT_MARKUP.has(dto.subtype)) {
151
- const d = dto;
152
- return {
153
- color: colorToCss(d.color),
154
- interiorColor: null,
155
- strokeWidth: 0,
156
- opacity: d.opacity,
157
- blendMode: dto.blendMode,
158
- border: { kind: "solid" }
159
- };
160
- }
161
- if (dto.subtype === "caret") {
162
- const d = dto;
163
- return {
164
- color: colorToCss(d.color),
165
- interiorColor: null,
166
- strokeWidth: 1,
167
- opacity: d.opacity,
168
- blendMode: dto.blendMode,
169
- border: { kind: "solid" }
170
- };
171
- }
172
- if (dto.subtype === "free-text") {
173
- const d = dto;
174
- return {
175
- color: colorToCss(d.color),
176
- interiorColor: d.interiorColor ? colorToCss(d.interiorColor) : null,
177
- strokeWidth: d.strokeWidth,
178
- opacity: d.opacity,
179
- blendMode: dto.blendMode,
180
- border: borderFromDTO(d)
181
- };
182
- }
183
- if (dto.subtype === "redact") {
184
- const d = dto;
185
- return {
186
- color: colorToCss(d.color),
187
- interiorColor: d.interiorColor ? colorToCss(d.interiorColor) : null,
188
- strokeWidth: 1.5,
189
- opacity: d.opacity,
190
- blendMode: dto.blendMode,
191
- border: { kind: "solid" }
192
- };
193
- }
194
- if (dto.subtype === "text" || dto.subtype === "file-attachment") {
195
- const d = dto;
196
- return {
197
- color: colorToCss(d.color),
198
- interiorColor: null,
199
- strokeWidth: 1,
200
- opacity: d.opacity,
201
- blendMode: dto.blendMode,
202
- border: { kind: "solid" }
203
- };
204
- }
205
- return {
206
- color: "#444444",
207
- interiorColor: null,
208
- strokeWidth: 1,
209
- opacity: 1,
210
- blendMode: dto.blendMode,
211
- border: { kind: "solid" }
212
- };
213
- }
214
- /**
215
- * The flat props vocabulary (CSS colours, house keys) → the engine's widget
216
- * appearance for `doc.forms` authoring — the SAME mapping the widget patch
217
- * lowering uses, exported as a boundary utility so the form plugin can style
218
- * `placeField` from a tool's `currentDefaults` without growing a second CSS
219
- * parser. Absent keys stay absent (the engine writes nothing for them).
220
- */
221
- function widgetAppearanceFromProps(props) {
222
- return {
223
- ...props.color !== void 0 ? { color: cssToColor(props.color) } : {},
224
- ...props.interiorColor !== void 0 ? { interiorColor: props.interiorColor ? cssToColor(props.interiorColor) : null } : {},
225
- ...props.strokeWidth !== void 0 ? { strokeWidth: props.strokeWidth } : {},
226
- ...props.border !== void 0 ? { borderStyle: props.border.kind === "dashed" ? "dashed" : "solid" } : {},
227
- ...props.fontFamily !== void 0 ? { fontFamily: props.fontFamily } : {},
228
- ...props.fontSize !== void 0 ? { fontSize: props.fontSize } : {},
229
- ...props.fontColor !== void 0 ? { fontColor: cssToColor(props.fontColor) } : {},
230
- ...props.textAlign !== void 0 ? { textAlign: props.textAlign } : {}
231
- };
232
- }
233
- //#endregion
9
+ let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
234
10
  //#region src/repository/props.ts
235
11
  /**
236
12
  * GENERIC per-key prop lowerings — the 1:1 mappings from the flat props
@@ -249,15 +25,15 @@ const borderSlice = (style) => ({
249
25
  * the old projections did (a fresh draft may not carry `text` yet). */
250
26
  const textOf = (a) => a.text ?? _embedpdf_core_annotation.initialTextStyle;
251
27
  const GENERIC_PROPS = {
252
- color: (a) => ({ color: cssToColor(a.style.color) }),
28
+ color: (a) => ({ color: require_seam.cssToColor(a.style.color) }),
253
29
  opacity: (a) => ({ opacity: a.style.opacity }),
254
30
  blendMode: (a) => ({ blendMode: a.style.blendMode }),
255
- interiorColor: (a) => ({ interiorColor: a.style.interiorColor ? cssToColor(a.style.interiorColor) : null }),
31
+ interiorColor: (a) => ({ interiorColor: a.style.interiorColor ? require_seam.cssToColor(a.style.interiorColor) : null }),
256
32
  strokeWidth: (a) => ({ strokeWidth: a.style.strokeWidth }),
257
33
  border: (a) => borderSlice(a.style),
258
34
  fontFamily: (a) => ({ fontFamily: textOf(a).fontFamily }),
259
35
  fontSize: (a) => ({ fontSize: textOf(a).fontSize }),
260
- fontColor: (a) => ({ fontColor: cssToColor(textOf(a).fontColor) }),
36
+ fontColor: (a) => ({ fontColor: require_seam.cssToColor(textOf(a).fontColor) }),
261
37
  textAlign: (a) => ({ textAlign: textOf(a).textAlign }),
262
38
  icon: (a) => a.icon !== void 0 ? { icon: a.icon } : {},
263
39
  link: () => ({})
@@ -268,7 +44,7 @@ const GENERIC_PROPS = {
268
44
  * `/Rect`(+`unrotatedRect`+`rotation`), total (nulls state the clears). */
269
45
  const boxEmit = (a, crop) => {
270
46
  const g = a.geom;
271
- return boxGeomFields(g.rect, (0, _embedpdf_core_annotation.geomRotation)(a.geom), crop);
47
+ return require_seam.boxGeomFields(g.rect, (0, _embedpdf_core_annotation.geomRotation)(a.geom), crop);
272
48
  };
273
49
  //#endregion
274
50
  //#region src/repository/kinds/shape.ts
@@ -296,7 +72,7 @@ function cloudyExtras(a) {
296
72
  }
297
73
  const ingest = (dto, crop, ellipse) => {
298
74
  const d = dto;
299
- return { geom: boxGeomFromDTO(d, d.rotation, d.unrotatedRect, crop, ellipse) };
75
+ return { geom: require_seam.boxGeomFromDTO(d, d.rotation, d.unrotatedRect, crop, ellipse) };
300
76
  };
301
77
  const projection = (ellipse) => ({
302
78
  ingest: (dto, crop) => ingest(dto, crop, ellipse),
@@ -320,7 +96,7 @@ const circle = projection(true);
320
96
  * omission would preserve a stale advisory angle. */
321
97
  const advisoryRotation = (g) => {
322
98
  const rot = (0, _embedpdf_core_annotation.geomRotation)(g);
323
- return { rotation: rot ? toPdfRotation(rot) : null };
99
+ return { rotation: rot ? require_seam.toPdfRotation(rot) : null };
324
100
  };
325
101
  /** `/BE` intensity for a closed poly (polygon): the curls are generated from
326
102
  * /Vertices + /BE alone — per ISO 32000 no /RD applies — and the /Rect
@@ -333,18 +109,29 @@ const visualRect = (a, crop) => {
333
109
  if (g.t === "poly") return { rect: (0, _embedpdf_core_annotation.geomPdfBounds)(g, a.style.strokeWidth, crop, a.style.border) };
334
110
  return {};
335
111
  };
336
- /** Stroke-family prop exceptions: width and border feed the derived /Rect. */
112
+ /**
113
+ * A lowering whose key is an INPUT of the derived /Rect: the visual bounds
114
+ * ride along with every emission, so a sparse patch can never change an input
115
+ * without re-emitting the derivation. (The bug this kills: patch `lineEndings`
116
+ * alone → the engine re-bakes the /AP inside the stale /Rect → the new
117
+ * arrowhead is clipped in every viewer except the live vector one.)
118
+ */
119
+ const withRect = (lower) => (a, crop) => ({
120
+ ...lower(a, crop),
121
+ ...visualRect(a, crop)
122
+ });
123
+ /** Stroke-family prop exceptions: width, border, AND line endings feed the
124
+ * derived /Rect — an ending's arrowhead reaches well past the endpoint, and
125
+ * ISO 32000 requires /Rect to enclose it. Where a key can't change the
126
+ * bounds (a line's border restyle), the re-emitted rect is an idempotent
127
+ * no-op — uniformity beats per-case reasoning here. */
337
128
  const strokeProps = {
338
- strokeWidth: (a, crop) => ({
339
- strokeWidth: a.style.strokeWidth,
340
- ...visualRect(a, crop)
341
- }),
342
- border: (a, crop) => ({
129
+ strokeWidth: withRect((a) => ({ strokeWidth: a.style.strokeWidth })),
130
+ border: withRect((a) => ({
343
131
  ...borderSlice(a.style),
344
- ...polyCloudy(a),
345
- ...a.geom.t === "poly" && a.geom.closed ? visualRect(a, crop) : {}
346
- }),
347
- lineEndings: (a) => (a.geom.t === "line" || a.geom.t === "poly") && a.geom.ends ? { lineEndings: a.geom.ends } : {}
132
+ ...polyCloudy(a)
133
+ })),
134
+ lineEndings: withRect((a) => (a.geom.t === "line" || a.geom.t === "poly") && a.geom.ends ? { lineEndings: a.geom.ends } : {})
348
135
  };
349
136
  const line = {
350
137
  ingest: (dto, crop) => {
@@ -354,7 +141,7 @@ const line = {
354
141
  a: (0, _embedpdf_core_annotation.pdfToContentPoint)(d.linePoints.start, crop),
355
142
  b: (0, _embedpdf_core_annotation.pdfToContentPoint)(d.linePoints.end, crop),
356
143
  ends: d.lineEndings,
357
- ...rotFromDTO(d.rotation)
144
+ ...require_seam.rotFromDTO(d.rotation)
358
145
  } };
359
146
  },
360
147
  geometry: (a, crop) => {
@@ -379,7 +166,7 @@ const polyProjection = (closed) => ({
379
166
  points: d.vertices.map((p) => (0, _embedpdf_core_annotation.pdfToContentPoint)(p, crop)),
380
167
  closed,
381
168
  ..."lineEndings" in d ? { ends: d.lineEndings } : {},
382
- ...rotFromDTO(d.rotation)
169
+ ...require_seam.rotFromDTO(d.rotation)
383
170
  } };
384
171
  },
385
172
  geometry: (a, crop) => {
@@ -402,7 +189,7 @@ const ink = {
402
189
  geom: {
403
190
  t: "ink",
404
191
  strokes: d.inkList.map((stroke) => stroke.map((p) => (0, _embedpdf_core_annotation.pdfToContentPoint)(p, crop))),
405
- ...rotFromDTO(d.rotation)
192
+ ...require_seam.rotFromDTO(d.rotation)
406
193
  },
407
194
  ...d.intent ? { intent: d.intent } : {}
408
195
  };
@@ -427,7 +214,7 @@ function textFromDTO(dto) {
427
214
  return {
428
215
  fontFamily: dto.fontFamily,
429
216
  fontSize: dto.fontSize,
430
- fontColor: colorToCss(dto.fontColor ?? dto.color),
217
+ fontColor: require_seam.colorToCss(dto.fontColor ?? dto.color),
431
218
  textAlign: dto.textAlign
432
219
  };
433
220
  }
@@ -471,7 +258,7 @@ function calloutFields(a, crop) {
471
258
  calloutLine,
472
259
  lineEnding: g.callout.ending,
473
260
  ...rot ? {
474
- rotation: toPdfRotation(rot),
261
+ rotation: require_seam.toPdfRotation(rot),
475
262
  unrotatedRect: (0, _embedpdf_core_annotation.contentToPdfRect)(g.rect, crop)
476
263
  } : {
477
264
  rotation: null,
@@ -484,11 +271,11 @@ const freeText = {
484
271
  const d = dto;
485
272
  if (d.intent === "free-text-callout" && d.calloutLine && d.calloutLine.length >= 2) {
486
273
  const cl = d.calloutLine;
487
- const rot = d.rotation ? fromPdfRotation(d.rotation) : 0;
274
+ const rot = d.rotation ? require_seam.fromPdfRotation(d.rotation) : 0;
488
275
  return {
489
276
  geom: {
490
277
  t: "text",
491
- rect: rot && d.unrotatedRect ? (0, _embedpdf_core_annotation.pdfToContentRect)(d.unrotatedRect, crop) : (0, _embedpdf_core_annotation.pdfToContentRect)(insetPdfRectByRD(d.rect, d.rectDifferences), crop),
278
+ rect: rot && d.unrotatedRect ? (0, _embedpdf_core_annotation.pdfToContentRect)(d.unrotatedRect, crop) : (0, _embedpdf_core_annotation.pdfToContentRect)(require_seam.insetPdfRectByRD(d.rect, d.rectDifferences), crop),
492
279
  callout: {
493
280
  tip: (0, _embedpdf_core_annotation.pdfToContentPoint)(cl[0], crop),
494
281
  knee: cl.length === 3 ? (0, _embedpdf_core_annotation.pdfToContentPoint)(cl[1], crop) : void 0,
@@ -499,7 +286,7 @@ const freeText = {
499
286
  text: textFromDTO(d)
500
287
  };
501
288
  }
502
- const rot = d.rotation ? fromPdfRotation(d.rotation) : 0;
289
+ const rot = d.rotation ? require_seam.fromPdfRotation(d.rotation) : 0;
503
290
  return {
504
291
  geom: {
505
292
  t: "text",
@@ -522,20 +309,29 @@ const freeText = {
522
309
  };
523
310
  //#endregion
524
311
  //#region src/repository/kinds/quads.ts
525
- const quadsFromDTO = (quadPoints, crop) => quadPoints.map((q) => [
526
- (0, _embedpdf_core_annotation.pdfToContentPoint)(q.p1, crop),
527
- (0, _embedpdf_core_annotation.pdfToContentPoint)(q.p2, crop),
528
- (0, _embedpdf_core_annotation.pdfToContentPoint)(q.p3, crop),
529
- (0, _embedpdf_core_annotation.pdfToContentPoint)(q.p4, crop)
530
- ]);
531
- /** Content quads → engine `/QuadPoints` (PDF user space); null off quads geom. */
312
+ /**
313
+ * Imported `/QuadPoints` → semantic TextQuads. `normalizeQuad` is a
314
+ * NORMALIZER, not a cast: the de-facto zigzag order passes through, ring-order
315
+ * producers are repaired, and garbage gets a deterministic labeling — so
316
+ * drawing code can never put an underline on the wrong edge of a well-formed
317
+ * quad, rotated ones included.
318
+ */
319
+ const quadsFromDTO = (quadPoints, crop) => quadPoints.map((q) => (0, _embedpdf_core_geometry.normalizeQuad)({
320
+ p1: (0, _embedpdf_core_annotation.pdfToContentPoint)(q.p1, crop),
321
+ p2: (0, _embedpdf_core_annotation.pdfToContentPoint)(q.p2, crop),
322
+ p3: (0, _embedpdf_core_annotation.pdfToContentPoint)(q.p3, crop),
323
+ p4: (0, _embedpdf_core_annotation.pdfToContentPoint)(q.p4, crop)
324
+ }));
325
+ /** Content quads → engine `/QuadPoints` (PDF user space, zigzag slot order:
326
+ * p1..p4 = upper-start, upper-end, lower-start, lower-end — under the y-flip
327
+ * exactly PDFium's documented TL, TR, BL, BR); null off quads geom. */
532
328
  function quadPointsFor(a, crop) {
533
329
  if (a.geom.t !== "quads") return null;
534
330
  return a.geom.quads.map((q) => ({
535
- p1: (0, _embedpdf_core_annotation.contentToPdfPoint)(q[0], crop),
536
- p2: (0, _embedpdf_core_annotation.contentToPdfPoint)(q[1], crop),
537
- p3: (0, _embedpdf_core_annotation.contentToPdfPoint)(q[2], crop),
538
- p4: (0, _embedpdf_core_annotation.contentToPdfPoint)(q[3], crop)
331
+ p1: (0, _embedpdf_core_annotation.contentToPdfPoint)(q.upperStart, crop),
332
+ p2: (0, _embedpdf_core_annotation.contentToPdfPoint)(q.upperEnd, crop),
333
+ p3: (0, _embedpdf_core_annotation.contentToPdfPoint)(q.lowerStart, crop),
334
+ p4: (0, _embedpdf_core_annotation.contentToPdfPoint)(q.lowerEnd, crop)
539
335
  }));
540
336
  }
541
337
  /** PDF-space bounding box of a set of `/QuadPoints` quads — the `/Rect` a
@@ -593,15 +389,17 @@ const strikeout = markupProjection("strikeout");
593
389
  const caret = {
594
390
  ingest: (dto, crop) => {
595
391
  const d = dto;
392
+ const rot = d.rotation ? require_seam.fromPdfRotation(d.rotation) : 0;
596
393
  return {
597
394
  geom: {
598
395
  t: "caret",
599
- rect: (0, _embedpdf_core_annotation.pdfToContentRect)(d.rect, crop)
396
+ rect: (0, _embedpdf_core_annotation.pdfToContentRect)(rot && d.unrotatedRect ? d.unrotatedRect : d.rect, crop),
397
+ ...rot ? { rot } : {}
600
398
  },
601
399
  ...d.intent ? { intent: d.intent } : {}
602
400
  };
603
401
  },
604
- geometry: (a, crop) => a.geom.t === "caret" ? { rect: (0, _embedpdf_core_annotation.contentToPdfRect)(a.geom.rect, crop) } : null,
402
+ geometry: (a, crop) => a.geom.t === "caret" ? require_seam.boxGeomFields(a.geom.rect, a.geom.rot ?? 0, crop) : null,
605
403
  draftExtras: (a) => ({
606
404
  rectDifferences: {
607
405
  left: .5,
@@ -628,7 +426,7 @@ const redact = {
628
426
  text: {
629
427
  fontFamily: d.fontFamily,
630
428
  fontSize: d.fontSize,
631
- fontColor: colorToCss(d.fontColor),
429
+ fontColor: require_seam.colorToCss(d.fontColor),
632
430
  textAlign: d.textAlign
633
431
  },
634
432
  ...d.overlayText ? { label: {
@@ -669,7 +467,7 @@ const fileAttachment = iconProjection("file-attachment");
669
467
  const stamp = {
670
468
  ingest: (dto, crop) => {
671
469
  const d = dto;
672
- return { geom: boxGeomFromDTO(d, d.rotation, d.unrotatedRect, crop, false) };
470
+ return { geom: require_seam.boxGeomFromDTO(d, d.rotation, d.unrotatedRect, crop, false) };
673
471
  },
674
472
  geometry: (a, crop) => boxEmit(a, crop),
675
473
  createable: false
@@ -688,10 +486,10 @@ const link = {
688
486
  },
689
487
  geometry: rectGeometry,
690
488
  prop: { link: (a) => {
691
- const target = writableTarget(a.link);
489
+ const target = require_seam.writableTarget(a.link);
692
490
  return target ? { target } : a.link == null ? { target: null } : {};
693
491
  } },
694
- draftExtras: (a) => ({ target: writableTarget(a.link) })
492
+ draftExtras: (a) => ({ target: require_seam.writableTarget(a.link) })
695
493
  };
696
494
  /** One PDF `widget` subtype → per-family CLIENT kinds (radios have no font). */
697
495
  const WIDGET_KIND_BY_FAMILY = {
@@ -712,7 +510,7 @@ function widgetTextFromDTO(dto) {
712
510
  return {
713
511
  fontFamily: dto.fontFamily ?? "helvetica",
714
512
  fontSize: dto.fontSize ?? 0,
715
- fontColor: dto.fontColor ? colorToCss(dto.fontColor) : "#000000",
513
+ fontColor: dto.fontColor ? require_seam.colorToCss(dto.fontColor) : "#000000",
716
514
  textAlign: dto.textAlign
717
515
  };
718
516
  }
@@ -720,7 +518,7 @@ const widget = {
720
518
  ingest: (dto, crop) => {
721
519
  const d = dto;
722
520
  return {
723
- geom: boxGeomFromDTO(d, void 0, void 0, crop, false),
521
+ geom: require_seam.boxGeomFromDTO(d, void 0, void 0, crop, false),
724
522
  ...WIDGET_TEXT_KINDS.has(widgetKindOf(d.fieldFamily)) ? { text: widgetTextFromDTO(d) } : {}
725
523
  };
726
524
  },
@@ -744,44 +542,6 @@ const unsupported = {
744
542
  * parent's `link` prop, and the reconciler derives the children it should
745
543
  * have from the parent's committed geometry.
746
544
  */
747
- /** Does this kind's table declare the `link` prop (may it carry an attached
748
- * link)? Widgets/caret/redact/file-attachment deliberately don't. */
749
- const takesLink = (subtype) => (0, _embedpdf_core_annotation.propsFor)(subtype).some((s) => s.key === "link");
750
- /**
751
- * Fold attached link children onto their parents: a `/Link` annotation
752
- * grouped (`/RT /Group`) under a linkable non-link parent is not an object
753
- * in the client model — it becomes the parent's `link` prop value plus the
754
- * `linkRefs` join keys the write side needs. Folded children are REMOVED
755
- * from the returned list, so they never render, hit-test, or select.
756
- * Anything that doesn't match (orphans, links grouped under widgets or
757
- * other links) stays a standalone annotation — nothing is ever hidden.
758
- *
759
- * Multi-segment parents (markup) carry several children with one shared
760
- * target; the first child's target wins and the next write heals any
761
- * foreign disagreement.
762
- */
763
- function foldAttachedLinks(annots) {
764
- const byId = new Map(annots.map((a) => [a.id, a]));
765
- const folded = /* @__PURE__ */ new Set();
766
- const patched = /* @__PURE__ */ new Map();
767
- for (const a of annots) {
768
- if (a.subtype !== "link" || a.data?.subtype !== "link") continue;
769
- if (a.data.replyType !== "group" || !a.data.inReplyTo || !a.ref) continue;
770
- const parentId = refKey(a.data.inReplyTo);
771
- const parent = patched.get(parentId) ?? byId.get(parentId);
772
- if (!parent || parent.subtype === "link" || !takesLink(parent.subtype)) continue;
773
- patched.set(parentId, {
774
- ...parent,
775
- link: parent.link ?? a.data.target,
776
- linkRefs: [...parent.linkRefs ?? [], a.ref]
777
- });
778
- folded.add(a.id);
779
- }
780
- if (!folded.size) return annots;
781
- return annots.filter((a) => !folded.has(a.id)).map((a) => patched.get(a.id) ?? a);
782
- }
783
- /** Bounds of one quad (content space). */
784
- const quadBounds = (q) => (0, _embedpdf_core_annotation.unionRect)(q);
785
545
  /**
786
546
  * The DESIRED hit rects (content space) of a parent's attached link
787
547
  * children — derived fresh from the parent's committed geometry, never
@@ -797,7 +557,7 @@ const quadBounds = (q) => (0, _embedpdf_core_annotation.unionRect)(q);
797
557
  * `/QuadPoints` (tier 2).
798
558
  */
799
559
  function linkChildRects(a) {
800
- if (a.geom.t === "quads") return a.geom.quads.map(quadBounds);
560
+ if (a.geom.t === "quads") return a.geom.quads.map(_embedpdf_core_geometry.textQuadBounds);
801
561
  return [(0, _embedpdf_core_annotation.unionRect)((0, _embedpdf_core_annotation.selectionQuad)(a.geom, a.style.strokeWidth, a.style.border))];
802
562
  }
803
563
  //#endregion
@@ -839,16 +599,16 @@ function fromDTO(dto, crop, source = "baked") {
839
599
  const slice = projectionOf(dto.subtype).ingest(dto, crop);
840
600
  const strippedRect = !(dto.subtype === "free-text" && dto.intent === "free-text-callout") && "unrotatedRect" in dto && "rotation" in dto && dto.rotation ? dto.unrotatedRect : void 0;
841
601
  return {
842
- id: refKey(dto.ref),
602
+ id: require_seam.refKey(dto.ref),
843
603
  ref: dto.ref,
844
604
  pon: dto.pageObjectNumber,
845
605
  subtype: dto.subtype === "widget" ? widgetKindOf(dto.fieldFamily) : dto.subtype,
846
606
  flags: dto.flags,
847
607
  source,
848
608
  data: dto,
849
- ...dto.inReplyTo ? { irt: refKey(dto.inReplyTo) } : {},
850
- ...dto.replyType === "group" && dto.inReplyTo ? { group: refKey(dto.inReplyTo) } : {},
851
- style: styleFromDTO(dto),
609
+ ...dto.inReplyTo ? { irt: require_seam.refKey(dto.inReplyTo) } : {},
610
+ ...dto.replyType === "group" && dto.inReplyTo ? { group: require_seam.refKey(dto.inReplyTo) } : {},
611
+ style: require_seam.styleFromDTO(dto),
852
612
  ...slice,
853
613
  apBox: (0, _embedpdf_core_annotation.pdfToContentRect)(strippedRect ?? dto.rect, crop),
854
614
  ...strippedRect ? { apRot: (0, _embedpdf_core_annotation.geomRotation)(slice.geom) } : {}
@@ -918,10 +678,51 @@ function toCreateDraft(a, crop) {
918
678
  return {
919
679
  ...base,
920
680
  ...extras,
681
+ nm: crypto.randomUUID(),
921
682
  flags: a.flags
922
683
  };
923
684
  }
924
685
  //#endregion
686
+ //#region src/hover-feed.ts
687
+ /**
688
+ * The annotation plane's E/X trigger feed — a thin adapter from the ONE
689
+ * pointer-driven hover seam (the `hoverAt` diff) onto the shared hover pump.
690
+ *
691
+ * Anti-cascade is structural and lives in the CALLER's seam placement:
692
+ * reducer-side hover clears (session-hide, remove, reload) never pass
693
+ * through `hoverAt`, so an effect-induced hover loss can never masquerade as
694
+ * a cursor exit. This module only decides WHO is hoverable here:
695
+ *
696
+ * - Drafts (no engine ref) can't carry /AA — skipped.
697
+ * - Widgets and links belong to their own event planes (fill controls /
698
+ * LinkLayer anchors own their pixels). When they ARE hit-testable on this
699
+ * plane, an authoring tool owns them — firing hover actions while editing
700
+ * would be wrong — so they are skipped unconditionally.
701
+ * - Tree-less annotations are FREE: no target, no dispatch, and per-event
702
+ * flags keep a lone /E or /X from dispatching its inert twin.
703
+ */
704
+ function createAnnotationHoverFeed(actions, annotOf) {
705
+ const pump = (0, _embedpdf_plugin_actions_contract.createHoverPump)(actions.dispatch);
706
+ const targetOf = (id) => {
707
+ if (!id) return null;
708
+ const annot = annotOf(id);
709
+ if (!annot?.ref) return null;
710
+ if (annot.subtype.startsWith("widget") || annot.subtype === "link") return null;
711
+ const enter = Boolean(annot.data?.actions?.cursorEnter?.root);
712
+ const exit = Boolean(annot.data?.actions?.cursorExit?.root);
713
+ if (!enter && !exit) return null;
714
+ return {
715
+ ref: annot.ref,
716
+ pon: annot.pon,
717
+ events: {
718
+ enter,
719
+ exit
720
+ }
721
+ };
722
+ };
723
+ return { hover: (id) => pump.hover(targetOf(id)) };
724
+ }
725
+ //#endregion
925
726
  //#region src/text-item.ts
926
727
  /**
927
728
  * The free-text presentation projection: the core's geometry-only `textBoxes`
@@ -949,6 +750,8 @@ function buildTextItems(m, pon, view) {
949
750
  return (0, _embedpdf_core_annotation.textBoxes)(m, pon, view).map((tb) => {
950
751
  const a = m.byId[tb.id];
951
752
  const t = a?.text ?? _embedpdf_core_annotation.initialTextStyle;
753
+ const sw = a?.style.strokeWidth ?? 0;
754
+ const isCallout = a?.geom.t === "text" && !!a.geom.callout;
952
755
  return {
953
756
  id: tb.id,
954
757
  ref: a?.ref ?? null,
@@ -962,7 +765,7 @@ function buildTextItems(m, pon, view) {
962
765
  lineHeight: t.fontSize,
963
766
  color: t.fontColor,
964
767
  align: t.textAlign,
965
- padding: 2,
768
+ padding: isCallout ? sw + 2 : sw,
966
769
  background: a?.style.interiorColor ?? null
967
770
  }
968
771
  };
@@ -1115,6 +918,17 @@ const TOOL_DEFAULT_KEYS = {
1115
918
  const DRAW_TAGS = ["annotation-draw", "annotation-edit"];
1116
919
  const MARKUP_TAGS = ["text-select", "annotation-edit"];
1117
920
  /**
921
+ * Whether arming this ANNOTATION tool is touch consent (`Tool.touchDirect`):
922
+ * drag-CREATE tools — shape/ink draw and text-markup select — take
923
+ * single-finger touch wholesale (finger draws/marks, two fingers navigate:
924
+ * the drawing-app convention). Click-to-PLACE tools (stamp, note) do not: a
925
+ * tap places without any claim, and drags keep scrolling. Derived from the
926
+ * gesture tags, so custom registered tools inherit the right behavior. Only
927
+ * annotation-owned tools run through this — the built-in `pointer` tool also
928
+ * enables `text-select`, but it is registered by the hub, not here.
929
+ */
930
+ const isTouchDirect = (enables) => enables.has("annotation-draw") || enables.has("text-select");
931
+ /**
1118
932
  * The built-in tools — a data mirror of v2's registrations (shapes + lines + ink
1119
933
  * + free text in the draw channel, text markup + caret behind text selection, and
1120
934
  * the click-to-place stamp). Order is display-neutral; the toolbar owns layout.
@@ -1475,7 +1289,7 @@ const isIconPlaceKind = (subtype) => subtype === "text" || subtype === "file-att
1475
1289
  function iconPlacementDraft(subtype, geom, defaults, flags, file) {
1476
1290
  const shared = {
1477
1291
  ...geom,
1478
- color: cssToColor(defaults.color),
1292
+ color: require_seam.cssToColor(defaults.color),
1479
1293
  opacity: defaults.opacity,
1480
1294
  flags: {
1481
1295
  print: true,
@@ -1504,10 +1318,6 @@ const viewEnv = (zoom, rotation) => zoom != null || rotation != null ? {
1504
1318
  zoom: zoom ?? 1,
1505
1319
  rotation: rotation ?? 0
1506
1320
  } : void 0;
1507
- /** Broad annotate-write capability (PDF bit 6). The engine independently
1508
- * enforces this AND the per-owner collab rules; `canEdit`/`canDelete` are the
1509
- * UI mirror of the coarse gate. */
1510
- const ANNOTATE_MODIFY = "doc.annotate.modify";
1511
1321
  const TEXT_COMMIT_DEBOUNCE_MS = 250;
1512
1322
  /**
1513
1323
  * The annotation shell. The pure `update` runs HERE (so it can emit effects);
@@ -1516,7 +1326,6 @@ const TEXT_COMMIT_DEBOUNCE_MS = 250;
1516
1326
  * ref, patch/delete fire-and-forget.
1517
1327
  */
1518
1328
  function createAnnotationCapability(ctx, config = {}) {
1519
- const loaded = /* @__PURE__ */ new Set();
1520
1329
  const behaviors = [];
1521
1330
  /** Per-annotation debounce timer for the engine `contents` write while typing. */
1522
1331
  const textTimers = /* @__PURE__ */ new Map();
@@ -1527,6 +1336,8 @@ function createAnnotationCapability(ctx, config = {}) {
1527
1336
  * slot. See {@link FilePickerProvider}. */
1528
1337
  let filePickerProvider = null;
1529
1338
  const model = () => ctx.getState().model;
1339
+ const actionsForHover = ctx.tryGet(_embedpdf_plugin_actions_contract.ActionsToken);
1340
+ const hoverFeed = actionsForHover ? createAnnotationHoverFeed(actionsForHover, (id) => model().byId[id] ?? null) : null;
1530
1341
  const chromeSettings = () => ctx.getState().chrome;
1531
1342
  /**
1532
1343
  * Ids on a page whose Behavior is currently ENGAGED (form widgets under a
@@ -1549,16 +1360,23 @@ function createAnnotationCapability(ctx, config = {}) {
1549
1360
  };
1550
1361
  /** The CSS-px chrome settings converted to CONTENT units by the page's view
1551
1362
  * scale (px per content unit) — screen-constant grab zones + stalk at every
1552
- * zoom. No scale → the values are read as content units (headless callers). */
1553
- const chromeGeomAt = (scale) => {
1363
+ * zoom. No scale → the values are read as content units (headless callers).
1364
+ * `boost` widens the GRAB tolerances only (never the drawn chrome or the
1365
+ * knob's position) — the touch path passes {@link TOUCH_GRAB_BOOST} so
1366
+ * handles present finger-sized targets. */
1367
+ const chromeGeomAt = (scale, boost = 1) => {
1554
1368
  const cs = chromeSettings();
1555
1369
  const s = scale || 1;
1556
1370
  return {
1557
- handleTol: cs.handles.hitSize / 2 / s,
1558
- knobTol: cs.knob.hitSize / 2 / s,
1371
+ handleTol: cs.handles.hitSize / 2 / s * boost,
1372
+ knobTol: cs.knob.hitSize / 2 / s * boost,
1559
1373
  knobOffset: cs.knob.offset / s
1560
1374
  };
1561
1375
  };
1376
+ /** Finger-sized grab zones: 2× the mouse tolerances lands a ~24px handle hit
1377
+ * box in Apple's ~44pt-target territory without moving any visuals. */
1378
+ const TOUCH_GRAB_BOOST = 2;
1379
+ const grabBoost = (touch) => touch ? TOUCH_GRAB_BOOST : 1;
1562
1380
  const itemsCache = /* @__PURE__ */ new Map();
1563
1381
  const chromeCache = /* @__PURE__ */ new Map();
1564
1382
  const memoItems = (pon, view) => {
@@ -1642,11 +1460,12 @@ function createAnnotationCapability(ctx, config = {}) {
1642
1460
  const specs = (0, _embedpdf_core_annotation.sharedProps)(members.map((a) => a.subtype));
1643
1461
  const values = {};
1644
1462
  const mixed = [];
1463
+ const valueOf = (a, key) => key === "link" && a.subtype !== "link" ? (0, _embedpdf_core_annotation.linkOf)(m, a.id) : (0, _embedpdf_core_annotation.readProp)(a, key);
1645
1464
  for (const spec of specs) {
1646
- const first = (0, _embedpdf_core_annotation.readProp)(members[0], spec.key);
1465
+ const first = valueOf(members[0], spec.key);
1647
1466
  values[spec.key] = first;
1648
1467
  const firstJson = JSON.stringify(first);
1649
- if (members.some((a) => JSON.stringify((0, _embedpdf_core_annotation.readProp)(a, spec.key)) !== firstJson)) mixed.push(spec.key);
1468
+ if (members.some((a) => JSON.stringify(valueOf(a, spec.key)) !== firstJson)) mixed.push(spec.key);
1650
1469
  }
1651
1470
  const v = {
1652
1471
  specs,
@@ -1667,19 +1486,26 @@ function createAnnotationCapability(ctx, config = {}) {
1667
1486
  const v = [];
1668
1487
  for (const id of m.order) {
1669
1488
  const a = m.byId[id];
1670
- if (!a || a.pon !== pon || a.link == null) continue;
1489
+ if (!a || a.pon !== pon || a.subtype !== "link") continue;
1671
1490
  if (!(0, _embedpdf_core_annotation.viewable)(a.flags, false)) continue;
1672
- if (a.subtype === "link") {
1673
- if (a.geom.t === "rect") v.push({
1674
- id,
1675
- rect: a.geom.rect,
1676
- target: a.link
1677
- });
1678
- } else linkChildRects(a).forEach((rect, i) => v.push({
1679
- id: `${id}#${i}`,
1680
- rect,
1681
- target: a.link
1682
- }));
1491
+ const target = a.link ?? (a.data?.subtype === "link" ? a.data.target ?? null : null);
1492
+ if (target == null || a.geom.t !== "rect") continue;
1493
+ const activate = a.data?.actions?.activate;
1494
+ const ref = a.ref ?? a.data?.ref ?? void 0;
1495
+ const hoverEnter = Boolean(a.data?.actions?.cursorEnter?.root);
1496
+ const hoverExit = Boolean(a.data?.actions?.cursorExit?.root);
1497
+ v.push({
1498
+ id,
1499
+ rect: a.geom.rect,
1500
+ target,
1501
+ attached: a.group !== void 0,
1502
+ ...activate ? { activate } : {},
1503
+ ...ref ? { ref } : {},
1504
+ ...hoverEnter || hoverExit ? { hoverEvents: {
1505
+ enter: hoverEnter,
1506
+ exit: hoverExit
1507
+ } } : {}
1508
+ });
1683
1509
  }
1684
1510
  linkItemsCache.set(pon, {
1685
1511
  model: m,
@@ -1783,7 +1609,7 @@ function createAnnotationCapability(ctx, config = {}) {
1783
1609
  preview
1784
1610
  };
1785
1611
  ctx.dispatch({ type: "STAMP_ARM_CHANGED" });
1786
- ctx.tryGet(_embedpdf_plugin_interaction.InteractionToken)?.activateTool("stamp");
1612
+ ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken)?.activateTool("stamp");
1787
1613
  };
1788
1614
  const disarmStamp = () => {
1789
1615
  if (!armedStamp) return;
@@ -1920,14 +1746,14 @@ function createAnnotationCapability(ctx, config = {}) {
1920
1746
  }, rotCW);
1921
1747
  doc.page(pon).annotations.create({
1922
1748
  subtype: "stamp",
1923
- ...boxGeomFields(box, rotCW, crop),
1749
+ ...require_seam.boxGeomFields(box, rotCW, crop),
1924
1750
  source,
1925
1751
  fit: "contain"
1926
1752
  }).then((res) => {
1927
1753
  syncDTO(res.created, "baked");
1928
1754
  apply({
1929
1755
  t: "select",
1930
- ids: [refKey(res.created.ref)]
1756
+ ids: [require_seam.refKey(res.created.ref)]
1931
1757
  });
1932
1758
  }, (err) => console.error("[annotation] stamp placement failed:", err));
1933
1759
  return true;
@@ -1936,7 +1762,7 @@ function createAnnotationCapability(ctx, config = {}) {
1936
1762
  * (0 when the tool doesn't ask for upright, or the display isn't rotated). */
1937
1763
  const uprightRotFor = (displayRotation) => {
1938
1764
  if (!displayRotation) return 0;
1939
- const ix = ctx.tryGet(_embedpdf_plugin_interaction.InteractionToken);
1765
+ const ix = ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken);
1940
1766
  return (ix ? registry.get(ix.activeToolId()) : void 0)?.upright ? (0, _embedpdf_core_annotation.uprightRotation)(displayRotation) : 0;
1941
1767
  };
1942
1768
  const placeArmedStamp = (pon, point, displayRotation) => {
@@ -1965,7 +1791,7 @@ function createAnnotationCapability(ctx, config = {}) {
1965
1791
  * document changed while the picker was open (the intent expired).
1966
1792
  */
1967
1793
  const requestStampAt = (pon, point, displayRotation) => {
1968
- const ix = ctx.tryGet(_embedpdf_plugin_interaction.InteractionToken);
1794
+ const ix = ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken);
1969
1795
  const tool = ix ? registry.get(ix.activeToolId()) : void 0;
1970
1796
  const spec = tool?.source;
1971
1797
  if (!spec) return false;
@@ -1998,7 +1824,7 @@ function createAnnotationCapability(ctx, config = {}) {
1998
1824
  provider(req).then((picked) => {
1999
1825
  if (!picked) return;
2000
1826
  if (ctx.doc !== docAtClick) return;
2001
- if (ctx.tryGet(_embedpdf_plugin_interaction.InteractionToken)?.activeToolId() !== tool.id) return;
1827
+ if (ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken)?.activeToolId() !== tool.id) return;
2002
1828
  place(picked);
2003
1829
  }, (err) => console.error("[annotation] file-picker provider failed:", err));
2004
1830
  return true;
@@ -2015,12 +1841,12 @@ function createAnnotationCapability(ctx, config = {}) {
2015
1841
  width: crop.right - crop.left,
2016
1842
  height: crop.top - crop.bottom
2017
1843
  }, rotCW);
2018
- const draft = iconPlacementDraft(tool.subtype, boxGeomFields(box, rotCW, crop), (0, _embedpdf_core_annotation.defaultsFor)(model(), tool.preset), tool.flags, file);
1844
+ const draft = iconPlacementDraft(tool.subtype, require_seam.boxGeomFields(box, rotCW, crop), (0, _embedpdf_core_annotation.defaultsFor)(model(), tool.preset), tool.flags, file);
2019
1845
  doc.page(pon).annotations.create(draft).then((res) => {
2020
1846
  syncDTO(res.created, "baked");
2021
1847
  apply({
2022
1848
  t: "select",
2023
- ids: [refKey(res.created.ref)]
1849
+ ids: [require_seam.refKey(res.created.ref)]
2024
1850
  });
2025
1851
  }, (err) => console.error("[annotation] icon placement failed:", err));
2026
1852
  return true;
@@ -2038,7 +1864,7 @@ function createAnnotationCapability(ctx, config = {}) {
2038
1864
  */
2039
1865
  const placeAt = (pon, point, displayRotation) => {
2040
1866
  if (placeArmedStamp(pon, point, displayRotation)) return true;
2041
- const ix = ctx.tryGet(_embedpdf_plugin_interaction.InteractionToken);
1867
+ const ix = ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken);
2042
1868
  const tool = ix ? registry.get(ix.activeToolId()) : void 0;
2043
1869
  if (!tool) return false;
2044
1870
  if (tool.subtype === "stamp") return requestStampAt(pon, point, displayRotation);
@@ -2049,7 +1875,7 @@ function createAnnotationCapability(ctx, config = {}) {
2049
1875
  };
2050
1876
  /** The page a ref lives on: from the loaded model first (covers obj/nm refs
2051
1877
  * that don't carry a pon), else the ref itself (index refs do). */
2052
- const ponForRef = (ref) => model().byId[refKey(ref)]?.pon ?? (ref.kind === "index" ? ref.pageObjectNumber : null);
1878
+ const ponForRef = (ref) => model().byId[require_seam.refKey(ref)]?.pon ?? (ref.kind === "index" ? ref.pageObjectNumber : null);
2053
1879
  /**
2054
1880
  * Re-sync one annotation into the model from the authoritative engine DTO,
2055
1881
  * with the render `source` the caller decides: `'vector'` when WE authored or
@@ -2063,7 +1889,7 @@ function createAnnotationCapability(ctx, config = {}) {
2063
1889
  const crop = cropOf(dto.pageObjectNumber);
2064
1890
  if (crop) apply({
2065
1891
  t: "upsert",
2066
- annots: [fromDTO(dto, crop, source)],
1892
+ annots: [ingestDTO(dto, crop, source)],
2067
1893
  bumpAp
2068
1894
  });
2069
1895
  };
@@ -2085,6 +1911,346 @@ function createAnnotationCapability(ctx, config = {}) {
2085
1911
  });
2086
1912
  for (const fx of effects) perform(fx, next);
2087
1913
  }
1914
+ let hydrationStarted = false;
1915
+ let hydrationWindow = false;
1916
+ let hydrationRunning = null;
1917
+ let hydrateAgain = false;
1918
+ const pendingRemote = [];
1919
+ const setHydration = (hydration) => ctx.dispatch({
1920
+ type: "SET_HYDRATION",
1921
+ hydration
1922
+ });
1923
+ const ingestSnapshot = (snap, bumpAp) => {
1924
+ const annots = [];
1925
+ for (const page of snap.pages) {
1926
+ const crop = cropOf(page.pageState.pageObjectNumber);
1927
+ if (!crop) continue;
1928
+ annots.push(...page.annotations.map((d) => ingestDTO(d, crop)));
1929
+ }
1930
+ apply({
1931
+ t: "hydrated",
1932
+ annots,
1933
+ bumpAp
1934
+ });
1935
+ };
1936
+ const canRead = () => ctx.doc?.security.allows("doc.annotate.read") ?? true;
1937
+ const runHydration = (bumpAp) => {
1938
+ const doc = ctx.doc;
1939
+ if (!doc) return Promise.resolve();
1940
+ if (!canRead()) {
1941
+ setHydration({ status: "forbidden" });
1942
+ return Promise.resolve();
1943
+ }
1944
+ hydrationWindow = true;
1945
+ setHydration({ status: "loading" });
1946
+ const run = doc.annotations.listRawAll().then((snap) => {
1947
+ ingestSnapshot(snap, bumpAp);
1948
+ const head = snap.auditHead;
1949
+ const queued = pendingRemote.splice(0);
1950
+ hydrationWindow = false;
1951
+ for (const event of queued) {
1952
+ const serverId = "origin" in event ? event.origin.serverId : null;
1953
+ if (head !== void 0 && serverId !== null && serverId <= head) continue;
1954
+ applyRemoteAnnotationEvent(event);
1955
+ }
1956
+ setHydration({ status: "complete" });
1957
+ }).catch((error) => {
1958
+ hydrationWindow = false;
1959
+ for (const event of pendingRemote.splice(0)) applyRemoteAnnotationEvent(event);
1960
+ setHydration({
1961
+ status: "error",
1962
+ error
1963
+ });
1964
+ }).finally(() => {
1965
+ hydrationRunning = null;
1966
+ if (hydrateAgain) {
1967
+ hydrateAgain = false;
1968
+ hydrationRunning = runHydration(true);
1969
+ }
1970
+ });
1971
+ hydrationRunning = run;
1972
+ return run;
1973
+ };
1974
+ const rehydrate = () => {
1975
+ if (hydrationRunning) {
1976
+ hydrateAgain = true;
1977
+ return hydrationRunning;
1978
+ }
1979
+ hydrationRunning = runHydration(true);
1980
+ return hydrationRunning;
1981
+ };
1982
+ /**
1983
+ * Fold one REMOTE annotation event into the model (transplanted from the
1984
+ * effects layer so hydration can queue + replay through the same code
1985
+ * path). `bump` re-fetches rasters, `keep` doesn't — driven by the
1986
+ * ENGINE'S appearance echo riding the event, so a remote MOVE costs
1987
+ * peers zero re-renders while a remote restyle refreshes exactly once.
1988
+ */
1989
+ const upsertRemote = (dtos, bumpAp) => {
1990
+ const bump = [];
1991
+ const keep = [];
1992
+ for (const dto of dtos) {
1993
+ const crop = cropOf(dto.pageObjectNumber);
1994
+ if (!crop) continue;
1995
+ const a = ingestDTO(dto, crop, "baked");
1996
+ (bumpAp && !(0, _embedpdf_core_annotation.isSubstrateOnly)(a) ? bump : keep).push(a);
1997
+ }
1998
+ if (bump.length) apply({
1999
+ t: "upsert",
2000
+ annots: bump,
2001
+ bumpAp: true
2002
+ });
2003
+ if (keep.length) apply({
2004
+ t: "upsert",
2005
+ annots: keep
2006
+ });
2007
+ };
2008
+ const applyRemoteAnnotationEvent = (event) => {
2009
+ switch (event.type) {
2010
+ case "annotation.created":
2011
+ upsertRemote([event.created], true);
2012
+ break;
2013
+ case "annotation.updated":
2014
+ upsertRemote([event.updated], event.appearance.changed);
2015
+ break;
2016
+ case "annotation.moved":
2017
+ upsertRemote(event.moved, false);
2018
+ break;
2019
+ case "annotation.deleted":
2020
+ if (event.deleted) {
2021
+ const key = (0, _embedpdf_engine_core_runtime.encodeStableIdKey)(event.deleted);
2022
+ if (model().byId[key]) apply({
2023
+ t: "remove",
2024
+ ids: [key]
2025
+ });
2026
+ }
2027
+ break;
2028
+ default: break;
2029
+ }
2030
+ };
2031
+ const currentUserId = () => ctx.doc?.security.identity?.user_id;
2032
+ let threadsMemo = null;
2033
+ /** Value-stable layout key: display order is all the sort consumes, so
2034
+ * the memo must survive hosts that rebuild the pages array per read. */
2035
+ const layoutSignature = () => (ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber).join(",");
2036
+ const computeThreadsIndex = () => {
2037
+ const m = model();
2038
+ const dtos = [];
2039
+ for (const id of m.order) {
2040
+ const data = m.byId[id]?.data;
2041
+ if (data) dtos.push(data);
2042
+ }
2043
+ const threads = (0, _embedpdf_engine_core_runtime.buildCommentThreads)(dtos, { currentUserId: currentUserId() });
2044
+ const pages = ctx.document()?.pages ?? [];
2045
+ const displayIndex = new Map(pages.map((p, i) => [p.pageObjectNumber, i]));
2046
+ threads.sort((a, b) => {
2047
+ const pa = displayIndex.get(a.pageObjectNumber) ?? Number.MAX_SAFE_INTEGER;
2048
+ const pb = displayIndex.get(b.pageObjectNumber) ?? Number.MAX_SAFE_INTEGER;
2049
+ if (pa !== pb) return pa - pb;
2050
+ if (a.root.rect.top !== b.root.rect.top) return b.root.rect.top - a.root.rect.top;
2051
+ const ca = a.root.created ?? "";
2052
+ const cb = b.root.created ?? "";
2053
+ return ca < cb ? -1 : ca > cb ? 1 : 0;
2054
+ });
2055
+ const byMember = /* @__PURE__ */ new Map();
2056
+ for (const t of threads) {
2057
+ byMember.set(require_seam.refKey(t.root.ref), t);
2058
+ for (const r of t.replies) byMember.set(require_seam.refKey(r.ref), t);
2059
+ for (const g of t.groupedParts) byMember.set(require_seam.refKey(g.ref), t);
2060
+ for (const s of t.review.statusRefs) byMember.set(require_seam.refKey(s), t);
2061
+ }
2062
+ return {
2063
+ threads,
2064
+ byMember
2065
+ };
2066
+ };
2067
+ const threadsIndex = () => {
2068
+ const m = model();
2069
+ const layout = layoutSignature();
2070
+ const userId = currentUserId();
2071
+ if (threadsMemo && threadsMemo.byId === m.byId && threadsMemo.order === m.order && threadsMemo.layout === layout && threadsMemo.userId === userId) return threadsMemo;
2072
+ threadsMemo = {
2073
+ ...computeThreadsIndex(),
2074
+ byId: m.byId,
2075
+ order: m.order,
2076
+ layout,
2077
+ userId
2078
+ };
2079
+ return threadsMemo;
2080
+ };
2081
+ const threadOf = (ref) => {
2082
+ const t = threadsIndex().byMember.get(require_seam.refKey(ref));
2083
+ if (!t) throw new Error("[annotation] no comment thread contains this ref");
2084
+ return t;
2085
+ };
2086
+ /** Engine create + model sync for conversation-plane annotations (they
2087
+ * never paint, so the render source is immaterial — 'baked' avoids any
2088
+ * vector-scene work). */
2089
+ const createConversationAnnot = async (pon, draft) => {
2090
+ const doc = ctx.doc;
2091
+ if (!doc) throw new Error("[annotation] no document bound");
2092
+ const res = await doc.page(pon).annotations.create(draft);
2093
+ syncDTO(res.created, "baked");
2094
+ return res.created;
2095
+ };
2096
+ const deleteOne = async (ref) => {
2097
+ const doc = ctx.doc;
2098
+ if (!doc) throw new Error("[annotation] no document bound");
2099
+ await doc.page(ref.pageObjectNumber).annotations.delete(ref);
2100
+ apply({
2101
+ t: "remove",
2102
+ ids: [require_seam.refKey(ref)]
2103
+ });
2104
+ };
2105
+ const REPLY_FLAGS = {
2106
+ print: true,
2107
+ noZoom: true,
2108
+ noRotate: true
2109
+ };
2110
+ const STATUS_FLAGS = {
2111
+ hidden: true,
2112
+ noZoom: true,
2113
+ noRotate: true
2114
+ };
2115
+ /** Coarse authority mirror (doc.annotate.modify) — B3 replaces this with
2116
+ * the collab-scope mirrors; the FLAG split is already the real rule. */
2117
+ const mutationTarget = (ref) => {
2118
+ const d = model().byId[require_seam.refKey(ref)]?.data;
2119
+ return {
2120
+ ...d?.userId !== void 0 ? { userId: d.userId } : {},
2121
+ ...d?.groupId !== void 0 ? { groupId: d.groupId } : {}
2122
+ };
2123
+ };
2124
+ const allowsCreate = () => ctx.doc?.security.allowsAnnotationCreate() ?? false;
2125
+ /**
2126
+ * Ingest an engine DTO with this session's per-record AUTHORITY projected
2127
+ * onto it (permissions.md) — asked of the SAME mirrors the server enforces
2128
+ * with, against the record's stamped owner. Fused into
2129
+ * `annotTransformable`/`annotDeletable`, so hit-test, chrome, props and
2130
+ * delete all agree with the engine by construction. No security context
2131
+ * (bare local engines, tests) → unstamped → allowed; the engine enforces.
2132
+ */
2133
+ const ingestDTO = (dto, crop, source) => {
2134
+ const a = fromDTO(dto, crop, source);
2135
+ const sec = ctx.doc?.security;
2136
+ if (!sec) return a;
2137
+ const target = {
2138
+ ...dto.userId !== void 0 ? { userId: dto.userId } : {},
2139
+ ...dto.groupId !== void 0 ? { groupId: dto.groupId } : {}
2140
+ };
2141
+ return {
2142
+ ...a,
2143
+ authority: {
2144
+ update: sec.allowsAnnotationMutation("update", target),
2145
+ delete: sec.allowsAnnotationMutation("delete", target)
2146
+ }
2147
+ };
2148
+ };
2149
+ const allowsMutation = (action, ref) => ctx.doc?.security.allowsAnnotationMutation(action, mutationTarget(ref)) ?? false;
2150
+ const deletableOne = (ref) => {
2151
+ const a = model().byId[require_seam.refKey(ref)];
2152
+ return !!a && (0, _embedpdf_core_annotation.annotDeletable)(a);
2153
+ };
2154
+ const threadMemberRefs = (t) => [
2155
+ ...t.replies.map((r) => r.ref),
2156
+ ...t.groupedParts.map((g) => g.ref),
2157
+ ...t.review.statusRefs,
2158
+ t.root.ref
2159
+ ];
2160
+ const comments = {
2161
+ threads: () => threadsIndex().threads,
2162
+ thread: (ref) => threadsIndex().byMember.get(require_seam.refKey(ref)) ?? null,
2163
+ hydration: () => ctx.getState().hydration,
2164
+ rehydrate,
2165
+ reply: async (ref, text) => {
2166
+ const t = threadOf(ref);
2167
+ return (await createConversationAnnot(t.pageObjectNumber, {
2168
+ subtype: "text",
2169
+ rect: t.root.rect,
2170
+ icon: "comment",
2171
+ contents: text,
2172
+ inReplyTo: t.root.ref,
2173
+ flags: { ...REPLY_FLAGS }
2174
+ })).ref;
2175
+ },
2176
+ edit: async (ref, text) => {
2177
+ const subtype = model().byId[require_seam.refKey(ref)]?.data?.subtype;
2178
+ if (!subtype) throw new Error("[annotation] cannot edit an uncommitted annotation");
2179
+ await updateOne(ref, {
2180
+ subtype,
2181
+ contents: text
2182
+ });
2183
+ },
2184
+ setStatus: async (ref, state) => {
2185
+ const t = threadOf(ref);
2186
+ const userId = currentUserId();
2187
+ const previous = userId ? t.review.byReviewer[userId] : void 0;
2188
+ await createConversationAnnot(t.pageObjectNumber, {
2189
+ subtype: "text",
2190
+ rect: t.root.rect,
2191
+ inReplyTo: previous?.ref ?? t.root.ref,
2192
+ state,
2193
+ stateModel: "review",
2194
+ flags: { ...STATUS_FLAGS }
2195
+ });
2196
+ },
2197
+ setMarked: async (ref, marked) => {
2198
+ const t = threadOf(ref);
2199
+ await createConversationAnnot(t.pageObjectNumber, {
2200
+ subtype: "text",
2201
+ rect: t.root.rect,
2202
+ inReplyTo: t.root.ref,
2203
+ state: marked ? "marked" : "unmarked",
2204
+ stateModel: "marked",
2205
+ flags: { ...STATUS_FLAGS }
2206
+ });
2207
+ },
2208
+ remove: deleteOne,
2209
+ removeThread: async (ref) => {
2210
+ const t = threadOf(ref);
2211
+ const members = threadMemberRefs(t);
2212
+ const blocked = members.filter((r) => !deletableOne(r));
2213
+ if (blocked.length > 0) return {
2214
+ deleted: [],
2215
+ failed: blocked.map((r) => ({
2216
+ ref: r,
2217
+ error: /* @__PURE__ */ new Error("delete not permitted")
2218
+ }))
2219
+ };
2220
+ const deleted = [];
2221
+ const failed = [];
2222
+ for (const member of members) {
2223
+ if (failed.length > 0) break;
2224
+ try {
2225
+ await deleteOne(member);
2226
+ deleted.push(member);
2227
+ } catch (error) {
2228
+ failed.push({
2229
+ ref: member,
2230
+ error
2231
+ });
2232
+ }
2233
+ }
2234
+ return {
2235
+ deleted,
2236
+ failed
2237
+ };
2238
+ },
2239
+ permissionsFor: (ref) => {
2240
+ model().byId[require_seam.refKey(ref)]?.flags;
2241
+ const t = threadsIndex().byMember.get(require_seam.refKey(ref)) ?? null;
2242
+ return {
2243
+ canReply: allowsCreate(),
2244
+ canSetStatus: allowsCreate(),
2245
+ canEditText: (() => {
2246
+ const a = model().byId[require_seam.refKey(ref)];
2247
+ return !!a && (0, _embedpdf_core_annotation.annotContentsEditable)(a);
2248
+ })(),
2249
+ canDelete: deletableOne(ref),
2250
+ canDeleteThread: t !== null && threadMemberRefs(t).every(deletableOne)
2251
+ };
2252
+ }
2253
+ };
2088
2254
  /**
2089
2255
  * Per-parent serialization of attached-link reconciles: rapid edits chain
2090
2256
  * instead of interleaving (two overlapping runs could double-create
@@ -2092,41 +2258,54 @@ function createAnnotationCapability(ctx, config = {}) {
2092
2258
  * chained run converges on the latest desired state.
2093
2259
  */
2094
2260
  const linkSyncChains = /* @__PURE__ */ new Map();
2095
- const scheduleLinkSync = (id) => {
2096
- const next = (linkSyncChains.get(id) ?? Promise.resolve()).then(() => reconcileLinkChildren(id));
2261
+ /**
2262
+ * Queue a reconcile. `intent` is either an explicit target (a set/clear —
2263
+ * captured by THIS run's closure, so chained sets stay latest-wins) or
2264
+ * `'keep'` (a geometry follow: re-rect the children toward whatever target
2265
+ * the substrate holds AT RUN TIME — so a remote retarget is never undone
2266
+ * by a local move). Returns the chain, so `links.set()` can await commit.
2267
+ */
2268
+ const scheduleLinkSync = (id, intent) => {
2269
+ const next = (linkSyncChains.get(id) ?? Promise.resolve()).then(() => reconcileLinkChildren(id, intent === "keep" ? (0, _embedpdf_core_annotation.linkOf)(model(), id) : intent.target));
2097
2270
  linkSyncChains.set(id, next);
2098
2271
  next.finally(() => {
2099
2272
  if (linkSyncChains.get(id) === next) linkSyncChains.delete(id);
2100
2273
  });
2274
+ return next;
2101
2275
  };
2102
2276
  /**
2103
2277
  * THE one place attached link children are created, retargeted, re-rected,
2104
- * or deleted (the delete-with-parent expansion in the core is the one other
2105
- * consumer of `linkRefs`). Declarative: desired state is derived fresh from
2106
- * the parent's `link` value + committed geometry (`linkChildRects`), then
2107
- * diffed against the join keys the last fold reported. Idempotent — foreign
2278
+ * or deleted. Declarative: desired state = `desired` target + the parent's
2279
+ * committed geometry (`linkChildRects`); CURRENT state is read straight
2280
+ * from the substrate (`linkChildrenOf`) — no join-key ledger. Results land
2281
+ * as ordinary substrate upserts/removes, so the `linkOf` lens converges
2282
+ * immediately locally and via events everywhere else. Idempotent — foreign
2108
2283
  * inconsistencies heal on the next local edit.
2109
2284
  */
2110
- const reconcileLinkChildren = async (id) => {
2285
+ const reconcileLinkChildren = async (id, desired) => {
2111
2286
  const doc = ctx.doc;
2112
2287
  const a = model().byId[id];
2113
2288
  if (!doc || !a || !a.ref || a.subtype === "link") return;
2114
2289
  const crop = cropOf(a.pon);
2115
2290
  if (!crop) return;
2116
2291
  const page = doc.page(a.pon);
2117
- const target = writableTarget(a.link);
2118
- const rects = a.link == null ? [] : linkChildRects(a).map((r) => (0, _embedpdf_core_annotation.contentToPdfRect)(r, crop));
2119
- const current = a.linkRefs ?? [];
2120
- const nextRefs = [];
2292
+ const target = require_seam.writableTarget(desired);
2293
+ const rects = desired == null ? [] : linkChildRects(a).map((r) => (0, _embedpdf_core_annotation.contentToPdfRect)(r, crop));
2294
+ const current = (0, _embedpdf_core_annotation.linkChildrenOf)(model(), id);
2121
2295
  try {
2122
2296
  const paired = Math.min(current.length, rects.length);
2123
2297
  for (let i = 0; i < paired; i++) {
2124
- const res = await page.annotations.update(current[i], {
2298
+ const ref = current[i].ref;
2299
+ if (!ref) continue;
2300
+ const res = await page.annotations.update(ref, {
2125
2301
  subtype: "link",
2126
2302
  rect: rects[i],
2127
2303
  ...target ? { target } : {}
2128
2304
  });
2129
- nextRefs.push(res.updated.ref);
2305
+ apply({
2306
+ t: "upsert",
2307
+ annots: [ingestDTO(res.updated, crop, "baked")]
2308
+ });
2130
2309
  }
2131
2310
  for (let i = current.length; i < rects.length; i++) {
2132
2311
  const res = await page.annotations.create({
@@ -2136,20 +2315,22 @@ function createAnnotationCapability(ctx, config = {}) {
2136
2315
  inReplyTo: a.ref,
2137
2316
  replyType: "group"
2138
2317
  });
2139
- nextRefs.push(res.created.ref);
2318
+ apply({
2319
+ t: "upsert",
2320
+ annots: [ingestDTO(res.created, crop, "baked")]
2321
+ });
2322
+ }
2323
+ for (let i = rects.length; i < current.length; i++) {
2324
+ const child = current[i];
2325
+ if (child.ref) await page.annotations.delete(child.ref);
2326
+ apply({
2327
+ t: "remove",
2328
+ ids: [child.id]
2329
+ });
2140
2330
  }
2141
- for (let i = rects.length; i < current.length; i++) await page.annotations.delete(current[i]);
2142
2331
  } catch (err) {
2143
2332
  console.error("[annotation] attached-link sync failed:", err);
2144
2333
  }
2145
- const cur = model().byId[id];
2146
- if (cur) apply({
2147
- t: "upsert",
2148
- annots: [{
2149
- ...cur,
2150
- linkRefs: nextRefs
2151
- }]
2152
- });
2153
2334
  };
2154
2335
  /** A relationship-only engine patch (sets/clears `/IRT` + `/RT`) — geometry and
2155
2336
  * style are left untouched, so grouping never re-bakes an appearance. */
@@ -2206,7 +2387,7 @@ function createAnnotationCapability(ctx, config = {}) {
2206
2387
  apply({
2207
2388
  t: "created",
2208
2389
  tempId: fx.primary,
2209
- id: refKey(primaryResult.created.ref),
2390
+ id: require_seam.refKey(primaryResult.created.ref),
2210
2391
  ref: primaryResult.created.ref
2211
2392
  });
2212
2393
  syncDTO(primaryResult.created, "vector");
@@ -2225,7 +2406,7 @@ function createAnnotationCapability(ctx, config = {}) {
2225
2406
  apply({
2226
2407
  t: "created",
2227
2408
  tempId,
2228
- id: refKey(result.created.ref),
2409
+ id: require_seam.refKey(result.created.ref),
2229
2410
  ref: result.created.ref
2230
2411
  });
2231
2412
  syncDTO(result.created, "vector");
@@ -2234,7 +2415,7 @@ function createAnnotationCapability(ctx, config = {}) {
2234
2415
  const removeIds = ids.filter((id) => !committed.some((c) => c.tempId === id));
2235
2416
  for (const part of [...committed].reverse()) try {
2236
2417
  await doc.page(primary.pon).annotations.delete(part.ref);
2237
- removeIds.push(refKey(part.ref));
2418
+ removeIds.push(require_seam.refKey(part.ref));
2238
2419
  } catch {}
2239
2420
  if (removeIds.length) apply({
2240
2421
  t: "remove",
@@ -2252,7 +2433,7 @@ function createAnnotationCapability(ctx, config = {}) {
2252
2433
  apply({
2253
2434
  t: "created",
2254
2435
  tempId: fx.id,
2255
- id: refKey(res.created.ref),
2436
+ id: require_seam.refKey(res.created.ref),
2256
2437
  ref: res.created.ref
2257
2438
  });
2258
2439
  syncDTO(res.created, "vector");
@@ -2274,11 +2455,70 @@ function createAnnotationCapability(ctx, config = {}) {
2274
2455
  if (!a || !a.ref || !patch) return;
2275
2456
  doc.page(a.pon).annotations.update(a.ref, patch).then((res) => {
2276
2457
  syncDTO(res.updated, a.source, res.appearance.changed);
2277
- if (a.linkRefs?.length) scheduleLinkSync(fx.id);
2278
- }, () => {});
2279
- } else if (fx.fx === "syncLink") scheduleLinkSync(fx.id);
2280
- else doc.page(fx.ref.pageObjectNumber).annotations.delete(fx.ref).then(() => {}, () => {});
2458
+ if ((0, _embedpdf_core_annotation.linkChildrenOf)(model(), fx.id).length) scheduleLinkSync(fx.id, "keep");
2459
+ }, () => {
2460
+ if (a.data && crop) apply({
2461
+ t: "upsert",
2462
+ annots: [ingestDTO(a.data, crop, a.source)]
2463
+ });
2464
+ });
2465
+ } else if (fx.fx === "syncLink") scheduleLinkSync(fx.id, { target: fx.target });
2466
+ else {
2467
+ require_seam.refKey(fx.ref);
2468
+ doc.page(fx.ref.pageObjectNumber).annotations.delete(fx.ref).then(() => {}, () => {});
2469
+ }
2281
2470
  }
2471
+ const reloadPageNow = async (pon) => {
2472
+ const doc = ctx.doc;
2473
+ const crop = cropOf(pon);
2474
+ if (!doc || !crop) return;
2475
+ try {
2476
+ const snap = await doc.page(pon).annotations.list();
2477
+ const m = model();
2478
+ const stale = m.order.filter((id) => m.byId[id]?.pon === pon);
2479
+ if (stale.length) apply({
2480
+ t: "remove",
2481
+ ids: stale
2482
+ });
2483
+ apply({
2484
+ t: "loaded",
2485
+ annots: snap.annotations.map((d) => ingestDTO(d, crop))
2486
+ });
2487
+ } catch {}
2488
+ };
2489
+ /** Script patch → the engine's per-kind patch vocabulary. Colors cross the
2490
+ * Acrobat-array → engine {r,g,b}/255 boundary here; everything else maps
2491
+ * one-to-one (the VM's validity matrix already scoped keys per kind). */
2492
+ const engineScriptPatch = (subtype, patch) => {
2493
+ const out = { subtype };
2494
+ const toEngineColor = (color) => {
2495
+ const rgb = (0, _embedpdf_core_acrojs.scriptColorToRgb)(color);
2496
+ return rgb ? {
2497
+ r: Math.round(rgb.r * 255),
2498
+ g: Math.round(rgb.g * 255),
2499
+ b: Math.round(rgb.b * 255)
2500
+ } : null;
2501
+ };
2502
+ if (patch.strokeColor) {
2503
+ const color = toEngineColor(patch.strokeColor);
2504
+ if (!color) return /* @__PURE__ */ new Error("transparent stroke colours are not supported");
2505
+ out.color = color;
2506
+ }
2507
+ if (patch.fillColor) out.interiorColor = toEngineColor(patch.fillColor);
2508
+ if (patch.opacity !== void 0) out.opacity = patch.opacity;
2509
+ if (patch.width !== void 0) out.strokeWidth = patch.width;
2510
+ if (patch.borderStyle) out.borderStyle = patch.borderStyle === "D" ? "dashed" : "solid";
2511
+ if (patch.dash) out.dashArray = patch.dash;
2512
+ if (patch.rect) out.rect = {
2513
+ left: patch.rect[0],
2514
+ bottom: patch.rect[1],
2515
+ right: patch.rect[2],
2516
+ top: patch.rect[3]
2517
+ };
2518
+ if (patch.contents !== void 0) out.contents = patch.contents;
2519
+ if (patch.flags) out.flags = patch.flags;
2520
+ return out;
2521
+ };
2282
2522
  return {
2283
2523
  create: async (pon, draft) => {
2284
2524
  const doc = ctx.doc;
@@ -2326,10 +2566,11 @@ function createAnnotationCapability(ctx, config = {}) {
2326
2566
  const resolved = buildToolRegistry([...configTools, def]).get(def.id);
2327
2567
  if (!resolved) throw new Error(`[annotation] could not resolve tool '${def.id}'`);
2328
2568
  registry.set(resolved.id, resolved);
2329
- const un = ctx.tryGet(_embedpdf_plugin_interaction.InteractionToken)?.registerTool({
2569
+ const un = ctx.tryGet(_embedpdf_plugin_interaction_contract.InteractionToken)?.registerTool({
2330
2570
  id: resolved.id,
2331
2571
  cursor: resolved.cursor,
2332
- enables: resolved.enables
2572
+ enables: resolved.enables,
2573
+ touchDirect: isTouchDirect(resolved.enables)
2333
2574
  });
2334
2575
  if (resolved.defaults) apply({
2335
2576
  t: "setDefaults",
@@ -2359,17 +2600,34 @@ function createAnnotationCapability(ctx, config = {}) {
2359
2600
  await doc.page(pon).annotations.delete(ref);
2360
2601
  apply({
2361
2602
  t: "remove",
2362
- ids: [refKey(ref)]
2603
+ ids: [require_seam.refKey(ref)]
2363
2604
  });
2364
2605
  },
2365
- canCreate: () => ctx.doc?.security.allows(ANNOTATE_MODIFY) ?? false,
2366
- canEdit: () => ctx.doc?.security.allows(ANNOTATE_MODIFY) ?? false,
2367
- canDelete: () => ctx.doc?.security.allows(ANNOTATE_MODIFY) ?? false,
2606
+ canRead: () => canRead(),
2607
+ canCreate: () => allowsCreate(),
2608
+ canEdit: (ref) => {
2609
+ const a = model().byId[require_seam.refKey(ref)];
2610
+ return !!a && (0, _embedpdf_core_annotation.annotTransformable)(a);
2611
+ },
2612
+ canDelete: (ref) => {
2613
+ const a = model().byId[require_seam.refKey(ref)];
2614
+ return !!a && (0, _embedpdf_core_annotation.annotDeletable)(a);
2615
+ },
2616
+ comments,
2617
+ links: {
2618
+ of: (ref) => {
2619
+ const a = model().byId[require_seam.refKey(ref)];
2620
+ if (!a) return null;
2621
+ return a.subtype === "link" ? a.link ?? null : (0, _embedpdf_core_annotation.linkOf)(model(), a.id);
2622
+ },
2623
+ set: (ref, target) => scheduleLinkSync(require_seam.refKey(ref), { target }),
2624
+ clear: (ref) => scheduleLinkSync(require_seam.refKey(ref), { target: null })
2625
+ },
2368
2626
  getSelection: () => {
2369
2627
  const m = model();
2370
2628
  return m.selected.map((id) => m.byId[id]?.ref).filter((r) => r != null);
2371
2629
  },
2372
- get: (ref) => model().byId[refKey(ref)]?.data ?? null,
2630
+ get: (ref) => model().byId[require_seam.refKey(ref)]?.data ?? null,
2373
2631
  list: (pon) => {
2374
2632
  const m = model();
2375
2633
  return m.order.map((id) => m.byId[id]).filter((a) => a?.pon === pon).map((a) => a?.data).filter((d) => d != null);
@@ -2385,7 +2643,14 @@ function createAnnotationCapability(ctx, config = {}) {
2385
2643
  selectionAnchor: (scale, rotation, zoom) => memoAnchor(scale, rotation, zoom),
2386
2644
  creationDraftAnchor: () => memoDraftAnchor(),
2387
2645
  selection: () => model().selected,
2388
- hitKind: (pon, point, scale, rotation, zoom) => (0, _embedpdf_core_annotation.hitTest)(model(), pon, point, chromeGeomAt(scale), model().hitMargin, pageBoxOf(pon), inertIdsAt(pon), viewEnv(zoom, rotation)).t,
2646
+ hitKind: (pon, point, scale, rotation, zoom, touch) => (0, _embedpdf_core_annotation.hitTest)(model(), pon, point, chromeGeomAt(scale, grabBoost(touch)), model().hitMargin, pageBoxOf(pon), inertIdsAt(pon), viewEnv(zoom, rotation)).t,
2647
+ claimsTouchAt: (pon, point, scale, rotation, zoom) => {
2648
+ const m = model();
2649
+ const t = (0, _embedpdf_core_annotation.hitTest)(m, pon, point, chromeGeomAt(scale, TOUCH_GRAB_BOOST), m.hitMargin, pageBoxOf(pon), inertIdsAt(pon), viewEnv(zoom, rotation));
2650
+ if (t.t === "handle" || t.t === "rotate" || t.t === "group-handle") return true;
2651
+ if (t.t === "annot") return m.selected.includes(t.id) && m.selected.every((id) => (0, _embedpdf_core_annotation.canMove)(m, id));
2652
+ return false;
2653
+ },
2389
2654
  cursorAt: (pon, point, scale, rotation, zoom) => (0, _embedpdf_core_annotation.cursorAt)(model(), pon, point, chromeGeomAt(scale), model().hitMargin, pageBoxOf(pon), inertIdsAt(pon), viewEnv(zoom, rotation)),
2390
2655
  hoverAt: (at) => {
2391
2656
  const m = model();
@@ -2394,10 +2659,13 @@ function createAnnotationCapability(ctx, config = {}) {
2394
2659
  const h = (0, _embedpdf_core_annotation.hitTest)(m, at.pon, at.point, chromeGeomAt(at.scale), m.hitMargin, pageBoxOf(at.pon), inertIdsAt(at.pon), viewEnv(at.zoom, at.rotation));
2395
2660
  if (h.t === "annot") id = h.id;
2396
2661
  }
2397
- if (m.hovered !== id) apply({
2398
- t: "hover",
2399
- id
2400
- });
2662
+ if (m.hovered !== id) {
2663
+ hoverFeed?.hover(id);
2664
+ apply({
2665
+ t: "hover",
2666
+ id
2667
+ });
2668
+ }
2401
2669
  },
2402
2670
  behaviorFor: (a) => behaviors.find((b) => b.matches(a) && b.engaged()) ?? null,
2403
2671
  linkItemsOn: (pon) => memoLinkItems(pon),
@@ -2407,6 +2675,7 @@ function createAnnotationCapability(ctx, config = {}) {
2407
2675
  for (const id of m.order) {
2408
2676
  const a = m.byId[id];
2409
2677
  if (!a || a.pon !== pon || a.source !== "baked" || !a.ref) continue;
2678
+ if ((0, _embedpdf_core_annotation.isSubstrateOnly)(a)) continue;
2410
2679
  parts.push(`${id}@${a.apVersion ?? 0}`);
2411
2680
  }
2412
2681
  return parts.sort().join("|");
@@ -2424,7 +2693,7 @@ function createAnnotationCapability(ctx, config = {}) {
2424
2693
  const crop = cropOf(pon);
2425
2694
  return crop ? (0, _embedpdf_core_annotation.pdfToContentRect)(rect, crop) : null;
2426
2695
  },
2427
- editPointer: (phase, pon, point, shift, scale, rotation, zoom) => apply({
2696
+ editPointer: (phase, pon, point, shift, scale, rotation, zoom, touch) => apply({
2428
2697
  t: "editPointer",
2429
2698
  phase,
2430
2699
  in: {
@@ -2432,7 +2701,7 @@ function createAnnotationCapability(ctx, config = {}) {
2432
2701
  point,
2433
2702
  shift,
2434
2703
  pageBox: pageBoxOf(pon),
2435
- chrome: chromeGeomAt(scale),
2704
+ chrome: chromeGeomAt(scale, grabBoost(touch)),
2436
2705
  inert: inertIdsAt(pon),
2437
2706
  ...zoom != null ? { zoom } : {},
2438
2707
  ...rotation != null ? { displayRotation: rotation } : {}
@@ -2452,6 +2721,7 @@ function createAnnotationCapability(ctx, config = {}) {
2452
2721
  }
2453
2722
  }),
2454
2723
  createPointer: (tool, phase, pon, point, finish = false, displayRotation) => {
2724
+ if (!allowsCreate()) return;
2455
2725
  const t = registry.get(tool);
2456
2726
  apply({
2457
2727
  t: "createPointer",
@@ -2477,30 +2747,60 @@ function createAnnotationCapability(ctx, config = {}) {
2477
2747
  finishCreationDraft: () => apply({ t: "finishCreationDraft" }),
2478
2748
  finishInkDraft: () => apply({ t: "finishInkDraft" }),
2479
2749
  cancelCreationDraft: () => apply({ t: "cancel" }),
2480
- createMarkup: (subtype, pon, rects, preset) => apply({
2481
- t: "createMarkup",
2482
- subtype,
2483
- pon,
2484
- rects,
2485
- preset,
2486
- flags: preset ? registry.get(preset)?.flags : void 0
2487
- }),
2488
- createCaret: (pon, textEndRect) => apply({
2489
- t: "createCaret",
2490
- pon,
2491
- rect: textEndRect
2492
- }),
2493
- createReplaceText: (pon, rects, textEndRect, preset) => apply({
2494
- t: "createReplaceText",
2495
- pon,
2496
- rects,
2497
- endRect: textEndRect,
2498
- preset
2499
- }),
2500
- previewMarkup: (subtype, rectsByPage, preset) => apply({
2750
+ markupFromSelection: (subtype, preset) => {
2751
+ if (!allowsCreate()) return false;
2752
+ const selection = ctx.tryGet(_embedpdf_plugin_selection_contract.SelectionToken);
2753
+ if (!selection || !selection.hasSelection()) return false;
2754
+ const snapshot = selection.snapshot();
2755
+ let created = false;
2756
+ for (const page of snapshot.pages) {
2757
+ if (!page.segments.length) continue;
2758
+ apply({
2759
+ t: "createMarkup",
2760
+ subtype,
2761
+ pon: page.pon,
2762
+ quads: page.segments.map((s) => s.quad),
2763
+ preset,
2764
+ flags: preset ? registry.get(preset)?.flags : void 0
2765
+ });
2766
+ created = true;
2767
+ }
2768
+ if (created) selection.clear();
2769
+ return created;
2770
+ },
2771
+ createMarkup: (subtype, pon, quads, preset) => {
2772
+ if (!allowsCreate()) return;
2773
+ apply({
2774
+ t: "createMarkup",
2775
+ subtype,
2776
+ pon,
2777
+ quads,
2778
+ preset,
2779
+ flags: preset ? registry.get(preset)?.flags : void 0
2780
+ });
2781
+ },
2782
+ createCaret: (pon, anchor) => {
2783
+ if (!allowsCreate()) return;
2784
+ apply({
2785
+ t: "createCaret",
2786
+ pon,
2787
+ anchor
2788
+ });
2789
+ },
2790
+ createReplaceText: (pon, quads, anchor, preset) => {
2791
+ if (!allowsCreate()) return;
2792
+ apply({
2793
+ t: "createReplaceText",
2794
+ pon,
2795
+ quads,
2796
+ anchor,
2797
+ preset
2798
+ });
2799
+ },
2800
+ previewMarkup: (subtype, quadsByPage, preset) => apply({
2501
2801
  t: "setMarkupPreview",
2502
2802
  subtype,
2503
- rectsByPage,
2803
+ quadsByPage,
2504
2804
  preset
2505
2805
  }),
2506
2806
  clearMarkupPreview: () => apply({ t: "clearMarkupPreview" }),
@@ -2525,7 +2825,7 @@ function createAnnotationCapability(ctx, config = {}) {
2525
2825
  select: (ref, options) => {
2526
2826
  apply({
2527
2827
  t: "select",
2528
- ids: [refKey(ref)],
2828
+ ids: [require_seam.refKey(ref)],
2529
2829
  add: options?.add
2530
2830
  });
2531
2831
  },
@@ -2570,76 +2870,62 @@ function createAnnotationCapability(ctx, config = {}) {
2570
2870
  await Promise.all(subs.map((a) => writeRelationship(a, { inReplyTo: null })));
2571
2871
  },
2572
2872
  canGroup: () => {
2573
- if (!(ctx.doc?.security.allows(ANNOTATE_MODIFY) ?? false)) return false;
2574
2873
  const m = model();
2575
2874
  const members = selectedCommitted();
2576
2875
  if (members.length < 2) return false;
2577
2876
  if (members.some((a) => a.pon !== members[0].pon)) return false;
2877
+ if (!members.every((a) => a.ref != null && allowsMutation("update", a.ref))) return false;
2578
2878
  const keys = new Set(m.selected.map((id) => (0, _embedpdf_core_annotation.groupKeyOf)(m, id)));
2579
2879
  if (keys.size === 1 && !keys.has(null)) return false;
2580
2880
  return true;
2581
2881
  },
2582
2882
  canUngroup: () => {
2583
- if (!(ctx.doc?.security.allows(ANNOTATE_MODIFY) ?? false)) return false;
2584
2883
  const m = model();
2585
- return m.selected.some((id) => (0, _embedpdf_core_annotation.groupKeyOf)(m, id) != null);
2884
+ const subs = (0, _embedpdf_core_annotation.expandGroups)(m, m.selected).map((id) => m.byId[id]).filter((a) => !!a && !!a.group);
2885
+ return subs.length > 0 && subs.every((a) => a.ref != null && allowsMutation("update", a.ref));
2586
2886
  },
2587
- ensurePage: (pon) => {
2588
- if (loaded.has(pon)) return;
2589
- const doc = ctx.doc;
2590
- const crop = cropOf(pon);
2591
- if (!doc || !crop) return;
2592
- loaded.add(pon);
2593
- doc.page(pon).annotations.list().then((snap) => apply({
2594
- t: "loaded",
2595
- annots: foldAttachedLinks(snap.annotations.map((d) => fromDTO(d, crop)))
2596
- }), () => {
2597
- loaded.delete(pon);
2598
- });
2887
+ ensurePage: () => {},
2888
+ reloadPage: (pon) => reloadPageNow(pon),
2889
+ hydration: () => ctx.getState().hydration,
2890
+ ensureHydrated: () => {
2891
+ if (hydrationStarted) return;
2892
+ hydrationStarted = true;
2893
+ runHydration(false);
2599
2894
  },
2600
- reloadPage: async (pon) => {
2601
- const doc = ctx.doc;
2602
- const crop = cropOf(pon);
2603
- if (!doc || !crop) return;
2604
- loaded.add(pon);
2605
- try {
2606
- const snap = await doc.page(pon).annotations.list();
2607
- const m = model();
2608
- const stale = m.order.filter((id) => m.byId[id]?.pon === pon);
2609
- if (stale.length) apply({
2610
- t: "remove",
2611
- ids: stale
2612
- });
2613
- apply({
2614
- t: "loaded",
2615
- annots: foldAttachedLinks(snap.annotations.map((d) => fromDTO(d, crop)))
2616
- });
2617
- } catch {
2618
- loaded.delete(pon);
2895
+ rehydrate,
2896
+ deliverRemoteAnnotationEvent: (event) => {
2897
+ if (hydrationWindow) {
2898
+ pendingRemote.push(event);
2899
+ return;
2619
2900
  }
2901
+ applyRemoteAnnotationEvent(event);
2620
2902
  },
2621
2903
  textItems: (pon, view) => memoTexts(pon, view),
2622
2904
  currentEditing: () => model().editing,
2623
2905
  beginTextEdit: (ref) => apply({
2624
2906
  t: "beginTextEdit",
2625
- id: refKey(ref)
2907
+ id: require_seam.refKey(ref)
2626
2908
  }),
2627
2909
  beginTextEditAt: (pon, point, scale, rotation, zoom) => {
2628
2910
  const m = model();
2629
2911
  const h = (0, _embedpdf_core_annotation.hitTest)(m, pon, point, chromeGeomAt(scale), m.hitMargin, pageBoxOf(pon), void 0, viewEnv(zoom, rotation));
2630
2912
  const id = h.t === "annot" || h.t === "handle" ? h.id : null;
2631
- if (id != null && m.byId[id]?.geom.t === "text") apply({
2632
- t: "beginTextEdit",
2633
- id
2634
- });
2913
+ if (id != null && m.byId[id]?.geom.t === "text") {
2914
+ apply({
2915
+ t: "beginTextEdit",
2916
+ id
2917
+ });
2918
+ return true;
2919
+ }
2920
+ return false;
2635
2921
  },
2636
2922
  setContents: (ref, text) => {
2637
2923
  apply({
2638
2924
  t: "setText",
2639
- id: refKey(ref),
2925
+ id: require_seam.refKey(ref),
2640
2926
  text
2641
2927
  });
2642
- const key = refKey(ref);
2928
+ const key = require_seam.refKey(ref);
2643
2929
  clearTimeout(textTimers.get(key));
2644
2930
  textTimers.set(key, setTimeout(() => {
2645
2931
  textTimers.delete(key);
@@ -2665,6 +2951,72 @@ function createAnnotationCapability(ctx, config = {}) {
2665
2951
  textTimers.clear();
2666
2952
  apply({ t: "endTextEdit" });
2667
2953
  },
2954
+ commitScriptEffects: async (entries) => {
2955
+ const doc = ctx.doc;
2956
+ if (!doc) return { results: entries.map((entry) => ({
2957
+ annotObjectNumber: entry.annotObjectNumber,
2958
+ status: "failed",
2959
+ error: "no document"
2960
+ })) };
2961
+ const results = [];
2962
+ const touchedPons = /* @__PURE__ */ new Set();
2963
+ let failed = false;
2964
+ for (const entry of entries) {
2965
+ if (failed) {
2966
+ results.push({
2967
+ annotObjectNumber: entry.annotObjectNumber,
2968
+ status: "skipped"
2969
+ });
2970
+ continue;
2971
+ }
2972
+ const loaded = model().byId[`obj:${entry.annotObjectNumber}`];
2973
+ const pon = loaded?.pon ?? entry.pageObjectNumber;
2974
+ let ref = loaded?.ref ?? null;
2975
+ let subtype = loaded?.subtype;
2976
+ if ((!ref || !subtype) && pon !== void 0) try {
2977
+ const { annotations } = await doc.page(pon).annotations.list();
2978
+ const dto = annotations.find((candidate) => candidate.ref.kind === "objectNumber" && candidate.ref.annotObjectNumber === entry.annotObjectNumber);
2979
+ if (dto) {
2980
+ ref = dto.ref;
2981
+ subtype = dto.subtype;
2982
+ }
2983
+ } catch {}
2984
+ if (pon === void 0 || !ref || !subtype) {
2985
+ results.push({
2986
+ annotObjectNumber: entry.annotObjectNumber,
2987
+ status: "failed",
2988
+ error: "annotation not resolved (page not loaded)"
2989
+ });
2990
+ continue;
2991
+ }
2992
+ const patch = engineScriptPatch(subtype, entry.patch);
2993
+ if (patch instanceof Error) {
2994
+ results.push({
2995
+ annotObjectNumber: entry.annotObjectNumber,
2996
+ status: "failed",
2997
+ error: patch.message
2998
+ });
2999
+ continue;
3000
+ }
3001
+ try {
3002
+ await doc.page(pon).annotations.update(ref, patch);
3003
+ touchedPons.add(pon);
3004
+ results.push({
3005
+ annotObjectNumber: entry.annotObjectNumber,
3006
+ status: "applied"
3007
+ });
3008
+ } catch (error) {
3009
+ failed = true;
3010
+ results.push({
3011
+ annotObjectNumber: entry.annotObjectNumber,
3012
+ status: "failed",
3013
+ error: error instanceof Error ? error.message : String(error)
3014
+ });
3015
+ }
3016
+ }
3017
+ for (const touched of touchedPons) await reloadPageNow(touched);
3018
+ return { results };
3019
+ },
2668
3020
  registerBehavior: (b) => {
2669
3021
  behaviors.push(b);
2670
3022
  return () => {
@@ -2675,12 +3027,6 @@ function createAnnotationCapability(ctx, config = {}) {
2675
3027
  };
2676
3028
  }
2677
3029
  //#endregion
2678
- Object.defineProperty(exports, "AnnotationToken", {
2679
- enumerable: true,
2680
- get: function() {
2681
- return AnnotationToken;
2682
- }
2683
- });
2684
3030
  Object.defineProperty(exports, "DEFAULT_TOOLS", {
2685
3031
  enumerable: true,
2686
3032
  get: function() {
@@ -2705,16 +3051,10 @@ Object.defineProperty(exports, "fromDTO", {
2705
3051
  return fromDTO;
2706
3052
  }
2707
3053
  });
2708
- Object.defineProperty(exports, "refKey", {
3054
+ Object.defineProperty(exports, "isTouchDirect", {
2709
3055
  enumerable: true,
2710
3056
  get: function() {
2711
- return refKey;
2712
- }
2713
- });
2714
- Object.defineProperty(exports, "styleFromDTO", {
2715
- enumerable: true,
2716
- get: function() {
2717
- return styleFromDTO;
3057
+ return isTouchDirect;
2718
3058
  }
2719
3059
  });
2720
3060
  Object.defineProperty(exports, "toCreateDraft", {
@@ -2729,11 +3069,5 @@ Object.defineProperty(exports, "toPatch", {
2729
3069
  return toPatch;
2730
3070
  }
2731
3071
  });
2732
- Object.defineProperty(exports, "widgetAppearanceFromProps", {
2733
- enumerable: true,
2734
- get: function() {
2735
- return widgetAppearanceFromProps;
2736
- }
2737
- });
2738
3072
 
2739
- //# sourceMappingURL=capability-hb2AU0f6.cjs.map
3073
+ //# sourceMappingURL=capability-Csb9WMm-.cjs.map