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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/{capability-DVx1NmTe.cjs → capability-Bt320iJP.cjs} +211 -26
  2. package/dist/capability-Bt320iJP.cjs.map +1 -0
  3. package/dist/{capability-B_dkoB1K.js → capability-CoL4na7g.js} +213 -28
  4. package/dist/capability-CoL4na7g.js.map +1 -0
  5. package/dist/contract/host.d.cts +2 -2
  6. package/dist/contract/host.d.ts +2 -2
  7. package/dist/{contract-CgAcfAE7.d.cts → contract-C7rcuNp_.d.cts} +2 -2
  8. package/dist/{contract-CiG2_HBq.d.ts → contract-CM2Ovdq_.d.ts} +2 -2
  9. package/dist/contract.cjs +1 -1
  10. package/dist/contract.d.cts +2 -2
  11. package/dist/contract.d.ts +2 -2
  12. package/dist/contract.js +1 -1
  13. package/dist/index.cjs +17 -3
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +2 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +17 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal.cjs +2 -2
  20. package/dist/internal.d.cts +2 -2
  21. package/dist/internal.d.ts +2 -2
  22. package/dist/internal.js +2 -2
  23. package/dist/{tools-0NI3b9z8.cjs → tools-B3i1gjRs.cjs} +54 -1
  24. package/dist/tools-B3i1gjRs.cjs.map +1 -0
  25. package/dist/{tools-DBEYx3-c.js → tools-DsfqBn_a.js} +54 -1
  26. package/dist/tools-DsfqBn_a.js.map +1 -0
  27. package/dist/types-BvB1vUKb.js.map +1 -1
  28. package/dist/{types-ObcPYc3-.d.cts → types-ChRnKozg.d.cts} +52 -6
  29. package/dist/types-Dlhdw2k_.cjs.map +1 -1
  30. package/dist/{types-D5V0TIwk.d.ts → types-Dmz3eRSG.d.ts} +52 -6
  31. package/package.json +9 -9
  32. package/dist/capability-B_dkoB1K.js.map +0 -1
  33. package/dist/capability-DVx1NmTe.cjs.map +0 -1
  34. package/dist/tools-0NI3b9z8.cjs.map +0 -1
  35. package/dist/tools-DBEYx3-c.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["AnnotationToken","AnnotationToken","PublicActionsToken","ActionsHostToken"],"sources":["../src/effects.ts","../src/handler.ts","../src/markup.ts","../src/reducer.ts","../src/annotation.plugin.ts"],"sourcesContent":["/**\n * Makes annotations REACTIVE to the document event stream.\n *\n * Own edits already update the model through the capability's promise path\n * (optimistic draw + re-sync from the engine DTO). This effect wires in\n * everything else:\n *\n * - it kicks whole-document hydration exactly once (`ensureHydrated`);\n * - remote `annotation.*` events are handed to the capability's\n * hydration-aware delivery (queued + cursor-replayed during the\n * hydration window, applied live otherwise);\n * - `stream.desynced` re-runs hydration — the gap's mutations will never\n * arrive as events;\n * - remote page inserts/deletes reconcile the model's page membership;\n * - form and redaction cross-plane events invalidate exactly what they\n * touched, as before.\n */\nimport type { DocumentEvent, EffectContext } from '@embedpdf/core';\nimport { encodeStableIdKey } from '@embedpdf/engine-core/runtime';\nimport { update, type Msg } from '@embedpdf/core-annotation';\n\nimport { AnnotationToken } from './types';\nimport type { AnnotationAction, AnnotationState } from './types';\n\nexport function registerAnnotationEffects(\n ctx: EffectContext<AnnotationState, AnnotationAction>,\n): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n const host = () => ctx.get(AnnotationToken);\n\n const apply = (msg: Msg): void => {\n const [next] = update(ctx.getState().model, msg);\n ctx.dispatch({ type: 'SET_MODEL', model: next });\n };\n\n // Subscribe FIRST, then hydrate: the coherence protocol needs every event\n // from the snapshot's cursor onward to be observed (queued or applied) —\n // a gap between snapshot and stream is exactly the resurrection bug.\n const unsubscribe = doc.events.subscribe((event: DocumentEvent) => {\n // Widget appearances are re-baked by FORM value writes — a plane this\n // plugin doesn't own, so no remote-only filter: direct fills emit\n // `form.valueChanged`, while scripted batches emit `form.effectsApplied`.\n // Neither path touches this model directly. The bump tells the render\n // layer to re-fetch exactly the repainted widgets' rasters.\n if (event.type === 'form.valueChanged' || event.type === 'form.effectsApplied') {\n apply({\n t: 'bumpAp',\n ids: event.changedWidgets\n .filter((w) => w.annotObjectNumber > 0)\n .map((w) => encodeStableIdKey({ kind: 'objectNumber', value: w.annotObjectNumber })),\n });\n return;\n }\n // A field update re-bakes its widgets too — a signature field's visual\n // fill (`forms.setSignatureAppearance`) draws a mark into the widget\n // without a value write, so it arrives as `form.fieldUpdated`.\n if (event.type === 'form.fieldUpdated') {\n apply({\n t: 'bumpAp',\n ids: event.field.widgets\n .filter((w) => w.annotObjectNumber > 0)\n .map((w) => encodeStableIdKey({ kind: 'objectNumber', value: w.annotObjectNumber })),\n });\n return;\n }\n // A completed signature sealed the field with its mark as appearance:\n // repaint that widget. The session also moved to new bytes\n // (`document.versioned` follows); a remote completion arrives as that\n // event alone, so the versioned handler below re-reads the plane.\n if (event.type === 'signature.completed') {\n const widget = event.signature.widget;\n if (widget && widget.annotObjectNumber > 0) {\n apply({\n t: 'bumpAp',\n ids: [encodeStableIdKey({ kind: 'objectNumber', value: widget.annotObjectNumber })],\n });\n }\n return;\n }\n if (event.type === 'document.versioned') {\n void host().rehydrate();\n return;\n }\n // A redaction apply deleted the consumed marks plus every intersecting\n // annotation on the applied pages. ORIGIN-AGNOSTIC: our own applies run\n // through the redaction plugin's doc-level verb, never this plane's\n // capability paths, so the model is stale either way — reload the\n // affected pages from the engine. (Works without plugin-redaction\n // installed: a remote collaborator's apply still reconciles this view.)\n if (event.type === 'redaction.applied') {\n const affected = new Set(\n event.results.filter((r) => r.status === 'applied').map((r) => r.pageObjectNumber),\n );\n if (affected.size) {\n for (const pon of affected) void host().reloadPage(pon);\n }\n return;\n }\n // The live stream fell too far behind to replay — the gap's mutations\n // will NEVER arrive as events, so re-read the whole document.\n if (event.type === 'stream.desynced') {\n void host().rehydrate();\n return;\n }\n // Page membership changed. ORIGIN-AGNOSTIC like redaction: removing a\n // deleted page's annotations twice is a no-op, and reloading an\n // inserted page merges idempotently — correct whether the op was ours\n // or a collaborator's.\n if (event.type === 'pages.deleted') {\n const gone = new Set<number>(event.pageObjectNumbers);\n const m = ctx.getState().model;\n const ids = m.order.filter((id) => {\n const a = m.byId[id];\n return a !== undefined && gone.has(a.pon);\n });\n if (ids.length) apply({ t: 'remove', ids });\n return;\n }\n if (event.type === 'pages.inserted') {\n // A fresh page usually has no annotations, but an insert-from-bytes\n // can carry them. `reloadPage` no-ops harmlessly if the layout for\n // the new page hasn't landed in core state yet (crop unknown).\n for (const pon of event.insertedPageObjectNumbers) void host().reloadPage(pon);\n return;\n }\n // Only fold in OTHER sessions' edits; our own flow through the capability.\n if (!('origin' in event) || event.origin.kind !== 'remote') return;\n switch (event.type) {\n case 'annotation.created':\n case 'annotation.updated':\n case 'annotation.moved':\n case 'annotation.deleted':\n host().deliverRemoteAnnotationEvent(event);\n break;\n default:\n break;\n }\n });\n ctx.cleanup(unsubscribe);\n\n host().ensureHydrated();\n}\n","import type {\n InteractionCapability,\n InteractionHandler,\n PointerSample,\n} from '@embedpdf/plugin-interaction/contract';\nimport type { PageRotation } from '@embedpdf/core-geometry';\nimport type { Subtype, Vec } from '@embedpdf/core-annotation';\nimport type { AnnotationHostCapability } from './types';\nimport {\n ANNOTATION_DRAW_PRIORITY,\n ANNOTATION_EDIT_PRIORITY,\n ANNOTATION_GHOST_PRIORITY,\n ANNOTATION_MARQUEE_PRIORITY,\n ANNOTATION_PLACE_PRIORITY,\n} from './priorities';\n\nconst MARQUEE_DRAG_THRESHOLD_PX = 4;\nconst isPolyTool = (subtype: Subtype): boolean => subtype === 'polygon' || subtype === 'polyline';\nconst isCalloutTool = (subtype: Subtype): boolean => subtype === 'free-text-callout';\n\n/**\n * Resolve a sample against a gesture's HOME page. Annotation gestures are\n * page-anchored: they track the page they started on, even when the cursor\n * wanders off it — `s.page` re-resolves per event (a page-2 point is a\n * DIFFERENT coordinate frame, the teleport bug), so prefer the source's\n * unclamped projection and fall back to the page hit only when it's the same\n * page. Null → this sample can't speak for the home page; ignore it.\n */\nconst pointOn = (s: PointerSample, pon: number): Vec | null =>\n s.project?.(pon) ?? (s.page?.pon === pon ? s.page.point : null);\n\n/**\n * Click-to-place for every payload-carrying tool (stamp / note / file\n * attachment). Each click places one annotation centred on the point (the\n * tool stays active for repeat placement — v2 rubber-stamp behaviour); the\n * capability's `placeAt` routes by the active tool's kind: armed stamp bytes,\n * a stamp/attachment prompt (pick the spot first, the payload second), or an\n * immediate note. No drag gesture — placement size comes from the content\n * (stamp aspect / the fixed icon box), not the pointer.\n *\n * Priority is deliberately BELOW the edit handler (100): a click over an\n * EXISTING annotation selects it; placement happens on empty page space.\n */\nexport function createPlaceHandler(anno: AnnotationHostCapability): InteractionHandler {\n return {\n id: 'annotation-place',\n // `annotation-stamp` is honoured as a legacy alias for embedder tool\n // configs written before the tags were unified.\n priority: ANNOTATION_PLACE_PRIORITY,\n enabledFor: (t) => t.enables.has('annotation-place') || t.enables.has('annotation-stamp'),\n onDown: (s) => {\n if (!s.page) return false;\n // The click sample's display rotation drives the tool's `upright` policy —\n // the placement lands reading horizontally on a rotated page/view.\n return anno.placeAt(s.page.pon, s.page.point, s.page.rotation);\n },\n };\n}\n\n/**\n * The armed tool's FOOTPRINT ghost: every hover re-computes the would-be\n * placement under the cursor (stamp image fit / click-create default geometry);\n * off-page clears it. One handler for every tool — `ghostHoverAt` resolves the\n * tool's ghost policy and clears when it isn't `footprint`. Never captures:\n * the highest priority makes its onDown run FIRST on every press (hiding the\n * ghost while a gesture runs), then declines so the real handlers route.\n */\nexport function createGhostHandler(\n anno: AnnotationHostCapability,\n interaction: InteractionCapability,\n): InteractionHandler {\n const hover = (s: PointerSample): void => {\n if (s.page)\n anno.ghostHoverAt(interaction.activeToolId(), s.page.pon, s.page.point, s.page.rotation);\n else anno.clearGhost();\n };\n return {\n id: 'annotation-ghost',\n priority: ANNOTATION_GHOST_PRIORITY,\n enabledFor: () => true,\n onDown: () => {\n anno.clearGhost();\n return false;\n },\n onHover: hover,\n };\n}\n\n/**\n * Ambient editing: live under the `annotation-edit` tag, which BOTH the pointer\n * and pan tools enable — so you select/move/resize in any navigation mode (Adobe\n * behaviour). It captures only over an annotation/handle; over empty it\n * deselects and declines (so text-selection / pan still work). Hover drives the\n * cursor (move / pointer / resize) via a priority cursor claim.\n */\nexport function createEditHandler(\n anno: AnnotationHostCapability,\n interaction: InteractionCapability,\n): InteractionHandler {\n // The gesture's home page + last resolved point, armed on down. Every\n // move/up resolves against THIS page (the annotation slides along its edge\n // when the cursor overshoots — the core clamps), never against whatever\n // page the sample happens to hit. `downPoint` keeps the untouched origin so\n // an aborted gesture (second finger → pinch) can REVERT instead of leaving\n // the annotation half-moved. `touch` rides the whole gesture so every phase\n // grabs with the same finger-sized zones the down resolved with.\n let origin: { pon: number; point: Vec; downPoint: Vec; touch: boolean } | null = null;\n return {\n id: 'annotation-edit',\n priority: ANNOTATION_EDIT_PRIORITY,\n enabledFor: (t) => t.enables.has('annotation-edit'),\n // Touch consent: a finger owns a tool drag only over the selection's own\n // chrome or a selected annotation's body (see claimsTouchAt) — unselected\n // bodies keep scrolling, matching the Markup convention.\n claimsTouch: (s) =>\n !!s.page &&\n anno.claimsTouchAt(s.page.pon, s.page.point, s.page.scale, s.page.rotation, s.page.zoom),\n onDown: (s) => {\n if (!s.page) return false;\n const touch = s.pointerType === 'touch';\n // While a free-text box is being edited it owns its own pointer events, so a\n // down that reaches the hub at all is a click OUTSIDE the editor — commit and\n // leave text edit. This makes exit hub-driven (deterministic) rather than\n // relying on a DOM blur, which races the focus-steal of the entering gesture.\n const wasEditing = anno.currentEditing() != null;\n if (wasEditing) anno.endTextEdit();\n if (\n anno.hitKind(\n s.page.pon,\n s.page.point,\n s.page.scale,\n s.page.rotation,\n s.page.zoom,\n touch,\n ) === 'empty'\n ) {\n // Plain empty click drops the selection. Shift-empty preserves it so the\n // lower-priority marquee handler can additive/toggle-select.\n if (!s.modifiers.shift) anno.deselect();\n // A click that dismissed an active edit is CONSUMED: its sole job was to\n // leave edit mode, so the draw tool doesn't also spawn a new annotation\n // (matches v2). Only when nothing was being edited do we decline, letting\n // pan / text-selection / draw act on the empty click.\n return wasEditing;\n }\n // Double-click / long-press over a FREE-TEXT box → enter text edit (not\n // a move). Over any other annotation the attempt reports false and the\n // press falls through to the normal path below (select / arm a move) —\n // so a long-press on a highlight selects it instead of the gesture\n // being swallowed by a no-op edit attempt.\n if ((s.clickCount ?? 1) >= 2) {\n if (\n anno.beginTextEditAt(s.page.pon, s.page.point, s.page.scale, s.page.rotation, s.page.zoom)\n ) {\n return true;\n }\n }\n anno.editPointer(\n 'down',\n s.page.pon,\n s.page.point,\n s.modifiers.shift,\n s.page.scale,\n s.page.rotation,\n s.page.zoom,\n touch,\n );\n origin = { pon: s.page.pon, point: s.page.point, downPoint: s.page.point, touch };\n return true;\n },\n onMove: (s) => {\n if (!origin) return;\n const point = pointOn(s, origin.pon);\n if (!point) return;\n origin.point = point;\n // The sample's scale/rotation ride along (uniform across pages), so a\n // screen-anchored member page-clamps at its effective footprint mid-drag.\n anno.editPointer(\n 'move',\n origin.pon,\n point,\n s.modifiers.shift,\n s.page?.scale,\n s.page?.rotation,\n s.page?.zoom,\n origin.touch,\n );\n },\n onUp: (s) => {\n if (!origin) return;\n // ALWAYS close the gesture — a release over a page gap or outside the\n // window must still commit (a dangling draft leaves a ghost that snaps\n // back on the next interaction). `editUp` doesn't read the point.\n anno.editPointer(\n 'up',\n origin.pon,\n pointOn(s, origin.pon) ?? origin.point,\n false,\n undefined,\n undefined,\n undefined,\n origin.touch,\n );\n origin = null;\n },\n onCancel: () => {\n if (!origin) return;\n // Aborted (second finger → pinch): REVERT, don't commit — replay the\n // gesture back to its own down point (every transform is delta-from-\n // down, so this restores the original geometry), then close it there.\n const { pon, downPoint, touch } = origin;\n origin = null;\n anno.editPointer('move', pon, downPoint, false, undefined, undefined, undefined, touch);\n anno.editPointer('up', pon, downPoint, false, undefined, undefined, undefined, touch);\n },\n onHover: (s) => {\n // priority 20 → beats text-select's 'text' (10) over an annotation; null clears.\n interaction.setCursor(\n 'annotation',\n s.page\n ? anno.cursorAt(s.page.pon, s.page.point, s.page.scale, s.page.rotation, s.page.zoom)\n : null,\n 20,\n );\n // Hover state for scene affordances (redact preview) — rides the same\n // per-move sample; the capability dispatches on change only.\n anno.hoverAt(\n s.page\n ? {\n pon: s.page.pon,\n point: s.page.point,\n scale: s.page.scale,\n rotation: s.page.rotation,\n zoom: s.page.zoom,\n }\n : null,\n );\n },\n };\n}\n\n/**\n * Empty-page drag selection. Lower priority than annotation edit and text\n * selection, so it only owns drags that begin on empty, non-text page space.\n */\nexport function createMarqueeHandler(anno: AnnotationHostCapability): InteractionHandler {\n let anchor: {\n pon: number;\n point: Vec;\n vx: number;\n vy: number;\n shift: boolean;\n } | null = null;\n let last: { pon: number; point: Vec } | null = null;\n let dragging = false;\n\n // The marquee page's view env at the down sample — the `up` intersects\n // screen-anchored annotations at their effective footprint with it.\n let view: { scale?: number; rotation?: PageRotation; zoom?: number } = {};\n return {\n id: 'annotation-marquee',\n priority: ANNOTATION_MARQUEE_PRIORITY,\n enabledFor: (t) => t.enables.has('annotation-marquee'),\n onDown: (s) => {\n if (!s.page) return false;\n anchor = {\n pon: s.page.pon,\n point: s.page.point,\n vx: s.viewport.x,\n vy: s.viewport.y,\n shift: s.modifiers.shift,\n };\n last = { pon: s.page.pon, point: s.page.point };\n view = { scale: s.page.scale, rotation: s.page.rotation, zoom: s.page.zoom };\n dragging = false;\n return true;\n },\n onMove: (s) => {\n if (!anchor) return;\n // Anchored to the page the drag started on; the projected point keeps the\n // marquee growing along the page edge when the cursor overshoots (the\n // core clamps it to the page box).\n const point = pointOn(s, anchor.pon);\n if (!point) return;\n last = { pon: anchor.pon, point };\n if (!dragging) {\n if (\n Math.hypot(s.viewport.x - anchor.vx, s.viewport.y - anchor.vy) < MARQUEE_DRAG_THRESHOLD_PX\n ) {\n return;\n }\n dragging = true;\n anno.marqueePointer(\n 'down',\n anchor.pon,\n anchor.point,\n anchor.shift,\n view.scale,\n view.rotation,\n view.zoom,\n );\n }\n anno.marqueePointer(\n 'move',\n anchor.pon,\n point,\n anchor.shift,\n view.scale,\n view.rotation,\n view.zoom,\n );\n },\n onUp: () => {\n if (dragging && anchor && last) {\n anno.marqueePointer(\n 'up',\n anchor.pon,\n last.point,\n anchor.shift,\n view.scale,\n view.rotation,\n view.zoom,\n );\n }\n anchor = null;\n last = null;\n dragging = false;\n },\n onCancel: () => {\n // Abort WITHOUT committing a selection; the core's cancel message clears\n // the marquee draft so no rectangle lingers. (Touch can't currently\n // reach the marquee — this is symmetry and future-proofing.)\n anchor = null;\n last = null;\n dragging = false;\n anno.cancelCreationDraft();\n },\n };\n}\n\n/** Drawing: live under `annotation-draw` (the square / circle / line tools). */\nexport function createDrawHandler(\n anno: AnnotationHostCapability,\n interaction: InteractionCapability,\n): InteractionHandler {\n // The active tool id + its ROUTING subtype. The id is what `createPointer` takes\n // (it resolves the defaults preset — arrow vs line); the subtype is what the\n // poly/callout gesture checks read (arrow → `line`, a polygon preset → `polygon`).\n const toolId = () => interaction.activeToolId();\n const subtypeOf = (id: string): Subtype => anno.toolSubtype(id);\n let drawingPoly = false;\n // A callout is mid-creation between its tip/knee/box clicks; while it is, hover\n // (no button) must still drive the leader/box preview, like a poly's vertices.\n let drawingCallout = false;\n // The active drag's home page (down→up): moves/ups resolve against it, so a\n // shape keeps sizing along the page edge when the cursor overshoots.\n let origin: { pon: number; point: Vec } | null = null;\n let pendingInk: {\n tool: string;\n pon: number;\n timer: ReturnType<typeof setTimeout>;\n } | null = null;\n const flushPendingInk = () => {\n if (!pendingInk) return;\n clearTimeout(pendingInk.timer);\n pendingInk = null;\n anno.finishInkDraft();\n };\n interaction.onToolChange(() => {\n flushPendingInk();\n drawingPoly = false;\n drawingCallout = false;\n origin = null;\n });\n return {\n id: 'annotation-draw',\n // The gesture cascade — relative order IS the composition policy:\n // 100 annotation-edit existing annotations own their gestures\n // 95 annotation-place click-to-place armed tools\n // 60 text-select claims TEXT ONLY (self-gates via isOverText,\n // yields everywhere else)\n // 55 annotation-draw drag-create takes whatever text didn't claim\n // 50 annotation-marquee empty-space selection, the final fallback\n // Draw sits BELOW text-select so a COMPOSED tool (redact: text-select +\n // annotation-draw) lets text claim text — over a paragraph the drag makes\n // per-line quad marks, anywhere else it drag-creates. Priority only breaks\n // ties between simultaneously-ELIGIBLE handlers, and draw-only tools\n // (square/ink/arrow…) never co-enable text-select, so they are unaffected.\n priority: ANNOTATION_DRAW_PRIORITY,\n enabledFor: (t) => t.enables.has('annotation-draw'),\n onDown: (s) => {\n if (!s.page) return false;\n const tool = toolId();\n const st = subtypeOf(tool);\n if (st === 'ink' && pendingInk) {\n if (pendingInk.tool === tool && pendingInk.pon === s.page.pon) {\n clearTimeout(pendingInk.timer);\n pendingInk = null;\n } else {\n flushPendingInk();\n }\n }\n // A down is a fresh intent — it may legitimately start on another page\n // (the core restarts the draft there), so it re-anchors the gesture.\n origin = { pon: s.page.pon, point: s.page.point };\n if (isPolyTool(st)) {\n const finish = (s.clickCount ?? 1) >= 2;\n anno.createPointer(tool, 'down', s.page.pon, s.page.point, finish);\n drawingPoly = !finish;\n return true;\n }\n drawingPoly = false;\n // Each callout click advances the core's tip → knee → box state machine; the\n // final box click/drag commits and clears the draft (so `drawingCallout`\n // resets on the next tool change or simply idles harmlessly).\n if (isCalloutTool(st)) drawingCallout = true;\n // The DOWN sample's display rotation rides along for the tool's `upright`\n // policy; the core captures it on the draft (later phases don't carry it).\n anno.createPointer(tool, 'down', s.page.pon, s.page.point, false, s.page.rotation);\n return true;\n },\n onMove: (s) => {\n const tool = toolId();\n const st = subtypeOf(tool);\n // Drag-moves (button down): rect/line/ink/free-text size their box, and a\n // callout (a non-poly tool) sizes its text box during the box step. Poly\n // tools take vertices by click, so they ignore drag-moves.\n if (!origin || (isPolyTool(st) && !drawingPoly)) return;\n const point = pointOn(s, origin.pon);\n if (!point) return;\n origin.point = point;\n anno.createPointer(tool, 'move', origin.pon, point);\n },\n onUp: (s) => {\n const tool = toolId();\n const st = subtypeOf(tool);\n if (origin && !isPolyTool(st)) {\n // ALWAYS commit the drag, even released off-page (point pins in core).\n anno.createPointer(tool, 'up', origin.pon, pointOn(s, origin.pon) ?? origin.point);\n if (st === 'ink') {\n const groupStrokesMs = anno.tool(tool)?.ink?.groupStrokesMs ?? 0;\n if (groupStrokesMs > 0) {\n const pon = origin.pon;\n const timer = setTimeout(() => {\n pendingInk = null;\n anno.finishInkDraft();\n }, groupStrokesMs);\n pendingInk = { tool, pon, timer };\n }\n }\n }\n origin = null;\n },\n onCancel: () => {\n // Aborted (second finger → pinch, or a system cancel): the draft DIES,\n // nothing commits — without this, the up-fallback would commit a shape\n // whose final point teleports to the second finger. For an ink group\n // this drops the whole in-window draft (there is no partial discard);\n // a cancel is a cancel, and the grouping window is sub-second.\n if (pendingInk) {\n clearTimeout(pendingInk.timer);\n pendingInk = null;\n }\n drawingPoly = false;\n drawingCallout = false;\n origin = null;\n anno.cancelCreationDraft();\n },\n onHover: (s) => {\n const tool = toolId();\n const st = subtypeOf(tool);\n // Hover preview for the multi-click tools: poly (while placing vertices) and\n // callout (while placing the tip/knee/box) follow the cursor between clicks.\n if (s.page && ((drawingPoly && isPolyTool(st)) || (drawingCallout && isCalloutTool(st)))) {\n anno.createPointer(tool, 'move', s.page.pon, s.page.point);\n }\n },\n };\n}\n","/**\n * Text-selection authoring: the bridge that turns a TEXT SELECTION into markup\n * or a text-edit annotation (Insert / Replace). These tools have no gesture of\n * their own — they enable the selection plugin's `text-select` gesture, and this\n * module consumes its typed signals (`onCommit` to create, `onChange` to preview).\n * Selection is the producer; annotation the consumer — selection never knows\n * annotation exists. Kept out of the plugin-definition file so that stays lean.\n */\nimport type { InteractionCapability } from '@embedpdf/plugin-interaction/contract';\nimport type { SelectionHostCapability } from '@embedpdf/plugin-selection/contract/host';\nimport type { AnnotationHostCapability } from './types';\n\n/**\n * Wire the selection → annotation BRIDGE. The markup / caret TOOLS and their\n * defaults are registered by the plugin `init` from the tool registry; this\n * function only consumes the selection plugin's typed signals — so call it from\n * `init` only when a selection plugin is present. Selection is the producer,\n * annotation the consumer; selection never knows annotation exists.\n */\nexport function wireMarkup(\n annotation: AnnotationHostCapability,\n selection: SelectionHostCapability,\n interaction: InteractionCapability,\n): void {\n // Keep the live preview + the selection's own visual in sync with (active tool,\n // selection). While a markup tool is active the blue highlight is suppressed and\n // the in-progress selection renders as a markup ghost instead.\n const sync = () => {\n const tool = annotation.tool(interaction.activeToolId());\n const authoring = tool?.selection;\n const previewSubtype =\n authoring?.kind === 'markup'\n ? tool!.subtype\n : authoring?.kind === 'text-edit' && authoring.operation === 'replace'\n ? 'strikeout'\n : null;\n selection.setHighlightVisible(previewSubtype == null);\n if (previewSubtype && tool && selection.hasSelection()) {\n const quadsByPage: Record<number, ReturnType<typeof selection.segmentsForPage>[number]['quad'][]> =\n {};\n for (const page of selection.snapshot().pages) {\n quadsByPage[page.pon] = page.segments.map((s) => s.quad);\n }\n annotation.previewMarkup(previewSubtype, quadsByPage, tool.preset);\n } else {\n annotation.clearMarkupPreview();\n }\n };\n selection.onChange(sync); // drag-extend → live preview\n interaction.onToolChange(sync); // entering/leaving a markup tool → restore blue / clear ghost\n\n // On gesture-end, if a markup tool is active, turn the selection into markup.\n selection.onCommit(() => {\n const tool = annotation.tool(interaction.activeToolId());\n const authoring = tool?.selection;\n if (!tool || !authoring) return; // pointer tool → leave the selection (copy)\n const snapshot = selection.snapshot();\n if (authoring.kind === 'text-edit' && authoring.operation === 'insert') {\n if (snapshot.end) {\n annotation.createCaret(snapshot.end.pon, {\n glyphQuad: snapshot.end.glyphQuad,\n advance: snapshot.end.advance,\n });\n }\n selection.clear();\n return;\n }\n if (authoring.kind === 'text-edit' && authoring.operation === 'replace') {\n // `/IRT` relationships are page-local, so a cross-page selection becomes\n // one self-contained Caret + StrikeOut pair per page. The true glyph-cell\n // anchor exists only on the selection's end page; other pages anchor at\n // their last segment's trailing edge.\n for (const page of snapshot.pages) {\n const last = page.segments[page.segments.length - 1];\n if (!last) continue;\n const anchor =\n snapshot.end && snapshot.end.pon === page.pon\n ? { glyphQuad: snapshot.end.glyphQuad, advance: snapshot.end.advance }\n : { glyphQuad: last.quad, advance: last.advance };\n annotation.createReplaceText(\n page.pon,\n page.segments.map((s) => s.quad),\n anchor,\n tool.preset,\n );\n }\n selection.clear();\n return;\n }\n for (const page of snapshot.pages) {\n annotation.createMarkup(\n tool.subtype,\n page.pon,\n page.segments.map((s) => s.quad),\n tool.preset,\n );\n }\n selection.clear(); // fires onChange → preview clears; blue stays suppressed (markup tool still active)\n });\n}\n","import { initialModel } from '@embedpdf/core-annotation';\nimport type {\n AnnotationAction,\n AnnotationConfig,\n AnnotationState,\n ChromeSettings,\n ChromeSettingsPatch,\n} from './types';\n\n/**\n * Out-of-the-box selection chrome — a sensible document-annotation feel. Every\n * length is CSS px (screen-constant across zoom); every color falls back to\n * `accent`. Just defaults: override any field at registration\n * (`annotationPlugin({ chrome })`) or at runtime (`setChrome`).\n */\nexport const DEFAULT_CHROME: ChromeSettings = {\n accent: '#3858e9',\n // Solid, like the shape's own resting look — one style at rest AND rotated.\n outline: { style: 'solid', width: 1 },\n // 8px squares to look at, 24px to grab (touch-friendly without visual bulk).\n handles: { size: 8, hitSize: 24, fill: '#ffffff' },\n knob: { size: 10, hitSize: 24, offset: 32, stalk: true, fill: '#ffffff' },\n // Faint reference cross, prominent live indicator (v2 feel).\n guides: { enabled: true, style: 'solid', width: 1, axisOpacity: 0.35, indicatorOpacity: 0.8 },\n};\n\n/** Deep-partial merge of a chrome patch — one level per piece, like the\n * stage-settings convention (align pairs / pageFrame). */\nexport const mergeChrome = (base: ChromeSettings, patch: ChromeSettingsPatch): ChromeSettings => ({\n accent: patch.accent ?? base.accent,\n outline: { ...base.outline, ...patch.outline },\n handles: { ...base.handles, ...patch.handles },\n knob: { ...base.knob, ...patch.knob },\n guides: { ...base.guides, ...patch.guides },\n});\n\n/**\n * The slice holds the annotation-core Model + the selection-chrome settings.\n * The pure `update` runs in the capability (the shell, which also performs\n * effects); the reducer only stores new state — keeping the kernel store a\n * dumb, serializable container. The registration config seeds the model's\n * snap settings and the chrome.\n */\nexport const initialAnnotationState = (config: AnnotationConfig = {}): AnnotationState => ({\n model: { ...initialModel, snap: { ...initialModel.snap, ...config.snap } },\n hydration: { status: 'loading' },\n chrome: mergeChrome(DEFAULT_CHROME, config.chrome ?? {}),\n toolGhost: null,\n stampArmEpoch: 0,\n textSelection: null,\n});\n\nexport const annotationReducer = (\n state: AnnotationState,\n action: AnnotationAction,\n): AnnotationState => {\n switch (action.type) {\n case 'SET_MODEL':\n return { ...state, model: action.model };\n case 'SET_HYDRATION':\n return { ...state, hydration: action.hydration };\n case 'SET_CHROME':\n return { ...state, chrome: mergeChrome(state.chrome, action.patch) };\n case 'SET_TOOL_GHOST':\n return { ...state, toolGhost: action.ghost };\n case 'SET_TEXT_SELECTION':\n return { ...state, textSelection: action.selection };\n case 'STAMP_ARM_CHANGED':\n // A new (or dropped) payload invalidates any ghost drawn for the old one.\n return { ...state, stampArmEpoch: state.stampArmEpoch + 1, toolGhost: null };\n default:\n return state;\n }\n};\n","import { definePlugin } from '@embedpdf/core';\nimport { ActionsToken as PublicActionsToken } from '@embedpdf/plugin-actions/contract';\nimport { ActionsToken as ActionsHostToken } from '@embedpdf/plugin-actions/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { SelectionToken } from '@embedpdf/plugin-selection/contract/host';\nimport { createAnnotationCapability } from './capability';\nimport { registerAnnotationEffects } from './effects';\nimport {\n createDrawHandler,\n createEditHandler,\n createGhostHandler,\n createMarqueeHandler,\n createPlaceHandler,\n} from './handler';\nimport { wireMarkup } from './markup';\nimport { annotationReducer, initialAnnotationState } from './reducer';\nimport { ARMED_STAMP_TOOL_ID, isTouchDirect } from './tools';\nimport { AnnotationToken } from './types';\nimport type {\n AnnotationAction,\n AnnotationConfig,\n AnnotationHostCapability,\n AnnotationState,\n} from './types';\n\n/**\n * The annotation plugin. Document-scoped; requires the interaction hub and\n * OPTIONALLY uses the selection plugin. Shapes/ink work with no selection; text\n * markup lights up only when a selection plugin is present.\n */\nexport const annotationPlugin = (config: AnnotationConfig = {}) =>\n definePlugin<AnnotationState, AnnotationAction, AnnotationHostCapability>({\n id: 'annotation',\n token: AnnotationToken,\n scope: 'document',\n requires: [InteractionToken],\n optional: [SelectionToken, PublicActionsToken],\n initialState: () => initialAnnotationState(config),\n reduce: annotationReducer,\n // The capability owns the resolved tool registry (built-ins + config `tools`),\n // so it needs the config too — not just the reducer state.\n capability: (ctx) => createAnnotationCapability(ctx, config),\n // Fold in remote collaborators' edits (own edits flow through the capability).\n effects: registerAnnotationEffects,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const annotation = ctx.get(AnnotationToken);\n const selection = ctx.tryGet(SelectionToken);\n\n // The actions plane's session-visibility door (Hide actions,\n // `annot.hidden`): resolve annotation object numbers to loaded model\n // ids (the `obj:` refKey seam — O(1), cross-page) and write the\n // session overlay. Session state only — never an engine write.\n const actions = ctx.tryGet(ActionsHostToken);\n if (actions) {\n ctx.cleanup(\n actions.registerAnnotCommitSink((entries) => annotation.commitScriptEffects(entries)),\n );\n }\n\n // Register every resolved tool (shapes, lines, ink, free-text, markup, stamp,\n // plus anything the embedder added via config `tools`) and seed its defaults.\n // A tool is a named preset over a subtype — see `tools.ts`. Markup / caret\n // tools ride the selection plugin's `text-select` gesture, so they stay\n // inert (skipped) when no selection plugin is installed.\n for (const tool of annotation.tools()) {\n if (tool.enables.has('text-select') && !selection) continue;\n interaction.registerTool({\n id: tool.id,\n cursor: tool.cursor,\n enables: tool.enables,\n // drag-create tools own single-finger touch; click-place tools don't\n touchDirect: isTouchDirect(tool.enables),\n });\n if (tool.defaults) annotation.setDefaults(tool.preset, tool.defaults);\n }\n\n interaction.registerHandler(createPlaceHandler(annotation));\n interaction.registerHandler(createGhostHandler(annotation, interaction));\n interaction.registerHandler(createEditHandler(annotation, interaction));\n interaction.registerHandler(createMarqueeHandler(annotation));\n interaction.registerHandler(createDrawHandler(annotation, interaction));\n interaction.onToolChange(() => {\n annotation.cancel();\n annotation.clearGhost(); // a footprint belongs to the tool that computed it\n // Engagement follows the tool: annotations whose Behavior just engaged\n // (form widgets under a fill tool) drop out of the selection — no\n // stranded chrome on a fill control.\n annotation.pruneEngagedSelection();\n // Leaving the armed stamp's own tool drops the payload — bytes are tool\n // state, not document state. It has to be the TOOL, not a capability\n // tag: `armStamp` activates ARMED_STAMP_TOOL_ID and every built-in\n // stamp tool carries `annotation-place`, so tag-matching either\n // disarmed the payload on the very activation that armed it, or let it\n // survive onto a sibling preset whose own `source` it would then\n // hijack. The legacy `annotation-stamp` tag still holds a payload, for\n // embedder tools written before the tags were unified.\n const active = interaction.activeTool();\n if (active.id !== ARMED_STAMP_TOOL_ID && !active.enables.has('annotation-stamp')) {\n annotation.disarmStamp();\n }\n });\n\n // Markup is opt-in: wire the selection→annotation BRIDGE only when a\n // selection plugin is present (the markup TOOLS were registered above).\n if (selection) wireMarkup(annotation, selection, interaction);\n },\n });\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["AnnotationToken","AnnotationToken","PublicActionsToken","ActionsHostToken"],"sources":["../src/effects.ts","../src/handler.ts","../src/markup.ts","../src/reducer.ts","../src/annotation.plugin.ts"],"sourcesContent":["/**\n * Makes annotations REACTIVE to the document event stream.\n *\n * Own edits already update the model through the capability's promise path\n * (optimistic draw + re-sync from the engine DTO). This effect wires in\n * everything else:\n *\n * - it kicks whole-document hydration exactly once (`ensureHydrated`);\n * - remote `annotation.*` events are handed to the capability's\n * hydration-aware delivery (queued + cursor-replayed during the\n * hydration window, applied live otherwise);\n * - `stream.desynced` re-runs hydration — the gap's mutations will never\n * arrive as events;\n * - remote page inserts/deletes reconcile the model's page membership;\n * - form and redaction cross-plane events invalidate exactly what they\n * touched, as before.\n */\nimport type { DocumentEvent, EffectContext } from '@embedpdf/core';\nimport { encodeStableIdKey } from '@embedpdf/engine-core/runtime';\nimport { update, type Msg } from '@embedpdf/core-annotation';\n\nimport { AnnotationToken } from './types';\nimport type { AnnotationAction, AnnotationState } from './types';\n\nexport function registerAnnotationEffects(\n ctx: EffectContext<AnnotationState, AnnotationAction>,\n): void {\n const doc = ctx.doc;\n if (!doc) return;\n\n const host = () => ctx.get(AnnotationToken);\n\n const apply = (msg: Msg): void => {\n const [next] = update(ctx.getState().model, msg);\n ctx.dispatch({ type: 'SET_MODEL', model: next });\n };\n\n // Subscribe FIRST, then hydrate: the coherence protocol needs every event\n // from the snapshot's cursor onward to be observed (queued or applied) —\n // a gap between snapshot and stream is exactly the resurrection bug.\n const unsubscribe = doc.events.subscribe((event: DocumentEvent) => {\n // Widget appearances are re-baked by FORM value writes — a plane this\n // plugin doesn't own, so no remote-only filter: direct fills emit\n // `form.valueChanged`, while scripted batches emit `form.effectsApplied`.\n // Neither path touches this model directly. The bump tells the render\n // layer to re-fetch exactly the repainted widgets' rasters.\n if (event.type === 'form.valueChanged' || event.type === 'form.effectsApplied') {\n apply({\n t: 'bumpAp',\n ids: event.changedWidgets\n .filter((w) => w.annotObjectNumber > 0)\n .map((w) => encodeStableIdKey({ kind: 'objectNumber', value: w.annotObjectNumber })),\n });\n return;\n }\n // A field update re-bakes its widgets too — a signature field's visual\n // fill (`forms.setSignatureAppearance`) draws a mark into the widget\n // without a value write, so it arrives as `form.fieldUpdated`.\n if (event.type === 'form.fieldUpdated') {\n apply({\n t: 'bumpAp',\n ids: event.field.widgets\n .filter((w) => w.annotObjectNumber > 0)\n .map((w) => encodeStableIdKey({ kind: 'objectNumber', value: w.annotObjectNumber })),\n });\n return;\n }\n // A completed signature sealed the field with its mark as appearance:\n // repaint that widget. The session also moved to new bytes\n // (`document.versioned` follows); a remote completion arrives as that\n // event alone, so the versioned handler below re-reads the plane.\n if (event.type === 'signature.completed') {\n const widget = event.signature.widget;\n if (widget && widget.annotObjectNumber > 0) {\n apply({\n t: 'bumpAp',\n ids: [encodeStableIdKey({ kind: 'objectNumber', value: widget.annotObjectNumber })],\n });\n }\n return;\n }\n if (event.type === 'document.versioned') {\n void host().rehydrate();\n return;\n }\n // A redaction apply deleted the consumed marks plus every intersecting\n // annotation on the applied pages. ORIGIN-AGNOSTIC: our own applies run\n // through the redaction plugin's doc-level verb, never this plane's\n // capability paths, so the model is stale either way — reload the\n // affected pages from the engine. (Works without plugin-redaction\n // installed: a remote collaborator's apply still reconciles this view.)\n if (event.type === 'redaction.applied') {\n const affected = new Set(\n event.results.filter((r) => r.status === 'applied').map((r) => r.pageObjectNumber),\n );\n if (affected.size) {\n for (const pon of affected) void host().reloadPage(pon);\n }\n return;\n }\n // The live stream fell too far behind to replay — the gap's mutations\n // will NEVER arrive as events, so re-read the whole document.\n if (event.type === 'stream.desynced') {\n void host().rehydrate();\n return;\n }\n // Page membership changed. ORIGIN-AGNOSTIC like redaction: removing a\n // deleted page's annotations twice is a no-op, and reloading an\n // inserted page merges idempotently — correct whether the op was ours\n // or a collaborator's.\n if (event.type === 'pages.deleted') {\n const gone = new Set<number>(event.pageObjectNumbers);\n const m = ctx.getState().model;\n const ids = m.order.filter((id) => {\n const a = m.byId[id];\n return a !== undefined && gone.has(a.pon);\n });\n if (ids.length) apply({ t: 'remove', ids });\n return;\n }\n if (event.type === 'pages.inserted') {\n // A fresh page usually has no annotations, but an insert-from-bytes\n // can carry them. `reloadPage` no-ops harmlessly if the layout for\n // the new page hasn't landed in core state yet (crop unknown).\n for (const pon of event.insertedPageObjectNumbers) void host().reloadPage(pon);\n return;\n }\n // Only fold in OTHER sessions' edits; our own flow through the capability.\n if (!('origin' in event) || event.origin.kind !== 'remote') return;\n switch (event.type) {\n case 'annotation.created':\n case 'annotation.updated':\n case 'annotation.moved':\n case 'annotation.deleted':\n host().deliverRemoteAnnotationEvent(event);\n break;\n default:\n break;\n }\n });\n ctx.cleanup(unsubscribe);\n\n host().ensureHydrated();\n}\n","import type {\n InteractionCapability,\n InteractionHandler,\n PointerSample,\n} from '@embedpdf/plugin-interaction/contract';\nimport type { PageRotation } from '@embedpdf/core-geometry';\nimport type { Subtype, Vec } from '@embedpdf/core-annotation';\nimport type { AnnotationHostCapability } from './types';\nimport {\n ANNOTATION_DRAW_PRIORITY,\n ANNOTATION_EDIT_PRIORITY,\n ANNOTATION_GHOST_PRIORITY,\n ANNOTATION_MARQUEE_PRIORITY,\n ANNOTATION_PLACE_PRIORITY,\n} from './priorities';\n\nconst MARQUEE_DRAG_THRESHOLD_PX = 4;\nconst isPolyTool = (subtype: Subtype): boolean => subtype === 'polygon' || subtype === 'polyline';\nconst isCalloutTool = (subtype: Subtype): boolean => subtype === 'free-text-callout';\n\n/**\n * Resolve a sample against a gesture's HOME page. Annotation gestures are\n * page-anchored: they track the page they started on, even when the cursor\n * wanders off it — `s.page` re-resolves per event (a page-2 point is a\n * DIFFERENT coordinate frame, the teleport bug), so prefer the source's\n * unclamped projection and fall back to the page hit only when it's the same\n * page. Null → this sample can't speak for the home page; ignore it.\n */\nconst pointOn = (s: PointerSample, pon: number): Vec | null =>\n s.project?.(pon) ?? (s.page?.pon === pon ? s.page.point : null);\n\n/**\n * Click-to-place for every payload-carrying tool (stamp / note / file\n * attachment). Each click places one annotation centred on the point (the\n * tool stays active for repeat placement — v2 rubber-stamp behaviour); the\n * capability's `placeAt` routes by the active tool's kind: armed stamp bytes,\n * a stamp/attachment prompt (pick the spot first, the payload second), or an\n * immediate note. No drag gesture — placement size comes from the content\n * (stamp aspect / the fixed icon box), not the pointer.\n *\n * Priority is deliberately BELOW the edit handler (100): a click over an\n * EXISTING annotation selects it; placement happens on empty page space.\n */\nexport function createPlaceHandler(anno: AnnotationHostCapability): InteractionHandler {\n return {\n id: 'annotation-place',\n // `annotation-stamp` is honoured as a legacy alias for embedder tool\n // configs written before the tags were unified.\n priority: ANNOTATION_PLACE_PRIORITY,\n enabledFor: (t) => t.enables.has('annotation-place') || t.enables.has('annotation-stamp'),\n onDown: (s) => {\n if (!s.page) return false;\n // The click sample's display rotation drives the tool's `upright` policy —\n // the placement lands reading horizontally on a rotated page/view.\n return anno.placeAt(s.page.pon, s.page.point, s.page.rotation);\n },\n };\n}\n\n/**\n * The armed tool's FOOTPRINT ghost: every hover re-computes the would-be\n * placement under the cursor (stamp image fit / click-create default geometry);\n * off-page clears it. One handler for every tool — `ghostHoverAt` resolves the\n * tool's ghost policy and clears when it isn't `footprint`. Never captures:\n * the highest priority makes its onDown run FIRST on every press (hiding the\n * ghost while a gesture runs), then declines so the real handlers route.\n */\nexport function createGhostHandler(\n anno: AnnotationHostCapability,\n interaction: InteractionCapability,\n): InteractionHandler {\n const hover = (s: PointerSample): void => {\n if (s.page)\n anno.ghostHoverAt(interaction.activeToolId(), s.page.pon, s.page.point, s.page.rotation);\n else anno.clearGhost();\n };\n return {\n id: 'annotation-ghost',\n priority: ANNOTATION_GHOST_PRIORITY,\n enabledFor: () => true,\n onDown: () => {\n anno.clearGhost();\n return false;\n },\n onHover: hover,\n };\n}\n\n/**\n * Ambient editing: live under the `annotation-edit` tag, which BOTH the pointer\n * and pan tools enable — so you select/move/resize in any navigation mode (Adobe\n * behaviour). It captures only over an annotation/handle; over empty it\n * deselects and declines (so text-selection / pan still work). Hover drives the\n * cursor (move / pointer / resize) via a priority cursor claim.\n */\nexport function createEditHandler(\n anno: AnnotationHostCapability,\n interaction: InteractionCapability,\n): InteractionHandler {\n // The gesture's home page + last resolved point, armed on down. Every\n // move/up resolves against THIS page (the annotation slides along its edge\n // when the cursor overshoots — the core clamps), never against whatever\n // page the sample happens to hit. `downPoint` keeps the untouched origin so\n // an aborted gesture (second finger → pinch) can REVERT instead of leaving\n // the annotation half-moved. `touch` rides the whole gesture so every phase\n // grabs with the same finger-sized zones the down resolved with.\n let origin: { pon: number; point: Vec; downPoint: Vec; touch: boolean } | null = null;\n return {\n id: 'annotation-edit',\n priority: ANNOTATION_EDIT_PRIORITY,\n enabledFor: (t) => t.enables.has('annotation-edit'),\n // Touch consent: a finger owns a tool drag only over the selection's own\n // chrome or a selected annotation's body (see claimsTouchAt) — unselected\n // bodies keep scrolling, matching the Markup convention.\n claimsTouch: (s) =>\n !!s.page &&\n anno.claimsTouchAt(s.page.pon, s.page.point, s.page.scale, s.page.rotation, s.page.zoom),\n onDown: (s) => {\n if (!s.page) return false;\n // The final distance-placement click belongs to its creation draft even\n // when it lands over an existing annotation.\n if (anno.distanceCreationPage?.() != null) return false;\n const touch = s.pointerType === 'touch';\n // While a free-text box is being edited it owns its own pointer events, so a\n // down that reaches the hub at all is a click OUTSIDE the editor — commit and\n // leave text edit. This makes exit hub-driven (deterministic) rather than\n // relying on a DOM blur, which races the focus-steal of the entering gesture.\n const wasEditing = anno.currentEditing() != null;\n if (wasEditing) anno.endTextEdit();\n if (\n anno.hitKind(\n s.page.pon,\n s.page.point,\n s.page.scale,\n s.page.rotation,\n s.page.zoom,\n touch,\n ) === 'empty'\n ) {\n // Plain empty click drops the selection. Shift-empty preserves it so the\n // lower-priority marquee handler can additive/toggle-select.\n if (!s.modifiers.shift) anno.deselect();\n // A click that dismissed an active edit is CONSUMED: its sole job was to\n // leave edit mode, so the draw tool doesn't also spawn a new annotation\n // (matches v2). Only when nothing was being edited do we decline, letting\n // pan / text-selection / draw act on the empty click.\n return wasEditing;\n }\n // Double-click / long-press over a FREE-TEXT box → enter text edit (not\n // a move). Over any other annotation the attempt reports false and the\n // press falls through to the normal path below (select / arm a move) —\n // so a long-press on a highlight selects it instead of the gesture\n // being swallowed by a no-op edit attempt.\n if ((s.clickCount ?? 1) >= 2) {\n if (\n anno.beginTextEditAt(s.page.pon, s.page.point, s.page.scale, s.page.rotation, s.page.zoom)\n ) {\n return true;\n }\n }\n anno.editPointer(\n 'down',\n s.page.pon,\n s.page.point,\n s.modifiers.shift,\n s.page.scale,\n s.page.rotation,\n s.page.zoom,\n touch,\n );\n origin = { pon: s.page.pon, point: s.page.point, downPoint: s.page.point, touch };\n return true;\n },\n onMove: (s) => {\n if (!origin) return;\n const point = pointOn(s, origin.pon);\n if (!point) return;\n origin.point = point;\n // The sample's scale/rotation ride along (uniform across pages), so a\n // screen-anchored member page-clamps at its effective footprint mid-drag.\n anno.editPointer(\n 'move',\n origin.pon,\n point,\n s.modifiers.shift,\n s.page?.scale,\n s.page?.rotation,\n s.page?.zoom,\n origin.touch,\n );\n },\n onUp: (s) => {\n if (!origin) return;\n // ALWAYS close the gesture — a release over a page gap or outside the\n // window must still commit (a dangling draft leaves a ghost that snaps\n // back on the next interaction). `editUp` doesn't read the point.\n anno.editPointer(\n 'up',\n origin.pon,\n pointOn(s, origin.pon) ?? origin.point,\n false,\n undefined,\n undefined,\n undefined,\n origin.touch,\n );\n origin = null;\n },\n onCancel: () => {\n if (!origin) return;\n // Aborted (second finger → pinch): REVERT, don't commit — replay the\n // gesture back to its own down point (every transform is delta-from-\n // down, so this restores the original geometry), then close it there.\n const { pon, downPoint, touch } = origin;\n origin = null;\n anno.editPointer('move', pon, downPoint, false, undefined, undefined, undefined, touch);\n anno.editPointer('up', pon, downPoint, false, undefined, undefined, undefined, touch);\n },\n onHover: (s) => {\n // priority 20 → beats text-select's 'text' (10) over an annotation; null clears.\n interaction.setCursor(\n 'annotation',\n s.page\n ? anno.cursorAt(s.page.pon, s.page.point, s.page.scale, s.page.rotation, s.page.zoom)\n : null,\n 20,\n );\n // Hover state for scene affordances (redact preview) — rides the same\n // per-move sample; the capability dispatches on change only.\n anno.hoverAt(\n s.page\n ? {\n pon: s.page.pon,\n point: s.page.point,\n scale: s.page.scale,\n rotation: s.page.rotation,\n zoom: s.page.zoom,\n }\n : null,\n );\n },\n };\n}\n\n/**\n * Empty-page drag selection. Lower priority than annotation edit and text\n * selection, so it only owns drags that begin on empty, non-text page space.\n */\nexport function createMarqueeHandler(anno: AnnotationHostCapability): InteractionHandler {\n let anchor: {\n pon: number;\n point: Vec;\n vx: number;\n vy: number;\n shift: boolean;\n } | null = null;\n let last: { pon: number; point: Vec } | null = null;\n let dragging = false;\n\n // The marquee page's view env at the down sample — the `up` intersects\n // screen-anchored annotations at their effective footprint with it.\n let view: { scale?: number; rotation?: PageRotation; zoom?: number } = {};\n return {\n id: 'annotation-marquee',\n priority: ANNOTATION_MARQUEE_PRIORITY,\n enabledFor: (t) => t.enables.has('annotation-marquee'),\n onDown: (s) => {\n if (!s.page) return false;\n anchor = {\n pon: s.page.pon,\n point: s.page.point,\n vx: s.viewport.x,\n vy: s.viewport.y,\n shift: s.modifiers.shift,\n };\n last = { pon: s.page.pon, point: s.page.point };\n view = { scale: s.page.scale, rotation: s.page.rotation, zoom: s.page.zoom };\n dragging = false;\n return true;\n },\n onMove: (s) => {\n if (!anchor) return;\n // Anchored to the page the drag started on; the projected point keeps the\n // marquee growing along the page edge when the cursor overshoots (the\n // core clamps it to the page box).\n const point = pointOn(s, anchor.pon);\n if (!point) return;\n last = { pon: anchor.pon, point };\n if (!dragging) {\n if (\n Math.hypot(s.viewport.x - anchor.vx, s.viewport.y - anchor.vy) < MARQUEE_DRAG_THRESHOLD_PX\n ) {\n return;\n }\n dragging = true;\n anno.marqueePointer(\n 'down',\n anchor.pon,\n anchor.point,\n anchor.shift,\n view.scale,\n view.rotation,\n view.zoom,\n );\n }\n anno.marqueePointer(\n 'move',\n anchor.pon,\n point,\n anchor.shift,\n view.scale,\n view.rotation,\n view.zoom,\n );\n },\n onUp: () => {\n if (dragging && anchor && last) {\n anno.marqueePointer(\n 'up',\n anchor.pon,\n last.point,\n anchor.shift,\n view.scale,\n view.rotation,\n view.zoom,\n );\n }\n anchor = null;\n last = null;\n dragging = false;\n },\n onCancel: () => {\n // Abort WITHOUT committing a selection; the core's cancel message clears\n // the marquee draft so no rectangle lingers. (Touch can't currently\n // reach the marquee — this is symmetry and future-proofing.)\n anchor = null;\n last = null;\n dragging = false;\n anno.cancelCreationDraft();\n },\n };\n}\n\n/** Drawing: live under `annotation-draw` (the square / circle / line tools). */\nexport function createDrawHandler(\n anno: AnnotationHostCapability,\n interaction: InteractionCapability,\n): InteractionHandler {\n // The active tool id + its ROUTING subtype. The id is what `createPointer` takes\n // (it resolves the defaults preset — arrow vs line); the subtype is what the\n // poly/callout gesture checks read (arrow → `line`, a polygon preset → `polygon`).\n const toolId = () => interaction.activeToolId();\n const subtypeOf = (id: string): Subtype => anno.toolSubtype(id);\n let drawingPoly = false;\n // A callout is mid-creation between its tip/knee/box clicks; while it is, hover\n // (no button) must still drive the leader/box preview, like a poly's vertices.\n let drawingCallout = false;\n // The active drag's home page (down→up): moves/ups resolve against it, so a\n // shape keeps sizing along the page edge when the cursor overshoots.\n let origin: { pon: number; point: Vec } | null = null;\n let pendingInk: {\n tool: string;\n pon: number;\n timer: ReturnType<typeof setTimeout>;\n } | null = null;\n const flushPendingInk = () => {\n if (!pendingInk) return;\n clearTimeout(pendingInk.timer);\n pendingInk = null;\n anno.finishInkDraft();\n };\n interaction.onToolChange(() => {\n flushPendingInk();\n drawingPoly = false;\n drawingCallout = false;\n origin = null;\n });\n return {\n id: 'annotation-draw',\n // The gesture cascade — relative order IS the composition policy:\n // 100 annotation-edit existing annotations own their gestures\n // 95 annotation-place click-to-place armed tools\n // 60 text-select claims TEXT ONLY (self-gates via isOverText,\n // yields everywhere else)\n // 55 annotation-draw drag-create takes whatever text didn't claim\n // 50 annotation-marquee empty-space selection, the final fallback\n // Draw sits BELOW text-select so a COMPOSED tool (redact: text-select +\n // annotation-draw) lets text claim text — over a paragraph the drag makes\n // per-line quad marks, anywhere else it drag-creates. Priority only breaks\n // ties between simultaneously-ELIGIBLE handlers, and draw-only tools\n // (square/ink/arrow…) never co-enable text-select, so they are unaffected.\n priority: ANNOTATION_DRAW_PRIORITY,\n enabledFor: (t) => t.enables.has('annotation-draw'),\n onDown: (s) => {\n const tool = toolId();\n const st = subtypeOf(tool);\n const distancePage = anno.distanceCreationPage?.();\n if (distancePage != null) {\n const point = pointOn(s, distancePage);\n if (point) {\n anno.createPointer(tool, 'down', distancePage, point);\n }\n // This click commits on down. Its up must not start another draft.\n origin = null;\n return true;\n }\n if (!s.page) return false;\n if (st === 'ink' && pendingInk) {\n if (pendingInk.tool === tool && pendingInk.pon === s.page.pon) {\n clearTimeout(pendingInk.timer);\n pendingInk = null;\n } else {\n flushPendingInk();\n }\n }\n // A down is a fresh intent — it may legitimately start on another page\n // (the core restarts the draft there), so it re-anchors the gesture.\n origin = { pon: s.page.pon, point: s.page.point };\n if (isPolyTool(st)) {\n const finish = (s.clickCount ?? 1) >= 2;\n anno.createPointer(tool, 'down', s.page.pon, s.page.point, finish);\n drawingPoly = !finish;\n return true;\n }\n drawingPoly = false;\n // Each callout click advances the core's tip → knee → box state machine; the\n // final box click/drag commits and clears the draft (so `drawingCallout`\n // resets on the next tool change or simply idles harmlessly).\n if (isCalloutTool(st)) drawingCallout = true;\n // The DOWN sample's display rotation rides along for the tool's `upright`\n // policy; the core captures it on the draft (later phases don't carry it).\n anno.createPointer(tool, 'down', s.page.pon, s.page.point, false, s.page.rotation);\n return true;\n },\n onMove: (s) => {\n const tool = toolId();\n const st = subtypeOf(tool);\n // Drag-moves (button down): rect/line/ink/free-text size their box, and a\n // callout (a non-poly tool) sizes its text box during the box step. Poly\n // tools take vertices by click, so they ignore drag-moves.\n if (!origin || (isPolyTool(st) && !drawingPoly)) return;\n const point = pointOn(s, origin.pon);\n if (!point) return;\n origin.point = point;\n anno.createPointer(tool, 'move', origin.pon, point);\n },\n onUp: (s) => {\n const tool = toolId();\n const st = subtypeOf(tool);\n if (origin && !isPolyTool(st)) {\n // ALWAYS commit the drag, even released off-page (point pins in core).\n anno.createPointer(tool, 'up', origin.pon, pointOn(s, origin.pon) ?? origin.point);\n if (st === 'ink') {\n const groupStrokesMs = anno.tool(tool)?.ink?.groupStrokesMs ?? 0;\n if (groupStrokesMs > 0) {\n const pon = origin.pon;\n const timer = setTimeout(() => {\n pendingInk = null;\n anno.finishInkDraft();\n }, groupStrokesMs);\n pendingInk = { tool, pon, timer };\n }\n }\n }\n origin = null;\n },\n onCancel: () => {\n // Aborted (second finger → pinch, or a system cancel): the draft DIES,\n // nothing commits — without this, the up-fallback would commit a shape\n // whose final point teleports to the second finger. For an ink group\n // this drops the whole in-window draft (there is no partial discard);\n // a cancel is a cancel, and the grouping window is sub-second.\n if (pendingInk) {\n clearTimeout(pendingInk.timer);\n pendingInk = null;\n }\n drawingPoly = false;\n drawingCallout = false;\n origin = null;\n anno.cancelCreationDraft();\n },\n onHover: (s) => {\n const tool = toolId();\n const st = subtypeOf(tool);\n const distancePage = anno.distanceCreationPage?.();\n if (distancePage != null) {\n const point = pointOn(s, distancePage);\n if (point) {\n anno.createPointer(tool, 'move', distancePage, point);\n }\n return;\n }\n // Hover preview for the multi-click tools: poly (while placing vertices) and\n // callout (while placing the tip/knee/box) follow the cursor between clicks.\n if (s.page && ((drawingPoly && isPolyTool(st)) || (drawingCallout && isCalloutTool(st)))) {\n anno.createPointer(tool, 'move', s.page.pon, s.page.point);\n }\n },\n };\n}\n","/**\n * Text-selection authoring: the bridge that turns a TEXT SELECTION into markup\n * or a text-edit annotation (Insert / Replace). These tools have no gesture of\n * their own — they enable the selection plugin's `text-select` gesture, and this\n * module consumes its typed signals (`onCommit` to create, `onChange` to preview).\n * Selection is the producer; annotation the consumer — selection never knows\n * annotation exists. Kept out of the plugin-definition file so that stays lean.\n */\nimport type { InteractionCapability } from '@embedpdf/plugin-interaction/contract';\nimport type { SelectionHostCapability } from '@embedpdf/plugin-selection/contract/host';\nimport type { AnnotationHostCapability } from './types';\n\n/**\n * Wire the selection → annotation BRIDGE. The markup / caret TOOLS and their\n * defaults are registered by the plugin `init` from the tool registry; this\n * function only consumes the selection plugin's typed signals — so call it from\n * `init` only when a selection plugin is present. Selection is the producer,\n * annotation the consumer; selection never knows annotation exists.\n */\nexport function wireMarkup(\n annotation: AnnotationHostCapability,\n selection: SelectionHostCapability,\n interaction: InteractionCapability,\n): void {\n // Keep the live preview + the selection's own visual in sync with (active tool,\n // selection). While a markup tool is active the blue highlight is suppressed and\n // the in-progress selection renders as a markup ghost instead.\n const sync = () => {\n const tool = annotation.tool(interaction.activeToolId());\n const authoring = tool?.selection;\n const previewSubtype =\n authoring?.kind === 'markup'\n ? tool!.subtype\n : authoring?.kind === 'text-edit' && authoring.operation === 'replace'\n ? 'strikeout'\n : null;\n selection.setHighlightVisible(previewSubtype == null);\n if (previewSubtype && tool && selection.hasSelection()) {\n const quadsByPage: Record<number, ReturnType<typeof selection.segmentsForPage>[number]['quad'][]> =\n {};\n for (const page of selection.snapshot().pages) {\n quadsByPage[page.pon] = page.segments.map((s) => s.quad);\n }\n annotation.previewMarkup(previewSubtype, quadsByPage, tool.preset);\n } else {\n annotation.clearMarkupPreview();\n }\n };\n selection.onChange(sync); // drag-extend → live preview\n interaction.onToolChange(sync); // entering/leaving a markup tool → restore blue / clear ghost\n\n // On gesture-end, if a markup tool is active, turn the selection into markup.\n selection.onCommit(() => {\n const tool = annotation.tool(interaction.activeToolId());\n const authoring = tool?.selection;\n if (!tool || !authoring) return; // pointer tool → leave the selection (copy)\n const snapshot = selection.snapshot();\n if (authoring.kind === 'text-edit' && authoring.operation === 'insert') {\n if (snapshot.end) {\n annotation.createCaret(snapshot.end.pon, {\n glyphQuad: snapshot.end.glyphQuad,\n advance: snapshot.end.advance,\n });\n }\n selection.clear();\n return;\n }\n if (authoring.kind === 'text-edit' && authoring.operation === 'replace') {\n // `/IRT` relationships are page-local, so a cross-page selection becomes\n // one self-contained Caret + StrikeOut pair per page. The true glyph-cell\n // anchor exists only on the selection's end page; other pages anchor at\n // their last segment's trailing edge.\n for (const page of snapshot.pages) {\n const last = page.segments[page.segments.length - 1];\n if (!last) continue;\n const anchor =\n snapshot.end && snapshot.end.pon === page.pon\n ? { glyphQuad: snapshot.end.glyphQuad, advance: snapshot.end.advance }\n : { glyphQuad: last.quad, advance: last.advance };\n annotation.createReplaceText(\n page.pon,\n page.segments.map((s) => s.quad),\n anchor,\n tool.preset,\n );\n }\n selection.clear();\n return;\n }\n for (const page of snapshot.pages) {\n annotation.createMarkup(\n tool.subtype,\n page.pon,\n page.segments.map((s) => s.quad),\n tool.preset,\n );\n }\n selection.clear(); // fires onChange → preview clears; blue stays suppressed (markup tool still active)\n });\n}\n","import { initialModel } from '@embedpdf/core-annotation';\nimport type {\n AnnotationAction,\n AnnotationConfig,\n AnnotationState,\n ChromeSettings,\n ChromeSettingsPatch,\n} from './types';\n\n/**\n * Out-of-the-box selection chrome — a sensible document-annotation feel. Every\n * length is CSS px (screen-constant across zoom); every color falls back to\n * `accent`. Just defaults: override any field at registration\n * (`annotationPlugin({ chrome })`) or at runtime (`setChrome`).\n */\nexport const DEFAULT_CHROME: ChromeSettings = {\n accent: '#3858e9',\n // Solid, like the shape's own resting look — one style at rest AND rotated.\n outline: { style: 'solid', width: 1 },\n // 8px squares to look at, 24px to grab (touch-friendly without visual bulk).\n handles: { size: 8, hitSize: 24, fill: '#ffffff' },\n knob: { size: 10, hitSize: 24, offset: 32, stalk: true, fill: '#ffffff' },\n // Faint reference cross, prominent live indicator (v2 feel).\n guides: { enabled: true, style: 'solid', width: 1, axisOpacity: 0.35, indicatorOpacity: 0.8 },\n};\n\n/** Deep-partial merge of a chrome patch — one level per piece, like the\n * stage-settings convention (align pairs / pageFrame). */\nexport const mergeChrome = (base: ChromeSettings, patch: ChromeSettingsPatch): ChromeSettings => ({\n accent: patch.accent ?? base.accent,\n outline: { ...base.outline, ...patch.outline },\n handles: { ...base.handles, ...patch.handles },\n knob: { ...base.knob, ...patch.knob },\n guides: { ...base.guides, ...patch.guides },\n});\n\n/**\n * The slice holds the annotation-core Model + the selection-chrome settings.\n * The pure `update` runs in the capability (the shell, which also performs\n * effects); the reducer only stores new state — keeping the kernel store a\n * dumb, serializable container. The registration config seeds the model's\n * snap settings and the chrome.\n */\nexport const initialAnnotationState = (config: AnnotationConfig = {}): AnnotationState => ({\n model: { ...initialModel, snap: { ...initialModel.snap, ...config.snap } },\n hydration: { status: 'loading' },\n chrome: mergeChrome(DEFAULT_CHROME, config.chrome ?? {}),\n toolGhost: null,\n stampArmEpoch: 0,\n textSelection: null,\n});\n\nexport const annotationReducer = (\n state: AnnotationState,\n action: AnnotationAction,\n): AnnotationState => {\n switch (action.type) {\n case 'SET_MODEL':\n return { ...state, model: action.model };\n case 'SET_HYDRATION':\n return { ...state, hydration: action.hydration };\n case 'SET_CHROME':\n return { ...state, chrome: mergeChrome(state.chrome, action.patch) };\n case 'SET_TOOL_GHOST':\n return { ...state, toolGhost: action.ghost };\n case 'SET_TEXT_SELECTION':\n return { ...state, textSelection: action.selection };\n case 'STAMP_ARM_CHANGED':\n // A new (or dropped) payload invalidates any ghost drawn for the old one.\n return { ...state, stampArmEpoch: state.stampArmEpoch + 1, toolGhost: null };\n default:\n return state;\n }\n};\n","import { definePlugin } from '@embedpdf/core';\nimport { ActionsToken as PublicActionsToken } from '@embedpdf/plugin-actions/contract';\nimport { ActionsToken as ActionsHostToken } from '@embedpdf/plugin-actions/contract/host';\nimport { InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { SelectionToken } from '@embedpdf/plugin-selection/contract/host';\nimport { createAnnotationCapability } from './capability';\nimport { registerAnnotationEffects } from './effects';\nimport {\n createDrawHandler,\n createEditHandler,\n createGhostHandler,\n createMarqueeHandler,\n createPlaceHandler,\n} from './handler';\nimport { wireMarkup } from './markup';\nimport { annotationReducer, initialAnnotationState } from './reducer';\nimport { ARMED_STAMP_TOOL_ID, isTouchDirect } from './tools';\nimport { AnnotationToken } from './types';\nimport type {\n AnnotationAction,\n AnnotationConfig,\n AnnotationHostCapability,\n AnnotationState,\n} from './types';\n\n/**\n * The annotation plugin. Document-scoped; requires the interaction hub and\n * OPTIONALLY uses the selection plugin. Shapes/ink work with no selection; text\n * markup lights up only when a selection plugin is present.\n */\nexport const annotationPlugin = (config: AnnotationConfig = {}) =>\n definePlugin<AnnotationState, AnnotationAction, AnnotationHostCapability>({\n id: 'annotation',\n token: AnnotationToken,\n scope: 'document',\n requires: [InteractionToken],\n optional: [SelectionToken, PublicActionsToken],\n initialState: () => initialAnnotationState(config),\n reduce: annotationReducer,\n // The capability owns the resolved tool registry (built-ins + config `tools`),\n // so it needs the config too — not just the reducer state.\n capability: (ctx) => createAnnotationCapability(ctx, config),\n // Fold in remote collaborators' edits (own edits flow through the capability).\n effects: registerAnnotationEffects,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const annotation = ctx.get(AnnotationToken);\n const selection = ctx.tryGet(SelectionToken);\n\n // The actions plane's session-visibility door (Hide actions,\n // `annot.hidden`): resolve annotation object numbers to loaded model\n // ids (the `obj:` refKey seam — O(1), cross-page) and write the\n // session overlay. Session state only — never an engine write.\n const actions = ctx.tryGet(ActionsHostToken);\n if (actions) {\n ctx.cleanup(\n actions.registerAnnotCommitSink((entries) => annotation.commitScriptEffects(entries)),\n );\n }\n\n // Register every resolved tool (shapes, lines, ink, free-text, markup, stamp,\n // plus anything the embedder added via config `tools`) and seed its defaults.\n // A tool is a named preset over a subtype — see `tools.ts`. Markup / caret\n // tools ride the selection plugin's `text-select` gesture, so they stay\n // inert (skipped) when no selection plugin is installed.\n for (const tool of annotation.tools()) {\n if (tool.enables.has('text-select') && !selection) continue;\n interaction.registerTool({\n id: tool.id,\n cursor: tool.cursor,\n enables: tool.enables,\n // drag-create tools own single-finger touch; click-place tools don't\n touchDirect: isTouchDirect(tool.enables),\n });\n if (tool.defaults) annotation.setDefaults(tool.preset, tool.defaults);\n }\n\n interaction.registerHandler(createPlaceHandler(annotation));\n interaction.registerHandler(createGhostHandler(annotation, interaction));\n interaction.registerHandler(createEditHandler(annotation, interaction));\n interaction.registerHandler(createMarqueeHandler(annotation));\n interaction.registerHandler(createDrawHandler(annotation, interaction));\n interaction.onToolChange(() => {\n annotation.cancel();\n annotation.clearGhost(); // a footprint belongs to the tool that computed it\n // Engagement follows the tool: annotations whose Behavior just engaged\n // (form widgets under a fill tool) drop out of the selection — no\n // stranded chrome on a fill control.\n annotation.pruneEngagedSelection();\n // Leaving the armed stamp's own tool drops the payload — bytes are tool\n // state, not document state. It has to be the TOOL, not a capability\n // tag: `armStamp` activates ARMED_STAMP_TOOL_ID and every built-in\n // stamp tool carries `annotation-place`, so tag-matching either\n // disarmed the payload on the very activation that armed it, or let it\n // survive onto a sibling preset whose own `source` it would then\n // hijack. The legacy `annotation-stamp` tag still holds a payload, for\n // embedder tools written before the tags were unified.\n const active = interaction.activeTool();\n if (active.id !== ARMED_STAMP_TOOL_ID && !active.enables.has('annotation-stamp')) {\n annotation.disarmStamp();\n }\n });\n\n // Markup is opt-in: wire the selection→annotation BRIDGE only when a\n // selection plugin is present (the markup TOOLS were registered above).\n if (selection) wireMarkup(annotation, selection, interaction);\n },\n });\n"],"mappings":"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"}
package/dist/internal.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_capability = require("./capability-DVx1NmTe.cjs");
3
- const require_tools = require("./tools-0NI3b9z8.cjs");
2
+ const require_capability = require("./capability-Bt320iJP.cjs");
3
+ const require_tools = require("./tools-B3i1gjRs.cjs");
4
4
  const require_types = require("./types-Dlhdw2k_.cjs");
5
5
  require("./contract/host.cjs");
6
6
  exports.AnnotationToken = require_types.AnnotationToken;
@@ -1,4 +1,4 @@
1
- import { L as buildToolRegistry, M as ResolvedTool, i as AnnotationHostCapability, o as AnnotationState, r as AnnotationConfig, s as AnnotationToken, t as AnnotationAction } from "./types-ObcPYc3-.cjs";
1
+ import { P as ResolvedTool, _ as RecalibrationReport, i as AnnotationHostCapability, l as CapturedAnnotationDraft, o as AnnotationState, r as AnnotationConfig, s as AnnotationToken, t as AnnotationAction, z as buildToolRegistry } from "./types-ChRnKozg.cjs";
2
2
  import "./contract/host.cjs";
3
3
  import { PluginContext } from "@embedpdf/core";
4
4
  //#region src/capability.d.ts
@@ -10,5 +10,5 @@ import { PluginContext } from "@embedpdf/core";
10
10
  */
11
11
  declare function createAnnotationCapability(ctx: PluginContext<AnnotationState, AnnotationAction>, config?: AnnotationConfig): AnnotationHostCapability;
12
12
  //#endregion
13
- export { type AnnotationHostCapability, AnnotationToken, type ResolvedTool, buildToolRegistry, createAnnotationCapability };
13
+ export { type AnnotationHostCapability, AnnotationToken, type CapturedAnnotationDraft, type RecalibrationReport, type ResolvedTool, buildToolRegistry, createAnnotationCapability };
14
14
  //# sourceMappingURL=internal.d.cts.map
@@ -1,4 +1,4 @@
1
- import { L as buildToolRegistry, M as ResolvedTool, i as AnnotationHostCapability, o as AnnotationState, r as AnnotationConfig, s as AnnotationToken, t as AnnotationAction } from "./types-D5V0TIwk.js";
1
+ import { P as ResolvedTool, _ as RecalibrationReport, i as AnnotationHostCapability, l as CapturedAnnotationDraft, o as AnnotationState, r as AnnotationConfig, s as AnnotationToken, t as AnnotationAction, z as buildToolRegistry } from "./types-Dmz3eRSG.js";
2
2
  import "./contract/host.js";
3
3
  import { PluginContext } from "@embedpdf/core";
4
4
  //#region src/capability.d.ts
@@ -10,5 +10,5 @@ import { PluginContext } from "@embedpdf/core";
10
10
  */
11
11
  declare function createAnnotationCapability(ctx: PluginContext<AnnotationState, AnnotationAction>, config?: AnnotationConfig): AnnotationHostCapability;
12
12
  //#endregion
13
- export { type AnnotationHostCapability, AnnotationToken, type ResolvedTool, buildToolRegistry, createAnnotationCapability };
13
+ export { type AnnotationHostCapability, AnnotationToken, type CapturedAnnotationDraft, type RecalibrationReport, type ResolvedTool, buildToolRegistry, createAnnotationCapability };
14
14
  //# sourceMappingURL=internal.d.ts.map
package/dist/internal.js CHANGED
@@ -1,5 +1,5 @@
1
- import { t as createAnnotationCapability } from "./capability-B_dkoB1K.js";
2
- import { r as buildToolRegistry } from "./tools-DBEYx3-c.js";
1
+ import { t as createAnnotationCapability } from "./capability-CoL4na7g.js";
2
+ import { r as buildToolRegistry } from "./tools-DsfqBn_a.js";
3
3
  import { t as AnnotationToken } from "./types-BvB1vUKb.js";
4
4
  import "./contract/host.js";
5
5
  export { AnnotationToken, buildToolRegistry, createAnnotationCapability };
@@ -211,6 +211,38 @@ const DEFAULT_TOOLS = [
211
211
  defaults: { strokeWidth: 6 },
212
212
  clickCreate: { length: 80 }
213
213
  },
214
+ {
215
+ id: "distance",
216
+ extends: "line",
217
+ intent: "LineDimension",
218
+ clickCreate: false,
219
+ defaults: {
220
+ strokeWidth: 1,
221
+ lineEndings: {
222
+ start: "closed-arrow",
223
+ end: "closed-arrow"
224
+ }
225
+ },
226
+ measurement: {
227
+ caption: {
228
+ enabled: true,
229
+ position: "inline"
230
+ },
231
+ leader: {
232
+ length: 12,
233
+ extension: 5,
234
+ offset: 0
235
+ }
236
+ }
237
+ },
238
+ {
239
+ id: "calibrate",
240
+ extends: "line",
241
+ enables: ["annotation-draw"],
242
+ defaults: { strokeWidth: 1 },
243
+ clickCreate: false,
244
+ meta: { capture: true }
245
+ },
214
246
  {
215
247
  id: "polygon",
216
248
  subtype: "polygon",
@@ -225,6 +257,26 @@ const DEFAULT_TOOLS = [
225
257
  enables: DRAW_TAGS,
226
258
  defaults: { strokeWidth: 6 }
227
259
  },
260
+ {
261
+ id: "perimeter",
262
+ extends: "polyline",
263
+ intent: "PolyLineDimension",
264
+ defaults: {
265
+ strokeWidth: 1,
266
+ lineEndings: {
267
+ start: "none",
268
+ end: "none"
269
+ }
270
+ },
271
+ measurement: { caption: { enabled: true } }
272
+ },
273
+ {
274
+ id: "area",
275
+ extends: "polygon",
276
+ intent: "PolygonDimension",
277
+ defaults: { strokeWidth: 1 },
278
+ measurement: { caption: { enabled: true } }
279
+ },
228
280
  {
229
281
  id: "ink",
230
282
  subtype: "ink",
@@ -488,6 +540,7 @@ function buildToolRegistry(overrides = []) {
488
540
  source: def.source ?? base?.source,
489
541
  selection: def.selection ?? base?.selection,
490
542
  intent: def.intent ?? base?.intent,
543
+ measurement: def.measurement ?? base?.measurement,
491
544
  ink: base?.ink || def.ink ? {
492
545
  ...base?.ink,
493
546
  ...def.ink
@@ -533,4 +586,4 @@ Object.defineProperty(exports, "isTouchDirect", {
533
586
  }
534
587
  });
535
588
 
536
- //# sourceMappingURL=tools-0NI3b9z8.cjs.map
589
+ //# sourceMappingURL=tools-B3i1gjRs.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-B3i1gjRs.cjs","names":[],"sources":["../src/tools.ts"],"sourcesContent":["/**\n * The annotation TOOL registry — the one place that says which tools exist and\n * what each one authors. A tool is a named authoring PRESET, not a new kind: it\n * binds an `id` to a PDF `subtype`, a bag of per-tool `defaults`, a cursor, and\n * the interaction TAGS it turns on. Two tools can share a subtype but differ in\n * defaults (an \"arrow\" is a `line` with an arrowhead default), which is exactly\n * why defaults are keyed by the tool id (the `preset`), never by the subtype.\n *\n * The built-in {@link DEFAULT_TOOLS} reproduce v2's tool set; an embedder tweaks\n * or extends them with `annotationPlugin({ tools: [...] })`. Same-id entries MERGE\n * over the built-in (configure it); a new id ADDS a tool; `extends` inherits an\n * existing tool's subtype/cursor/tags so a preset is one line.\n */\nimport type {\n AnnotationFlags,\n AnnotationPropsPatch,\n ClickCreate,\n InkStraightenOptions,\n PropKey,\n Subtype,\n} from '@embedpdf/core-annotation';\nimport type {\n BinarySource,\n InkIntent,\n LineDimensionCaption,\n LineLeader,\n ShapeDimensionCaption,\n} from '@embedpdf/engine-core/runtime';\n\n/**\n * How a stamp-family tool resolves the image bytes it places — pure DATA, so a\n * tool table stays JSON-serializable (config can come from a file, a DB, or the\n * server). Evaluated at CLICK time, so the user picks the spot first, then the\n * source:\n * - `{ kind: 'bytes' }` — fixed bytes: a company rubber-stamp.\n * - `{ kind: 'prompt' }` — ask the environment for bytes. The plugin does NOT\n * know how (a file dialog is DOM); the file-picker port (`FilePickerProvider`),\n * installed by the framework adapter, fulfils it. Resolve `null` there to cancel.\n */\nexport type StampSourceSpec = { kind: 'bytes'; source: BinarySource } | PromptSourceSpec;\n\n/**\n * The ask-the-environment resolution, shared by every click-then-pick tool\n * (a stamp's `'prompt'` source, the file-attachment tool). `accept` is the\n * file-dialog filter the picker request carries — a UX hint only, the engine\n * sniffs/validates the bytes for real. Unset = any file.\n */\nexport interface PromptSourceSpec {\n kind: 'prompt';\n accept?: string;\n}\n\n/**\n * The tool `armStamp` activates — and the only tool an armed payload survives\n * onto (plus the legacy `annotation-stamp` tag, honoured for embedder configs\n * written before the tags were unified). Placement consults the armed payload\n * BEFORE the active tool's own `source` spec, so a payload must not outlive\n * its tool onto a sibling stamp preset: an `image` preset's click has to open\n * its own picker, not silently place whatever the stamp panel last armed.\n */\nexport const ARMED_STAMP_TOOL_ID = 'stamp';\n\n/** Declarative result of committing a text selection while a tool is active. */\nexport type SelectionAuthoring =\n | { kind: 'markup' }\n | { kind: 'text-edit'; operation: 'insert' | 'replace' };\n\n/**\n * The armed tool's IN-PAGE preview:\n * - `footprint` — the EXACT box a click would place, in the page (content\n * space, page-clamped, WYSIWYG). Meaningful only when the box is\n * determinable: an armed stamp payload, or a `clickCreate` size.\n * - `false` — no preview.\n * WHICH tool is armed is the CURSOR's job, not a ghost's: give the tool an\n * image cursor built from your toolbar icon (the hub's `setToolCursor` + the\n * web package's `svgCursor`), and the hub's claim/gap arbitration keeps it\n * honest over annotations, form fields, and page gaps.\n */\nexport type GhostPolicy = false | { mode: 'footprint' };\n\n/** Time/geometry policy for freehand ink authoring. */\nexport interface InkAuthoringOptions {\n /** Group strokes made within this interval into one `/InkList`; 0 commits immediately. */\n groupStrokesMs?: number;\n /** Optional pure straight-line recognition applied to each completed stroke. */\n straighten?: InkStraightenOptions;\n}\n\n/**\n * The creation properties each known authoring kind actually consumes. This is\n * deliberately separate from the flat internal patch vocabulary: it gives tool\n * configuration precise compile-time and runtime validation while mixed-selection\n * edits can keep using `AnnotationPropsPatch`.\n */\nexport const TOOL_DEFAULT_KEYS = {\n square: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border'],\n circle: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border'],\n line: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border', 'lineEndings'],\n polygon: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border'],\n polyline: ['color', 'interiorColor', 'opacity', 'strokeWidth', 'border', 'lineEndings'],\n ink: ['color', 'opacity', 'strokeWidth', 'blendMode'],\n 'free-text': [\n 'fontFamily',\n 'fontSize',\n 'fontColor',\n 'bold',\n 'italic',\n 'underline',\n 'textAlign',\n 'opacity',\n 'interiorColor',\n 'color',\n 'strokeWidth',\n 'border',\n ],\n 'free-text-callout': [\n 'fontFamily',\n 'fontSize',\n 'fontColor',\n 'bold',\n 'italic',\n 'underline',\n 'textAlign',\n 'opacity',\n 'interiorColor',\n 'color',\n 'strokeWidth',\n 'border',\n 'lineEndings',\n ],\n highlight: ['color', 'opacity', 'blendMode'],\n underline: ['color', 'opacity', 'blendMode'],\n strikeout: ['color', 'opacity', 'blendMode'],\n squiggly: ['color', 'opacity', 'blendMode'],\n caret: ['color', 'opacity'],\n redact: ['color', 'interiorColor', 'opacity', 'fontFamily', 'fontSize', 'fontColor', 'textAlign'],\n stamp: [],\n // A link preset may carry a FIXED target ('docs-link' style one-click links).\n link: ['link'],\n text: ['icon', 'color', 'opacity'],\n 'file-attachment': ['icon', 'color', 'opacity'],\n // Widget CLIENT kinds (the form plugin's palette tools): the same key sets\n // the kind table declares — box styling for every family, /DA text styling\n // for the text-bearing ones. The engine maps them onto /MK //BS //DA //Q.\n 'widget-text': [\n 'color',\n 'interiorColor',\n 'strokeWidth',\n 'border',\n 'fontFamily',\n 'fontSize',\n 'fontColor',\n 'textAlign',\n ],\n 'widget-choice': [\n 'color',\n 'interiorColor',\n 'strokeWidth',\n 'border',\n 'fontFamily',\n 'fontSize',\n 'fontColor',\n 'textAlign',\n ],\n 'widget-toggle': ['color', 'interiorColor', 'strokeWidth', 'border'],\n // The bare box every other widget family ingests as (a signature field's\n // widget among them): box styling only — no text to style.\n 'widget-box': ['color', 'interiorColor', 'strokeWidth', 'border'],\n} as const satisfies Record<string, readonly PropKey[]>;\n\nexport type ToolAuthoringKind = keyof typeof TOOL_DEFAULT_KEYS;\ntype ToolDefaultKey<K extends ToolAuthoringKind> = (typeof TOOL_DEFAULT_KEYS)[K][number];\nexport type ToolDefaultsFor<K extends ToolAuthoringKind> = [ToolDefaultKey<K>] extends [never]\n ? never\n : Pick<AnnotationPropsPatch, ToolDefaultKey<K>>;\n\n/**\n * A tool definition — the public config vocabulary. Every field except `id` is\n * optional: the common cases are \"configure a built-in\" (`{ id, defaults }`) and\n * \"add a preset\" (`{ id, extends, defaults }`).\n */\nexport interface AnnotationToolDef<K extends ToolAuthoringKind = ToolAuthoringKind> {\n /** Stable tool id — the value passed to `activateTool` and the `defaults` key. */\n id: string;\n /** Inherit `subtype` / `propsKind` / `cursor` / `enables` / `source` /\n * `selection` / `intent` / `ink` / `meta`\n * from an existing tool id (a built-in or another entry). Own fields win. */\n extends?: string;\n /** The ROUTING KIND this tool authors — the core's client kind (the geometry\n * it draws + the props key). Usually the PDF subtype, but not always:\n * `free-text-callout` routes the callout gesture onto a free-text\n * annotation, and `widget-text`/`widget-choice`/`widget-toggle` are client\n * views of the ONE PDF `widget` subtype (a form tool's commit goes through\n * `doc.forms`, never this plugin — see the form plugin's tool table).\n * Defaults to the inherited kind, or the id when neither is given. */\n subtype?: Subtype;\n /** The kind whose editable-property specs a style panel shows for this tool.\n * Defaults to `subtype` (a callout authors `free-text` props, for example). */\n propsKind?: string;\n /** ADVANCED: the `defaults` key this tool reads/writes. Defaults to the id, and\n * that is almost always right — override it only to alias a shared defaults bag\n * (the built-in insert-caret tool points its preset at the `caret` key). */\n preset?: string;\n /** Seed defaults for newly drawn annotations — the flat AnnotationProps patch,\n * merged over any inherited defaults (line endings merge per side). */\n defaults?: ToolDefaultsFor<K>;\n /**\n * `/F` annotation flags seeded on everything this tool creates, merged over\n * the drawn default (`print` set — Acrobat parity) and any inherited seed.\n * A note tool passes `{ noZoom: true, noRotate: true }` so its icons stay\n * screen-sized and upright (the spec's rule for Text annotations).\n */\n flags?: Partial<AnnotationFlags>;\n /** The pointer cursor while this tool is active. Defaults inherited / `crosshair`. */\n cursor?: string;\n /** Interaction capability tags this tool enables (which handlers wake up). */\n enables?: string[];\n /**\n * How the click-to-place payload resolves. Stamps take the full spec\n * ({@link StampSourceSpec}: fixed bytes or prompt); file attachments are\n * prompt-only ({@link PromptSourceSpec} — the file always comes from the\n * environment). Other kinds carry no payload.\n */\n source?: K extends 'stamp'\n ? StampSourceSpec\n : K extends 'file-attachment'\n ? PromptSourceSpec\n : never;\n /** What a committed text selection authors. Omit for pointer/click tools. */\n selection?: SelectionAuthoring;\n /** PDF `/IT` authored by an intent-bearing ink preset. */\n intent?: K extends 'ink'\n ? InkIntent\n : K extends 'line'\n ? 'LineDimension'\n : K extends 'polygon'\n ? 'PolygonDimension'\n : K extends 'polyline'\n ? 'PolyLineDimension'\n : never;\n /** Caption defaults for a measurement preset. Shape centers use absolute PDF coordinates. */\n measurement?: K extends 'line'\n ? { caption: LineDimensionCaption; leader?: LineLeader }\n : K extends 'polygon' | 'polyline'\n ? { caption: ShapeDimensionCaption }\n : never;\n /** Ink-only stroke grouping and straightening policy. */\n ink?: K extends 'ink' ? InkAuthoringOptions : never;\n /**\n * Place annotations UPRIGHT: counter-rotate what this tool creates against the\n * page's TOTAL display rotation (document /Rotate + any stage view rotation),\n * so it reads horizontally exactly as the author saw it — Adobe's behaviour\n * for stamps and text on rotated pages. WYSIWYG at authoring time: the\n * rotation is baked into the annotation, so a save keeps what the author saw\n * (other viewers apply only /Rotate — document that when view rotation is in\n * play). Box kinds only (stamp / free-text, where reading orientation is\n * meaningful); ignored by vertex kinds and callouts. Default: on for the\n * built-in `stamp` and `free-text` tools, off elsewhere.\n */\n upright?: boolean;\n /**\n * What a bare CLICK creates (v2's `clickBehavior`): a default-size shape\n * centred on the point / a default-length line from it / free-text's\n * type-here box — page-clamped, in PDF pt. `false` = drag-only. Defaults:\n * on for the built-in square/circle/line/free-text, off elsewhere.\n */\n clickCreate?: ClickCreate | false;\n /**\n * The armed-tool in-page preview ({@link GhostPolicy}). Defaults: inherited,\n * else off (the built-in stamp declares `footprint`).\n */\n ghost?: GhostPolicy;\n /** Opaque presentation hints (label/icon…) for a toolbar or cursor that builds\n * itself from the tool table. `capture: true` captures a line gesture without\n * creating an annotation (used by calibration). */\n meta?: Record<string, unknown>;\n}\n\nexport interface BuiltinToolKindMap {\n square: 'square';\n circle: 'circle';\n line: 'line';\n distance: 'line';\n perimeter: 'polyline';\n area: 'polygon';\n calibrate: 'line';\n polygon: 'polygon';\n polyline: 'polyline';\n ink: 'ink';\n 'ink-highlight': 'ink';\n 'free-text': 'free-text';\n 'free-text-callout': 'free-text-callout';\n highlight: 'highlight';\n underline: 'underline';\n strikeout: 'strikeout';\n squiggly: 'squiggly';\n 'insert-text': 'caret';\n 'replace-text': 'strikeout';\n redact: 'redact';\n stamp: 'stamp';\n note: 'text';\n attachment: 'file-attachment';\n link: 'link';\n}\n\ntype DirectToolDef = {\n [K in ToolAuthoringKind]: Omit<AnnotationToolDef<K>, 'subtype'> & { subtype: K };\n}[ToolAuthoringKind];\ntype BuiltinToolOverride = {\n [I in keyof BuiltinToolKindMap]: Omit<\n AnnotationToolDef<BuiltinToolKindMap[I]>,\n 'id' | 'extends' | 'subtype'\n > & {\n id: I;\n extends?: never;\n subtype?: never;\n };\n}[keyof BuiltinToolKindMap];\ntype ExtendedBuiltinToolDef = {\n [I in keyof BuiltinToolKindMap]: Omit<\n AnnotationToolDef<BuiltinToolKindMap[I]>,\n 'extends' | 'subtype'\n > & {\n extends: I;\n subtype?: never;\n };\n}[keyof BuiltinToolKindMap];\n\n/** Public tool input: known overrides, known-base presets, or an explicit subtype. */\nexport type AnnotationToolInput = DirectToolDef | BuiltinToolOverride | ExtendedBuiltinToolDef;\n\n/** A fully-resolved tool — what the plugin, handlers, and registration loop read. */\nexport interface ResolvedTool {\n id: string;\n /** Routing token for the draw core + the created annotation's PDF subtype. */\n subtype: Subtype;\n /** The `defaults` key (always the tool id) — keeps same-subtype tools apart. */\n preset: string;\n /** The `propsFor` key for the style panel. */\n propsKind: string;\n cursor: string;\n enables: ReadonlySet<string>;\n defaults?: AnnotationPropsPatch;\n /** `/F` seed for created annotations (see {@link AnnotationToolDef.flags}). */\n flags?: Partial<AnnotationFlags>;\n source?: StampSourceSpec;\n selection?: SelectionAuthoring;\n intent?: InkIntent | 'LineDimension' | 'PolyLineDimension' | 'PolygonDimension';\n measurement?: { caption: LineDimensionCaption | ShapeDimensionCaption; leader?: LineLeader };\n ink?: InkAuthoringOptions;\n /** Counter-rotate creations against the page's display rotation (see\n * {@link AnnotationToolDef.upright}). */\n upright: boolean;\n /** What a bare click creates, or `false` for drag-only. */\n clickCreate: ClickCreate | false;\n /** The armed-tool in-page preview ({@link GhostPolicy}). */\n ghost: GhostPolicy;\n meta?: Record<string, unknown>;\n}\n\n// ── field groups shared by the built-ins (keeps the table readable) ──────────\nconst DRAW_TAGS = ['annotation-draw', 'annotation-edit'];\nconst MARKUP_TAGS = ['text-select', 'annotation-edit'];\n\n/**\n * Whether arming this ANNOTATION tool is touch consent (`Tool.touchDirect`):\n * drag-CREATE tools — shape/ink draw and text-markup select — take\n * single-finger touch wholesale (finger draws/marks, two fingers navigate:\n * the drawing-app convention). Click-to-PLACE tools (stamp, note) do not: a\n * tap places without any claim, and drags keep scrolling. Derived from the\n * gesture tags, so custom registered tools inherit the right behavior. Only\n * annotation-owned tools run through this — the built-in `pointer` tool also\n * enables `text-select`, but it is registered by the hub, not here.\n */\nexport const isTouchDirect = (enables: ReadonlySet<string>): boolean =>\n enables.has('annotation-draw') || enables.has('text-select');\n\n/**\n * The built-in tools — a data mirror of v2's registrations (shapes + lines + ink\n * + free text in the draw channel, text markup + caret behind text selection, and\n * the click-to-place stamp). Order is display-neutral; the toolbar owns layout.\n */\nexport const DEFAULT_TOOLS: AnnotationToolInput[] = [\n // shapes / lines / ink / free text — the `annotation-draw` gesture.\n // Draw tools carry v2's click-create defaults (a bare click places a\n // default-size annotation); armed-tool identity rides the cursor, so none\n // of them needs a ghost.\n {\n id: 'square',\n subtype: 'square',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n clickCreate: { width: 80, height: 60 },\n },\n {\n id: 'circle',\n subtype: 'circle',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n clickCreate: { width: 80, height: 60 },\n },\n {\n id: 'line',\n subtype: 'line',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n clickCreate: { length: 80 },\n },\n {\n id: 'distance',\n extends: 'line',\n intent: 'LineDimension',\n clickCreate: false,\n defaults: { strokeWidth: 1, lineEndings: { start: 'closed-arrow', end: 'closed-arrow' } },\n measurement: {\n caption: { enabled: true, position: 'inline' },\n leader: { length: 12, extension: 5, offset: 0 },\n },\n },\n {\n id: 'calibrate',\n extends: 'line',\n enables: ['annotation-draw'],\n defaults: { strokeWidth: 1 },\n clickCreate: false,\n meta: { capture: true },\n },\n {\n id: 'polygon',\n subtype: 'polygon',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n },\n {\n id: 'polyline',\n subtype: 'polyline',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6 },\n },\n {\n id: 'perimeter',\n extends: 'polyline',\n intent: 'PolyLineDimension',\n defaults: { strokeWidth: 1, lineEndings: { start: 'none', end: 'none' } },\n measurement: { caption: { enabled: true } },\n },\n {\n id: 'area',\n extends: 'polygon',\n intent: 'PolygonDimension',\n defaults: { strokeWidth: 1 },\n measurement: { caption: { enabled: true } },\n },\n {\n id: 'ink',\n subtype: 'ink',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { color: '#ef4444', strokeWidth: 6, blendMode: 'normal' },\n ink: { groupStrokesMs: 800 },\n },\n {\n id: 'ink-highlight',\n extends: 'ink',\n intent: 'ink-highlight',\n defaults: { color: '#ffcd45', strokeWidth: 14, blendMode: 'multiply' },\n ink: {\n straighten: { deviationThreshold: 0.15, axisSnapDegrees: 15 },\n },\n },\n {\n id: 'free-text',\n subtype: 'free-text',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { fontColor: '#ef4444' },\n upright: true,\n // Top-left anchored: the box hangs where you'll type (the kind's reading\n // feel); shapes default to `center`. Anchoring is explicit policy data.\n clickCreate: { width: 180, height: 40, anchor: 'top-left' },\n },\n {\n // Routes on the `free-text-callout` subtype token but authors a `free-text`\n // annotation (leader + box). Its leader defaults to an open arrowhead.\n // `upright` applies to the text BOX only: on a rotated page it commits\n // counter-rotated (readable), while the leader tip/knee stay page-space\n // anchors — see the core's `calloutPointer`.\n id: 'free-text-callout',\n subtype: 'free-text-callout',\n propsKind: 'free-text',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n defaults: { strokeWidth: 6, lineEndings: { end: 'open-arrow' } },\n upright: true,\n },\n // text markup — the `text-select` gesture (inert without a selection plugin).\n // Base cursor is the plain arrow: the I-beam appears only where the action is\n // possible, via the selection handler's over-text claim (which an app can\n // reskin with the tool's icon — see the hub's ToolCursorSkin).\n {\n id: 'highlight',\n subtype: 'highlight',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ffe16a', blendMode: 'multiply' },\n selection: { kind: 'markup' },\n },\n // Redaction marking — the COMPOSED tool: over text it rides the selection\n // gesture (per-line quad marks, like a highlight); anywhere else it drag-\n // draws an area mark. One tool, both modes; the destructive apply lives in\n // plugin-redaction.\n {\n id: 'redact',\n subtype: 'redact',\n cursor: 'crosshair',\n enables: ['text-select', 'annotation-draw', 'annotation-edit'],\n defaults: {\n color: '#e44234',\n interiorColor: '#000000',\n fontColor: '#ffffff',\n opacity: 1,\n },\n selection: { kind: 'markup' },\n },\n {\n id: 'underline',\n subtype: 'underline',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'markup' },\n },\n {\n id: 'strikeout',\n subtype: 'strikeout',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'markup' },\n },\n {\n id: 'squiggly',\n subtype: 'squiggly',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'markup' },\n },\n {\n // The insert-caret tool authors a `caret` from a text selection. Its defaults\n // live under the `caret` preset — the key `createCaret` resolves.\n id: 'insert-text',\n subtype: 'caret',\n propsKind: 'caret',\n preset: 'caret',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'text-edit', operation: 'insert' },\n },\n {\n id: 'replace-text',\n subtype: 'strikeout',\n propsKind: 'strikeout',\n cursor: 'default',\n enables: MARKUP_TAGS,\n defaults: { color: '#ef4444' },\n selection: { kind: 'text-edit', operation: 'replace' },\n },\n // stamp — click-to-place; 'prompt' asks the environment through the ONE\n // file-picker port (the React adapter wires a file dialog by default; an\n // embedder can pass fixed bytes instead). `accept` narrows the dialog to\n // what the engine's stamp sniffer takes anyway.\n {\n id: ARMED_STAMP_TOOL_ID,\n subtype: 'stamp',\n cursor: 'copy',\n enables: ['annotation-place', 'annotation-edit'],\n source: { kind: 'prompt', accept: 'image/png,image/jpeg,application/pdf' },\n upright: true,\n ghost: { mode: 'footprint' },\n },\n // link — drag an invisible hit rectangle; the target is set afterwards\n // through the selection editor (create-then-edit), unless a preset carries\n // a fixed one (`{ id: 'docs-link', extends: 'link', defaults: { link: … } }`).\n // While this tool is active the navigation plane stands down (no `link-nav`),\n // so existing links become plain editable rects.\n {\n id: 'link',\n subtype: 'link',\n cursor: 'crosshair',\n enables: DRAW_TAGS,\n },\n // sticky note (\"comment\") — click-to-place, no payload: each click drops a\n // fixed 20×20 icon (engine-baked /AP from /C + /Name), screen-sized and\n // upright per the spec's Text-icon rule (the noZoom/noRotate seed).\n {\n id: 'note',\n subtype: 'text',\n cursor: 'crosshair',\n enables: ['annotation-place', 'annotation-edit'],\n defaults: { icon: 'comment', color: '#facc15' },\n flags: { noZoom: true, noRotate: true },\n upright: true,\n ghost: { mode: 'footprint' },\n },\n // file attachment — click-to-place with the spot-first-file-second rule:\n // the click opens the installed file-picker port (a file dialog by\n // default; `accept` unset = any file — attaching any format is the point);\n // the picked file embeds at the clicked point.\n {\n id: 'attachment',\n subtype: 'file-attachment',\n cursor: 'copy',\n enables: ['annotation-place', 'annotation-edit'],\n source: { kind: 'prompt' },\n defaults: { icon: 'paperclip', color: '#facc15' },\n flags: { noZoom: true, noRotate: true },\n upright: true,\n ghost: { mode: 'footprint' },\n },\n];\n\n/** Merge two default patches, `b` over `a`, with line endings merged per side. */\nfunction mergeDefaults(\n a?: AnnotationPropsPatch,\n b?: AnnotationPropsPatch,\n): AnnotationPropsPatch | undefined {\n if (!a) return b;\n if (!b) return a;\n const merged: AnnotationPropsPatch = { ...a, ...b };\n if (a.lineEndings || b.lineEndings) merged.lineEndings = { ...a.lineEndings, ...b.lineEndings };\n return merged;\n}\n\n/** Overlay a same-id override onto a base definition (configure a built-in). */\nfunction mergeDef(base: AnnotationToolDef, over: AnnotationToolDef): AnnotationToolDef {\n return {\n ...base,\n ...over,\n enables: over.enables ?? base.enables,\n meta: base.meta || over.meta ? { ...base.meta, ...over.meta } : undefined,\n defaults: mergeDefaults(base.defaults, over.defaults),\n flags: base.flags || over.flags ? { ...base.flags, ...over.flags } : undefined,\n ink: base.ink || over.ink ? { ...base.ink, ...over.ink } : undefined,\n };\n}\n\nfunction validateDefaults(tool: ResolvedTool): void {\n if (!tool.defaults) return;\n const allowed = TOOL_DEFAULT_KEYS[tool.subtype as ToolAuthoringKind];\n // Unknown/custom routing kinds remain extensible; known built-ins are strict.\n if (!allowed) return;\n const keys = new Set<string>(allowed);\n for (const key of Object.keys(tool.defaults)) {\n if (!keys.has(key)) {\n throw new Error(\n `[annotation] tool '${tool.id}' does not support default '${key}' for '${tool.subtype}'`,\n );\n }\n }\n}\n\n/**\n * Resolve {@link DEFAULT_TOOLS} + embedder overrides into the ready-to-use table.\n * A same-id override merges over the built-in; a new id adds a tool; `extends`\n * inherits from an already-defined tool. Keyed by id, so lookups are O(1).\n */\nexport function buildToolRegistry(\n overrides: AnnotationToolInput[] = [],\n): Map<string, ResolvedTool> {\n const defs = new Map<string, AnnotationToolDef>();\n for (const d of DEFAULT_TOOLS) defs.set(d.id, d);\n for (const o of overrides) {\n const prev = defs.get(o.id);\n defs.set(o.id, prev ? mergeDef(prev, o) : o);\n }\n\n const out = new Map<string, ResolvedTool>();\n const resolving = new Set<string>();\n const resolve = (id: string): ResolvedTool => {\n const cached = out.get(id);\n if (cached) return cached;\n const def = defs.get(id);\n if (!def) throw new Error(`[annotation] tool '${id}' extends an unknown tool`);\n // Inherit from the base first (guarding self / cyclic extends), then own fields win.\n let base: ResolvedTool | undefined;\n if (def.extends && def.extends !== id && !resolving.has(def.extends)) {\n resolving.add(id);\n base = resolve(def.extends);\n resolving.delete(id);\n }\n const subtype = (def.subtype ?? base?.subtype ?? def.id) as Subtype;\n const resolved: ResolvedTool = {\n id: def.id,\n subtype,\n preset: def.preset ?? def.id,\n propsKind: def.propsKind ?? base?.propsKind ?? subtype,\n cursor: def.cursor ?? base?.cursor ?? 'crosshair',\n enables: new Set(def.enables ?? (base ? [...base.enables] : [])),\n defaults: mergeDefaults(base?.defaults, def.defaults),\n flags: base?.flags || def.flags ? { ...base?.flags, ...def.flags } : undefined,\n source: def.source ?? base?.source,\n selection: def.selection ?? base?.selection,\n intent: def.intent ?? base?.intent,\n measurement: def.measurement ?? base?.measurement,\n ink: base?.ink || def.ink ? { ...base?.ink, ...def.ink } : undefined,\n upright: def.upright ?? base?.upright ?? false,\n clickCreate: def.clickCreate ?? base?.clickCreate ?? false,\n ghost: def.ghost ?? base?.ghost ?? false,\n meta: base?.meta || def.meta ? { ...base?.meta, ...def.meta } : undefined,\n };\n validateDefaults(resolved);\n out.set(id, resolved);\n return resolved;\n };\n for (const id of defs.keys()) resolve(id);\n return out;\n}\n"],"mappings":"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"}
@@ -211,6 +211,38 @@ const DEFAULT_TOOLS = [
211
211
  defaults: { strokeWidth: 6 },
212
212
  clickCreate: { length: 80 }
213
213
  },
214
+ {
215
+ id: "distance",
216
+ extends: "line",
217
+ intent: "LineDimension",
218
+ clickCreate: false,
219
+ defaults: {
220
+ strokeWidth: 1,
221
+ lineEndings: {
222
+ start: "closed-arrow",
223
+ end: "closed-arrow"
224
+ }
225
+ },
226
+ measurement: {
227
+ caption: {
228
+ enabled: true,
229
+ position: "inline"
230
+ },
231
+ leader: {
232
+ length: 12,
233
+ extension: 5,
234
+ offset: 0
235
+ }
236
+ }
237
+ },
238
+ {
239
+ id: "calibrate",
240
+ extends: "line",
241
+ enables: ["annotation-draw"],
242
+ defaults: { strokeWidth: 1 },
243
+ clickCreate: false,
244
+ meta: { capture: true }
245
+ },
214
246
  {
215
247
  id: "polygon",
216
248
  subtype: "polygon",
@@ -225,6 +257,26 @@ const DEFAULT_TOOLS = [
225
257
  enables: DRAW_TAGS,
226
258
  defaults: { strokeWidth: 6 }
227
259
  },
260
+ {
261
+ id: "perimeter",
262
+ extends: "polyline",
263
+ intent: "PolyLineDimension",
264
+ defaults: {
265
+ strokeWidth: 1,
266
+ lineEndings: {
267
+ start: "none",
268
+ end: "none"
269
+ }
270
+ },
271
+ measurement: { caption: { enabled: true } }
272
+ },
273
+ {
274
+ id: "area",
275
+ extends: "polygon",
276
+ intent: "PolygonDimension",
277
+ defaults: { strokeWidth: 1 },
278
+ measurement: { caption: { enabled: true } }
279
+ },
228
280
  {
229
281
  id: "ink",
230
282
  subtype: "ink",
@@ -488,6 +540,7 @@ function buildToolRegistry(overrides = []) {
488
540
  source: def.source ?? base?.source,
489
541
  selection: def.selection ?? base?.selection,
490
542
  intent: def.intent ?? base?.intent,
543
+ measurement: def.measurement ?? base?.measurement,
491
544
  ink: base?.ink || def.ink ? {
492
545
  ...base?.ink,
493
546
  ...def.ink
@@ -510,4 +563,4 @@ function buildToolRegistry(overrides = []) {
510
563
  //#endregion
511
564
  export { isTouchDirect as i, DEFAULT_TOOLS as n, buildToolRegistry as r, ARMED_STAMP_TOOL_ID as t };
512
565
 
513
- //# sourceMappingURL=tools-DBEYx3-c.js.map
566
+ //# sourceMappingURL=tools-DsfqBn_a.js.map