@embedpdf/plugin-selection 1.0.5 → 1.0.7

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/index.js CHANGED
@@ -13,7 +13,17 @@ var manifest = {
13
13
  };
14
14
 
15
15
  // src/lib/selection-plugin.ts
16
- import { BasePlugin, SET_DOCUMENT } from "@embedpdf/core";
16
+ import {
17
+ BasePlugin,
18
+ SET_DOCUMENT,
19
+ createBehaviorEmitter,
20
+ createEmitter
21
+ } from "@embedpdf/core";
22
+ import {
23
+ PdfTaskHelper,
24
+ PdfErrorCode,
25
+ ignore
26
+ } from "@embedpdf/models";
17
27
 
18
28
  // src/lib/actions.ts
19
29
  var CACHE_PAGE_GEOMETRY = "CACHE_PAGE_GEOMETRY";
@@ -22,6 +32,7 @@ var START_SELECTION = "START_SELECTION";
22
32
  var END_SELECTION = "END_SELECTION";
23
33
  var CLEAR_SELECTION = "CLEAR_SELECTION";
24
34
  var SET_RECTS = "SET_RECTS";
35
+ var SET_SLICES = "SET_SLICES";
25
36
  var RESET = "RESET";
26
37
  var cachePageGeometry = (page, geo) => ({
27
38
  type: CACHE_PAGE_GEOMETRY,
@@ -34,15 +45,13 @@ var setSelection = (sel) => ({
34
45
  var startSelection = () => ({ type: START_SELECTION });
35
46
  var endSelection = () => ({ type: END_SELECTION });
36
47
  var clearSelection = () => ({ type: CLEAR_SELECTION });
37
- var setRects = (page, rects) => ({
48
+ var setRects = (allRects) => ({
38
49
  type: SET_RECTS,
39
- payload: { page, rects }
50
+ payload: allRects
40
51
  });
52
+ var setSlices = (slices) => ({ type: SET_SLICES, payload: slices });
41
53
  var reset = () => ({ type: RESET });
42
54
 
43
- // src/lib/selection-plugin.ts
44
- import { createBehaviorEmitter } from "@embedpdf/core";
45
-
46
55
  // src/lib/selectors.ts
47
56
  import { boundingRect } from "@embedpdf/models";
48
57
  function selectRectsForPage(state, page) {
@@ -61,15 +70,176 @@ function selectBoundingRectsForAllPages(state) {
61
70
  }
62
71
  return out;
63
72
  }
73
+ function getFormattedSelectionForPage(state, page) {
74
+ const segmentRects = state.rects[page] || [];
75
+ if (segmentRects.length === 0) return null;
76
+ const boundingRect2 = selectBoundingRectForPage(state, page);
77
+ if (!boundingRect2) return null;
78
+ return { pageIndex: page, rect: boundingRect2, segmentRects };
79
+ }
80
+ function getFormattedSelection(state) {
81
+ const result = [];
82
+ const pages = Object.keys(state.rects).map(Number);
83
+ for (const pageIndex of pages) {
84
+ const segmentRects = state.rects[pageIndex] || [];
85
+ if (segmentRects.length === 0) continue;
86
+ const boundingRect2 = selectBoundingRectForPage(state, pageIndex);
87
+ if (boundingRect2) {
88
+ result.push({
89
+ pageIndex,
90
+ rect: boundingRect2,
91
+ segmentRects
92
+ });
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+
98
+ // src/lib/utils.ts
99
+ function glyphAt(geo, pt) {
100
+ for (const run of geo.runs) {
101
+ const inRun = pt.y >= run.rect.y && pt.y <= run.rect.y + run.rect.height && pt.x >= run.rect.x && pt.x <= run.rect.x + run.rect.width;
102
+ if (!inRun) continue;
103
+ const rel = run.glyphs.findIndex(
104
+ (g) => pt.x >= g.x && pt.x <= g.x + g.width && pt.y >= g.y && pt.y <= g.y + g.height
105
+ );
106
+ if (rel !== -1) {
107
+ return run.charStart + rel;
108
+ }
109
+ }
110
+ return -1;
111
+ }
112
+ function sliceBounds(sel, geo, page) {
113
+ if (!sel || !geo) return null;
114
+ if (page < sel.start.page || page > sel.end.page) return null;
115
+ const from = page === sel.start.page ? sel.start.index : 0;
116
+ const lastRun = geo.runs[geo.runs.length - 1];
117
+ const lastCharOnPage = lastRun.charStart + lastRun.glyphs.length - 1;
118
+ const to = page === sel.end.page ? sel.end.index : lastCharOnPage;
119
+ return { from, to };
120
+ }
121
+ function rectsWithinSlice(geo, from, to, merge = true) {
122
+ const textRuns = [];
123
+ for (const run of geo.runs) {
124
+ const runStart = run.charStart;
125
+ const runEnd = runStart + run.glyphs.length - 1;
126
+ if (runEnd < from || runStart > to) continue;
127
+ const sIdx = Math.max(from, runStart) - runStart;
128
+ const eIdx = Math.min(to, runEnd) - runStart;
129
+ let minX = Infinity, maxX = -Infinity;
130
+ let minY = Infinity, maxY = -Infinity;
131
+ let charCount = 0;
132
+ for (let i = sIdx; i <= eIdx; i++) {
133
+ const g = run.glyphs[i];
134
+ if (g.flags === 2) continue;
135
+ minX = Math.min(minX, g.x);
136
+ maxX = Math.max(maxX, g.x + g.width);
137
+ minY = Math.min(minY, g.y);
138
+ maxY = Math.max(maxY, g.y + g.height);
139
+ charCount++;
140
+ }
141
+ if (minX !== Infinity && charCount > 0) {
142
+ textRuns.push({
143
+ rect: {
144
+ origin: { x: minX, y: minY },
145
+ size: { width: maxX - minX, height: maxY - minY }
146
+ },
147
+ charCount
148
+ });
149
+ }
150
+ }
151
+ if (!merge) {
152
+ return textRuns.map((run) => run.rect);
153
+ }
154
+ return mergeAdjacentRects(textRuns);
155
+ }
156
+ function rectUnion(rect1, rect2) {
157
+ const left = Math.min(rect1.origin.x, rect2.origin.x);
158
+ const top = Math.min(rect1.origin.y, rect2.origin.y);
159
+ const right = Math.max(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);
160
+ const bottom = Math.max(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);
161
+ return {
162
+ origin: { x: left, y: top },
163
+ size: { width: right - left, height: bottom - top }
164
+ };
165
+ }
166
+ function rectIntersect(rect1, rect2) {
167
+ const left = Math.max(rect1.origin.x, rect2.origin.x);
168
+ const top = Math.max(rect1.origin.y, rect2.origin.y);
169
+ const right = Math.min(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);
170
+ const bottom = Math.min(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);
171
+ const width = Math.max(0, right - left);
172
+ const height = Math.max(0, bottom - top);
173
+ return {
174
+ origin: { x: left, y: top },
175
+ size: { width, height }
176
+ };
177
+ }
178
+ function rectIsEmpty(rect) {
179
+ return rect.size.width <= 0 || rect.size.height <= 0;
180
+ }
181
+ function getVerticalOverlap(rect1, rect2) {
182
+ if (rectIsEmpty(rect1) || rectIsEmpty(rect2)) return 0;
183
+ const unionRect = rectUnion(rect1, rect2);
184
+ if (unionRect.size.height === rect1.size.height || unionRect.size.height === rect2.size.height) {
185
+ return 1;
186
+ }
187
+ const intersectRect = rectIntersect(rect1, rect2);
188
+ return intersectRect.size.height / unionRect.size.height;
189
+ }
190
+ function shouldMergeHorizontalRects(textRun1, textRun2) {
191
+ const VERTICAL_OVERLAP_THRESHOLD = 0.8;
192
+ const rect1 = textRun1.rect;
193
+ const rect2 = textRun2.rect;
194
+ if (getVerticalOverlap(rect1, rect2) < VERTICAL_OVERLAP_THRESHOLD) {
195
+ return false;
196
+ }
197
+ const HORIZONTAL_WIDTH_FACTOR = 1;
198
+ const averageWidth1 = HORIZONTAL_WIDTH_FACTOR * rect1.size.width / textRun1.charCount;
199
+ const averageWidth2 = HORIZONTAL_WIDTH_FACTOR * rect2.size.width / textRun2.charCount;
200
+ const rect1Left = rect1.origin.x - averageWidth1;
201
+ const rect1Right = rect1.origin.x + rect1.size.width + averageWidth1;
202
+ const rect2Left = rect2.origin.x - averageWidth2;
203
+ const rect2Right = rect2.origin.x + rect2.size.width + averageWidth2;
204
+ return rect1Left < rect2Right && rect1Right > rect2Left;
205
+ }
206
+ function mergeAdjacentRects(textRuns) {
207
+ const results = [];
208
+ let previousTextRun = null;
209
+ let currentRect = null;
210
+ for (const textRun of textRuns) {
211
+ if (previousTextRun && currentRect) {
212
+ if (shouldMergeHorizontalRects(previousTextRun, textRun)) {
213
+ currentRect = rectUnion(currentRect, textRun.rect);
214
+ } else {
215
+ results.push(currentRect);
216
+ currentRect = textRun.rect;
217
+ }
218
+ } else {
219
+ currentRect = textRun.rect;
220
+ }
221
+ previousTextRun = textRun;
222
+ }
223
+ if (currentRect && !rectIsEmpty(currentRect)) {
224
+ results.push(currentRect);
225
+ }
226
+ return results;
227
+ }
64
228
 
65
229
  // src/lib/selection-plugin.ts
66
230
  var SelectionPlugin = class extends BasePlugin {
67
231
  constructor(id, registry, engine) {
68
232
  super(id, registry);
69
233
  this.engine = engine;
234
+ /** Modes that should trigger text-selection logic */
235
+ this.enabledModes = /* @__PURE__ */ new Set(["default"]);
70
236
  /* interactive state */
71
237
  this.selecting = false;
72
238
  this.selChange$ = createBehaviorEmitter();
239
+ this.textRetrieved$ = createBehaviorEmitter();
240
+ this.copyToClipboard$ = createEmitter();
241
+ this.beginSelection$ = createEmitter();
242
+ this.endSelection$ = createEmitter();
73
243
  this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {
74
244
  this.dispatch(reset());
75
245
  this.doc = state.core.document ?? void 0;
@@ -85,42 +255,52 @@ var SelectionPlugin = class extends BasePlugin {
85
255
  buildCapability() {
86
256
  return {
87
257
  getGeometry: (p) => this.getOrLoadGeometry(p),
88
- getHighlightRects: (p) => selectRectsForPage(this.state, p),
89
- getBoundingRect: (p) => selectBoundingRectForPage(this.state, p),
258
+ getFormattedSelection: () => getFormattedSelection(this.state),
259
+ getFormattedSelectionForPage: (p) => getFormattedSelectionForPage(this.state, p),
260
+ getHighlightRectsForPage: (p) => selectRectsForPage(this.state, p),
261
+ getHighlightRects: () => this.state.rects,
262
+ getBoundingRectForPage: (p) => selectBoundingRectForPage(this.state, p),
90
263
  getBoundingRects: () => selectBoundingRectsForAllPages(this.state),
91
264
  begin: (p, i) => this.beginSelection(p, i),
92
265
  update: (p, i) => this.updateSelection(p, i),
93
266
  end: () => this.endSelection(),
94
267
  clear: () => this.clearSelection(),
95
- onSelectionChange: this.selChange$.on
268
+ onCopyToClipboard: this.copyToClipboard$.on,
269
+ onSelectionChange: this.selChange$.on,
270
+ onTextRetrieved: this.textRetrieved$.on,
271
+ onBeginSelection: this.beginSelection$.on,
272
+ onEndSelection: this.endSelection$.on,
273
+ getSelectedText: () => this.getSelectedText(),
274
+ copyToClipboard: () => this.copyToClipboard(),
275
+ enableForMode: (id) => this.enabledModes.add(id),
276
+ isEnabledForMode: (id) => this.enabledModes.has(id)
96
277
  };
97
278
  }
98
279
  /* ── geometry cache ───────────────────────────────────── */
99
280
  getOrLoadGeometry(pageIdx) {
100
281
  const cached = this.state.geometry[pageIdx];
101
- if (cached) return Promise.resolve(cached);
102
- if (!this.doc) return Promise.reject("doc closed");
282
+ if (cached) return PdfTaskHelper.resolve(cached);
283
+ if (!this.doc)
284
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: "Doc Not Found" });
103
285
  const page = this.doc.pages.find((p) => p.index === pageIdx);
104
- return new Promise((res, rej) => {
105
- this.engine.getPageGeometry(this.doc, page).wait(
106
- (geo) => {
107
- this.dispatch(cachePageGeometry(pageIdx, geo));
108
- res(geo);
109
- },
110
- (e) => rej(e)
111
- );
112
- });
286
+ const task = this.engine.getPageGeometry(this.doc, page);
287
+ task.wait((geo) => {
288
+ this.dispatch(cachePageGeometry(pageIdx, geo));
289
+ }, ignore);
290
+ return task;
113
291
  }
114
292
  /* ── selection state updates ───────────────────────────── */
115
293
  beginSelection(page, index) {
116
294
  this.selecting = true;
117
295
  this.anchor = { page, index };
118
296
  this.dispatch(startSelection());
297
+ this.beginSelection$.emit({ page, index });
119
298
  }
120
299
  endSelection() {
121
300
  this.selecting = false;
122
301
  this.anchor = void 0;
123
302
  this.dispatch(endSelection());
303
+ this.endSelection$.emit();
124
304
  }
125
305
  clearSelection() {
126
306
  this.selecting = false;
@@ -128,12 +308,6 @@ var SelectionPlugin = class extends BasePlugin {
128
308
  this.dispatch(clearSelection());
129
309
  this.selChange$.emit(null);
130
310
  }
131
- updateRectsForRange(range) {
132
- for (let p = range.start.page; p <= range.end.page; p++) {
133
- const rects = this.buildRectsForPage(p);
134
- this.dispatch(setRects(p, rects));
135
- }
136
- }
137
311
  updateSelection(page, index) {
138
312
  if (!this.selecting || !this.anchor) return;
139
313
  const a = this.anchor;
@@ -142,35 +316,47 @@ var SelectionPlugin = class extends BasePlugin {
142
316
  const end = forward ? { page, index } : a;
143
317
  const range = { start, end };
144
318
  this.dispatch(setSelection(range));
145
- this.updateRectsForRange(range);
319
+ this.updateRectsAndSlices(range);
146
320
  this.selChange$.emit(range);
147
321
  }
148
- /* ── rect builder: 1 div per run slice ─────────────────── */
149
- buildRectsForPage(page) {
150
- const sel = this.state.selection;
151
- if (!sel) return [];
152
- if (page < sel.start.page || page > sel.end.page) return [];
153
- const geo = this.state.geometry[page];
154
- if (!geo) return [];
155
- const from = page === sel.start.page ? sel.start.index : 0;
156
- const to = page === sel.end.page ? sel.end.index : geo.runs[geo.runs.length - 1].charStart + geo.runs[geo.runs.length - 1].glyphs.length - 1;
157
- const rects = [];
158
- for (const run of geo.runs) {
159
- const runStart = run.charStart;
160
- const runEnd = runStart + run.glyphs.length - 1;
161
- if (runEnd < from || runStart > to) continue;
162
- const sIdx = Math.max(from, runStart) - runStart;
163
- const eIdx = Math.min(to, runEnd) - runStart;
164
- const left = run.glyphs[sIdx].x;
165
- const right = run.glyphs[eIdx].x + run.glyphs[eIdx].width;
166
- const top = run.glyphs[sIdx].y;
167
- const bottom = run.glyphs[eIdx].y + run.glyphs[eIdx].height;
168
- rects.push({
169
- origin: { x: left, y: top },
170
- size: { width: right - left, height: bottom - top }
322
+ updateRectsAndSlices(range) {
323
+ const allRects = {};
324
+ const allSlices = {};
325
+ for (let p = range.start.page; p <= range.end.page; p++) {
326
+ const geo = this.state.geometry[p];
327
+ const sb = sliceBounds(range, geo, p);
328
+ if (!sb) continue;
329
+ allRects[p] = rectsWithinSlice(geo, sb.from, sb.to);
330
+ allSlices[p] = { start: sb.from, count: sb.to - sb.from + 1 };
331
+ }
332
+ this.dispatch(setRects(allRects));
333
+ this.dispatch(setSlices(allSlices));
334
+ }
335
+ getSelectedText() {
336
+ if (!this.doc || !this.state.selection) {
337
+ return PdfTaskHelper.reject({
338
+ code: PdfErrorCode.NotFound,
339
+ message: "Doc Not Found or No Selection"
171
340
  });
172
341
  }
173
- return rects;
342
+ const sel = this.state.selection;
343
+ const req = [];
344
+ for (let p = sel.start.page; p <= sel.end.page; p++) {
345
+ const s = this.state.slices[p];
346
+ if (s) req.push({ pageIndex: p, charIndex: s.start, charCount: s.count });
347
+ }
348
+ if (req.length === 0) return PdfTaskHelper.resolve([]);
349
+ const task = this.engine.getTextSlices(this.doc, req);
350
+ task.wait((text) => {
351
+ this.textRetrieved$.emit(text);
352
+ }, ignore);
353
+ return task;
354
+ }
355
+ copyToClipboard() {
356
+ const text = this.getSelectedText();
357
+ text.wait((text2) => {
358
+ this.copyToClipboard$.emit(text2.join("\n"));
359
+ }, ignore);
174
360
  }
175
361
  };
176
362
  SelectionPlugin.id = "selection";
@@ -179,6 +365,7 @@ SelectionPlugin.id = "selection";
179
365
  var initialState = {
180
366
  geometry: {},
181
367
  rects: {},
368
+ slices: {},
182
369
  selection: null,
183
370
  active: false,
184
371
  selecting: false
@@ -198,7 +385,9 @@ var selectionReducer = (state = initialState, action) => {
198
385
  case CLEAR_SELECTION:
199
386
  return { ...state, selecting: false, selection: null, rects: {}, active: false };
200
387
  case SET_RECTS:
201
- return { ...state, rects: { ...state.rects, [action.payload.page]: action.payload.rects } };
388
+ return { ...state, rects: action.payload };
389
+ case SET_SLICES:
390
+ return { ...state, slices: action.payload };
202
391
  case RESET:
203
392
  return initialState;
204
393
  default:
@@ -206,21 +395,6 @@ var selectionReducer = (state = initialState, action) => {
206
395
  }
207
396
  };
208
397
 
209
- // src/lib/utils.ts
210
- function glyphAt(geo, pt) {
211
- for (const run of geo.runs) {
212
- const inRun = pt.y >= run.rect.y && pt.y <= run.rect.y + run.rect.height && pt.x >= run.rect.x && pt.x <= run.rect.x + run.rect.width;
213
- if (!inRun) continue;
214
- const rel = run.glyphs.findIndex(
215
- (g) => pt.x >= g.x && pt.x <= g.x + g.width && pt.y >= g.y && pt.y <= g.y + g.height
216
- );
217
- if (rel !== -1) {
218
- return run.charStart + rel;
219
- }
220
- }
221
- return -1;
222
- }
223
-
224
398
  // src/lib/index.ts
225
399
  var SelectionPluginPackage = {
226
400
  manifest,
@@ -232,7 +406,15 @@ export {
232
406
  SELECTION_PLUGIN_ID,
233
407
  SelectionPlugin,
234
408
  SelectionPluginPackage,
409
+ getVerticalOverlap,
235
410
  glyphAt,
236
- manifest
411
+ manifest,
412
+ mergeAdjacentRects,
413
+ rectIntersect,
414
+ rectIsEmpty,
415
+ rectUnion,
416
+ rectsWithinSlice,
417
+ shouldMergeHorizontalRects,
418
+ sliceBounds
237
419
  };
238
420
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/manifest.ts","../src/lib/selection-plugin.ts","../src/lib/actions.ts","../src/lib/selectors.ts","../src/lib/reducer.ts","../src/lib/utils.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { SelectionPluginConfig } from './types';\n\nexport const SELECTION_PLUGIN_ID = 'selection';\n\nexport const manifest: PluginManifest<SelectionPluginConfig> = {\n id: SELECTION_PLUGIN_ID,\n name: 'Selection Plugin',\n version: '1.0.0',\n provides: ['selection'],\n requires: ['interaction-manager'],\n optional: [],\n defaultConfig: {\n enabled: true,\n },\n};\n","import { BasePlugin, PluginRegistry, SET_DOCUMENT } from '@embedpdf/core';\nimport {\n SelectionCapability,\n SelectionPluginConfig,\n SelectionRangeX,\n SelectionState,\n} from './types';\nimport {\n cachePageGeometry,\n setSelection,\n SelectionAction,\n endSelection,\n startSelection,\n setRects,\n clearSelection,\n reset,\n} from './actions';\nimport { PdfEngine, PdfDocumentObject, PdfPageGeometry, TaskError, Rect } from '@embedpdf/models';\nimport { createBehaviorEmitter } from '@embedpdf/core';\nimport * as selector from './selectors';\n\nexport class SelectionPlugin extends BasePlugin<\n SelectionPluginConfig,\n SelectionCapability,\n SelectionState,\n SelectionAction\n> {\n static readonly id = 'selection' as const;\n private doc?: PdfDocumentObject;\n\n /* interactive state */\n private selecting = false;\n private anchor?: { page: number; index: number };\n\n private readonly selChange$ = createBehaviorEmitter<SelectionState['selection']>();\n\n constructor(\n id: string,\n registry: PluginRegistry,\n private engine: PdfEngine,\n ) {\n super(id, registry);\n\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.dispatch(reset());\n this.doc = state.core.document ?? undefined;\n });\n }\n\n /* ── life-cycle ────────────────────────────────────────── */\n async initialize() {}\n async destroy() {\n this.selChange$.clear();\n }\n\n /* ── capability exposed to UI / other plugins ─────────── */\n buildCapability(): SelectionCapability {\n return {\n getGeometry: (p) => this.getOrLoadGeometry(p),\n getHighlightRects: (p) => selector.selectRectsForPage(this.state, p),\n getBoundingRect: (p) => selector.selectBoundingRectForPage(this.state, p),\n getBoundingRects: () => selector.selectBoundingRectsForAllPages(this.state),\n begin: (p, i) => this.beginSelection(p, i),\n update: (p, i) => this.updateSelection(p, i),\n end: () => this.endSelection(),\n clear: () => this.clearSelection(),\n\n onSelectionChange: this.selChange$.on,\n };\n }\n\n /* ── geometry cache ───────────────────────────────────── */\n private getOrLoadGeometry(pageIdx: number): Promise<PdfPageGeometry> {\n const cached = this.state.geometry[pageIdx];\n if (cached) return Promise.resolve(cached);\n\n if (!this.doc) return Promise.reject('doc closed');\n const page = this.doc.pages.find((p) => p.index === pageIdx)!;\n\n return new Promise((res, rej) => {\n this.engine.getPageGeometry(this.doc!, page).wait(\n (geo) => {\n this.dispatch(cachePageGeometry(pageIdx, geo));\n res(geo);\n },\n (e: TaskError<any>) => rej(e),\n );\n });\n }\n\n /* ── selection state updates ───────────────────────────── */\n private beginSelection(page: number, index: number) {\n this.selecting = true;\n this.anchor = { page, index };\n this.dispatch(startSelection());\n }\n\n private endSelection() {\n this.selecting = false;\n this.anchor = undefined;\n this.dispatch(endSelection());\n }\n\n private clearSelection() {\n this.selecting = false;\n this.anchor = undefined;\n this.dispatch(clearSelection());\n this.selChange$.emit(null);\n }\n\n private updateRectsForRange(range: SelectionRangeX) {\n for (let p = range.start.page; p <= range.end.page; p++) {\n const rects = this.buildRectsForPage(p); // existing pure fn\n this.dispatch(setRects(p, rects));\n }\n }\n\n private updateSelection(page: number, index: number) {\n if (!this.selecting || !this.anchor) return;\n\n const a = this.anchor;\n const forward = page > a.page || (page === a.page && index >= a.index);\n\n const start = forward ? a : { page, index };\n const end = forward ? { page, index } : a;\n\n const range = { start, end };\n this.dispatch(setSelection(range));\n this.updateRectsForRange(range);\n this.selChange$.emit(range);\n }\n\n /* ── rect builder: 1 div per run slice ─────────────────── */\n private buildRectsForPage(page: number): Rect[] {\n const sel = this.state.selection;\n if (!sel) return [];\n\n /* page not covered by the current selection */\n if (page < sel.start.page || page > sel.end.page) return [];\n\n const geo = this.state.geometry[page];\n if (!geo) return [];\n\n const from = page === sel.start.page ? sel.start.index : 0;\n const to =\n page === sel.end.page\n ? sel.end.index\n : geo.runs[geo.runs.length - 1].charStart + geo.runs[geo.runs.length - 1].glyphs.length - 1;\n\n const rects = [];\n for (const run of geo.runs) {\n const runStart = run.charStart;\n const runEnd = runStart + run.glyphs.length - 1;\n if (runEnd < from || runStart > to) continue;\n\n const sIdx = Math.max(from, runStart) - runStart;\n const eIdx = Math.min(to, runEnd) - runStart;\n const left = run.glyphs[sIdx].x;\n const right = run.glyphs[eIdx].x + run.glyphs[eIdx].width;\n const top = run.glyphs[sIdx].y;\n const bottom = run.glyphs[eIdx].y + run.glyphs[eIdx].height;\n\n rects.push({\n origin: { x: left, y: top },\n size: { width: right - left, height: bottom - top },\n });\n }\n return rects;\n }\n}\n","import { Action } from '@embedpdf/core';\nimport { PdfPageGeometry, Rect } from '@embedpdf/models';\nimport { SelectionRangeX } from './types';\n\nexport const CACHE_PAGE_GEOMETRY = 'CACHE_PAGE_GEOMETRY';\nexport const SET_SELECTION = 'SET_SELECTION';\nexport const START_SELECTION = 'START_SELECTION';\nexport const END_SELECTION = 'END_SELECTION';\nexport const CLEAR_SELECTION = 'CLEAR_SELECTION';\nexport const SET_RECTS = 'SET_RECTS';\nexport const RESET = 'RESET';\n\nexport interface CachePageGeometryAction extends Action {\n type: typeof CACHE_PAGE_GEOMETRY;\n payload: { page: number; geo: PdfPageGeometry };\n}\nexport interface SetSelectionAction extends Action {\n type: typeof SET_SELECTION;\n payload: SelectionRangeX | null;\n}\n\nexport interface StartSelectionAction extends Action {\n type: typeof START_SELECTION;\n}\n\nexport interface EndSelectionAction extends Action {\n type: typeof END_SELECTION;\n}\n\nexport interface ClearSelectionAction extends Action {\n type: typeof CLEAR_SELECTION;\n}\n\nexport interface SetRectsAction extends Action {\n type: typeof SET_RECTS;\n payload: { page: number; rects: Rect[] };\n}\n\nexport interface ResetAction extends Action {\n type: typeof RESET;\n}\n\nexport type SelectionAction =\n | CachePageGeometryAction\n | SetSelectionAction\n | StartSelectionAction\n | EndSelectionAction\n | ClearSelectionAction\n | SetRectsAction\n | ResetAction;\n\nexport const cachePageGeometry = (page: number, geo: PdfPageGeometry): CachePageGeometryAction => ({\n type: CACHE_PAGE_GEOMETRY,\n payload: { page, geo },\n});\n\nexport const setSelection = (sel: SelectionRangeX): SetSelectionAction => ({\n type: SET_SELECTION,\n payload: sel,\n});\n\nexport const startSelection = (): StartSelectionAction => ({ type: START_SELECTION });\n\nexport const endSelection = (): EndSelectionAction => ({ type: END_SELECTION });\n\nexport const clearSelection = (): ClearSelectionAction => ({ type: CLEAR_SELECTION });\n\nexport const setRects = (page: number, rects: Rect[]): SetRectsAction => ({\n type: SET_RECTS,\n payload: { page, rects },\n});\n\nexport const reset = (): ResetAction => ({ type: RESET });\n","import { Rect, boundingRect } from '@embedpdf/models';\nimport { SelectionState } from './types';\n\nexport function selectRectsForPage(state: SelectionState, page: number) {\n return state.rects[page] ?? [];\n}\n\nexport function selectBoundingRectForPage(state: SelectionState, page: number) {\n return boundingRect(selectRectsForPage(state, page));\n}\n\nexport function selectBoundingRectsForAllPages(state: SelectionState) {\n const out: { page: number; rect: Rect }[] = [];\n const rectMap = state.rects;\n\n for (const key in rectMap) {\n const page = Number(key);\n const bRect = boundingRect(rectMap[page]);\n if (bRect) out.push({ page, rect: bRect });\n }\n return out;\n}\n","import { SelectionState } from './types';\nimport {\n SelectionAction,\n CACHE_PAGE_GEOMETRY,\n SET_SELECTION,\n START_SELECTION,\n END_SELECTION,\n SET_RECTS,\n CLEAR_SELECTION,\n RESET,\n} from './actions';\n\nexport const initialState: SelectionState = {\n geometry: {},\n rects: {},\n selection: null,\n active: false,\n selecting: false,\n};\n\nexport const selectionReducer = (state = initialState, action: SelectionAction): SelectionState => {\n switch (action.type) {\n case CACHE_PAGE_GEOMETRY: {\n const { page, geo } = action.payload;\n return { ...state, geometry: { ...state.geometry, [page]: geo } };\n }\n case SET_SELECTION:\n return { ...state, selection: action.payload, active: true };\n case START_SELECTION:\n return { ...state, selecting: true, selection: null, rects: {} };\n case END_SELECTION:\n return { ...state, selecting: false };\n case CLEAR_SELECTION:\n return { ...state, selecting: false, selection: null, rects: {}, active: false };\n case SET_RECTS:\n return { ...state, rects: { ...state.rects, [action.payload.page]: action.payload.rects } };\n case RESET:\n return initialState;\n default:\n return state;\n }\n};\n","import { PdfPageGeometry, Rect } from '@embedpdf/models';\n\n/**\n * Hit-test helper using runs\n * @param geo - page geometry\n * @param pt - point\n * @returns glyph index\n */\nexport function glyphAt(geo: PdfPageGeometry, pt: { x: number; y: number }) {\n for (const run of geo.runs) {\n const inRun =\n pt.y >= run.rect.y &&\n pt.y <= run.rect.y + run.rect.height &&\n pt.x >= run.rect.x &&\n pt.x <= run.rect.x + run.rect.width;\n\n if (!inRun) continue;\n\n // Simply check if the point is within any glyph's bounding box\n const rel = run.glyphs.findIndex(\n (g) => pt.x >= g.x && pt.x <= g.x + g.width && pt.y >= g.y && pt.y <= g.y + g.height,\n );\n\n if (rel !== -1) {\n return run.charStart + rel;\n }\n }\n return -1;\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, SELECTION_PLUGIN_ID } from './manifest';\nimport { SelectionPluginConfig, SelectionState } from './types';\n\nimport { SelectionPlugin } from './selection-plugin';\nimport { SelectionAction } from './actions';\nimport { selectionReducer, initialState } from './reducer';\n\nexport const SelectionPluginPackage: PluginPackage<\n SelectionPlugin,\n SelectionPluginConfig,\n SelectionState,\n SelectionAction\n> = {\n manifest,\n create: (registry, engine) => new SelectionPlugin(SELECTION_PLUGIN_ID, registry, engine),\n reducer: selectionReducer,\n initialState,\n};\n\nexport * from './selection-plugin';\nexport * from './types';\nexport * from './manifest';\nexport * from './utils';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lib/manifest.ts","../src/lib/selection-plugin.ts","../src/lib/actions.ts","../src/lib/selectors.ts","../src/lib/utils.ts","../src/lib/reducer.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { SelectionPluginConfig } from './types';\n\nexport const SELECTION_PLUGIN_ID = 'selection';\n\nexport const manifest: PluginManifest<SelectionPluginConfig> = {\n id: SELECTION_PLUGIN_ID,\n name: 'Selection Plugin',\n version: '1.0.0',\n provides: ['selection'],\n requires: ['interaction-manager'],\n optional: [],\n defaultConfig: {\n enabled: true,\n },\n};\n","import {\n BasePlugin,\n PluginRegistry,\n SET_DOCUMENT,\n createBehaviorEmitter,\n createEmitter,\n} from '@embedpdf/core';\nimport {\n PdfEngine,\n PdfDocumentObject,\n PdfPageGeometry,\n Rect,\n PdfTask,\n PdfTaskHelper,\n PdfErrorCode,\n ignore,\n PageTextSlice,\n} from '@embedpdf/models';\n\nimport {\n cachePageGeometry,\n setSelection,\n SelectionAction,\n endSelection,\n startSelection,\n clearSelection,\n reset,\n setRects,\n setSlices,\n} from './actions';\nimport * as selector from './selectors';\nimport {\n SelectionCapability,\n SelectionPluginConfig,\n SelectionRangeX,\n SelectionState,\n} from './types';\nimport { sliceBounds, rectsWithinSlice } from './utils';\n\nexport class SelectionPlugin extends BasePlugin<\n SelectionPluginConfig,\n SelectionCapability,\n SelectionState,\n SelectionAction\n> {\n static readonly id = 'selection' as const;\n private doc?: PdfDocumentObject;\n\n /** Modes that should trigger text-selection logic */\n private enabledModes = new Set<string>(['default']);\n\n /* interactive state */\n private selecting = false;\n private anchor?: { page: number; index: number };\n\n private readonly selChange$ = createBehaviorEmitter<SelectionState['selection']>();\n private readonly textRetrieved$ = createBehaviorEmitter<string[]>();\n private readonly copyToClipboard$ = createEmitter<string>();\n private readonly beginSelection$ = createEmitter<{ page: number; index: number }>();\n private readonly endSelection$ = createEmitter<void>();\n\n constructor(\n id: string,\n registry: PluginRegistry,\n private engine: PdfEngine,\n ) {\n super(id, registry);\n\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.dispatch(reset());\n this.doc = state.core.document ?? undefined;\n });\n }\n\n /* ── life-cycle ────────────────────────────────────────── */\n async initialize() {}\n async destroy() {\n this.selChange$.clear();\n }\n\n /* ── capability exposed to UI / other plugins ─────────── */\n buildCapability(): SelectionCapability {\n return {\n getGeometry: (p) => this.getOrLoadGeometry(p),\n getFormattedSelection: () => selector.getFormattedSelection(this.state),\n getFormattedSelectionForPage: (p) => selector.getFormattedSelectionForPage(this.state, p),\n getHighlightRectsForPage: (p) => selector.selectRectsForPage(this.state, p),\n getHighlightRects: () => this.state.rects,\n getBoundingRectForPage: (p) => selector.selectBoundingRectForPage(this.state, p),\n getBoundingRects: () => selector.selectBoundingRectsForAllPages(this.state),\n begin: (p, i) => this.beginSelection(p, i),\n update: (p, i) => this.updateSelection(p, i),\n end: () => this.endSelection(),\n clear: () => this.clearSelection(),\n onCopyToClipboard: this.copyToClipboard$.on,\n onSelectionChange: this.selChange$.on,\n onTextRetrieved: this.textRetrieved$.on,\n onBeginSelection: this.beginSelection$.on,\n onEndSelection: this.endSelection$.on,\n getSelectedText: () => this.getSelectedText(),\n copyToClipboard: () => this.copyToClipboard(),\n enableForMode: (id: string) => this.enabledModes.add(id),\n isEnabledForMode: (id: string) => this.enabledModes.has(id),\n };\n }\n\n /* ── geometry cache ───────────────────────────────────── */\n private getOrLoadGeometry(pageIdx: number): PdfTask<PdfPageGeometry> {\n const cached = this.state.geometry[pageIdx];\n if (cached) return PdfTaskHelper.resolve(cached);\n\n if (!this.doc)\n return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: 'Doc Not Found' });\n const page = this.doc.pages.find((p) => p.index === pageIdx)!;\n\n const task = this.engine.getPageGeometry(this.doc!, page);\n\n task.wait((geo) => {\n this.dispatch(cachePageGeometry(pageIdx, geo));\n }, ignore);\n\n return task;\n }\n\n /* ── selection state updates ───────────────────────────── */\n private beginSelection(page: number, index: number) {\n this.selecting = true;\n this.anchor = { page, index };\n this.dispatch(startSelection());\n this.beginSelection$.emit({ page, index });\n }\n\n private endSelection() {\n this.selecting = false;\n this.anchor = undefined;\n this.dispatch(endSelection());\n this.endSelection$.emit();\n }\n\n private clearSelection() {\n this.selecting = false;\n this.anchor = undefined;\n this.dispatch(clearSelection());\n this.selChange$.emit(null);\n }\n\n private updateSelection(page: number, index: number) {\n if (!this.selecting || !this.anchor) return;\n\n const a = this.anchor;\n const forward = page > a.page || (page === a.page && index >= a.index);\n\n const start = forward ? a : { page, index };\n const end = forward ? { page, index } : a;\n\n const range = { start, end };\n this.dispatch(setSelection(range));\n this.updateRectsAndSlices(range);\n this.selChange$.emit(range);\n }\n\n private updateRectsAndSlices(range: SelectionRangeX) {\n const allRects: Record<number, Rect[]> = {};\n const allSlices: Record<number, { start: number; count: number }> = {};\n\n for (let p = range.start.page; p <= range.end.page; p++) {\n const geo = this.state.geometry[p];\n const sb = sliceBounds(range, geo, p);\n if (!sb) continue;\n\n allRects[p] = rectsWithinSlice(geo!, sb.from, sb.to);\n allSlices[p] = { start: sb.from, count: sb.to - sb.from + 1 };\n }\n\n this.dispatch(setRects(allRects));\n this.dispatch(setSlices(allSlices));\n }\n\n private getSelectedText(): PdfTask<string[]> {\n if (!this.doc || !this.state.selection) {\n return PdfTaskHelper.reject({\n code: PdfErrorCode.NotFound,\n message: 'Doc Not Found or No Selection',\n });\n }\n\n const sel = this.state.selection;\n const req: PageTextSlice[] = [];\n\n for (let p = sel.start.page; p <= sel.end.page; p++) {\n const s = this.state.slices[p];\n if (s) req.push({ pageIndex: p, charIndex: s.start, charCount: s.count });\n }\n\n if (req.length === 0) return PdfTaskHelper.resolve([] as string[]);\n\n const task = this.engine.getTextSlices(this.doc!, req);\n\n // Emit the text when it's retrieved\n task.wait((text) => {\n this.textRetrieved$.emit(text);\n }, ignore);\n\n return task;\n }\n\n private copyToClipboard() {\n const text = this.getSelectedText();\n text.wait((text) => {\n this.copyToClipboard$.emit(text.join('\\n'));\n }, ignore);\n }\n}\n","import { Action } from '@embedpdf/core';\nimport { PdfPageGeometry, Rect } from '@embedpdf/models';\nimport { SelectionRangeX } from './types';\n\nexport const CACHE_PAGE_GEOMETRY = 'CACHE_PAGE_GEOMETRY';\nexport const SET_SELECTION = 'SET_SELECTION';\nexport const START_SELECTION = 'START_SELECTION';\nexport const END_SELECTION = 'END_SELECTION';\nexport const CLEAR_SELECTION = 'CLEAR_SELECTION';\nexport const SET_RECTS = 'SET_RECTS';\nexport const SET_SLICES = 'SET_SLICES';\nexport const RESET = 'RESET';\n\nexport interface CachePageGeometryAction extends Action {\n type: typeof CACHE_PAGE_GEOMETRY;\n payload: { page: number; geo: PdfPageGeometry };\n}\nexport interface SetSelectionAction extends Action {\n type: typeof SET_SELECTION;\n payload: SelectionRangeX | null;\n}\n\nexport interface StartSelectionAction extends Action {\n type: typeof START_SELECTION;\n}\n\nexport interface EndSelectionAction extends Action {\n type: typeof END_SELECTION;\n}\n\nexport interface ClearSelectionAction extends Action {\n type: typeof CLEAR_SELECTION;\n}\n\nexport interface SetRectsAction extends Action {\n type: typeof SET_RECTS;\n payload: Record<number, Rect[]>;\n}\n\nexport interface SetSlicesAction extends Action {\n type: typeof SET_SLICES;\n payload: Record<number, { start: number; count: number }>;\n}\n\nexport interface ResetAction extends Action {\n type: typeof RESET;\n}\n\nexport type SelectionAction =\n | CachePageGeometryAction\n | SetSelectionAction\n | StartSelectionAction\n | EndSelectionAction\n | ClearSelectionAction\n | SetRectsAction\n | SetSlicesAction\n | ResetAction;\n\nexport const cachePageGeometry = (page: number, geo: PdfPageGeometry): CachePageGeometryAction => ({\n type: CACHE_PAGE_GEOMETRY,\n payload: { page, geo },\n});\n\nexport const setSelection = (sel: SelectionRangeX): SetSelectionAction => ({\n type: SET_SELECTION,\n payload: sel,\n});\n\nexport const startSelection = (): StartSelectionAction => ({ type: START_SELECTION });\n\nexport const endSelection = (): EndSelectionAction => ({ type: END_SELECTION });\n\nexport const clearSelection = (): ClearSelectionAction => ({ type: CLEAR_SELECTION });\n\nexport const setRects = (allRects: Record<number, Rect[]>): SetRectsAction => ({\n type: SET_RECTS,\n payload: allRects,\n});\n\nexport const setSlices = (\n slices: Record<number, { start: number; count: number }>,\n): SetSlicesAction => ({ type: SET_SLICES, payload: slices });\n\nexport const reset = (): ResetAction => ({ type: RESET });\n","import { Rect, boundingRect } from '@embedpdf/models';\nimport { FormattedSelection, SelectionState } from './types';\n\nexport function selectRectsForPage(state: SelectionState, page: number) {\n return state.rects[page] ?? [];\n}\n\nexport function selectBoundingRectForPage(state: SelectionState, page: number) {\n return boundingRect(selectRectsForPage(state, page));\n}\n\nexport function selectBoundingRectsForAllPages(state: SelectionState) {\n const out: { page: number; rect: Rect }[] = [];\n const rectMap = state.rects;\n\n for (const key in rectMap) {\n const page = Number(key);\n const bRect = boundingRect(rectMap[page]);\n if (bRect) out.push({ page, rect: bRect });\n }\n return out;\n}\n\nexport function getFormattedSelectionForPage(\n state: SelectionState,\n page: number,\n): FormattedSelection | null {\n const segmentRects = state.rects[page] || [];\n if (segmentRects.length === 0) return null;\n const boundingRect = selectBoundingRectForPage(state, page);\n if (!boundingRect) return null;\n return { pageIndex: page, rect: boundingRect, segmentRects };\n}\n\nexport function getFormattedSelection(state: SelectionState) {\n const result: FormattedSelection[] = [];\n\n // Get all pages that have rects\n const pages = Object.keys(state.rects).map(Number);\n\n for (const pageIndex of pages) {\n const segmentRects = state.rects[pageIndex] || [];\n\n if (segmentRects.length === 0) continue;\n\n // Calculate bounding rect for this page\n const boundingRect = selectBoundingRectForPage(state, pageIndex);\n\n if (boundingRect) {\n result.push({\n pageIndex,\n rect: boundingRect,\n segmentRects,\n });\n }\n }\n\n return result;\n}\n","import { PdfPageGeometry, Rect } from '@embedpdf/models';\nimport { SelectionRangeX } from './types';\n\n/**\n * Hit-test helper using runs\n * @param geo - page geometry\n * @param pt - point\n * @returns glyph index\n */\nexport function glyphAt(geo: PdfPageGeometry, pt: { x: number; y: number }) {\n for (const run of geo.runs) {\n const inRun =\n pt.y >= run.rect.y &&\n pt.y <= run.rect.y + run.rect.height &&\n pt.x >= run.rect.x &&\n pt.x <= run.rect.x + run.rect.width;\n\n if (!inRun) continue;\n\n // Simply check if the point is within any glyph's bounding box\n const rel = run.glyphs.findIndex(\n (g) => pt.x >= g.x && pt.x <= g.x + g.width && pt.y >= g.y && pt.y <= g.y + g.height,\n );\n\n if (rel !== -1) {\n return run.charStart + rel;\n }\n }\n return -1;\n}\n\n/**\n * Helper: min/max glyph indices on `page` for current sel\n * @param sel - selection range\n * @param geo - page geometry\n * @param page - page index\n * @returns { from: number; to: number } | null\n */\nexport function sliceBounds(\n sel: SelectionRangeX | null,\n geo: PdfPageGeometry | undefined,\n page: number,\n): { from: number; to: number } | null {\n if (!sel || !geo) return null;\n if (page < sel.start.page || page > sel.end.page) return null;\n\n const from = page === sel.start.page ? sel.start.index : 0;\n\n const lastRun = geo.runs[geo.runs.length - 1];\n const lastCharOnPage = lastRun.charStart + lastRun.glyphs.length - 1;\n\n const to = page === sel.end.page ? sel.end.index : lastCharOnPage;\n\n return { from, to };\n}\n\n/**\n * Helper: build rects for a slice of the page\n * @param geo - page geometry\n * @param from - from index\n * @param to - to index\n * @param merge - whether to merge adjacent rects (default: true)\n * @returns rects\n */\nexport function rectsWithinSlice(\n geo: PdfPageGeometry,\n from: number,\n to: number,\n merge: boolean = true,\n): Rect[] {\n const textRuns: TextRunInfo[] = [];\n\n for (const run of geo.runs) {\n const runStart = run.charStart;\n const runEnd = runStart + run.glyphs.length - 1;\n if (runEnd < from || runStart > to) continue;\n\n const sIdx = Math.max(from, runStart) - runStart;\n const eIdx = Math.min(to, runEnd) - runStart;\n\n let minX = Infinity,\n maxX = -Infinity;\n let minY = Infinity,\n maxY = -Infinity;\n let charCount = 0;\n\n for (let i = sIdx; i <= eIdx; i++) {\n const g = run.glyphs[i];\n if (g.flags === 2) continue; // empty glyph\n\n minX = Math.min(minX, g.x);\n maxX = Math.max(maxX, g.x + g.width);\n minY = Math.min(minY, g.y);\n maxY = Math.max(maxY, g.y + g.height);\n charCount++;\n }\n\n if (minX !== Infinity && charCount > 0) {\n textRuns.push({\n rect: {\n origin: { x: minX, y: minY },\n size: { width: maxX - minX, height: maxY - minY },\n },\n charCount,\n });\n }\n }\n\n // If merge is false, just return the individual rects\n if (!merge) {\n return textRuns.map((run) => run.rect);\n }\n\n // Otherwise merge adjacent rects\n return mergeAdjacentRects(textRuns);\n}\n\n/**\n * ============================================================================\n * Rectangle Merging Algorithm\n * ============================================================================\n *\n * The following code is adapted from Chromium's PDF text selection implementation.\n *\n * Copyright 2010 The Chromium Authors\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file: https://source.chromium.org/chromium/chromium/src/+/main:LICENSE\n *\n * Original source:\n * https://source.chromium.org/chromium/chromium/src/+/main:pdf/pdfium/pdfium_range.cc\n *\n * Adapted for TypeScript and this project's Rect/geometry types.\n */\n\n/**\n * Text run info for rect merging (similar to Chromium's ScreenRectTextRunInfo)\n */\nexport interface TextRunInfo {\n rect: Rect;\n charCount: number;\n}\n\n/**\n * Helper functions for Rect operations\n */\nexport function rectUnion(rect1: Rect, rect2: Rect): Rect {\n const left = Math.min(rect1.origin.x, rect2.origin.x);\n const top = Math.min(rect1.origin.y, rect2.origin.y);\n const right = Math.max(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);\n const bottom = Math.max(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);\n\n return {\n origin: { x: left, y: top },\n size: { width: right - left, height: bottom - top },\n };\n}\n\nexport function rectIntersect(rect1: Rect, rect2: Rect): Rect {\n const left = Math.max(rect1.origin.x, rect2.origin.x);\n const top = Math.max(rect1.origin.y, rect2.origin.y);\n const right = Math.min(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);\n const bottom = Math.min(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);\n\n const width = Math.max(0, right - left);\n const height = Math.max(0, bottom - top);\n\n return {\n origin: { x: left, y: top },\n size: { width, height },\n };\n}\n\nexport function rectIsEmpty(rect: Rect): boolean {\n return rect.size.width <= 0 || rect.size.height <= 0;\n}\n\n/**\n * Returns a ratio between [0, 1] representing vertical overlap\n */\nexport function getVerticalOverlap(rect1: Rect, rect2: Rect): number {\n if (rectIsEmpty(rect1) || rectIsEmpty(rect2)) return 0;\n\n const unionRect = rectUnion(rect1, rect2);\n\n if (unionRect.size.height === rect1.size.height || unionRect.size.height === rect2.size.height) {\n return 1.0;\n }\n\n const intersectRect = rectIntersect(rect1, rect2);\n return intersectRect.size.height / unionRect.size.height;\n}\n\n/**\n * Returns true if there is sufficient horizontal and vertical overlap\n */\nexport function shouldMergeHorizontalRects(textRun1: TextRunInfo, textRun2: TextRunInfo): boolean {\n const VERTICAL_OVERLAP_THRESHOLD = 0.8;\n const rect1 = textRun1.rect;\n const rect2 = textRun2.rect;\n\n if (getVerticalOverlap(rect1, rect2) < VERTICAL_OVERLAP_THRESHOLD) {\n return false;\n }\n\n const HORIZONTAL_WIDTH_FACTOR = 1.0;\n const averageWidth1 = (HORIZONTAL_WIDTH_FACTOR * rect1.size.width) / textRun1.charCount;\n const averageWidth2 = (HORIZONTAL_WIDTH_FACTOR * rect2.size.width) / textRun2.charCount;\n\n const rect1Left = rect1.origin.x - averageWidth1;\n const rect1Right = rect1.origin.x + rect1.size.width + averageWidth1;\n const rect2Left = rect2.origin.x - averageWidth2;\n const rect2Right = rect2.origin.x + rect2.size.width + averageWidth2;\n\n return rect1Left < rect2Right && rect1Right > rect2Left;\n}\n\n/**\n * Merge adjacent rectangles based on proximity and overlap (similar to Chromium's algorithm)\n */\nexport function mergeAdjacentRects(textRuns: TextRunInfo[]): Rect[] {\n const results: Rect[] = [];\n let previousTextRun: TextRunInfo | null = null;\n let currentRect: Rect | null = null;\n\n for (const textRun of textRuns) {\n if (previousTextRun && currentRect) {\n if (shouldMergeHorizontalRects(previousTextRun, textRun)) {\n currentRect = rectUnion(currentRect, textRun.rect);\n } else {\n results.push(currentRect);\n currentRect = textRun.rect;\n }\n } else {\n currentRect = textRun.rect;\n }\n previousTextRun = textRun;\n }\n\n if (currentRect && !rectIsEmpty(currentRect)) {\n results.push(currentRect);\n }\n\n return results;\n}\n","import { SelectionState } from './types';\nimport {\n SelectionAction,\n CACHE_PAGE_GEOMETRY,\n SET_SELECTION,\n START_SELECTION,\n END_SELECTION,\n CLEAR_SELECTION,\n RESET,\n SET_SLICES,\n SET_RECTS,\n} from './actions';\n\nexport const initialState: SelectionState = {\n geometry: {},\n rects: {},\n slices: {},\n selection: null,\n active: false,\n selecting: false,\n};\n\nexport const selectionReducer = (state = initialState, action: SelectionAction): SelectionState => {\n switch (action.type) {\n case CACHE_PAGE_GEOMETRY: {\n const { page, geo } = action.payload;\n return { ...state, geometry: { ...state.geometry, [page]: geo } };\n }\n case SET_SELECTION:\n return { ...state, selection: action.payload, active: true };\n case START_SELECTION:\n return { ...state, selecting: true, selection: null, rects: {} };\n case END_SELECTION:\n return { ...state, selecting: false };\n case CLEAR_SELECTION:\n return { ...state, selecting: false, selection: null, rects: {}, active: false };\n case SET_RECTS:\n return { ...state, rects: action.payload };\n case SET_SLICES:\n return { ...state, slices: action.payload };\n case RESET:\n return initialState;\n default:\n return state;\n }\n};\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, SELECTION_PLUGIN_ID } from './manifest';\nimport { SelectionPluginConfig, SelectionState } from './types';\n\nimport { SelectionPlugin } from './selection-plugin';\nimport { SelectionAction } from './actions';\nimport { selectionReducer, initialState } from './reducer';\n\nexport const SelectionPluginPackage: PluginPackage<\n SelectionPlugin,\n SelectionPluginConfig,\n SelectionState,\n SelectionAction\n> = {\n manifest,\n create: (registry, engine) => new SelectionPlugin(SELECTION_PLUGIN_ID, registry, engine),\n reducer: selectionReducer,\n initialState,\n};\n\nexport * from './selection-plugin';\nexport * from './types';\nexport * from './manifest';\nexport * from './utils';\n"],"mappings":"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","names":["boundingRect","text"]}
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/preact/index.ts
21
21
  var preact_exports = {};
22
22
  __export(preact_exports, {
23
+ CopyToClipboard: () => CopyToClipboard,
23
24
  SelectionLayer: () => SelectionLayer,
24
25
  useSelectionCapability: () => useSelectionCapability,
25
26
  useSelectionPlugin: () => useSelectionPlugin
@@ -34,18 +35,25 @@ var useSelectionPlugin = () => (0, import_preact.usePlugin)(import_plugin_select
34
35
 
35
36
  // src/preact/components/selection-layer.tsx
36
37
  var import_hooks = require("preact/hooks");
37
- var import_plugin_selection2 = require("@embedpdf/plugin-selection");
38
+ var import_models = require("@embedpdf/models");
38
39
  var import_preact2 = require("@embedpdf/plugin-interaction-manager/preact");
40
+ var import_plugin_selection2 = require("@embedpdf/plugin-selection");
39
41
  var import_jsx_runtime = require("preact/jsx-runtime");
40
- function SelectionLayer({ pageIndex, scale }) {
42
+ function SelectionLayer({ pageIndex, scale, background = "rgba(33,150,243)" }) {
41
43
  const { provides: sel } = useSelectionCapability();
42
- const { register } = (0, import_preact2.usePointerHandlers)({ modeId: "default", pageIndex });
44
+ const { provides: im } = (0, import_preact2.useInteractionManagerCapability)();
45
+ const { register } = (0, import_preact2.usePointerHandlers)({ pageIndex });
43
46
  const [rects, setRects] = (0, import_hooks.useState)([]);
44
47
  const { setCursor, removeCursor } = (0, import_preact2.useCursor)();
45
48
  (0, import_hooks.useEffect)(() => {
46
49
  if (!sel) return;
47
50
  return sel.onSelectionChange(() => {
48
- setRects(sel.getHighlightRects(pageIndex));
51
+ const mode = im?.getActiveMode();
52
+ if (mode === "default") {
53
+ setRects(sel.getHighlightRectsForPage(pageIndex));
54
+ } else {
55
+ setRects([]);
56
+ }
49
57
  });
50
58
  }, [sel, pageIndex]);
51
59
  let geoCache;
@@ -55,20 +63,30 @@ function SelectionLayer({ pageIndex, scale }) {
55
63
  }, []);
56
64
  (0, import_hooks.useEffect)(() => {
57
65
  if (!sel) return;
58
- sel.getGeometry(pageIndex).then((g) => geoCache = g);
66
+ const task = sel.getGeometry(pageIndex);
67
+ task.wait((g) => geoCache = g, import_models.ignore);
68
+ return () => {
69
+ task.abort({
70
+ code: import_models.PdfErrorCode.Cancelled,
71
+ message: "Cancelled"
72
+ });
73
+ };
59
74
  }, [sel, pageIndex]);
60
75
  const handlers = (0, import_hooks.useMemo)(
61
76
  () => ({
62
- onPointerDown: (point) => {
77
+ onPointerDown: (point, _evt, modeId) => {
63
78
  if (!sel) return;
79
+ if (!sel.isEnabledForMode(modeId)) return;
64
80
  sel.clear();
65
- sel.getGeometry(pageIndex).then((geo) => {
81
+ const task = sel.getGeometry(pageIndex);
82
+ task.wait((geo) => {
66
83
  const g = (0, import_plugin_selection2.glyphAt)(geo, point);
67
84
  if (g !== -1) sel.begin(pageIndex, g);
68
- });
85
+ }, import_models.ignore);
69
86
  },
70
- onPointerMove: (point) => {
87
+ onPointerMove: (point, _evt, modeId) => {
71
88
  if (!sel) return;
89
+ if (!sel.isEnabledForMode(modeId)) return;
72
90
  const g = cachedGlyphAt(point);
73
91
  if (g !== -1) {
74
92
  setCursor("selection-text", "text", 10);
@@ -77,9 +95,15 @@ function SelectionLayer({ pageIndex, scale }) {
77
95
  }
78
96
  if (g !== -1) sel.update(pageIndex, g);
79
97
  },
80
- onPointerUp: () => {
98
+ onPointerUp: (_point, _evt, modeId) => {
81
99
  if (!sel) return;
100
+ if (!sel.isEnabledForMode(modeId)) return;
82
101
  sel.end();
102
+ },
103
+ onHandlerActiveEnd(modeId) {
104
+ if (!sel) return;
105
+ if (!sel.isEnabledForMode(modeId)) return;
106
+ sel.clear();
83
107
  }
84
108
  }),
85
109
  [sel, pageIndex, cachedGlyphAt]
@@ -97,15 +121,29 @@ function SelectionLayer({ pageIndex, scale }) {
97
121
  top: b.origin.y * scale,
98
122
  width: b.size.width * scale,
99
123
  height: b.size.height * scale,
100
- background: "rgba(33,150,243)",
124
+ background,
101
125
  pointerEvents: "none"
102
126
  }
103
127
  },
104
128
  i
105
129
  )) });
106
130
  }
131
+
132
+ // src/preact/components/copy-to-clipboard.tsx
133
+ var import_hooks3 = require("preact/hooks");
134
+ function CopyToClipboard() {
135
+ const { provides: sel } = useSelectionCapability();
136
+ (0, import_hooks3.useEffect)(() => {
137
+ if (!sel) return;
138
+ return sel.onCopyToClipboard((text) => {
139
+ navigator.clipboard.writeText(text);
140
+ });
141
+ }, [sel]);
142
+ return null;
143
+ }
107
144
  // Annotate the CommonJS export names for ESM import in node:
108
145
  0 && (module.exports = {
146
+ CopyToClipboard,
109
147
  SelectionLayer,
110
148
  useSelectionCapability,
111
149
  useSelectionPlugin