@embedpdf/plugin-selection 1.0.4 → 1.0.6
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.cjs +205 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -14
- package/dist/index.d.ts +88 -14
- package/dist/index.js +200 -68
- package/dist/index.js.map +1 -1
- package/dist/preact/index.cjs +16 -7
- package/dist/preact/index.cjs.map +1 -1
- package/dist/preact/index.d.cts +2 -1
- package/dist/preact/index.d.ts +2 -1
- package/dist/preact/index.js +16 -7
- package/dist/preact/index.js.map +1 -1
- package/dist/react/index.cjs +122 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +23 -0
- package/dist/react/index.d.ts +23 -0
- package/dist/react/index.js +93 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +12 -7
package/dist/index.cjs
CHANGED
|
@@ -23,8 +23,16 @@ __export(index_exports, {
|
|
|
23
23
|
SELECTION_PLUGIN_ID: () => SELECTION_PLUGIN_ID,
|
|
24
24
|
SelectionPlugin: () => SelectionPlugin,
|
|
25
25
|
SelectionPluginPackage: () => SelectionPluginPackage,
|
|
26
|
+
getVerticalOverlap: () => getVerticalOverlap,
|
|
26
27
|
glyphAt: () => glyphAt,
|
|
27
|
-
manifest: () => manifest
|
|
28
|
+
manifest: () => manifest,
|
|
29
|
+
mergeAdjacentRects: () => mergeAdjacentRects,
|
|
30
|
+
rectIntersect: () => rectIntersect,
|
|
31
|
+
rectIsEmpty: () => rectIsEmpty,
|
|
32
|
+
rectUnion: () => rectUnion,
|
|
33
|
+
rectsWithinSlice: () => rectsWithinSlice,
|
|
34
|
+
shouldMergeHorizontalRects: () => shouldMergeHorizontalRects,
|
|
35
|
+
sliceBounds: () => sliceBounds
|
|
28
36
|
});
|
|
29
37
|
module.exports = __toCommonJS(index_exports);
|
|
30
38
|
|
|
@@ -44,6 +52,7 @@ var manifest = {
|
|
|
44
52
|
|
|
45
53
|
// src/lib/selection-plugin.ts
|
|
46
54
|
var import_core = require("@embedpdf/core");
|
|
55
|
+
var import_models2 = require("@embedpdf/models");
|
|
47
56
|
|
|
48
57
|
// src/lib/actions.ts
|
|
49
58
|
var CACHE_PAGE_GEOMETRY = "CACHE_PAGE_GEOMETRY";
|
|
@@ -52,6 +61,7 @@ var START_SELECTION = "START_SELECTION";
|
|
|
52
61
|
var END_SELECTION = "END_SELECTION";
|
|
53
62
|
var CLEAR_SELECTION = "CLEAR_SELECTION";
|
|
54
63
|
var SET_RECTS = "SET_RECTS";
|
|
64
|
+
var SET_SLICES = "SET_SLICES";
|
|
55
65
|
var RESET = "RESET";
|
|
56
66
|
var cachePageGeometry = (page, geo) => ({
|
|
57
67
|
type: CACHE_PAGE_GEOMETRY,
|
|
@@ -64,15 +74,13 @@ var setSelection = (sel) => ({
|
|
|
64
74
|
var startSelection = () => ({ type: START_SELECTION });
|
|
65
75
|
var endSelection = () => ({ type: END_SELECTION });
|
|
66
76
|
var clearSelection = () => ({ type: CLEAR_SELECTION });
|
|
67
|
-
var setRects = (
|
|
77
|
+
var setRects = (allRects) => ({
|
|
68
78
|
type: SET_RECTS,
|
|
69
|
-
payload:
|
|
79
|
+
payload: allRects
|
|
70
80
|
});
|
|
81
|
+
var setSlices = (slices) => ({ type: SET_SLICES, payload: slices });
|
|
71
82
|
var reset = () => ({ type: RESET });
|
|
72
83
|
|
|
73
|
-
// src/lib/selection-plugin.ts
|
|
74
|
-
var import_core2 = require("@embedpdf/core");
|
|
75
|
-
|
|
76
84
|
// src/lib/selectors.ts
|
|
77
85
|
var import_models = require("@embedpdf/models");
|
|
78
86
|
function selectRectsForPage(state, page) {
|
|
@@ -92,6 +100,137 @@ function selectBoundingRectsForAllPages(state) {
|
|
|
92
100
|
return out;
|
|
93
101
|
}
|
|
94
102
|
|
|
103
|
+
// src/lib/utils.ts
|
|
104
|
+
function glyphAt(geo, pt) {
|
|
105
|
+
for (const run of geo.runs) {
|
|
106
|
+
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;
|
|
107
|
+
if (!inRun) continue;
|
|
108
|
+
const rel = run.glyphs.findIndex(
|
|
109
|
+
(g) => pt.x >= g.x && pt.x <= g.x + g.width && pt.y >= g.y && pt.y <= g.y + g.height
|
|
110
|
+
);
|
|
111
|
+
if (rel !== -1) {
|
|
112
|
+
return run.charStart + rel;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return -1;
|
|
116
|
+
}
|
|
117
|
+
function sliceBounds(sel, geo, page) {
|
|
118
|
+
if (!sel || !geo) return null;
|
|
119
|
+
if (page < sel.start.page || page > sel.end.page) return null;
|
|
120
|
+
const from = page === sel.start.page ? sel.start.index : 0;
|
|
121
|
+
const lastRun = geo.runs[geo.runs.length - 1];
|
|
122
|
+
const lastCharOnPage = lastRun.charStart + lastRun.glyphs.length - 1;
|
|
123
|
+
const to = page === sel.end.page ? sel.end.index : lastCharOnPage;
|
|
124
|
+
return { from, to };
|
|
125
|
+
}
|
|
126
|
+
function rectsWithinSlice(geo, from, to, merge = true) {
|
|
127
|
+
const textRuns = [];
|
|
128
|
+
for (const run of geo.runs) {
|
|
129
|
+
const runStart = run.charStart;
|
|
130
|
+
const runEnd = runStart + run.glyphs.length - 1;
|
|
131
|
+
if (runEnd < from || runStart > to) continue;
|
|
132
|
+
const sIdx = Math.max(from, runStart) - runStart;
|
|
133
|
+
const eIdx = Math.min(to, runEnd) - runStart;
|
|
134
|
+
let minX = Infinity, maxX = -Infinity;
|
|
135
|
+
let minY = Infinity, maxY = -Infinity;
|
|
136
|
+
let charCount = 0;
|
|
137
|
+
for (let i = sIdx; i <= eIdx; i++) {
|
|
138
|
+
const g = run.glyphs[i];
|
|
139
|
+
if (g.flags === 2) continue;
|
|
140
|
+
minX = Math.min(minX, g.x);
|
|
141
|
+
maxX = Math.max(maxX, g.x + g.width);
|
|
142
|
+
minY = Math.min(minY, g.y);
|
|
143
|
+
maxY = Math.max(maxY, g.y + g.height);
|
|
144
|
+
charCount++;
|
|
145
|
+
}
|
|
146
|
+
if (minX !== Infinity && charCount > 0) {
|
|
147
|
+
textRuns.push({
|
|
148
|
+
rect: {
|
|
149
|
+
origin: { x: minX, y: minY },
|
|
150
|
+
size: { width: maxX - minX, height: maxY - minY }
|
|
151
|
+
},
|
|
152
|
+
charCount
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!merge) {
|
|
157
|
+
return textRuns.map((run) => run.rect);
|
|
158
|
+
}
|
|
159
|
+
return mergeAdjacentRects(textRuns);
|
|
160
|
+
}
|
|
161
|
+
function rectUnion(rect1, rect2) {
|
|
162
|
+
const left = Math.min(rect1.origin.x, rect2.origin.x);
|
|
163
|
+
const top = Math.min(rect1.origin.y, rect2.origin.y);
|
|
164
|
+
const right = Math.max(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);
|
|
165
|
+
const bottom = Math.max(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);
|
|
166
|
+
return {
|
|
167
|
+
origin: { x: left, y: top },
|
|
168
|
+
size: { width: right - left, height: bottom - top }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function rectIntersect(rect1, rect2) {
|
|
172
|
+
const left = Math.max(rect1.origin.x, rect2.origin.x);
|
|
173
|
+
const top = Math.max(rect1.origin.y, rect2.origin.y);
|
|
174
|
+
const right = Math.min(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width);
|
|
175
|
+
const bottom = Math.min(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height);
|
|
176
|
+
const width = Math.max(0, right - left);
|
|
177
|
+
const height = Math.max(0, bottom - top);
|
|
178
|
+
return {
|
|
179
|
+
origin: { x: left, y: top },
|
|
180
|
+
size: { width, height }
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function rectIsEmpty(rect) {
|
|
184
|
+
return rect.size.width <= 0 || rect.size.height <= 0;
|
|
185
|
+
}
|
|
186
|
+
function getVerticalOverlap(rect1, rect2) {
|
|
187
|
+
if (rectIsEmpty(rect1) || rectIsEmpty(rect2)) return 0;
|
|
188
|
+
const unionRect = rectUnion(rect1, rect2);
|
|
189
|
+
if (unionRect.size.height === rect1.size.height || unionRect.size.height === rect2.size.height) {
|
|
190
|
+
return 1;
|
|
191
|
+
}
|
|
192
|
+
const intersectRect = rectIntersect(rect1, rect2);
|
|
193
|
+
return intersectRect.size.height / unionRect.size.height;
|
|
194
|
+
}
|
|
195
|
+
function shouldMergeHorizontalRects(textRun1, textRun2) {
|
|
196
|
+
const VERTICAL_OVERLAP_THRESHOLD = 0.8;
|
|
197
|
+
const rect1 = textRun1.rect;
|
|
198
|
+
const rect2 = textRun2.rect;
|
|
199
|
+
if (getVerticalOverlap(rect1, rect2) < VERTICAL_OVERLAP_THRESHOLD) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const HORIZONTAL_WIDTH_FACTOR = 1;
|
|
203
|
+
const averageWidth1 = HORIZONTAL_WIDTH_FACTOR * rect1.size.width / textRun1.charCount;
|
|
204
|
+
const averageWidth2 = HORIZONTAL_WIDTH_FACTOR * rect2.size.width / textRun2.charCount;
|
|
205
|
+
const rect1Left = rect1.origin.x - averageWidth1;
|
|
206
|
+
const rect1Right = rect1.origin.x + rect1.size.width + averageWidth1;
|
|
207
|
+
const rect2Left = rect2.origin.x - averageWidth2;
|
|
208
|
+
const rect2Right = rect2.origin.x + rect2.size.width + averageWidth2;
|
|
209
|
+
return rect1Left < rect2Right && rect1Right > rect2Left;
|
|
210
|
+
}
|
|
211
|
+
function mergeAdjacentRects(textRuns) {
|
|
212
|
+
const results = [];
|
|
213
|
+
let previousTextRun = null;
|
|
214
|
+
let currentRect = null;
|
|
215
|
+
for (const textRun of textRuns) {
|
|
216
|
+
if (previousTextRun && currentRect) {
|
|
217
|
+
if (shouldMergeHorizontalRects(previousTextRun, textRun)) {
|
|
218
|
+
currentRect = rectUnion(currentRect, textRun.rect);
|
|
219
|
+
} else {
|
|
220
|
+
results.push(currentRect);
|
|
221
|
+
currentRect = textRun.rect;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
currentRect = textRun.rect;
|
|
225
|
+
}
|
|
226
|
+
previousTextRun = textRun;
|
|
227
|
+
}
|
|
228
|
+
if (currentRect && !rectIsEmpty(currentRect)) {
|
|
229
|
+
results.push(currentRect);
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
|
|
95
234
|
// src/lib/selection-plugin.ts
|
|
96
235
|
var SelectionPlugin = class extends import_core.BasePlugin {
|
|
97
236
|
constructor(id, registry, engine) {
|
|
@@ -99,7 +238,8 @@ var SelectionPlugin = class extends import_core.BasePlugin {
|
|
|
99
238
|
this.engine = engine;
|
|
100
239
|
/* interactive state */
|
|
101
240
|
this.selecting = false;
|
|
102
|
-
this.selChange$ = (0,
|
|
241
|
+
this.selChange$ = (0, import_core.createBehaviorEmitter)();
|
|
242
|
+
this.textRetrieved$ = (0, import_core.createBehaviorEmitter)();
|
|
103
243
|
this.coreStore.onAction(import_core.SET_DOCUMENT, (_action, state) => {
|
|
104
244
|
this.dispatch(reset());
|
|
105
245
|
this.doc = state.core.document ?? void 0;
|
|
@@ -115,31 +255,31 @@ var SelectionPlugin = class extends import_core.BasePlugin {
|
|
|
115
255
|
buildCapability() {
|
|
116
256
|
return {
|
|
117
257
|
getGeometry: (p) => this.getOrLoadGeometry(p),
|
|
118
|
-
|
|
119
|
-
|
|
258
|
+
getHighlightRectsForPage: (p) => selectRectsForPage(this.state, p),
|
|
259
|
+
getHighlightRects: () => this.state.rects,
|
|
260
|
+
getBoundingRectForPage: (p) => selectBoundingRectForPage(this.state, p),
|
|
120
261
|
getBoundingRects: () => selectBoundingRectsForAllPages(this.state),
|
|
121
262
|
begin: (p, i) => this.beginSelection(p, i),
|
|
122
263
|
update: (p, i) => this.updateSelection(p, i),
|
|
123
264
|
end: () => this.endSelection(),
|
|
124
265
|
clear: () => this.clearSelection(),
|
|
125
|
-
onSelectionChange: this.selChange$.on
|
|
266
|
+
onSelectionChange: this.selChange$.on,
|
|
267
|
+
onTextRetrieved: this.textRetrieved$.on,
|
|
268
|
+
getSelectedText: () => this.getSelectedText()
|
|
126
269
|
};
|
|
127
270
|
}
|
|
128
271
|
/* ── geometry cache ───────────────────────────────────── */
|
|
129
272
|
getOrLoadGeometry(pageIdx) {
|
|
130
273
|
const cached = this.state.geometry[pageIdx];
|
|
131
|
-
if (cached) return
|
|
132
|
-
if (!this.doc)
|
|
274
|
+
if (cached) return import_models2.PdfTaskHelper.resolve(cached);
|
|
275
|
+
if (!this.doc)
|
|
276
|
+
return import_models2.PdfTaskHelper.reject({ code: import_models2.PdfErrorCode.NotFound, message: "Doc Not Found" });
|
|
133
277
|
const page = this.doc.pages.find((p) => p.index === pageIdx);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
},
|
|
140
|
-
(e) => rej(e)
|
|
141
|
-
);
|
|
142
|
-
});
|
|
278
|
+
const task = this.engine.getPageGeometry(this.doc, page);
|
|
279
|
+
task.wait((geo) => {
|
|
280
|
+
this.dispatch(cachePageGeometry(pageIdx, geo));
|
|
281
|
+
}, import_models2.ignore);
|
|
282
|
+
return task;
|
|
143
283
|
}
|
|
144
284
|
/* ── selection state updates ───────────────────────────── */
|
|
145
285
|
beginSelection(page, index) {
|
|
@@ -158,12 +298,6 @@ var SelectionPlugin = class extends import_core.BasePlugin {
|
|
|
158
298
|
this.dispatch(clearSelection());
|
|
159
299
|
this.selChange$.emit(null);
|
|
160
300
|
}
|
|
161
|
-
updateRectsForRange(range) {
|
|
162
|
-
for (let p = range.start.page; p <= range.end.page; p++) {
|
|
163
|
-
const rects = this.buildRectsForPage(p);
|
|
164
|
-
this.dispatch(setRects(p, rects));
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
301
|
updateSelection(page, index) {
|
|
168
302
|
if (!this.selecting || !this.anchor) return;
|
|
169
303
|
const a = this.anchor;
|
|
@@ -172,35 +306,41 @@ var SelectionPlugin = class extends import_core.BasePlugin {
|
|
|
172
306
|
const end = forward ? { page, index } : a;
|
|
173
307
|
const range = { start, end };
|
|
174
308
|
this.dispatch(setSelection(range));
|
|
175
|
-
this.
|
|
309
|
+
this.updateRectsAndSlices(range);
|
|
176
310
|
this.selChange$.emit(range);
|
|
177
311
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const top = run.glyphs[sIdx].y;
|
|
197
|
-
const bottom = run.glyphs[eIdx].y + run.glyphs[eIdx].height;
|
|
198
|
-
rects.push({
|
|
199
|
-
origin: { x: left, y: top },
|
|
200
|
-
size: { width: right - left, height: bottom - top }
|
|
312
|
+
updateRectsAndSlices(range) {
|
|
313
|
+
const allRects = {};
|
|
314
|
+
const allSlices = {};
|
|
315
|
+
for (let p = range.start.page; p <= range.end.page; p++) {
|
|
316
|
+
const geo = this.state.geometry[p];
|
|
317
|
+
const sb = sliceBounds(range, geo, p);
|
|
318
|
+
if (!sb) continue;
|
|
319
|
+
allRects[p] = rectsWithinSlice(geo, sb.from, sb.to);
|
|
320
|
+
allSlices[p] = { start: sb.from, count: sb.to - sb.from + 1 };
|
|
321
|
+
}
|
|
322
|
+
this.dispatch(setRects(allRects));
|
|
323
|
+
this.dispatch(setSlices(allSlices));
|
|
324
|
+
}
|
|
325
|
+
getSelectedText() {
|
|
326
|
+
if (!this.doc || !this.state.selection) {
|
|
327
|
+
return import_models2.PdfTaskHelper.reject({
|
|
328
|
+
code: import_models2.PdfErrorCode.NotFound,
|
|
329
|
+
message: "Doc Not Found or No Selection"
|
|
201
330
|
});
|
|
202
331
|
}
|
|
203
|
-
|
|
332
|
+
const sel = this.state.selection;
|
|
333
|
+
const req = [];
|
|
334
|
+
for (let p = sel.start.page; p <= sel.end.page; p++) {
|
|
335
|
+
const s = this.state.slices[p];
|
|
336
|
+
if (s) req.push({ pageIndex: p, charIndex: s.start, charCount: s.count });
|
|
337
|
+
}
|
|
338
|
+
if (req.length === 0) return import_models2.PdfTaskHelper.resolve([]);
|
|
339
|
+
const task = this.engine.getTextSlices(this.doc, req);
|
|
340
|
+
task.wait((text) => {
|
|
341
|
+
this.textRetrieved$.emit(text);
|
|
342
|
+
}, import_models2.ignore);
|
|
343
|
+
return task;
|
|
204
344
|
}
|
|
205
345
|
};
|
|
206
346
|
SelectionPlugin.id = "selection";
|
|
@@ -209,6 +349,7 @@ SelectionPlugin.id = "selection";
|
|
|
209
349
|
var initialState = {
|
|
210
350
|
geometry: {},
|
|
211
351
|
rects: {},
|
|
352
|
+
slices: {},
|
|
212
353
|
selection: null,
|
|
213
354
|
active: false,
|
|
214
355
|
selecting: false
|
|
@@ -228,7 +369,9 @@ var selectionReducer = (state = initialState, action) => {
|
|
|
228
369
|
case CLEAR_SELECTION:
|
|
229
370
|
return { ...state, selecting: false, selection: null, rects: {}, active: false };
|
|
230
371
|
case SET_RECTS:
|
|
231
|
-
return { ...state, rects:
|
|
372
|
+
return { ...state, rects: action.payload };
|
|
373
|
+
case SET_SLICES:
|
|
374
|
+
return { ...state, slices: action.payload };
|
|
232
375
|
case RESET:
|
|
233
376
|
return initialState;
|
|
234
377
|
default:
|
|
@@ -236,21 +379,6 @@ var selectionReducer = (state = initialState, action) => {
|
|
|
236
379
|
}
|
|
237
380
|
};
|
|
238
381
|
|
|
239
|
-
// src/lib/utils.ts
|
|
240
|
-
function glyphAt(geo, pt) {
|
|
241
|
-
for (const run of geo.runs) {
|
|
242
|
-
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;
|
|
243
|
-
if (!inRun) continue;
|
|
244
|
-
const rel = run.glyphs.findIndex(
|
|
245
|
-
(g) => pt.x >= g.x && pt.x <= g.x + g.width && pt.y >= g.y && pt.y <= g.y + g.height
|
|
246
|
-
);
|
|
247
|
-
if (rel !== -1) {
|
|
248
|
-
return run.charStart + rel;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return -1;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
382
|
// src/lib/index.ts
|
|
255
383
|
var SelectionPluginPackage = {
|
|
256
384
|
manifest,
|
|
@@ -263,7 +391,15 @@ var SelectionPluginPackage = {
|
|
|
263
391
|
SELECTION_PLUGIN_ID,
|
|
264
392
|
SelectionPlugin,
|
|
265
393
|
SelectionPluginPackage,
|
|
394
|
+
getVerticalOverlap,
|
|
266
395
|
glyphAt,
|
|
267
|
-
manifest
|
|
396
|
+
manifest,
|
|
397
|
+
mergeAdjacentRects,
|
|
398
|
+
rectIntersect,
|
|
399
|
+
rectIsEmpty,
|
|
400
|
+
rectUnion,
|
|
401
|
+
rectsWithinSlice,
|
|
402
|
+
shouldMergeHorizontalRects,
|
|
403
|
+
sliceBounds
|
|
268
404
|
});
|
|
269
405
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../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":["export * from './lib';\n","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":["import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../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":["export * from './lib';\n","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, createBehaviorEmitter } 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 /* 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\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 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\n onSelectionChange: this.selChange$.on,\n onTextRetrieved: this.textRetrieved$.on,\n getSelectedText: () => this.getSelectedText(),\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 }\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 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","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 { 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 { 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":["import_models"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BasePluginConfig, Action, BasePlugin, PluginRegistry, PluginManifest, PluginPackage } from '@embedpdf/core';
|
|
2
|
-
import { PdfPageGeometry, Rect, PdfEngine } from '@embedpdf/models';
|
|
1
|
+
import { BasePluginConfig, EventHook, Action, BasePlugin, PluginRegistry, PluginManifest, PluginPackage } from '@embedpdf/core';
|
|
2
|
+
import { PdfTask, PdfPageGeometry, Rect, PdfEngine } from '@embedpdf/models';
|
|
3
3
|
|
|
4
4
|
interface SelectionPluginConfig extends BasePluginConfig {
|
|
5
5
|
}
|
|
@@ -17,22 +17,29 @@ interface SelectionState {
|
|
|
17
17
|
/** current selection or null */
|
|
18
18
|
rects: Record<number, Rect[]>;
|
|
19
19
|
selection: SelectionRangeX | null;
|
|
20
|
+
slices: Record<number, {
|
|
21
|
+
start: number;
|
|
22
|
+
count: number;
|
|
23
|
+
}>;
|
|
20
24
|
active: boolean;
|
|
21
25
|
selecting: boolean;
|
|
22
26
|
}
|
|
23
27
|
interface SelectionCapability {
|
|
24
|
-
getGeometry(page: number):
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
getGeometry(page: number): PdfTask<PdfPageGeometry>;
|
|
29
|
+
getHighlightRectsForPage(page: number): Rect[];
|
|
30
|
+
getHighlightRects(): Record<number, Rect[]>;
|
|
31
|
+
getBoundingRectForPage(page: number): Rect | null;
|
|
27
32
|
getBoundingRects(): {
|
|
28
33
|
page: number;
|
|
29
34
|
rect: Rect;
|
|
30
35
|
}[];
|
|
36
|
+
getSelectedText(): PdfTask<string[]>;
|
|
31
37
|
begin(page: number, glyphIdx: number): void;
|
|
32
38
|
update(page: number, glyphIdx: number): void;
|
|
33
39
|
end(): void;
|
|
34
40
|
clear(): void;
|
|
35
|
-
onSelectionChange
|
|
41
|
+
onSelectionChange: EventHook<SelectionRangeX | null>;
|
|
42
|
+
onTextRetrieved: EventHook<string[]>;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
declare const CACHE_PAGE_GEOMETRY = "CACHE_PAGE_GEOMETRY";
|
|
@@ -41,6 +48,7 @@ declare const START_SELECTION = "START_SELECTION";
|
|
|
41
48
|
declare const END_SELECTION = "END_SELECTION";
|
|
42
49
|
declare const CLEAR_SELECTION = "CLEAR_SELECTION";
|
|
43
50
|
declare const SET_RECTS = "SET_RECTS";
|
|
51
|
+
declare const SET_SLICES = "SET_SLICES";
|
|
44
52
|
declare const RESET = "RESET";
|
|
45
53
|
interface CachePageGeometryAction extends Action {
|
|
46
54
|
type: typeof CACHE_PAGE_GEOMETRY;
|
|
@@ -64,15 +72,19 @@ interface ClearSelectionAction extends Action {
|
|
|
64
72
|
}
|
|
65
73
|
interface SetRectsAction extends Action {
|
|
66
74
|
type: typeof SET_RECTS;
|
|
67
|
-
payload:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
payload: Record<number, Rect[]>;
|
|
76
|
+
}
|
|
77
|
+
interface SetSlicesAction extends Action {
|
|
78
|
+
type: typeof SET_SLICES;
|
|
79
|
+
payload: Record<number, {
|
|
80
|
+
start: number;
|
|
81
|
+
count: number;
|
|
82
|
+
}>;
|
|
71
83
|
}
|
|
72
84
|
interface ResetAction extends Action {
|
|
73
85
|
type: typeof RESET;
|
|
74
86
|
}
|
|
75
|
-
type SelectionAction = CachePageGeometryAction | SetSelectionAction | StartSelectionAction | EndSelectionAction | ClearSelectionAction | SetRectsAction | ResetAction;
|
|
87
|
+
type SelectionAction = CachePageGeometryAction | SetSelectionAction | StartSelectionAction | EndSelectionAction | ClearSelectionAction | SetRectsAction | SetSlicesAction | ResetAction;
|
|
76
88
|
|
|
77
89
|
declare class SelectionPlugin extends BasePlugin<SelectionPluginConfig, SelectionCapability, SelectionState, SelectionAction> {
|
|
78
90
|
private engine;
|
|
@@ -81,6 +93,7 @@ declare class SelectionPlugin extends BasePlugin<SelectionPluginConfig, Selectio
|
|
|
81
93
|
private selecting;
|
|
82
94
|
private anchor?;
|
|
83
95
|
private readonly selChange$;
|
|
96
|
+
private readonly textRetrieved$;
|
|
84
97
|
constructor(id: string, registry: PluginRegistry, engine: PdfEngine);
|
|
85
98
|
initialize(): Promise<void>;
|
|
86
99
|
destroy(): Promise<void>;
|
|
@@ -89,9 +102,9 @@ declare class SelectionPlugin extends BasePlugin<SelectionPluginConfig, Selectio
|
|
|
89
102
|
private beginSelection;
|
|
90
103
|
private endSelection;
|
|
91
104
|
private clearSelection;
|
|
92
|
-
private updateRectsForRange;
|
|
93
105
|
private updateSelection;
|
|
94
|
-
private
|
|
106
|
+
private updateRectsAndSlices;
|
|
107
|
+
private getSelectedText;
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
declare const SELECTION_PLUGIN_ID = "selection";
|
|
@@ -107,7 +120,68 @@ declare function glyphAt(geo: PdfPageGeometry, pt: {
|
|
|
107
120
|
x: number;
|
|
108
121
|
y: number;
|
|
109
122
|
}): number;
|
|
123
|
+
/**
|
|
124
|
+
* Helper: min/max glyph indices on `page` for current sel
|
|
125
|
+
* @param sel - selection range
|
|
126
|
+
* @param geo - page geometry
|
|
127
|
+
* @param page - page index
|
|
128
|
+
* @returns { from: number; to: number } | null
|
|
129
|
+
*/
|
|
130
|
+
declare function sliceBounds(sel: SelectionRangeX | null, geo: PdfPageGeometry | undefined, page: number): {
|
|
131
|
+
from: number;
|
|
132
|
+
to: number;
|
|
133
|
+
} | null;
|
|
134
|
+
/**
|
|
135
|
+
* Helper: build rects for a slice of the page
|
|
136
|
+
* @param geo - page geometry
|
|
137
|
+
* @param from - from index
|
|
138
|
+
* @param to - to index
|
|
139
|
+
* @param merge - whether to merge adjacent rects (default: true)
|
|
140
|
+
* @returns rects
|
|
141
|
+
*/
|
|
142
|
+
declare function rectsWithinSlice(geo: PdfPageGeometry, from: number, to: number, merge?: boolean): Rect[];
|
|
143
|
+
/**
|
|
144
|
+
* ============================================================================
|
|
145
|
+
* Rectangle Merging Algorithm
|
|
146
|
+
* ============================================================================
|
|
147
|
+
*
|
|
148
|
+
* The following code is adapted from Chromium's PDF text selection implementation.
|
|
149
|
+
*
|
|
150
|
+
* Copyright 2010 The Chromium Authors
|
|
151
|
+
* Use of this source code is governed by a BSD-style license that can be
|
|
152
|
+
* found in the LICENSE file: https://source.chromium.org/chromium/chromium/src/+/main:LICENSE
|
|
153
|
+
*
|
|
154
|
+
* Original source:
|
|
155
|
+
* https://source.chromium.org/chromium/chromium/src/+/main:pdf/pdfium/pdfium_range.cc
|
|
156
|
+
*
|
|
157
|
+
* Adapted for TypeScript and this project's Rect/geometry types.
|
|
158
|
+
*/
|
|
159
|
+
/**
|
|
160
|
+
* Text run info for rect merging (similar to Chromium's ScreenRectTextRunInfo)
|
|
161
|
+
*/
|
|
162
|
+
interface TextRunInfo {
|
|
163
|
+
rect: Rect;
|
|
164
|
+
charCount: number;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Helper functions for Rect operations
|
|
168
|
+
*/
|
|
169
|
+
declare function rectUnion(rect1: Rect, rect2: Rect): Rect;
|
|
170
|
+
declare function rectIntersect(rect1: Rect, rect2: Rect): Rect;
|
|
171
|
+
declare function rectIsEmpty(rect: Rect): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Returns a ratio between [0, 1] representing vertical overlap
|
|
174
|
+
*/
|
|
175
|
+
declare function getVerticalOverlap(rect1: Rect, rect2: Rect): number;
|
|
176
|
+
/**
|
|
177
|
+
* Returns true if there is sufficient horizontal and vertical overlap
|
|
178
|
+
*/
|
|
179
|
+
declare function shouldMergeHorizontalRects(textRun1: TextRunInfo, textRun2: TextRunInfo): boolean;
|
|
180
|
+
/**
|
|
181
|
+
* Merge adjacent rectangles based on proximity and overlap (similar to Chromium's algorithm)
|
|
182
|
+
*/
|
|
183
|
+
declare function mergeAdjacentRects(textRuns: TextRunInfo[]): Rect[];
|
|
110
184
|
|
|
111
185
|
declare const SelectionPluginPackage: PluginPackage<SelectionPlugin, SelectionPluginConfig, SelectionState, SelectionAction>;
|
|
112
186
|
|
|
113
|
-
export { type GlyphPointer, SELECTION_PLUGIN_ID, type SelectionCapability, SelectionPlugin, type SelectionPluginConfig, SelectionPluginPackage, type SelectionRangeX, type SelectionState, glyphAt, manifest };
|
|
187
|
+
export { type GlyphPointer, SELECTION_PLUGIN_ID, type SelectionCapability, SelectionPlugin, type SelectionPluginConfig, SelectionPluginPackage, type SelectionRangeX, type SelectionState, type TextRunInfo, getVerticalOverlap, glyphAt, manifest, mergeAdjacentRects, rectIntersect, rectIsEmpty, rectUnion, rectsWithinSlice, shouldMergeHorizontalRects, sliceBounds };
|