@embedpdf/plugin-selection 3.0.0-next.1 → 3.0.0-next.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract/host.cjs +3 -0
- package/dist/contract/host.d.cts +2 -0
- package/dist/contract/host.d.ts +2 -0
- package/dist/contract/host.js +2 -0
- package/dist/contract.cjs +7 -0
- package/dist/contract.cjs.map +1 -0
- package/dist/contract.d.cts +7 -0
- package/dist/contract.d.ts +7 -0
- package/dist/contract.js +7 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.cjs +119 -547
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -149
- package/dist/index.d.ts +71 -149
- package/dist/index.js +116 -540
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +13 -0
- package/dist/internal.d.cts +63 -0
- package/dist/internal.d.ts +63 -0
- package/dist/internal.js +4 -0
- package/dist/reducer-BAOQ2CJ8.cjs +732 -0
- package/dist/reducer-BAOQ2CJ8.cjs.map +1 -0
- package/dist/reducer-DbKtk9Xl.js +685 -0
- package/dist/reducer-DbKtk9Xl.js.map +1 -0
- package/dist/types-BVUcOsCh.js +14 -0
- package/dist/types-BVUcOsCh.js.map +1 -0
- package/dist/types-CUrUVl6K.d.cts +292 -0
- package/dist/types-CUrUVl6K.d.ts +292 -0
- package/dist/types-D9kEBMFj.cjs +18 -0
- package/dist/types-D9kEBMFj.cjs.map +1 -0
- package/package.json +17 -5
|
@@ -0,0 +1,292 @@
|
|
|
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.toPixels`).
|
|
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 through `@embedpdf/plugin-selection/contract/host` (and the
|
|
170
|
+
* framework's `/internal` entry). Both are
|
|
171
|
+
* the SAME runtime object — two typed lenses on one token.
|
|
172
|
+
*/
|
|
173
|
+
interface SelectionCapability {
|
|
174
|
+
/** Whether this caller may create selections at all (`doc.text.select`).
|
|
175
|
+
* Authorization only — not "is something selected" ({@link hasSelection})
|
|
176
|
+
* and not geometry readiness (a host concern). */
|
|
177
|
+
canSelect(): boolean;
|
|
178
|
+
/** Whether this caller may extract literal text (`doc.text.copy`) —
|
|
179
|
+
* gates {@link readText} and any copy UI. */
|
|
180
|
+
canCopy(): boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Select a range programmatically — the same path gestures use, so the
|
|
183
|
+
* highlight, `onChange`, and markup bridges all behave identically. An
|
|
184
|
+
* empty range clears. Throws the engine's `PermissionDenied` when
|
|
185
|
+
* `doc.text.select` is not granted (gate UI with {@link canSelect}).
|
|
186
|
+
*/
|
|
187
|
+
select(range: SelectionRangeInput): void;
|
|
188
|
+
/** Select every character of the document. Same gating as {@link select}. */
|
|
189
|
+
selectAll(): void;
|
|
190
|
+
/** Clear the selection. Always allowed. */
|
|
191
|
+
clear(): void;
|
|
192
|
+
/** Coherent read-model: per-page segments, endpoints, direction, and the
|
|
193
|
+
* half-open {@link TextRange} (a valid {@link select} input). */
|
|
194
|
+
snapshot(): SelectionSnapshot;
|
|
195
|
+
/**
|
|
196
|
+
* Whether a selection GESTURE is in flight (drag, double- or triple-click
|
|
197
|
+
* — between pointer-down and pointer-up). Programmatic {@link select} /
|
|
198
|
+
* {@link selectAll} never set it (a programmatic selection is born
|
|
199
|
+
* settled), and derived recomputes (page geometry loading, rotate, move)
|
|
200
|
+
* never touch it. THE fact selection-scoped UI applies policy to — e.g.
|
|
201
|
+
* `<SelectionMenu>` hides while true and appears at pointer-up.
|
|
202
|
+
*/
|
|
203
|
+
isSelecting(): boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Where selection-scoped floating UI should attach: the union box of the
|
|
206
|
+
* selection's segments on its END page (where the gesture finished),
|
|
207
|
+
* falling back to the last page with materialized segments while a
|
|
208
|
+
* boundary page's geometry is still loading. One anchor regardless of
|
|
209
|
+
* cross-page selection — the shape every anchored-overlay consumer
|
|
210
|
+
* positions on. Null when nothing is selected (or nothing has
|
|
211
|
+
* materialized yet).
|
|
212
|
+
*/
|
|
213
|
+
menuAnchor(): SelectionMenuAnchor | null;
|
|
214
|
+
hasSelection(): boolean;
|
|
215
|
+
/** The pages the current selection covers (those with at least one
|
|
216
|
+
* segment) — so a cross-page action (e.g. markup creation) can fan out
|
|
217
|
+
* per page. */
|
|
218
|
+
selectedPages(): PageObjectNumber[];
|
|
219
|
+
/** Per-line oriented segments for a page, in content space — build your
|
|
220
|
+
* own highlight layer from these. */
|
|
221
|
+
segmentsForPage(pon: PageObjectNumber): SelectionSegment[];
|
|
222
|
+
/** The segments' AABBs — for consumers that genuinely want boxes (scroll,
|
|
223
|
+
* conservative regions). Never a substitute for the oriented quads in
|
|
224
|
+
* geometry that gets drawn or persisted. */
|
|
225
|
+
rectsForPage(pon: PageObjectNumber): Rect[];
|
|
226
|
+
/**
|
|
227
|
+
* The selected text. Fetches each selected page's text snapshot (cached
|
|
228
|
+
* per page, immutable per content version) and slices it by the
|
|
229
|
+
* selection's character range through the engine's `charMap` — at most
|
|
230
|
+
* one text read per page per content version, amortized across every
|
|
231
|
+
* call. Pages are joined with `\n`. Resolves `''` when nothing is
|
|
232
|
+
* selected; rejects with `PermissionDenied` when `doc.text.copy` is not
|
|
233
|
+
* granted. Clipboard writes are deliberately NOT here — this package is
|
|
234
|
+
* DOM-free; use `@embedpdf/web`'s clipboard helpers (or your platform's)
|
|
235
|
+
* on top of this.
|
|
236
|
+
*/
|
|
237
|
+
readText(): Promise<string>;
|
|
238
|
+
/** Fires whenever the selection segments change (drag-extend, programmatic
|
|
239
|
+
* select, recompute) — for live preview. Returns an unsubscriber. */
|
|
240
|
+
onChange(cb: () => void): () => void;
|
|
241
|
+
/** Fires when a selection gesture ends (pointer-up) — the commit point
|
|
242
|
+
* consumers (markup creation, copy prefetch) act on. */
|
|
243
|
+
onCommit(cb: () => void): () => void;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The HOST (framework) surface: what render layers, the interaction hub,
|
|
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
|
|
250
|
+
* point-based functions are gesture vocabulary (`…At` = point-addressed);
|
|
251
|
+
* the programmatic equivalent is the public range-based {@link
|
|
252
|
+
* SelectionCapability.select}. Every function here is INERT (no requests,
|
|
253
|
+
* no state) when `canSelect()` is false — the pointer handler needs no
|
|
254
|
+
* permission special-casing.
|
|
255
|
+
*/
|
|
256
|
+
interface SelectionHostCapability extends SelectionCapability {
|
|
257
|
+
/** Warm a page's text geometry (idempotent; no-op without
|
|
258
|
+
* `doc.text.select`). Layers call this when a page mounts. */
|
|
259
|
+
ensurePage(pon: PageObjectNumber): void;
|
|
260
|
+
isLoaded(pon: PageObjectNumber): boolean;
|
|
261
|
+
/** Is a content-space point on (or near) text? Drives the I-beam cursor. */
|
|
262
|
+
isOverText(pon: PageObjectNumber, point: Point): boolean;
|
|
263
|
+
/** Begin a caret selection at a page point. Returns false if not near any
|
|
264
|
+
* text — the caller deselects instead of capturing. */
|
|
265
|
+
beginAt(pon: PageObjectNumber, point: Point): boolean;
|
|
266
|
+
/** Double-click / touch long-press: select the word around the point.
|
|
267
|
+
* Returns false when the point has no selectable text (geometry not
|
|
268
|
+
* loaded, or no glyph there) — nothing was selected. */
|
|
269
|
+
selectWordAt(pon: PageObjectNumber, point: Point): boolean;
|
|
270
|
+
/** Triple-click: select the whole visual line around the point. Same
|
|
271
|
+
* success contract as {@link selectWordAt}. */
|
|
272
|
+
selectLineAt(pon: PageObjectNumber, point: Point): boolean;
|
|
273
|
+
/** Extend the current selection to a page point (drag). */
|
|
274
|
+
extendTo(pon: PageObjectNumber, point: Point): void;
|
|
275
|
+
/** The gesture ended (pointer-up) → notify `onCommit` consumers. */
|
|
276
|
+
end(): void;
|
|
277
|
+
/** Suppress / restore the default highlight visual (a consumer drawing its
|
|
278
|
+
* own preview — the markup ghost). */
|
|
279
|
+
setHighlightVisible(visible: boolean): void;
|
|
280
|
+
highlightVisible(): boolean;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* The selection capability token. Typed to the full
|
|
284
|
+
* {@link SelectionHostCapability} for this module, `/contract/host`, and
|
|
285
|
+
* `/internal`; the
|
|
286
|
+
* package root re-exports the SAME token narrowed to
|
|
287
|
+
* {@link SelectionCapability}.
|
|
288
|
+
*/
|
|
289
|
+
declare const SelectionToken: import("@embedpdf/core").CapabilityToken<SelectionHostCapability>;
|
|
290
|
+
//#endregion
|
|
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 };
|
|
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.
|
|
3
|
+
"version": "3.0.0-next.11",
|
|
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,28 @@
|
|
|
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
|
+
},
|
|
23
|
+
"./internal": {
|
|
24
|
+
"import": "./dist/internal.js",
|
|
25
|
+
"require": "./dist/internal.cjs"
|
|
26
|
+
},
|
|
15
27
|
"./package.json": "./package.json"
|
|
16
28
|
},
|
|
17
29
|
"files": [
|
|
18
30
|
"dist"
|
|
19
31
|
],
|
|
20
32
|
"dependencies": {
|
|
21
|
-
"@embedpdf/core": "3.0.0-next.
|
|
22
|
-
"@embedpdf/core-geometry": "3.0.0-next.
|
|
23
|
-
"@embedpdf/
|
|
24
|
-
"@embedpdf/
|
|
33
|
+
"@embedpdf/core": "3.0.0-next.11",
|
|
34
|
+
"@embedpdf/core-geometry": "3.0.0-next.11",
|
|
35
|
+
"@embedpdf/engine-core": "3.0.0-next.11",
|
|
36
|
+
"@embedpdf/plugin-interaction": "3.0.0-next.11"
|
|
25
37
|
},
|
|
26
38
|
"devDependencies": {
|
|
27
39
|
"typescript": "^5.0.0",
|