@embedpdf/plugin-selection 2.14.3 → 2.15.0
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 +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/selection-plugin.d.ts +29 -0
- package/dist/lib/types.d.ts +2 -0
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -809,6 +809,7 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
|
|
|
809
809
|
getBoundingRectForPage: (p, docId) => selectBoundingRectForPage(this.getDocumentState(getDocId(docId)), p),
|
|
810
810
|
getBoundingRects: (docId) => selectBoundingRectsForAllPages(this.getDocumentState(getDocId(docId))),
|
|
811
811
|
getSelectedText: (docId) => this.getSelectedText(getDocId(docId)),
|
|
812
|
+
setSelection: (range, docId) => this.applySelection(getDocId(docId), range),
|
|
812
813
|
clear: (docId) => this.clearSelection(getDocId(docId)),
|
|
813
814
|
copyToClipboard: (docId) => this.copyToClipboard(getDocId(docId)),
|
|
814
815
|
getState: (docId) => this.getDocumentState(getDocId(docId)),
|
|
@@ -846,6 +847,7 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
|
|
|
846
847
|
getBoundingRectForPage: (p) => selectBoundingRectForPage(this.getDocumentState(documentId), p),
|
|
847
848
|
getBoundingRects: () => selectBoundingRectsForAllPages(this.getDocumentState(documentId)),
|
|
848
849
|
getSelectedText: () => this.getSelectedText(documentId),
|
|
850
|
+
setSelection: (range) => this.applySelection(documentId, range),
|
|
849
851
|
clear: () => this.clearSelection(documentId),
|
|
850
852
|
copyToClipboard: () => this.copyToClipboard(documentId),
|
|
851
853
|
getState: () => this.getDocumentState(documentId),
|
|
@@ -1213,6 +1215,137 @@ const _SelectionPlugin = class _SelectionPlugin extends BasePlugin {
|
|
|
1213
1215
|
this.emitMenuPlacement(documentId, null);
|
|
1214
1216
|
this.notifyAllPages(documentId);
|
|
1215
1217
|
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Programmatically apply (or restore) a text selection without going through
|
|
1220
|
+
* the pointer drag flow. Useful for restoring a previously persisted selection
|
|
1221
|
+
* or driving selection from code (e.g. search results).
|
|
1222
|
+
*
|
|
1223
|
+
* Geometry for every page spanned by the range is loaded on demand (the data
|
|
1224
|
+
* comes from the WASM engine), so the returned task settles only once the
|
|
1225
|
+
* selection rects have actually been computed.
|
|
1226
|
+
*
|
|
1227
|
+
* Passing `null` is equivalent to calling {@link clearSelection}.
|
|
1228
|
+
*
|
|
1229
|
+
* @returns a task that resolves once the selection has been applied.
|
|
1230
|
+
*/
|
|
1231
|
+
applySelection(documentId, range) {
|
|
1232
|
+
if (!this.state.documents[documentId]) {
|
|
1233
|
+
return PdfTaskHelper.reject({
|
|
1234
|
+
code: PdfErrorCode.NotFound,
|
|
1235
|
+
message: `Selection state not found for document: ${documentId}`
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
if (range === null) {
|
|
1239
|
+
this.clearSelection(documentId);
|
|
1240
|
+
return PdfTaskHelper.resolve(void 0);
|
|
1241
|
+
}
|
|
1242
|
+
const validationError = this.validateRange(documentId, range);
|
|
1243
|
+
if (validationError) {
|
|
1244
|
+
return PdfTaskHelper.reject(validationError);
|
|
1245
|
+
}
|
|
1246
|
+
const normalized = this.normalizeRange(range);
|
|
1247
|
+
const pages = [];
|
|
1248
|
+
for (let p = normalized.start.page; p <= normalized.end.page; p++) {
|
|
1249
|
+
pages.push(p);
|
|
1250
|
+
}
|
|
1251
|
+
const geoTasks = pages.map((p) => this.getOrLoadGeometry(documentId, p));
|
|
1252
|
+
const result = PdfTaskHelper.create();
|
|
1253
|
+
Task.all(geoTasks).wait(() => {
|
|
1254
|
+
if (!this.state.documents[documentId]) {
|
|
1255
|
+
result.reject({
|
|
1256
|
+
code: PdfErrorCode.NotFound,
|
|
1257
|
+
message: `Selection state not found for document: ${documentId}`
|
|
1258
|
+
});
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const clamped = this.clampRangeToGeometry(documentId, normalized);
|
|
1262
|
+
if (!clamped) {
|
|
1263
|
+
result.reject({
|
|
1264
|
+
code: PdfErrorCode.NotFound,
|
|
1265
|
+
message: `Cannot apply selection: page(s) ${normalized.start.page}-${normalized.end.page} have no text geometry`
|
|
1266
|
+
});
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
this.selecting.set(documentId, false);
|
|
1270
|
+
this.anchor.set(documentId, void 0);
|
|
1271
|
+
const previouslyHighlighted = Object.keys(this.getDocumentState(documentId).rects).map(
|
|
1272
|
+
Number
|
|
1273
|
+
);
|
|
1274
|
+
this.dispatch(startSelection(documentId));
|
|
1275
|
+
this.dispatch(setSelection(documentId, clamped));
|
|
1276
|
+
this.updateRectsAndSlices(documentId, clamped);
|
|
1277
|
+
this.dispatch(endSelection(documentId));
|
|
1278
|
+
this.selChange$.emit(documentId, clamped);
|
|
1279
|
+
const pagesToNotify = new Set(previouslyHighlighted);
|
|
1280
|
+
for (let p = clamped.start.page; p <= clamped.end.page; p++) {
|
|
1281
|
+
pagesToNotify.add(p);
|
|
1282
|
+
}
|
|
1283
|
+
pagesToNotify.forEach((p) => this.notifyPage(documentId, p));
|
|
1284
|
+
this.recalculateMenuPlacement(documentId);
|
|
1285
|
+
result.resolve(void 0);
|
|
1286
|
+
}, (error) => result.reject(error.reason));
|
|
1287
|
+
return result;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Validate the structure and page bounds of a programmatic selection range.
|
|
1291
|
+
* Returns a rejection reason when the input is invalid, or null when it is
|
|
1292
|
+
* structurally sound. Glyph-index bounds are enforced separately, after the
|
|
1293
|
+
* page geometry has loaded (see {@link clampRangeToGeometry}).
|
|
1294
|
+
*/
|
|
1295
|
+
validateRange(documentId, range) {
|
|
1296
|
+
var _a, _b;
|
|
1297
|
+
const isValidPointer = (g) => !!g && typeof g === "object" && Number.isInteger(g.page) && Number.isInteger(g.index);
|
|
1298
|
+
if (!range || typeof range !== "object" || !range.start || !range.end) {
|
|
1299
|
+
return { code: PdfErrorCode.Unknown, message: "Invalid selection range: expected { start, end }" };
|
|
1300
|
+
}
|
|
1301
|
+
if (!isValidPointer(range.start) || !isValidPointer(range.end)) {
|
|
1302
|
+
return {
|
|
1303
|
+
code: PdfErrorCode.Unknown,
|
|
1304
|
+
message: "Invalid selection range: start/end must have integer page and index"
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
if (range.start.index < 0 || range.end.index < 0) {
|
|
1308
|
+
return { code: PdfErrorCode.Unknown, message: "Invalid selection range: glyph index cannot be negative" };
|
|
1309
|
+
}
|
|
1310
|
+
const pageCount = ((_b = (_a = this.getCoreDocument(documentId)) == null ? void 0 : _a.document) == null ? void 0 : _b.pageCount) ?? 0;
|
|
1311
|
+
const minPage = Math.min(range.start.page, range.end.page);
|
|
1312
|
+
const maxPage = Math.max(range.start.page, range.end.page);
|
|
1313
|
+
if (minPage < 0 || maxPage >= pageCount) {
|
|
1314
|
+
return {
|
|
1315
|
+
code: PdfErrorCode.NotFound,
|
|
1316
|
+
message: `Invalid selection range: page ${minPage < 0 ? minPage : maxPage} out of bounds [0, ${pageCount})`
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Clamp the glyph indices of a (normalized) range to the number of glyphs
|
|
1323
|
+
* available on the corresponding pages. Returns null when none of the
|
|
1324
|
+
* spanned pages have any text geometry to select.
|
|
1325
|
+
*/
|
|
1326
|
+
clampRangeToGeometry(documentId, range) {
|
|
1327
|
+
const docState = this.getDocumentState(documentId);
|
|
1328
|
+
const lastGlyphIndex = (page) => {
|
|
1329
|
+
const geo = docState.geometry[page];
|
|
1330
|
+
if (!geo || geo.runs.length === 0) return -1;
|
|
1331
|
+
const lastRun = geo.runs[geo.runs.length - 1];
|
|
1332
|
+
return lastRun.charStart + lastRun.glyphs.length - 1;
|
|
1333
|
+
};
|
|
1334
|
+
const startMax = lastGlyphIndex(range.start.page);
|
|
1335
|
+
const endMax = lastGlyphIndex(range.end.page);
|
|
1336
|
+
if (startMax < 0 && endMax < 0) return null;
|
|
1337
|
+
const clampIndex = (index, max) => max < 0 ? 0 : Math.min(Math.max(index, 0), max);
|
|
1338
|
+
return {
|
|
1339
|
+
start: { page: range.start.page, index: clampIndex(range.start.index, startMax) },
|
|
1340
|
+
end: { page: range.end.page, index: clampIndex(range.end.index, endMax) }
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
/** Order a range so that `start` never comes after `end`. */
|
|
1344
|
+
normalizeRange(range) {
|
|
1345
|
+
const { start, end } = range;
|
|
1346
|
+
const forward = end.page > start.page || end.page === start.page && end.index >= start.index;
|
|
1347
|
+
return forward ? { start, end } : { start: end, end: start };
|
|
1348
|
+
}
|
|
1216
1349
|
selectWord(documentId, page, charIndex, modeId) {
|
|
1217
1350
|
const geo = this.getDocumentState(documentId).geometry[page];
|
|
1218
1351
|
if (!geo) return;
|