@embedpdf/plugin-selection 3.0.0-next.10 → 3.0.0-next.12

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.
@@ -0,0 +1,3 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_types = require("../types-D9kEBMFj.cjs");
3
+ exports.SelectionToken = require_types.SelectionToken;
@@ -0,0 +1,2 @@
1
+ import { a as SelectionHostCapability, d as SelectionToken, n as SelectionAction, s as SelectionRange, t as GlyphPointer, u as SelectionState } from "../types-CUrUVl6K.cjs";
2
+ export { type GlyphPointer, type SelectionAction, type SelectionHostCapability, type SelectionRange, type SelectionState, SelectionToken };
@@ -0,0 +1,2 @@
1
+ import { a as SelectionHostCapability, d as SelectionToken, n as SelectionAction, s as SelectionRange, t as GlyphPointer, u as SelectionState } from "../types-CUrUVl6K.js";
2
+ export { type GlyphPointer, type SelectionAction, type SelectionHostCapability, type SelectionRange, type SelectionState, SelectionToken };
@@ -0,0 +1,2 @@
1
+ import { t as SelectionToken } from "../types-BVUcOsCh.js";
2
+ export { SelectionToken };
@@ -0,0 +1,7 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/contract.ts
3
+ const SelectionToken = require("./types-D9kEBMFj.cjs").SelectionToken;
4
+ //#endregion
5
+ exports.SelectionToken = SelectionToken;
6
+
7
+ //# sourceMappingURL=contract.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.cjs","names":["SelectionHostToken"],"sources":["../src/contract.ts"],"sourcesContent":["/** Public selection capability protocol without gesture/reducer/plugin wiring. */\nimport type { CapabilityToken } from '@embedpdf/core';\n\nimport { SelectionToken as SelectionHostToken } from './types';\nimport type { SelectionCapability } from './types';\n\nexport const SelectionToken = SelectionHostToken as unknown as CapabilityToken<SelectionCapability>;\nexport type {\n SelectionCapability,\n SelectionEndpoint,\n SelectionMenuAnchor,\n SelectionRangeInput,\n SelectionSnapshot,\n TextPosition,\n TextRange,\n} from './types';\nexport type { SelectionSegment } from './geometry';\n"],"mappings":";;AAMA,MAAa,+CAAiBA,CAAAA,CAAAA"}
@@ -0,0 +1,7 @@
1
+ import { c as SelectionRangeInput, f as TextPosition, h as SelectionSegment, i as SelectionEndpoint, l as SelectionSnapshot, o as SelectionMenuAnchor, p as TextRange, r as SelectionCapability } from "./types-CUrUVl6K.cjs";
2
+ import { CapabilityToken } from "@embedpdf/core";
3
+ //#region src/contract.d.ts
4
+ declare const SelectionToken: CapabilityToken<SelectionCapability>;
5
+ //#endregion
6
+ export { type SelectionCapability, type SelectionEndpoint, type SelectionMenuAnchor, type SelectionRangeInput, type SelectionSegment, type SelectionSnapshot, SelectionToken, type TextPosition, type TextRange };
7
+ //# sourceMappingURL=contract.d.cts.map
@@ -0,0 +1,7 @@
1
+ import { c as SelectionRangeInput, f as TextPosition, h as SelectionSegment, i as SelectionEndpoint, l as SelectionSnapshot, o as SelectionMenuAnchor, p as TextRange, r as SelectionCapability } from "./types-CUrUVl6K.js";
2
+ import { CapabilityToken } from "@embedpdf/core";
3
+ //#region src/contract.d.ts
4
+ declare const SelectionToken: CapabilityToken<SelectionCapability>;
5
+ //#endregion
6
+ export { type SelectionCapability, type SelectionEndpoint, type SelectionMenuAnchor, type SelectionRangeInput, type SelectionSegment, type SelectionSnapshot, SelectionToken, type TextPosition, type TextRange };
7
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { t as SelectionToken$1 } from "./types-BVUcOsCh.js";
2
+ //#region src/contract.ts
3
+ const SelectionToken = SelectionToken$1;
4
+ //#endregion
5
+ export { SelectionToken };
6
+
7
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","names":["SelectionHostToken"],"sources":["../src/contract.ts"],"sourcesContent":["/** Public selection capability protocol without gesture/reducer/plugin wiring. */\nimport type { CapabilityToken } from '@embedpdf/core';\n\nimport { SelectionToken as SelectionHostToken } from './types';\nimport type { SelectionCapability } from './types';\n\nexport const SelectionToken = SelectionHostToken as unknown as CapabilityToken<SelectionCapability>;\nexport type {\n SelectionCapability,\n SelectionEndpoint,\n SelectionMenuAnchor,\n SelectionRangeInput,\n SelectionSnapshot,\n TextPosition,\n TextRange,\n} from './types';\nexport type { SelectionSegment } from './geometry';\n"],"mappings":";;AAMA,MAAa,iBAAiBA"}
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_reducer = require("./reducer-dmDFWFVx.cjs");
2
+ const require_reducer = require("./reducer-BAOQ2CJ8.cjs");
3
+ const require_types = require("./types-D9kEBMFj.cjs");
4
+ const require_contract = require("./contract.cjs");
3
5
  let _embedpdf_core = require("@embedpdf/core");
4
- let _embedpdf_plugin_interaction = require("@embedpdf/plugin-interaction");
6
+ let _embedpdf_plugin_interaction_contract = require("@embedpdf/plugin-interaction/contract");
5
7
  let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
6
8
  //#region src/selection.plugin.ts
7
9
  /**
@@ -16,17 +18,17 @@ let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
16
18
  */
17
19
  const selectionPlugin = () => (0, _embedpdf_core.definePlugin)({
18
20
  id: "selection",
19
- token: require_reducer.SelectionToken,
21
+ token: require_types.SelectionToken,
20
22
  scope: "document",
21
- requires: [_embedpdf_plugin_interaction.InteractionToken],
22
- optional: [_embedpdf_plugin_interaction.FeedbackToken],
23
+ requires: [_embedpdf_plugin_interaction_contract.InteractionToken],
24
+ optional: [_embedpdf_plugin_interaction_contract.FeedbackToken],
23
25
  initialState: require_reducer.initialSelectionState,
24
26
  reduce: require_reducer.selectionReducer,
25
27
  capability: require_reducer.createSelectionCapability,
26
28
  init: (ctx) => {
27
- const interaction = ctx.get(_embedpdf_plugin_interaction.InteractionToken);
28
- const selection = ctx.get(require_reducer.SelectionToken);
29
- const feedback = ctx.tryGet(_embedpdf_plugin_interaction.FeedbackToken);
29
+ const interaction = ctx.get(_embedpdf_plugin_interaction_contract.InteractionToken);
30
+ const selection = ctx.get(require_types.SelectionToken);
31
+ const feedback = ctx.tryGet(_embedpdf_plugin_interaction_contract.FeedbackToken);
30
32
  interaction.registerHandler(require_reducer.createTextSelectHandler(selection, interaction, feedback ?? void 0));
31
33
  }
32
34
  });
@@ -134,19 +136,10 @@ function createSelectionHandleDrag(selection, view, opposite, draggedPon) {
134
136
  };
135
137
  }
136
138
  //#endregion
137
- //#region src/index.ts
138
- /**
139
- * The selection capability token, narrowed to the public
140
- * {@link SelectionCapability} lens. It is the SAME runtime token the plugin
141
- * provides — the host-only surface (pointer gestures, geometry warming) is
142
- * reachable only via `@embedpdf/plugin-selection/internal`.
143
- */
144
- const SelectionToken = require_reducer.SelectionToken;
145
- //#endregion
146
139
  exports.HANDLE_BAR = HANDLE_BAR;
147
140
  exports.HANDLE_HEAD = HANDLE_HEAD;
148
141
  exports.HANDLE_PAD = HANDLE_PAD;
149
- exports.SelectionToken = SelectionToken;
142
+ exports.SelectionToken = require_contract.SelectionToken;
150
143
  exports.createSelectionHandleDrag = createSelectionHandleDrag;
151
144
  exports.selectionHandleGeom = selectionHandleGeom;
152
145
  exports.selectionPlugin = selectionPlugin;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["SelectionToken","InteractionToken","FeedbackToken","initialSelectionState","selectionReducer","createSelectionCapability","createTextSelectHandler","SelectionHostToken"],"sources":["../src/selection.plugin.ts","../src/handles.ts","../src/index.ts"],"sourcesContent":["import { definePlugin } from '@embedpdf/core';\nimport { FeedbackToken, InteractionToken } from '@embedpdf/plugin-interaction';\nimport { createSelectionCapability } from './capability';\nimport { createTextSelectHandler } from './handler';\nimport { initialSelectionState, selectionReducer } from './reducer';\nimport { SelectionToken } from './types';\nimport type { SelectionAction, SelectionHostCapability, SelectionState } from './types';\n\n/**\n * Text selection — document-scoped, requires the interaction hub. In `init` it\n * registers ITS pointer handler with the hub; the hub owns the pointer stream\n * and arbitration. Works with `<Stage>` or a standalone `<PageView>` — selection\n * only needs the page coordinate context + the engine's text geometry.\n *\n * Registry/content invalidation is wired inside the capability itself\n * (revision watch + document event subscription), so headless/programmatic\n * use gets the same reconciliation as the full viewer.\n */\nexport const selectionPlugin = () =>\n definePlugin<SelectionState, SelectionAction, SelectionHostCapability>({\n id: 'selection',\n token: SelectionToken,\n scope: 'document',\n requires: [InteractionToken],\n // Platform feedback (haptics) is OPTIONAL: absent in headless setups, the\n // handler simply never buzzes.\n optional: [FeedbackToken],\n initialState: initialSelectionState,\n reduce: selectionReducer,\n capability: createSelectionCapability,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const selection = ctx.get(SelectionToken); // our own capability (built before init)\n const feedback = ctx.tryGet(FeedbackToken);\n interaction.registerHandler(\n createTextSelectHandler(selection, interaction, feedback ?? undefined),\n );\n },\n });\n","/**\n * Selection handles — the pure policy half (the `wheel.ts` convention).\n *\n * A handle IS the boundary glyph's leading (or trailing) edge: a segment\n * between two corners of the glyph's ORIENTED cell, with the grab head just\n * beyond the ascent at the start / past the baseline at the end. Bar length,\n * angle, and head all derive from that one segment, so rotated text and\n * rotated pages are carried by construction — the AABB never enters.\n *\n * Everything here is DOM-free and framework-free: geometry is pure given a\n * projector, and the drag session speaks the selection's own gesture verbs\n * (`beginAt`/`extendTo`/`end` — the same ones the pointer handler uses; a\n * handle drag IS a selection gesture, re-anchored). The framework adapters\n * keep only subscriptions and markup; the DOM listener mechanics live in\n * `@embedpdf/web`'s `attachSelectionHandle`.\n *\n * The view dependency is STRUCTURAL (satisfied by five lines over\n * `StageCapability`) so this plugin stays stage-free — selection also runs\n * in stage-less hosts (`PageView`) — and the math tests run against a fake.\n */\nimport { textQuadEdge } from '@embedpdf/core-geometry';\nimport type { Point, TextQuad } from '@embedpdf/core-geometry';\nimport type { PageObjectNumber } from '@embedpdf/core';\n\n/** What handle geometry & drags need from the hosting view. */\nexport interface SelectionHandleView {\n /** Page content point → overlay px. Must be POINT-exact (compose\n * `pageToWorld` with `toScreen`); an AABB projector loses orientation. */\n toOverlay(pon: PageObjectNumber, pt: Point): Point | null;\n /** Overlay px → the page under it, or null over a gap. */\n pageAt(overlay: Point): { pon: PageObjectNumber; point: Point } | null;\n /** Overlay px → a SPECIFIC page's content space, unclamped. */\n pointOnPage(pon: PageObjectNumber, overlay: Point): Point | null;\n}\n\n/** One selection boundary, as the handle needs it (a `SelectionEndpoint` slice). */\nexport interface SelectionHandleEndpoint {\n pon: PageObjectNumber;\n /** The boundary glyph's own oriented cell, page content space. */\n glyphQuad: TextQuad;\n /** Reading direction of its segment (+1 = the frame's +x) — decides which\n * side of the cell is the selection's leading edge. */\n advance: 1 | -1;\n}\n\n/** The gesture verbs a handle drag drives — `SelectionHostCapability` satisfies it. */\nexport interface SelectionHandleTarget {\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n extendTo(pon: PageObjectNumber, point: Point): void;\n end(): void;\n}\n\n// The iOS caret-handle design, one source for every adapter: a thin BAR that\n// is the selection's edge, capped by a screen-constant circle; PAD is the\n// invisible finger padding around the visual.\nexport const HANDLE_HEAD = 12; // px — the circle\nexport const HANDLE_BAR = 2; // px — the caret bar\nexport const HANDLE_PAD = 14; // px — grab padding\n\n/** Tilts within ~0.05° of upright render untransformed (float-noise guard —\n * and the dominant case stays pixel-identical to an axis-aligned box). */\nconst HANDLE_ROT_EPSILON = 0.05;\n\nexport interface SelectionHandleGeom {\n /** The projected edge, ascent corner first, overlay px. */\n bar: { from: Point; to: Point };\n /** The glyph's ink height on screen — zoom scaling comes free. */\n length: number;\n /** Degrees clockwise; 0 when the text is upright on screen. */\n rotation: number;\n /** True within the epsilon of upright: render with NO transform. */\n upright: boolean;\n /** Centre of the grab head, overlay px. */\n head: Point;\n}\n\nconst midpoint = (a: Point, b: Point): Point => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n\n/**\n * The handle's geometry for one endpoint, in overlay space. Null when the\n * endpoint's page isn't laid out (or the cell is degenerate) — render nothing.\n */\nexport function selectionHandleGeom(\n view: SelectionHandleView,\n ep: SelectionHandleEndpoint,\n role: 'start' | 'end',\n): SelectionHandleGeom | null {\n // Which SIDE of the cell is this selection's edge is a reading-order\n // question (`advance`), never a geometric one.\n const leading = role === 'start' ? ep.advance > 0 : ep.advance < 0;\n const [upperPage, lowerPage] = textQuadEdge(ep.glyphQuad, leading ? 'start' : 'end');\n const upper = view.toOverlay(ep.pon, upperPage);\n const lower = view.toOverlay(ep.pon, lowerPage);\n if (!upper || !lower) return null;\n const dx = lower.x - upper.x;\n const dy = lower.y - upper.y;\n const length = Math.hypot(dx, dy);\n if (length === 0) return null;\n // 0° when the text is upright: the bar runs straight down the screen.\n const rotation = (Math.atan2(dy, dx) * 180) / Math.PI - 90;\n const upright = Math.abs(rotation) < HANDLE_ROT_EPSILON;\n const ux = dx / length;\n const uy = dy / length;\n const head =\n role === 'start'\n ? { x: upper.x - (ux * HANDLE_HEAD) / 2, y: upper.y - (uy * HANDLE_HEAD) / 2 }\n : { x: lower.x + (ux * HANDLE_HEAD) / 2, y: lower.y + (uy * HANDLE_HEAD) / 2 };\n return { bar: { from: upper, to: lower }, length, rotation, upright, head };\n}\n\nexport interface SelectionHandleDragSession {\n /** Feed the pointer's current overlay position. */\n move(overlay: Point): void;\n /** The pointer released (or was cancelled): settle the gesture. */\n end(): void;\n}\n\n/**\n * One armed handle drag. Dragging a handle extends from the OPPOSITE\n * endpoint: the first movement re-roots the selection gesture at that\n * endpoint's cell centre, and every position then extends toward the pointer\n * — snapping to glyphs and crossing pages exactly like a pointer drag,\n * firing the same change/commit signals. Over a gap the point projects onto\n * the last page hit (unclamped), so the selection keeps tracking instead of\n * freezing at a page edge. `end()` commits only if the drag actually\n * re-rooted; an untouched press settles nothing.\n */\nexport function createSelectionHandleDrag(\n selection: SelectionHandleTarget,\n view: SelectionHandleView,\n opposite: SelectionHandleEndpoint,\n draggedPon: PageObjectNumber,\n): SelectionHandleDragSession {\n // The fixed anchor: the opposite endpoint's cell CENTRE — orientation-free\n // (a parallelogram's diagonal midpoints coincide), so it lands inside the\n // glyph for rotated text too.\n const q = opposite.glyphQuad;\n const anchorPoint = midpoint(midpoint(q.upperStart, q.lowerEnd), midpoint(q.upperEnd, q.lowerStart));\n let begun = false;\n let lastPon = draggedPon;\n return {\n move: (overlay) => {\n if (!begun) {\n if (!selection.beginAt(opposite.pon, anchorPoint)) return;\n begun = true;\n }\n const hit = view.pageAt(overlay);\n if (hit) {\n lastPon = hit.pon;\n selection.extendTo(hit.pon, hit.point);\n } else {\n const p = view.pointOnPage(lastPon, overlay);\n if (p) selection.extendTo(lastPon, p);\n }\n },\n end: () => {\n if (begun) selection.end(); // settle → menu reappears, onCommit fires\n },\n };\n}\n","/**\n * @embedpdf/plugin-selection — text selection over the engine's text geometry.\n *\n * The PUBLIC surface: the plugin factory, the capability token (narrowed to\n * the public lens), and the range/read vocabulary. Selection ranges live in\n * CHARACTER space (half-open `TextRange` — the same space search hits\n * address), geometry needs `doc.text.select`, text extraction needs\n * `doc.text.copy`, and neither permission implies the other.\n *\n * Framework/host plumbing (gesture intents, geometry warming, the reducer,\n * the coordinate seam) lives behind `@embedpdf/plugin-selection/internal`.\n * Clipboard writes live in `@embedpdf/web` — this package is DOM-free.\n */\nimport type { CapabilityToken } from '@embedpdf/core';\nimport { SelectionToken as SelectionHostToken } from './types';\nimport type { SelectionCapability } from './types';\n\nexport { selectionPlugin } from './selection.plugin';\nexport type {\n SelectionCapability,\n SelectionEndpoint,\n SelectionMenuAnchor,\n SelectionRangeInput,\n SelectionSnapshot,\n TextPosition,\n TextRange,\n} from './types';\nexport type { SelectionSegment } from './geometry';\n// Selection-handle policy (the touch affordance): pure geometry + the drag\n// session, consumed by the framework adapters' handle views.\nexport {\n HANDLE_BAR,\n HANDLE_HEAD,\n HANDLE_PAD,\n createSelectionHandleDrag,\n selectionHandleGeom,\n} from './handles';\nexport type {\n SelectionHandleDragSession,\n SelectionHandleEndpoint,\n SelectionHandleGeom,\n SelectionHandleTarget,\n SelectionHandleView,\n} from './handles';\n\n/**\n * The selection capability token, narrowed to the public\n * {@link SelectionCapability} lens. It is the SAME runtime token the plugin\n * provides — the host-only surface (pointer gestures, geometry warming) is\n * reachable only via `@embedpdf/plugin-selection/internal`.\n */\nexport const SelectionToken =\n SelectionHostToken as unknown as CapabilityToken<SelectionCapability>;\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,yBAAA,GAAA,eAAA,aAAA,CAC4D;CACrE,IAAI;CACJ,OAAOA,gBAAAA;CACP,OAAO;CACP,UAAU,CAACC,6BAAAA,gBAAgB;CAG3B,UAAU,CAACC,6BAAAA,aAAa;CACxB,cAAcC,gBAAAA;CACd,QAAQC,gBAAAA;CACR,YAAYC,gBAAAA;CACZ,OAAO,QAAQ;EACb,MAAM,cAAc,IAAI,IAAIJ,6BAAAA,gBAAgB;EAC5C,MAAM,YAAY,IAAI,IAAID,gBAAAA,cAAc;EACxC,MAAM,WAAW,IAAI,OAAOE,6BAAAA,aAAa;EACzC,YAAY,gBACVI,gBAAAA,wBAAwB,WAAW,aAAa,YAAY,KAAA,CAAS,CACvE;CACF;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;ACiBH,MAAa,cAAc;AAC3B,MAAa,aAAa;AAC1B,MAAa,aAAa;;;AAI1B,MAAM,qBAAqB;AAe3B,MAAM,YAAY,GAAU,OAAqB;CAAE,IAAI,EAAE,IAAI,EAAE,KAAK;CAAG,IAAI,EAAE,IAAI,EAAE,KAAK;AAAE;;;;;AAM1F,SAAgB,oBACd,MACA,IACA,MAC4B;CAG5B,MAAM,UAAU,SAAS,UAAU,GAAG,UAAU,IAAI,GAAG,UAAU;CACjE,MAAM,CAAC,WAAW,cAAA,GAAA,wBAAA,aAAA,CAA0B,GAAG,WAAW,UAAU,UAAU,KAAK;CACnF,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,OAAO;CAC7B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;CAChC,IAAI,WAAW,GAAG,OAAO;CAEzB,MAAM,WAAY,KAAK,MAAM,IAAI,EAAE,IAAI,MAAO,KAAK,KAAK;CACxD,MAAM,UAAU,KAAK,IAAI,QAAQ,IAAI;CACrC,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,KAAK;CAChB,MAAM,OACJ,SAAS,UACL;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE,IAC3E;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE;CACjF,OAAO;EAAE,KAAK;GAAE,MAAM;GAAO,IAAI;EAAM;EAAG;EAAQ;EAAU;EAAS;CAAK;AAC5E;;;;;;;;;;;AAmBA,SAAgB,0BACd,WACA,MACA,UACA,YAC4B;CAI5B,MAAM,IAAI,SAAS;CACnB,MAAM,cAAc,SAAS,SAAS,EAAE,YAAY,EAAE,QAAQ,GAAG,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC;CACnG,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,OAAO;EACL,OAAO,YAAY;GACjB,IAAI,CAAC,OAAO;IACV,IAAI,CAAC,UAAU,QAAQ,SAAS,KAAK,WAAW,GAAG;IACnD,QAAQ;GACV;GACA,MAAM,MAAM,KAAK,OAAO,OAAO;GAC/B,IAAI,KAAK;IACP,UAAU,IAAI;IACd,UAAU,SAAS,IAAI,KAAK,IAAI,KAAK;GACvC,OAAO;IACL,MAAM,IAAI,KAAK,YAAY,SAAS,OAAO;IAC3C,IAAI,GAAG,UAAU,SAAS,SAAS,CAAC;GACtC;EACF;EACA,WAAW;GACT,IAAI,OAAO,UAAU,IAAI;EAC3B;CACF;AACF;;;;;;;;;AC5GA,MAAa,iBACXC,gBAAAA"}
