@embedpdf/plugin-annotation 2.15.0 → 3.0.0-next.0

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 (348) hide show
  1. package/LICENSE +192 -21
  2. package/dist/capability-fghl0JmE.js +2680 -0
  3. package/dist/capability-fghl0JmE.js.map +1 -0
  4. package/dist/capability-hb2AU0f6.cjs +2739 -0
  5. package/dist/capability-hb2AU0f6.cjs.map +1 -0
  6. package/dist/index.cjs +593 -2
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +65 -0
  9. package/dist/index.d.ts +65 -1
  10. package/dist/index.js +562 -6998
  11. package/dist/index.js.map +1 -1
  12. package/dist/internal.cjs +5 -0
  13. package/dist/internal.d.cts +13 -0
  14. package/dist/internal.d.ts +13 -0
  15. package/dist/internal.js +2 -0
  16. package/dist/types-BhWgqgbX.d.cts +879 -0
  17. package/dist/types-Da6JRmhw.d.ts +879 -0
  18. package/package.json +26 -66
  19. package/dist/lib/actions.d.ts +0 -192
  20. package/dist/lib/annotation-plugin.d.ts +0 -310
  21. package/dist/lib/geometry/cloudy-border.d.ts +0 -90
  22. package/dist/lib/geometry/index.d.ts +0 -2
  23. package/dist/lib/geometry/rotation.d.ts +0 -32
  24. package/dist/lib/handlers/callout-free-text.handler.d.ts +0 -3
  25. package/dist/lib/handlers/circle.handler.d.ts +0 -3
  26. package/dist/lib/handlers/click-detector.d.ts +0 -27
  27. package/dist/lib/handlers/free-text.handler.d.ts +0 -3
  28. package/dist/lib/handlers/index.d.ts +0 -14
  29. package/dist/lib/handlers/ink.handler.d.ts +0 -3
  30. package/dist/lib/handlers/insert-text.handler.d.ts +0 -8
  31. package/dist/lib/handlers/line.handler.d.ts +0 -3
  32. package/dist/lib/handlers/link.handler.d.ts +0 -3
  33. package/dist/lib/handlers/polygon.handler.d.ts +0 -3
  34. package/dist/lib/handlers/polyline.handler.d.ts +0 -3
  35. package/dist/lib/handlers/replace-text.handler.d.ts +0 -9
  36. package/dist/lib/handlers/selection-utils.d.ts +0 -7
  37. package/dist/lib/handlers/square.handler.d.ts +0 -3
  38. package/dist/lib/handlers/stamp.handler.d.ts +0 -3
  39. package/dist/lib/handlers/text-markup.handler.d.ts +0 -7
  40. package/dist/lib/handlers/text.handler.d.ts +0 -3
  41. package/dist/lib/handlers/types.d.ts +0 -176
  42. package/dist/lib/helpers.d.ts +0 -46
  43. package/dist/lib/index.d.ts +0 -21
  44. package/dist/lib/manifest.d.ts +0 -4
  45. package/dist/lib/patching/base-patch.d.ts +0 -87
  46. package/dist/lib/patching/index.d.ts +0 -6
  47. package/dist/lib/patching/insert-upright.d.ts +0 -20
  48. package/dist/lib/patching/line-ending-handlers.d.ts +0 -20
  49. package/dist/lib/patching/line-endings.d.ts +0 -13
  50. package/dist/lib/patching/patch-registry.d.ts +0 -40
  51. package/dist/lib/patching/patch-utils.d.ts +0 -97
  52. package/dist/lib/patching/patches/callout-freetext.patch.d.ts +0 -3
  53. package/dist/lib/patching/patches/circle.patch.d.ts +0 -3
  54. package/dist/lib/patching/patches/freetext.patch.d.ts +0 -3
  55. package/dist/lib/patching/patches/index.d.ts +0 -9
  56. package/dist/lib/patching/patches/ink.patch.d.ts +0 -3
  57. package/dist/lib/patching/patches/line.patch.d.ts +0 -3
  58. package/dist/lib/patching/patches/polygon.patch.d.ts +0 -3
  59. package/dist/lib/patching/patches/polyline.patch.d.ts +0 -3
  60. package/dist/lib/patching/patches/square.patch.d.ts +0 -3
  61. package/dist/lib/patching/patches/stamp.patch.d.ts +0 -3
  62. package/dist/lib/reducer.d.ts +0 -6
  63. package/dist/lib/selectors.d.ts +0 -139
  64. package/dist/lib/tools/default-tools.d.ts +0 -521
  65. package/dist/lib/tools/tools-utils.d.ts +0 -453
  66. package/dist/lib/tools/types.d.ts +0 -209
  67. package/dist/lib/types.d.ts +0 -671
  68. package/dist/lib/utils/index.d.ts +0 -1
  69. package/dist/lib/utils/use-state.d.ts +0 -12
  70. package/dist/preact/adapter.d.ts +0 -14
  71. package/dist/preact/core.d.ts +0 -1
  72. package/dist/preact/index.cjs +0 -2
  73. package/dist/preact/index.cjs.map +0 -1
  74. package/dist/preact/index.d.ts +0 -1
  75. package/dist/preact/index.js +0 -4013
  76. package/dist/preact/index.js.map +0 -1
  77. package/dist/preact/interaction-manager.d.ts +0 -1
  78. package/dist/preact/selection.d.ts +0 -1
  79. package/dist/preact/utils.d.ts +0 -1
  80. package/dist/react/adapter.d.ts +0 -6
  81. package/dist/react/core.d.ts +0 -1
  82. package/dist/react/index.cjs +0 -2
  83. package/dist/react/index.cjs.map +0 -1
  84. package/dist/react/index.d.ts +0 -1
  85. package/dist/react/index.js +0 -4014
  86. package/dist/react/index.js.map +0 -1
  87. package/dist/react/interaction-manager.d.ts +0 -1
  88. package/dist/react/selection.d.ts +0 -1
  89. package/dist/react/utils.d.ts +0 -1
  90. package/dist/shared/annotation-bounds.d.ts +0 -14
  91. package/dist/shared/components/annotation-container.d.ts +0 -71
  92. package/dist/shared/components/annotation-layer.d.ts +0 -33
  93. package/dist/shared/components/annotation-navigation-handler.d.ts +0 -1
  94. package/dist/shared/components/annotation-paint-layer.d.ts +0 -7
  95. package/dist/shared/components/annotations/callout-free-text-preview.d.ts +0 -15
  96. package/dist/shared/components/annotations/callout-free-text.d.ts +0 -15
  97. package/dist/shared/components/annotations/caret.d.ts +0 -24
  98. package/dist/shared/components/annotations/circle.d.ts +0 -35
  99. package/dist/shared/components/annotations/free-text.d.ts +0 -17
  100. package/dist/shared/components/annotations/ink.d.ts +0 -27
  101. package/dist/shared/components/annotations/line.d.ts +0 -35
  102. package/dist/shared/components/annotations/link-locked.d.ts +0 -3
  103. package/dist/shared/components/annotations/link.d.ts +0 -28
  104. package/dist/shared/components/annotations/polygon.d.ts +0 -23
  105. package/dist/shared/components/annotations/polyline.d.ts +0 -23
  106. package/dist/shared/components/annotations/square.d.ts +0 -35
  107. package/dist/shared/components/annotations/stamp.d.ts +0 -13
  108. package/dist/shared/components/annotations/text.d.ts +0 -14
  109. package/dist/shared/components/annotations.d.ts +0 -22
  110. package/dist/shared/components/appearance-image.d.ts +0 -12
  111. package/dist/shared/components/built-in-renderers.d.ts +0 -2
  112. package/dist/shared/components/group-selection-box.d.ts +0 -40
  113. package/dist/shared/components/index.d.ts +0 -2
  114. package/dist/shared/components/preview-renderer.d.ts +0 -8
  115. package/dist/shared/components/render-annotation.d.ts +0 -13
  116. package/dist/shared/components/text-markup/highlight.d.ts +0 -16
  117. package/dist/shared/components/text-markup/squiggly.d.ts +0 -16
  118. package/dist/shared/components/text-markup/strikeout.d.ts +0 -16
  119. package/dist/shared/components/text-markup/underline.d.ts +0 -16
  120. package/dist/shared/components/text-markup.d.ts +0 -7
  121. package/dist/shared/components/types.d.ts +0 -253
  122. package/dist/shared/context/index.d.ts +0 -1
  123. package/dist/shared/context/renderer-registry.d.ts +0 -21
  124. package/dist/shared/hooks/index.d.ts +0 -2
  125. package/dist/shared/hooks/use-annotation.d.ts +0 -19
  126. package/dist/shared/hooks/use-ios-zoom-prevention.d.ts +0 -16
  127. package/dist/shared/index.d.ts +0 -6
  128. package/dist/shared/types.d.ts +0 -10
  129. package/dist/shared-preact/annotation-bounds.d.ts +0 -14
  130. package/dist/shared-preact/components/annotation-container.d.ts +0 -71
  131. package/dist/shared-preact/components/annotation-layer.d.ts +0 -33
  132. package/dist/shared-preact/components/annotation-navigation-handler.d.ts +0 -1
  133. package/dist/shared-preact/components/annotation-paint-layer.d.ts +0 -7
  134. package/dist/shared-preact/components/annotations/callout-free-text-preview.d.ts +0 -15
  135. package/dist/shared-preact/components/annotations/callout-free-text.d.ts +0 -15
  136. package/dist/shared-preact/components/annotations/caret.d.ts +0 -24
  137. package/dist/shared-preact/components/annotations/circle.d.ts +0 -35
  138. package/dist/shared-preact/components/annotations/free-text.d.ts +0 -17
  139. package/dist/shared-preact/components/annotations/ink.d.ts +0 -27
  140. package/dist/shared-preact/components/annotations/line.d.ts +0 -35
  141. package/dist/shared-preact/components/annotations/link-locked.d.ts +0 -3
  142. package/dist/shared-preact/components/annotations/link.d.ts +0 -28
  143. package/dist/shared-preact/components/annotations/polygon.d.ts +0 -23
  144. package/dist/shared-preact/components/annotations/polyline.d.ts +0 -23
  145. package/dist/shared-preact/components/annotations/square.d.ts +0 -35
  146. package/dist/shared-preact/components/annotations/stamp.d.ts +0 -13
  147. package/dist/shared-preact/components/annotations/text.d.ts +0 -14
  148. package/dist/shared-preact/components/annotations.d.ts +0 -22
  149. package/dist/shared-preact/components/appearance-image.d.ts +0 -12
  150. package/dist/shared-preact/components/built-in-renderers.d.ts +0 -2
  151. package/dist/shared-preact/components/group-selection-box.d.ts +0 -40
  152. package/dist/shared-preact/components/index.d.ts +0 -2
  153. package/dist/shared-preact/components/preview-renderer.d.ts +0 -8
  154. package/dist/shared-preact/components/render-annotation.d.ts +0 -13
  155. package/dist/shared-preact/components/text-markup/highlight.d.ts +0 -16
  156. package/dist/shared-preact/components/text-markup/squiggly.d.ts +0 -16
  157. package/dist/shared-preact/components/text-markup/strikeout.d.ts +0 -16
  158. package/dist/shared-preact/components/text-markup/underline.d.ts +0 -16
  159. package/dist/shared-preact/components/text-markup.d.ts +0 -7
  160. package/dist/shared-preact/components/types.d.ts +0 -253
  161. package/dist/shared-preact/context/index.d.ts +0 -1
  162. package/dist/shared-preact/context/renderer-registry.d.ts +0 -21
  163. package/dist/shared-preact/hooks/index.d.ts +0 -2
  164. package/dist/shared-preact/hooks/use-annotation.d.ts +0 -19
  165. package/dist/shared-preact/hooks/use-ios-zoom-prevention.d.ts +0 -15
  166. package/dist/shared-preact/index.d.ts +0 -6
  167. package/dist/shared-preact/types.d.ts +0 -10
  168. package/dist/shared-react/annotation-bounds.d.ts +0 -14
  169. package/dist/shared-react/components/annotation-container.d.ts +0 -71
  170. package/dist/shared-react/components/annotation-layer.d.ts +0 -33
  171. package/dist/shared-react/components/annotation-navigation-handler.d.ts +0 -1
  172. package/dist/shared-react/components/annotation-paint-layer.d.ts +0 -7
  173. package/dist/shared-react/components/annotations/callout-free-text-preview.d.ts +0 -15
  174. package/dist/shared-react/components/annotations/callout-free-text.d.ts +0 -15
  175. package/dist/shared-react/components/annotations/caret.d.ts +0 -24
  176. package/dist/shared-react/components/annotations/circle.d.ts +0 -35
  177. package/dist/shared-react/components/annotations/free-text.d.ts +0 -17
  178. package/dist/shared-react/components/annotations/ink.d.ts +0 -27
  179. package/dist/shared-react/components/annotations/line.d.ts +0 -35
  180. package/dist/shared-react/components/annotations/link-locked.d.ts +0 -3
  181. package/dist/shared-react/components/annotations/link.d.ts +0 -28
  182. package/dist/shared-react/components/annotations/polygon.d.ts +0 -23
  183. package/dist/shared-react/components/annotations/polyline.d.ts +0 -23
  184. package/dist/shared-react/components/annotations/square.d.ts +0 -35
  185. package/dist/shared-react/components/annotations/stamp.d.ts +0 -13
  186. package/dist/shared-react/components/annotations/text.d.ts +0 -14
  187. package/dist/shared-react/components/annotations.d.ts +0 -22
  188. package/dist/shared-react/components/appearance-image.d.ts +0 -12
  189. package/dist/shared-react/components/built-in-renderers.d.ts +0 -2
  190. package/dist/shared-react/components/group-selection-box.d.ts +0 -40
  191. package/dist/shared-react/components/index.d.ts +0 -2
  192. package/dist/shared-react/components/preview-renderer.d.ts +0 -8
  193. package/dist/shared-react/components/render-annotation.d.ts +0 -13
  194. package/dist/shared-react/components/text-markup/highlight.d.ts +0 -16
  195. package/dist/shared-react/components/text-markup/squiggly.d.ts +0 -16
  196. package/dist/shared-react/components/text-markup/strikeout.d.ts +0 -16
  197. package/dist/shared-react/components/text-markup/underline.d.ts +0 -16
  198. package/dist/shared-react/components/text-markup.d.ts +0 -7
  199. package/dist/shared-react/components/types.d.ts +0 -253
  200. package/dist/shared-react/context/index.d.ts +0 -1
  201. package/dist/shared-react/context/renderer-registry.d.ts +0 -21
  202. package/dist/shared-react/hooks/index.d.ts +0 -2
  203. package/dist/shared-react/hooks/use-annotation.d.ts +0 -19
  204. package/dist/shared-react/hooks/use-ios-zoom-prevention.d.ts +0 -16
  205. package/dist/shared-react/index.d.ts +0 -6
  206. package/dist/shared-react/types.d.ts +0 -10
  207. package/dist/shared-vue/annotation-bounds.d.ts +0 -14
  208. package/dist/svelte/components/AnnotationContainer.svelte.d.ts +0 -26
  209. package/dist/svelte/components/AnnotationLayer.svelte.d.ts +0 -40
  210. package/dist/svelte/components/AnnotationNavigationHandler.svelte.d.ts +0 -3
  211. package/dist/svelte/components/AnnotationPaintLayer.svelte.d.ts +0 -8
  212. package/dist/svelte/components/Annotations.svelte.d.ts +0 -28
  213. package/dist/svelte/components/AppearanceImage.svelte.d.ts +0 -8
  214. package/dist/svelte/components/GroupSelectionBox.svelte.d.ts +0 -40
  215. package/dist/svelte/components/PreviewRenderer.svelte.d.ts +0 -9
  216. package/dist/svelte/components/RenderAnnotation.svelte.d.ts +0 -14
  217. package/dist/svelte/components/RendererRegistryProvider.svelte.d.ts +0 -7
  218. package/dist/svelte/components/TextMarkup.svelte.d.ts +0 -8
  219. package/dist/svelte/components/annotations/CalloutFreeText.svelte.d.ts +0 -15
  220. package/dist/svelte/components/annotations/CalloutFreeTextPreview.svelte.d.ts +0 -10
  221. package/dist/svelte/components/annotations/Caret.svelte.d.ts +0 -13
  222. package/dist/svelte/components/annotations/Circle.svelte.d.ts +0 -19
  223. package/dist/svelte/components/annotations/CirclePreview.svelte.d.ts +0 -10
  224. package/dist/svelte/components/annotations/FreeText.svelte.d.ts +0 -16
  225. package/dist/svelte/components/annotations/FreeTextPreview.svelte.d.ts +0 -10
  226. package/dist/svelte/components/annotations/Ink.svelte.d.ts +0 -15
  227. package/dist/svelte/components/annotations/InkPreview.svelte.d.ts +0 -10
  228. package/dist/svelte/components/annotations/Line.svelte.d.ts +0 -19
  229. package/dist/svelte/components/annotations/LinePreview.svelte.d.ts +0 -10
  230. package/dist/svelte/components/annotations/Link.svelte.d.ts +0 -24
  231. package/dist/svelte/components/annotations/LinkLockedMode.svelte.d.ts +0 -5
  232. package/dist/svelte/components/annotations/LinkPreview.svelte.d.ts +0 -10
  233. package/dist/svelte/components/annotations/Polygon.svelte.d.ts +0 -21
  234. package/dist/svelte/components/annotations/PolygonPreview.svelte.d.ts +0 -10
  235. package/dist/svelte/components/annotations/Polyline.svelte.d.ts +0 -19
  236. package/dist/svelte/components/annotations/PolylinePreview.svelte.d.ts +0 -10
  237. package/dist/svelte/components/annotations/Square.svelte.d.ts +0 -19
  238. package/dist/svelte/components/annotations/SquarePreview.svelte.d.ts +0 -10
  239. package/dist/svelte/components/annotations/Stamp.svelte.d.ts +0 -13
  240. package/dist/svelte/components/annotations/StampPreview.svelte.d.ts +0 -10
  241. package/dist/svelte/components/annotations/Text.svelte.d.ts +0 -10
  242. package/dist/svelte/components/annotations/index.d.ts +0 -10
  243. package/dist/svelte/components/built-in-renderers.d.ts +0 -2
  244. package/dist/svelte/components/index.d.ts +0 -11
  245. package/dist/svelte/components/renderers/CalloutFreeTextRenderer.svelte.d.ts +0 -5
  246. package/dist/svelte/components/renderers/CaretRenderer.svelte.d.ts +0 -5
  247. package/dist/svelte/components/renderers/CircleRenderer.svelte.d.ts +0 -5
  248. package/dist/svelte/components/renderers/FreeTextRenderer.svelte.d.ts +0 -5
  249. package/dist/svelte/components/renderers/HighlightRenderer.svelte.d.ts +0 -5
  250. package/dist/svelte/components/renderers/InkRenderer.svelte.d.ts +0 -5
  251. package/dist/svelte/components/renderers/LineRenderer.svelte.d.ts +0 -5
  252. package/dist/svelte/components/renderers/LinkRenderer.svelte.d.ts +0 -5
  253. package/dist/svelte/components/renderers/PolygonRenderer.svelte.d.ts +0 -5
  254. package/dist/svelte/components/renderers/PolylineRenderer.svelte.d.ts +0 -5
  255. package/dist/svelte/components/renderers/SquareRenderer.svelte.d.ts +0 -5
  256. package/dist/svelte/components/renderers/SquigglyRenderer.svelte.d.ts +0 -5
  257. package/dist/svelte/components/renderers/StampRenderer.svelte.d.ts +0 -5
  258. package/dist/svelte/components/renderers/StrikeoutRenderer.svelte.d.ts +0 -5
  259. package/dist/svelte/components/renderers/TextRenderer.svelte.d.ts +0 -5
  260. package/dist/svelte/components/renderers/UnderlineRenderer.svelte.d.ts +0 -5
  261. package/dist/svelte/components/text-markup/Highlight.svelte.d.ts +0 -16
  262. package/dist/svelte/components/text-markup/Squiggly.svelte.d.ts +0 -16
  263. package/dist/svelte/components/text-markup/Strikeout.svelte.d.ts +0 -16
  264. package/dist/svelte/components/text-markup/Underline.svelte.d.ts +0 -16
  265. package/dist/svelte/components/text-markup/index.d.ts +0 -4
  266. package/dist/svelte/components/types.d.ts +0 -66
  267. package/dist/svelte/context/index.d.ts +0 -2
  268. package/dist/svelte/context/renderer-registry.svelte.d.ts +0 -20
  269. package/dist/svelte/context/types.d.ts +0 -129
  270. package/dist/svelte/hooks/index.d.ts +0 -2
  271. package/dist/svelte/hooks/use-annotation.svelte.d.ts +0 -21
  272. package/dist/svelte/hooks/use-ios-zoom-prevention.svelte.d.ts +0 -6
  273. package/dist/svelte/index.cjs +0 -2
  274. package/dist/svelte/index.cjs.map +0 -1
  275. package/dist/svelte/index.d.ts +0 -6
  276. package/dist/svelte/index.js +0 -5257
  277. package/dist/svelte/index.js.map +0 -1
  278. package/dist/svelte/types.d.ts +0 -126
  279. package/dist/vue/components/annotation-container.vue.d.ts +0 -134
  280. package/dist/vue/components/annotation-layer.vue.d.ts +0 -99
  281. package/dist/vue/components/annotation-navigation-handler.vue.d.ts +0 -3
  282. package/dist/vue/components/annotation-paint-layer.vue.d.ts +0 -8
  283. package/dist/vue/components/annotations/callout-free-text-preview.vue.d.ts +0 -10
  284. package/dist/vue/components/annotations/callout-free-text.vue.d.ts +0 -25
  285. package/dist/vue/components/annotations/caret.vue.d.ts +0 -24
  286. package/dist/vue/components/annotations/circle-preview.vue.d.ts +0 -10
  287. package/dist/vue/components/annotations/circle.vue.d.ts +0 -37
  288. package/dist/vue/components/annotations/free-text-preview.vue.d.ts +0 -10
  289. package/dist/vue/components/annotations/free-text.vue.d.ts +0 -25
  290. package/dist/vue/components/annotations/index.d.ts +0 -10
  291. package/dist/vue/components/annotations/ink-preview.vue.d.ts +0 -10
  292. package/dist/vue/components/annotations/ink.vue.d.ts +0 -27
  293. package/dist/vue/components/annotations/line-preview.vue.d.ts +0 -10
  294. package/dist/vue/components/annotations/line.vue.d.ts +0 -38
  295. package/dist/vue/components/annotations/link-locked.vue.d.ts +0 -5
  296. package/dist/vue/components/annotations/link-preview.vue.d.ts +0 -10
  297. package/dist/vue/components/annotations/link.vue.d.ts +0 -47
  298. package/dist/vue/components/annotations/polygon-preview.vue.d.ts +0 -10
  299. package/dist/vue/components/annotations/polygon.vue.d.ts +0 -43
  300. package/dist/vue/components/annotations/polyline-preview.vue.d.ts +0 -10
  301. package/dist/vue/components/annotations/polyline.vue.d.ts +0 -38
  302. package/dist/vue/components/annotations/square-preview.vue.d.ts +0 -10
  303. package/dist/vue/components/annotations/square.vue.d.ts +0 -37
  304. package/dist/vue/components/annotations/stamp-preview.vue.d.ts +0 -10
  305. package/dist/vue/components/annotations/stamp.vue.d.ts +0 -19
  306. package/dist/vue/components/annotations/text.vue.d.ts +0 -14
  307. package/dist/vue/components/annotations.vue.d.ts +0 -111
  308. package/dist/vue/components/appearance-image.vue.d.ts +0 -9
  309. package/dist/vue/components/built-in-renderers.d.ts +0 -2
  310. package/dist/vue/components/group-selection-box.vue.d.ts +0 -98
  311. package/dist/vue/components/index.d.ts +0 -11
  312. package/dist/vue/components/preview-renderer.vue.d.ts +0 -9
  313. package/dist/vue/components/render-annotation.vue.d.ts +0 -16
  314. package/dist/vue/components/renderer-registry-provider.vue.d.ts +0 -13
  315. package/dist/vue/components/renderers/callout-free-text-renderer.vue.d.ts +0 -6
  316. package/dist/vue/components/renderers/caret-renderer.vue.d.ts +0 -6
  317. package/dist/vue/components/renderers/circle-renderer.vue.d.ts +0 -6
  318. package/dist/vue/components/renderers/free-text-renderer.vue.d.ts +0 -6
  319. package/dist/vue/components/renderers/highlight-renderer.vue.d.ts +0 -6
  320. package/dist/vue/components/renderers/ink-renderer.vue.d.ts +0 -6
  321. package/dist/vue/components/renderers/line-renderer.vue.d.ts +0 -6
  322. package/dist/vue/components/renderers/link-renderer.vue.d.ts +0 -6
  323. package/dist/vue/components/renderers/polygon-renderer.vue.d.ts +0 -6
  324. package/dist/vue/components/renderers/polyline-renderer.vue.d.ts +0 -6
  325. package/dist/vue/components/renderers/square-renderer.vue.d.ts +0 -6
  326. package/dist/vue/components/renderers/squiggly-renderer.vue.d.ts +0 -6
  327. package/dist/vue/components/renderers/stamp-renderer.vue.d.ts +0 -6
  328. package/dist/vue/components/renderers/strikeout-renderer.vue.d.ts +0 -6
  329. package/dist/vue/components/renderers/text-renderer.vue.d.ts +0 -6
  330. package/dist/vue/components/renderers/underline-renderer.vue.d.ts +0 -6
  331. package/dist/vue/components/text-markup/highlight.vue.d.ts +0 -27
  332. package/dist/vue/components/text-markup/index.d.ts +0 -4
  333. package/dist/vue/components/text-markup/squiggly.vue.d.ts +0 -27
  334. package/dist/vue/components/text-markup/strikeout.vue.d.ts +0 -27
  335. package/dist/vue/components/text-markup/underline.vue.d.ts +0 -27
  336. package/dist/vue/components/text-markup.vue.d.ts +0 -8
  337. package/dist/vue/context/index.d.ts +0 -2
  338. package/dist/vue/context/renderer-registry.d.ts +0 -26
  339. package/dist/vue/context/types.d.ts +0 -129
  340. package/dist/vue/hooks/index.d.ts +0 -2
  341. package/dist/vue/hooks/use-annotation.d.ts +0 -50
  342. package/dist/vue/hooks/use-ios-zoom-prevention.d.ts +0 -7
  343. package/dist/vue/index.cjs +0 -2
  344. package/dist/vue/index.cjs.map +0 -1
  345. package/dist/vue/index.d.ts +0 -6
  346. package/dist/vue/index.js +0 -4984
  347. package/dist/vue/index.js.map +0 -1
  348. package/dist/vue/types.d.ts +0 -94
