@embedpdf/plugin-selection 3.0.0-next.3 → 3.0.0-next.5

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,286 @@
1
+ import { PageObjectNumber } from "@embedpdf/core";
2
+ import { Mat2D, Point, Rect, TextQuad } from "@embedpdf/core-geometry";
3
+ import { PageGeometrySnapshot, PageTextLayout, PdfPoint, PdfQuad, PdfRect, PdfTextSegment } from "@embedpdf/engine-core/runtime";
4
+ //#region src/geometry.d.ts
5
+ /**
6
+ * One merged visual line of a selection, in content space — the structural
7
+ * twin of the engine's `PdfTextSegment`. `quad` carries frame-geometric
8
+ * corner semantics (upper = ascent side, start = the frame's −x); `advance`
9
+ * is the READING direction along the baseline (+1 = toward `end`), derived
10
+ * from the glyph sequence — geometry and bidi stay separate concerns.
11
+ * `rect` is the content-space AABB.
12
+ */
13
+ interface SelectionSegment {
14
+ quad: TextQuad;
15
+ rect: Rect;
16
+ advance: 1 | -1;
17
+ }
18
+ /** A page's cached selection geometry: the canonical layout + the seam. */
19
+ interface SelectionPageGeometry {
20
+ layout: PageTextLayout;
21
+ toContent: Mat2D<'pdf', 'content'>;
22
+ fromContent: Mat2D<'content', 'pdf'>;
23
+ }
24
+ /**
25
+ * Build a page's selection geometry. Page display rotation does NOT enter
26
+ * here — the overlay rides the page's CSS rotation, so content space stays
27
+ * un-rotated (the crop-relative y-flip is the only conversion; zoom = 1,
28
+ * the viewer's zoom is applied later by `PageTransform.pageToContent`).
29
+ */
30
+ declare function buildSelectionPageGeometry(snapshot: PageGeometrySnapshot, crop: PdfRect, rotation: 0 | 90 | 180 | 270, userUnit: number): SelectionPageGeometry;
31
+ /** A content-space pointer point in the layout's PDF space. */
32
+ declare function contentPointToPdf(geom: SelectionPageGeometry, p: Point): PdfPoint;
33
+ /**
34
+ * A canonical PDF quad in content space. Engine quads carry frame-geometric
35
+ * slot semantics (`p1..p4` = upper-start, upper-end, lower-start,
36
+ * lower-end) — a trusted producer, so the y-flip maps corners straight onto
37
+ * their names; imported-annotation normalization does NOT apply here.
38
+ */
39
+ declare function toContentTextQuad(geom: SelectionPageGeometry, quad: PdfQuad): TextQuad;
40
+ /** A canonical segment in content space (`rect` recomputed from the mapped
41
+ * quad so the pair can never drift). */
42
+ declare function toContentSegment(geom: SelectionPageGeometry, segment: PdfTextSegment): SelectionSegment;
43
+ //#endregion
44
+ //#region src/types.d.ts
45
+ /**
46
+ * A position in a page's CHARACTER space — the space the engine's geometry
47
+ * runs tile (`PageGeometryRun.charStart`) and search hits address
48
+ * (`SearchMatch.charStart`). NOT a string offset into extracted text; the
49
+ * two spaces are joined by the engine's `charMap` (see engine-core
50
+ * `text/charmap.ts`), which `readText()` applies for you.
51
+ */
52
+ interface TextPosition {
53
+ pon: PageObjectNumber;
54
+ /** Character index, 0-based. */
55
+ index: number;
56
+ }
57
+ /**
58
+ * A half-open range in character space: `start` inclusive, `end` exclusive,
59
+ * in document order (`start` at or before `end`). `end.index` may be 0 to
60
+ * end a range exactly at a page boundary.
61
+ */
62
+ interface TextRange {
63
+ start: TextPosition;
64
+ end: TextPosition;
65
+ }
66
+ /**
67
+ * What {@link SelectionCapability.select} accepts: a cross-page
68
+ * {@link TextRange}, or a single-page span — the shape of a search hit, so
69
+ * `select({ pon: hit.pageObjectNumber, start: hit.charStart, count: hit.charCount })`
70
+ * needs no conversion.
71
+ */
72
+ type SelectionRangeInput = TextRange | {
73
+ pon: PageObjectNumber;
74
+ start: number;
75
+ count: number;
76
+ };
77
+ /** A glyph address: a page + a flat character index within that page. */
78
+ interface GlyphPointer {
79
+ pon: PageObjectNumber;
80
+ glyph: number;
81
+ }
82
+ /** Anchor = where the drag began, focus = the current end. Inclusive. */
83
+ interface SelectionRange {
84
+ anchor: GlyphPointer;
85
+ focus: GlyphPointer;
86
+ }
87
+ /**
88
+ * A selection boundary, anchored to the boundary GLYPH's own oriented cell.
89
+ * `advance` is the reading direction of the segment it belongs to (+1 = the
90
+ * frame's +x), so caret consumers place at the trailing edge without
91
+ * re-deriving bidi from geometry. `rect` is the AABB (scroll targets).
92
+ */
93
+ interface SelectionEndpoint {
94
+ pon: PageObjectNumber;
95
+ glyphQuad: TextQuad;
96
+ advance: 1 | -1;
97
+ rect: Rect;
98
+ }
99
+ /** The selection's floating-UI anchor: a page + the selection's union box on
100
+ * it, in CONTENT space. See {@link SelectionCapability.menuAnchor}. */
101
+ interface SelectionMenuAnchor {
102
+ pon: PageObjectNumber;
103
+ bounds: Rect;
104
+ }
105
+ interface SelectionSnapshot {
106
+ /** Per-page canonical segments — the ONE geometry consumers act on.
107
+ * Boxes are derived views (`segment.rect`, or `rectsForPage()`). */
108
+ pages: Array<{
109
+ pon: PageObjectNumber;
110
+ segments: SelectionSegment[];
111
+ }>;
112
+ start: SelectionEndpoint | null;
113
+ end: SelectionEndpoint | null;
114
+ direction: 'forward' | 'backward';
115
+ /**
116
+ * The selection as a half-open character range in document order — a
117
+ * valid {@link SelectionCapability.select} input (persist and restore).
118
+ * Null when nothing is selected. Until a boundary page's text geometry
119
+ * has loaded, its index may still exceed the page's character count
120
+ * (`selectAll` settles as geometry arrives); consumers of the range
121
+ * (`select`, `readText`) clamp, so round-tripping is always safe.
122
+ */
123
+ range: TextRange | null;
124
+ }
125
+ interface SelectionState {
126
+ selection: SelectionRange | null;
127
+ /** Derived per-line segments per page, in CONTENT space (y-down, PDF units). */
128
+ segments: Record<number, SelectionSegment[]>;
129
+ /** Pages whose text geometry has loaded (so the layer re-renders when ready). */
130
+ loaded: Record<number, boolean>;
131
+ /** When a consumer owns the selection visual (e.g. a markup tool draws its own
132
+ * preview), the default highlight rects are suppressed. */
133
+ highlightHidden: boolean;
134
+ /** A selection GESTURE is in flight (drag / double- / triple-click, between
135
+ * pointer-down and pointer-up). A readable FACT, not an event: derived
136
+ * recomputes (page load, rotate, move) never touch it, and programmatic
137
+ * selections are born settled. Consumers apply policy (e.g. a menu hides
138
+ * while true). */
139
+ selecting: boolean;
140
+ }
141
+ type SelectionAction = {
142
+ type: 'PAGE_LOADED';
143
+ pon: PageObjectNumber;
144
+ } | {
145
+ type: 'SET';
146
+ selection: SelectionRange;
147
+ segments: Record<number, SelectionSegment[]>;
148
+ } | {
149
+ type: 'CLEAR';
150
+ } | {
151
+ type: 'SET_HIGHLIGHT_HIDDEN';
152
+ hidden: boolean;
153
+ } | {
154
+ type: 'SET_SELECTING';
155
+ selecting: boolean;
156
+ };
157
+ /**
158
+ * The PUBLIC selection API — the documented, stable surface for application
159
+ * code (toolbars, context menus, automation). Resolve it with the token
160
+ * re-exported from the package root (`@embedpdf/plugin-selection`).
161
+ *
162
+ * The permission model: GEOMETRY enables selection (`doc.text.select`),
163
+ * TEXT enables extraction (`doc.text.copy`) — neither implies the other.
164
+ * Everything here except `readText()` works with the select scope alone, so
165
+ * a deployment can allow selecting and highlighting while denying copy.
166
+ *
167
+ * Framework-only plumbing (pointer-gesture intents, geometry warming, the
168
+ * highlight-visibility handshake) lives on {@link SelectionHostCapability},
169
+ * reachable only through `@embedpdf/plugin-selection/internal`. Both are
170
+ * the SAME runtime object — two typed lenses on one token.
171
+ */
172
+ interface SelectionCapability {
173
+ /** Whether this caller may create selections at all (`doc.text.select`).
174
+ * Authorization only — not "is something selected" ({@link hasSelection})
175
+ * and not geometry readiness (a host concern). */
176
+ canSelect(): boolean;
177
+ /** Whether this caller may extract literal text (`doc.text.copy`) —
178
+ * gates {@link readText} and any copy UI. */
179
+ canCopy(): boolean;
180
+ /**
181
+ * Select a range programmatically — the same path gestures use, so the
182
+ * highlight, `onChange`, and markup bridges all behave identically. An
183
+ * empty range clears. Throws the engine's `PermissionDenied` when
184
+ * `doc.text.select` is not granted (gate UI with {@link canSelect}).
185
+ */
186
+ select(range: SelectionRangeInput): void;
187
+ /** Select every character of the document. Same gating as {@link select}. */
188
+ selectAll(): void;
189
+ /** Clear the selection. Always allowed. */
190
+ clear(): void;
191
+ /** Coherent read-model: per-page segments, endpoints, direction, and the
192
+ * half-open {@link TextRange} (a valid {@link select} input). */
193
+ snapshot(): SelectionSnapshot;
194
+ /**
195
+ * Whether a selection GESTURE is in flight (drag, double- or triple-click
196
+ * — between pointer-down and pointer-up). Programmatic {@link select} /
197
+ * {@link selectAll} never set it (a programmatic selection is born
198
+ * settled), and derived recomputes (page geometry loading, rotate, move)
199
+ * never touch it. THE fact selection-scoped UI applies policy to — e.g.
200
+ * `<SelectionMenu>` hides while true and appears at pointer-up.
201
+ */
202
+ isSelecting(): boolean;
203
+ /**
204
+ * Where selection-scoped floating UI should attach: the union box of the
205
+ * selection's segments on its END page (where the gesture finished),
206
+ * falling back to the last page with materialized segments while a
207
+ * boundary page's geometry is still loading. One anchor regardless of
208
+ * cross-page selection — the shape every anchored-overlay consumer
209
+ * positions on. Null when nothing is selected (or nothing has
210
+ * materialized yet).
211
+ */
212
+ menuAnchor(): SelectionMenuAnchor | null;
213
+ hasSelection(): boolean;
214
+ /** The pages the current selection covers (those with at least one
215
+ * segment) — so a cross-page action (e.g. markup creation) can fan out
216
+ * per page. */
217
+ selectedPages(): PageObjectNumber[];
218
+ /** Per-line oriented segments for a page, in content space — build your
219
+ * own highlight layer from these. */
220
+ segmentsForPage(pon: PageObjectNumber): SelectionSegment[];
221
+ /** The segments' AABBs — for consumers that genuinely want boxes (scroll,
222
+ * conservative regions). Never a substitute for the oriented quads in
223
+ * geometry that gets drawn or persisted. */
224
+ rectsForPage(pon: PageObjectNumber): Rect[];
225
+ /**
226
+ * The selected text. Fetches each selected page's text snapshot (cached
227
+ * per page, immutable per content version) and slices it by the
228
+ * selection's character range through the engine's `charMap` — at most
229
+ * one text read per page per content version, amortized across every
230
+ * call. Pages are joined with `\n`. Resolves `''` when nothing is
231
+ * selected; rejects with `PermissionDenied` when `doc.text.copy` is not
232
+ * granted. Clipboard writes are deliberately NOT here — this package is
233
+ * DOM-free; use `@embedpdf/web`'s clipboard helpers (or your platform's)
234
+ * on top of this.
235
+ */
236
+ readText(): Promise<string>;
237
+ /** Fires whenever the selection segments change (drag-extend, programmatic
238
+ * select, recompute) — for live preview. Returns an unsubscriber. */
239
+ onChange(cb: () => void): () => void;
240
+ /** Fires when a selection gesture ends (pointer-up) — the commit point
241
+ * consumers (markup creation, copy prefetch) act on. */
242
+ onCommit(cb: () => void): () => void;
243
+ }
244
+ /**
245
+ * 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
248
+ * point-based functions are gesture vocabulary (`…At` = point-addressed);
249
+ * the programmatic equivalent is the public range-based {@link
250
+ * SelectionCapability.select}. Every function here is INERT (no requests,
251
+ * no state) when `canSelect()` is false — the pointer handler needs no
252
+ * permission special-casing.
253
+ */
254
+ interface SelectionHostCapability extends SelectionCapability {
255
+ /** Warm a page's text geometry (idempotent; no-op without
256
+ * `doc.text.select`). Layers call this when a page mounts. */
257
+ ensurePage(pon: PageObjectNumber): void;
258
+ isLoaded(pon: PageObjectNumber): boolean;
259
+ /** Is a content-space point on (or near) text? Drives the I-beam cursor. */
260
+ isOverText(pon: PageObjectNumber, point: Point): boolean;
261
+ /** Begin a caret selection at a page point. Returns false if not near any
262
+ * text — the caller deselects instead of capturing. */
263
+ beginAt(pon: PageObjectNumber, point: Point): boolean;
264
+ /** Double-click: select the word around the point. */
265
+ selectWordAt(pon: PageObjectNumber, point: Point): void;
266
+ /** Triple-click: select the whole visual line around the point. */
267
+ selectLineAt(pon: PageObjectNumber, point: Point): void;
268
+ /** Extend the current selection to a page point (drag). */
269
+ extendTo(pon: PageObjectNumber, point: Point): void;
270
+ /** The gesture ended (pointer-up) → notify `onCommit` consumers. */
271
+ end(): void;
272
+ /** Suppress / restore the default highlight visual (a consumer drawing its
273
+ * own preview — the markup ghost). */
274
+ setHighlightVisible(visible: boolean): void;
275
+ highlightVisible(): boolean;
276
+ }
277
+ /**
278
+ * The selection capability token. Typed to the full
279
+ * {@link SelectionHostCapability} for this module and `/internal`; the
280
+ * package root re-exports the SAME token narrowed to
281
+ * {@link SelectionCapability}.
282
+ */
283
+ declare const SelectionToken: import("@embedpdf/core").CapabilityToken<SelectionHostCapability>;
284
+ //#endregion
285
+ 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 };
286
+ //# sourceMappingURL=types-DtWaZIsm.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-selection",
3
- "version": "3.0.0-next.3",
3
+ "version": "3.0.0-next.5",
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,16 +12,20 @@
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
+ "./internal": {
16
+ "import": "./dist/internal.js",
17
+ "require": "./dist/internal.cjs"
18
+ },
15
19
  "./package.json": "./package.json"
16
20
  },
17
21
  "files": [
18
22
  "dist"
19
23
  ],
20
24
  "dependencies": {
21
- "@embedpdf/core": "3.0.0-next.3",
22
- "@embedpdf/core-geometry": "3.0.0-next.3",
23
- "@embedpdf/engine-core": "3.0.0-next.3",
24
- "@embedpdf/plugin-interaction": "3.0.0-next.3"
25
+ "@embedpdf/core": "3.0.0-next.5",
26
+ "@embedpdf/plugin-interaction": "3.0.0-next.5",
27
+ "@embedpdf/engine-core": "3.0.0-next.5",
28
+ "@embedpdf/core-geometry": "3.0.0-next.5"
25
29
  },
26
30
  "devDependencies": {
27
31
  "typescript": "^5.0.0",