1
+ {"version":3,"file":"index.cjs","names":["SelectionToken","InteractionToken","FeedbackToken","initialSelectionState","selectionReducer","createSelectionCapability","createTextSelectHandler"],"sources":["../src/selection.plugin.ts","../src/handles.ts"],"sourcesContent":["import { definePlugin } from '@embedpdf/core';\nimport { FeedbackToken, InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { createSelectionCapability } from './capability';\nimport { createTextSelectHandler } from './handler';\nimport { initialSelectionState, selectionReducer } from './reducer';\nimport { SelectionToken } from './types';\nimport type { SelectionAction, SelectionHostCapability, SelectionState } from './types';\n\n/**\n * Text selection — document-scoped, requires the interaction hub. In `init` it\n * registers ITS pointer handler with the hub; the hub owns the pointer stream\n * and arbitration. Works with `<Stage>` or a standalone `<PageView>` — selection\n * only needs the page coordinate context + the engine's text geometry.\n *\n * Registry/content invalidation is wired inside the capability itself\n * (revision watch + document event subscription), so headless/programmatic\n * use gets the same reconciliation as the full viewer.\n */\nexport const selectionPlugin = () =>\n definePlugin<SelectionState, SelectionAction, SelectionHostCapability>({\n id: 'selection',\n token: SelectionToken,\n scope: 'document',\n requires: [InteractionToken],\n // Platform feedback (haptics) is OPTIONAL: absent in headless setups, the\n // handler simply never buzzes.\n optional: [FeedbackToken],\n initialState: initialSelectionState,\n reduce: selectionReducer,\n capability: createSelectionCapability,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const selection = ctx.get(SelectionToken); // our own capability (built before init)\n const feedback = ctx.tryGet(FeedbackToken);\n interaction.registerHandler(\n createTextSelectHandler(selection, interaction, feedback ?? undefined),\n );\n },\n });\n","/**\n * Selection handles — the pure policy half (the `wheel.ts` convention).\n *\n * A handle IS the boundary glyph's leading (or trailing) edge: a segment\n * between two corners of the glyph's ORIENTED cell, with the grab head just\n * beyond the ascent at the start / past the baseline at the end. Bar length,\n * angle, and head all derive from that one segment, so rotated text and\n * rotated pages are carried by construction — the AABB never enters.\n *\n * Everything here is DOM-free and framework-free: geometry is pure given a\n * projector, and the drag session speaks the selection's own gesture verbs\n * (`beginAt`/`extendTo`/`end` — the same ones the pointer handler uses; a\n * handle drag IS a selection gesture, re-anchored). The framework adapters\n * keep only subscriptions and markup; the DOM listener mechanics live in\n * `@embedpdf/web`'s `attachSelectionHandle`.\n *\n * The view dependency is STRUCTURAL (satisfied by five lines over\n * `StageCapability`) so this plugin stays stage-free — selection also runs\n * in stage-less hosts (`PageView`) — and the math tests run against a fake.\n */\nimport { textQuadEdge } from '@embedpdf/core-geometry';\nimport type { Point, TextQuad } from '@embedpdf/core-geometry';\nimport type { PageObjectNumber } from '@embedpdf/core';\n\n/** What handle geometry & drags need from the hosting view. */\nexport interface SelectionHandleView {\n /** Page content point → overlay px. Must be POINT-exact (compose\n * `pageToWorld` with `toScreen`); an AABB projector loses orientation. */\n toOverlay(pon: PageObjectNumber, pt: Point): Point | null;\n /** Overlay px → the page under it, or null over a gap. */\n pageAt(overlay: Point): { pon: PageObjectNumber; point: Point } | null;\n /** Overlay px → a SPECIFIC page's content space, unclamped. */\n pointOnPage(pon: PageObjectNumber, overlay: Point): Point | null;\n}\n\n/** One selection boundary, as the handle needs it (a `SelectionEndpoint` slice). */\nexport interface SelectionHandleEndpoint {\n pon: PageObjectNumber;\n /** The boundary glyph's own oriented cell, page content space. */\n glyphQuad: TextQuad;\n /** Reading direction of its segment (+1 = the frame's +x) — decides which\n * side of the cell is the selection's leading edge. */\n advance: 1 | -1;\n}\n\n/** The gesture verbs a handle drag drives — `SelectionHostCapability` satisfies it. */\nexport interface SelectionHandleTarget {\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n extendTo(pon: PageObjectNumber, point: Point): void;\n end(): void;\n}\n\n// The iOS caret-handle design, one source for every adapter: a thin BAR that\n// is the selection's edge, capped by a screen-constant circle; PAD is the\n// invisible finger padding around the visual.\nexport const HANDLE_HEAD = 12; // px — the circle\nexport const HANDLE_BAR = 2; // px — the caret bar\nexport const HANDLE_PAD = 14; // px — grab padding\n\n/** Tilts within ~0.05° of upright render untransformed (float-noise guard —\n * and the dominant case stays pixel-identical to an axis-aligned box). */\nconst HANDLE_ROT_EPSILON = 0.05;\n\nexport interface SelectionHandleGeom {\n /** The projected edge, ascent corner first, overlay px. */\n bar: { from: Point; to: Point };\n /** The glyph's ink height on screen — zoom scaling comes free. */\n length: number;\n /** Degrees clockwise; 0 when the text is upright on screen. */\n rotation: number;\n /** True within the epsilon of upright: render with NO transform. */\n upright: boolean;\n /** Centre of the grab head, overlay px. */\n head: Point;\n}\n\nconst midpoint = (a: Point, b: Point): Point => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n\n/**\n * The handle's geometry for one endpoint, in overlay space. Null when the\n * endpoint's page isn't laid out (or the cell is degenerate) — render nothing.\n */\nexport function selectionHandleGeom(\n view: SelectionHandleView,\n ep: SelectionHandleEndpoint,\n role: 'start' | 'end',\n): SelectionHandleGeom | null {\n // Which SIDE of the cell is this selection's edge is a reading-order\n // question (`advance`), never a geometric one.\n const leading = role === 'start' ? ep.advance > 0 : ep.advance < 0;\n const [upperPage, lowerPage] = textQuadEdge(ep.glyphQuad, leading ? 'start' : 'end');\n const upper = view.toOverlay(ep.pon, upperPage);\n const lower = view.toOverlay(ep.pon, lowerPage);\n if (!upper || !lower) return null;\n const dx = lower.x - upper.x;\n const dy = lower.y - upper.y;\n const length = Math.hypot(dx, dy);\n if (length === 0) return null;\n // 0° when the text is upright: the bar runs straight down the screen.\n const rotation = (Math.atan2(dy, dx) * 180) / Math.PI - 90;\n const upright = Math.abs(rotation) < HANDLE_ROT_EPSILON;\n const ux = dx / length;\n const uy = dy / length;\n const head =\n role === 'start'\n ? { x: upper.x - (ux * HANDLE_HEAD) / 2, y: upper.y - (uy * HANDLE_HEAD) / 2 }\n : { x: lower.x + (ux * HANDLE_HEAD) / 2, y: lower.y + (uy * HANDLE_HEAD) / 2 };\n return { bar: { from: upper, to: lower }, length, rotation, upright, head };\n}\n\nexport interface SelectionHandleDragSession {\n /** Feed the pointer's current overlay position. */\n move(overlay: Point): void;\n /** The pointer released (or was cancelled): settle the gesture. */\n end(): void;\n}\n\n/**\n * One armed handle drag. Dragging a handle extends from the OPPOSITE\n * endpoint: the first movement re-roots the selection gesture at that\n * endpoint's cell centre, and every position then extends toward the pointer\n * — snapping to glyphs and crossing pages exactly like a pointer drag,\n * firing the same change/commit signals. Over a gap the point projects onto\n * the last page hit (unclamped), so the selection keeps tracking instead of\n * freezing at a page edge. `end()` commits only if the drag actually\n * re-rooted; an untouched press settles nothing.\n */\nexport function createSelectionHandleDrag(\n selection: SelectionHandleTarget,\n view: SelectionHandleView,\n opposite: SelectionHandleEndpoint,\n draggedPon: PageObjectNumber,\n): SelectionHandleDragSession {\n // The fixed anchor: the opposite endpoint's cell CENTRE — orientation-free\n // (a parallelogram's diagonal midpoints coincide), so it lands inside the\n // glyph for rotated text too.\n const q = opposite.glyphQuad;\n const anchorPoint = midpoint(midpoint(q.upperStart, q.lowerEnd), midpoint(q.upperEnd, q.lowerStart));\n let begun = false;\n let lastPon = draggedPon;\n return {\n move: (overlay) => {\n if (!begun) {\n if (!selection.beginAt(opposite.pon, anchorPoint)) return;\n begun = true;\n }\n const hit = view.pageAt(overlay);\n if (hit) {\n lastPon = hit.pon;\n selection.extendTo(hit.pon, hit.point);\n } else {\n const p = view.pointOnPage(lastPon, overlay);\n if (p) selection.extendTo(lastPon, p);\n }\n },\n end: () => {\n if (begun) selection.end(); // settle → menu reappears, onCommit fires\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAa,yBAAA,GAAA,eAAA,aAAA,CAC4D;CACrE,IAAI;CACJ,OAAOA,cAAAA;CACP,OAAO;CACP,UAAU,CAACC,sCAAAA,gBAAgB;CAG3B,UAAU,CAACC,sCAAAA,aAAa;CACxB,cAAcC,gBAAAA;CACd,QAAQC,gBAAAA;CACR,YAAYC,gBAAAA;CACZ,OAAO,QAAQ;EACb,MAAM,cAAc,IAAI,IAAIJ,sCAAAA,gBAAgB;EAC5C,MAAM,YAAY,IAAI,IAAID,cAAAA,cAAc;EACxC,MAAM,WAAW,IAAI,OAAOE,sCAAAA,aAAa;EACzC,YAAY,gBACVI,gBAAAA,wBAAwB,WAAW,aAAa,YAAY,KAAA,CAAS,CACvE;CACF;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;ACiBH,MAAa,cAAc;AAC3B,MAAa,aAAa;AAC1B,MAAa,aAAa;;;AAI1B,MAAM,qBAAqB;AAe3B,MAAM,YAAY,GAAU,OAAqB;CAAE,IAAI,EAAE,IAAI,EAAE,KAAK;CAAG,IAAI,EAAE,IAAI,EAAE,KAAK;AAAE;;;;;AAM1F,SAAgB,oBACd,MACA,IACA,MAC4B;CAG5B,MAAM,UAAU,SAAS,UAAU,GAAG,UAAU,IAAI,GAAG,UAAU;CACjE,MAAM,CAAC,WAAW,cAAA,GAAA,wBAAA,aAAA,CAA0B,GAAG,WAAW,UAAU,UAAU,KAAK;CACnF,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,OAAO;CAC7B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;CAChC,IAAI,WAAW,GAAG,OAAO;CAEzB,MAAM,WAAY,KAAK,MAAM,IAAI,EAAE,IAAI,MAAO,KAAK,KAAK;CACxD,MAAM,UAAU,KAAK,IAAI,QAAQ,IAAI;CACrC,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,KAAK;CAChB,MAAM,OACJ,SAAS,UACL;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE,IAC3E;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE;CACjF,OAAO;EAAE,KAAK;GAAE,MAAM;GAAO,IAAI;EAAM;EAAG;EAAQ;EAAU;EAAS;CAAK;AAC5E;;;;;;;;;;;AAmBA,SAAgB,0BACd,WACA,MACA,UACA,YAC4B;CAI5B,MAAM,IAAI,SAAS;CACnB,MAAM,cAAc,SAAS,SAAS,EAAE,YAAY,EAAE,QAAQ,GAAG,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC;CACnG,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,OAAO;EACL,OAAO,YAAY;GACjB,IAAI,CAAC,OAAO;IACV,IAAI,CAAC,UAAU,QAAQ,SAAS,KAAK,WAAW,GAAG;IACnD,QAAQ;GACV;GACA,MAAM,MAAM,KAAK,OAAO,OAAO;GAC/B,IAAI,KAAK;IACP,UAAU,IAAI;IACd,UAAU,SAAS,IAAI,KAAK,IAAI,KAAK;GACvC,OAAO;IACL,MAAM,IAAI,KAAK,YAAY,SAAS,OAAO;IAC3C,IAAI,GAAG,UAAU,SAAS,SAAS,CAAC;GACtC;EACF;EACA,WAAW;GACT,IAAI,OAAO,UAAU,IAAI;EAC3B;CACF;AACF"}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { a as SelectionHostCapability, c as SelectionRangeInput, f as TextPosition, h as SelectionSegment, i as SelectionEndpoint, l as SelectionSnapshot, n as SelectionAction, o as SelectionMenuAnchor, p as TextRange, r as SelectionCapability, u as SelectionState } from "./types-DCsyRlZS.cjs";
2
- import { CapabilityToken, PageObjectNumber } from "@embedpdf/core";
1
+ import { a as SelectionHostCapability, c as SelectionRangeInput, f as TextPosition, h as SelectionSegment, i as SelectionEndpoint, l as SelectionSnapshot, n as SelectionAction, o as SelectionMenuAnchor, p as TextRange, r as SelectionCapability, u as SelectionState } from "./types-CUrUVl6K.cjs";
2
+ import { SelectionToken } from "./contract.cjs";
3
+ import { PageObjectNumber } from "@embedpdf/core";
3
4
  import { Point, TextQuad } from "@embedpdf/core-geometry";
4
5
  //#region src/selection.plugin.d.ts
5
6
  /**
@@ -84,14 +85,5 @@ interface SelectionHandleDragSession {
84
85
  */
85
86
  declare function createSelectionHandleDrag(selection: SelectionHandleTarget, view: SelectionHandleView, opposite: SelectionHandleEndpoint, draggedPon: PageObjectNumber): SelectionHandleDragSession;
86
87
  //#endregion
87
- //#region src/index.d.ts
88
- /**
89
- * The selection capability token, narrowed to the public
90
- * {@link SelectionCapability} lens. It is the SAME runtime token the plugin
91
- * provides — the host-only surface (pointer gestures, geometry warming) is
92
- * reachable only via `@embedpdf/plugin-selection/internal`.
93
- */
94
- declare const SelectionToken: CapabilityToken<SelectionCapability>;
95
- //#endregion
96
88
  export { HANDLE_BAR, HANDLE_HEAD, HANDLE_PAD, type SelectionCapability, type SelectionEndpoint, type SelectionHandleDragSession, type SelectionHandleEndpoint, type SelectionHandleGeom, type SelectionHandleTarget, type SelectionHandleView, type SelectionMenuAnchor, type SelectionRangeInput, type SelectionSegment, type SelectionSnapshot, SelectionToken, type TextPosition, type TextRange, createSelectionHandleDrag, selectionHandleGeom, selectionPlugin };
97
89
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { a as SelectionHostCapability, c as SelectionRangeInput, f as TextPosition, h as SelectionSegment, i as SelectionEndpoint, l as SelectionSnapshot, n as SelectionAction, o as SelectionMenuAnchor, p as TextRange, r as SelectionCapability, u as SelectionState } from "./types-DCsyRlZS.js";
2
- import { CapabilityToken, PageObjectNumber } from "@embedpdf/core";
1
+ import { a as SelectionHostCapability, c as SelectionRangeInput, f as TextPosition, h as SelectionSegment, i as SelectionEndpoint, l as SelectionSnapshot, n as SelectionAction, o as SelectionMenuAnchor, p as TextRange, r as SelectionCapability, u as SelectionState } from "./types-CUrUVl6K.js";
2
+ import { SelectionToken } from "./contract.js";
3
+ import { PageObjectNumber } from "@embedpdf/core";
3
4
  import { Point, TextQuad } from "@embedpdf/core-geometry";
4
5
  //#region src/selection.plugin.d.ts
5
6
  /**
@@ -84,14 +85,5 @@ interface SelectionHandleDragSession {
84
85
  */
85
86
  declare function createSelectionHandleDrag(selection: SelectionHandleTarget, view: SelectionHandleView, opposite: SelectionHandleEndpoint, draggedPon: PageObjectNumber): SelectionHandleDragSession;
86
87
  //#endregion
87
- //#region src/index.d.ts
88
- /**
89
- * The selection capability token, narrowed to the public
90
- * {@link SelectionCapability} lens. It is the SAME runtime token the plugin
91
- * provides — the host-only surface (pointer gestures, geometry warming) is
92
- * reachable only via `@embedpdf/plugin-selection/internal`.
93
- */
94
- declare const SelectionToken: CapabilityToken<SelectionCapability>;
95
- //#endregion
96
88
  export { HANDLE_BAR, HANDLE_HEAD, HANDLE_PAD, type SelectionCapability, type SelectionEndpoint, type SelectionHandleDragSession, type SelectionHandleEndpoint, type SelectionHandleGeom, type SelectionHandleTarget, type SelectionHandleView, type SelectionMenuAnchor, type SelectionRangeInput, type SelectionSegment, type SelectionSnapshot, SelectionToken, type TextPosition, type TextRange, createSelectionHandleDrag, selectionHandleGeom, selectionPlugin };
97
89
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
- import { i as createSelectionCapability, l as SelectionToken$1, n as selectionReducer, r as createTextSelectHandler, t as initialSelectionState } from "./reducer-Dy0YPuX0.js";
1
+ import { i as createSelectionCapability, n as selectionReducer, r as createTextSelectHandler, t as initialSelectionState } from "./reducer-DbKtk9Xl.js";
2
+ import { t as SelectionToken$1 } from "./types-BVUcOsCh.js";
3
+ import { SelectionToken } from "./contract.js";
2
4
  import { definePlugin } from "@embedpdf/core";
3
- import { FeedbackToken, InteractionToken } from "@embedpdf/plugin-interaction";
5
+ import { FeedbackToken, InteractionToken } from "@embedpdf/plugin-interaction/contract";
4
6
  import { textQuadEdge } from "@embedpdf/core-geometry";
5
7
  //#region src/selection.plugin.ts
6
8
  /**
@@ -133,15 +135,6 @@ function createSelectionHandleDrag(selection, view, opposite, draggedPon) {
133
135
  };
134
136
  }
135
137
  //#endregion
136
- //#region src/index.ts
137
- /**
138
- * The selection capability token, narrowed to the public
139
- * {@link SelectionCapability} lens. It is the SAME runtime token the plugin
140
- * provides — the host-only surface (pointer gestures, geometry warming) is
141
- * reachable only via `@embedpdf/plugin-selection/internal`.
142
- */
143
- const SelectionToken = SelectionToken$1;
144
- //#endregion
145
138
  export { HANDLE_BAR, HANDLE_HEAD, HANDLE_PAD, SelectionToken, createSelectionHandleDrag, selectionHandleGeom, selectionPlugin };
146
139
 
147
140
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["SelectionToken","SelectionHostToken"],"sources":["../src/selection.plugin.ts","../src/handles.ts","../src/index.ts"],"sourcesContent":["import { definePlugin } from '@embedpdf/core';\nimport { FeedbackToken, InteractionToken } from '@embedpdf/plugin-interaction';\nimport { createSelectionCapability } from './capability';\nimport { createTextSelectHandler } from './handler';\nimport { initialSelectionState, selectionReducer } from './reducer';\nimport { SelectionToken } from './types';\nimport type { SelectionAction, SelectionHostCapability, SelectionState } from './types';\n\n/**\n * Text selection — document-scoped, requires the interaction hub. In `init` it\n * registers ITS pointer handler with the hub; the hub owns the pointer stream\n * and arbitration. Works with `<Stage>` or a standalone `<PageView>` — selection\n * only needs the page coordinate context + the engine's text geometry.\n *\n * Registry/content invalidation is wired inside the capability itself\n * (revision watch + document event subscription), so headless/programmatic\n * use gets the same reconciliation as the full viewer.\n */\nexport const selectionPlugin = () =>\n definePlugin<SelectionState, SelectionAction, SelectionHostCapability>({\n id: 'selection',\n token: SelectionToken,\n scope: 'document',\n requires: [InteractionToken],\n // Platform feedback (haptics) is OPTIONAL: absent in headless setups, the\n // handler simply never buzzes.\n optional: [FeedbackToken],\n initialState: initialSelectionState,\n reduce: selectionReducer,\n capability: createSelectionCapability,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const selection = ctx.get(SelectionToken); // our own capability (built before init)\n const feedback = ctx.tryGet(FeedbackToken);\n interaction.registerHandler(\n createTextSelectHandler(selection, interaction, feedback ?? undefined),\n );\n },\n });\n","/**\n * Selection handles — the pure policy half (the `wheel.ts` convention).\n *\n * A handle IS the boundary glyph's leading (or trailing) edge: a segment\n * between two corners of the glyph's ORIENTED cell, with the grab head just\n * beyond the ascent at the start / past the baseline at the end. Bar length,\n * angle, and head all derive from that one segment, so rotated text and\n * rotated pages are carried by construction — the AABB never enters.\n *\n * Everything here is DOM-free and framework-free: geometry is pure given a\n * projector, and the drag session speaks the selection's own gesture verbs\n * (`beginAt`/`extendTo`/`end` — the same ones the pointer handler uses; a\n * handle drag IS a selection gesture, re-anchored). The framework adapters\n * keep only subscriptions and markup; the DOM listener mechanics live in\n * `@embedpdf/web`'s `attachSelectionHandle`.\n *\n * The view dependency is STRUCTURAL (satisfied by five lines over\n * `StageCapability`) so this plugin stays stage-free — selection also runs\n * in stage-less hosts (`PageView`) — and the math tests run against a fake.\n */\nimport { textQuadEdge } from '@embedpdf/core-geometry';\nimport type { Point, TextQuad } from '@embedpdf/core-geometry';\nimport type { PageObjectNumber } from '@embedpdf/core';\n\n/** What handle geometry & drags need from the hosting view. */\nexport interface SelectionHandleView {\n /** Page content point → overlay px. Must be POINT-exact (compose\n * `pageToWorld` with `toScreen`); an AABB projector loses orientation. */\n toOverlay(pon: PageObjectNumber, pt: Point): Point | null;\n /** Overlay px → the page under it, or null over a gap. */\n pageAt(overlay: Point): { pon: PageObjectNumber; point: Point } | null;\n /** Overlay px → a SPECIFIC page's content space, unclamped. */\n pointOnPage(pon: PageObjectNumber, overlay: Point): Point | null;\n}\n\n/** One selection boundary, as the handle needs it (a `SelectionEndpoint` slice). */\nexport interface SelectionHandleEndpoint {\n pon: PageObjectNumber;\n /** The boundary glyph's own oriented cell, page content space. */\n glyphQuad: TextQuad;\n /** Reading direction of its segment (+1 = the frame's +x) — decides which\n * side of the cell is the selection's leading edge. */\n advance: 1 | -1;\n}\n\n/** The gesture verbs a handle drag drives — `SelectionHostCapability` satisfies it. */\nexport interface SelectionHandleTarget {\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n extendTo(pon: PageObjectNumber, point: Point): void;\n end(): void;\n}\n\n// The iOS caret-handle design, one source for every adapter: a thin BAR that\n// is the selection's edge, capped by a screen-constant circle; PAD is the\n// invisible finger padding around the visual.\nexport const HANDLE_HEAD = 12; // px — the circle\nexport const HANDLE_BAR = 2; // px — the caret bar\nexport const HANDLE_PAD = 14; // px — grab padding\n\n/** Tilts within ~0.05° of upright render untransformed (float-noise guard —\n * and the dominant case stays pixel-identical to an axis-aligned box). */\nconst HANDLE_ROT_EPSILON = 0.05;\n\nexport interface SelectionHandleGeom {\n /** The projected edge, ascent corner first, overlay px. */\n bar: { from: Point; to: Point };\n /** The glyph's ink height on screen — zoom scaling comes free. */\n length: number;\n /** Degrees clockwise; 0 when the text is upright on screen. */\n rotation: number;\n /** True within the epsilon of upright: render with NO transform. */\n upright: boolean;\n /** Centre of the grab head, overlay px. */\n head: Point;\n}\n\nconst midpoint = (a: Point, b: Point): Point => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n\n/**\n * The handle's geometry for one endpoint, in overlay space. Null when the\n * endpoint's page isn't laid out (or the cell is degenerate) — render nothing.\n */\nexport function selectionHandleGeom(\n view: SelectionHandleView,\n ep: SelectionHandleEndpoint,\n role: 'start' | 'end',\n): SelectionHandleGeom | null {\n // Which SIDE of the cell is this selection's edge is a reading-order\n // question (`advance`), never a geometric one.\n const leading = role === 'start' ? ep.advance > 0 : ep.advance < 0;\n const [upperPage, lowerPage] = textQuadEdge(ep.glyphQuad, leading ? 'start' : 'end');\n const upper = view.toOverlay(ep.pon, upperPage);\n const lower = view.toOverlay(ep.pon, lowerPage);\n if (!upper || !lower) return null;\n const dx = lower.x - upper.x;\n const dy = lower.y - upper.y;\n const length = Math.hypot(dx, dy);\n if (length === 0) return null;\n // 0° when the text is upright: the bar runs straight down the screen.\n const rotation = (Math.atan2(dy, dx) * 180) / Math.PI - 90;\n const upright = Math.abs(rotation) < HANDLE_ROT_EPSILON;\n const ux = dx / length;\n const uy = dy / length;\n const head =\n role === 'start'\n ? { x: upper.x - (ux * HANDLE_HEAD) / 2, y: upper.y - (uy * HANDLE_HEAD) / 2 }\n : { x: lower.x + (ux * HANDLE_HEAD) / 2, y: lower.y + (uy * HANDLE_HEAD) / 2 };\n return { bar: { from: upper, to: lower }, length, rotation, upright, head };\n}\n\nexport interface SelectionHandleDragSession {\n /** Feed the pointer's current overlay position. */\n move(overlay: Point): void;\n /** The pointer released (or was cancelled): settle the gesture. */\n end(): void;\n}\n\n/**\n * One armed handle drag. Dragging a handle extends from the OPPOSITE\n * endpoint: the first movement re-roots the selection gesture at that\n * endpoint's cell centre, and every position then extends toward the pointer\n * — snapping to glyphs and crossing pages exactly like a pointer drag,\n * firing the same change/commit signals. Over a gap the point projects onto\n * the last page hit (unclamped), so the selection keeps tracking instead of\n * freezing at a page edge. `end()` commits only if the drag actually\n * re-rooted; an untouched press settles nothing.\n */\nexport function createSelectionHandleDrag(\n selection: SelectionHandleTarget,\n view: SelectionHandleView,\n opposite: SelectionHandleEndpoint,\n draggedPon: PageObjectNumber,\n): SelectionHandleDragSession {\n // The fixed anchor: the opposite endpoint's cell CENTRE — orientation-free\n // (a parallelogram's diagonal midpoints coincide), so it lands inside the\n // glyph for rotated text too.\n const q = opposite.glyphQuad;\n const anchorPoint = midpoint(midpoint(q.upperStart, q.lowerEnd), midpoint(q.upperEnd, q.lowerStart));\n let begun = false;\n let lastPon = draggedPon;\n return {\n move: (overlay) => {\n if (!begun) {\n if (!selection.beginAt(opposite.pon, anchorPoint)) return;\n begun = true;\n }\n const hit = view.pageAt(overlay);\n if (hit) {\n lastPon = hit.pon;\n selection.extendTo(hit.pon, hit.point);\n } else {\n const p = view.pointOnPage(lastPon, overlay);\n if (p) selection.extendTo(lastPon, p);\n }\n },\n end: () => {\n if (begun) selection.end(); // settle → menu reappears, onCommit fires\n },\n };\n}\n","/**\n * @embedpdf/plugin-selection — text selection over the engine's text geometry.\n *\n * The PUBLIC surface: the plugin factory, the capability token (narrowed to\n * the public lens), and the range/read vocabulary. Selection ranges live in\n * CHARACTER space (half-open `TextRange` — the same space search hits\n * address), geometry needs `doc.text.select`, text extraction needs\n * `doc.text.copy`, and neither permission implies the other.\n *\n * Framework/host plumbing (gesture intents, geometry warming, the reducer,\n * the coordinate seam) lives behind `@embedpdf/plugin-selection/internal`.\n * Clipboard writes live in `@embedpdf/web` — this package is DOM-free.\n */\nimport type { CapabilityToken } from '@embedpdf/core';\nimport { SelectionToken as SelectionHostToken } from './types';\nimport type { SelectionCapability } from './types';\n\nexport { selectionPlugin } from './selection.plugin';\nexport type {\n SelectionCapability,\n SelectionEndpoint,\n SelectionMenuAnchor,\n SelectionRangeInput,\n SelectionSnapshot,\n TextPosition,\n TextRange,\n} from './types';\nexport type { SelectionSegment } from './geometry';\n// Selection-handle policy (the touch affordance): pure geometry + the drag\n// session, consumed by the framework adapters' handle views.\nexport {\n HANDLE_BAR,\n HANDLE_HEAD,\n HANDLE_PAD,\n createSelectionHandleDrag,\n selectionHandleGeom,\n} from './handles';\nexport type {\n SelectionHandleDragSession,\n SelectionHandleEndpoint,\n SelectionHandleGeom,\n SelectionHandleTarget,\n SelectionHandleView,\n} from './handles';\n\n/**\n * The selection capability token, narrowed to the public\n * {@link SelectionCapability} lens. It is the SAME runtime token the plugin\n * provides — the host-only surface (pointer gestures, geometry warming) is\n * reachable only via `@embedpdf/plugin-selection/internal`.\n */\nexport const SelectionToken =\n SelectionHostToken as unknown as CapabilityToken<SelectionCapability>;\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,MAAa,wBACX,aAAuE;CACrE,IAAI;CACJ,OAAOA;CACP,OAAO;CACP,UAAU,CAAC,gBAAgB;CAG3B,UAAU,CAAC,aAAa;CACxB,cAAc;CACd,QAAQ;CACR,YAAY;CACZ,OAAO,QAAQ;EACb,MAAM,cAAc,IAAI,IAAI,gBAAgB;EAC5C,MAAM,YAAY,IAAI,IAAIA,gBAAc;EACxC,MAAM,WAAW,IAAI,OAAO,aAAa;EACzC,YAAY,gBACV,wBAAwB,WAAW,aAAa,YAAY,KAAA,CAAS,CACvE;CACF;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;ACiBH,MAAa,cAAc;AAC3B,MAAa,aAAa;AAC1B,MAAa,aAAa;;;AAI1B,MAAM,qBAAqB;AAe3B,MAAM,YAAY,GAAU,OAAqB;CAAE,IAAI,EAAE,IAAI,EAAE,KAAK;CAAG,IAAI,EAAE,IAAI,EAAE,KAAK;AAAE;;;;;AAM1F,SAAgB,oBACd,MACA,IACA,MAC4B;CAG5B,MAAM,UAAU,SAAS,UAAU,GAAG,UAAU,IAAI,GAAG,UAAU;CACjE,MAAM,CAAC,WAAW,aAAa,aAAa,GAAG,WAAW,UAAU,UAAU,KAAK;CACnF,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,OAAO;CAC7B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;CAChC,IAAI,WAAW,GAAG,OAAO;CAEzB,MAAM,WAAY,KAAK,MAAM,IAAI,EAAE,IAAI,MAAO,KAAK,KAAK;CACxD,MAAM,UAAU,KAAK,IAAI,QAAQ,IAAI;CACrC,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,KAAK;CAChB,MAAM,OACJ,SAAS,UACL;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE,IAC3E;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE;CACjF,OAAO;EAAE,KAAK;GAAE,MAAM;GAAO,IAAI;EAAM;EAAG;EAAQ;EAAU;EAAS;CAAK;AAC5E;;;;;;;;;;;AAmBA,SAAgB,0BACd,WACA,MACA,UACA,YAC4B;CAI5B,MAAM,IAAI,SAAS;CACnB,MAAM,cAAc,SAAS,SAAS,EAAE,YAAY,EAAE,QAAQ,GAAG,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC;CACnG,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,OAAO;EACL,OAAO,YAAY;GACjB,IAAI,CAAC,OAAO;IACV,IAAI,CAAC,UAAU,QAAQ,SAAS,KAAK,WAAW,GAAG;IACnD,QAAQ;GACV;GACA,MAAM,MAAM,KAAK,OAAO,OAAO;GAC/B,IAAI,KAAK;IACP,UAAU,IAAI;IACd,UAAU,SAAS,IAAI,KAAK,IAAI,KAAK;GACvC,OAAO;IACL,MAAM,IAAI,KAAK,YAAY,SAAS,OAAO;IAC3C,IAAI,GAAG,UAAU,SAAS,SAAS,CAAC;GACtC;EACF;EACA,WAAW;GACT,IAAI,OAAO,UAAU,IAAI;EAC3B;CACF;AACF;;;;;;;;;AC5GA,MAAa,iBACXC"}
1
+ {"version":3,"file":"index.js","names":["SelectionToken"],"sources":["../src/selection.plugin.ts","../src/handles.ts"],"sourcesContent":["import { definePlugin } from '@embedpdf/core';\nimport { FeedbackToken, InteractionToken } from '@embedpdf/plugin-interaction/contract';\nimport { createSelectionCapability } from './capability';\nimport { createTextSelectHandler } from './handler';\nimport { initialSelectionState, selectionReducer } from './reducer';\nimport { SelectionToken } from './types';\nimport type { SelectionAction, SelectionHostCapability, SelectionState } from './types';\n\n/**\n * Text selection — document-scoped, requires the interaction hub. In `init` it\n * registers ITS pointer handler with the hub; the hub owns the pointer stream\n * and arbitration. Works with `<Stage>` or a standalone `<PageView>` — selection\n * only needs the page coordinate context + the engine's text geometry.\n *\n * Registry/content invalidation is wired inside the capability itself\n * (revision watch + document event subscription), so headless/programmatic\n * use gets the same reconciliation as the full viewer.\n */\nexport const selectionPlugin = () =>\n definePlugin<SelectionState, SelectionAction, SelectionHostCapability>({\n id: 'selection',\n token: SelectionToken,\n scope: 'document',\n requires: [InteractionToken],\n // Platform feedback (haptics) is OPTIONAL: absent in headless setups, the\n // handler simply never buzzes.\n optional: [FeedbackToken],\n initialState: initialSelectionState,\n reduce: selectionReducer,\n capability: createSelectionCapability,\n init: (ctx) => {\n const interaction = ctx.get(InteractionToken);\n const selection = ctx.get(SelectionToken); // our own capability (built before init)\n const feedback = ctx.tryGet(FeedbackToken);\n interaction.registerHandler(\n createTextSelectHandler(selection, interaction, feedback ?? undefined),\n );\n },\n });\n","/**\n * Selection handles — the pure policy half (the `wheel.ts` convention).\n *\n * A handle IS the boundary glyph's leading (or trailing) edge: a segment\n * between two corners of the glyph's ORIENTED cell, with the grab head just\n * beyond the ascent at the start / past the baseline at the end. Bar length,\n * angle, and head all derive from that one segment, so rotated text and\n * rotated pages are carried by construction — the AABB never enters.\n *\n * Everything here is DOM-free and framework-free: geometry is pure given a\n * projector, and the drag session speaks the selection's own gesture verbs\n * (`beginAt`/`extendTo`/`end` — the same ones the pointer handler uses; a\n * handle drag IS a selection gesture, re-anchored). The framework adapters\n * keep only subscriptions and markup; the DOM listener mechanics live in\n * `@embedpdf/web`'s `attachSelectionHandle`.\n *\n * The view dependency is STRUCTURAL (satisfied by five lines over\n * `StageCapability`) so this plugin stays stage-free — selection also runs\n * in stage-less hosts (`PageView`) — and the math tests run against a fake.\n */\nimport { textQuadEdge } from '@embedpdf/core-geometry';\nimport type { Point, TextQuad } from '@embedpdf/core-geometry';\nimport type { PageObjectNumber } from '@embedpdf/core';\n\n/** What handle geometry & drags need from the hosting view. */\nexport interface SelectionHandleView {\n /** Page content point → overlay px. Must be POINT-exact (compose\n * `pageToWorld` with `toScreen`); an AABB projector loses orientation. */\n toOverlay(pon: PageObjectNumber, pt: Point): Point | null;\n /** Overlay px → the page under it, or null over a gap. */\n pageAt(overlay: Point): { pon: PageObjectNumber; point: Point } | null;\n /** Overlay px → a SPECIFIC page's content space, unclamped. */\n pointOnPage(pon: PageObjectNumber, overlay: Point): Point | null;\n}\n\n/** One selection boundary, as the handle needs it (a `SelectionEndpoint` slice). */\nexport interface SelectionHandleEndpoint {\n pon: PageObjectNumber;\n /** The boundary glyph's own oriented cell, page content space. */\n glyphQuad: TextQuad;\n /** Reading direction of its segment (+1 = the frame's +x) — decides which\n * side of the cell is the selection's leading edge. */\n advance: 1 | -1;\n}\n\n/** The gesture verbs a handle drag drives — `SelectionHostCapability` satisfies it. */\nexport interface SelectionHandleTarget {\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n extendTo(pon: PageObjectNumber, point: Point): void;\n end(): void;\n}\n\n// The iOS caret-handle design, one source for every adapter: a thin BAR that\n// is the selection's edge, capped by a screen-constant circle; PAD is the\n// invisible finger padding around the visual.\nexport const HANDLE_HEAD = 12; // px — the circle\nexport const HANDLE_BAR = 2; // px — the caret bar\nexport const HANDLE_PAD = 14; // px — grab padding\n\n/** Tilts within ~0.05° of upright render untransformed (float-noise guard —\n * and the dominant case stays pixel-identical to an axis-aligned box). */\nconst HANDLE_ROT_EPSILON = 0.05;\n\nexport interface SelectionHandleGeom {\n /** The projected edge, ascent corner first, overlay px. */\n bar: { from: Point; to: Point };\n /** The glyph's ink height on screen — zoom scaling comes free. */\n length: number;\n /** Degrees clockwise; 0 when the text is upright on screen. */\n rotation: number;\n /** True within the epsilon of upright: render with NO transform. */\n upright: boolean;\n /** Centre of the grab head, overlay px. */\n head: Point;\n}\n\nconst midpoint = (a: Point, b: Point): Point => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n\n/**\n * The handle's geometry for one endpoint, in overlay space. Null when the\n * endpoint's page isn't laid out (or the cell is degenerate) — render nothing.\n */\nexport function selectionHandleGeom(\n view: SelectionHandleView,\n ep: SelectionHandleEndpoint,\n role: 'start' | 'end',\n): SelectionHandleGeom | null {\n // Which SIDE of the cell is this selection's edge is a reading-order\n // question (`advance`), never a geometric one.\n const leading = role === 'start' ? ep.advance > 0 : ep.advance < 0;\n const [upperPage, lowerPage] = textQuadEdge(ep.glyphQuad, leading ? 'start' : 'end');\n const upper = view.toOverlay(ep.pon, upperPage);\n const lower = view.toOverlay(ep.pon, lowerPage);\n if (!upper || !lower) return null;\n const dx = lower.x - upper.x;\n const dy = lower.y - upper.y;\n const length = Math.hypot(dx, dy);\n if (length === 0) return null;\n // 0° when the text is upright: the bar runs straight down the screen.\n const rotation = (Math.atan2(dy, dx) * 180) / Math.PI - 90;\n const upright = Math.abs(rotation) < HANDLE_ROT_EPSILON;\n const ux = dx / length;\n const uy = dy / length;\n const head =\n role === 'start'\n ? { x: upper.x - (ux * HANDLE_HEAD) / 2, y: upper.y - (uy * HANDLE_HEAD) / 2 }\n : { x: lower.x + (ux * HANDLE_HEAD) / 2, y: lower.y + (uy * HANDLE_HEAD) / 2 };\n return { bar: { from: upper, to: lower }, length, rotation, upright, head };\n}\n\nexport interface SelectionHandleDragSession {\n /** Feed the pointer's current overlay position. */\n move(overlay: Point): void;\n /** The pointer released (or was cancelled): settle the gesture. */\n end(): void;\n}\n\n/**\n * One armed handle drag. Dragging a handle extends from the OPPOSITE\n * endpoint: the first movement re-roots the selection gesture at that\n * endpoint's cell centre, and every position then extends toward the pointer\n * — snapping to glyphs and crossing pages exactly like a pointer drag,\n * firing the same change/commit signals. Over a gap the point projects onto\n * the last page hit (unclamped), so the selection keeps tracking instead of\n * freezing at a page edge. `end()` commits only if the drag actually\n * re-rooted; an untouched press settles nothing.\n */\nexport function createSelectionHandleDrag(\n selection: SelectionHandleTarget,\n view: SelectionHandleView,\n opposite: SelectionHandleEndpoint,\n draggedPon: PageObjectNumber,\n): SelectionHandleDragSession {\n // The fixed anchor: the opposite endpoint's cell CENTRE — orientation-free\n // (a parallelogram's diagonal midpoints coincide), so it lands inside the\n // glyph for rotated text too.\n const q = opposite.glyphQuad;\n const anchorPoint = midpoint(midpoint(q.upperStart, q.lowerEnd), midpoint(q.upperEnd, q.lowerStart));\n let begun = false;\n let lastPon = draggedPon;\n return {\n move: (overlay) => {\n if (!begun) {\n if (!selection.beginAt(opposite.pon, anchorPoint)) return;\n begun = true;\n }\n const hit = view.pageAt(overlay);\n if (hit) {\n lastPon = hit.pon;\n selection.extendTo(hit.pon, hit.point);\n } else {\n const p = view.pointOnPage(lastPon, overlay);\n if (p) selection.extendTo(lastPon, p);\n }\n },\n end: () => {\n if (begun) selection.end(); // settle → menu reappears, onCommit fires\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBA,MAAa,wBACX,aAAuE;CACrE,IAAI;CACJ,OAAOA;CACP,OAAO;CACP,UAAU,CAAC,gBAAgB;CAG3B,UAAU,CAAC,aAAa;CACxB,cAAc;CACd,QAAQ;CACR,YAAY;CACZ,OAAO,QAAQ;EACb,MAAM,cAAc,IAAI,IAAI,gBAAgB;EAC5C,MAAM,YAAY,IAAI,IAAIA,gBAAc;EACxC,MAAM,WAAW,IAAI,OAAO,aAAa;EACzC,YAAY,gBACV,wBAAwB,WAAW,aAAa,YAAY,KAAA,CAAS,CACvE;CACF;AACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;ACiBH,MAAa,cAAc;AAC3B,MAAa,aAAa;AAC1B,MAAa,aAAa;;;AAI1B,MAAM,qBAAqB;AAe3B,MAAM,YAAY,GAAU,OAAqB;CAAE,IAAI,EAAE,IAAI,EAAE,KAAK;CAAG,IAAI,EAAE,IAAI,EAAE,KAAK;AAAE;;;;;AAM1F,SAAgB,oBACd,MACA,IACA,MAC4B;CAG5B,MAAM,UAAU,SAAS,UAAU,GAAG,UAAU,IAAI,GAAG,UAAU;CACjE,MAAM,CAAC,WAAW,aAAa,aAAa,GAAG,WAAW,UAAU,UAAU,KAAK;CACnF,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,SAAS;CAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,OAAO;CAC7B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,KAAK,MAAM,IAAI,MAAM;CAC3B,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;CAChC,IAAI,WAAW,GAAG,OAAO;CAEzB,MAAM,WAAY,KAAK,MAAM,IAAI,EAAE,IAAI,MAAO,KAAK,KAAK;CACxD,MAAM,UAAU,KAAK,IAAI,QAAQ,IAAI;CACrC,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,KAAK;CAChB,MAAM,OACJ,SAAS,UACL;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE,IAC3E;EAAE,GAAG,MAAM,IAAK,KAAA,KAAoB;EAAG,GAAG,MAAM,IAAK,KAAA,KAAoB;CAAE;CACjF,OAAO;EAAE,KAAK;GAAE,MAAM;GAAO,IAAI;EAAM;EAAG;EAAQ;EAAU;EAAS;CAAK;AAC5E;;;;;;;;;;;AAmBA,SAAgB,0BACd,WACA,MACA,UACA,YAC4B;CAI5B,MAAM,IAAI,SAAS;CACnB,MAAM,cAAc,SAAS,SAAS,EAAE,YAAY,EAAE,QAAQ,GAAG,SAAS,EAAE,UAAU,EAAE,UAAU,CAAC;CACnG,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,OAAO;EACL,OAAO,YAAY;GACjB,IAAI,CAAC,OAAO;IACV,IAAI,CAAC,UAAU,QAAQ,SAAS,KAAK,WAAW,GAAG;IACnD,QAAQ;GACV;GACA,MAAM,MAAM,KAAK,OAAO,OAAO;GAC/B,IAAI,KAAK;IACP,UAAU,IAAI;IACd,UAAU,SAAS,IAAI,KAAK,IAAI,KAAK;GACvC,OAAO;IACL,MAAM,IAAI,KAAK,YAAY,SAAS,OAAO;IAC3C,IAAI,GAAG,UAAU,SAAS,SAAS,CAAC;GACtC;EACF;EACA,WAAW;GACT,IAAI,OAAO,UAAU,IAAI;EAC3B;CACF;AACF"}
package/dist/internal.cjs CHANGED
@@ -1,6 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_reducer = require("./reducer-dmDFWFVx.cjs");
3
- exports.SelectionToken = require_reducer.SelectionToken;
2
+ const require_reducer = require("./reducer-BAOQ2CJ8.cjs");
3
+ const require_types = require("./types-D9kEBMFj.cjs");
4
+ require("./contract/host.cjs");
5
+ exports.SelectionToken = require_types.SelectionToken;
4
6
  exports.buildSelectionPageGeometry = require_reducer.buildSelectionPageGeometry;
5
7
  exports.contentPointToPdf = require_reducer.contentPointToPdf;
6
8
  exports.createSelectionCapability = require_reducer.createSelectionCapability;
@@ -1,6 +1,7 @@
1
- import { _ as contentPointToPdf, a as SelectionHostCapability, d as SelectionToken, g as buildSelectionPageGeometry, m as SelectionPageGeometry, n as SelectionAction, s as SelectionRange, t as GlyphPointer, u as SelectionState, v as toContentSegment, y as toContentTextQuad } from "./types-DCsyRlZS.cjs";
1
+ import { _ as contentPointToPdf, a as SelectionHostCapability, d as SelectionToken, g as buildSelectionPageGeometry, m as SelectionPageGeometry, n as SelectionAction, s as SelectionRange, t as GlyphPointer, u as SelectionState, v as toContentSegment, y as toContentTextQuad } from "./types-CUrUVl6K.cjs";
2
+ import "./contract/host.cjs";
2
3
  import { PluginContext } from "@embedpdf/core";
3
- import { InteractionCapability, InteractionHandler, PlatformFeedback } from "@embedpdf/plugin-interaction";
4
+ import { InteractionCapability, InteractionHandler, PlatformFeedback } from "@embedpdf/plugin-interaction/contract";
4
5
  //#region src/capability.d.ts
5
6
  /**
6
7
  * The selection capability — ONE closure returning the host lens (the
@@ -1,6 +1,7 @@
1
- import { _ as contentPointToPdf, a as SelectionHostCapability, d as SelectionToken, g as buildSelectionPageGeometry, m as SelectionPageGeometry, n as SelectionAction, s as SelectionRange, t as GlyphPointer, u as SelectionState, v as toContentSegment, y as toContentTextQuad } from "./types-DCsyRlZS.js";
1
+ import { _ as contentPointToPdf, a as SelectionHostCapability, d as SelectionToken, g as buildSelectionPageGeometry, m as SelectionPageGeometry, n as SelectionAction, s as SelectionRange, t as GlyphPointer, u as SelectionState, v as toContentSegment, y as toContentTextQuad } from "./types-CUrUVl6K.js";
2
+ import "./contract/host.js";
2
3
  import { PluginContext } from "@embedpdf/core";
3
- import { InteractionCapability, InteractionHandler, PlatformFeedback } from "@embedpdf/plugin-interaction";
4
+ import { InteractionCapability, InteractionHandler, PlatformFeedback } from "@embedpdf/plugin-interaction/contract";
4
5
  //#region src/capability.d.ts
5
6
  /**
6
7
  * The selection capability — ONE closure returning the host lens (the
package/dist/internal.js CHANGED
@@ -1,2 +1,4 @@
1
- import { a as buildSelectionPageGeometry, c as toContentTextQuad, i as createSelectionCapability, l as SelectionToken, n as selectionReducer, o as contentPointToPdf, r as createTextSelectHandler, s as toContentSegment, t as initialSelectionState } from "./reducer-Dy0YPuX0.js";
1
+ import { a as buildSelectionPageGeometry, c as toContentTextQuad, i as createSelectionCapability, n as selectionReducer, o as contentPointToPdf, r as createTextSelectHandler, s as toContentSegment, t as initialSelectionState } from "./reducer-DbKtk9Xl.js";
2
+ import { t as SelectionToken } from "./types-BVUcOsCh.js";
3
+ import "./contract/host.js";
2
4
  export { SelectionToken, buildSelectionPageGeometry, contentPointToPdf, createSelectionCapability, createTextSelectHandler, initialSelectionState, selectionReducer, toContentSegment, toContentTextQuad };
@@ -1,15 +1,5 @@
1
- let _embedpdf_core = require("@embedpdf/core");
2
1
  let _embedpdf_core_geometry = require("@embedpdf/core-geometry");
3
2
  let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
4
- //#region src/types.ts
5
- /**
6
- * The selection capability token. Typed to the full
7
- * {@link SelectionHostCapability} for this module and `/internal`; the
8
- * package root re-exports the SAME token narrowed to
9
- * {@link SelectionCapability}.
10
- */
11
- const SelectionToken = (0, _embedpdf_core.createCapabilityToken)("selection");
12
- //#endregion
13
3
  //#region src/geometry.ts
14
4
  /**
15
5
  * The selection plugin's COORDINATE SEAM — and nothing else.
@@ -690,12 +680,6 @@ const selectionReducer = (state, action) => {
690
680
  }
691
681
  };
692
682
  //#endregion
693
- Object.defineProperty(exports, "SelectionToken", {
694
- enumerable: true,
695
- get: function() {
696
- return SelectionToken;
697
- }
698
- });
699
683
  Object.defineProperty(exports, "buildSelectionPageGeometry", {
700
684
  enumerable: true,
701
685
  get: function() {
@@ -745,4 +729,4 @@ Object.defineProperty(exports, "toContentTextQuad", {
745
729
  }
746
730
  });
747
731
 
748
- //# sourceMappingURL=reducer-dmDFWFVx.cjs.map
732
+ //# sourceMappingURL=reducer-BAOQ2CJ8.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducer-BAOQ2CJ8.cjs","names":["PermissionDenied"],"sources":["../src/geometry.ts","../src/capability.ts","../src/handler.ts","../src/reducer.ts"],"sourcesContent":["/**\n * The selection plugin's COORDINATE SEAM — and nothing else.\n *\n * Segmentation, hit-testing, and word/line expansion live in the canonical\n * engine-core text layout (`@embedpdf/engine-core` `text/layout`): a text\n * range has exactly one segmentation whether it came from a drag or a\n * search. This module only converts between the viewer's content space\n * (y-down, crop-relative) and the engine's PDF space (y-up) — pointer\n * points inward, canonical segments outward. The PDF↔content y-flip is the\n * geometry package's `pageGeometry` (the one place that math lives), never\n * hand-rolled here.\n */\nimport {\n applyPoint,\n invert,\n pageGeometry,\n textQuadBounds,\n type Mat2D,\n type Point,\n type PointIn,\n type Rect,\n type TextQuad,\n} from '@embedpdf/core-geometry';\nimport {\n buildPageTextLayout,\n type PageGeometrySnapshot,\n type PageTextLayout,\n type PdfPoint,\n type PdfQuad,\n type PdfRect,\n type PdfTextSegment,\n} from '@embedpdf/engine-core/runtime';\n\n/**\n * One merged visual line of a selection, in content space — the structural\n * twin of the engine's `PdfTextSegment`. `quad` carries frame-geometric\n * corner semantics (upper = ascent side, start = the frame's −x); `advance`\n * is the READING direction along the baseline (+1 = toward `end`), derived\n * from the glyph sequence — geometry and bidi stay separate concerns.\n * `rect` is the content-space AABB.\n */\nexport interface SelectionSegment {\n quad: TextQuad;\n rect: Rect;\n advance: 1 | -1;\n}\n\n/** A page's cached selection geometry: the canonical layout + the seam. */\nexport interface SelectionPageGeometry {\n layout: PageTextLayout;\n toContent: Mat2D<'pdf', 'content'>;\n fromContent: Mat2D<'content', 'pdf'>;\n}\n\n/**\n * Build a page's selection geometry. Page display rotation does NOT enter\n * here — the overlay rides the page's CSS rotation, so content space stays\n * un-rotated (the crop-relative y-flip is the only conversion; zoom = 1,\n * the viewer's zoom is applied later by `PageTransform.toPixels`).\n */\nexport function buildSelectionPageGeometry(\n snapshot: PageGeometrySnapshot,\n crop: PdfRect,\n rotation: 0 | 90 | 180 | 270,\n userUnit: number,\n): SelectionPageGeometry {\n const { pdfToContent } = pageGeometry({ crop, rotation, userUnit }, 1);\n return {\n layout: buildPageTextLayout(snapshot),\n toContent: pdfToContent,\n fromContent: invert(pdfToContent),\n };\n}\n\n/** A content-space pointer point in the layout's PDF space. */\nexport function contentPointToPdf(geom: SelectionPageGeometry, p: Point): PdfPoint {\n return applyPoint(geom.fromContent, p as PointIn<'content'>);\n}\n\n/**\n * A canonical PDF quad in content space. Engine quads carry frame-geometric\n * slot semantics (`p1..p4` = upper-start, upper-end, lower-start,\n * lower-end) — a trusted producer, so the y-flip maps corners straight onto\n * their names; imported-annotation normalization does NOT apply here.\n */\nexport function toContentTextQuad(geom: SelectionPageGeometry, quad: PdfQuad): TextQuad {\n const pt = (p: PdfPoint): Point => applyPoint(geom.toContent, p as PointIn<'pdf'>);\n return {\n upperStart: pt(quad.p1),\n upperEnd: pt(quad.p2),\n lowerStart: pt(quad.p3),\n lowerEnd: pt(quad.p4),\n };\n}\n\n/** A canonical segment in content space (`rect` recomputed from the mapped\n * quad so the pair can never drift). */\nexport function toContentSegment(\n geom: SelectionPageGeometry,\n segment: PdfTextSegment,\n): SelectionSegment {\n const quad = toContentTextQuad(geom, segment.quad);\n return { quad, rect: textQuadBounds(quad), advance: segment.advance };\n}\n","import type { PageObjectNumber, PluginContext } from '@embedpdf/core';\nimport { textQuadBounds, type Point, type Rect } from '@embedpdf/core-geometry';\nimport {\n expandTextRangeToLine,\n expandTextRangeToWord,\n PermissionDenied,\n sliceTextByChars,\n textGlyphAt,\n textGlyphQuad,\n textSegmentsForRange,\n type PageGeometrySnapshot,\n type PageTextSnapshot,\n} from '@embedpdf/engine-core/runtime';\nimport {\n buildSelectionPageGeometry,\n contentPointToPdf,\n toContentSegment,\n toContentTextQuad,\n type SelectionPageGeometry,\n type SelectionSegment,\n} from './geometry';\nimport type {\n GlyphPointer,\n SelectionAction,\n SelectionEndpoint,\n SelectionHostCapability,\n SelectionMenuAnchor,\n SelectionRange,\n SelectionRangeInput,\n SelectionSnapshot,\n SelectionState,\n TextRange,\n} from './types';\n\nconst EMPTY_SEGMENTS: SelectionSegment[] = [];\n\n/** How many page-text reads `readText()` keeps in flight at once. */\nconst TEXT_READ_CONCURRENCY = 8;\n\n/**\n * The selection capability — ONE closure returning the host lens (the\n * public lens is the same object, narrowed at the package root).\n *\n * State model: the reducer holds the selection range and the derived\n * content-space segments; the large, non-serializable per-page caches live\n * HERE in the closure, split by what invalidates them:\n *\n * - `rawGeometry` — PDF-space geometry snapshots, rotation-independent\n * (pages load normalized; see `PageRotateResult`). Dropped only when a\n * page leaves the registry or page CONTENT changes (redaction apply /\n * flatten).\n * - `derived` — the content-space transform + layout, keyed by the\n * layout params (crop/rotation/userUnit) so a view-rotate re-derives\n * from the cached raw snapshot with NO refetch.\n * - `textSnapshots` — per-page text (for `readText`), same content-only\n * invalidation as `rawGeometry`.\n *\n * Permission model: `doc.text.select` gates geometry (selection exists),\n * `doc.text.copy` gates text (extraction) — neither implies the other.\n * Host gesture functions are INERT when select is denied (`ensurePage`\n * no-ops, so nothing warms and nothing hit-tests: the pointer handler\n * needs no special-casing); the public `select()` THROWS `PermissionDenied`\n * so programmatic misuse is loud; `clear()` is always allowed. The engine\n * independently enforces both scopes — these gates exist so the plugin\n * never issues a request that is guaranteed to fail.\n *\n * Selection is cross-page: glyphs are ordered globally by (pageIndex,\n * glyph), so a drag from page 2 into page 4 selects the tail of 2, all of\n * 3, and the head of 4. `recompute` rebuilds the merged line segments for\n * every loaded page in the span, re-runs whenever a mid-span page finishes\n * loading, and CLAMPS stored endpoints once their page's geometry is known\n * (so `selectAll`'s open-ended focus settles to the real last character).\n */\nexport function createSelectionCapability(\n ctx: PluginContext<SelectionState, SelectionAction>,\n): SelectionHostCapability {\n const rawGeometry = new Map<PageObjectNumber, PageGeometrySnapshot>();\n const derived = new Map<PageObjectNumber, { key: string; geom: SelectionPageGeometry }>();\n const pending = new Set<PageObjectNumber>();\n const textSnapshots = new Map<PageObjectNumber, Promise<PageTextSnapshot>>();\n /** Bumped on content invalidation; in-flight reads from before are discarded. */\n let epoch = 0;\n // Consumers (e.g. text-markup) observe the selection without selection knowing\n // about them: `change` fires whenever the segments change, `commit` when a\n // gesture ends (pointer-up). One typed callback each — not an event bus.\n const changeCbs = new Set<() => void>();\n const commitCbs = new Set<() => void>();\n const fireChange = (): void => changeCbs.forEach((cb) => cb());\n\n const canSelect = (): boolean => ctx.doc?.security.allows('doc.text.select') ?? false;\n const canCopy = (): boolean => ctx.doc?.security.allows('doc.text.copy') ?? false;\n\n const layoutOf = (pon: PageObjectNumber) =>\n ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n const pageIndexOf = (pon: PageObjectNumber): number =>\n ctx.document()?.pages.findIndex((p) => p.pageObjectNumber === pon) ?? -1;\n const ponAtIndex = (i: number): PageObjectNumber | undefined =>\n ctx.document()?.pages[i]?.pageObjectNumber;\n\n /**\n * The content-space geometry for a page, derived on demand from the raw\n * snapshot + the CURRENT layout params. A view-rotate (or crop change)\n * changes the key and re-derives — raw caches and the engine are never\n * touched (rotation is presentation metadata).\n */\n function geometryFor(pon: PageObjectNumber): SelectionPageGeometry | null {\n const raw = rawGeometry.get(pon);\n const layout = layoutOf(pon);\n if (!raw || !layout) return null;\n const crop = layout.boxes.crop;\n const key = `${layout.rotation}|${layout.userUnit}|${crop.left},${crop.bottom},${crop.right},${crop.top}`;\n const hit = derived.get(pon);\n if (hit && hit.key === key) return hit.geom;\n const geom = buildSelectionPageGeometry(raw, crop, layout.rotation, layout.userUnit);\n derived.set(pon, { key, geom });\n return geom;\n }\n\n const glyphAt = (geom: SelectionPageGeometry, point: Point): number | null =>\n textGlyphAt(geom.layout, contentPointToPdf(geom, point));\n\n // Order the two ends of a selection by document position (page, then glyph).\n function orderedEnds(sel: SelectionRange): {\n start: GlyphPointer;\n end: GlyphPointer;\n direction: 'forward' | 'backward';\n } {\n const ai = pageIndexOf(sel.anchor.pon);\n const fi = pageIndexOf(sel.focus.pon);\n const anchorFirst = ai < fi || (ai === fi && sel.anchor.glyph <= sel.focus.glyph);\n return anchorFirst\n ? { start: sel.anchor, end: sel.focus, direction: 'forward' }\n : { start: sel.focus, end: sel.anchor, direction: 'backward' };\n }\n\n function endpointFor(ptr: GlyphPointer, which: 'start' | 'end'): SelectionEndpoint | null {\n const segments = ctx.getState().segments[ptr.pon] ?? EMPTY_SEGMENTS;\n if (!segments.length) return null;\n const segment = which === 'start' ? segments[0] : segments[segments.length - 1];\n\n // Anchor the endpoint to the boundary GLYPH's own oriented cell so caret\n // placement lands on the exact character edge; fall back to the segment\n // when the glyph is degenerate (e.g. a generated space).\n const geom = geometryFor(ptr.pon);\n const cell = geom ? textGlyphQuad(geom.layout, ptr.glyph) : null;\n if (geom && cell) {\n const glyphQuad = toContentTextQuad(geom, cell);\n return {\n pon: ptr.pon,\n glyphQuad,\n advance: segment.advance,\n rect: textQuadBounds(glyphQuad),\n };\n }\n return { pon: ptr.pon, glyphQuad: segment.quad, advance: segment.advance, rect: segment.rect };\n }\n\n /** Union box of a page's segments, or null when none have materialized. */\n function pageBounds(pon: PageObjectNumber): Rect | null {\n const segs = ctx.getState().segments[pon];\n if (!segs || segs.length === 0) return null;\n let x1 = Infinity;\n let y1 = Infinity;\n let x2 = -Infinity;\n let y2 = -Infinity;\n for (const s of segs) {\n x1 = Math.min(x1, s.rect.x);\n y1 = Math.min(y1, s.rect.y);\n x2 = Math.max(x2, s.rect.x + s.rect.width);\n y2 = Math.max(y2, s.rect.y + s.rect.height);\n }\n return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };\n }\n\n function menuAnchor(): SelectionMenuAnchor | null {\n const sel = ctx.getState().selection;\n if (!sel) return null;\n const { end } = orderedEnds(sel);\n // Prefer the gesture's end page; while its geometry is still loading,\n // fall back to the LAST page (document order) with materialized segments\n // so the anchor never teleports backwards mid-drag.\n let bounds = pageBounds(end.pon);\n if (bounds) return { pon: end.pon, bounds };\n const pages = ctx.document()?.pages ?? [];\n for (let i = pages.length - 1; i >= 0; i--) {\n const pon = pages[i].pageObjectNumber;\n bounds = pageBounds(pon);\n if (bounds) return { pon, bounds };\n }\n return null;\n }\n\n function snapshot(): SelectionSnapshot {\n const { selection, segments } = ctx.getState();\n const pages = Object.keys(segments)\n .map(Number)\n .filter((pon) => (segments[pon]?.length ?? 0) > 0)\n .map((pon) => ({ pon: pon as PageObjectNumber, segments: segments[pon] }));\n if (!selection) return { pages, start: null, end: null, direction: 'forward', range: null };\n const { start, end, direction } = orderedEnds(selection);\n return {\n pages,\n start: endpointFor(start, 'start'),\n end: endpointFor(end, 'end'),\n direction,\n range: {\n start: { pon: start.pon, index: start.glyph },\n end: { pon: end.pon, index: end.glyph + 1 },\n },\n };\n }\n\n function ensurePage(pon: PageObjectNumber): void {\n if (rawGeometry.has(pon) || pending.has(pon)) return;\n // Authorized-only warming: without doc.text.select the read is\n // guaranteed to be rejected — don't issue it. The engine guard stays\n // the security boundary either way.\n if (!canSelect()) return;\n const doc = ctx.doc;\n if (!doc || !layoutOf(pon)) return;\n pending.add(pon);\n const at = epoch;\n doc\n .page(pon)\n .geometry.read()\n .then(\n (snapshot) => {\n pending.delete(pon);\n if (at !== epoch) return; // content changed while in flight — stale\n rawGeometry.set(pon, snapshot);\n ctx.dispatch({ type: 'PAGE_LOADED', pon });\n if (ctx.getState().selection) recompute(); // a mid-span page arrived → fill its segments\n },\n () => {\n pending.delete(pon); // doc closed / read aborted / denied — ignore\n },\n );\n }\n\n /** Clamp a pointer into its page's real character range once geometry is\n * known — how `selectAll`'s open-ended focus settles. */\n function clampPointer(ptr: GlyphPointer): GlyphPointer {\n const geom = geometryFor(ptr.pon);\n if (!geom) return ptr;\n const max = Math.max(geom.layout.glyphs.length - 1, 0);\n const glyph = Math.max(0, Math.min(ptr.glyph, max));\n return glyph === ptr.glyph ? ptr : { pon: ptr.pon, glyph };\n }\n\n // Rebuild merged line segments for every loaded page in the span; warm the\n // rest (they recompute on arrival). Clears when an endpoint's page has left\n // the registry — a range across a structural edit is meaningless.\n function recompute(sel: SelectionRange | null = ctx.getState().selection): void {\n if (!sel) return;\n const clamped: SelectionRange = {\n anchor: clampPointer(sel.anchor),\n focus: clampPointer(sel.focus),\n };\n const { start, end } = orderedEnds(clamped);\n const si = pageIndexOf(start.pon);\n const ei = pageIndexOf(end.pon);\n if (si < 0 || ei < 0) {\n clearSelection();\n return;\n }\n const segments: Record<number, SelectionSegment[]> = {};\n for (let i = si; i <= ei; i++) {\n const pon = ponAtIndex(i);\n if (pon == null) continue;\n const geom = geometryFor(pon);\n if (!geom) {\n ensurePage(pon); // not loaded yet — it'll recompute when ready\n continue;\n }\n const from = i === si ? start.glyph : 0;\n const to = i === ei ? end.glyph : geom.layout.glyphs.length - 1;\n segments[pon] = textSegmentsForRange(geom.layout, from, to - from + 1).map((s) =>\n toContentSegment(geom, s),\n );\n }\n ctx.dispatch({ type: 'SET', selection: clamped, segments });\n fireChange();\n }\n\n /** Flip the gesture-in-flight FACT (see `SelectionState.selecting`). Set\n * BEFORE the recompute that fires `onChange`, so change listeners always\n * observe a coherent (selecting, segments) pair. */\n function setSelecting(selecting: boolean): void {\n ctx.dispatch({ type: 'SET_SELECTING', selecting });\n }\n\n function clearSelection(): void {\n setSelecting(false);\n ctx.dispatch({ type: 'CLEAR' });\n fireChange();\n }\n\n // Set the selection to a flat [from,to] glyph span on one page (word/line).\n // These are GESTURE vocabulary (double-/triple-click) — the commit arrives\n // at the pointer-up that follows, so the span counts as in-flight until then.\n // Returns whether a span actually engaged (geometry present AND a glyph\n // under the point) — the fact haptics and other success-gated feedback key\n // on, so nothing ever buzzes over blank space.\n function selectSpanAt(pon: PageObjectNumber, point: Point, expand: 'word' | 'line'): boolean {\n const geom = geometryFor(pon);\n if (!geom) return false;\n const i = glyphAt(geom, point);\n if (i == null) return false;\n const [from, to] =\n expand === 'word'\n ? expandTextRangeToWord(geom.layout, i)\n : expandTextRangeToLine(geom.layout, i);\n setSelecting(true);\n recompute({ anchor: { pon, glyph: from }, focus: { pon, glyph: to } });\n return true;\n }\n\n function assertCanSelect(context: string): void {\n if (!canSelect()) throw new PermissionDenied('doc.text.select', context);\n }\n\n /** The public programmatic entry: a half-open character range in. */\n function select(input: SelectionRangeInput): void {\n assertCanSelect('selection.select');\n const range: TextRange =\n 'pon' in input\n ? {\n start: { pon: input.pon, index: input.start },\n end: { pon: input.pon, index: input.start + input.count },\n }\n : input;\n const si = pageIndexOf(range.start.pon);\n let ei = pageIndexOf(range.end.pon);\n if (si < 0 || ei < 0) {\n throw new Error(\n `select: unknown page object ${si < 0 ? range.start.pon : range.end.pon}`,\n );\n }\n let endIndex = range.end.index;\n if (ei < si || (ei === si && endIndex <= range.start.index)) {\n clearSelection(); // empty range = no selection\n return;\n }\n if (endIndex === 0) {\n // Half-open end exactly at a page boundary: the last included\n // character is the previous page's last one. Its index settles via\n // clamping once that page's geometry loads.\n ei -= 1;\n if (ei < si) {\n clearSelection();\n return;\n }\n endIndex = Number.MAX_SAFE_INTEGER;\n }\n const focusPon = ponAtIndex(ei);\n if (focusPon == null) throw new Error(`select: unknown page at index ${ei}`);\n setSelecting(false); // programmatic selections are born settled\n recompute({\n anchor: { pon: range.start.pon, glyph: Math.max(0, range.start.index) },\n focus: { pon: focusPon, glyph: endIndex - 1 },\n });\n }\n\n function selectAll(): void {\n assertCanSelect('selection.selectAll');\n const pages = ctx.document()?.pages ?? [];\n if (pages.length === 0) return;\n setSelecting(false); // programmatic selections are born settled\n recompute({\n anchor: { pon: pages[0].pageObjectNumber, glyph: 0 },\n focus: {\n pon: pages[pages.length - 1].pageObjectNumber,\n glyph: Number.MAX_SAFE_INTEGER, // settles to the real last character on load\n },\n });\n }\n\n /** Per-page text snapshot, cached as a PROMISE so concurrent readers\n * share one fetch. Rejections are evicted (a denied or failed read must\n * not poison the cache for a later authorized call). */\n function pageTextSnapshot(pon: PageObjectNumber): Promise<PageTextSnapshot> {\n let p = textSnapshots.get(pon);\n if (!p) {\n const doc = ctx.doc;\n if (!doc) return Promise.reject(new Error('readText: document is not open'));\n p = Promise.resolve(doc.page(pon).text.read());\n p.catch(() => textSnapshots.delete(pon));\n textSnapshots.set(pon, p);\n }\n return p;\n }\n\n async function readText(): Promise<string> {\n const sel = ctx.getState().selection;\n if (!sel) return '';\n if (!canCopy()) throw new PermissionDenied('doc.text.copy', 'selection.readText');\n const { start, end } = orderedEnds(sel);\n const si = pageIndexOf(start.pon);\n const ei = pageIndexOf(end.pon);\n if (si < 0 || ei < 0) return '';\n // Per-page half-open character spans. Geometry is NOT needed here —\n // boundary offsets come from the range, interior pages span their whole\n // charCount (known from the text snapshot itself).\n const spans: Array<{ pon: PageObjectNumber; from: number; to: number }> = [];\n for (let i = si; i <= ei; i++) {\n const pon = ponAtIndex(i);\n if (pon == null) continue;\n spans.push({\n pon,\n from: i === si ? start.glyph : 0,\n to: i === ei ? end.glyph + 1 : Number.MAX_SAFE_INTEGER, // slice clamps to charCount\n });\n }\n const parts: string[] = new Array(spans.length);\n for (let base = 0; base < spans.length; base += TEXT_READ_CONCURRENCY) {\n const batch = spans.slice(base, base + TEXT_READ_CONCURRENCY);\n const snapshots = await Promise.all(batch.map((s) => pageTextSnapshot(s.pon)));\n snapshots.forEach((snap, j) => {\n parts[base + j] = sliceTextByChars(snap, batch[j].from, batch[j].to);\n });\n }\n return parts.join('\\n');\n }\n\n /** Structural registry change (rotate/move/delete/insert — `revision`\n * bumped): drop caches for pages that left, then recompute. Rotation\n * re-derives transforms via the layout key; a deleted endpoint page\n * clears via recompute's registry check. Raw snapshots are NEVER\n * refetched here — they are rotation-independent. */\n function onPagesUpdated(): void {\n const alive = new Set((ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber));\n for (const pon of [...rawGeometry.keys()]) {\n if (!alive.has(pon)) {\n rawGeometry.delete(pon);\n derived.delete(pon);\n textSnapshots.delete(pon);\n }\n }\n if (ctx.getState().selection) recompute();\n }\n\n /** Page CONTENT changed (redaction apply / flatten): every cached\n * snapshot — geometry AND text — is stale, and any live range points\n * into a character space that no longer exists. Drop everything, clear. */\n function invalidateContent(): void {\n epoch++;\n rawGeometry.clear();\n derived.clear();\n pending.clear();\n textSnapshots.clear();\n if (ctx.getState().selection) clearSelection();\n }\n\n // ── lifecycle wiring (registry + content invalidation) ──\n let lastRevision = ctx.document()?.revision ?? -1;\n const offStore = ctx.subscribe(() => {\n const revision = ctx.document()?.revision;\n if (revision === undefined || revision === lastRevision) return;\n lastRevision = revision;\n onPagesUpdated();\n });\n ctx.cleanup(offStore);\n const offEvents = ctx.doc?.events.subscribe((event) => {\n if (event.type === 'redaction.applied' || event.type === 'pages.flattened') {\n invalidateContent();\n }\n });\n if (offEvents) ctx.cleanup(offEvents);\n\n return {\n // ── public lens ──\n canSelect,\n canCopy,\n select,\n selectAll,\n clear: clearSelection,\n snapshot,\n menuAnchor,\n isSelecting: () => ctx.getState().selecting,\n hasSelection: () => ctx.getState().selection != null,\n selectedPages: () => {\n const { segments } = ctx.getState();\n return Object.keys(segments)\n .map(Number)\n .filter((pon) => (segments[pon]?.length ?? 0) > 0) as PageObjectNumber[];\n },\n segmentsForPage: (pon) => ctx.getState().segments[pon] ?? EMPTY_SEGMENTS,\n rectsForPage: (pon) =>\n (ctx.getState().segments[pon] ?? EMPTY_SEGMENTS).map((s) => s.rect),\n readText,\n onChange: (cb) => {\n changeCbs.add(cb);\n return () => changeCbs.delete(cb);\n },\n onCommit: (cb) => {\n commitCbs.add(cb);\n return () => commitCbs.delete(cb);\n },\n\n // ── host lens ──\n ensurePage,\n isLoaded: (pon) => !!ctx.getState().loaded[pon],\n isOverText: (pon, point: Point) => {\n const geom = geometryFor(pon);\n return geom ? glyphAt(geom, point) != null : false;\n },\n beginAt: (pon, point: Point) => {\n const geom = geometryFor(pon);\n if (!geom) return false;\n const i = glyphAt(geom, point);\n if (i == null) return false; // not near text — caller deselects, doesn't capture\n setSelecting(true); // the gesture opened\n recompute({ anchor: { pon, glyph: i }, focus: { pon, glyph: i } });\n return true;\n },\n selectWordAt: (pon, point: Point) => selectSpanAt(pon, point, 'word'),\n selectLineAt: (pon, point: Point) => selectSpanAt(pon, point, 'line'),\n extendTo: (pon, point: Point) => {\n const cur = ctx.getState().selection;\n if (!cur) return;\n const geom = geometryFor(pon);\n if (!geom) {\n ensurePage(pon); // dragged onto a not-yet-loaded page — warm it, recompute on load\n return;\n }\n const i = glyphAt(geom, point);\n if (i == null) return; // off-text — keep the last focus\n recompute({ anchor: cur.anchor, focus: { pon, glyph: i } });\n },\n end: () => {\n // The gesture ended (pointer-up): settle FIRST, so commit listeners\n // (menus, clipboard prefetch) observe isSelecting() === false.\n setSelecting(false);\n commitCbs.forEach((cb) => cb());\n },\n setHighlightVisible: (visible) =>\n ctx.dispatch({ type: 'SET_HIGHLIGHT_HIDDEN', hidden: !visible }),\n highlightVisible: () => !ctx.getState().highlightHidden,\n };\n}\n","import type {\n InteractionCapability,\n InteractionHandler,\n PlatformFeedback,\n} from '@embedpdf/plugin-interaction/contract';\nimport type { SelectionHostCapability } from './types';\n\nconst CURSOR_TOKEN = 'selection-text';\nconst DRAG_THRESHOLD_PX = 4; // viewport px the pointer must move before a drag-select begins\n\n/**\n * The text-selection pointer handler. It is live only under tools that enable the\n * `'text-select'` tag (the built-in `pointer` tool does; `pan` does not — so\n * switching to pan disables text selection with zero special-casing here).\n *\n * - single click → deselect; records an anchor but selects NOTHING until\n * the pointer moves past the drag threshold (Chrome/Acrobat feel)\n * - single-click + drag → caret selection from the anchor (extends across pages)\n * - double-click → select the word\n * - triple-click → select the visual line\n * - down off-text → deselect, and DON'T capture (so it can't block)\n * - hover → I-beam only when over text, else the pointer cursor\n */\nexport function createTextSelectHandler(\n selection: SelectionHostCapability,\n interaction: InteractionCapability,\n feedback?: PlatformFeedback,\n): InteractionHandler {\n // Per-gesture drag-threshold state (one active gesture at a time — the hub owner).\n let anchor: { pon: number; point: { x: number; y: number }; vx: number; vy: number } | null =\n null;\n let dragging = false;\n\n return {\n id: 'text-select',\n priority: 60,\n enabledFor: (tool) => tool.enables.has('text-select'),\n onDown: (s) => {\n anchor = null;\n dragging = false;\n if (!s.page) return false; // over a gap — let a lower handler (e.g. scroll) try\n const { pon, point } = s.page;\n const clicks = s.clickCount ?? 1;\n if (clicks >= 3) {\n selection.selectLineAt(pon, point);\n return true;\n }\n if (clicks === 2) {\n const selected = selection.selectWordAt(pon, point);\n // The platform's \"selection changed\" tick — ONLY when a touch\n // long-press actually engaged a word (never on blank space, never for\n // a mouse double-click). The gesture marker is the honest signal; a\n // raw double-click sample never carries it.\n if (selected && s.gesture === 'long-press') feedback?.selection();\n return true;\n }\n // Single click: clear immediately (clicking deselects), then record an anchor\n // ONLY if over text — but begin no selection until the drag threshold is met.\n selection.clear();\n if (!selection.isOverText(pon, point)) return false; // empty space → don't capture\n anchor = { pon, point, vx: s.viewport.x, vy: s.viewport.y };\n return true;\n },\n onMove: (s) => {\n if (!s.page || !anchor) return;\n if (!dragging) {\n if (Math.hypot(s.viewport.x - anchor.vx, s.viewport.y - anchor.vy) < DRAG_THRESHOLD_PX) {\n return; // still a click, not a drag — select nothing yet\n }\n dragging = true;\n selection.beginAt(anchor.pon, anchor.point); // open the selection at the anchor\n }\n selection.extendTo(s.page.pon, s.page.point);\n },\n onUp: () => {\n anchor = null;\n dragging = false;\n selection.end();\n },\n onCancel: () => {\n // Aborted (second finger → pinch, or a system cancel): drop the\n // in-flight selection instead of committing a sliver of it. end() after\n // clear() settles the `selecting` flag without anything to commit.\n anchor = null;\n dragging = false;\n selection.clear();\n selection.end();\n },\n onHover: (s) => {\n if (!s.page) {\n interaction.setCursor(CURSOR_TOKEN, null); // off the page → pointer\n return;\n }\n // Warm geometry on first hover; show the I-beam ONLY when actually over text.\n selection.ensurePage(s.page.pon);\n const overText = selection.isOverText(s.page.pon, s.page.point);\n interaction.setCursor(CURSOR_TOKEN, overText ? 'text' : null, 10);\n },\n };\n}\n","import type { SelectionAction, SelectionState } from './types';\n\nexport const initialSelectionState: SelectionState = {\n selection: null,\n segments: {},\n loaded: {},\n highlightHidden: false,\n selecting: false,\n};\n\nexport const selectionReducer = (\n state: SelectionState,\n action: SelectionAction,\n): SelectionState => {\n switch (action.type) {\n case 'PAGE_LOADED':\n return state.loaded[action.pon]\n ? state\n : { ...state, loaded: { ...state.loaded, [action.pon]: true } };\n case 'SET':\n return { ...state, selection: action.selection, segments: action.segments };\n case 'CLEAR':\n return state.selection === null && Object.keys(state.segments).length === 0\n ? state\n : { ...state, selection: null, segments: {} };\n case 'SET_HIGHLIGHT_HIDDEN':\n return state.highlightHidden === action.hidden\n ? state\n : { ...state, highlightHidden: action.hidden };\n case 'SET_SELECTING':\n return state.selecting === action.selecting\n ? state\n : { ...state, selecting: action.selecting };\n default:\n return state;\n }\n};\n"],"mappings":"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"}
@@ -1,15 +1,5 @@
1
- import { createCapabilityToken } from "@embedpdf/core";
2
1
  import { applyPoint, invert, pageGeometry, textQuadBounds } from "@embedpdf/core-geometry";
3
2
  import { PermissionDenied, buildPageTextLayout, expandTextRangeToLine, expandTextRangeToWord, sliceTextByChars, textGlyphAt, textGlyphQuad, textSegmentsForRange } from "@embedpdf/engine-core/runtime";
4
- //#region src/types.ts
5
- /**
6
- * The selection capability token. Typed to the full
7
- * {@link SelectionHostCapability} for this module and `/internal`; the
8
- * package root re-exports the SAME token narrowed to
9
- * {@link SelectionCapability}.
10
- */
11
- const SelectionToken = createCapabilityToken("selection");
12
- //#endregion
13
3
  //#region src/geometry.ts
14
4
  /**
15
5
  * The selection plugin's COORDINATE SEAM — and nothing else.
@@ -690,6 +680,6 @@ const selectionReducer = (state, action) => {
690
680
  }
691
681
  };
692
682
  //#endregion
693
- export { buildSelectionPageGeometry as a, toContentTextQuad as c, createSelectionCapability as i, SelectionToken as l, selectionReducer as n, contentPointToPdf as o, createTextSelectHandler as r, toContentSegment as s, initialSelectionState as t };
683
+ export { buildSelectionPageGeometry as a, toContentTextQuad as c, createSelectionCapability as i, selectionReducer as n, contentPointToPdf as o, createTextSelectHandler as r, toContentSegment as s, initialSelectionState as t };
694
684
 
695
- //# sourceMappingURL=reducer-Dy0YPuX0.js.map
685
+ //# sourceMappingURL=reducer-DbKtk9Xl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducer-DbKtk9Xl.js","names":[],"sources":["../src/geometry.ts","../src/capability.ts","../src/handler.ts","../src/reducer.ts"],"sourcesContent":["/**\n * The selection plugin's COORDINATE SEAM — and nothing else.\n *\n * Segmentation, hit-testing, and word/line expansion live in the canonical\n * engine-core text layout (`@embedpdf/engine-core` `text/layout`): a text\n * range has exactly one segmentation whether it came from a drag or a\n * search. This module only converts between the viewer's content space\n * (y-down, crop-relative) and the engine's PDF space (y-up) — pointer\n * points inward, canonical segments outward. The PDF↔content y-flip is the\n * geometry package's `pageGeometry` (the one place that math lives), never\n * hand-rolled here.\n */\nimport {\n applyPoint,\n invert,\n pageGeometry,\n textQuadBounds,\n type Mat2D,\n type Point,\n type PointIn,\n type Rect,\n type TextQuad,\n} from '@embedpdf/core-geometry';\nimport {\n buildPageTextLayout,\n type PageGeometrySnapshot,\n type PageTextLayout,\n type PdfPoint,\n type PdfQuad,\n type PdfRect,\n type PdfTextSegment,\n} from '@embedpdf/engine-core/runtime';\n\n/**\n * One merged visual line of a selection, in content space — the structural\n * twin of the engine's `PdfTextSegment`. `quad` carries frame-geometric\n * corner semantics (upper = ascent side, start = the frame's −x); `advance`\n * is the READING direction along the baseline (+1 = toward `end`), derived\n * from the glyph sequence — geometry and bidi stay separate concerns.\n * `rect` is the content-space AABB.\n */\nexport interface SelectionSegment {\n quad: TextQuad;\n rect: Rect;\n advance: 1 | -1;\n}\n\n/** A page's cached selection geometry: the canonical layout + the seam. */\nexport interface SelectionPageGeometry {\n layout: PageTextLayout;\n toContent: Mat2D<'pdf', 'content'>;\n fromContent: Mat2D<'content', 'pdf'>;\n}\n\n/**\n * Build a page's selection geometry. Page display rotation does NOT enter\n * here — the overlay rides the page's CSS rotation, so content space stays\n * un-rotated (the crop-relative y-flip is the only conversion; zoom = 1,\n * the viewer's zoom is applied later by `PageTransform.toPixels`).\n */\nexport function buildSelectionPageGeometry(\n snapshot: PageGeometrySnapshot,\n crop: PdfRect,\n rotation: 0 | 90 | 180 | 270,\n userUnit: number,\n): SelectionPageGeometry {\n const { pdfToContent } = pageGeometry({ crop, rotation, userUnit }, 1);\n return {\n layout: buildPageTextLayout(snapshot),\n toContent: pdfToContent,\n fromContent: invert(pdfToContent),\n };\n}\n\n/** A content-space pointer point in the layout's PDF space. */\nexport function contentPointToPdf(geom: SelectionPageGeometry, p: Point): PdfPoint {\n return applyPoint(geom.fromContent, p as PointIn<'content'>);\n}\n\n/**\n * A canonical PDF quad in content space. Engine quads carry frame-geometric\n * slot semantics (`p1..p4` = upper-start, upper-end, lower-start,\n * lower-end) — a trusted producer, so the y-flip maps corners straight onto\n * their names; imported-annotation normalization does NOT apply here.\n */\nexport function toContentTextQuad(geom: SelectionPageGeometry, quad: PdfQuad): TextQuad {\n const pt = (p: PdfPoint): Point => applyPoint(geom.toContent, p as PointIn<'pdf'>);\n return {\n upperStart: pt(quad.p1),\n upperEnd: pt(quad.p2),\n lowerStart: pt(quad.p3),\n lowerEnd: pt(quad.p4),\n };\n}\n\n/** A canonical segment in content space (`rect` recomputed from the mapped\n * quad so the pair can never drift). */\nexport function toContentSegment(\n geom: SelectionPageGeometry,\n segment: PdfTextSegment,\n): SelectionSegment {\n const quad = toContentTextQuad(geom, segment.quad);\n return { quad, rect: textQuadBounds(quad), advance: segment.advance };\n}\n","import type { PageObjectNumber, PluginContext } from '@embedpdf/core';\nimport { textQuadBounds, type Point, type Rect } from '@embedpdf/core-geometry';\nimport {\n expandTextRangeToLine,\n expandTextRangeToWord,\n PermissionDenied,\n sliceTextByChars,\n textGlyphAt,\n textGlyphQuad,\n textSegmentsForRange,\n type PageGeometrySnapshot,\n type PageTextSnapshot,\n} from '@embedpdf/engine-core/runtime';\nimport {\n buildSelectionPageGeometry,\n contentPointToPdf,\n toContentSegment,\n toContentTextQuad,\n type SelectionPageGeometry,\n type SelectionSegment,\n} from './geometry';\nimport type {\n GlyphPointer,\n SelectionAction,\n SelectionEndpoint,\n SelectionHostCapability,\n SelectionMenuAnchor,\n SelectionRange,\n SelectionRangeInput,\n SelectionSnapshot,\n SelectionState,\n TextRange,\n} from './types';\n\nconst EMPTY_SEGMENTS: SelectionSegment[] = [];\n\n/** How many page-text reads `readText()` keeps in flight at once. */\nconst TEXT_READ_CONCURRENCY = 8;\n\n/**\n * The selection capability — ONE closure returning the host lens (the\n * public lens is the same object, narrowed at the package root).\n *\n * State model: the reducer holds the selection range and the derived\n * content-space segments; the large, non-serializable per-page caches live\n * HERE in the closure, split by what invalidates them:\n *\n * - `rawGeometry` — PDF-space geometry snapshots, rotation-independent\n * (pages load normalized; see `PageRotateResult`). Dropped only when a\n * page leaves the registry or page CONTENT changes (redaction apply /\n * flatten).\n * - `derived` — the content-space transform + layout, keyed by the\n * layout params (crop/rotation/userUnit) so a view-rotate re-derives\n * from the cached raw snapshot with NO refetch.\n * - `textSnapshots` — per-page text (for `readText`), same content-only\n * invalidation as `rawGeometry`.\n *\n * Permission model: `doc.text.select` gates geometry (selection exists),\n * `doc.text.copy` gates text (extraction) — neither implies the other.\n * Host gesture functions are INERT when select is denied (`ensurePage`\n * no-ops, so nothing warms and nothing hit-tests: the pointer handler\n * needs no special-casing); the public `select()` THROWS `PermissionDenied`\n * so programmatic misuse is loud; `clear()` is always allowed. The engine\n * independently enforces both scopes — these gates exist so the plugin\n * never issues a request that is guaranteed to fail.\n *\n * Selection is cross-page: glyphs are ordered globally by (pageIndex,\n * glyph), so a drag from page 2 into page 4 selects the tail of 2, all of\n * 3, and the head of 4. `recompute` rebuilds the merged line segments for\n * every loaded page in the span, re-runs whenever a mid-span page finishes\n * loading, and CLAMPS stored endpoints once their page's geometry is known\n * (so `selectAll`'s open-ended focus settles to the real last character).\n */\nexport function createSelectionCapability(\n ctx: PluginContext<SelectionState, SelectionAction>,\n): SelectionHostCapability {\n const rawGeometry = new Map<PageObjectNumber, PageGeometrySnapshot>();\n const derived = new Map<PageObjectNumber, { key: string; geom: SelectionPageGeometry }>();\n const pending = new Set<PageObjectNumber>();\n const textSnapshots = new Map<PageObjectNumber, Promise<PageTextSnapshot>>();\n /** Bumped on content invalidation; in-flight reads from before are discarded. */\n let epoch = 0;\n // Consumers (e.g. text-markup) observe the selection without selection knowing\n // about them: `change` fires whenever the segments change, `commit` when a\n // gesture ends (pointer-up). One typed callback each — not an event bus.\n const changeCbs = new Set<() => void>();\n const commitCbs = new Set<() => void>();\n const fireChange = (): void => changeCbs.forEach((cb) => cb());\n\n const canSelect = (): boolean => ctx.doc?.security.allows('doc.text.select') ?? false;\n const canCopy = (): boolean => ctx.doc?.security.allows('doc.text.copy') ?? false;\n\n const layoutOf = (pon: PageObjectNumber) =>\n ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n const pageIndexOf = (pon: PageObjectNumber): number =>\n ctx.document()?.pages.findIndex((p) => p.pageObjectNumber === pon) ?? -1;\n const ponAtIndex = (i: number): PageObjectNumber | undefined =>\n ctx.document()?.pages[i]?.pageObjectNumber;\n\n /**\n * The content-space geometry for a page, derived on demand from the raw\n * snapshot + the CURRENT layout params. A view-rotate (or crop change)\n * changes the key and re-derives — raw caches and the engine are never\n * touched (rotation is presentation metadata).\n */\n function geometryFor(pon: PageObjectNumber): SelectionPageGeometry | null {\n const raw = rawGeometry.get(pon);\n const layout = layoutOf(pon);\n if (!raw || !layout) return null;\n const crop = layout.boxes.crop;\n const key = `${layout.rotation}|${layout.userUnit}|${crop.left},${crop.bottom},${crop.right},${crop.top}`;\n const hit = derived.get(pon);\n if (hit && hit.key === key) return hit.geom;\n const geom = buildSelectionPageGeometry(raw, crop, layout.rotation, layout.userUnit);\n derived.set(pon, { key, geom });\n return geom;\n }\n\n const glyphAt = (geom: SelectionPageGeometry, point: Point): number | null =>\n textGlyphAt(geom.layout, contentPointToPdf(geom, point));\n\n // Order the two ends of a selection by document position (page, then glyph).\n function orderedEnds(sel: SelectionRange): {\n start: GlyphPointer;\n end: GlyphPointer;\n direction: 'forward' | 'backward';\n } {\n const ai = pageIndexOf(sel.anchor.pon);\n const fi = pageIndexOf(sel.focus.pon);\n const anchorFirst = ai < fi || (ai === fi && sel.anchor.glyph <= sel.focus.glyph);\n return anchorFirst\n ? { start: sel.anchor, end: sel.focus, direction: 'forward' }\n : { start: sel.focus, end: sel.anchor, direction: 'backward' };\n }\n\n function endpointFor(ptr: GlyphPointer, which: 'start' | 'end'): SelectionEndpoint | null {\n const segments = ctx.getState().segments[ptr.pon] ?? EMPTY_SEGMENTS;\n if (!segments.length) return null;\n const segment = which === 'start' ? segments[0] : segments[segments.length - 1];\n\n // Anchor the endpoint to the boundary GLYPH's own oriented cell so caret\n // placement lands on the exact character edge; fall back to the segment\n // when the glyph is degenerate (e.g. a generated space).\n const geom = geometryFor(ptr.pon);\n const cell = geom ? textGlyphQuad(geom.layout, ptr.glyph) : null;\n if (geom && cell) {\n const glyphQuad = toContentTextQuad(geom, cell);\n return {\n pon: ptr.pon,\n glyphQuad,\n advance: segment.advance,\n rect: textQuadBounds(glyphQuad),\n };\n }\n return { pon: ptr.pon, glyphQuad: segment.quad, advance: segment.advance, rect: segment.rect };\n }\n\n /** Union box of a page's segments, or null when none have materialized. */\n function pageBounds(pon: PageObjectNumber): Rect | null {\n const segs = ctx.getState().segments[pon];\n if (!segs || segs.length === 0) return null;\n let x1 = Infinity;\n let y1 = Infinity;\n let x2 = -Infinity;\n let y2 = -Infinity;\n for (const s of segs) {\n x1 = Math.min(x1, s.rect.x);\n y1 = Math.min(y1, s.rect.y);\n x2 = Math.max(x2, s.rect.x + s.rect.width);\n y2 = Math.max(y2, s.rect.y + s.rect.height);\n }\n return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };\n }\n\n function menuAnchor(): SelectionMenuAnchor | null {\n const sel = ctx.getState().selection;\n if (!sel) return null;\n const { end } = orderedEnds(sel);\n // Prefer the gesture's end page; while its geometry is still loading,\n // fall back to the LAST page (document order) with materialized segments\n // so the anchor never teleports backwards mid-drag.\n let bounds = pageBounds(end.pon);\n if (bounds) return { pon: end.pon, bounds };\n const pages = ctx.document()?.pages ?? [];\n for (let i = pages.length - 1; i >= 0; i--) {\n const pon = pages[i].pageObjectNumber;\n bounds = pageBounds(pon);\n if (bounds) return { pon, bounds };\n }\n return null;\n }\n\n function snapshot(): SelectionSnapshot {\n const { selection, segments } = ctx.getState();\n const pages = Object.keys(segments)\n .map(Number)\n .filter((pon) => (segments[pon]?.length ?? 0) > 0)\n .map((pon) => ({ pon: pon as PageObjectNumber, segments: segments[pon] }));\n if (!selection) return { pages, start: null, end: null, direction: 'forward', range: null };\n const { start, end, direction } = orderedEnds(selection);\n return {\n pages,\n start: endpointFor(start, 'start'),\n end: endpointFor(end, 'end'),\n direction,\n range: {\n start: { pon: start.pon, index: start.glyph },\n end: { pon: end.pon, index: end.glyph + 1 },\n },\n };\n }\n\n function ensurePage(pon: PageObjectNumber): void {\n if (rawGeometry.has(pon) || pending.has(pon)) return;\n // Authorized-only warming: without doc.text.select the read is\n // guaranteed to be rejected — don't issue it. The engine guard stays\n // the security boundary either way.\n if (!canSelect()) return;\n const doc = ctx.doc;\n if (!doc || !layoutOf(pon)) return;\n pending.add(pon);\n const at = epoch;\n doc\n .page(pon)\n .geometry.read()\n .then(\n (snapshot) => {\n pending.delete(pon);\n if (at !== epoch) return; // content changed while in flight — stale\n rawGeometry.set(pon, snapshot);\n ctx.dispatch({ type: 'PAGE_LOADED', pon });\n if (ctx.getState().selection) recompute(); // a mid-span page arrived → fill its segments\n },\n () => {\n pending.delete(pon); // doc closed / read aborted / denied — ignore\n },\n );\n }\n\n /** Clamp a pointer into its page's real character range once geometry is\n * known — how `selectAll`'s open-ended focus settles. */\n function clampPointer(ptr: GlyphPointer): GlyphPointer {\n const geom = geometryFor(ptr.pon);\n if (!geom) return ptr;\n const max = Math.max(geom.layout.glyphs.length - 1, 0);\n const glyph = Math.max(0, Math.min(ptr.glyph, max));\n return glyph === ptr.glyph ? ptr : { pon: ptr.pon, glyph };\n }\n\n // Rebuild merged line segments for every loaded page in the span; warm the\n // rest (they recompute on arrival). Clears when an endpoint's page has left\n // the registry — a range across a structural edit is meaningless.\n function recompute(sel: SelectionRange | null = ctx.getState().selection): void {\n if (!sel) return;\n const clamped: SelectionRange = {\n anchor: clampPointer(sel.anchor),\n focus: clampPointer(sel.focus),\n };\n const { start, end } = orderedEnds(clamped);\n const si = pageIndexOf(start.pon);\n const ei = pageIndexOf(end.pon);\n if (si < 0 || ei < 0) {\n clearSelection();\n return;\n }\n const segments: Record<number, SelectionSegment[]> = {};\n for (let i = si; i <= ei; i++) {\n const pon = ponAtIndex(i);\n if (pon == null) continue;\n const geom = geometryFor(pon);\n if (!geom) {\n ensurePage(pon); // not loaded yet — it'll recompute when ready\n continue;\n }\n const from = i === si ? start.glyph : 0;\n const to = i === ei ? end.glyph : geom.layout.glyphs.length - 1;\n segments[pon] = textSegmentsForRange(geom.layout, from, to - from + 1).map((s) =>\n toContentSegment(geom, s),\n );\n }\n ctx.dispatch({ type: 'SET', selection: clamped, segments });\n fireChange();\n }\n\n /** Flip the gesture-in-flight FACT (see `SelectionState.selecting`). Set\n * BEFORE the recompute that fires `onChange`, so change listeners always\n * observe a coherent (selecting, segments) pair. */\n function setSelecting(selecting: boolean): void {\n ctx.dispatch({ type: 'SET_SELECTING', selecting });\n }\n\n function clearSelection(): void {\n setSelecting(false);\n ctx.dispatch({ type: 'CLEAR' });\n fireChange();\n }\n\n // Set the selection to a flat [from,to] glyph span on one page (word/line).\n // These are GESTURE vocabulary (double-/triple-click) — the commit arrives\n // at the pointer-up that follows, so the span counts as in-flight until then.\n // Returns whether a span actually engaged (geometry present AND a glyph\n // under the point) — the fact haptics and other success-gated feedback key\n // on, so nothing ever buzzes over blank space.\n function selectSpanAt(pon: PageObjectNumber, point: Point, expand: 'word' | 'line'): boolean {\n const geom = geometryFor(pon);\n if (!geom) return false;\n const i = glyphAt(geom, point);\n if (i == null) return false;\n const [from, to] =\n expand === 'word'\n ? expandTextRangeToWord(geom.layout, i)\n : expandTextRangeToLine(geom.layout, i);\n setSelecting(true);\n recompute({ anchor: { pon, glyph: from }, focus: { pon, glyph: to } });\n return true;\n }\n\n function assertCanSelect(context: string): void {\n if (!canSelect()) throw new PermissionDenied('doc.text.select', context);\n }\n\n /** The public programmatic entry: a half-open character range in. */\n function select(input: SelectionRangeInput): void {\n assertCanSelect('selection.select');\n const range: TextRange =\n 'pon' in input\n ? {\n start: { pon: input.pon, index: input.start },\n end: { pon: input.pon, index: input.start + input.count },\n }\n : input;\n const si = pageIndexOf(range.start.pon);\n let ei = pageIndexOf(range.end.pon);\n if (si < 0 || ei < 0) {\n throw new Error(\n `select: unknown page object ${si < 0 ? range.start.pon : range.end.pon}`,\n );\n }\n let endIndex = range.end.index;\n if (ei < si || (ei === si && endIndex <= range.start.index)) {\n clearSelection(); // empty range = no selection\n return;\n }\n if (endIndex === 0) {\n // Half-open end exactly at a page boundary: the last included\n // character is the previous page's last one. Its index settles via\n // clamping once that page's geometry loads.\n ei -= 1;\n if (ei < si) {\n clearSelection();\n return;\n }\n endIndex = Number.MAX_SAFE_INTEGER;\n }\n const focusPon = ponAtIndex(ei);\n if (focusPon == null) throw new Error(`select: unknown page at index ${ei}`);\n setSelecting(false); // programmatic selections are born settled\n recompute({\n anchor: { pon: range.start.pon, glyph: Math.max(0, range.start.index) },\n focus: { pon: focusPon, glyph: endIndex - 1 },\n });\n }\n\n function selectAll(): void {\n assertCanSelect('selection.selectAll');\n const pages = ctx.document()?.pages ?? [];\n if (pages.length === 0) return;\n setSelecting(false); // programmatic selections are born settled\n recompute({\n anchor: { pon: pages[0].pageObjectNumber, glyph: 0 },\n focus: {\n pon: pages[pages.length - 1].pageObjectNumber,\n glyph: Number.MAX_SAFE_INTEGER, // settles to the real last character on load\n },\n });\n }\n\n /** Per-page text snapshot, cached as a PROMISE so concurrent readers\n * share one fetch. Rejections are evicted (a denied or failed read must\n * not poison the cache for a later authorized call). */\n function pageTextSnapshot(pon: PageObjectNumber): Promise<PageTextSnapshot> {\n let p = textSnapshots.get(pon);\n if (!p) {\n const doc = ctx.doc;\n if (!doc) return Promise.reject(new Error('readText: document is not open'));\n p = Promise.resolve(doc.page(pon).text.read());\n p.catch(() => textSnapshots.delete(pon));\n textSnapshots.set(pon, p);\n }\n return p;\n }\n\n async function readText(): Promise<string> {\n const sel = ctx.getState().selection;\n if (!sel) return '';\n if (!canCopy()) throw new PermissionDenied('doc.text.copy', 'selection.readText');\n const { start, end } = orderedEnds(sel);\n const si = pageIndexOf(start.pon);\n const ei = pageIndexOf(end.pon);\n if (si < 0 || ei < 0) return '';\n // Per-page half-open character spans. Geometry is NOT needed here —\n // boundary offsets come from the range, interior pages span their whole\n // charCount (known from the text snapshot itself).\n const spans: Array<{ pon: PageObjectNumber; from: number; to: number }> = [];\n for (let i = si; i <= ei; i++) {\n const pon = ponAtIndex(i);\n if (pon == null) continue;\n spans.push({\n pon,\n from: i === si ? start.glyph : 0,\n to: i === ei ? end.glyph + 1 : Number.MAX_SAFE_INTEGER, // slice clamps to charCount\n });\n }\n const parts: string[] = new Array(spans.length);\n for (let base = 0; base < spans.length; base += TEXT_READ_CONCURRENCY) {\n const batch = spans.slice(base, base + TEXT_READ_CONCURRENCY);\n const snapshots = await Promise.all(batch.map((s) => pageTextSnapshot(s.pon)));\n snapshots.forEach((snap, j) => {\n parts[base + j] = sliceTextByChars(snap, batch[j].from, batch[j].to);\n });\n }\n return parts.join('\\n');\n }\n\n /** Structural registry change (rotate/move/delete/insert — `revision`\n * bumped): drop caches for pages that left, then recompute. Rotation\n * re-derives transforms via the layout key; a deleted endpoint page\n * clears via recompute's registry check. Raw snapshots are NEVER\n * refetched here — they are rotation-independent. */\n function onPagesUpdated(): void {\n const alive = new Set((ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber));\n for (const pon of [...rawGeometry.keys()]) {\n if (!alive.has(pon)) {\n rawGeometry.delete(pon);\n derived.delete(pon);\n textSnapshots.delete(pon);\n }\n }\n if (ctx.getState().selection) recompute();\n }\n\n /** Page CONTENT changed (redaction apply / flatten): every cached\n * snapshot — geometry AND text — is stale, and any live range points\n * into a character space that no longer exists. Drop everything, clear. */\n function invalidateContent(): void {\n epoch++;\n rawGeometry.clear();\n derived.clear();\n pending.clear();\n textSnapshots.clear();\n if (ctx.getState().selection) clearSelection();\n }\n\n // ── lifecycle wiring (registry + content invalidation) ──\n let lastRevision = ctx.document()?.revision ?? -1;\n const offStore = ctx.subscribe(() => {\n const revision = ctx.document()?.revision;\n if (revision === undefined || revision === lastRevision) return;\n lastRevision = revision;\n onPagesUpdated();\n });\n ctx.cleanup(offStore);\n const offEvents = ctx.doc?.events.subscribe((event) => {\n if (event.type === 'redaction.applied' || event.type === 'pages.flattened') {\n invalidateContent();\n }\n });\n if (offEvents) ctx.cleanup(offEvents);\n\n return {\n // ── public lens ──\n canSelect,\n canCopy,\n select,\n selectAll,\n clear: clearSelection,\n snapshot,\n menuAnchor,\n isSelecting: () => ctx.getState().selecting,\n hasSelection: () => ctx.getState().selection != null,\n selectedPages: () => {\n const { segments } = ctx.getState();\n return Object.keys(segments)\n .map(Number)\n .filter((pon) => (segments[pon]?.length ?? 0) > 0) as PageObjectNumber[];\n },\n segmentsForPage: (pon) => ctx.getState().segments[pon] ?? EMPTY_SEGMENTS,\n rectsForPage: (pon) =>\n (ctx.getState().segments[pon] ?? EMPTY_SEGMENTS).map((s) => s.rect),\n readText,\n onChange: (cb) => {\n changeCbs.add(cb);\n return () => changeCbs.delete(cb);\n },\n onCommit: (cb) => {\n commitCbs.add(cb);\n return () => commitCbs.delete(cb);\n },\n\n // ── host lens ──\n ensurePage,\n isLoaded: (pon) => !!ctx.getState().loaded[pon],\n isOverText: (pon, point: Point) => {\n const geom = geometryFor(pon);\n return geom ? glyphAt(geom, point) != null : false;\n },\n beginAt: (pon, point: Point) => {\n const geom = geometryFor(pon);\n if (!geom) return false;\n const i = glyphAt(geom, point);\n if (i == null) return false; // not near text — caller deselects, doesn't capture\n setSelecting(true); // the gesture opened\n recompute({ anchor: { pon, glyph: i }, focus: { pon, glyph: i } });\n return true;\n },\n selectWordAt: (pon, point: Point) => selectSpanAt(pon, point, 'word'),\n selectLineAt: (pon, point: Point) => selectSpanAt(pon, point, 'line'),\n extendTo: (pon, point: Point) => {\n const cur = ctx.getState().selection;\n if (!cur) return;\n const geom = geometryFor(pon);\n if (!geom) {\n ensurePage(pon); // dragged onto a not-yet-loaded page — warm it, recompute on load\n return;\n }\n const i = glyphAt(geom, point);\n if (i == null) return; // off-text — keep the last focus\n recompute({ anchor: cur.anchor, focus: { pon, glyph: i } });\n },\n end: () => {\n // The gesture ended (pointer-up): settle FIRST, so commit listeners\n // (menus, clipboard prefetch) observe isSelecting() === false.\n setSelecting(false);\n commitCbs.forEach((cb) => cb());\n },\n setHighlightVisible: (visible) =>\n ctx.dispatch({ type: 'SET_HIGHLIGHT_HIDDEN', hidden: !visible }),\n highlightVisible: () => !ctx.getState().highlightHidden,\n };\n}\n","import type {\n InteractionCapability,\n InteractionHandler,\n PlatformFeedback,\n} from '@embedpdf/plugin-interaction/contract';\nimport type { SelectionHostCapability } from './types';\n\nconst CURSOR_TOKEN = 'selection-text';\nconst DRAG_THRESHOLD_PX = 4; // viewport px the pointer must move before a drag-select begins\n\n/**\n * The text-selection pointer handler. It is live only under tools that enable the\n * `'text-select'` tag (the built-in `pointer` tool does; `pan` does not — so\n * switching to pan disables text selection with zero special-casing here).\n *\n * - single click → deselect; records an anchor but selects NOTHING until\n * the pointer moves past the drag threshold (Chrome/Acrobat feel)\n * - single-click + drag → caret selection from the anchor (extends across pages)\n * - double-click → select the word\n * - triple-click → select the visual line\n * - down off-text → deselect, and DON'T capture (so it can't block)\n * - hover → I-beam only when over text, else the pointer cursor\n */\nexport function createTextSelectHandler(\n selection: SelectionHostCapability,\n interaction: InteractionCapability,\n feedback?: PlatformFeedback,\n): InteractionHandler {\n // Per-gesture drag-threshold state (one active gesture at a time — the hub owner).\n let anchor: { pon: number; point: { x: number; y: number }; vx: number; vy: number } | null =\n null;\n let dragging = false;\n\n return {\n id: 'text-select',\n priority: 60,\n enabledFor: (tool) => tool.enables.has('text-select'),\n onDown: (s) => {\n anchor = null;\n dragging = false;\n if (!s.page) return false; // over a gap — let a lower handler (e.g. scroll) try\n const { pon, point } = s.page;\n const clicks = s.clickCount ?? 1;\n if (clicks >= 3) {\n selection.selectLineAt(pon, point);\n return true;\n }\n if (clicks === 2) {\n const selected = selection.selectWordAt(pon, point);\n // The platform's \"selection changed\" tick — ONLY when a touch\n // long-press actually engaged a word (never on blank space, never for\n // a mouse double-click). The gesture marker is the honest signal; a\n // raw double-click sample never carries it.\n if (selected && s.gesture === 'long-press') feedback?.selection();\n return true;\n }\n // Single click: clear immediately (clicking deselects), then record an anchor\n // ONLY if over text — but begin no selection until the drag threshold is met.\n selection.clear();\n if (!selection.isOverText(pon, point)) return false; // empty space → don't capture\n anchor = { pon, point, vx: s.viewport.x, vy: s.viewport.y };\n return true;\n },\n onMove: (s) => {\n if (!s.page || !anchor) return;\n if (!dragging) {\n if (Math.hypot(s.viewport.x - anchor.vx, s.viewport.y - anchor.vy) < DRAG_THRESHOLD_PX) {\n return; // still a click, not a drag — select nothing yet\n }\n dragging = true;\n selection.beginAt(anchor.pon, anchor.point); // open the selection at the anchor\n }\n selection.extendTo(s.page.pon, s.page.point);\n },\n onUp: () => {\n anchor = null;\n dragging = false;\n selection.end();\n },\n onCancel: () => {\n // Aborted (second finger → pinch, or a system cancel): drop the\n // in-flight selection instead of committing a sliver of it. end() after\n // clear() settles the `selecting` flag without anything to commit.\n anchor = null;\n dragging = false;\n selection.clear();\n selection.end();\n },\n onHover: (s) => {\n if (!s.page) {\n interaction.setCursor(CURSOR_TOKEN, null); // off the page → pointer\n return;\n }\n // Warm geometry on first hover; show the I-beam ONLY when actually over text.\n selection.ensurePage(s.page.pon);\n const overText = selection.isOverText(s.page.pon, s.page.point);\n interaction.setCursor(CURSOR_TOKEN, overText ? 'text' : null, 10);\n },\n };\n}\n","import type { SelectionAction, SelectionState } from './types';\n\nexport const initialSelectionState: SelectionState = {\n selection: null,\n segments: {},\n loaded: {},\n highlightHidden: false,\n selecting: false,\n};\n\nexport const selectionReducer = (\n state: SelectionState,\n action: SelectionAction,\n): SelectionState => {\n switch (action.type) {\n case 'PAGE_LOADED':\n return state.loaded[action.pon]\n ? state\n : { ...state, loaded: { ...state.loaded, [action.pon]: true } };\n case 'SET':\n return { ...state, selection: action.selection, segments: action.segments };\n case 'CLEAR':\n return state.selection === null && Object.keys(state.segments).length === 0\n ? state\n : { ...state, selection: null, segments: {} };\n case 'SET_HIGHLIGHT_HIDDEN':\n return state.highlightHidden === action.hidden\n ? state\n : { ...state, highlightHidden: action.hidden };\n case 'SET_SELECTING':\n return state.selecting === action.selecting\n ? state\n : { ...state, selecting: action.selecting };\n default:\n return state;\n }\n};\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { createCapabilityToken } from "@embedpdf/core";
2
+ //#region src/types.ts
3
+ /**
4
+ * The selection capability token. Typed to the full
5
+ * {@link SelectionHostCapability} for this module, `/contract/host`, and
6
+ * `/internal`; the
7
+ * package root re-exports the SAME token narrowed to
8
+ * {@link SelectionCapability}.
9
+ */
10
+ const SelectionToken = createCapabilityToken("selection");
11
+ //#endregion
12
+ export { SelectionToken as t };
13
+
14
+ //# sourceMappingURL=types-BVUcOsCh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-BVUcOsCh.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type { Point, Rect, TextQuad } from '@embedpdf/core-geometry';\nimport type { SelectionSegment } from './geometry';\n\nexport type { SelectionSegment } from './geometry';\n\n// ── the public range vocabulary ─────────────────────────────────────────────\n\n/**\n * A position in a page's CHARACTER space — the space the engine's geometry\n * runs tile (`PageGeometryRun.charStart`) and search hits address\n * (`SearchMatch.charStart`). NOT a string offset into extracted text; the\n * two spaces are joined by the engine's `charMap` (see engine-core\n * `text/charmap.ts`), which `readText()` applies for you.\n */\nexport interface TextPosition {\n pon: PageObjectNumber;\n /** Character index, 0-based. */\n index: number;\n}\n\n/**\n * A half-open range in character space: `start` inclusive, `end` exclusive,\n * in document order (`start` at or before `end`). `end.index` may be 0 to\n * end a range exactly at a page boundary.\n */\nexport interface TextRange {\n start: TextPosition;\n end: TextPosition;\n}\n\n/**\n * What {@link SelectionCapability.select} accepts: a cross-page\n * {@link TextRange}, or a single-page span — the shape of a search hit, so\n * `select({ pon: hit.pageObjectNumber, start: hit.charStart, count: hit.charCount })`\n * needs no conversion.\n */\nexport type SelectionRangeInput =\n | TextRange\n | { pon: PageObjectNumber; start: number; count: number };\n\n// ── gesture-state vocabulary (host/internal) ────────────────────────────────\n\n/** A glyph address: a page + a flat character index within that page. */\nexport interface GlyphPointer {\n pon: PageObjectNumber;\n glyph: number;\n}\n\n/** Anchor = where the drag began, focus = the current end. Inclusive. */\nexport interface SelectionRange {\n anchor: GlyphPointer;\n focus: GlyphPointer;\n}\n\n/**\n * A selection boundary, anchored to the boundary GLYPH's own oriented cell.\n * `advance` is the reading direction of the segment it belongs to (+1 = the\n * frame's +x), so caret consumers place at the trailing edge without\n * re-deriving bidi from geometry. `rect` is the AABB (scroll targets).\n */\nexport interface SelectionEndpoint {\n pon: PageObjectNumber;\n glyphQuad: TextQuad;\n advance: 1 | -1;\n rect: Rect;\n}\n\n/** The selection's floating-UI anchor: a page + the selection's union box on\n * it, in CONTENT space. See {@link SelectionCapability.menuAnchor}. */\nexport interface SelectionMenuAnchor {\n pon: PageObjectNumber;\n bounds: Rect;\n}\n\nexport interface SelectionSnapshot {\n /** Per-page canonical segments — the ONE geometry consumers act on.\n * Boxes are derived views (`segment.rect`, or `rectsForPage()`). */\n pages: Array<{ pon: PageObjectNumber; segments: SelectionSegment[] }>;\n start: SelectionEndpoint | null;\n end: SelectionEndpoint | null;\n direction: 'forward' | 'backward';\n /**\n * The selection as a half-open character range in document order — a\n * valid {@link SelectionCapability.select} input (persist and restore).\n * Null when nothing is selected. Until a boundary page's text geometry\n * has loaded, its index may still exceed the page's character count\n * (`selectAll` settles as geometry arrives); consumers of the range\n * (`select`, `readText`) clamp, so round-tripping is always safe.\n */\n range: TextRange | null;\n}\n\nexport interface SelectionState {\n selection: SelectionRange | null;\n /** Derived per-line segments per page, in CONTENT space (y-down, PDF units). */\n segments: Record<number, SelectionSegment[]>;\n /** Pages whose text geometry has loaded (so the layer re-renders when ready). */\n loaded: Record<number, boolean>;\n /** When a consumer owns the selection visual (e.g. a markup tool draws its own\n * preview), the default highlight rects are suppressed. */\n highlightHidden: boolean;\n /** A selection GESTURE is in flight (drag / double- / triple-click, between\n * pointer-down and pointer-up). A readable FACT, not an event: derived\n * recomputes (page load, rotate, move) never touch it, and programmatic\n * selections are born settled. Consumers apply policy (e.g. a menu hides\n * while true). */\n selecting: boolean;\n}\n\nexport type SelectionAction =\n | { type: 'PAGE_LOADED'; pon: PageObjectNumber }\n | { type: 'SET'; selection: SelectionRange; segments: Record<number, SelectionSegment[]> }\n | { type: 'CLEAR' }\n | { type: 'SET_HIGHLIGHT_HIDDEN'; hidden: boolean }\n | { type: 'SET_SELECTING'; selecting: boolean };\n\n// ── the PUBLIC capability ───────────────────────────────────────────────────\n\n/**\n * The PUBLIC selection API — the documented, stable surface for application\n * code (toolbars, context menus, automation). Resolve it with the token\n * re-exported from the package root (`@embedpdf/plugin-selection`).\n *\n * The permission model: GEOMETRY enables selection (`doc.text.select`),\n * TEXT enables extraction (`doc.text.copy`) — neither implies the other.\n * Everything here except `readText()` works with the select scope alone, so\n * a deployment can allow selecting and highlighting while denying copy.\n *\n * Framework-only plumbing (pointer-gesture intents, geometry warming, the\n * highlight-visibility handshake) lives on {@link SelectionHostCapability},\n * reachable through `@embedpdf/plugin-selection/contract/host` (and the\n * framework's `/internal` entry). Both are\n * the SAME runtime object — two typed lenses on one token.\n */\nexport interface SelectionCapability {\n // ── authorization (mirrors the engine's own enforcement) ──\n /** Whether this caller may create selections at all (`doc.text.select`).\n * Authorization only — not \"is something selected\" ({@link hasSelection})\n * and not geometry readiness (a host concern). */\n canSelect(): boolean;\n /** Whether this caller may extract literal text (`doc.text.copy`) —\n * gates {@link readText} and any copy UI. */\n canCopy(): boolean;\n\n // ── programmatic selection (character space, half-open) ──\n /**\n * Select a range programmatically — the same path gestures use, so the\n * highlight, `onChange`, and markup bridges all behave identically. An\n * empty range clears. Throws the engine's `PermissionDenied` when\n * `doc.text.select` is not granted (gate UI with {@link canSelect}).\n */\n select(range: SelectionRangeInput): void;\n /** Select every character of the document. Same gating as {@link select}. */\n selectAll(): void;\n /** Clear the selection. Always allowed. */\n clear(): void;\n\n // ── reads ──\n /** Coherent read-model: per-page segments, endpoints, direction, and the\n * half-open {@link TextRange} (a valid {@link select} input). */\n snapshot(): SelectionSnapshot;\n /**\n * Whether a selection GESTURE is in flight (drag, double- or triple-click\n * — between pointer-down and pointer-up). Programmatic {@link select} /\n * {@link selectAll} never set it (a programmatic selection is born\n * settled), and derived recomputes (page geometry loading, rotate, move)\n * never touch it. THE fact selection-scoped UI applies policy to — e.g.\n * `<SelectionMenu>` hides while true and appears at pointer-up.\n */\n isSelecting(): boolean;\n /**\n * Where selection-scoped floating UI should attach: the union box of the\n * selection's segments on its END page (where the gesture finished),\n * falling back to the last page with materialized segments while a\n * boundary page's geometry is still loading. One anchor regardless of\n * cross-page selection — the shape every anchored-overlay consumer\n * positions on. Null when nothing is selected (or nothing has\n * materialized yet).\n */\n menuAnchor(): SelectionMenuAnchor | null;\n hasSelection(): boolean;\n /** The pages the current selection covers (those with at least one\n * segment) — so a cross-page action (e.g. markup creation) can fan out\n * per page. */\n selectedPages(): PageObjectNumber[];\n /** Per-line oriented segments for a page, in content space — build your\n * own highlight layer from these. */\n segmentsForPage(pon: PageObjectNumber): SelectionSegment[];\n /** The segments' AABBs — for consumers that genuinely want boxes (scroll,\n * conservative regions). Never a substitute for the oriented quads in\n * geometry that gets drawn or persisted. */\n rectsForPage(pon: PageObjectNumber): Rect[];\n\n // ── text extraction (requires doc.text.copy) ──\n /**\n * The selected text. Fetches each selected page's text snapshot (cached\n * per page, immutable per content version) and slices it by the\n * selection's character range through the engine's `charMap` — at most\n * one text read per page per content version, amortized across every\n * call. Pages are joined with `\\n`. Resolves `''` when nothing is\n * selected; rejects with `PermissionDenied` when `doc.text.copy` is not\n * granted. Clipboard writes are deliberately NOT here — this package is\n * DOM-free; use `@embedpdf/web`'s clipboard helpers (or your platform's)\n * on top of this.\n */\n readText(): Promise<string>;\n\n // ── signals ──\n /** Fires whenever the selection segments change (drag-extend, programmatic\n * select, recompute) — for live preview. Returns an unsubscriber. */\n onChange(cb: () => void): () => void;\n /** Fires when a selection gesture ends (pointer-up) — the commit point\n * consumers (markup creation, copy prefetch) act on. */\n onCommit(cb: () => void): () => void;\n}\n\n// ── the HOST capability ─────────────────────────────────────────────────────\n\n/**\n * The HOST (framework) surface: what render layers, the interaction hub,\n * and sibling plugins need on top of the public lens. Sibling plugins import\n * the token from `@embedpdf/plugin-selection/contract/host`; framework\n * implementation code may use `/internal`. Never use either from application code. All\n * point-based functions are gesture vocabulary (`…At` = point-addressed);\n * the programmatic equivalent is the public range-based {@link\n * SelectionCapability.select}. Every function here is INERT (no requests,\n * no state) when `canSelect()` is false — the pointer handler needs no\n * permission special-casing.\n */\nexport interface SelectionHostCapability extends SelectionCapability {\n /** Warm a page's text geometry (idempotent; no-op without\n * `doc.text.select`). Layers call this when a page mounts. */\n ensurePage(pon: PageObjectNumber): void;\n isLoaded(pon: PageObjectNumber): boolean;\n /** Is a content-space point on (or near) text? Drives the I-beam cursor. */\n isOverText(pon: PageObjectNumber, point: Point): boolean;\n /** Begin a caret selection at a page point. Returns false if not near any\n * text — the caller deselects instead of capturing. */\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n /** Double-click / touch long-press: select the word around the point.\n * Returns false when the point has no selectable text (geometry not\n * loaded, or no glyph there) — nothing was selected. */\n selectWordAt(pon: PageObjectNumber, point: Point): boolean;\n /** Triple-click: select the whole visual line around the point. Same\n * success contract as {@link selectWordAt}. */\n selectLineAt(pon: PageObjectNumber, point: Point): boolean;\n /** Extend the current selection to a page point (drag). */\n extendTo(pon: PageObjectNumber, point: Point): void;\n /** The gesture ended (pointer-up) → notify `onCommit` consumers. */\n end(): void;\n /** Suppress / restore the default highlight visual (a consumer drawing its\n * own preview — the markup ghost). */\n setHighlightVisible(visible: boolean): void;\n highlightVisible(): boolean;\n}\n\n/**\n * The selection capability token. Typed to the full\n * {@link SelectionHostCapability} for this module, `/contract/host`, and\n * `/internal`; the\n * package root re-exports the SAME token narrowed to\n * {@link SelectionCapability}.\n */\nexport const SelectionToken = createCapabilityToken<SelectionHostCapability>('selection');\n"],"mappings":";;;;;;;;;AAwQA,MAAa,iBAAiB,sBAA+C,WAAW"}
@@ -166,7 +166,8 @@ type SelectionAction = {
166
166
  *
167
167
  * Framework-only plumbing (pointer-gesture intents, geometry warming, the
168
168
  * highlight-visibility handshake) lives on {@link SelectionHostCapability},
169
- * reachable only through `@embedpdf/plugin-selection/internal`. Both are
169
+ * reachable through `@embedpdf/plugin-selection/contract/host` (and the
170
+ * framework's `/internal` entry). Both are
170
171
  * the SAME runtime object — two typed lenses on one token.
171
172
  */
172
173
  interface SelectionCapability {
@@ -243,8 +244,9 @@ interface SelectionCapability {
243
244
  }
244
245
  /**
245
246
  * The HOST (framework) surface: what render layers, the interaction hub,
246
- * and sibling plugins need on top of the public lens. Import the token from
247
- * `@embedpdf/plugin-selection/internal`, never from application code. All
247
+ * and sibling plugins need on top of the public lens. Sibling plugins import
248
+ * the token from `@embedpdf/plugin-selection/contract/host`; framework
249
+ * implementation code may use `/internal`. Never use either from application code. All
248
250
  * point-based functions are gesture vocabulary (`…At` = point-addressed);
249
251
  * the programmatic equivalent is the public range-based {@link
250
252
  * SelectionCapability.select}. Every function here is INERT (no requests,
@@ -279,11 +281,12 @@ interface SelectionHostCapability extends SelectionCapability {
279
281
  }
280
282
  /**
281
283
  * The selection capability token. Typed to the full
282
- * {@link SelectionHostCapability} for this module and `/internal`; the
284
+ * {@link SelectionHostCapability} for this module, `/contract/host`, and
285
+ * `/internal`; the
283
286
  * package root re-exports the SAME token narrowed to
284
287
  * {@link SelectionCapability}.
285
288
  */
286
289
  declare const SelectionToken: import("@embedpdf/core").CapabilityToken<SelectionHostCapability>;
287
290
  //#endregion
288
291
  export { contentPointToPdf as _, SelectionHostCapability as a, SelectionRangeInput as c, SelectionToken as d, TextPosition as f, buildSelectionPageGeometry as g, SelectionSegment as h, SelectionEndpoint as i, SelectionSnapshot as l, SelectionPageGeometry as m, SelectionAction as n, SelectionMenuAnchor as o, TextRange as p, SelectionCapability as r, SelectionRange as s, GlyphPointer as t, SelectionState as u, toContentSegment as v, toContentTextQuad as y };
289
- //# sourceMappingURL=types-DCsyRlZS.d.ts.map
292
+ //# sourceMappingURL=types-CUrUVl6K.d.cts.map
@@ -166,7 +166,8 @@ type SelectionAction = {
166
166
  *
167
167
  * Framework-only plumbing (pointer-gesture intents, geometry warming, the
168
168
  * highlight-visibility handshake) lives on {@link SelectionHostCapability},
169
- * reachable only through `@embedpdf/plugin-selection/internal`. Both are
169
+ * reachable through `@embedpdf/plugin-selection/contract/host` (and the
170
+ * framework's `/internal` entry). Both are
170
171
  * the SAME runtime object — two typed lenses on one token.
171
172
  */
172
173
  interface SelectionCapability {
@@ -243,8 +244,9 @@ interface SelectionCapability {
243
244
  }
244
245
  /**
245
246
  * The HOST (framework) surface: what render layers, the interaction hub,
246
- * and sibling plugins need on top of the public lens. Import the token from
247
- * `@embedpdf/plugin-selection/internal`, never from application code. All
247
+ * and sibling plugins need on top of the public lens. Sibling plugins import
248
+ * the token from `@embedpdf/plugin-selection/contract/host`; framework
249
+ * implementation code may use `/internal`. Never use either from application code. All
248
250
  * point-based functions are gesture vocabulary (`…At` = point-addressed);
249
251
  * the programmatic equivalent is the public range-based {@link
250
252
  * SelectionCapability.select}. Every function here is INERT (no requests,
@@ -279,11 +281,12 @@ interface SelectionHostCapability extends SelectionCapability {
279
281
  }
280
282
  /**
281
283
  * The selection capability token. Typed to the full
282
- * {@link SelectionHostCapability} for this module and `/internal`; the
284
+ * {@link SelectionHostCapability} for this module, `/contract/host`, and
285
+ * `/internal`; the
283
286
  * package root re-exports the SAME token narrowed to
284
287
  * {@link SelectionCapability}.
285
288
  */
286
289
  declare const SelectionToken: import("@embedpdf/core").CapabilityToken<SelectionHostCapability>;
287
290
  //#endregion
288
291
  export { contentPointToPdf as _, SelectionHostCapability as a, SelectionRangeInput as c, SelectionToken as d, TextPosition as f, buildSelectionPageGeometry as g, SelectionSegment as h, SelectionEndpoint as i, SelectionSnapshot as l, SelectionPageGeometry as m, SelectionAction as n, SelectionMenuAnchor as o, TextRange as p, SelectionCapability as r, SelectionRange as s, GlyphPointer as t, SelectionState as u, toContentSegment as v, toContentTextQuad as y };
289
- //# sourceMappingURL=types-DCsyRlZS.d.cts.map
292
+ //# sourceMappingURL=types-CUrUVl6K.d.ts.map
@@ -0,0 +1,18 @@
1
+ //#region src/types.ts
2
+ /**
3
+ * The selection capability token. Typed to the full
4
+ * {@link SelectionHostCapability} for this module, `/contract/host`, and
5
+ * `/internal`; the
6
+ * package root re-exports the SAME token narrowed to
7
+ * {@link SelectionCapability}.
8
+ */
9
+ const SelectionToken = (0, require("@embedpdf/core").createCapabilityToken)("selection");
10
+ //#endregion
11
+ Object.defineProperty(exports, "SelectionToken", {
12
+ enumerable: true,
13
+ get: function() {
14
+ return SelectionToken;
15
+ }
16
+ });
17
+
18
+ //# sourceMappingURL=types-D9kEBMFj.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-D9kEBMFj.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type { Point, Rect, TextQuad } from '@embedpdf/core-geometry';\nimport type { SelectionSegment } from './geometry';\n\nexport type { SelectionSegment } from './geometry';\n\n// ── the public range vocabulary ─────────────────────────────────────────────\n\n/**\n * A position in a page's CHARACTER space — the space the engine's geometry\n * runs tile (`PageGeometryRun.charStart`) and search hits address\n * (`SearchMatch.charStart`). NOT a string offset into extracted text; the\n * two spaces are joined by the engine's `charMap` (see engine-core\n * `text/charmap.ts`), which `readText()` applies for you.\n */\nexport interface TextPosition {\n pon: PageObjectNumber;\n /** Character index, 0-based. */\n index: number;\n}\n\n/**\n * A half-open range in character space: `start` inclusive, `end` exclusive,\n * in document order (`start` at or before `end`). `end.index` may be 0 to\n * end a range exactly at a page boundary.\n */\nexport interface TextRange {\n start: TextPosition;\n end: TextPosition;\n}\n\n/**\n * What {@link SelectionCapability.select} accepts: a cross-page\n * {@link TextRange}, or a single-page span — the shape of a search hit, so\n * `select({ pon: hit.pageObjectNumber, start: hit.charStart, count: hit.charCount })`\n * needs no conversion.\n */\nexport type SelectionRangeInput =\n | TextRange\n | { pon: PageObjectNumber; start: number; count: number };\n\n// ── gesture-state vocabulary (host/internal) ────────────────────────────────\n\n/** A glyph address: a page + a flat character index within that page. */\nexport interface GlyphPointer {\n pon: PageObjectNumber;\n glyph: number;\n}\n\n/** Anchor = where the drag began, focus = the current end. Inclusive. */\nexport interface SelectionRange {\n anchor: GlyphPointer;\n focus: GlyphPointer;\n}\n\n/**\n * A selection boundary, anchored to the boundary GLYPH's own oriented cell.\n * `advance` is the reading direction of the segment it belongs to (+1 = the\n * frame's +x), so caret consumers place at the trailing edge without\n * re-deriving bidi from geometry. `rect` is the AABB (scroll targets).\n */\nexport interface SelectionEndpoint {\n pon: PageObjectNumber;\n glyphQuad: TextQuad;\n advance: 1 | -1;\n rect: Rect;\n}\n\n/** The selection's floating-UI anchor: a page + the selection's union box on\n * it, in CONTENT space. See {@link SelectionCapability.menuAnchor}. */\nexport interface SelectionMenuAnchor {\n pon: PageObjectNumber;\n bounds: Rect;\n}\n\nexport interface SelectionSnapshot {\n /** Per-page canonical segments — the ONE geometry consumers act on.\n * Boxes are derived views (`segment.rect`, or `rectsForPage()`). */\n pages: Array<{ pon: PageObjectNumber; segments: SelectionSegment[] }>;\n start: SelectionEndpoint | null;\n end: SelectionEndpoint | null;\n direction: 'forward' | 'backward';\n /**\n * The selection as a half-open character range in document order — a\n * valid {@link SelectionCapability.select} input (persist and restore).\n * Null when nothing is selected. Until a boundary page's text geometry\n * has loaded, its index may still exceed the page's character count\n * (`selectAll` settles as geometry arrives); consumers of the range\n * (`select`, `readText`) clamp, so round-tripping is always safe.\n */\n range: TextRange | null;\n}\n\nexport interface SelectionState {\n selection: SelectionRange | null;\n /** Derived per-line segments per page, in CONTENT space (y-down, PDF units). */\n segments: Record<number, SelectionSegment[]>;\n /** Pages whose text geometry has loaded (so the layer re-renders when ready). */\n loaded: Record<number, boolean>;\n /** When a consumer owns the selection visual (e.g. a markup tool draws its own\n * preview), the default highlight rects are suppressed. */\n highlightHidden: boolean;\n /** A selection GESTURE is in flight (drag / double- / triple-click, between\n * pointer-down and pointer-up). A readable FACT, not an event: derived\n * recomputes (page load, rotate, move) never touch it, and programmatic\n * selections are born settled. Consumers apply policy (e.g. a menu hides\n * while true). */\n selecting: boolean;\n}\n\nexport type SelectionAction =\n | { type: 'PAGE_LOADED'; pon: PageObjectNumber }\n | { type: 'SET'; selection: SelectionRange; segments: Record<number, SelectionSegment[]> }\n | { type: 'CLEAR' }\n | { type: 'SET_HIGHLIGHT_HIDDEN'; hidden: boolean }\n | { type: 'SET_SELECTING'; selecting: boolean };\n\n// ── the PUBLIC capability ───────────────────────────────────────────────────\n\n/**\n * The PUBLIC selection API — the documented, stable surface for application\n * code (toolbars, context menus, automation). Resolve it with the token\n * re-exported from the package root (`@embedpdf/plugin-selection`).\n *\n * The permission model: GEOMETRY enables selection (`doc.text.select`),\n * TEXT enables extraction (`doc.text.copy`) — neither implies the other.\n * Everything here except `readText()` works with the select scope alone, so\n * a deployment can allow selecting and highlighting while denying copy.\n *\n * Framework-only plumbing (pointer-gesture intents, geometry warming, the\n * highlight-visibility handshake) lives on {@link SelectionHostCapability},\n * reachable through `@embedpdf/plugin-selection/contract/host` (and the\n * framework's `/internal` entry). Both are\n * the SAME runtime object — two typed lenses on one token.\n */\nexport interface SelectionCapability {\n // ── authorization (mirrors the engine's own enforcement) ──\n /** Whether this caller may create selections at all (`doc.text.select`).\n * Authorization only — not \"is something selected\" ({@link hasSelection})\n * and not geometry readiness (a host concern). */\n canSelect(): boolean;\n /** Whether this caller may extract literal text (`doc.text.copy`) —\n * gates {@link readText} and any copy UI. */\n canCopy(): boolean;\n\n // ── programmatic selection (character space, half-open) ──\n /**\n * Select a range programmatically — the same path gestures use, so the\n * highlight, `onChange`, and markup bridges all behave identically. An\n * empty range clears. Throws the engine's `PermissionDenied` when\n * `doc.text.select` is not granted (gate UI with {@link canSelect}).\n */\n select(range: SelectionRangeInput): void;\n /** Select every character of the document. Same gating as {@link select}. */\n selectAll(): void;\n /** Clear the selection. Always allowed. */\n clear(): void;\n\n // ── reads ──\n /** Coherent read-model: per-page segments, endpoints, direction, and the\n * half-open {@link TextRange} (a valid {@link select} input). */\n snapshot(): SelectionSnapshot;\n /**\n * Whether a selection GESTURE is in flight (drag, double- or triple-click\n * — between pointer-down and pointer-up). Programmatic {@link select} /\n * {@link selectAll} never set it (a programmatic selection is born\n * settled), and derived recomputes (page geometry loading, rotate, move)\n * never touch it. THE fact selection-scoped UI applies policy to — e.g.\n * `<SelectionMenu>` hides while true and appears at pointer-up.\n */\n isSelecting(): boolean;\n /**\n * Where selection-scoped floating UI should attach: the union box of the\n * selection's segments on its END page (where the gesture finished),\n * falling back to the last page with materialized segments while a\n * boundary page's geometry is still loading. One anchor regardless of\n * cross-page selection — the shape every anchored-overlay consumer\n * positions on. Null when nothing is selected (or nothing has\n * materialized yet).\n */\n menuAnchor(): SelectionMenuAnchor | null;\n hasSelection(): boolean;\n /** The pages the current selection covers (those with at least one\n * segment) — so a cross-page action (e.g. markup creation) can fan out\n * per page. */\n selectedPages(): PageObjectNumber[];\n /** Per-line oriented segments for a page, in content space — build your\n * own highlight layer from these. */\n segmentsForPage(pon: PageObjectNumber): SelectionSegment[];\n /** The segments' AABBs — for consumers that genuinely want boxes (scroll,\n * conservative regions). Never a substitute for the oriented quads in\n * geometry that gets drawn or persisted. */\n rectsForPage(pon: PageObjectNumber): Rect[];\n\n // ── text extraction (requires doc.text.copy) ──\n /**\n * The selected text. Fetches each selected page's text snapshot (cached\n * per page, immutable per content version) and slices it by the\n * selection's character range through the engine's `charMap` — at most\n * one text read per page per content version, amortized across every\n * call. Pages are joined with `\\n`. Resolves `''` when nothing is\n * selected; rejects with `PermissionDenied` when `doc.text.copy` is not\n * granted. Clipboard writes are deliberately NOT here — this package is\n * DOM-free; use `@embedpdf/web`'s clipboard helpers (or your platform's)\n * on top of this.\n */\n readText(): Promise<string>;\n\n // ── signals ──\n /** Fires whenever the selection segments change (drag-extend, programmatic\n * select, recompute) — for live preview. Returns an unsubscriber. */\n onChange(cb: () => void): () => void;\n /** Fires when a selection gesture ends (pointer-up) — the commit point\n * consumers (markup creation, copy prefetch) act on. */\n onCommit(cb: () => void): () => void;\n}\n\n// ── the HOST capability ─────────────────────────────────────────────────────\n\n/**\n * The HOST (framework) surface: what render layers, the interaction hub,\n * and sibling plugins need on top of the public lens. Sibling plugins import\n * the token from `@embedpdf/plugin-selection/contract/host`; framework\n * implementation code may use `/internal`. Never use either from application code. All\n * point-based functions are gesture vocabulary (`…At` = point-addressed);\n * the programmatic equivalent is the public range-based {@link\n * SelectionCapability.select}. Every function here is INERT (no requests,\n * no state) when `canSelect()` is false — the pointer handler needs no\n * permission special-casing.\n */\nexport interface SelectionHostCapability extends SelectionCapability {\n /** Warm a page's text geometry (idempotent; no-op without\n * `doc.text.select`). Layers call this when a page mounts. */\n ensurePage(pon: PageObjectNumber): void;\n isLoaded(pon: PageObjectNumber): boolean;\n /** Is a content-space point on (or near) text? Drives the I-beam cursor. */\n isOverText(pon: PageObjectNumber, point: Point): boolean;\n /** Begin a caret selection at a page point. Returns false if not near any\n * text — the caller deselects instead of capturing. */\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n /** Double-click / touch long-press: select the word around the point.\n * Returns false when the point has no selectable text (geometry not\n * loaded, or no glyph there) — nothing was selected. */\n selectWordAt(pon: PageObjectNumber, point: Point): boolean;\n /** Triple-click: select the whole visual line around the point. Same\n * success contract as {@link selectWordAt}. */\n selectLineAt(pon: PageObjectNumber, point: Point): boolean;\n /** Extend the current selection to a page point (drag). */\n extendTo(pon: PageObjectNumber, point: Point): void;\n /** The gesture ended (pointer-up) → notify `onCommit` consumers. */\n end(): void;\n /** Suppress / restore the default highlight visual (a consumer drawing its\n * own preview — the markup ghost). */\n setHighlightVisible(visible: boolean): void;\n highlightVisible(): boolean;\n}\n\n/**\n * The selection capability token. Typed to the full\n * {@link SelectionHostCapability} for this module, `/contract/host`, and\n * `/internal`; the\n * package root re-exports the SAME token narrowed to\n * {@link SelectionCapability}.\n */\nexport const SelectionToken = createCapabilityToken<SelectionHostCapability>('selection');\n"],"mappings":";;;;;;;;AAwQA,MAAa,kBAAA,2BAAA,CAAA,CAAA,sBAAA,CAAgE,WAAW"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-selection",
3
- "version": "3.0.0-next.10",
3
+ "version": "3.0.0-next.12",
4
4
  "private": false,
5
5
  "description": "Text selection: reads engine text geometry (PDF y-up), bridges it to viewer content space, hit-tests glyphs, and renders highlight rects. The first client of the interaction hub.",
6
6
  "type": "module",
@@ -12,6 +12,14 @@
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
+ "./contract": {
16
+ "import": "./dist/contract.js",
17
+ "require": "./dist/contract.cjs"
18
+ },
19
+ "./contract/host": {
20
+ "import": "./dist/contract/host.js",
21
+ "require": "./dist/contract/host.cjs"
22
+ },
15
23
  "./internal": {
16
24
  "import": "./dist/internal.js",
17
25
  "require": "./dist/internal.cjs"
@@ -22,10 +30,10 @@
22
30
  "dist"
23
31
  ],
24
32
  "dependencies": {
25
- "@embedpdf/core": "3.0.0-next.10",
26
- "@embedpdf/core-geometry": "3.0.0-next.10",
27
- "@embedpdf/engine-core": "3.0.0-next.10",
28
- "@embedpdf/plugin-interaction": "3.0.0-next.10"
33
+ "@embedpdf/core-geometry": "3.0.0-next.12",
34
+ "@embedpdf/core": "3.0.0-next.12",
35
+ "@embedpdf/plugin-interaction": "3.0.0-next.12",
36
+ "@embedpdf/engine-core": "3.0.0-next.12"
29
37
  },
30
38
  "devDependencies": {
31
39
  "typescript": "^5.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"reducer-Dy0YPuX0.js","names":[],"sources":["../src/types.ts","../src/geometry.ts","../src/capability.ts","../src/handler.ts","../src/reducer.ts"],"sourcesContent":["import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type { Point, Rect, TextQuad } from '@embedpdf/core-geometry';\nimport type { SelectionSegment } from './geometry';\n\nexport type { SelectionSegment } from './geometry';\n\n// ── the public range vocabulary ─────────────────────────────────────────────\n\n/**\n * A position in a page's CHARACTER space — the space the engine's geometry\n * runs tile (`PageGeometryRun.charStart`) and search hits address\n * (`SearchMatch.charStart`). NOT a string offset into extracted text; the\n * two spaces are joined by the engine's `charMap` (see engine-core\n * `text/charmap.ts`), which `readText()` applies for you.\n */\nexport interface TextPosition {\n pon: PageObjectNumber;\n /** Character index, 0-based. */\n index: number;\n}\n\n/**\n * A half-open range in character space: `start` inclusive, `end` exclusive,\n * in document order (`start` at or before `end`). `end.index` may be 0 to\n * end a range exactly at a page boundary.\n */\nexport interface TextRange {\n start: TextPosition;\n end: TextPosition;\n}\n\n/**\n * What {@link SelectionCapability.select} accepts: a cross-page\n * {@link TextRange}, or a single-page span — the shape of a search hit, so\n * `select({ pon: hit.pageObjectNumber, start: hit.charStart, count: hit.charCount })`\n * needs no conversion.\n */\nexport type SelectionRangeInput =\n | TextRange\n | { pon: PageObjectNumber; start: number; count: number };\n\n// ── gesture-state vocabulary (host/internal) ────────────────────────────────\n\n/** A glyph address: a page + a flat character index within that page. */\nexport interface GlyphPointer {\n pon: PageObjectNumber;\n glyph: number;\n}\n\n/** Anchor = where the drag began, focus = the current end. Inclusive. */\nexport interface SelectionRange {\n anchor: GlyphPointer;\n focus: GlyphPointer;\n}\n\n/**\n * A selection boundary, anchored to the boundary GLYPH's own oriented cell.\n * `advance` is the reading direction of the segment it belongs to (+1 = the\n * frame's +x), so caret consumers place at the trailing edge without\n * re-deriving bidi from geometry. `rect` is the AABB (scroll targets).\n */\nexport interface SelectionEndpoint {\n pon: PageObjectNumber;\n glyphQuad: TextQuad;\n advance: 1 | -1;\n rect: Rect;\n}\n\n/** The selection's floating-UI anchor: a page + the selection's union box on\n * it, in CONTENT space. See {@link SelectionCapability.menuAnchor}. */\nexport interface SelectionMenuAnchor {\n pon: PageObjectNumber;\n bounds: Rect;\n}\n\nexport interface SelectionSnapshot {\n /** Per-page canonical segments — the ONE geometry consumers act on.\n * Boxes are derived views (`segment.rect`, or `rectsForPage()`). */\n pages: Array<{ pon: PageObjectNumber; segments: SelectionSegment[] }>;\n start: SelectionEndpoint | null;\n end: SelectionEndpoint | null;\n direction: 'forward' | 'backward';\n /**\n * The selection as a half-open character range in document order — a\n * valid {@link SelectionCapability.select} input (persist and restore).\n * Null when nothing is selected. Until a boundary page's text geometry\n * has loaded, its index may still exceed the page's character count\n * (`selectAll` settles as geometry arrives); consumers of the range\n * (`select`, `readText`) clamp, so round-tripping is always safe.\n */\n range: TextRange | null;\n}\n\nexport interface SelectionState {\n selection: SelectionRange | null;\n /** Derived per-line segments per page, in CONTENT space (y-down, PDF units). */\n segments: Record<number, SelectionSegment[]>;\n /** Pages whose text geometry has loaded (so the layer re-renders when ready). */\n loaded: Record<number, boolean>;\n /** When a consumer owns the selection visual (e.g. a markup tool draws its own\n * preview), the default highlight rects are suppressed. */\n highlightHidden: boolean;\n /** A selection GESTURE is in flight (drag / double- / triple-click, between\n * pointer-down and pointer-up). A readable FACT, not an event: derived\n * recomputes (page load, rotate, move) never touch it, and programmatic\n * selections are born settled. Consumers apply policy (e.g. a menu hides\n * while true). */\n selecting: boolean;\n}\n\nexport type SelectionAction =\n | { type: 'PAGE_LOADED'; pon: PageObjectNumber }\n | { type: 'SET'; selection: SelectionRange; segments: Record<number, SelectionSegment[]> }\n | { type: 'CLEAR' }\n | { type: 'SET_HIGHLIGHT_HIDDEN'; hidden: boolean }\n | { type: 'SET_SELECTING'; selecting: boolean };\n\n// ── the PUBLIC capability ───────────────────────────────────────────────────\n\n/**\n * The PUBLIC selection API — the documented, stable surface for application\n * code (toolbars, context menus, automation). Resolve it with the token\n * re-exported from the package root (`@embedpdf/plugin-selection`).\n *\n * The permission model: GEOMETRY enables selection (`doc.text.select`),\n * TEXT enables extraction (`doc.text.copy`) — neither implies the other.\n * Everything here except `readText()` works with the select scope alone, so\n * a deployment can allow selecting and highlighting while denying copy.\n *\n * Framework-only plumbing (pointer-gesture intents, geometry warming, the\n * highlight-visibility handshake) lives on {@link SelectionHostCapability},\n * reachable only through `@embedpdf/plugin-selection/internal`. Both are\n * the SAME runtime object — two typed lenses on one token.\n */\nexport interface SelectionCapability {\n // ── authorization (mirrors the engine's own enforcement) ──\n /** Whether this caller may create selections at all (`doc.text.select`).\n * Authorization only — not \"is something selected\" ({@link hasSelection})\n * and not geometry readiness (a host concern). */\n canSelect(): boolean;\n /** Whether this caller may extract literal text (`doc.text.copy`) —\n * gates {@link readText} and any copy UI. */\n canCopy(): boolean;\n\n // ── programmatic selection (character space, half-open) ──\n /**\n * Select a range programmatically — the same path gestures use, so the\n * highlight, `onChange`, and markup bridges all behave identically. An\n * empty range clears. Throws the engine's `PermissionDenied` when\n * `doc.text.select` is not granted (gate UI with {@link canSelect}).\n */\n select(range: SelectionRangeInput): void;\n /** Select every character of the document. Same gating as {@link select}. */\n selectAll(): void;\n /** Clear the selection. Always allowed. */\n clear(): void;\n\n // ── reads ──\n /** Coherent read-model: per-page segments, endpoints, direction, and the\n * half-open {@link TextRange} (a valid {@link select} input). */\n snapshot(): SelectionSnapshot;\n /**\n * Whether a selection GESTURE is in flight (drag, double- or triple-click\n * — between pointer-down and pointer-up). Programmatic {@link select} /\n * {@link selectAll} never set it (a programmatic selection is born\n * settled), and derived recomputes (page geometry loading, rotate, move)\n * never touch it. THE fact selection-scoped UI applies policy to — e.g.\n * `<SelectionMenu>` hides while true and appears at pointer-up.\n */\n isSelecting(): boolean;\n /**\n * Where selection-scoped floating UI should attach: the union box of the\n * selection's segments on its END page (where the gesture finished),\n * falling back to the last page with materialized segments while a\n * boundary page's geometry is still loading. One anchor regardless of\n * cross-page selection — the shape every anchored-overlay consumer\n * positions on. Null when nothing is selected (or nothing has\n * materialized yet).\n */\n menuAnchor(): SelectionMenuAnchor | null;\n hasSelection(): boolean;\n /** The pages the current selection covers (those with at least one\n * segment) — so a cross-page action (e.g. markup creation) can fan out\n * per page. */\n selectedPages(): PageObjectNumber[];\n /** Per-line oriented segments for a page, in content space — build your\n * own highlight layer from these. */\n segmentsForPage(pon: PageObjectNumber): SelectionSegment[];\n /** The segments' AABBs — for consumers that genuinely want boxes (scroll,\n * conservative regions). Never a substitute for the oriented quads in\n * geometry that gets drawn or persisted. */\n rectsForPage(pon: PageObjectNumber): Rect[];\n\n // ── text extraction (requires doc.text.copy) ──\n /**\n * The selected text. Fetches each selected page's text snapshot (cached\n * per page, immutable per content version) and slices it by the\n * selection's character range through the engine's `charMap` — at most\n * one text read per page per content version, amortized across every\n * call. Pages are joined with `\\n`. Resolves `''` when nothing is\n * selected; rejects with `PermissionDenied` when `doc.text.copy` is not\n * granted. Clipboard writes are deliberately NOT here — this package is\n * DOM-free; use `@embedpdf/web`'s clipboard helpers (or your platform's)\n * on top of this.\n */\n readText(): Promise<string>;\n\n // ── signals ──\n /** Fires whenever the selection segments change (drag-extend, programmatic\n * select, recompute) — for live preview. Returns an unsubscriber. */\n onChange(cb: () => void): () => void;\n /** Fires when a selection gesture ends (pointer-up) — the commit point\n * consumers (markup creation, copy prefetch) act on. */\n onCommit(cb: () => void): () => void;\n}\n\n// ── the HOST capability ─────────────────────────────────────────────────────\n\n/**\n * The HOST (framework) surface: what render layers, the interaction hub,\n * and sibling plugins need on top of the public lens. Import the token from\n * `@embedpdf/plugin-selection/internal`, never from application code. All\n * point-based functions are gesture vocabulary (`…At` = point-addressed);\n * the programmatic equivalent is the public range-based {@link\n * SelectionCapability.select}. Every function here is INERT (no requests,\n * no state) when `canSelect()` is false — the pointer handler needs no\n * permission special-casing.\n */\nexport interface SelectionHostCapability extends SelectionCapability {\n /** Warm a page's text geometry (idempotent; no-op without\n * `doc.text.select`). Layers call this when a page mounts. */\n ensurePage(pon: PageObjectNumber): void;\n isLoaded(pon: PageObjectNumber): boolean;\n /** Is a content-space point on (or near) text? Drives the I-beam cursor. */\n isOverText(pon: PageObjectNumber, point: Point): boolean;\n /** Begin a caret selection at a page point. Returns false if not near any\n * text — the caller deselects instead of capturing. */\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n /** Double-click / touch long-press: select the word around the point.\n * Returns false when the point has no selectable text (geometry not\n * loaded, or no glyph there) — nothing was selected. */\n selectWordAt(pon: PageObjectNumber, point: Point): boolean;\n /** Triple-click: select the whole visual line around the point. Same\n * success contract as {@link selectWordAt}. */\n selectLineAt(pon: PageObjectNumber, point: Point): boolean;\n /** Extend the current selection to a page point (drag). */\n extendTo(pon: PageObjectNumber, point: Point): void;\n /** The gesture ended (pointer-up) → notify `onCommit` consumers. */\n end(): void;\n /** Suppress / restore the default highlight visual (a consumer drawing its\n * own preview — the markup ghost). */\n setHighlightVisible(visible: boolean): void;\n highlightVisible(): boolean;\n}\n\n/**\n * The selection capability token. Typed to the full\n * {@link SelectionHostCapability} for this module and `/internal`; the\n * package root re-exports the SAME token narrowed to\n * {@link SelectionCapability}.\n */\nexport const SelectionToken = createCapabilityToken<SelectionHostCapability>('selection');\n","/**\n * The selection plugin's COORDINATE SEAM — and nothing else.\n *\n * Segmentation, hit-testing, and word/line expansion live in the canonical\n * engine-core text layout (`@embedpdf/engine-core` `text/layout`): a text\n * range has exactly one segmentation whether it came from a drag or a\n * search. This module only converts between the viewer's content space\n * (y-down, crop-relative) and the engine's PDF space (y-up) — pointer\n * points inward, canonical segments outward. The PDF↔content y-flip is the\n * geometry package's `pageGeometry` (the one place that math lives), never\n * hand-rolled here.\n */\nimport {\n applyPoint,\n invert,\n pageGeometry,\n textQuadBounds,\n type Mat2D,\n type Point,\n type PointIn,\n type Rect,\n type TextQuad,\n} from '@embedpdf/core-geometry';\nimport {\n buildPageTextLayout,\n type PageGeometrySnapshot,\n type PageTextLayout,\n type PdfPoint,\n type PdfQuad,\n type PdfRect,\n type PdfTextSegment,\n} from '@embedpdf/engine-core/runtime';\n\n/**\n * One merged visual line of a selection, in content space — the structural\n * twin of the engine's `PdfTextSegment`. `quad` carries frame-geometric\n * corner semantics (upper = ascent side, start = the frame's −x); `advance`\n * is the READING direction along the baseline (+1 = toward `end`), derived\n * from the glyph sequence — geometry and bidi stay separate concerns.\n * `rect` is the content-space AABB.\n */\nexport interface SelectionSegment {\n quad: TextQuad;\n rect: Rect;\n advance: 1 | -1;\n}\n\n/** A page's cached selection geometry: the canonical layout + the seam. */\nexport interface SelectionPageGeometry {\n layout: PageTextLayout;\n toContent: Mat2D<'pdf', 'content'>;\n fromContent: Mat2D<'content', 'pdf'>;\n}\n\n/**\n * Build a page's selection geometry. Page display rotation does NOT enter\n * here — the overlay rides the page's CSS rotation, so content space stays\n * un-rotated (the crop-relative y-flip is the only conversion; zoom = 1,\n * the viewer's zoom is applied later by `PageTransform.toPixels`).\n */\nexport function buildSelectionPageGeometry(\n snapshot: PageGeometrySnapshot,\n crop: PdfRect,\n rotation: 0 | 90 | 180 | 270,\n userUnit: number,\n): SelectionPageGeometry {\n const { pdfToContent } = pageGeometry({ crop, rotation, userUnit }, 1);\n return {\n layout: buildPageTextLayout(snapshot),\n toContent: pdfToContent,\n fromContent: invert(pdfToContent),\n };\n}\n\n/** A content-space pointer point in the layout's PDF space. */\nexport function contentPointToPdf(geom: SelectionPageGeometry, p: Point): PdfPoint {\n return applyPoint(geom.fromContent, p as PointIn<'content'>);\n}\n\n/**\n * A canonical PDF quad in content space. Engine quads carry frame-geometric\n * slot semantics (`p1..p4` = upper-start, upper-end, lower-start,\n * lower-end) — a trusted producer, so the y-flip maps corners straight onto\n * their names; imported-annotation normalization does NOT apply here.\n */\nexport function toContentTextQuad(geom: SelectionPageGeometry, quad: PdfQuad): TextQuad {\n const pt = (p: PdfPoint): Point => applyPoint(geom.toContent, p as PointIn<'pdf'>);\n return {\n upperStart: pt(quad.p1),\n upperEnd: pt(quad.p2),\n lowerStart: pt(quad.p3),\n lowerEnd: pt(quad.p4),\n };\n}\n\n/** A canonical segment in content space (`rect` recomputed from the mapped\n * quad so the pair can never drift). */\nexport function toContentSegment(\n geom: SelectionPageGeometry,\n segment: PdfTextSegment,\n): SelectionSegment {\n const quad = toContentTextQuad(geom, segment.quad);\n return { quad, rect: textQuadBounds(quad), advance: segment.advance };\n}\n","import type { PageObjectNumber, PluginContext } from '@embedpdf/core';\nimport { textQuadBounds, type Point, type Rect } from '@embedpdf/core-geometry';\nimport {\n expandTextRangeToLine,\n expandTextRangeToWord,\n PermissionDenied,\n sliceTextByChars,\n textGlyphAt,\n textGlyphQuad,\n textSegmentsForRange,\n type PageGeometrySnapshot,\n type PageTextSnapshot,\n} from '@embedpdf/engine-core/runtime';\nimport {\n buildSelectionPageGeometry,\n contentPointToPdf,\n toContentSegment,\n toContentTextQuad,\n type SelectionPageGeometry,\n type SelectionSegment,\n} from './geometry';\nimport type {\n GlyphPointer,\n SelectionAction,\n SelectionEndpoint,\n SelectionHostCapability,\n SelectionMenuAnchor,\n SelectionRange,\n SelectionRangeInput,\n SelectionSnapshot,\n SelectionState,\n TextRange,\n} from './types';\n\nconst EMPTY_SEGMENTS: SelectionSegment[] = [];\n\n/** How many page-text reads `readText()` keeps in flight at once. */\nconst TEXT_READ_CONCURRENCY = 8;\n\n/**\n * The selection capability — ONE closure returning the host lens (the\n * public lens is the same object, narrowed at the package root).\n *\n * State model: the reducer holds the selection range and the derived\n * content-space segments; the large, non-serializable per-page caches live\n * HERE in the closure, split by what invalidates them:\n *\n * - `rawGeometry` — PDF-space geometry snapshots, rotation-independent\n * (pages load normalized; see `PageRotateResult`). Dropped only when a\n * page leaves the registry or page CONTENT changes (redaction apply /\n * flatten).\n * - `derived` — the content-space transform + layout, keyed by the\n * layout params (crop/rotation/userUnit) so a view-rotate re-derives\n * from the cached raw snapshot with NO refetch.\n * - `textSnapshots` — per-page text (for `readText`), same content-only\n * invalidation as `rawGeometry`.\n *\n * Permission model: `doc.text.select` gates geometry (selection exists),\n * `doc.text.copy` gates text (extraction) — neither implies the other.\n * Host gesture functions are INERT when select is denied (`ensurePage`\n * no-ops, so nothing warms and nothing hit-tests: the pointer handler\n * needs no special-casing); the public `select()` THROWS `PermissionDenied`\n * so programmatic misuse is loud; `clear()` is always allowed. The engine\n * independently enforces both scopes — these gates exist so the plugin\n * never issues a request that is guaranteed to fail.\n *\n * Selection is cross-page: glyphs are ordered globally by (pageIndex,\n * glyph), so a drag from page 2 into page 4 selects the tail of 2, all of\n * 3, and the head of 4. `recompute` rebuilds the merged line segments for\n * every loaded page in the span, re-runs whenever a mid-span page finishes\n * loading, and CLAMPS stored endpoints once their page's geometry is known\n * (so `selectAll`'s open-ended focus settles to the real last character).\n */\nexport function createSelectionCapability(\n ctx: PluginContext<SelectionState, SelectionAction>,\n): SelectionHostCapability {\n const rawGeometry = new Map<PageObjectNumber, PageGeometrySnapshot>();\n const derived = new Map<PageObjectNumber, { key: string; geom: SelectionPageGeometry }>();\n const pending = new Set<PageObjectNumber>();\n const textSnapshots = new Map<PageObjectNumber, Promise<PageTextSnapshot>>();\n /** Bumped on content invalidation; in-flight reads from before are discarded. */\n let epoch = 0;\n // Consumers (e.g. text-markup) observe the selection without selection knowing\n // about them: `change` fires whenever the segments change, `commit` when a\n // gesture ends (pointer-up). One typed callback each — not an event bus.\n const changeCbs = new Set<() => void>();\n const commitCbs = new Set<() => void>();\n const fireChange = (): void => changeCbs.forEach((cb) => cb());\n\n const canSelect = (): boolean => ctx.doc?.security.allows('doc.text.select') ?? false;\n const canCopy = (): boolean => ctx.doc?.security.allows('doc.text.copy') ?? false;\n\n const layoutOf = (pon: PageObjectNumber) =>\n ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n const pageIndexOf = (pon: PageObjectNumber): number =>\n ctx.document()?.pages.findIndex((p) => p.pageObjectNumber === pon) ?? -1;\n const ponAtIndex = (i: number): PageObjectNumber | undefined =>\n ctx.document()?.pages[i]?.pageObjectNumber;\n\n /**\n * The content-space geometry for a page, derived on demand from the raw\n * snapshot + the CURRENT layout params. A view-rotate (or crop change)\n * changes the key and re-derives — raw caches and the engine are never\n * touched (rotation is presentation metadata).\n */\n function geometryFor(pon: PageObjectNumber): SelectionPageGeometry | null {\n const raw = rawGeometry.get(pon);\n const layout = layoutOf(pon);\n if (!raw || !layout) return null;\n const crop = layout.boxes.crop;\n const key = `${layout.rotation}|${layout.userUnit}|${crop.left},${crop.bottom},${crop.right},${crop.top}`;\n const hit = derived.get(pon);\n if (hit && hit.key === key) return hit.geom;\n const geom = buildSelectionPageGeometry(raw, crop, layout.rotation, layout.userUnit);\n derived.set(pon, { key, geom });\n return geom;\n }\n\n const glyphAt = (geom: SelectionPageGeometry, point: Point): number | null =>\n textGlyphAt(geom.layout, contentPointToPdf(geom, point));\n\n // Order the two ends of a selection by document position (page, then glyph).\n function orderedEnds(sel: SelectionRange): {\n start: GlyphPointer;\n end: GlyphPointer;\n direction: 'forward' | 'backward';\n } {\n const ai = pageIndexOf(sel.anchor.pon);\n const fi = pageIndexOf(sel.focus.pon);\n const anchorFirst = ai < fi || (ai === fi && sel.anchor.glyph <= sel.focus.glyph);\n return anchorFirst\n ? { start: sel.anchor, end: sel.focus, direction: 'forward' }\n : { start: sel.focus, end: sel.anchor, direction: 'backward' };\n }\n\n function endpointFor(ptr: GlyphPointer, which: 'start' | 'end'): SelectionEndpoint | null {\n const segments = ctx.getState().segments[ptr.pon] ?? EMPTY_SEGMENTS;\n if (!segments.length) return null;\n const segment = which === 'start' ? segments[0] : segments[segments.length - 1];\n\n // Anchor the endpoint to the boundary GLYPH's own oriented cell so caret\n // placement lands on the exact character edge; fall back to the segment\n // when the glyph is degenerate (e.g. a generated space).\n const geom = geometryFor(ptr.pon);\n const cell = geom ? textGlyphQuad(geom.layout, ptr.glyph) : null;\n if (geom && cell) {\n const glyphQuad = toContentTextQuad(geom, cell);\n return {\n pon: ptr.pon,\n glyphQuad,\n advance: segment.advance,\n rect: textQuadBounds(glyphQuad),\n };\n }\n return { pon: ptr.pon, glyphQuad: segment.quad, advance: segment.advance, rect: segment.rect };\n }\n\n /** Union box of a page's segments, or null when none have materialized. */\n function pageBounds(pon: PageObjectNumber): Rect | null {\n const segs = ctx.getState().segments[pon];\n if (!segs || segs.length === 0) return null;\n let x1 = Infinity;\n let y1 = Infinity;\n let x2 = -Infinity;\n let y2 = -Infinity;\n for (const s of segs) {\n x1 = Math.min(x1, s.rect.x);\n y1 = Math.min(y1, s.rect.y);\n x2 = Math.max(x2, s.rect.x + s.rect.width);\n y2 = Math.max(y2, s.rect.y + s.rect.height);\n }\n return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };\n }\n\n function menuAnchor(): SelectionMenuAnchor | null {\n const sel = ctx.getState().selection;\n if (!sel) return null;\n const { end } = orderedEnds(sel);\n // Prefer the gesture's end page; while its geometry is still loading,\n // fall back to the LAST page (document order) with materialized segments\n // so the anchor never teleports backwards mid-drag.\n let bounds = pageBounds(end.pon);\n if (bounds) return { pon: end.pon, bounds };\n const pages = ctx.document()?.pages ?? [];\n for (let i = pages.length - 1; i >= 0; i--) {\n const pon = pages[i].pageObjectNumber;\n bounds = pageBounds(pon);\n if (bounds) return { pon, bounds };\n }\n return null;\n }\n\n function snapshot(): SelectionSnapshot {\n const { selection, segments } = ctx.getState();\n const pages = Object.keys(segments)\n .map(Number)\n .filter((pon) => (segments[pon]?.length ?? 0) > 0)\n .map((pon) => ({ pon: pon as PageObjectNumber, segments: segments[pon] }));\n if (!selection) return { pages, start: null, end: null, direction: 'forward', range: null };\n const { start, end, direction } = orderedEnds(selection);\n return {\n pages,\n start: endpointFor(start, 'start'),\n end: endpointFor(end, 'end'),\n direction,\n range: {\n start: { pon: start.pon, index: start.glyph },\n end: { pon: end.pon, index: end.glyph + 1 },\n },\n };\n }\n\n function ensurePage(pon: PageObjectNumber): void {\n if (rawGeometry.has(pon) || pending.has(pon)) return;\n // Authorized-only warming: without doc.text.select the read is\n // guaranteed to be rejected — don't issue it. The engine guard stays\n // the security boundary either way.\n if (!canSelect()) return;\n const doc = ctx.doc;\n if (!doc || !layoutOf(pon)) return;\n pending.add(pon);\n const at = epoch;\n doc\n .page(pon)\n .geometry.read()\n .then(\n (snapshot) => {\n pending.delete(pon);\n if (at !== epoch) return; // content changed while in flight — stale\n rawGeometry.set(pon, snapshot);\n ctx.dispatch({ type: 'PAGE_LOADED', pon });\n if (ctx.getState().selection) recompute(); // a mid-span page arrived → fill its segments\n },\n () => {\n pending.delete(pon); // doc closed / read aborted / denied — ignore\n },\n );\n }\n\n /** Clamp a pointer into its page's real character range once geometry is\n * known — how `selectAll`'s open-ended focus settles. */\n function clampPointer(ptr: GlyphPointer): GlyphPointer {\n const geom = geometryFor(ptr.pon);\n if (!geom) return ptr;\n const max = Math.max(geom.layout.glyphs.length - 1, 0);\n const glyph = Math.max(0, Math.min(ptr.glyph, max));\n return glyph === ptr.glyph ? ptr : { pon: ptr.pon, glyph };\n }\n\n // Rebuild merged line segments for every loaded page in the span; warm the\n // rest (they recompute on arrival). Clears when an endpoint's page has left\n // the registry — a range across a structural edit is meaningless.\n function recompute(sel: SelectionRange | null = ctx.getState().selection): void {\n if (!sel) return;\n const clamped: SelectionRange = {\n anchor: clampPointer(sel.anchor),\n focus: clampPointer(sel.focus),\n };\n const { start, end } = orderedEnds(clamped);\n const si = pageIndexOf(start.pon);\n const ei = pageIndexOf(end.pon);\n if (si < 0 || ei < 0) {\n clearSelection();\n return;\n }\n const segments: Record<number, SelectionSegment[]> = {};\n for (let i = si; i <= ei; i++) {\n const pon = ponAtIndex(i);\n if (pon == null) continue;\n const geom = geometryFor(pon);\n if (!geom) {\n ensurePage(pon); // not loaded yet — it'll recompute when ready\n continue;\n }\n const from = i === si ? start.glyph : 0;\n const to = i === ei ? end.glyph : geom.layout.glyphs.length - 1;\n segments[pon] = textSegmentsForRange(geom.layout, from, to - from + 1).map((s) =>\n toContentSegment(geom, s),\n );\n }\n ctx.dispatch({ type: 'SET', selection: clamped, segments });\n fireChange();\n }\n\n /** Flip the gesture-in-flight FACT (see `SelectionState.selecting`). Set\n * BEFORE the recompute that fires `onChange`, so change listeners always\n * observe a coherent (selecting, segments) pair. */\n function setSelecting(selecting: boolean): void {\n ctx.dispatch({ type: 'SET_SELECTING', selecting });\n }\n\n function clearSelection(): void {\n setSelecting(false);\n ctx.dispatch({ type: 'CLEAR' });\n fireChange();\n }\n\n // Set the selection to a flat [from,to] glyph span on one page (word/line).\n // These are GESTURE vocabulary (double-/triple-click) — the commit arrives\n // at the pointer-up that follows, so the span counts as in-flight until then.\n // Returns whether a span actually engaged (geometry present AND a glyph\n // under the point) — the fact haptics and other success-gated feedback key\n // on, so nothing ever buzzes over blank space.\n function selectSpanAt(pon: PageObjectNumber, point: Point, expand: 'word' | 'line'): boolean {\n const geom = geometryFor(pon);\n if (!geom) return false;\n const i = glyphAt(geom, point);\n if (i == null) return false;\n const [from, to] =\n expand === 'word'\n ? expandTextRangeToWord(geom.layout, i)\n : expandTextRangeToLine(geom.layout, i);\n setSelecting(true);\n recompute({ anchor: { pon, glyph: from }, focus: { pon, glyph: to } });\n return true;\n }\n\n function assertCanSelect(context: string): void {\n if (!canSelect()) throw new PermissionDenied('doc.text.select', context);\n }\n\n /** The public programmatic entry: a half-open character range in. */\n function select(input: SelectionRangeInput): void {\n assertCanSelect('selection.select');\n const range: TextRange =\n 'pon' in input\n ? {\n start: { pon: input.pon, index: input.start },\n end: { pon: input.pon, index: input.start + input.count },\n }\n : input;\n const si = pageIndexOf(range.start.pon);\n let ei = pageIndexOf(range.end.pon);\n if (si < 0 || ei < 0) {\n throw new Error(\n `select: unknown page object ${si < 0 ? range.start.pon : range.end.pon}`,\n );\n }\n let endIndex = range.end.index;\n if (ei < si || (ei === si && endIndex <= range.start.index)) {\n clearSelection(); // empty range = no selection\n return;\n }\n if (endIndex === 0) {\n // Half-open end exactly at a page boundary: the last included\n // character is the previous page's last one. Its index settles via\n // clamping once that page's geometry loads.\n ei -= 1;\n if (ei < si) {\n clearSelection();\n return;\n }\n endIndex = Number.MAX_SAFE_INTEGER;\n }\n const focusPon = ponAtIndex(ei);\n if (focusPon == null) throw new Error(`select: unknown page at index ${ei}`);\n setSelecting(false); // programmatic selections are born settled\n recompute({\n anchor: { pon: range.start.pon, glyph: Math.max(0, range.start.index) },\n focus: { pon: focusPon, glyph: endIndex - 1 },\n });\n }\n\n function selectAll(): void {\n assertCanSelect('selection.selectAll');\n const pages = ctx.document()?.pages ?? [];\n if (pages.length === 0) return;\n setSelecting(false); // programmatic selections are born settled\n recompute({\n anchor: { pon: pages[0].pageObjectNumber, glyph: 0 },\n focus: {\n pon: pages[pages.length - 1].pageObjectNumber,\n glyph: Number.MAX_SAFE_INTEGER, // settles to the real last character on load\n },\n });\n }\n\n /** Per-page text snapshot, cached as a PROMISE so concurrent readers\n * share one fetch. Rejections are evicted (a denied or failed read must\n * not poison the cache for a later authorized call). */\n function pageTextSnapshot(pon: PageObjectNumber): Promise<PageTextSnapshot> {\n let p = textSnapshots.get(pon);\n if (!p) {\n const doc = ctx.doc;\n if (!doc) return Promise.reject(new Error('readText: document is not open'));\n p = Promise.resolve(doc.page(pon).text.read());\n p.catch(() => textSnapshots.delete(pon));\n textSnapshots.set(pon, p);\n }\n return p;\n }\n\n async function readText(): Promise<string> {\n const sel = ctx.getState().selection;\n if (!sel) return '';\n if (!canCopy()) throw new PermissionDenied('doc.text.copy', 'selection.readText');\n const { start, end } = orderedEnds(sel);\n const si = pageIndexOf(start.pon);\n const ei = pageIndexOf(end.pon);\n if (si < 0 || ei < 0) return '';\n // Per-page half-open character spans. Geometry is NOT needed here —\n // boundary offsets come from the range, interior pages span their whole\n // charCount (known from the text snapshot itself).\n const spans: Array<{ pon: PageObjectNumber; from: number; to: number }> = [];\n for (let i = si; i <= ei; i++) {\n const pon = ponAtIndex(i);\n if (pon == null) continue;\n spans.push({\n pon,\n from: i === si ? start.glyph : 0,\n to: i === ei ? end.glyph + 1 : Number.MAX_SAFE_INTEGER, // slice clamps to charCount\n });\n }\n const parts: string[] = new Array(spans.length);\n for (let base = 0; base < spans.length; base += TEXT_READ_CONCURRENCY) {\n const batch = spans.slice(base, base + TEXT_READ_CONCURRENCY);\n const snapshots = await Promise.all(batch.map((s) => pageTextSnapshot(s.pon)));\n snapshots.forEach((snap, j) => {\n parts[base + j] = sliceTextByChars(snap, batch[j].from, batch[j].to);\n });\n }\n return parts.join('\\n');\n }\n\n /** Structural registry change (rotate/move/delete/insert — `revision`\n * bumped): drop caches for pages that left, then recompute. Rotation\n * re-derives transforms via the layout key; a deleted endpoint page\n * clears via recompute's registry check. Raw snapshots are NEVER\n * refetched here — they are rotation-independent. */\n function onPagesUpdated(): void {\n const alive = new Set((ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber));\n for (const pon of [...rawGeometry.keys()]) {\n if (!alive.has(pon)) {\n rawGeometry.delete(pon);\n derived.delete(pon);\n textSnapshots.delete(pon);\n }\n }\n if (ctx.getState().selection) recompute();\n }\n\n /** Page CONTENT changed (redaction apply / flatten): every cached\n * snapshot — geometry AND text — is stale, and any live range points\n * into a character space that no longer exists. Drop everything, clear. */\n function invalidateContent(): void {\n epoch++;\n rawGeometry.clear();\n derived.clear();\n pending.clear();\n textSnapshots.clear();\n if (ctx.getState().selection) clearSelection();\n }\n\n // ── lifecycle wiring (registry + content invalidation) ──\n let lastRevision = ctx.document()?.revision ?? -1;\n const offStore = ctx.subscribe(() => {\n const revision = ctx.document()?.revision;\n if (revision === undefined || revision === lastRevision) return;\n lastRevision = revision;\n onPagesUpdated();\n });\n ctx.cleanup(offStore);\n const offEvents = ctx.doc?.events.subscribe((event) => {\n if (event.type === 'redaction.applied' || event.type === 'pages.flattened') {\n invalidateContent();\n }\n });\n if (offEvents) ctx.cleanup(offEvents);\n\n return {\n // ── public lens ──\n canSelect,\n canCopy,\n select,\n selectAll,\n clear: clearSelection,\n snapshot,\n menuAnchor,\n isSelecting: () => ctx.getState().selecting,\n hasSelection: () => ctx.getState().selection != null,\n selectedPages: () => {\n const { segments } = ctx.getState();\n return Object.keys(segments)\n .map(Number)\n .filter((pon) => (segments[pon]?.length ?? 0) > 0) as PageObjectNumber[];\n },\n segmentsForPage: (pon) => ctx.getState().segments[pon] ?? EMPTY_SEGMENTS,\n rectsForPage: (pon) =>\n (ctx.getState().segments[pon] ?? EMPTY_SEGMENTS).map((s) => s.rect),\n readText,\n onChange: (cb) => {\n changeCbs.add(cb);\n return () => changeCbs.delete(cb);\n },\n onCommit: (cb) => {\n commitCbs.add(cb);\n return () => commitCbs.delete(cb);\n },\n\n // ── host lens ──\n ensurePage,\n isLoaded: (pon) => !!ctx.getState().loaded[pon],\n isOverText: (pon, point: Point) => {\n const geom = geometryFor(pon);\n return geom ? glyphAt(geom, point) != null : false;\n },\n beginAt: (pon, point: Point) => {\n const geom = geometryFor(pon);\n if (!geom) return false;\n const i = glyphAt(geom, point);\n if (i == null) return false; // not near text — caller deselects, doesn't capture\n setSelecting(true); // the gesture opened\n recompute({ anchor: { pon, glyph: i }, focus: { pon, glyph: i } });\n return true;\n },\n selectWordAt: (pon, point: Point) => selectSpanAt(pon, point, 'word'),\n selectLineAt: (pon, point: Point) => selectSpanAt(pon, point, 'line'),\n extendTo: (pon, point: Point) => {\n const cur = ctx.getState().selection;\n if (!cur) return;\n const geom = geometryFor(pon);\n if (!geom) {\n ensurePage(pon); // dragged onto a not-yet-loaded page — warm it, recompute on load\n return;\n }\n const i = glyphAt(geom, point);\n if (i == null) return; // off-text — keep the last focus\n recompute({ anchor: cur.anchor, focus: { pon, glyph: i } });\n },\n end: () => {\n // The gesture ended (pointer-up): settle FIRST, so commit listeners\n // (menus, clipboard prefetch) observe isSelecting() === false.\n setSelecting(false);\n commitCbs.forEach((cb) => cb());\n },\n setHighlightVisible: (visible) =>\n ctx.dispatch({ type: 'SET_HIGHLIGHT_HIDDEN', hidden: !visible }),\n highlightVisible: () => !ctx.getState().highlightHidden,\n };\n}\n","import type {\n InteractionCapability,\n InteractionHandler,\n PlatformFeedback,\n} from '@embedpdf/plugin-interaction';\nimport type { SelectionHostCapability } from './types';\n\nconst CURSOR_TOKEN = 'selection-text';\nconst DRAG_THRESHOLD_PX = 4; // viewport px the pointer must move before a drag-select begins\n\n/**\n * The text-selection pointer handler. It is live only under tools that enable the\n * `'text-select'` tag (the built-in `pointer` tool does; `pan` does not — so\n * switching to pan disables text selection with zero special-casing here).\n *\n * - single click → deselect; records an anchor but selects NOTHING until\n * the pointer moves past the drag threshold (Chrome/Acrobat feel)\n * - single-click + drag → caret selection from the anchor (extends across pages)\n * - double-click → select the word\n * - triple-click → select the visual line\n * - down off-text → deselect, and DON'T capture (so it can't block)\n * - hover → I-beam only when over text, else the pointer cursor\n */\nexport function createTextSelectHandler(\n selection: SelectionHostCapability,\n interaction: InteractionCapability,\n feedback?: PlatformFeedback,\n): InteractionHandler {\n // Per-gesture drag-threshold state (one active gesture at a time — the hub owner).\n let anchor: { pon: number; point: { x: number; y: number }; vx: number; vy: number } | null =\n null;\n let dragging = false;\n\n return {\n id: 'text-select',\n priority: 60,\n enabledFor: (tool) => tool.enables.has('text-select'),\n onDown: (s) => {\n anchor = null;\n dragging = false;\n if (!s.page) return false; // over a gap — let a lower handler (e.g. scroll) try\n const { pon, point } = s.page;\n const clicks = s.clickCount ?? 1;\n if (clicks >= 3) {\n selection.selectLineAt(pon, point);\n return true;\n }\n if (clicks === 2) {\n const selected = selection.selectWordAt(pon, point);\n // The platform's \"selection changed\" tick — ONLY when a touch\n // long-press actually engaged a word (never on blank space, never for\n // a mouse double-click). The gesture marker is the honest signal; a\n // raw double-click sample never carries it.\n if (selected && s.gesture === 'long-press') feedback?.selection();\n return true;\n }\n // Single click: clear immediately (clicking deselects), then record an anchor\n // ONLY if over text — but begin no selection until the drag threshold is met.\n selection.clear();\n if (!selection.isOverText(pon, point)) return false; // empty space → don't capture\n anchor = { pon, point, vx: s.viewport.x, vy: s.viewport.y };\n return true;\n },\n onMove: (s) => {\n if (!s.page || !anchor) return;\n if (!dragging) {\n if (Math.hypot(s.viewport.x - anchor.vx, s.viewport.y - anchor.vy) < DRAG_THRESHOLD_PX) {\n return; // still a click, not a drag — select nothing yet\n }\n dragging = true;\n selection.beginAt(anchor.pon, anchor.point); // open the selection at the anchor\n }\n selection.extendTo(s.page.pon, s.page.point);\n },\n onUp: () => {\n anchor = null;\n dragging = false;\n selection.end();\n },\n onCancel: () => {\n // Aborted (second finger → pinch, or a system cancel): drop the\n // in-flight selection instead of committing a sliver of it. end() after\n // clear() settles the `selecting` flag without anything to commit.\n anchor = null;\n dragging = false;\n selection.clear();\n selection.end();\n },\n onHover: (s) => {\n if (!s.page) {\n interaction.setCursor(CURSOR_TOKEN, null); // off the page → pointer\n return;\n }\n // Warm geometry on first hover; show the I-beam ONLY when actually over text.\n selection.ensurePage(s.page.pon);\n const overText = selection.isOverText(s.page.pon, s.page.point);\n interaction.setCursor(CURSOR_TOKEN, overText ? 'text' : null, 10);\n },\n };\n}\n","import type { SelectionAction, SelectionState } from './types';\n\nexport const initialSelectionState: SelectionState = {\n selection: null,\n segments: {},\n loaded: {},\n highlightHidden: false,\n selecting: false,\n};\n\nexport const selectionReducer = (\n state: SelectionState,\n action: SelectionAction,\n): SelectionState => {\n switch (action.type) {\n case 'PAGE_LOADED':\n return state.loaded[action.pon]\n ? state\n : { ...state, loaded: { ...state.loaded, [action.pon]: true } };\n case 'SET':\n return { ...state, selection: action.selection, segments: action.segments };\n case 'CLEAR':\n return state.selection === null && Object.keys(state.segments).length === 0\n ? state\n : { ...state, selection: null, segments: {} };\n case 'SET_HIGHLIGHT_HIDDEN':\n return state.highlightHidden === action.hidden\n ? state\n : { ...state, highlightHidden: action.hidden };\n case 'SET_SELECTING':\n return state.selecting === action.selecting\n ? state\n : { ...state, selecting: action.selecting };\n default:\n return state;\n }\n};\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"reducer-dmDFWFVx.cjs","names":["PermissionDenied"],"sources":["../src/types.ts","../src/geometry.ts","../src/capability.ts","../src/handler.ts","../src/reducer.ts"],"sourcesContent":["import { createCapabilityToken, type PageObjectNumber } from '@embedpdf/core';\nimport type { Point, Rect, TextQuad } from '@embedpdf/core-geometry';\nimport type { SelectionSegment } from './geometry';\n\nexport type { SelectionSegment } from './geometry';\n\n// ── the public range vocabulary ─────────────────────────────────────────────\n\n/**\n * A position in a page's CHARACTER space — the space the engine's geometry\n * runs tile (`PageGeometryRun.charStart`) and search hits address\n * (`SearchMatch.charStart`). NOT a string offset into extracted text; the\n * two spaces are joined by the engine's `charMap` (see engine-core\n * `text/charmap.ts`), which `readText()` applies for you.\n */\nexport interface TextPosition {\n pon: PageObjectNumber;\n /** Character index, 0-based. */\n index: number;\n}\n\n/**\n * A half-open range in character space: `start` inclusive, `end` exclusive,\n * in document order (`start` at or before `end`). `end.index` may be 0 to\n * end a range exactly at a page boundary.\n */\nexport interface TextRange {\n start: TextPosition;\n end: TextPosition;\n}\n\n/**\n * What {@link SelectionCapability.select} accepts: a cross-page\n * {@link TextRange}, or a single-page span — the shape of a search hit, so\n * `select({ pon: hit.pageObjectNumber, start: hit.charStart, count: hit.charCount })`\n * needs no conversion.\n */\nexport type SelectionRangeInput =\n | TextRange\n | { pon: PageObjectNumber; start: number; count: number };\n\n// ── gesture-state vocabulary (host/internal) ────────────────────────────────\n\n/** A glyph address: a page + a flat character index within that page. */\nexport interface GlyphPointer {\n pon: PageObjectNumber;\n glyph: number;\n}\n\n/** Anchor = where the drag began, focus = the current end. Inclusive. */\nexport interface SelectionRange {\n anchor: GlyphPointer;\n focus: GlyphPointer;\n}\n\n/**\n * A selection boundary, anchored to the boundary GLYPH's own oriented cell.\n * `advance` is the reading direction of the segment it belongs to (+1 = the\n * frame's +x), so caret consumers place at the trailing edge without\n * re-deriving bidi from geometry. `rect` is the AABB (scroll targets).\n */\nexport interface SelectionEndpoint {\n pon: PageObjectNumber;\n glyphQuad: TextQuad;\n advance: 1 | -1;\n rect: Rect;\n}\n\n/** The selection's floating-UI anchor: a page + the selection's union box on\n * it, in CONTENT space. See {@link SelectionCapability.menuAnchor}. */\nexport interface SelectionMenuAnchor {\n pon: PageObjectNumber;\n bounds: Rect;\n}\n\nexport interface SelectionSnapshot {\n /** Per-page canonical segments — the ONE geometry consumers act on.\n * Boxes are derived views (`segment.rect`, or `rectsForPage()`). */\n pages: Array<{ pon: PageObjectNumber; segments: SelectionSegment[] }>;\n start: SelectionEndpoint | null;\n end: SelectionEndpoint | null;\n direction: 'forward' | 'backward';\n /**\n * The selection as a half-open character range in document order — a\n * valid {@link SelectionCapability.select} input (persist and restore).\n * Null when nothing is selected. Until a boundary page's text geometry\n * has loaded, its index may still exceed the page's character count\n * (`selectAll` settles as geometry arrives); consumers of the range\n * (`select`, `readText`) clamp, so round-tripping is always safe.\n */\n range: TextRange | null;\n}\n\nexport interface SelectionState {\n selection: SelectionRange | null;\n /** Derived per-line segments per page, in CONTENT space (y-down, PDF units). */\n segments: Record<number, SelectionSegment[]>;\n /** Pages whose text geometry has loaded (so the layer re-renders when ready). */\n loaded: Record<number, boolean>;\n /** When a consumer owns the selection visual (e.g. a markup tool draws its own\n * preview), the default highlight rects are suppressed. */\n highlightHidden: boolean;\n /** A selection GESTURE is in flight (drag / double- / triple-click, between\n * pointer-down and pointer-up). A readable FACT, not an event: derived\n * recomputes (page load, rotate, move) never touch it, and programmatic\n * selections are born settled. Consumers apply policy (e.g. a menu hides\n * while true). */\n selecting: boolean;\n}\n\nexport type SelectionAction =\n | { type: 'PAGE_LOADED'; pon: PageObjectNumber }\n | { type: 'SET'; selection: SelectionRange; segments: Record<number, SelectionSegment[]> }\n | { type: 'CLEAR' }\n | { type: 'SET_HIGHLIGHT_HIDDEN'; hidden: boolean }\n | { type: 'SET_SELECTING'; selecting: boolean };\n\n// ── the PUBLIC capability ───────────────────────────────────────────────────\n\n/**\n * The PUBLIC selection API — the documented, stable surface for application\n * code (toolbars, context menus, automation). Resolve it with the token\n * re-exported from the package root (`@embedpdf/plugin-selection`).\n *\n * The permission model: GEOMETRY enables selection (`doc.text.select`),\n * TEXT enables extraction (`doc.text.copy`) — neither implies the other.\n * Everything here except `readText()` works with the select scope alone, so\n * a deployment can allow selecting and highlighting while denying copy.\n *\n * Framework-only plumbing (pointer-gesture intents, geometry warming, the\n * highlight-visibility handshake) lives on {@link SelectionHostCapability},\n * reachable only through `@embedpdf/plugin-selection/internal`. Both are\n * the SAME runtime object — two typed lenses on one token.\n */\nexport interface SelectionCapability {\n // ── authorization (mirrors the engine's own enforcement) ──\n /** Whether this caller may create selections at all (`doc.text.select`).\n * Authorization only — not \"is something selected\" ({@link hasSelection})\n * and not geometry readiness (a host concern). */\n canSelect(): boolean;\n /** Whether this caller may extract literal text (`doc.text.copy`) —\n * gates {@link readText} and any copy UI. */\n canCopy(): boolean;\n\n // ── programmatic selection (character space, half-open) ──\n /**\n * Select a range programmatically — the same path gestures use, so the\n * highlight, `onChange`, and markup bridges all behave identically. An\n * empty range clears. Throws the engine's `PermissionDenied` when\n * `doc.text.select` is not granted (gate UI with {@link canSelect}).\n */\n select(range: SelectionRangeInput): void;\n /** Select every character of the document. Same gating as {@link select}. */\n selectAll(): void;\n /** Clear the selection. Always allowed. */\n clear(): void;\n\n // ── reads ──\n /** Coherent read-model: per-page segments, endpoints, direction, and the\n * half-open {@link TextRange} (a valid {@link select} input). */\n snapshot(): SelectionSnapshot;\n /**\n * Whether a selection GESTURE is in flight (drag, double- or triple-click\n * — between pointer-down and pointer-up). Programmatic {@link select} /\n * {@link selectAll} never set it (a programmatic selection is born\n * settled), and derived recomputes (page geometry loading, rotate, move)\n * never touch it. THE fact selection-scoped UI applies policy to — e.g.\n * `<SelectionMenu>` hides while true and appears at pointer-up.\n */\n isSelecting(): boolean;\n /**\n * Where selection-scoped floating UI should attach: the union box of the\n * selection's segments on its END page (where the gesture finished),\n * falling back to the last page with materialized segments while a\n * boundary page's geometry is still loading. One anchor regardless of\n * cross-page selection — the shape every anchored-overlay consumer\n * positions on. Null when nothing is selected (or nothing has\n * materialized yet).\n */\n menuAnchor(): SelectionMenuAnchor | null;\n hasSelection(): boolean;\n /** The pages the current selection covers (those with at least one\n * segment) — so a cross-page action (e.g. markup creation) can fan out\n * per page. */\n selectedPages(): PageObjectNumber[];\n /** Per-line oriented segments for a page, in content space — build your\n * own highlight layer from these. */\n segmentsForPage(pon: PageObjectNumber): SelectionSegment[];\n /** The segments' AABBs — for consumers that genuinely want boxes (scroll,\n * conservative regions). Never a substitute for the oriented quads in\n * geometry that gets drawn or persisted. */\n rectsForPage(pon: PageObjectNumber): Rect[];\n\n // ── text extraction (requires doc.text.copy) ──\n /**\n * The selected text. Fetches each selected page's text snapshot (cached\n * per page, immutable per content version) and slices it by the\n * selection's character range through the engine's `charMap` — at most\n * one text read per page per content version, amortized across every\n * call. Pages are joined with `\\n`. Resolves `''` when nothing is\n * selected; rejects with `PermissionDenied` when `doc.text.copy` is not\n * granted. Clipboard writes are deliberately NOT here — this package is\n * DOM-free; use `@embedpdf/web`'s clipboard helpers (or your platform's)\n * on top of this.\n */\n readText(): Promise<string>;\n\n // ── signals ──\n /** Fires whenever the selection segments change (drag-extend, programmatic\n * select, recompute) — for live preview. Returns an unsubscriber. */\n onChange(cb: () => void): () => void;\n /** Fires when a selection gesture ends (pointer-up) — the commit point\n * consumers (markup creation, copy prefetch) act on. */\n onCommit(cb: () => void): () => void;\n}\n\n// ── the HOST capability ─────────────────────────────────────────────────────\n\n/**\n * The HOST (framework) surface: what render layers, the interaction hub,\n * and sibling plugins need on top of the public lens. Import the token from\n * `@embedpdf/plugin-selection/internal`, never from application code. All\n * point-based functions are gesture vocabulary (`…At` = point-addressed);\n * the programmatic equivalent is the public range-based {@link\n * SelectionCapability.select}. Every function here is INERT (no requests,\n * no state) when `canSelect()` is false — the pointer handler needs no\n * permission special-casing.\n */\nexport interface SelectionHostCapability extends SelectionCapability {\n /** Warm a page's text geometry (idempotent; no-op without\n * `doc.text.select`). Layers call this when a page mounts. */\n ensurePage(pon: PageObjectNumber): void;\n isLoaded(pon: PageObjectNumber): boolean;\n /** Is a content-space point on (or near) text? Drives the I-beam cursor. */\n isOverText(pon: PageObjectNumber, point: Point): boolean;\n /** Begin a caret selection at a page point. Returns false if not near any\n * text — the caller deselects instead of capturing. */\n beginAt(pon: PageObjectNumber, point: Point): boolean;\n /** Double-click / touch long-press: select the word around the point.\n * Returns false when the point has no selectable text (geometry not\n * loaded, or no glyph there) — nothing was selected. */\n selectWordAt(pon: PageObjectNumber, point: Point): boolean;\n /** Triple-click: select the whole visual line around the point. Same\n * success contract as {@link selectWordAt}. */\n selectLineAt(pon: PageObjectNumber, point: Point): boolean;\n /** Extend the current selection to a page point (drag). */\n extendTo(pon: PageObjectNumber, point: Point): void;\n /** The gesture ended (pointer-up) → notify `onCommit` consumers. */\n end(): void;\n /** Suppress / restore the default highlight visual (a consumer drawing its\n * own preview — the markup ghost). */\n setHighlightVisible(visible: boolean): void;\n highlightVisible(): boolean;\n}\n\n/**\n * The selection capability token. Typed to the full\n * {@link SelectionHostCapability} for this module and `/internal`; the\n * package root re-exports the SAME token narrowed to\n * {@link SelectionCapability}.\n */\nexport const SelectionToken = createCapabilityToken<SelectionHostCapability>('selection');\n","/**\n * The selection plugin's COORDINATE SEAM — and nothing else.\n *\n * Segmentation, hit-testing, and word/line expansion live in the canonical\n * engine-core text layout (`@embedpdf/engine-core` `text/layout`): a text\n * range has exactly one segmentation whether it came from a drag or a\n * search. This module only converts between the viewer's content space\n * (y-down, crop-relative) and the engine's PDF space (y-up) — pointer\n * points inward, canonical segments outward. The PDF↔content y-flip is the\n * geometry package's `pageGeometry` (the one place that math lives), never\n * hand-rolled here.\n */\nimport {\n applyPoint,\n invert,\n pageGeometry,\n textQuadBounds,\n type Mat2D,\n type Point,\n type PointIn,\n type Rect,\n type TextQuad,\n} from '@embedpdf/core-geometry';\nimport {\n buildPageTextLayout,\n type PageGeometrySnapshot,\n type PageTextLayout,\n type PdfPoint,\n type PdfQuad,\n type PdfRect,\n type PdfTextSegment,\n} from '@embedpdf/engine-core/runtime';\n\n/**\n * One merged visual line of a selection, in content space — the structural\n * twin of the engine's `PdfTextSegment`. `quad` carries frame-geometric\n * corner semantics (upper = ascent side, start = the frame's −x); `advance`\n * is the READING direction along the baseline (+1 = toward `end`), derived\n * from the glyph sequence — geometry and bidi stay separate concerns.\n * `rect` is the content-space AABB.\n */\nexport interface SelectionSegment {\n quad: TextQuad;\n rect: Rect;\n advance: 1 | -1;\n}\n\n/** A page's cached selection geometry: the canonical layout + the seam. */\nexport interface SelectionPageGeometry {\n layout: PageTextLayout;\n toContent: Mat2D<'pdf', 'content'>;\n fromContent: Mat2D<'content', 'pdf'>;\n}\n\n/**\n * Build a page's selection geometry. Page display rotation does NOT enter\n * here — the overlay rides the page's CSS rotation, so content space stays\n * un-rotated (the crop-relative y-flip is the only conversion; zoom = 1,\n * the viewer's zoom is applied later by `PageTransform.toPixels`).\n */\nexport function buildSelectionPageGeometry(\n snapshot: PageGeometrySnapshot,\n crop: PdfRect,\n rotation: 0 | 90 | 180 | 270,\n userUnit: number,\n): SelectionPageGeometry {\n const { pdfToContent } = pageGeometry({ crop, rotation, userUnit }, 1);\n return {\n layout: buildPageTextLayout(snapshot),\n toContent: pdfToContent,\n fromContent: invert(pdfToContent),\n };\n}\n\n/** A content-space pointer point in the layout's PDF space. */\nexport function contentPointToPdf(geom: SelectionPageGeometry, p: Point): PdfPoint {\n return applyPoint(geom.fromContent, p as PointIn<'content'>);\n}\n\n/**\n * A canonical PDF quad in content space. Engine quads carry frame-geometric\n * slot semantics (`p1..p4` = upper-start, upper-end, lower-start,\n * lower-end) — a trusted producer, so the y-flip maps corners straight onto\n * their names; imported-annotation normalization does NOT apply here.\n */\nexport function toContentTextQuad(geom: SelectionPageGeometry, quad: PdfQuad): TextQuad {\n const pt = (p: PdfPoint): Point => applyPoint(geom.toContent, p as PointIn<'pdf'>);\n return {\n upperStart: pt(quad.p1),\n upperEnd: pt(quad.p2),\n lowerStart: pt(quad.p3),\n lowerEnd: pt(quad.p4),\n };\n}\n\n/** A canonical segment in content space (`rect` recomputed from the mapped\n * quad so the pair can never drift). */\nexport function toContentSegment(\n geom: SelectionPageGeometry,\n segment: PdfTextSegment,\n): SelectionSegment {\n const quad = toContentTextQuad(geom, segment.quad);\n return { quad, rect: textQuadBounds(quad), advance: segment.advance };\n}\n","import type { PageObjectNumber, PluginContext } from '@embedpdf/core';\nimport { textQuadBounds, type Point, type Rect } from '@embedpdf/core-geometry';\nimport {\n expandTextRangeToLine,\n expandTextRangeToWord,\n PermissionDenied,\n sliceTextByChars,\n textGlyphAt,\n textGlyphQuad,\n textSegmentsForRange,\n type PageGeometrySnapshot,\n type PageTextSnapshot,\n} from '@embedpdf/engine-core/runtime';\nimport {\n buildSelectionPageGeometry,\n contentPointToPdf,\n toContentSegment,\n toContentTextQuad,\n type SelectionPageGeometry,\n type SelectionSegment,\n} from './geometry';\nimport type {\n GlyphPointer,\n SelectionAction,\n SelectionEndpoint,\n SelectionHostCapability,\n SelectionMenuAnchor,\n SelectionRange,\n SelectionRangeInput,\n SelectionSnapshot,\n SelectionState,\n TextRange,\n} from './types';\n\nconst EMPTY_SEGMENTS: SelectionSegment[] = [];\n\n/** How many page-text reads `readText()` keeps in flight at once. */\nconst TEXT_READ_CONCURRENCY = 8;\n\n/**\n * The selection capability — ONE closure returning the host lens (the\n * public lens is the same object, narrowed at the package root).\n *\n * State model: the reducer holds the selection range and the derived\n * content-space segments; the large, non-serializable per-page caches live\n * HERE in the closure, split by what invalidates them:\n *\n * - `rawGeometry` — PDF-space geometry snapshots, rotation-independent\n * (pages load normalized; see `PageRotateResult`). Dropped only when a\n * page leaves the registry or page CONTENT changes (redaction apply /\n * flatten).\n * - `derived` — the content-space transform + layout, keyed by the\n * layout params (crop/rotation/userUnit) so a view-rotate re-derives\n * from the cached raw snapshot with NO refetch.\n * - `textSnapshots` — per-page text (for `readText`), same content-only\n * invalidation as `rawGeometry`.\n *\n * Permission model: `doc.text.select` gates geometry (selection exists),\n * `doc.text.copy` gates text (extraction) — neither implies the other.\n * Host gesture functions are INERT when select is denied (`ensurePage`\n * no-ops, so nothing warms and nothing hit-tests: the pointer handler\n * needs no special-casing); the public `select()` THROWS `PermissionDenied`\n * so programmatic misuse is loud; `clear()` is always allowed. The engine\n * independently enforces both scopes — these gates exist so the plugin\n * never issues a request that is guaranteed to fail.\n *\n * Selection is cross-page: glyphs are ordered globally by (pageIndex,\n * glyph), so a drag from page 2 into page 4 selects the tail of 2, all of\n * 3, and the head of 4. `recompute` rebuilds the merged line segments for\n * every loaded page in the span, re-runs whenever a mid-span page finishes\n * loading, and CLAMPS stored endpoints once their page's geometry is known\n * (so `selectAll`'s open-ended focus settles to the real last character).\n */\nexport function createSelectionCapability(\n ctx: PluginContext<SelectionState, SelectionAction>,\n): SelectionHostCapability {\n const rawGeometry = new Map<PageObjectNumber, PageGeometrySnapshot>();\n const derived = new Map<PageObjectNumber, { key: string; geom: SelectionPageGeometry }>();\n const pending = new Set<PageObjectNumber>();\n const textSnapshots = new Map<PageObjectNumber, Promise<PageTextSnapshot>>();\n /** Bumped on content invalidation; in-flight reads from before are discarded. */\n let epoch = 0;\n // Consumers (e.g. text-markup) observe the selection without selection knowing\n // about them: `change` fires whenever the segments change, `commit` when a\n // gesture ends (pointer-up). One typed callback each — not an event bus.\n const changeCbs = new Set<() => void>();\n const commitCbs = new Set<() => void>();\n const fireChange = (): void => changeCbs.forEach((cb) => cb());\n\n const canSelect = (): boolean => ctx.doc?.security.allows('doc.text.select') ?? false;\n const canCopy = (): boolean => ctx.doc?.security.allows('doc.text.copy') ?? false;\n\n const layoutOf = (pon: PageObjectNumber) =>\n ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);\n const pageIndexOf = (pon: PageObjectNumber): number =>\n ctx.document()?.pages.findIndex((p) => p.pageObjectNumber === pon) ?? -1;\n const ponAtIndex = (i: number): PageObjectNumber | undefined =>\n ctx.document()?.pages[i]?.pageObjectNumber;\n\n /**\n * The content-space geometry for a page, derived on demand from the raw\n * snapshot + the CURRENT layout params. A view-rotate (or crop change)\n * changes the key and re-derives — raw caches and the engine are never\n * touched (rotation is presentation metadata).\n */\n function geometryFor(pon: PageObjectNumber): SelectionPageGeometry | null {\n const raw = rawGeometry.get(pon);\n const layout = layoutOf(pon);\n if (!raw || !layout) return null;\n const crop = layout.boxes.crop;\n const key = `${layout.rotation}|${layout.userUnit}|${crop.left},${crop.bottom},${crop.right},${crop.top}`;\n const hit = derived.get(pon);\n if (hit && hit.key === key) return hit.geom;\n const geom = buildSelectionPageGeometry(raw, crop, layout.rotation, layout.userUnit);\n derived.set(pon, { key, geom });\n return geom;\n }\n\n const glyphAt = (geom: SelectionPageGeometry, point: Point): number | null =>\n textGlyphAt(geom.layout, contentPointToPdf(geom, point));\n\n // Order the two ends of a selection by document position (page, then glyph).\n function orderedEnds(sel: SelectionRange): {\n start: GlyphPointer;\n end: GlyphPointer;\n direction: 'forward' | 'backward';\n } {\n const ai = pageIndexOf(sel.anchor.pon);\n const fi = pageIndexOf(sel.focus.pon);\n const anchorFirst = ai < fi || (ai === fi && sel.anchor.glyph <= sel.focus.glyph);\n return anchorFirst\n ? { start: sel.anchor, end: sel.focus, direction: 'forward' }\n : { start: sel.focus, end: sel.anchor, direction: 'backward' };\n }\n\n function endpointFor(ptr: GlyphPointer, which: 'start' | 'end'): SelectionEndpoint | null {\n const segments = ctx.getState().segments[ptr.pon] ?? EMPTY_SEGMENTS;\n if (!segments.length) return null;\n const segment = which === 'start' ? segments[0] : segments[segments.length - 1];\n\n // Anchor the endpoint to the boundary GLYPH's own oriented cell so caret\n // placement lands on the exact character edge; fall back to the segment\n // when the glyph is degenerate (e.g. a generated space).\n const geom = geometryFor(ptr.pon);\n const cell = geom ? textGlyphQuad(geom.layout, ptr.glyph) : null;\n if (geom && cell) {\n const glyphQuad = toContentTextQuad(geom, cell);\n return {\n pon: ptr.pon,\n glyphQuad,\n advance: segment.advance,\n rect: textQuadBounds(glyphQuad),\n };\n }\n return { pon: ptr.pon, glyphQuad: segment.quad, advance: segment.advance, rect: segment.rect };\n }\n\n /** Union box of a page's segments, or null when none have materialized. */\n function pageBounds(pon: PageObjectNumber): Rect | null {\n const segs = ctx.getState().segments[pon];\n if (!segs || segs.length === 0) return null;\n let x1 = Infinity;\n let y1 = Infinity;\n let x2 = -Infinity;\n let y2 = -Infinity;\n for (const s of segs) {\n x1 = Math.min(x1, s.rect.x);\n y1 = Math.min(y1, s.rect.y);\n x2 = Math.max(x2, s.rect.x + s.rect.width);\n y2 = Math.max(y2, s.rect.y + s.rect.height);\n }\n return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };\n }\n\n function menuAnchor(): SelectionMenuAnchor | null {\n const sel = ctx.getState().selection;\n if (!sel) return null;\n const { end } = orderedEnds(sel);\n // Prefer the gesture's end page; while its geometry is still loading,\n // fall back to the LAST page (document order) with materialized segments\n // so the anchor never teleports backwards mid-drag.\n let bounds = pageBounds(end.pon);\n if (bounds) return { pon: end.pon, bounds };\n const pages = ctx.document()?.pages ?? [];\n for (let i = pages.length - 1; i >= 0; i--) {\n const pon = pages[i].pageObjectNumber;\n bounds = pageBounds(pon);\n if (bounds) return { pon, bounds };\n }\n return null;\n }\n\n function snapshot(): SelectionSnapshot {\n const { selection, segments } = ctx.getState();\n const pages = Object.keys(segments)\n .map(Number)\n .filter((pon) => (segments[pon]?.length ?? 0) > 0)\n .map((pon) => ({ pon: pon as PageObjectNumber, segments: segments[pon] }));\n if (!selection) return { pages, start: null, end: null, direction: 'forward', range: null };\n const { start, end, direction } = orderedEnds(selection);\n return {\n pages,\n start: endpointFor(start, 'start'),\n end: endpointFor(end, 'end'),\n direction,\n range: {\n start: { pon: start.pon, index: start.glyph },\n end: { pon: end.pon, index: end.glyph + 1 },\n },\n };\n }\n\n function ensurePage(pon: PageObjectNumber): void {\n if (rawGeometry.has(pon) || pending.has(pon)) return;\n // Authorized-only warming: without doc.text.select the read is\n // guaranteed to be rejected — don't issue it. The engine guard stays\n // the security boundary either way.\n if (!canSelect()) return;\n const doc = ctx.doc;\n if (!doc || !layoutOf(pon)) return;\n pending.add(pon);\n const at = epoch;\n doc\n .page(pon)\n .geometry.read()\n .then(\n (snapshot) => {\n pending.delete(pon);\n if (at !== epoch) return; // content changed while in flight — stale\n rawGeometry.set(pon, snapshot);\n ctx.dispatch({ type: 'PAGE_LOADED', pon });\n if (ctx.getState().selection) recompute(); // a mid-span page arrived → fill its segments\n },\n () => {\n pending.delete(pon); // doc closed / read aborted / denied — ignore\n },\n );\n }\n\n /** Clamp a pointer into its page's real character range once geometry is\n * known — how `selectAll`'s open-ended focus settles. */\n function clampPointer(ptr: GlyphPointer): GlyphPointer {\n const geom = geometryFor(ptr.pon);\n if (!geom) return ptr;\n const max = Math.max(geom.layout.glyphs.length - 1, 0);\n const glyph = Math.max(0, Math.min(ptr.glyph, max));\n return glyph === ptr.glyph ? ptr : { pon: ptr.pon, glyph };\n }\n\n // Rebuild merged line segments for every loaded page in the span; warm the\n // rest (they recompute on arrival). Clears when an endpoint's page has left\n // the registry — a range across a structural edit is meaningless.\n function recompute(sel: SelectionRange | null = ctx.getState().selection): void {\n if (!sel) return;\n const clamped: SelectionRange = {\n anchor: clampPointer(sel.anchor),\n focus: clampPointer(sel.focus),\n };\n const { start, end } = orderedEnds(clamped);\n const si = pageIndexOf(start.pon);\n const ei = pageIndexOf(end.pon);\n if (si < 0 || ei < 0) {\n clearSelection();\n return;\n }\n const segments: Record<number, SelectionSegment[]> = {};\n for (let i = si; i <= ei; i++) {\n const pon = ponAtIndex(i);\n if (pon == null) continue;\n const geom = geometryFor(pon);\n if (!geom) {\n ensurePage(pon); // not loaded yet — it'll recompute when ready\n continue;\n }\n const from = i === si ? start.glyph : 0;\n const to = i === ei ? end.glyph : geom.layout.glyphs.length - 1;\n segments[pon] = textSegmentsForRange(geom.layout, from, to - from + 1).map((s) =>\n toContentSegment(geom, s),\n );\n }\n ctx.dispatch({ type: 'SET', selection: clamped, segments });\n fireChange();\n }\n\n /** Flip the gesture-in-flight FACT (see `SelectionState.selecting`). Set\n * BEFORE the recompute that fires `onChange`, so change listeners always\n * observe a coherent (selecting, segments) pair. */\n function setSelecting(selecting: boolean): void {\n ctx.dispatch({ type: 'SET_SELECTING', selecting });\n }\n\n function clearSelection(): void {\n setSelecting(false);\n ctx.dispatch({ type: 'CLEAR' });\n fireChange();\n }\n\n // Set the selection to a flat [from,to] glyph span on one page (word/line).\n // These are GESTURE vocabulary (double-/triple-click) — the commit arrives\n // at the pointer-up that follows, so the span counts as in-flight until then.\n // Returns whether a span actually engaged (geometry present AND a glyph\n // under the point) — the fact haptics and other success-gated feedback key\n // on, so nothing ever buzzes over blank space.\n function selectSpanAt(pon: PageObjectNumber, point: Point, expand: 'word' | 'line'): boolean {\n const geom = geometryFor(pon);\n if (!geom) return false;\n const i = glyphAt(geom, point);\n if (i == null) return false;\n const [from, to] =\n expand === 'word'\n ? expandTextRangeToWord(geom.layout, i)\n : expandTextRangeToLine(geom.layout, i);\n setSelecting(true);\n recompute({ anchor: { pon, glyph: from }, focus: { pon, glyph: to } });\n return true;\n }\n\n function assertCanSelect(context: string): void {\n if (!canSelect()) throw new PermissionDenied('doc.text.select', context);\n }\n\n /** The public programmatic entry: a half-open character range in. */\n function select(input: SelectionRangeInput): void {\n assertCanSelect('selection.select');\n const range: TextRange =\n 'pon' in input\n ? {\n start: { pon: input.pon, index: input.start },\n end: { pon: input.pon, index: input.start + input.count },\n }\n : input;\n const si = pageIndexOf(range.start.pon);\n let ei = pageIndexOf(range.end.pon);\n if (si < 0 || ei < 0) {\n throw new Error(\n `select: unknown page object ${si < 0 ? range.start.pon : range.end.pon}`,\n );\n }\n let endIndex = range.end.index;\n if (ei < si || (ei === si && endIndex <= range.start.index)) {\n clearSelection(); // empty range = no selection\n return;\n }\n if (endIndex === 0) {\n // Half-open end exactly at a page boundary: the last included\n // character is the previous page's last one. Its index settles via\n // clamping once that page's geometry loads.\n ei -= 1;\n if (ei < si) {\n clearSelection();\n return;\n }\n endIndex = Number.MAX_SAFE_INTEGER;\n }\n const focusPon = ponAtIndex(ei);\n if (focusPon == null) throw new Error(`select: unknown page at index ${ei}`);\n setSelecting(false); // programmatic selections are born settled\n recompute({\n anchor: { pon: range.start.pon, glyph: Math.max(0, range.start.index) },\n focus: { pon: focusPon, glyph: endIndex - 1 },\n });\n }\n\n function selectAll(): void {\n assertCanSelect('selection.selectAll');\n const pages = ctx.document()?.pages ?? [];\n if (pages.length === 0) return;\n setSelecting(false); // programmatic selections are born settled\n recompute({\n anchor: { pon: pages[0].pageObjectNumber, glyph: 0 },\n focus: {\n pon: pages[pages.length - 1].pageObjectNumber,\n glyph: Number.MAX_SAFE_INTEGER, // settles to the real last character on load\n },\n });\n }\n\n /** Per-page text snapshot, cached as a PROMISE so concurrent readers\n * share one fetch. Rejections are evicted (a denied or failed read must\n * not poison the cache for a later authorized call). */\n function pageTextSnapshot(pon: PageObjectNumber): Promise<PageTextSnapshot> {\n let p = textSnapshots.get(pon);\n if (!p) {\n const doc = ctx.doc;\n if (!doc) return Promise.reject(new Error('readText: document is not open'));\n p = Promise.resolve(doc.page(pon).text.read());\n p.catch(() => textSnapshots.delete(pon));\n textSnapshots.set(pon, p);\n }\n return p;\n }\n\n async function readText(): Promise<string> {\n const sel = ctx.getState().selection;\n if (!sel) return '';\n if (!canCopy()) throw new PermissionDenied('doc.text.copy', 'selection.readText');\n const { start, end } = orderedEnds(sel);\n const si = pageIndexOf(start.pon);\n const ei = pageIndexOf(end.pon);\n if (si < 0 || ei < 0) return '';\n // Per-page half-open character spans. Geometry is NOT needed here —\n // boundary offsets come from the range, interior pages span their whole\n // charCount (known from the text snapshot itself).\n const spans: Array<{ pon: PageObjectNumber; from: number; to: number }> = [];\n for (let i = si; i <= ei; i++) {\n const pon = ponAtIndex(i);\n if (pon == null) continue;\n spans.push({\n pon,\n from: i === si ? start.glyph : 0,\n to: i === ei ? end.glyph + 1 : Number.MAX_SAFE_INTEGER, // slice clamps to charCount\n });\n }\n const parts: string[] = new Array(spans.length);\n for (let base = 0; base < spans.length; base += TEXT_READ_CONCURRENCY) {\n const batch = spans.slice(base, base + TEXT_READ_CONCURRENCY);\n const snapshots = await Promise.all(batch.map((s) => pageTextSnapshot(s.pon)));\n snapshots.forEach((snap, j) => {\n parts[base + j] = sliceTextByChars(snap, batch[j].from, batch[j].to);\n });\n }\n return parts.join('\\n');\n }\n\n /** Structural registry change (rotate/move/delete/insert — `revision`\n * bumped): drop caches for pages that left, then recompute. Rotation\n * re-derives transforms via the layout key; a deleted endpoint page\n * clears via recompute's registry check. Raw snapshots are NEVER\n * refetched here — they are rotation-independent. */\n function onPagesUpdated(): void {\n const alive = new Set((ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber));\n for (const pon of [...rawGeometry.keys()]) {\n if (!alive.has(pon)) {\n rawGeometry.delete(pon);\n derived.delete(pon);\n textSnapshots.delete(pon);\n }\n }\n if (ctx.getState().selection) recompute();\n }\n\n /** Page CONTENT changed (redaction apply / flatten): every cached\n * snapshot — geometry AND text — is stale, and any live range points\n * into a character space that no longer exists. Drop everything, clear. */\n function invalidateContent(): void {\n epoch++;\n rawGeometry.clear();\n derived.clear();\n pending.clear();\n textSnapshots.clear();\n if (ctx.getState().selection) clearSelection();\n }\n\n // ── lifecycle wiring (registry + content invalidation) ──\n let lastRevision = ctx.document()?.revision ?? -1;\n const offStore = ctx.subscribe(() => {\n const revision = ctx.document()?.revision;\n if (revision === undefined || revision === lastRevision) return;\n lastRevision = revision;\n onPagesUpdated();\n });\n ctx.cleanup(offStore);\n const offEvents = ctx.doc?.events.subscribe((event) => {\n if (event.type === 'redaction.applied' || event.type === 'pages.flattened') {\n invalidateContent();\n }\n });\n if (offEvents) ctx.cleanup(offEvents);\n\n return {\n // ── public lens ──\n canSelect,\n canCopy,\n select,\n selectAll,\n clear: clearSelection,\n snapshot,\n menuAnchor,\n isSelecting: () => ctx.getState().selecting,\n hasSelection: () => ctx.getState().selection != null,\n selectedPages: () => {\n const { segments } = ctx.getState();\n return Object.keys(segments)\n .map(Number)\n .filter((pon) => (segments[pon]?.length ?? 0) > 0) as PageObjectNumber[];\n },\n segmentsForPage: (pon) => ctx.getState().segments[pon] ?? EMPTY_SEGMENTS,\n rectsForPage: (pon) =>\n (ctx.getState().segments[pon] ?? EMPTY_SEGMENTS).map((s) => s.rect),\n readText,\n onChange: (cb) => {\n changeCbs.add(cb);\n return () => changeCbs.delete(cb);\n },\n onCommit: (cb) => {\n commitCbs.add(cb);\n return () => commitCbs.delete(cb);\n },\n\n // ── host lens ──\n ensurePage,\n isLoaded: (pon) => !!ctx.getState().loaded[pon],\n isOverText: (pon, point: Point) => {\n const geom = geometryFor(pon);\n return geom ? glyphAt(geom, point) != null : false;\n },\n beginAt: (pon, point: Point) => {\n const geom = geometryFor(pon);\n if (!geom) return false;\n const i = glyphAt(geom, point);\n if (i == null) return false; // not near text — caller deselects, doesn't capture\n setSelecting(true); // the gesture opened\n recompute({ anchor: { pon, glyph: i }, focus: { pon, glyph: i } });\n return true;\n },\n selectWordAt: (pon, point: Point) => selectSpanAt(pon, point, 'word'),\n selectLineAt: (pon, point: Point) => selectSpanAt(pon, point, 'line'),\n extendTo: (pon, point: Point) => {\n const cur = ctx.getState().selection;\n if (!cur) return;\n const geom = geometryFor(pon);\n if (!geom) {\n ensurePage(pon); // dragged onto a not-yet-loaded page — warm it, recompute on load\n return;\n }\n const i = glyphAt(geom, point);\n if (i == null) return; // off-text — keep the last focus\n recompute({ anchor: cur.anchor, focus: { pon, glyph: i } });\n },\n end: () => {\n // The gesture ended (pointer-up): settle FIRST, so commit listeners\n // (menus, clipboard prefetch) observe isSelecting() === false.\n setSelecting(false);\n commitCbs.forEach((cb) => cb());\n },\n setHighlightVisible: (visible) =>\n ctx.dispatch({ type: 'SET_HIGHLIGHT_HIDDEN', hidden: !visible }),\n highlightVisible: () => !ctx.getState().highlightHidden,\n };\n}\n","import type {\n InteractionCapability,\n InteractionHandler,\n PlatformFeedback,\n} from '@embedpdf/plugin-interaction';\nimport type { SelectionHostCapability } from './types';\n\nconst CURSOR_TOKEN = 'selection-text';\nconst DRAG_THRESHOLD_PX = 4; // viewport px the pointer must move before a drag-select begins\n\n/**\n * The text-selection pointer handler. It is live only under tools that enable the\n * `'text-select'` tag (the built-in `pointer` tool does; `pan` does not — so\n * switching to pan disables text selection with zero special-casing here).\n *\n * - single click → deselect; records an anchor but selects NOTHING until\n * the pointer moves past the drag threshold (Chrome/Acrobat feel)\n * - single-click + drag → caret selection from the anchor (extends across pages)\n * - double-click → select the word\n * - triple-click → select the visual line\n * - down off-text → deselect, and DON'T capture (so it can't block)\n * - hover → I-beam only when over text, else the pointer cursor\n */\nexport function createTextSelectHandler(\n selection: SelectionHostCapability,\n interaction: InteractionCapability,\n feedback?: PlatformFeedback,\n): InteractionHandler {\n // Per-gesture drag-threshold state (one active gesture at a time — the hub owner).\n let anchor: { pon: number; point: { x: number; y: number }; vx: number; vy: number } | null =\n null;\n let dragging = false;\n\n return {\n id: 'text-select',\n priority: 60,\n enabledFor: (tool) => tool.enables.has('text-select'),\n onDown: (s) => {\n anchor = null;\n dragging = false;\n if (!s.page) return false; // over a gap — let a lower handler (e.g. scroll) try\n const { pon, point } = s.page;\n const clicks = s.clickCount ?? 1;\n if (clicks >= 3) {\n selection.selectLineAt(pon, point);\n return true;\n }\n if (clicks === 2) {\n const selected = selection.selectWordAt(pon, point);\n // The platform's \"selection changed\" tick — ONLY when a touch\n // long-press actually engaged a word (never on blank space, never for\n // a mouse double-click). The gesture marker is the honest signal; a\n // raw double-click sample never carries it.\n if (selected && s.gesture === 'long-press') feedback?.selection();\n return true;\n }\n // Single click: clear immediately (clicking deselects), then record an anchor\n // ONLY if over text — but begin no selection until the drag threshold is met.\n selection.clear();\n if (!selection.isOverText(pon, point)) return false; // empty space → don't capture\n anchor = { pon, point, vx: s.viewport.x, vy: s.viewport.y };\n return true;\n },\n onMove: (s) => {\n if (!s.page || !anchor) return;\n if (!dragging) {\n if (Math.hypot(s.viewport.x - anchor.vx, s.viewport.y - anchor.vy) < DRAG_THRESHOLD_PX) {\n return; // still a click, not a drag — select nothing yet\n }\n dragging = true;\n selection.beginAt(anchor.pon, anchor.point); // open the selection at the anchor\n }\n selection.extendTo(s.page.pon, s.page.point);\n },\n onUp: () => {\n anchor = null;\n dragging = false;\n selection.end();\n },\n onCancel: () => {\n // Aborted (second finger → pinch, or a system cancel): drop the\n // in-flight selection instead of committing a sliver of it. end() after\n // clear() settles the `selecting` flag without anything to commit.\n anchor = null;\n dragging = false;\n selection.clear();\n selection.end();\n },\n onHover: (s) => {\n if (!s.page) {\n interaction.setCursor(CURSOR_TOKEN, null); // off the page → pointer\n return;\n }\n // Warm geometry on first hover; show the I-beam ONLY when actually over text.\n selection.ensurePage(s.page.pon);\n const overText = selection.isOverText(s.page.pon, s.page.point);\n interaction.setCursor(CURSOR_TOKEN, overText ? 'text' : null, 10);\n },\n };\n}\n","import type { SelectionAction, SelectionState } from './types';\n\nexport const initialSelectionState: SelectionState = {\n selection: null,\n segments: {},\n loaded: {},\n highlightHidden: false,\n selecting: false,\n};\n\nexport const selectionReducer = (\n state: SelectionState,\n action: SelectionAction,\n): SelectionState => {\n switch (action.type) {\n case 'PAGE_LOADED':\n return state.loaded[action.pon]\n ? state\n : { ...state, loaded: { ...state.loaded, [action.pon]: true } };\n case 'SET':\n return { ...state, selection: action.selection, segments: action.segments };\n case 'CLEAR':\n return state.selection === null && Object.keys(state.segments).length === 0\n ? state\n : { ...state, selection: null, segments: {} };\n case 'SET_HIGHLIGHT_HIDDEN':\n return state.highlightHidden === action.hidden\n ? state\n : { ...state, highlightHidden: action.hidden };\n case 'SET_SELECTING':\n return state.selecting === action.selecting\n ? state\n : { ...state, selecting: action.selecting };\n default:\n return state;\n }\n};\n"],"mappings":"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"}