@@ -0,0 +1,2680 @@
1
+ import { CONTINUOUS_RENDER_POLICY, createCapabilityToken, snapAppearanceScale } from "@embedpdf/core";
2
+ import { InteractionToken } from "@embedpdf/plugin-interaction";
3
+ import { resolveBinarySource, sniffBinaryMetadata } from "@embedpdf/engine-core/runtime";
4
+ import { FLAG_KEYS, calloutLinePoints, chrome, clickCreateGeom, cloudyBorderExtent, contentToPdfPoint, contentToPdfRect, contentToPdfRect as contentToPdfRect$1, creationDraftAnchor, cursorAt, defaultsFor, expandGroups, fitStampBox, geomPdfBounds, geomRotation, geomVisualBounds, groupKeyOf, hitTest, initialTextStyle, normalizeDeg, pageItems, pdfToContentPoint, pdfToContentPoint as pdfToContentPoint$1, pdfToContentRect, pdfToContentRect as pdfToContentRect$1, propsFor, readProp, resolveClickPlacement, rotatedAabb, selectionAnchor, selectionQuad, sharedProps, styleFromProps, textBoxes, unionRect, update, uprightRotation, viewable } from "@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 = 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) => normalizeDeg(-rotCW);
46
+ const fromPdfRotation = (rotPdf) => 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: contentToPdfRect(rect, crop),
62
+ rotation: null,
63
+ unrotatedRect: null
64
+ };
65
+ return {
66
+ rect: contentToPdfRect(rotatedAabb(rect, rot), crop),
67
+ unrotatedRect: 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: 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
234
+ //#region src/repository/props.ts
235
+ /**
236
+ * GENERIC per-key prop lowerings — the 1:1 mappings from the flat props
237
+ * vocabulary to wire fields that hold for every kind that declares the key.
238
+ * Kind modules override ONLY their exceptions (a coupling lives in its
239
+ * owner's file: cloudy `/RD` in shape.ts, visual-bounds `/Rect` in stroke.ts,
240
+ * the link target in link.ts).
241
+ */
242
+ /** /BS slice of the style — a cloudy border keeps a solid underlying stroke
243
+ * (the scallops are the /BE effect, layered on by the shape kinds). */
244
+ const borderSlice = (style) => ({
245
+ borderStyle: style.border.kind === "dashed" ? "dashed" : "solid",
246
+ ...style.border.kind === "dashed" ? { dashArray: style.border.dash } : {}
247
+ });
248
+ /** The `/DA`-styled text slice falls back to the draw-time seed exactly like
249
+ * the old projections did (a fresh draft may not carry `text` yet). */
250
+ const textOf = (a) => a.text ?? initialTextStyle;
251
+ const GENERIC_PROPS = {
252
+ color: (a) => ({ color: cssToColor(a.style.color) }),
253
+ opacity: (a) => ({ opacity: a.style.opacity }),
254
+ blendMode: (a) => ({ blendMode: a.style.blendMode }),
255
+ interiorColor: (a) => ({ interiorColor: a.style.interiorColor ? cssToColor(a.style.interiorColor) : null }),
256
+ strokeWidth: (a) => ({ strokeWidth: a.style.strokeWidth }),
257
+ border: (a) => borderSlice(a.style),
258
+ fontFamily: (a) => ({ fontFamily: textOf(a).fontFamily }),
259
+ fontSize: (a) => ({ fontSize: textOf(a).fontSize }),
260
+ fontColor: (a) => ({ fontColor: cssToColor(textOf(a).fontColor) }),
261
+ textAlign: (a) => ({ textAlign: textOf(a).textAlign }),
262
+ icon: (a) => a.icon !== void 0 ? { icon: a.icon } : {},
263
+ link: () => ({})
264
+ };
265
+ //#endregion
266
+ //#region src/repository/projection.ts
267
+ /** Box-kind geometry emission: the model's unrotated `rect` + applied tilt →
268
+ * `/Rect`(+`unrotatedRect`+`rotation`), total (nulls state the clears). */
269
+ const boxEmit = (a, crop) => {
270
+ const g = a.geom;
271
+ return boxGeomFields(g.rect, geomRotation(a.geom), crop);
272
+ };
273
+ //#endregion
274
+ //#region src/repository/kinds/shape.ts
275
+ /** Cloudy `/BE` + `/RD` for a rect shape — TOTAL: the non-cloudy state is
276
+ * stated as `null` (tri-state remove), never omitted, so toggling cloudy OFF
277
+ * removes the stale inset (the Adobe phantom-padding bug). */
278
+ function cloudyExtras(a) {
279
+ if (a.geom.t !== "rect") return {};
280
+ if (a.style.border.kind === "cloudy") {
281
+ const inset = cloudyBorderExtent(a.style.border.intensity, a.style.strokeWidth, a.geom.ellipse);
282
+ return {
283
+ cloudyIntensity: a.style.border.intensity,
284
+ rectDifferences: {
285
+ left: inset,
286
+ top: inset,
287
+ right: inset,
288
+ bottom: inset
289
+ }
290
+ };
291
+ }
292
+ return {
293
+ cloudyIntensity: null,
294
+ rectDifferences: null
295
+ };
296
+ }
297
+ const ingest = (dto, crop, ellipse) => {
298
+ const d = dto;
299
+ return { geom: boxGeomFromDTO(d, d.rotation, d.unrotatedRect, crop, ellipse) };
300
+ };
301
+ const projection = (ellipse) => ({
302
+ ingest: (dto, crop) => ingest(dto, crop, ellipse),
303
+ geometry: (a, crop) => boxEmit(a, crop),
304
+ prop: {
305
+ strokeWidth: (a) => ({
306
+ strokeWidth: a.style.strokeWidth,
307
+ ...cloudyExtras(a)
308
+ }),
309
+ border: (a) => ({
310
+ ...borderSlice(a.style),
311
+ ...cloudyExtras(a)
312
+ })
313
+ }
314
+ });
315
+ const square = projection(false);
316
+ const circle = projection(true);
317
+ //#endregion
318
+ //#region src/repository/kinds/stroke.ts
319
+ /** Advisory rotation, TOTAL: rotation 0 states `null` (tri-state clear) —
320
+ * omission would preserve a stale advisory angle. */
321
+ const advisoryRotation = (g) => {
322
+ const rot = geomRotation(g);
323
+ return { rotation: rot ? toPdfRotation(rot) : null };
324
+ };
325
+ /** `/BE` intensity for a closed poly (polygon): the curls are generated from
326
+ * /Vertices + /BE alone — per ISO 32000 no /RD applies — and the /Rect
327
+ * (geomPdfBounds WITH the border) already includes the outward cloud extent. */
328
+ const polyCloudy = (a) => a.geom.t === "poly" && a.geom.closed ? { cloudyIntensity: a.style.border.kind === "cloudy" ? a.style.border.intensity : null } : {};
329
+ /** The visual-bounds `/Rect` (stroke + border included) of a stroke geom. */
330
+ const visualRect = (a, crop) => {
331
+ const g = a.geom;
332
+ if (g.t === "line" || g.t === "ink") return { rect: geomPdfBounds(g, a.style.strokeWidth, crop) };
333
+ if (g.t === "poly") return { rect: geomPdfBounds(g, a.style.strokeWidth, crop, a.style.border) };
334
+ return {};
335
+ };
336
+ /** Stroke-family prop exceptions: width and border feed the derived /Rect. */
337
+ const strokeProps = {
338
+ strokeWidth: (a, crop) => ({
339
+ strokeWidth: a.style.strokeWidth,
340
+ ...visualRect(a, crop)
341
+ }),
342
+ border: (a, crop) => ({
343
+ ...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 } : {}
348
+ };
349
+ const line = {
350
+ ingest: (dto, crop) => {
351
+ const d = dto;
352
+ return { geom: {
353
+ t: "line",
354
+ a: pdfToContentPoint(d.linePoints.start, crop),
355
+ b: pdfToContentPoint(d.linePoints.end, crop),
356
+ ends: d.lineEndings,
357
+ ...rotFromDTO(d.rotation)
358
+ } };
359
+ },
360
+ geometry: (a, crop) => {
361
+ const g = a.geom;
362
+ if (g.t !== "line") return null;
363
+ return {
364
+ linePoints: {
365
+ start: contentToPdfPoint(g.a, crop),
366
+ end: contentToPdfPoint(g.b, crop)
367
+ },
368
+ ...visualRect(a, crop),
369
+ ...advisoryRotation(g)
370
+ };
371
+ },
372
+ prop: strokeProps
373
+ };
374
+ const polyProjection = (closed) => ({
375
+ ingest: (dto, crop) => {
376
+ const d = dto;
377
+ return { geom: {
378
+ t: "poly",
379
+ points: d.vertices.map((p) => pdfToContentPoint(p, crop)),
380
+ closed,
381
+ ..."lineEndings" in d ? { ends: d.lineEndings } : {},
382
+ ...rotFromDTO(d.rotation)
383
+ } };
384
+ },
385
+ geometry: (a, crop) => {
386
+ const g = a.geom;
387
+ if (g.t !== "poly") return null;
388
+ return {
389
+ vertices: g.points.map((p) => contentToPdfPoint(p, crop)),
390
+ ...visualRect(a, crop),
391
+ ...advisoryRotation(g)
392
+ };
393
+ },
394
+ prop: strokeProps
395
+ });
396
+ const polygon = polyProjection(true);
397
+ const polyline = polyProjection(false);
398
+ const ink = {
399
+ ingest: (dto, crop) => {
400
+ const d = dto;
401
+ return {
402
+ geom: {
403
+ t: "ink",
404
+ strokes: d.inkList.map((stroke) => stroke.map((p) => pdfToContentPoint(p, crop))),
405
+ ...rotFromDTO(d.rotation)
406
+ },
407
+ ...d.intent ? { intent: d.intent } : {}
408
+ };
409
+ },
410
+ geometry: (a, crop) => {
411
+ const g = a.geom;
412
+ if (g.t !== "ink") return null;
413
+ return {
414
+ inkList: g.strokes.map((stroke) => stroke.map((p) => contentToPdfPoint(p, crop))),
415
+ ...visualRect(a, crop),
416
+ ...advisoryRotation(g)
417
+ };
418
+ },
419
+ prop: strokeProps,
420
+ draftExtras: (a) => a.intent === "ink-highlight" ? { intent: a.intent } : {}
421
+ };
422
+ //#endregion
423
+ //#region src/repository/kinds/freeText.ts
424
+ /** Free-text `/DA` fields → content {@link TextStyle}. An absent `fontColor`
425
+ * falls back to the `/DA` colour — the same rule the CPVT renderer applies. */
426
+ function textFromDTO(dto) {
427
+ return {
428
+ fontFamily: dto.fontFamily,
429
+ fontSize: dto.fontSize,
430
+ fontColor: colorToCss(dto.fontColor ?? dto.color),
431
+ textAlign: dto.textAlign
432
+ };
433
+ }
434
+ /**
435
+ * The engine geometry for a callout: the overall `/Rect` (text box ∪ leader ∪
436
+ * arrow), the `/RD` inset that recovers the text box from it, the `/CL` leader
437
+ * (`[tip, knee, conn]` with the connection point derived), and the `/LE`
438
+ * ending. All in PDF user space (y-up), with every `/RD` inset clamped
439
+ * non-negative.
440
+ *
441
+ * A tilted box (the upright policy) additionally emits `rotation` +
442
+ * `unrotatedRect` (the logical text box) — the engine bakes the box + text
443
+ * under an INLINE rotation about the box centre while the leader stays
444
+ * page-space, so (unlike plain boxes) the /AP form `/Matrix` stays identity
445
+ * and the raster stays placed by `/Rect`. `/RD` then insets to the ROTATED
446
+ * box's AABB — the best axis-aligned text box a foreign viewer regenerating
447
+ * the AP can draw (spec-conformant degradation). The transform pair is TOTAL
448
+ * (nulls state the clears) like every box emission.
449
+ */
450
+ function calloutFields(a, crop) {
451
+ const g = a.geom;
452
+ if (g.t !== "text" || !g.callout) return null;
453
+ const rot = geomRotation(g);
454
+ const overall = geomPdfBounds(g, a.style.strokeWidth, crop);
455
+ const tb = contentToPdfRect$1(rot ? rotatedAabb(g.rect, rot) : g.rect, crop);
456
+ const nn = (n) => Math.max(0, n);
457
+ const pts = calloutLinePoints(g).map((p) => contentToPdfPoint(p, crop));
458
+ const calloutLine = pts.length === 3 ? [
459
+ pts[0],
460
+ pts[1],
461
+ pts[2]
462
+ ] : [pts[0], pts[1]];
463
+ return {
464
+ rect: overall,
465
+ rectDifferences: {
466
+ left: nn(tb.left - overall.left),
467
+ bottom: nn(tb.bottom - overall.bottom),
468
+ right: nn(overall.right - tb.right),
469
+ top: nn(overall.top - tb.top)
470
+ },
471
+ calloutLine,
472
+ lineEnding: g.callout.ending,
473
+ ...rot ? {
474
+ rotation: toPdfRotation(rot),
475
+ unrotatedRect: contentToPdfRect$1(g.rect, crop)
476
+ } : {
477
+ rotation: null,
478
+ unrotatedRect: null
479
+ }
480
+ };
481
+ }
482
+ const freeText = {
483
+ ingest: (dto, crop) => {
484
+ const d = dto;
485
+ if (d.intent === "free-text-callout" && d.calloutLine && d.calloutLine.length >= 2) {
486
+ const cl = d.calloutLine;
487
+ const rot = d.rotation ? fromPdfRotation(d.rotation) : 0;
488
+ return {
489
+ geom: {
490
+ t: "text",
491
+ rect: rot && d.unrotatedRect ? pdfToContentRect$1(d.unrotatedRect, crop) : pdfToContentRect$1(insetPdfRectByRD(d.rect, d.rectDifferences), crop),
492
+ callout: {
493
+ tip: pdfToContentPoint$1(cl[0], crop),
494
+ knee: cl.length === 3 ? pdfToContentPoint$1(cl[1], crop) : void 0,
495
+ ending: d.lineEnding ?? "none"
496
+ },
497
+ ...rot && d.unrotatedRect ? { rot } : {}
498
+ },
499
+ text: textFromDTO(d)
500
+ };
501
+ }
502
+ const rot = d.rotation ? fromPdfRotation(d.rotation) : 0;
503
+ return {
504
+ geom: {
505
+ t: "text",
506
+ rect: pdfToContentRect$1(rot && d.unrotatedRect ? d.unrotatedRect : d.rect, crop),
507
+ ...rot ? { rot } : {}
508
+ },
509
+ text: textFromDTO(d)
510
+ };
511
+ },
512
+ geometry: (a, crop) => {
513
+ if (a.geom.t !== "text") return null;
514
+ const cf = calloutFields(a, crop);
515
+ if (cf) return { ...cf };
516
+ return boxEmit(a, crop);
517
+ },
518
+ draftExtras: (a, crop) => ({
519
+ intent: calloutFields(a, crop) ? "free-text-callout" : "free-text",
520
+ contents: a.data?.contents ?? ""
521
+ })
522
+ };
523
+ //#endregion
524
+ //#region src/repository/kinds/quads.ts
525
+ const quadsFromDTO = (quadPoints, crop) => quadPoints.map((q) => [
526
+ pdfToContentPoint$1(q.p1, crop),
527
+ pdfToContentPoint$1(q.p2, crop),
528
+ pdfToContentPoint$1(q.p3, crop),
529
+ pdfToContentPoint$1(q.p4, crop)
530
+ ]);
531
+ /** Content quads → engine `/QuadPoints` (PDF user space); null off quads geom. */
532
+ function quadPointsFor(a, crop) {
533
+ if (a.geom.t !== "quads") return null;
534
+ return a.geom.quads.map((q) => ({
535
+ p1: contentToPdfPoint(q[0], crop),
536
+ p2: contentToPdfPoint(q[1], crop),
537
+ p3: contentToPdfPoint(q[2], crop),
538
+ p4: contentToPdfPoint(q[3], crop)
539
+ }));
540
+ }
541
+ /** PDF-space bounding box of a set of `/QuadPoints` quads — the `/Rect` a
542
+ * quad-bearing draft must carry alongside its quads. */
543
+ const pdfBoundsOfQuads = (quads) => {
544
+ let left = Infinity;
545
+ let bottom = Infinity;
546
+ let right = -Infinity;
547
+ let top = -Infinity;
548
+ for (const q of quads) for (const p of [
549
+ q.p1,
550
+ q.p2,
551
+ q.p3,
552
+ q.p4
553
+ ]) {
554
+ if (p.x < left) left = p.x;
555
+ if (p.x > right) right = p.x;
556
+ if (p.y < bottom) bottom = p.y;
557
+ if (p.y > top) top = p.y;
558
+ }
559
+ return {
560
+ left,
561
+ bottom,
562
+ right,
563
+ top
564
+ };
565
+ };
566
+ const markupProjection = (subtype) => {
567
+ return {
568
+ ingest: (dto, crop) => {
569
+ const d = dto;
570
+ return {
571
+ geom: {
572
+ t: "quads",
573
+ quads: quadsFromDTO(d.quadPoints, crop)
574
+ },
575
+ ...subtype === "strikeout" && "intent" in d && d.intent ? { intent: d.intent } : {}
576
+ };
577
+ },
578
+ geometry: () => null,
579
+ draftExtras: (a, crop) => {
580
+ const quads = quadPointsFor(a, crop);
581
+ if (!quads) return null;
582
+ return {
583
+ quadPoints: quads,
584
+ ...subtype === "strikeout" && a.intent === "strikeout-text-edit" ? { intent: a.intent } : {}
585
+ };
586
+ }
587
+ };
588
+ };
589
+ const highlight = markupProjection("highlight");
590
+ const underline = markupProjection("underline");
591
+ const squiggly = markupProjection("squiggly");
592
+ const strikeout = markupProjection("strikeout");
593
+ const caret = {
594
+ ingest: (dto, crop) => {
595
+ const d = dto;
596
+ return {
597
+ geom: {
598
+ t: "caret",
599
+ rect: pdfToContentRect$1(d.rect, crop)
600
+ },
601
+ ...d.intent ? { intent: d.intent } : {}
602
+ };
603
+ },
604
+ geometry: (a, crop) => a.geom.t === "caret" ? { rect: contentToPdfRect$1(a.geom.rect, crop) } : null,
605
+ draftExtras: (a) => ({
606
+ rectDifferences: {
607
+ left: .5,
608
+ top: .5,
609
+ right: .5,
610
+ bottom: .5
611
+ },
612
+ ...a.intent === "replace" ? { intent: a.intent } : {},
613
+ ...a.data?.contents != null ? { contents: a.data.contents } : {}
614
+ })
615
+ };
616
+ const redact = {
617
+ ingest: (dto, crop) => {
618
+ const d = dto;
619
+ return {
620
+ geom: d.quadPoints.length > 0 ? {
621
+ t: "quads",
622
+ quads: quadsFromDTO(d.quadPoints, crop)
623
+ } : {
624
+ t: "rect",
625
+ rect: pdfToContentRect$1(d.rect, crop),
626
+ ellipse: false
627
+ },
628
+ text: {
629
+ fontFamily: d.fontFamily,
630
+ fontSize: d.fontSize,
631
+ fontColor: colorToCss(d.fontColor),
632
+ textAlign: d.textAlign
633
+ },
634
+ ...d.overlayText ? { label: {
635
+ text: d.overlayText,
636
+ repeat: d.repeat
637
+ } } : {}
638
+ };
639
+ },
640
+ geometry: (a, crop) => a.geom.t === "rect" ? { rect: contentToPdfRect$1(a.geom.rect, crop) } : null,
641
+ draftExtras: (a, crop) => {
642
+ const quads = quadPointsFor(a, crop);
643
+ return quads ? {
644
+ quadPoints: quads,
645
+ rect: pdfBoundsOfQuads(quads)
646
+ } : {};
647
+ }
648
+ };
649
+ //#endregion
650
+ //#region src/repository/kinds/misc.ts
651
+ const rectGeometry = (a, crop) => "rect" in a.geom ? { rect: contentToPdfRect$1(a.geom.rect, crop) } : null;
652
+ const iconProjection = (subtype) => ({
653
+ ingest: (dto, crop) => {
654
+ const d = dto;
655
+ return {
656
+ geom: {
657
+ t: "rect",
658
+ rect: pdfToContentRect$1(d.rect, crop),
659
+ ellipse: false
660
+ },
661
+ icon: d.icon
662
+ };
663
+ },
664
+ geometry: rectGeometry,
665
+ createable: false
666
+ });
667
+ const textNote = iconProjection("text");
668
+ const fileAttachment = iconProjection("file-attachment");
669
+ const stamp = {
670
+ ingest: (dto, crop) => {
671
+ const d = dto;
672
+ return { geom: boxGeomFromDTO(d, d.rotation, d.unrotatedRect, crop, false) };
673
+ },
674
+ geometry: (a, crop) => boxEmit(a, crop),
675
+ createable: false
676
+ };
677
+ const link = {
678
+ ingest: (dto, crop) => {
679
+ const d = dto;
680
+ return {
681
+ geom: {
682
+ t: "rect",
683
+ rect: pdfToContentRect$1(d.rect, crop),
684
+ ellipse: false
685
+ },
686
+ link: d.target
687
+ };
688
+ },
689
+ geometry: rectGeometry,
690
+ prop: { link: (a) => {
691
+ const target = writableTarget(a.link);
692
+ return target ? { target } : a.link == null ? { target: null } : {};
693
+ } },
694
+ draftExtras: (a) => ({ target: writableTarget(a.link) })
695
+ };
696
+ /** One PDF `widget` subtype → per-family CLIENT kinds (radios have no font). */
697
+ const WIDGET_KIND_BY_FAMILY = {
698
+ text: "widget-text",
699
+ combobox: "widget-choice",
700
+ listbox: "widget-choice",
701
+ pushbutton: "widget-button",
702
+ checkbox: "widget-toggle",
703
+ radio: "widget-toggle"
704
+ };
705
+ const widgetKindOf = (family) => WIDGET_KIND_BY_FAMILY[family] ?? "widget-box";
706
+ const WIDGET_TEXT_KINDS = /* @__PURE__ */ new Set([
707
+ "widget-text",
708
+ "widget-choice",
709
+ "widget-button"
710
+ ]);
711
+ function widgetTextFromDTO(dto) {
712
+ return {
713
+ fontFamily: dto.fontFamily ?? "helvetica",
714
+ fontSize: dto.fontSize ?? 0,
715
+ fontColor: dto.fontColor ? colorToCss(dto.fontColor) : "#000000",
716
+ textAlign: dto.textAlign
717
+ };
718
+ }
719
+ const widget = {
720
+ ingest: (dto, crop) => {
721
+ const d = dto;
722
+ return {
723
+ geom: boxGeomFromDTO(d, void 0, void 0, crop, false),
724
+ ...WIDGET_TEXT_KINDS.has(widgetKindOf(d.fieldFamily)) ? { text: widgetTextFromDTO(d) } : {}
725
+ };
726
+ },
727
+ geometry: rectGeometry,
728
+ createable: false
729
+ };
730
+ const unsupported = {
731
+ ingest: (dto, crop) => ({ geom: {
732
+ t: "rect",
733
+ rect: pdfToContentRect$1(dto.rect, crop),
734
+ ellipse: false
735
+ } }),
736
+ geometry: () => null,
737
+ createable: false
738
+ };
739
+ //#endregion
740
+ //#region src/repository/links.ts
741
+ /**
742
+ * Attached-link folding + desired-state derivation — RELATIONSHIP logic, not
743
+ * projection: a `/Link` child grouped under a linkable parent becomes the
744
+ * parent's `link` prop, and the reconciler derives the children it should
745
+ * have from the parent's committed geometry.
746
+ */
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) => 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) => unionRect(q);
785
+ /**
786
+ * The DESIRED hit rects (content space) of a parent's attached link
787
+ * children — derived fresh from the parent's committed geometry, never
788
+ * tracked: markup gets one child per quad (per-line hit areas, the v2
789
+ * segment behaviour), every other kind one child over its visual bounds.
790
+ * The reconciler and the navigation lens both read THIS, so the clickable
791
+ * area and the written `/Rect`s can never disagree.
792
+ *
793
+ * A link's `/Rect` is axis-aligned by spec (no rotation exists for links),
794
+ * so a ROTATED parent gets the AABB of its rotated footprint — the
795
+ * `selectionQuad` corners (rotation + stroke included), the same envelope
796
+ * the selection chrome outlines. Exact rotated hit regions need
797
+ * `/QuadPoints` (tier 2).
798
+ */
799
+ function linkChildRects(a) {
800
+ if (a.geom.t === "quads") return a.geom.quads.map(quadBounds);
801
+ return [unionRect(selectionQuad(a.geom, a.style.strokeWidth, a.style.border))];
802
+ }
803
+ //#endregion
804
+ //#region src/repository/index.ts
805
+ /** Every wire subtype declares exactly one projection — a missing kind is a
806
+ * COMPILE error, not a silent fall-through. */
807
+ const KINDS = {
808
+ square,
809
+ circle,
810
+ line,
811
+ polygon,
812
+ polyline,
813
+ ink,
814
+ "free-text": freeText,
815
+ highlight,
816
+ underline,
817
+ squiggly,
818
+ strikeout,
819
+ caret,
820
+ redact,
821
+ text: textNote,
822
+ "file-attachment": fileAttachment,
823
+ stamp,
824
+ link,
825
+ widget,
826
+ unsupported
827
+ };
828
+ /** Model subtypes are wire subtypes except the widget CLIENT kinds
829
+ * (`widget-text`…), which all project through the widget family. */
830
+ const projectionOf = (subtype) => subtype.startsWith("widget") ? KINDS.widget : KINDS[subtype] ?? KINDS.unsupported;
831
+ const wireSubtypeOf = (a) => a.subtype.startsWith("widget") ? "widget" : a.subtype;
832
+ /**
833
+ * Engine DTO → content-space Annot. `source` decides how it renders: `'baked'`
834
+ * shows the engine's appearance raster (a page load, or a remote edit — trust
835
+ * the authored AP); `'vector'` renders live from geom/style (we authored or
836
+ * changed it). `apBox` is the raster's content-space box, used while baked.
837
+ */
838
+ function fromDTO(dto, crop, source = "baked") {
839
+ const slice = projectionOf(dto.subtype).ingest(dto, crop);
840
+ const strippedRect = !(dto.subtype === "free-text" && dto.intent === "free-text-callout") && "unrotatedRect" in dto && "rotation" in dto && dto.rotation ? dto.unrotatedRect : void 0;
841
+ return {
842
+ id: refKey(dto.ref),
843
+ ref: dto.ref,
844
+ pon: dto.pageObjectNumber,
845
+ subtype: dto.subtype === "widget" ? widgetKindOf(dto.fieldFamily) : dto.subtype,
846
+ flags: dto.flags,
847
+ source,
848
+ data: dto,
849
+ ...dto.inReplyTo ? { irt: refKey(dto.inReplyTo) } : {},
850
+ ...dto.replyType === "group" && dto.inReplyTo ? { group: refKey(dto.inReplyTo) } : {},
851
+ style: styleFromDTO(dto),
852
+ ...slice,
853
+ apBox: pdfToContentRect$1(strippedRect ?? dto.rect, crop),
854
+ ...strippedRect ? { apRot: geomRotation(slice.geom) } : {}
855
+ };
856
+ }
857
+ /** Lower `keys` through the kind's overrides + the generic table. `null` =
858
+ * some key has no lowering — the caller degrades to the full projection. */
859
+ function emitProps(a, crop, keys) {
860
+ const kind = projectionOf(a.subtype);
861
+ const out = {};
862
+ for (const key of keys) {
863
+ const lower = kind.prop?.[key] ?? GENERIC_PROPS[key];
864
+ if (!lower) return null;
865
+ Object.assign(out, lower(a, crop));
866
+ }
867
+ return out;
868
+ }
869
+ const editableKeys = (subtype) => propsFor(subtype).map((s) => s.key);
870
+ /** Content Annot → the FULL engine patch: the kind's geometry group plus every
871
+ * prop it declares editable. The reference statement — scoped emission and
872
+ * drafts both build on it. */
873
+ function toPatch(a, crop) {
874
+ const geo = projectionOf(a.subtype).geometry(a, crop);
875
+ const props = emitProps(a, crop, editableKeys(a.subtype)) ?? {};
876
+ if (!geo && Object.keys(props).length === 0) return null;
877
+ return {
878
+ subtype: wireSubtypeOf(a),
879
+ ...geo,
880
+ ...props
881
+ };
882
+ }
883
+ /**
884
+ * Content Annot + a {@link PatchScope} → the SPARSE patch for exactly that
885
+ * intent (the shell's `patch` effect emitter): the reducer says WHAT changed —
886
+ * geometry, or the props keys verbatim — and this lowers only that. Kinds
887
+ * without editable geometry (text markup) and unlowerable keys degrade to the
888
+ * full patch: verbose, never a dropped write.
889
+ */
890
+ function toScopedPatch(a, scope, crop) {
891
+ const kind = projectionOf(a.subtype);
892
+ if (scope.kind === "geometry") {
893
+ const geo = kind.geometry(a, crop);
894
+ return geo ? {
895
+ subtype: wireSubtypeOf(a),
896
+ ...geo
897
+ } : toPatch(a, crop);
898
+ }
899
+ const props = emitProps(a, crop, scope.keys);
900
+ if (props === null) return toPatch(a, crop);
901
+ if (Object.keys(props).length === 0) return null;
902
+ return {
903
+ subtype: wireSubtypeOf(a),
904
+ ...props
905
+ };
906
+ }
907
+ /** Content Annot → engine create draft: the full statement plus the kind's
908
+ * create-only extras, with the model's `/F` emitted verbatim, once, for every
909
+ * kind (a fresh draw carries DRAWN_FLAGS plus any tool seed). `null` for the
910
+ * kinds whose creates travel their own path (stamps, widgets, icon place). */
911
+ function toCreateDraft(a, crop) {
912
+ const kind = projectionOf(a.subtype);
913
+ if (kind.createable === false) return null;
914
+ const base = toPatch(a, crop);
915
+ if (!base) return null;
916
+ const extras = kind.draftExtras?.(a, crop);
917
+ if (kind.draftExtras && extras === null) return null;
918
+ return {
919
+ ...base,
920
+ ...extras,
921
+ flags: a.flags
922
+ };
923
+ }
924
+ //#endregion
925
+ //#region src/text-item.ts
926
+ /**
927
+ * The free-text presentation projection: the core's geometry-only `textBoxes`
928
+ * joined with the DTO-derived font + CSS into render-ready {@link TextItem}s. This
929
+ * is the text analogue of the core's `scene()` "paint" for shapes — it lives in
930
+ * the plugin (not the portable core) because the font→CSS stack mapping and the
931
+ * engine `Color`→CSS seam are web concerns, shared across every web framework.
932
+ */
933
+ /** Map a free-text `/DA` font to a CSS font-family. A standard PDF font → a web
934
+ * stack with the same metrics; anything else is a registered font key → mount it
935
+ * as a `@font-face` of that family (see the framework `mountWebFont` helper). */
936
+ const STANDARD_FONT_CSS = {
937
+ helvetica: "Helvetica, Arial, sans-serif",
938
+ "helvetica-bold": "Helvetica, Arial, sans-serif",
939
+ courier: "\"Courier New\", Courier, monospace",
940
+ "times-roman": "\"Times New Roman\", Times, serif",
941
+ symbol: "serif",
942
+ "zapf-dingbats": "serif"
943
+ };
944
+ const cssFontFor = (font) => STANDARD_FONT_CSS[font] ?? `"${font}", sans-serif`;
945
+ /** Project the model's free-text boxes into render-ready {@link TextItem}s — the
946
+ * core geometry (`textBoxes`) joined with the DTO-derived CSS. Pure; memoized by
947
+ * model identity at the call site so selectors get a stable reference. */
948
+ function buildTextItems(m, pon, view) {
949
+ return textBoxes(m, pon, view).map((tb) => {
950
+ const a = m.byId[tb.id];
951
+ const t = a?.text ?? initialTextStyle;
952
+ return {
953
+ id: tb.id,
954
+ ref: a?.ref ?? null,
955
+ box: tb.box,
956
+ contents: a?.data?.contents ?? "",
957
+ editing: tb.editing,
958
+ ...tb.rot ? { rot: tb.rot } : {},
959
+ css: {
960
+ fontFamily: cssFontFor(t.fontFamily),
961
+ fontSize: t.fontSize,
962
+ lineHeight: t.fontSize,
963
+ color: t.fontColor,
964
+ align: t.textAlign,
965
+ padding: 2,
966
+ background: a?.style.interiorColor ?? null
967
+ }
968
+ };
969
+ });
970
+ }
971
+ //#endregion
972
+ //#region src/tools.ts
973
+ /**
974
+ * The creation properties each known authoring kind actually consumes. This is
975
+ * deliberately separate from the flat internal patch vocabulary: it gives tool
976
+ * configuration precise compile-time and runtime validation while mixed-selection
977
+ * edits can keep using `AnnotationPropsPatch`.
978
+ */
979
+ const TOOL_DEFAULT_KEYS = {
980
+ square: [
981
+ "color",
982
+ "interiorColor",
983
+ "opacity",
984
+ "strokeWidth",
985
+ "border"
986
+ ],
987
+ circle: [
988
+ "color",
989
+ "interiorColor",
990
+ "opacity",
991
+ "strokeWidth",
992
+ "border"
993
+ ],
994
+ line: [
995
+ "color",
996
+ "interiorColor",
997
+ "opacity",
998
+ "strokeWidth",
999
+ "border",
1000
+ "lineEndings"
1001
+ ],
1002
+ polygon: [
1003
+ "color",
1004
+ "interiorColor",
1005
+ "opacity",
1006
+ "strokeWidth",
1007
+ "border"
1008
+ ],
1009
+ polyline: [
1010
+ "color",
1011
+ "interiorColor",
1012
+ "opacity",
1013
+ "strokeWidth",
1014
+ "border",
1015
+ "lineEndings"
1016
+ ],
1017
+ ink: [
1018
+ "color",
1019
+ "opacity",
1020
+ "strokeWidth",
1021
+ "blendMode"
1022
+ ],
1023
+ "free-text": [
1024
+ "fontFamily",
1025
+ "fontSize",
1026
+ "fontColor",
1027
+ "textAlign",
1028
+ "opacity",
1029
+ "interiorColor",
1030
+ "color",
1031
+ "strokeWidth",
1032
+ "border"
1033
+ ],
1034
+ "free-text-callout": [
1035
+ "fontFamily",
1036
+ "fontSize",
1037
+ "fontColor",
1038
+ "textAlign",
1039
+ "opacity",
1040
+ "interiorColor",
1041
+ "color",
1042
+ "strokeWidth",
1043
+ "border",
1044
+ "lineEndings"
1045
+ ],
1046
+ highlight: [
1047
+ "color",
1048
+ "opacity",
1049
+ "blendMode"
1050
+ ],
1051
+ underline: [
1052
+ "color",
1053
+ "opacity",
1054
+ "blendMode"
1055
+ ],
1056
+ strikeout: [
1057
+ "color",
1058
+ "opacity",
1059
+ "blendMode"
1060
+ ],
1061
+ squiggly: [
1062
+ "color",
1063
+ "opacity",
1064
+ "blendMode"
1065
+ ],
1066
+ caret: ["color", "opacity"],
1067
+ redact: [
1068
+ "color",
1069
+ "interiorColor",
1070
+ "opacity",
1071
+ "fontFamily",
1072
+ "fontSize",
1073
+ "fontColor",
1074
+ "textAlign"
1075
+ ],
1076
+ stamp: [],
1077
+ link: ["link"],
1078
+ text: [
1079
+ "icon",
1080
+ "color",
1081
+ "opacity"
1082
+ ],
1083
+ "file-attachment": [
1084
+ "icon",
1085
+ "color",
1086
+ "opacity"
1087
+ ],
1088
+ "widget-text": [
1089
+ "color",
1090
+ "interiorColor",
1091
+ "strokeWidth",
1092
+ "border",
1093
+ "fontFamily",
1094
+ "fontSize",
1095
+ "fontColor",
1096
+ "textAlign"
1097
+ ],
1098
+ "widget-choice": [
1099
+ "color",
1100
+ "interiorColor",
1101
+ "strokeWidth",
1102
+ "border",
1103
+ "fontFamily",
1104
+ "fontSize",
1105
+ "fontColor",
1106
+ "textAlign"
1107
+ ],
1108
+ "widget-toggle": [
1109
+ "color",
1110
+ "interiorColor",
1111
+ "strokeWidth",
1112
+ "border"
1113
+ ]
1114
+ };
1115
+ const DRAW_TAGS = ["annotation-draw", "annotation-edit"];
1116
+ const MARKUP_TAGS = ["text-select", "annotation-edit"];
1117
+ /**
1118
+ * The built-in tools — a data mirror of v2's registrations (shapes + lines + ink
1119
+ * + free text in the draw channel, text markup + caret behind text selection, and
1120
+ * the click-to-place stamp). Order is display-neutral; the toolbar owns layout.
1121
+ */
1122
+ const DEFAULT_TOOLS = [
1123
+ {
1124
+ id: "square",
1125
+ subtype: "square",
1126
+ cursor: "crosshair",
1127
+ enables: DRAW_TAGS,
1128
+ defaults: { strokeWidth: 6 },
1129
+ clickCreate: {
1130
+ width: 80,
1131
+ height: 60
1132
+ }
1133
+ },
1134
+ {
1135
+ id: "circle",
1136
+ subtype: "circle",
1137
+ cursor: "crosshair",
1138
+ enables: DRAW_TAGS,
1139
+ defaults: { strokeWidth: 6 },
1140
+ clickCreate: {
1141
+ width: 80,
1142
+ height: 60
1143
+ }
1144
+ },
1145
+ {
1146
+ id: "line",
1147
+ subtype: "line",
1148
+ cursor: "crosshair",
1149
+ enables: DRAW_TAGS,
1150
+ defaults: { strokeWidth: 6 },
1151
+ clickCreate: { length: 80 }
1152
+ },
1153
+ {
1154
+ id: "polygon",
1155
+ subtype: "polygon",
1156
+ cursor: "crosshair",
1157
+ enables: DRAW_TAGS,
1158
+ defaults: { strokeWidth: 6 }
1159
+ },
1160
+ {
1161
+ id: "polyline",
1162
+ subtype: "polyline",
1163
+ cursor: "crosshair",
1164
+ enables: DRAW_TAGS,
1165
+ defaults: { strokeWidth: 6 }
1166
+ },
1167
+ {
1168
+ id: "ink",
1169
+ subtype: "ink",
1170
+ cursor: "crosshair",
1171
+ enables: DRAW_TAGS,
1172
+ defaults: {
1173
+ color: "#ef4444",
1174
+ strokeWidth: 6,
1175
+ blendMode: "normal"
1176
+ },
1177
+ ink: { groupStrokesMs: 800 }
1178
+ },
1179
+ {
1180
+ id: "ink-highlight",
1181
+ extends: "ink",
1182
+ intent: "ink-highlight",
1183
+ defaults: {
1184
+ color: "#ffcd45",
1185
+ strokeWidth: 14,
1186
+ blendMode: "multiply"
1187
+ },
1188
+ ink: { straighten: {
1189
+ deviationThreshold: .15,
1190
+ axisSnapDegrees: 15
1191
+ } }
1192
+ },
1193
+ {
1194
+ id: "free-text",
1195
+ subtype: "free-text",
1196
+ cursor: "crosshair",
1197
+ enables: DRAW_TAGS,
1198
+ defaults: { fontColor: "#ef4444" },
1199
+ upright: true,
1200
+ clickCreate: {
1201
+ width: 180,
1202
+ height: 40,
1203
+ anchor: "top-left"
1204
+ }
1205
+ },
1206
+ {
1207
+ id: "free-text-callout",
1208
+ subtype: "free-text-callout",
1209
+ propsKind: "free-text",
1210
+ cursor: "crosshair",
1211
+ enables: DRAW_TAGS,
1212
+ defaults: {
1213
+ strokeWidth: 6,
1214
+ lineEndings: { end: "open-arrow" }
1215
+ },
1216
+ upright: true
1217
+ },
1218
+ {
1219
+ id: "highlight",
1220
+ subtype: "highlight",
1221
+ cursor: "default",
1222
+ enables: MARKUP_TAGS,
1223
+ defaults: {
1224
+ color: "#ffe16a",
1225
+ blendMode: "multiply"
1226
+ },
1227
+ selection: { kind: "markup" }
1228
+ },
1229
+ {
1230
+ id: "redact",
1231
+ subtype: "redact",
1232
+ cursor: "crosshair",
1233
+ enables: [
1234
+ "text-select",
1235
+ "annotation-draw",
1236
+ "annotation-edit"
1237
+ ],
1238
+ defaults: {
1239
+ color: "#e44234",
1240
+ interiorColor: "#000000",
1241
+ fontColor: "#ffffff",
1242
+ opacity: 1
1243
+ },
1244
+ selection: { kind: "markup" }
1245
+ },
1246
+ {
1247
+ id: "underline",
1248
+ subtype: "underline",
1249
+ cursor: "default",
1250
+ enables: MARKUP_TAGS,
1251
+ defaults: { color: "#ef4444" },
1252
+ selection: { kind: "markup" }
1253
+ },
1254
+ {
1255
+ id: "strikeout",
1256
+ subtype: "strikeout",
1257
+ cursor: "default",
1258
+ enables: MARKUP_TAGS,
1259
+ defaults: { color: "#ef4444" },
1260
+ selection: { kind: "markup" }
1261
+ },
1262
+ {
1263
+ id: "squiggly",
1264
+ subtype: "squiggly",
1265
+ cursor: "default",
1266
+ enables: MARKUP_TAGS,
1267
+ defaults: { color: "#ef4444" },
1268
+ selection: { kind: "markup" }
1269
+ },
1270
+ {
1271
+ id: "insert-text",
1272
+ subtype: "caret",
1273
+ propsKind: "caret",
1274
+ preset: "caret",
1275
+ cursor: "default",
1276
+ enables: MARKUP_TAGS,
1277
+ defaults: { color: "#ef4444" },
1278
+ selection: {
1279
+ kind: "text-edit",
1280
+ operation: "insert"
1281
+ }
1282
+ },
1283
+ {
1284
+ id: "replace-text",
1285
+ subtype: "strikeout",
1286
+ propsKind: "strikeout",
1287
+ cursor: "default",
1288
+ enables: MARKUP_TAGS,
1289
+ defaults: { color: "#ef4444" },
1290
+ selection: {
1291
+ kind: "text-edit",
1292
+ operation: "replace"
1293
+ }
1294
+ },
1295
+ {
1296
+ id: "stamp",
1297
+ subtype: "stamp",
1298
+ cursor: "copy",
1299
+ enables: ["annotation-place", "annotation-edit"],
1300
+ source: {
1301
+ kind: "prompt",
1302
+ accept: "image/png,image/jpeg,application/pdf"
1303
+ },
1304
+ upright: true,
1305
+ ghost: { mode: "footprint" }
1306
+ },
1307
+ {
1308
+ id: "link",
1309
+ subtype: "link",
1310
+ cursor: "crosshair",
1311
+ enables: DRAW_TAGS
1312
+ },
1313
+ {
1314
+ id: "note",
1315
+ subtype: "text",
1316
+ cursor: "crosshair",
1317
+ enables: ["annotation-place", "annotation-edit"],
1318
+ defaults: {
1319
+ icon: "comment",
1320
+ color: "#facc15"
1321
+ },
1322
+ flags: {
1323
+ noZoom: true,
1324
+ noRotate: true
1325
+ },
1326
+ upright: true,
1327
+ ghost: { mode: "footprint" }
1328
+ },
1329
+ {
1330
+ id: "attachment",
1331
+ subtype: "file-attachment",
1332
+ cursor: "copy",
1333
+ enables: ["annotation-place", "annotation-edit"],
1334
+ source: { kind: "prompt" },
1335
+ defaults: {
1336
+ icon: "paperclip",
1337
+ color: "#facc15"
1338
+ },
1339
+ flags: {
1340
+ noZoom: true,
1341
+ noRotate: true
1342
+ },
1343
+ upright: true,
1344
+ ghost: { mode: "footprint" }
1345
+ }
1346
+ ];
1347
+ /** Merge two default patches, `b` over `a`, with line endings merged per side. */
1348
+ function mergeDefaults(a, b) {
1349
+ if (!a) return b;
1350
+ if (!b) return a;
1351
+ const merged = {
1352
+ ...a,
1353
+ ...b
1354
+ };
1355
+ if (a.lineEndings || b.lineEndings) merged.lineEndings = {
1356
+ ...a.lineEndings,
1357
+ ...b.lineEndings
1358
+ };
1359
+ return merged;
1360
+ }
1361
+ /** Overlay a same-id override onto a base definition (configure a built-in). */
1362
+ function mergeDef(base, over) {
1363
+ return {
1364
+ ...base,
1365
+ ...over,
1366
+ enables: over.enables ?? base.enables,
1367
+ meta: base.meta || over.meta ? {
1368
+ ...base.meta,
1369
+ ...over.meta
1370
+ } : void 0,
1371
+ defaults: mergeDefaults(base.defaults, over.defaults),
1372
+ flags: base.flags || over.flags ? {
1373
+ ...base.flags,
1374
+ ...over.flags
1375
+ } : void 0,
1376
+ ink: base.ink || over.ink ? {
1377
+ ...base.ink,
1378
+ ...over.ink
1379
+ } : void 0
1380
+ };
1381
+ }
1382
+ function validateDefaults(tool) {
1383
+ if (!tool.defaults) return;
1384
+ const allowed = TOOL_DEFAULT_KEYS[tool.subtype];
1385
+ if (!allowed) return;
1386
+ const keys = new Set(allowed);
1387
+ 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}'`);
1388
+ }
1389
+ /**
1390
+ * Resolve {@link DEFAULT_TOOLS} + embedder overrides into the ready-to-use table.
1391
+ * A same-id override merges over the built-in; a new id adds a tool; `extends`
1392
+ * inherits from an already-defined tool. Keyed by id, so lookups are O(1).
1393
+ */
1394
+ function buildToolRegistry(overrides = []) {
1395
+ const defs = /* @__PURE__ */ new Map();
1396
+ for (const d of DEFAULT_TOOLS) defs.set(d.id, d);
1397
+ for (const o of overrides) {
1398
+ const prev = defs.get(o.id);
1399
+ defs.set(o.id, prev ? mergeDef(prev, o) : o);
1400
+ }
1401
+ const out = /* @__PURE__ */ new Map();
1402
+ const resolving = /* @__PURE__ */ new Set();
1403
+ const resolve = (id) => {
1404
+ const cached = out.get(id);
1405
+ if (cached) return cached;
1406
+ const def = defs.get(id);
1407
+ if (!def) throw new Error(`[annotation] tool '${id}' extends an unknown tool`);
1408
+ let base;
1409
+ if (def.extends && def.extends !== id && !resolving.has(def.extends)) {
1410
+ resolving.add(id);
1411
+ base = resolve(def.extends);
1412
+ resolving.delete(id);
1413
+ }
1414
+ const subtype = def.subtype ?? base?.subtype ?? def.id;
1415
+ const resolved = {
1416
+ id: def.id,
1417
+ subtype,
1418
+ preset: def.preset ?? def.id,
1419
+ propsKind: def.propsKind ?? base?.propsKind ?? subtype,
1420
+ cursor: def.cursor ?? base?.cursor ?? "crosshair",
1421
+ enables: new Set(def.enables ?? (base ? [...base.enables] : [])),
1422
+ defaults: mergeDefaults(base?.defaults, def.defaults),
1423
+ flags: base?.flags || def.flags ? {
1424
+ ...base?.flags,
1425
+ ...def.flags
1426
+ } : void 0,
1427
+ source: def.source ?? base?.source,
1428
+ selection: def.selection ?? base?.selection,
1429
+ intent: def.intent ?? base?.intent,
1430
+ ink: base?.ink || def.ink ? {
1431
+ ...base?.ink,
1432
+ ...def.ink
1433
+ } : void 0,
1434
+ upright: def.upright ?? base?.upright ?? false,
1435
+ clickCreate: def.clickCreate ?? base?.clickCreate ?? false,
1436
+ ghost: def.ghost ?? base?.ghost ?? false,
1437
+ meta: base?.meta || def.meta ? {
1438
+ ...base?.meta,
1439
+ ...def.meta
1440
+ } : void 0
1441
+ };
1442
+ validateDefaults(resolved);
1443
+ out.set(id, resolved);
1444
+ return resolved;
1445
+ };
1446
+ for (const id of defs.keys()) resolve(id);
1447
+ return out;
1448
+ }
1449
+ //#endregion
1450
+ //#region src/placement.ts
1451
+ /**
1452
+ * Per-kind CODE for the click-to-place ICON kinds (note / file attachment)
1453
+ * — the `props.ts`/`repository.ts` pattern: all tool CONFIG stays in the
1454
+ * tool table; this module only interprets it. The stamp keeps its own
1455
+ * sizing/sniffing path in the capability; everything funnels through the
1456
+ * one `placeAt` entry there.
1457
+ */
1458
+ /**
1459
+ * Icon kinds place at the generator's fixed box: the engine forces the
1460
+ * `/Rect` to 20×20 from its bottom-left corner (the spec's note-icon rule),
1461
+ * so the footprint ghost and the placement use exactly this size.
1462
+ */
1463
+ const ICON_PLACE_SIZE = {
1464
+ width: 20,
1465
+ height: 20
1466
+ };
1467
+ const isIconPlaceKind = (subtype) => subtype === "text" || subtype === "file-attachment";
1468
+ /**
1469
+ * Build the engine create draft for a placed icon annotation. `geom` is the
1470
+ * repository's `boxGeomFields` emit (the `/Rect` + upright rotation pair);
1471
+ * `defaults` is the tool's resolved flat props bag (`defaultsFor`) — the
1472
+ * colour seam is crossed here via the repository's `cssToColor`, and the
1473
+ * icon falls back to the kind's own default when the bag carries none.
1474
+ */
1475
+ function iconPlacementDraft(subtype, geom, defaults, flags, file) {
1476
+ const shared = {
1477
+ ...geom,
1478
+ color: cssToColor(defaults.color),
1479
+ opacity: defaults.opacity,
1480
+ flags: {
1481
+ print: true,
1482
+ ...flags
1483
+ }
1484
+ };
1485
+ if (subtype === "text") return {
1486
+ subtype: "text",
1487
+ icon: defaults.icon ?? "comment",
1488
+ ...shared
1489
+ };
1490
+ if (!file) throw new Error("[annotation] a file-attachment placement requires a file payload");
1491
+ return {
1492
+ subtype: "file-attachment",
1493
+ icon: defaults.icon ?? "paperclip",
1494
+ file,
1495
+ ...shared
1496
+ };
1497
+ }
1498
+ //#endregion
1499
+ //#region src/capability.ts
1500
+ /** Fold `zoom`/`rotation` host args into the core's ViewEnv (or none).
1501
+ * `zoom` is the page's RELATIVE zoom (`transform.zoom`) — never the
1502
+ * px-per-point `scale` (that one only converts CSS-px chrome settings). */
1503
+ const viewEnv = (zoom, rotation) => zoom != null || rotation != null ? {
1504
+ zoom: zoom ?? 1,
1505
+ rotation: rotation ?? 0
1506
+ } : 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
+ const TEXT_COMMIT_DEBOUNCE_MS = 250;
1512
+ /**
1513
+ * The annotation shell. The pure `update` runs HERE (so it can emit effects);
1514
+ * the resulting model is dispatched to the store, and each effect is performed
1515
+ * against the engine repository — optimistic create → reconcile to the durable
1516
+ * ref, patch/delete fire-and-forget.
1517
+ */
1518
+ function createAnnotationCapability(ctx, config = {}) {
1519
+ const loaded = /* @__PURE__ */ new Set();
1520
+ const behaviors = [];
1521
+ /** Per-annotation debounce timer for the engine `contents` write while typing. */
1522
+ const textTimers = /* @__PURE__ */ new Map();
1523
+ const configTools = config.tools ?? [];
1524
+ const registry = buildToolRegistry(configTools);
1525
+ /** The installed file-picker port (a DOM file dialog, wired by the framework
1526
+ * adapter), or null — every click-then-pick tool resolves through this ONE
1527
+ * slot. See {@link FilePickerProvider}. */
1528
+ let filePickerProvider = null;
1529
+ const model = () => ctx.getState().model;
1530
+ const chromeSettings = () => ctx.getState().chrome;
1531
+ /**
1532
+ * Ids on a page whose Behavior is currently ENGAGED (form widgets under a
1533
+ * fill tool): they render their own DOM, so hit-test/marquee must not see
1534
+ * them. Resolved per event — engagement follows the active tool live.
1535
+ */
1536
+ const inertIdsAt = (pon) => {
1537
+ if (!behaviors.length) return void 0;
1538
+ const m = model();
1539
+ let out;
1540
+ for (const id of m.order) {
1541
+ const a = m.byId[id];
1542
+ if (!a || a.pon !== pon) continue;
1543
+ if (behaviors.some((b) => b.matches({
1544
+ subtype: a.subtype,
1545
+ ref: a.ref
1546
+ }) && b.engaged())) (out ?? (out = /* @__PURE__ */ new Set())).add(id);
1547
+ }
1548
+ return out;
1549
+ };
1550
+ /** The CSS-px chrome settings converted to CONTENT units by the page's view
1551
+ * 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) => {
1554
+ const cs = chromeSettings();
1555
+ const s = scale || 1;
1556
+ return {
1557
+ handleTol: cs.handles.hitSize / 2 / s,
1558
+ knobTol: cs.knob.hitSize / 2 / s,
1559
+ knobOffset: cs.knob.offset / s
1560
+ };
1561
+ };
1562
+ const itemsCache = /* @__PURE__ */ new Map();
1563
+ const chromeCache = /* @__PURE__ */ new Map();
1564
+ const memoItems = (pon, view) => {
1565
+ const m = model();
1566
+ const g = ctx.getState().toolGhost;
1567
+ const c = itemsCache.get(pon);
1568
+ if (c && c.model === m && c.ghost === g && c.zoom === view?.zoom && c.rotation === view?.rotation) return c.v;
1569
+ const v = pageItems(m, pon, view);
1570
+ if (g && g.pon === pon && g.kind === "vector") {
1571
+ const tool = registry.get(g.toolId);
1572
+ const style = styleFromProps(defaultsFor(m, tool?.preset ?? g.toolId));
1573
+ v.push({
1574
+ id: "tool-ghost",
1575
+ ref: null,
1576
+ subtype: tool?.subtype ?? "square",
1577
+ geom: g.geom,
1578
+ box: g.box,
1579
+ style,
1580
+ source: "ghost",
1581
+ selected: false
1582
+ });
1583
+ }
1584
+ itemsCache.set(pon, {
1585
+ model: m,
1586
+ ghost: g,
1587
+ zoom: view?.zoom,
1588
+ rotation: view?.rotation,
1589
+ v
1590
+ });
1591
+ return v;
1592
+ };
1593
+ const memoChrome = (pon, scale, rotation, zoom) => {
1594
+ const m = model();
1595
+ const cs = chromeSettings();
1596
+ const c = chromeCache.get(pon);
1597
+ if (c && c.model === m && c.cs === cs && c.scale === scale && c.rotation === rotation && c.zoom === zoom) return c.v;
1598
+ let v = chrome(m, pon, pageBoxOf(pon), chromeGeomAt(scale).knobOffset, viewEnv(zoom, rotation));
1599
+ if (!cs.guides.enabled) v = v.filter((n) => n.kind !== "rotate-guides");
1600
+ chromeCache.set(pon, {
1601
+ model: m,
1602
+ cs,
1603
+ scale,
1604
+ rotation,
1605
+ zoom,
1606
+ v
1607
+ });
1608
+ return v;
1609
+ };
1610
+ let anchorCache = null;
1611
+ const memoAnchor = (scale, rotation, zoom) => {
1612
+ const m = model();
1613
+ const cs = chromeSettings();
1614
+ if (anchorCache && anchorCache.model === m && anchorCache.cs === cs && anchorCache.scale === scale && anchorCache.rotation === rotation && anchorCache.zoom === zoom) return anchorCache.v;
1615
+ const v = selectionAnchor(m, pageBoxOf, () => chromeGeomAt(scale).knobOffset, () => viewEnv(zoom, rotation));
1616
+ anchorCache = {
1617
+ model: m,
1618
+ cs,
1619
+ scale,
1620
+ rotation,
1621
+ zoom,
1622
+ v
1623
+ };
1624
+ return v;
1625
+ };
1626
+ let draftAnchorCache = null;
1627
+ const memoDraftAnchor = () => {
1628
+ const m = model();
1629
+ if (draftAnchorCache && draftAnchorCache.model === m) return draftAnchorCache.v;
1630
+ const v = creationDraftAnchor(m);
1631
+ draftAnchorCache = {
1632
+ model: m,
1633
+ v
1634
+ };
1635
+ return v;
1636
+ };
1637
+ let selPropsCache = null;
1638
+ const memoSelectionProps = () => {
1639
+ const m = model();
1640
+ if (selPropsCache && selPropsCache.model === m) return selPropsCache.v;
1641
+ const members = m.selected.map((id) => m.byId[id]).filter((a) => !!a);
1642
+ const specs = sharedProps(members.map((a) => a.subtype));
1643
+ const values = {};
1644
+ const mixed = [];
1645
+ for (const spec of specs) {
1646
+ const first = readProp(members[0], spec.key);
1647
+ values[spec.key] = first;
1648
+ const firstJson = JSON.stringify(first);
1649
+ if (members.some((a) => JSON.stringify(readProp(a, spec.key)) !== firstJson)) mixed.push(spec.key);
1650
+ }
1651
+ const v = {
1652
+ specs,
1653
+ values,
1654
+ mixed
1655
+ };
1656
+ selPropsCache = {
1657
+ model: m,
1658
+ v
1659
+ };
1660
+ return v;
1661
+ };
1662
+ const linkItemsCache = /* @__PURE__ */ new Map();
1663
+ const memoLinkItems = (pon) => {
1664
+ const m = model();
1665
+ const c = linkItemsCache.get(pon);
1666
+ if (c && c.model === m) return c.v;
1667
+ const v = [];
1668
+ for (const id of m.order) {
1669
+ const a = m.byId[id];
1670
+ if (!a || a.pon !== pon || a.link == null) continue;
1671
+ if (!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
+ }));
1683
+ }
1684
+ linkItemsCache.set(pon, {
1685
+ model: m,
1686
+ v
1687
+ });
1688
+ return v;
1689
+ };
1690
+ const textsCache = /* @__PURE__ */ new Map();
1691
+ const memoTexts = (pon, view) => {
1692
+ const m = model();
1693
+ const c = textsCache.get(pon);
1694
+ if (c && c.model === m && c.zoom === view?.zoom && c.rotation === view?.rotation) return c.v;
1695
+ const v = buildTextItems(m, pon, view);
1696
+ textsCache.set(pon, {
1697
+ model: m,
1698
+ zoom: view?.zoom,
1699
+ rotation: view?.rotation,
1700
+ v
1701
+ });
1702
+ return v;
1703
+ };
1704
+ let selFlagsCache = null;
1705
+ const memoSelectionFlags = () => {
1706
+ const m = model();
1707
+ if (selFlagsCache && selFlagsCache.model === m) return selFlagsCache.v;
1708
+ const members = m.selected.map((id) => m.byId[id]).filter((a) => !!a);
1709
+ let v = null;
1710
+ if (members.length) {
1711
+ v = {};
1712
+ for (const key of FLAG_KEYS) {
1713
+ const first = members[0].flags[key];
1714
+ v[key] = members.every((a) => a.flags[key] === first) ? first : null;
1715
+ }
1716
+ }
1717
+ selFlagsCache = {
1718
+ model: m,
1719
+ v
1720
+ };
1721
+ return v;
1722
+ };
1723
+ const cropOf = (pon) => ctx.document()?.pages.find((p) => p.pageObjectNumber === pon)?.boxes.crop ?? null;
1724
+ /** The page's box in content space (origin at the crop top-left) — the box
1725
+ * pointer gestures clamp to, so annotations stay page-bound. */
1726
+ const pageBoxOf = (pon) => {
1727
+ const crop = cropOf(pon);
1728
+ return crop ? {
1729
+ x: 0,
1730
+ y: 0,
1731
+ width: crop.right - crop.left,
1732
+ height: crop.top - crop.bottom
1733
+ } : void 0;
1734
+ };
1735
+ /**
1736
+ * The armed stamp-tool payload: the bytes the next click places, plus the
1737
+ * PDF-point placement size (derived from the sniffed intrinsic aspect).
1738
+ * Transient tool state — deliberately NOT in the model: it is never
1739
+ * rendered, never synced, and dies with the tool.
1740
+ */
1741
+ let armedStamp = null;
1742
+ /**
1743
+ * The desired stamp size (PDF points) from sniffed bytes: the image's
1744
+ * INTRINSIC pixel dimensions taken 1:1 as points (the v2 rule — keep the
1745
+ * artwork's own size, then clamp to the page at placement). A `targetWidth`
1746
+ * override scales to that width, aspect preserved. Vector (PDF) stamps carry
1747
+ * no sniffable dimensions — a caller-supplied `intrinsic` override (a stamp
1748
+ * library knows its page size) keeps the true aspect; without one they fall
1749
+ * back to a square target.
1750
+ */
1751
+ const desiredStampSize = (meta, targetWidth, intrinsicOverride) => {
1752
+ const intrinsic = intrinsicOverride && intrinsicOverride.width > 0 && intrinsicOverride.height > 0 ? intrinsicOverride : "width" in meta && meta.width > 0 ? {
1753
+ width: meta.width,
1754
+ height: meta.height
1755
+ } : {
1756
+ width: targetWidth ?? 150,
1757
+ height: targetWidth ?? 150
1758
+ };
1759
+ if (targetWidth === void 0) return intrinsic;
1760
+ return {
1761
+ width: targetWidth,
1762
+ height: targetWidth * (intrinsic.height / intrinsic.width)
1763
+ };
1764
+ };
1765
+ const armStamp = async (input) => {
1766
+ const resolved = await resolveBinarySource(input.source);
1767
+ const meta = sniffBinaryMetadata(resolved.bytes);
1768
+ if (!meta) throw new Error("[annotation] stamp source must be PNG, JPEG, or single-page PDF bytes");
1769
+ let preview = null;
1770
+ if (input.preview) {
1771
+ const p = await resolveBinarySource(input.preview);
1772
+ preview = {
1773
+ bytes: new Uint8Array(p.bytes),
1774
+ mimeType: p.mimeType
1775
+ };
1776
+ } else if (meta.mimeType !== "application/pdf") preview = {
1777
+ bytes: new Uint8Array(resolved.bytes),
1778
+ mimeType: meta.mimeType
1779
+ };
1780
+ armedStamp = {
1781
+ source: input.source,
1782
+ ...desiredStampSize(meta, input.targetWidth, input.intrinsicSize),
1783
+ preview
1784
+ };
1785
+ ctx.dispatch({ type: "STAMP_ARM_CHANGED" });
1786
+ ctx.tryGet(InteractionToken)?.activateTool("stamp");
1787
+ };
1788
+ const disarmStamp = () => {
1789
+ if (!armedStamp) return;
1790
+ armedStamp = null;
1791
+ ctx.dispatch({ type: "STAMP_ARM_CHANGED" });
1792
+ };
1793
+ /** Move the hover FOOTPRINT ghost to a content point. The box/geometry is
1794
+ * computed by the SAME rules the click's placement uses ({@link createStampAt}
1795
+ * fit + clamp for an armed stamp; the click-create anchor + page clamp for a
1796
+ * draw tool), so the ghost is the placement, not an approximation of it. */
1797
+ const ghostHoverAt = (toolId, pon, point, displayRotation) => {
1798
+ const tool = registry.get(toolId);
1799
+ const crop = cropOf(pon);
1800
+ if (!tool || tool.ghost === false || !crop) {
1801
+ clearGhost();
1802
+ return;
1803
+ }
1804
+ const page = {
1805
+ width: crop.right - crop.left,
1806
+ height: crop.top - crop.bottom
1807
+ };
1808
+ if (armedStamp) {
1809
+ const rot = uprightRotFor(displayRotation);
1810
+ const box = fitStampBox(point, {
1811
+ width: armedStamp.width,
1812
+ height: armedStamp.height
1813
+ }, page, rot);
1814
+ ctx.dispatch({
1815
+ type: "SET_TOOL_GHOST",
1816
+ ghost: {
1817
+ pon,
1818
+ box,
1819
+ rot,
1820
+ kind: "image"
1821
+ }
1822
+ });
1823
+ return;
1824
+ }
1825
+ if (isIconPlaceKind(tool.subtype)) {
1826
+ const box = fitStampBox(point, ICON_PLACE_SIZE, page, uprightRotFor(displayRotation));
1827
+ dispatchVectorGhost(pon, toolId, {
1828
+ t: "rect",
1829
+ rect: box,
1830
+ ellipse: false
1831
+ });
1832
+ return;
1833
+ }
1834
+ if (!tool.clickCreate) {
1835
+ clearGhost();
1836
+ return;
1837
+ }
1838
+ const placement = resolveClickPlacement(point, tool.clickCreate, {
1839
+ pageBox: {
1840
+ x: 0,
1841
+ y: 0,
1842
+ width: page.width,
1843
+ height: page.height
1844
+ },
1845
+ upright: tool.upright,
1846
+ displayRotation
1847
+ });
1848
+ const geom = clickCreateGeom(tool.subtype, placement, defaultsFor(model(), tool.preset));
1849
+ if (!geom) {
1850
+ clearGhost();
1851
+ return;
1852
+ }
1853
+ dispatchVectorGhost(pon, toolId, geom);
1854
+ };
1855
+ /** Paint a vector ghost item for a tool's would-be geometry — shared by the
1856
+ * hover footprint and the externally-driven placement preview. */
1857
+ const dispatchVectorGhost = (pon, toolId, geom) => {
1858
+ const tool = registry.get(toolId);
1859
+ const style = styleFromProps(defaultsFor(model(), tool?.preset ?? toolId));
1860
+ ctx.dispatch({
1861
+ type: "SET_TOOL_GHOST",
1862
+ ghost: {
1863
+ pon,
1864
+ box: geomVisualBounds(geom, style.strokeWidth, style.border),
1865
+ rot: 0,
1866
+ kind: "vector",
1867
+ toolId,
1868
+ geom
1869
+ }
1870
+ });
1871
+ };
1872
+ /**
1873
+ * Drive the placement preview during an EXTERNALLY-owned creation gesture
1874
+ * (the form plugin's drag-to-place): paint the box the commit would use,
1875
+ * styled from the TOOL's defaults, through the same ghost pipeline as every
1876
+ * footprint. The box is clamped to the page (a drag may overshoot).
1877
+ */
1878
+ const setPlacementPreview = (toolId, pon, box) => {
1879
+ const crop = cropOf(pon);
1880
+ if (!crop) return;
1881
+ const page = {
1882
+ width: crop.right - crop.left,
1883
+ height: crop.top - crop.bottom
1884
+ };
1885
+ const x = Math.max(0, Math.min(box.x, page.width));
1886
+ const y = Math.max(0, Math.min(box.y, page.height));
1887
+ const rect = {
1888
+ x,
1889
+ y,
1890
+ width: Math.max(0, Math.min(box.x + box.width, page.width) - x),
1891
+ height: Math.max(0, Math.min(box.y + box.height, page.height) - y)
1892
+ };
1893
+ dispatchVectorGhost(pon, toolId, {
1894
+ t: "rect",
1895
+ rect,
1896
+ ellipse: false
1897
+ });
1898
+ };
1899
+ const clearGhost = () => {
1900
+ if (ctx.getState().toolGhost) ctx.dispatch({
1901
+ type: "SET_TOOL_GHOST",
1902
+ ghost: null
1903
+ });
1904
+ };
1905
+ /** Place a stamp of `desired` PDF-point size centred on a content point — the
1906
+ * one engine-write both the armed and click-to-place paths funnel through.
1907
+ * The size is fit to the page and clamped fully onto it (v2 rubber-stamp
1908
+ * rule: never larger than the page, aspect preserved), and never spills off
1909
+ * the edge. `rotCW` (the tool's upright counter-rotation, CW content degrees)
1910
+ * emits the repository's box rotation fields — the engine bakes the tilted
1911
+ * /AP exactly as an interactively rotated stamp round-trips, and the fit uses
1912
+ * the ROTATED footprint. Returns false when the page/document isn't ready. */
1913
+ const createStampAt = (pon, point, source, desired, rotCW = 0) => {
1914
+ const doc = ctx.doc;
1915
+ const crop = cropOf(pon);
1916
+ if (!doc || !crop) return false;
1917
+ const box = fitStampBox(point, desired, {
1918
+ width: crop.right - crop.left,
1919
+ height: crop.top - crop.bottom
1920
+ }, rotCW);
1921
+ doc.page(pon).annotations.create({
1922
+ subtype: "stamp",
1923
+ ...boxGeomFields(box, rotCW, crop),
1924
+ source,
1925
+ fit: "contain"
1926
+ }).then((res) => {
1927
+ syncDTO(res.created, "baked");
1928
+ apply({
1929
+ t: "select",
1930
+ ids: [refKey(res.created.ref)]
1931
+ });
1932
+ }, (err) => console.error("[annotation] stamp placement failed:", err));
1933
+ return true;
1934
+ };
1935
+ /** The active tool's upright counter-rotation for a click at `displayRotation`
1936
+ * (0 when the tool doesn't ask for upright, or the display isn't rotated). */
1937
+ const uprightRotFor = (displayRotation) => {
1938
+ if (!displayRotation) return 0;
1939
+ const ix = ctx.tryGet(InteractionToken);
1940
+ return (ix ? registry.get(ix.activeToolId()) : void 0)?.upright ? uprightRotation(displayRotation) : 0;
1941
+ };
1942
+ const placeArmedStamp = (pon, point, displayRotation) => {
1943
+ const armed = armedStamp;
1944
+ if (!armed) return false;
1945
+ return createStampAt(pon, point, armed.source, {
1946
+ width: armed.width,
1947
+ height: armed.height
1948
+ }, uprightRotFor(displayRotation));
1949
+ };
1950
+ /** Sniff a stamp source (rejecting non-image bytes) and place it at its
1951
+ * intrinsic size (fit to the page in {@link createStampAt}); `targetWidth`
1952
+ * overrides the width, aspect preserved. */
1953
+ const placeStampSource = async (pon, point, source, rotCW, targetWidth) => {
1954
+ const meta = sniffBinaryMetadata((await resolveBinarySource(source)).bytes);
1955
+ if (!meta) {
1956
+ console.error("[annotation] stamp source must be PNG, JPEG, or single-page PDF bytes");
1957
+ return;
1958
+ }
1959
+ createStampAt(pon, point, source, desiredStampSize(meta, targetWidth), rotCW);
1960
+ };
1961
+ /**
1962
+ * Click-to-place: resolve the ACTIVE tool's source spec. Fixed `bytes` place
1963
+ * immediately; a `'prompt'` source asks the installed provider, then places on
1964
+ * resolve — dropping the placement if it was cancelled, or if the tool or
1965
+ * document changed while the picker was open (the intent expired).
1966
+ */
1967
+ const requestStampAt = (pon, point, displayRotation) => {
1968
+ const ix = ctx.tryGet(InteractionToken);
1969
+ const tool = ix ? registry.get(ix.activeToolId()) : void 0;
1970
+ const spec = tool?.source;
1971
+ if (!spec) return false;
1972
+ const rotCW = tool.upright && displayRotation ? uprightRotation(displayRotation) : 0;
1973
+ if (spec.kind === "bytes") {
1974
+ placeStampSource(pon, point, spec.source, rotCW);
1975
+ return true;
1976
+ }
1977
+ return promptFileAt(tool, pon, point, (picked) => void placeStampSource(pon, point, picked.data, rotCW));
1978
+ };
1979
+ /**
1980
+ * Run the installed file-picker port for a click-then-pick tool, with the
1981
+ * shared expiry rule: the placement is dropped if the picker cancels, or if
1982
+ * the document or the active tool changed while it was open (the intent
1983
+ * expired with the click). No provider installed → decline the click (let a
1984
+ * lower-priority handler act) rather than swallow it.
1985
+ */
1986
+ const promptFileAt = (tool, pon, point, place) => {
1987
+ const provider = filePickerProvider;
1988
+ if (!provider) return false;
1989
+ const spec = tool.source;
1990
+ const req = {
1991
+ toolId: tool.id,
1992
+ subtype: tool.subtype,
1993
+ accept: spec?.kind === "prompt" ? spec.accept : void 0,
1994
+ pon,
1995
+ point
1996
+ };
1997
+ const docAtClick = ctx.doc;
1998
+ provider(req).then((picked) => {
1999
+ if (!picked) return;
2000
+ if (ctx.doc !== docAtClick) return;
2001
+ if (ctx.tryGet(InteractionToken)?.activeToolId() !== tool.id) return;
2002
+ place(picked);
2003
+ }, (err) => console.error("[annotation] file-picker provider failed:", err));
2004
+ return true;
2005
+ };
2006
+ /** Place a fixed-size ICON annotation (note / file attachment) centred on a
2007
+ * content point — the icon-kind sibling of {@link createStampAt}. The
2008
+ * engine bakes the 20×20 /AP from /C + /Name; a successful create selects
2009
+ * the new annotation (the anchor for its menu / future comment popup). */
2010
+ const createIconAt = (tool, pon, point, rotCW, file) => {
2011
+ const doc = ctx.doc;
2012
+ const crop = cropOf(pon);
2013
+ if (!doc || !crop || !isIconPlaceKind(tool.subtype)) return false;
2014
+ const box = fitStampBox(point, ICON_PLACE_SIZE, {
2015
+ width: crop.right - crop.left,
2016
+ height: crop.top - crop.bottom
2017
+ }, rotCW);
2018
+ const draft = iconPlacementDraft(tool.subtype, boxGeomFields(box, rotCW, crop), defaultsFor(model(), tool.preset), tool.flags, file);
2019
+ doc.page(pon).annotations.create(draft).then((res) => {
2020
+ syncDTO(res.created, "baked");
2021
+ apply({
2022
+ t: "select",
2023
+ ids: [refKey(res.created.ref)]
2024
+ });
2025
+ }, (err) => console.error("[annotation] icon placement failed:", err));
2026
+ return true;
2027
+ };
2028
+ /**
2029
+ * The ONE click-to-place entry the place handler forwards every down to —
2030
+ * armed payload first, then the active tool's kind decides:
2031
+ * - stamp → the source spec (fixed bytes, or the file-picker port)
2032
+ * - note (text) → place immediately (no payload)
2033
+ * - attachment → spot first, file second: the file-picker port opens
2034
+ * inside the click gesture, and placement lands when it
2035
+ * resolves (dropped on cancel, or if the tool/document
2036
+ * changed while the picker was open).
2037
+ * Returns whether the click was consumed.
2038
+ */
2039
+ const placeAt = (pon, point, displayRotation) => {
2040
+ if (placeArmedStamp(pon, point, displayRotation)) return true;
2041
+ const ix = ctx.tryGet(InteractionToken);
2042
+ const tool = ix ? registry.get(ix.activeToolId()) : void 0;
2043
+ if (!tool) return false;
2044
+ if (tool.subtype === "stamp") return requestStampAt(pon, point, displayRotation);
2045
+ if (!isIconPlaceKind(tool.subtype)) return false;
2046
+ const rotCW = tool.upright && displayRotation ? uprightRotation(displayRotation) : 0;
2047
+ if (tool.subtype === "text") return createIconAt(tool, pon, point, rotCW, null);
2048
+ return promptFileAt(tool, pon, point, (picked) => createIconAt(tool, pon, point, rotCW, picked));
2049
+ };
2050
+ /** The page a ref lives on: from the loaded model first (covers obj/nm refs
2051
+ * 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);
2053
+ /**
2054
+ * Re-sync one annotation into the model from the authoritative engine DTO,
2055
+ * with the render `source` the caller decides: `'vector'` when WE authored or
2056
+ * changed the appearance (create / restyle / resize), `'baked'` when the AP is
2057
+ * still authoritative (a move, which preserves it, or a remote edit).
2058
+ * `bumpAp` marks the upsert as confirming an engine /AP re-bake with new
2059
+ * content, so the annotation's `apVersion` — and with it the page's
2060
+ * appearance epoch — advances and the shell fetches the fresh raster.
2061
+ */
2062
+ const syncDTO = (dto, source, bumpAp = false) => {
2063
+ const crop = cropOf(dto.pageObjectNumber);
2064
+ if (crop) apply({
2065
+ t: "upsert",
2066
+ annots: [fromDTO(dto, crop, source)],
2067
+ bumpAp
2068
+ });
2069
+ };
2070
+ /** The one engine-update path, shared by `update` and `updateSelection`. A
2071
+ * programmatic patch changes the appearance → render live (vector). */
2072
+ const updateOne = async (ref, patch) => {
2073
+ const doc = ctx.doc;
2074
+ if (!doc) throw new Error("[annotation] no document bound");
2075
+ const pon = ponForRef(ref);
2076
+ if (pon == null) throw new Error("[annotation] cannot resolve page for ref");
2077
+ const res = await doc.page(pon).annotations.update(ref, patch);
2078
+ syncDTO(res.updated, "vector");
2079
+ };
2080
+ function apply(msg) {
2081
+ const [next, effects] = update(model(), msg);
2082
+ ctx.dispatch({
2083
+ type: "SET_MODEL",
2084
+ model: next
2085
+ });
2086
+ for (const fx of effects) perform(fx, next);
2087
+ }
2088
+ /**
2089
+ * Per-parent serialization of attached-link reconciles: rapid edits chain
2090
+ * instead of interleaving (two overlapping runs could double-create
2091
+ * children). Each run reads the CURRENT model at execution time, so a
2092
+ * chained run converges on the latest desired state.
2093
+ */
2094
+ const linkSyncChains = /* @__PURE__ */ new Map();
2095
+ const scheduleLinkSync = (id) => {
2096
+ const next = (linkSyncChains.get(id) ?? Promise.resolve()).then(() => reconcileLinkChildren(id));
2097
+ linkSyncChains.set(id, next);
2098
+ next.finally(() => {
2099
+ if (linkSyncChains.get(id) === next) linkSyncChains.delete(id);
2100
+ });
2101
+ };
2102
+ /**
2103
+ * 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
2108
+ * inconsistencies heal on the next local edit.
2109
+ */
2110
+ const reconcileLinkChildren = async (id) => {
2111
+ const doc = ctx.doc;
2112
+ const a = model().byId[id];
2113
+ if (!doc || !a || !a.ref || a.subtype === "link") return;
2114
+ const crop = cropOf(a.pon);
2115
+ if (!crop) return;
2116
+ const page = doc.page(a.pon);
2117
+ const target = writableTarget(a.link);
2118
+ const rects = a.link == null ? [] : linkChildRects(a).map((r) => contentToPdfRect(r, crop));
2119
+ const current = a.linkRefs ?? [];
2120
+ const nextRefs = [];
2121
+ try {
2122
+ const paired = Math.min(current.length, rects.length);
2123
+ for (let i = 0; i < paired; i++) {
2124
+ const res = await page.annotations.update(current[i], {
2125
+ subtype: "link",
2126
+ rect: rects[i],
2127
+ ...target ? { target } : {}
2128
+ });
2129
+ nextRefs.push(res.updated.ref);
2130
+ }
2131
+ for (let i = current.length; i < rects.length; i++) {
2132
+ const res = await page.annotations.create({
2133
+ subtype: "link",
2134
+ rect: rects[i],
2135
+ target,
2136
+ inReplyTo: a.ref,
2137
+ replyType: "group"
2138
+ });
2139
+ nextRefs.push(res.created.ref);
2140
+ }
2141
+ for (let i = rects.length; i < current.length; i++) await page.annotations.delete(current[i]);
2142
+ } catch (err) {
2143
+ console.error("[annotation] attached-link sync failed:", err);
2144
+ }
2145
+ const cur = model().byId[id];
2146
+ if (cur) apply({
2147
+ t: "upsert",
2148
+ annots: [{
2149
+ ...cur,
2150
+ linkRefs: nextRefs
2151
+ }]
2152
+ });
2153
+ };
2154
+ /** A relationship-only engine patch (sets/clears `/IRT` + `/RT`) — geometry and
2155
+ * style are left untouched, so grouping never re-bakes an appearance. */
2156
+ const relationshipPatch = (subtype, rel) => ({
2157
+ subtype,
2158
+ ...rel
2159
+ });
2160
+ /** Write a relationship change to one committed annotation and re-sync it from
2161
+ * the authoritative DTO (preserving its render source — relationships don't
2162
+ * change the appearance). */
2163
+ const writeRelationship = async (a, rel) => {
2164
+ const doc = ctx.doc;
2165
+ if (!doc || !a.ref || !a.data) return;
2166
+ const res = await doc.page(a.pon).annotations.update(a.ref, relationshipPatch(a.data.subtype, rel));
2167
+ syncDTO(res.updated, a.source);
2168
+ };
2169
+ /** Committed, data-backed annotations in the current selection. */
2170
+ const selectedCommitted = () => {
2171
+ const m = model();
2172
+ return m.selected.map((id) => m.byId[id]).filter((a) => !!a && !!a.ref && !!a.data);
2173
+ };
2174
+ function perform(fx, m) {
2175
+ const doc = ctx.doc;
2176
+ if (!doc) return;
2177
+ if (fx.fx === "createGroup") {
2178
+ const ids = [fx.primary, ...fx.members];
2179
+ const annots = ids.map((id) => m.byId[id]);
2180
+ const primary = annots[0];
2181
+ if (!primary || annots.some((a) => !a || a.pon !== primary.pon)) {
2182
+ apply({
2183
+ t: "remove",
2184
+ ids
2185
+ });
2186
+ return;
2187
+ }
2188
+ const crop = cropOf(primary.pon);
2189
+ const drafts = crop ? annots.map((a) => a ? toCreateDraft(a, crop) : null) : annots.map(() => null);
2190
+ if (drafts.some((d) => !d)) {
2191
+ apply({
2192
+ t: "remove",
2193
+ ids
2194
+ });
2195
+ return;
2196
+ }
2197
+ (async () => {
2198
+ const committed = [];
2199
+ try {
2200
+ const page = doc.page(primary.pon);
2201
+ const primaryResult = await page.annotations.create(drafts[0]);
2202
+ committed.push({
2203
+ tempId: fx.primary,
2204
+ ref: primaryResult.created.ref
2205
+ });
2206
+ apply({
2207
+ t: "created",
2208
+ tempId: fx.primary,
2209
+ id: refKey(primaryResult.created.ref),
2210
+ ref: primaryResult.created.ref
2211
+ });
2212
+ syncDTO(primaryResult.created, "vector");
2213
+ for (let i = 0; i < fx.members.length; i++) {
2214
+ const tempId = fx.members[i];
2215
+ const draft = {
2216
+ ...drafts[i + 1],
2217
+ inReplyTo: primaryResult.created.ref,
2218
+ replyType: "group"
2219
+ };
2220
+ const result = await page.annotations.create(draft);
2221
+ committed.push({
2222
+ tempId,
2223
+ ref: result.created.ref
2224
+ });
2225
+ apply({
2226
+ t: "created",
2227
+ tempId,
2228
+ id: refKey(result.created.ref),
2229
+ ref: result.created.ref
2230
+ });
2231
+ syncDTO(result.created, "vector");
2232
+ }
2233
+ } catch (error) {
2234
+ const removeIds = ids.filter((id) => !committed.some((c) => c.tempId === id));
2235
+ for (const part of [...committed].reverse()) try {
2236
+ await doc.page(primary.pon).annotations.delete(part.ref);
2237
+ removeIds.push(refKey(part.ref));
2238
+ } catch {}
2239
+ if (removeIds.length) apply({
2240
+ t: "remove",
2241
+ ids: removeIds
2242
+ });
2243
+ console.error("[annotation] grouped annotation creation failed:", error);
2244
+ }
2245
+ })();
2246
+ } else if (fx.fx === "create") {
2247
+ const a = m.byId[fx.id];
2248
+ const crop = a && cropOf(a.pon);
2249
+ const draft = a && crop ? toCreateDraft(a, crop) : null;
2250
+ if (!a || !draft) return;
2251
+ doc.page(a.pon).annotations.create(draft).then((res) => {
2252
+ apply({
2253
+ t: "created",
2254
+ tempId: fx.id,
2255
+ id: refKey(res.created.ref),
2256
+ ref: res.created.ref
2257
+ });
2258
+ syncDTO(res.created, "vector");
2259
+ }, () => apply({
2260
+ t: "createFailed",
2261
+ tempId: fx.id
2262
+ }));
2263
+ } else if (fx.fx === "flags") {
2264
+ const a = m.byId[fx.id];
2265
+ if (!a || !a.ref || !a.data) return;
2266
+ doc.page(a.pon).annotations.update(a.ref, {
2267
+ subtype: a.data.subtype,
2268
+ flags: a.flags
2269
+ }).then((res) => syncDTO(res.updated, a.source), (err) => console.error("[annotation] flags write failed:", err));
2270
+ } else if (fx.fx === "patch") {
2271
+ const a = m.byId[fx.id];
2272
+ const crop = a && cropOf(a.pon);
2273
+ const patch = a && a.ref && crop ? toScopedPatch(a, fx.scope, crop) : null;
2274
+ if (!a || !a.ref || !patch) return;
2275
+ doc.page(a.pon).annotations.update(a.ref, patch).then((res) => {
2276
+ 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(() => {}, () => {});
2281
+ }
2282
+ return {
2283
+ create: async (pon, draft) => {
2284
+ const doc = ctx.doc;
2285
+ if (!doc) throw new Error("[annotation] no document bound");
2286
+ const withFlags = draft.flags ? draft : {
2287
+ ...draft,
2288
+ flags: { print: true }
2289
+ };
2290
+ const res = await doc.page(pon).annotations.create(withFlags);
2291
+ syncDTO(res.created, res.created.subtype === "stamp" ? "baked" : "vector");
2292
+ return res.created.ref;
2293
+ },
2294
+ armStamp,
2295
+ disarmStamp,
2296
+ placeArmedStamp,
2297
+ requestStampAt,
2298
+ placeAt,
2299
+ hasArmedStamp: () => armedStamp != null,
2300
+ ghostHoverAt,
2301
+ clearGhost,
2302
+ setPlacementPreview,
2303
+ clearPlacementPreview: clearGhost,
2304
+ toolGhost: (pon) => {
2305
+ const g = ctx.getState().toolGhost;
2306
+ return g && g.pon === pon ? g : null;
2307
+ },
2308
+ armedStampPreview: () => armedStamp?.preview ?? null,
2309
+ stampArmEpoch: () => ctx.getState().stampArmEpoch,
2310
+ setFilePickerProvider: (provider) => {
2311
+ filePickerProvider = provider;
2312
+ },
2313
+ downloadAttachment: async (ref) => {
2314
+ const doc = ctx.doc;
2315
+ if (!doc) throw new Error("[annotation] no document bound");
2316
+ const pon = ponForRef(ref);
2317
+ if (pon == null) throw new Error("[annotation] cannot resolve page for ref");
2318
+ const annotations = doc.page(pon).annotations;
2319
+ if (!annotations.downloadFile) throw new Error("[annotation] this engine does not support attachment download");
2320
+ return annotations.downloadFile(ref);
2321
+ },
2322
+ tools: () => [...registry.values()],
2323
+ tool: (id) => registry.get(id) ?? null,
2324
+ toolSubtype: (id) => registry.get(id)?.subtype ?? id,
2325
+ registerTool: (def) => {
2326
+ const resolved = buildToolRegistry([...configTools, def]).get(def.id);
2327
+ if (!resolved) throw new Error(`[annotation] could not resolve tool '${def.id}'`);
2328
+ registry.set(resolved.id, resolved);
2329
+ const un = ctx.tryGet(InteractionToken)?.registerTool({
2330
+ id: resolved.id,
2331
+ cursor: resolved.cursor,
2332
+ enables: resolved.enables
2333
+ });
2334
+ if (resolved.defaults) apply({
2335
+ t: "setDefaults",
2336
+ subtype: resolved.preset,
2337
+ patch: resolved.defaults
2338
+ });
2339
+ return () => {
2340
+ registry.delete(resolved.id);
2341
+ un?.();
2342
+ };
2343
+ },
2344
+ update: (ref, patch) => updateOne(ref, patch),
2345
+ updateSelection: (patch) => apply({
2346
+ t: "setProps",
2347
+ patch
2348
+ }),
2349
+ updateSelectionFlags: (patch) => apply({
2350
+ t: "setFlags",
2351
+ patch
2352
+ }),
2353
+ getSelectionFlags: () => memoSelectionFlags(),
2354
+ delete: async (ref) => {
2355
+ const doc = ctx.doc;
2356
+ if (!doc) throw new Error("[annotation] no document bound");
2357
+ const pon = ponForRef(ref);
2358
+ if (pon == null) throw new Error("[annotation] cannot resolve page for ref");
2359
+ await doc.page(pon).annotations.delete(ref);
2360
+ apply({
2361
+ t: "remove",
2362
+ ids: [refKey(ref)]
2363
+ });
2364
+ },
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,
2368
+ getSelection: () => {
2369
+ const m = model();
2370
+ return m.selected.map((id) => m.byId[id]?.ref).filter((r) => r != null);
2371
+ },
2372
+ get: (ref) => model().byId[refKey(ref)]?.data ?? null,
2373
+ list: (pon) => {
2374
+ const m = model();
2375
+ return m.order.map((id) => m.byId[id]).filter((a) => a?.pon === pon).map((a) => a?.data).filter((d) => d != null);
2376
+ },
2377
+ getSelected: () => {
2378
+ const m = model();
2379
+ return m.selected.map((id) => m.byId[id]?.data).filter((d) => d != null);
2380
+ },
2381
+ getSelectionProps: () => memoSelectionProps(),
2382
+ propsForTool: (toolId) => propsFor(registry.get(toolId)?.propsKind ?? toolId),
2383
+ pageItems: (pon, view) => memoItems(pon, view),
2384
+ chrome: (pon, scale, rotation, zoom) => memoChrome(pon, scale, rotation, zoom),
2385
+ selectionAnchor: (scale, rotation, zoom) => memoAnchor(scale, rotation, zoom),
2386
+ creationDraftAnchor: () => memoDraftAnchor(),
2387
+ selection: () => model().selected,
2388
+ hitKind: (pon, point, scale, rotation, zoom) => hitTest(model(), pon, point, chromeGeomAt(scale), model().hitMargin, pageBoxOf(pon), inertIdsAt(pon), viewEnv(zoom, rotation)).t,
2389
+ cursorAt: (pon, point, scale, rotation, zoom) => cursorAt(model(), pon, point, chromeGeomAt(scale), model().hitMargin, pageBoxOf(pon), inertIdsAt(pon), viewEnv(zoom, rotation)),
2390
+ hoverAt: (at) => {
2391
+ const m = model();
2392
+ let id = null;
2393
+ if (at) {
2394
+ const h = hitTest(m, at.pon, at.point, chromeGeomAt(at.scale), m.hitMargin, pageBoxOf(at.pon), inertIdsAt(at.pon), viewEnv(at.zoom, at.rotation));
2395
+ if (h.t === "annot") id = h.id;
2396
+ }
2397
+ if (m.hovered !== id) apply({
2398
+ t: "hover",
2399
+ id
2400
+ });
2401
+ },
2402
+ behaviorFor: (a) => behaviors.find((b) => b.matches(a) && b.engaged()) ?? null,
2403
+ linkItemsOn: (pon) => memoLinkItems(pon),
2404
+ appearanceEpoch: (pon) => {
2405
+ const m = model();
2406
+ const parts = [];
2407
+ for (const id of m.order) {
2408
+ const a = m.byId[id];
2409
+ if (!a || a.pon !== pon || a.source !== "baked" || !a.ref) continue;
2410
+ parts.push(`${id}@${a.apVersion ?? 0}`);
2411
+ }
2412
+ return parts.sort().join("|");
2413
+ },
2414
+ bakeScale: (renderScale) => snapAppearanceScale(ctx.document()?.renderPolicy ?? CONTINUOUS_RENDER_POLICY, renderScale),
2415
+ appearances: (pon, scale, signal) => {
2416
+ const doc = ctx.doc;
2417
+ if (!doc) return Promise.resolve([]);
2418
+ const task = doc.page(pon).annotations.renderAppearanceImages({ scale });
2419
+ if (signal) if (signal.aborted) task.abort(signal.reason);
2420
+ else signal.addEventListener("abort", () => task.abort(signal.reason), { once: true });
2421
+ return task.then((r) => r.appearances, () => []);
2422
+ },
2423
+ toContentBox: (pon, rect) => {
2424
+ const crop = cropOf(pon);
2425
+ return crop ? pdfToContentRect(rect, crop) : null;
2426
+ },
2427
+ editPointer: (phase, pon, point, shift, scale, rotation, zoom) => apply({
2428
+ t: "editPointer",
2429
+ phase,
2430
+ in: {
2431
+ pon,
2432
+ point,
2433
+ shift,
2434
+ pageBox: pageBoxOf(pon),
2435
+ chrome: chromeGeomAt(scale),
2436
+ inert: inertIdsAt(pon),
2437
+ ...zoom != null ? { zoom } : {},
2438
+ ...rotation != null ? { displayRotation: rotation } : {}
2439
+ }
2440
+ }),
2441
+ marqueePointer: (phase, pon, point, shift, scale, rotation, zoom) => apply({
2442
+ t: "marqueePointer",
2443
+ phase,
2444
+ in: {
2445
+ pon,
2446
+ point,
2447
+ shift,
2448
+ pageBox: pageBoxOf(pon),
2449
+ inert: inertIdsAt(pon),
2450
+ ...zoom != null ? { zoom } : {},
2451
+ ...rotation != null ? { displayRotation: rotation } : {}
2452
+ }
2453
+ }),
2454
+ createPointer: (tool, phase, pon, point, finish = false, displayRotation) => {
2455
+ const t = registry.get(tool);
2456
+ apply({
2457
+ t: "createPointer",
2458
+ phase,
2459
+ subtype: t?.subtype ?? tool,
2460
+ preset: t?.preset ?? tool,
2461
+ intent: t?.intent,
2462
+ clickCreate: t?.clickCreate,
2463
+ flags: t?.flags,
2464
+ deferInkCommit: (t?.ink?.groupStrokesMs ?? 0) > 0,
2465
+ straightenInk: t?.ink?.straighten,
2466
+ in: {
2467
+ pon,
2468
+ point,
2469
+ shift: false,
2470
+ finish,
2471
+ pageBox: pageBoxOf(pon),
2472
+ displayRotation,
2473
+ upright: t?.upright
2474
+ }
2475
+ });
2476
+ },
2477
+ finishCreationDraft: () => apply({ t: "finishCreationDraft" }),
2478
+ finishInkDraft: () => apply({ t: "finishInkDraft" }),
2479
+ 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({
2501
+ t: "setMarkupPreview",
2502
+ subtype,
2503
+ rectsByPage,
2504
+ preset
2505
+ }),
2506
+ clearMarkupPreview: () => apply({ t: "clearMarkupPreview" }),
2507
+ setDefaults: (subtype, patch) => apply({
2508
+ t: "setDefaults",
2509
+ subtype,
2510
+ patch
2511
+ }),
2512
+ currentDefaults: (toolId) => defaultsFor(model(), registry.get(toolId)?.preset ?? toolId),
2513
+ setSnap: (patch) => apply({
2514
+ t: "setSnap",
2515
+ patch
2516
+ }),
2517
+ snapSettings: () => model().snap,
2518
+ setChrome: (patch) => ctx.dispatch({
2519
+ type: "SET_CHROME",
2520
+ patch
2521
+ }),
2522
+ chromeSettings: () => chromeSettings(),
2523
+ deleteSelection: () => apply({ t: "delete" }),
2524
+ deselect: () => apply({ t: "deselect" }),
2525
+ select: (ref, options) => {
2526
+ apply({
2527
+ t: "select",
2528
+ ids: [refKey(ref)],
2529
+ add: options?.add
2530
+ });
2531
+ },
2532
+ pruneEngagedSelection: () => {
2533
+ const m = model();
2534
+ const drop = m.selected.filter((id) => {
2535
+ const a = m.byId[id];
2536
+ return a && behaviors.some((b) => b.matches({
2537
+ subtype: a.subtype,
2538
+ ref: a.ref
2539
+ }) && b.engaged());
2540
+ });
2541
+ if (drop.length) apply({
2542
+ t: "deselect",
2543
+ ids: drop
2544
+ });
2545
+ },
2546
+ cancel: () => apply({ t: "cancel" }),
2547
+ rotateSelection90: () => apply({ t: "rotate90" }),
2548
+ resetSelectionRotation: () => apply({ t: "resetRotation" }),
2549
+ /** Group the current selection into one unit: the bottom-most member becomes
2550
+ * the primary, every other member becomes a `/RT /Group` subordinate of it.
2551
+ * No-op unless 2+ committed annotations on a single page are selected. */
2552
+ group: async () => {
2553
+ const m = model();
2554
+ const members = selectedCommitted();
2555
+ if (members.length < 2) return;
2556
+ const pon = members[0].pon;
2557
+ if (members.some((a) => a.pon !== pon)) return;
2558
+ const [primary, ...rest] = [...members].sort((a, b) => m.order.indexOf(a.id) - m.order.indexOf(b.id));
2559
+ if (!primary.ref) return;
2560
+ await Promise.all(rest.map((a) => writeRelationship(a, {
2561
+ inReplyTo: primary.ref,
2562
+ replyType: "group"
2563
+ })));
2564
+ },
2565
+ /** Ungroup: clear `/IRT` (+ `/RT`) on every subordinate in the group(s) the
2566
+ * selection touches, so each member becomes top-level again. */
2567
+ ungroup: async () => {
2568
+ const m = model();
2569
+ const subs = expandGroups(m, m.selected).map((id) => m.byId[id]).filter((a) => !!a && !!a.ref && !!a.data && !!a.group);
2570
+ await Promise.all(subs.map((a) => writeRelationship(a, { inReplyTo: null })));
2571
+ },
2572
+ canGroup: () => {
2573
+ if (!(ctx.doc?.security.allows(ANNOTATE_MODIFY) ?? false)) return false;
2574
+ const m = model();
2575
+ const members = selectedCommitted();
2576
+ if (members.length < 2) return false;
2577
+ if (members.some((a) => a.pon !== members[0].pon)) return false;
2578
+ const keys = new Set(m.selected.map((id) => groupKeyOf(m, id)));
2579
+ if (keys.size === 1 && !keys.has(null)) return false;
2580
+ return true;
2581
+ },
2582
+ canUngroup: () => {
2583
+ if (!(ctx.doc?.security.allows(ANNOTATE_MODIFY) ?? false)) return false;
2584
+ const m = model();
2585
+ return m.selected.some((id) => groupKeyOf(m, id) != null);
2586
+ },
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
+ });
2599
+ },
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);
2619
+ }
2620
+ },
2621
+ textItems: (pon, view) => memoTexts(pon, view),
2622
+ currentEditing: () => model().editing,
2623
+ beginTextEdit: (ref) => apply({
2624
+ t: "beginTextEdit",
2625
+ id: refKey(ref)
2626
+ }),
2627
+ beginTextEditAt: (pon, point, scale, rotation, zoom) => {
2628
+ const m = model();
2629
+ const h = hitTest(m, pon, point, chromeGeomAt(scale), m.hitMargin, pageBoxOf(pon), void 0, viewEnv(zoom, rotation));
2630
+ 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
+ });
2635
+ },
2636
+ setContents: (ref, text) => {
2637
+ apply({
2638
+ t: "setText",
2639
+ id: refKey(ref),
2640
+ text
2641
+ });
2642
+ const key = refKey(ref);
2643
+ clearTimeout(textTimers.get(key));
2644
+ textTimers.set(key, setTimeout(() => {
2645
+ textTimers.delete(key);
2646
+ const pon = ponForRef(ref);
2647
+ if (pon != null) ctx.doc?.page(pon).annotations.update(ref, {
2648
+ subtype: "free-text",
2649
+ contents: text
2650
+ }).then(() => {}, () => {});
2651
+ }, TEXT_COMMIT_DEBOUNCE_MS));
2652
+ },
2653
+ endTextEdit: () => {
2654
+ for (const t of textTimers.values()) clearTimeout(t);
2655
+ const id = model().editing;
2656
+ const a = id ? model().byId[id] : null;
2657
+ if (a?.ref) {
2658
+ const pon = ponForRef(a.ref);
2659
+ const text = a.data?.contents ?? "";
2660
+ if (pon != null) ctx.doc?.page(pon).annotations.update(a.ref, {
2661
+ subtype: "free-text",
2662
+ contents: text
2663
+ }).then(() => {}, () => {});
2664
+ }
2665
+ textTimers.clear();
2666
+ apply({ t: "endTextEdit" });
2667
+ },
2668
+ registerBehavior: (b) => {
2669
+ behaviors.push(b);
2670
+ return () => {
2671
+ const i = behaviors.indexOf(b);
2672
+ if (i >= 0) behaviors.splice(i, 1);
2673
+ };
2674
+ }
2675
+ };
2676
+ }
2677
+ //#endregion
2678
+ export { toCreateDraft as a, styleFromDTO as c, fromDTO as i, widgetAppearanceFromProps as l, DEFAULT_TOOLS as n, toPatch as o, buildToolRegistry as r, refKey as s, createAnnotationCapability as t, AnnotationToken as u };
2679
+
2680
+ //# sourceMappingURL=capability-fghl0JmE.js.map