@anaralabs/lector 3.14.13 → 3.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/_tsup-dts-rollup.d.ts +197 -18
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1074 -46
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -219,6 +219,815 @@ function createDarkModeColorMap(colors) {
|
|
|
219
219
|
return map;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
// src/lib/selection-page.ts
|
|
223
|
+
var SelectionPage = class {
|
|
224
|
+
constructor(element, pageNumber, width, height) {
|
|
225
|
+
this.element = element;
|
|
226
|
+
this.pageNumber = pageNumber;
|
|
227
|
+
this.width = width;
|
|
228
|
+
this.height = height;
|
|
229
|
+
const walker = element.ownerDocument.createTreeWalker(
|
|
230
|
+
element,
|
|
231
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT
|
|
232
|
+
);
|
|
233
|
+
const chunks = [];
|
|
234
|
+
const lineBreaks = [];
|
|
235
|
+
let offset3 = 0;
|
|
236
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
237
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
238
|
+
const text = node.textContent ?? "";
|
|
239
|
+
if (!text) continue;
|
|
240
|
+
this.nodes.push({
|
|
241
|
+
node,
|
|
242
|
+
start: offset3,
|
|
243
|
+
end: offset3 + text.length
|
|
244
|
+
});
|
|
245
|
+
chunks.push(text);
|
|
246
|
+
offset3 += text.length;
|
|
247
|
+
} else if (node.tagName === "BR") lineBreaks.push(offset3);
|
|
248
|
+
}
|
|
249
|
+
this.content = { text: chunks.join(""), lineBreaks };
|
|
250
|
+
}
|
|
251
|
+
nodes = [];
|
|
252
|
+
content;
|
|
253
|
+
offset(node, offset3) {
|
|
254
|
+
if (!this.element.contains(node)) return null;
|
|
255
|
+
const item = this.nodes.find((item2) => item2.node === node);
|
|
256
|
+
if (item)
|
|
257
|
+
return item.start + Math.max(0, Math.min(offset3, item.end - item.start));
|
|
258
|
+
const range = this.element.ownerDocument.createRange();
|
|
259
|
+
range.selectNodeContents(this.element);
|
|
260
|
+
try {
|
|
261
|
+
range.setEnd(node, offset3);
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
return Math.min(range.toString().length, this.content.text.length);
|
|
266
|
+
}
|
|
267
|
+
point(offset3, end = false) {
|
|
268
|
+
const index = Math.max(0, Math.min(offset3, this.content.text.length));
|
|
269
|
+
const item = this.nodes.find((item2) => end ? item2.end >= index : item2.end > index) ?? this.nodes.at(-1);
|
|
270
|
+
return item ? { node: item.node, offset: Math.max(0, index - item.start) } : null;
|
|
271
|
+
}
|
|
272
|
+
rects(from, to) {
|
|
273
|
+
const bounds = this.element.getBoundingClientRect();
|
|
274
|
+
const scaleX = bounds.width / this.width || 1;
|
|
275
|
+
const scaleY = bounds.height / this.height || 1;
|
|
276
|
+
const rects = [];
|
|
277
|
+
for (const item of this.nodes) {
|
|
278
|
+
const start = Math.max(from, item.start);
|
|
279
|
+
const end = Math.min(to, item.end);
|
|
280
|
+
if (end <= start || !item.node.textContent?.slice(start - item.start, end - item.start).trim())
|
|
281
|
+
continue;
|
|
282
|
+
const range = this.element.ownerDocument.createRange();
|
|
283
|
+
range.setStart(item.node, start - item.start);
|
|
284
|
+
range.setEnd(item.node, end - item.start);
|
|
285
|
+
for (const rect of range.getClientRects()) {
|
|
286
|
+
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
287
|
+
rects.push({
|
|
288
|
+
pageNumber: this.pageNumber,
|
|
289
|
+
left: (rect.left - bounds.left) / scaleX,
|
|
290
|
+
top: (rect.top - bounds.top) / scaleY,
|
|
291
|
+
width: rect.width / scaleX,
|
|
292
|
+
height: rect.height / scaleY
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return rects;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
async function withSelectionPage(proxy, owner, signal, read) {
|
|
300
|
+
signal.throwIfAborted();
|
|
301
|
+
const { TextLayer: TextLayer2 } = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
302
|
+
signal.throwIfAborted();
|
|
303
|
+
const viewport = proxy.getViewport({ scale: 1 });
|
|
304
|
+
const container = owner.createElement("div");
|
|
305
|
+
container.className = "textLayer";
|
|
306
|
+
container.setAttribute("aria-hidden", "true");
|
|
307
|
+
container.style.cssText = `position:fixed;left:-100000px;top:0;visibility:hidden;pointer-events:none;width:${viewport.width}px;height:${viewport.height}px;--scale-factor:1;--total-scale-factor:1;`;
|
|
308
|
+
owner.body.append(container);
|
|
309
|
+
let layer;
|
|
310
|
+
const cancel = () => layer?.cancel();
|
|
311
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
312
|
+
try {
|
|
313
|
+
layer = new TextLayer2({
|
|
314
|
+
container,
|
|
315
|
+
viewport,
|
|
316
|
+
textContentSource: proxy.streamTextContent()
|
|
317
|
+
});
|
|
318
|
+
await layer.render();
|
|
319
|
+
signal.throwIfAborted();
|
|
320
|
+
const unrotated = proxy.getViewport({ scale: 1, rotation: 0 });
|
|
321
|
+
container.style.width = `${unrotated.width}px`;
|
|
322
|
+
container.style.height = `${unrotated.height}px`;
|
|
323
|
+
return read(
|
|
324
|
+
new SelectionPage(
|
|
325
|
+
container,
|
|
326
|
+
proxy.pageNumber,
|
|
327
|
+
viewport.width,
|
|
328
|
+
viewport.height
|
|
329
|
+
)
|
|
330
|
+
);
|
|
331
|
+
} finally {
|
|
332
|
+
signal.removeEventListener("abort", cancel);
|
|
333
|
+
layer?.cancel();
|
|
334
|
+
container.remove();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/lib/text-normalization.ts
|
|
339
|
+
function replace(source, replacements2) {
|
|
340
|
+
const chunks = [];
|
|
341
|
+
const edits = [];
|
|
342
|
+
let offset3 = 0;
|
|
343
|
+
let outputLength = 0;
|
|
344
|
+
for (const replacement of replacements2) {
|
|
345
|
+
const { index, length, text } = replacement;
|
|
346
|
+
if (source.text.slice(index, index + length) === text) continue;
|
|
347
|
+
const before = source.text.slice(offset3, index);
|
|
348
|
+
chunks.push(before, text);
|
|
349
|
+
outputLength += before.length;
|
|
350
|
+
edits.push({
|
|
351
|
+
start: outputLength,
|
|
352
|
+
end: outputLength + text.length,
|
|
353
|
+
sourceStart: index,
|
|
354
|
+
sourceEnd: index + length
|
|
355
|
+
});
|
|
356
|
+
outputLength += text.length;
|
|
357
|
+
offset3 = index + length;
|
|
358
|
+
}
|
|
359
|
+
if (!edits.length) return source;
|
|
360
|
+
chunks.push(source.text.slice(offset3));
|
|
361
|
+
return { text: chunks.join(""), maps: [...source.maps, edits] };
|
|
362
|
+
}
|
|
363
|
+
function* replacements(text, pattern, value) {
|
|
364
|
+
for (const match of text.matchAll(pattern)) {
|
|
365
|
+
yield { index: match.index, length: match[0].length, text: value };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function* insertLineBreaks(text, lineBreaks) {
|
|
369
|
+
let previous = -1;
|
|
370
|
+
for (const index of lineBreaks) {
|
|
371
|
+
if (!Number.isInteger(index) || index <= previous || index < 0 || index > text.length)
|
|
372
|
+
continue;
|
|
373
|
+
previous = index;
|
|
374
|
+
yield { index, length: 0, text: "\n" };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
var ligatures = ["ff", "fi", "fl", "ffi", "ffl", "st", "st"];
|
|
378
|
+
var softHyphen = /\u00ad(?:[^\S\r\n]*\r?\n[^\S\r\n]*(?=\p{L}))?/gu;
|
|
379
|
+
var wrappedHyphen = /(?<=[\p{L}\p{M}])[-\u2010][^\S\r\n]*\r?\n[^\S\r\n]*(?=\p{Ll})/gu;
|
|
380
|
+
var cjkLineBreak = /(?<=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])\r?\n(?=[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}])/gu;
|
|
381
|
+
function normalizeSelectionText(text, options = {}) {
|
|
382
|
+
let result = normalizeUnicode(text).replace(softHyphen, "");
|
|
383
|
+
if (options.ignoreHyphenation) result = result.replace(wrappedHyphen, "");
|
|
384
|
+
if (options.lineBreaks === "space") {
|
|
385
|
+
result = result.replace(cjkLineBreak, "").replace(/(?<!\n)\n(?!\n)/g, " ");
|
|
386
|
+
}
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
function normalizeUnicode(text, matchDiacritics = true) {
|
|
390
|
+
let result = text.replace(
|
|
391
|
+
/[\uFB00-\uFB06]/g,
|
|
392
|
+
(char) => ligatures[char.charCodeAt(0) - 64256]
|
|
393
|
+
);
|
|
394
|
+
if (!matchDiacritics) {
|
|
395
|
+
result = result.normalize("NFD").replace(
|
|
396
|
+
/([\p{Script=Latin}\p{Script=Greek}\p{Script=Cyrillic}])\p{M}+/gu,
|
|
397
|
+
"$1"
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
return result.normalize("NFC");
|
|
401
|
+
}
|
|
402
|
+
var segmenter;
|
|
403
|
+
function* unicodeReplacements(text, matchDiacritics) {
|
|
404
|
+
segmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
405
|
+
for (const match of text.matchAll(/\p{ASCII}?\P{ASCII}+/gu)) {
|
|
406
|
+
if (normalizeUnicode(match[0], matchDiacritics) === match[0]) continue;
|
|
407
|
+
for (const { segment, index } of segmenter.segment(match[0])) {
|
|
408
|
+
yield {
|
|
409
|
+
index: match.index + index,
|
|
410
|
+
length: segment.length,
|
|
411
|
+
text: normalizeUnicode(segment, matchDiacritics)
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function normalizeSearchText(text, options = {}, lineBreaks = []) {
|
|
417
|
+
let result = { text, maps: [] };
|
|
418
|
+
if (lineBreaks.length)
|
|
419
|
+
result = replace(result, insertLineBreaks(text, lineBreaks));
|
|
420
|
+
const matchDiacritics = options.matchDiacritics !== false;
|
|
421
|
+
if (normalizeUnicode(result.text, matchDiacritics) !== result.text) {
|
|
422
|
+
result = replace(result, unicodeReplacements(result.text, matchDiacritics));
|
|
423
|
+
}
|
|
424
|
+
result = replace(result, replacements(result.text, softHyphen, ""));
|
|
425
|
+
if (options.ignoreHyphenation) {
|
|
426
|
+
result = replace(result, replacements(result.text, wrappedHyphen, ""));
|
|
427
|
+
}
|
|
428
|
+
result = replace(result, replacements(result.text, cjkLineBreak, ""));
|
|
429
|
+
result = replace(result, replacements(result.text, /\s+/gu, " "));
|
|
430
|
+
const lower = result.text.toLowerCase();
|
|
431
|
+
if (lower.length !== result.text.length) {
|
|
432
|
+
function* caseReplacements(value) {
|
|
433
|
+
let index = 0;
|
|
434
|
+
for (const char of value) {
|
|
435
|
+
const folded = char.toLowerCase();
|
|
436
|
+
if (folded.length !== char.length)
|
|
437
|
+
yield { index, length: char.length, text: folded };
|
|
438
|
+
index += char.length;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
result = replace(result, caseReplacements(result.text));
|
|
442
|
+
}
|
|
443
|
+
return { ...result, text: lower };
|
|
444
|
+
}
|
|
445
|
+
function sourceOffset(edits, offset3, end) {
|
|
446
|
+
let low = 0;
|
|
447
|
+
let high = edits.length;
|
|
448
|
+
while (low < high) {
|
|
449
|
+
const middle = low + high >>> 1;
|
|
450
|
+
if (edits[middle].start <= offset3) low = middle + 1;
|
|
451
|
+
else high = middle;
|
|
452
|
+
}
|
|
453
|
+
const edit = edits[low - 1];
|
|
454
|
+
if (!edit) return offset3 + (end ? 1 : 0);
|
|
455
|
+
if (offset3 < edit.end) return end ? edit.sourceEnd : edit.sourceStart;
|
|
456
|
+
return offset3 + edit.sourceEnd - edit.end + (end ? 1 : 0);
|
|
457
|
+
}
|
|
458
|
+
function originalTextRange(normalized, start, end) {
|
|
459
|
+
for (let i = normalized.maps.length - 1; i >= 0; i--) {
|
|
460
|
+
const map = normalized.maps[i];
|
|
461
|
+
start = sourceOffset(map, start, false);
|
|
462
|
+
end = sourceOffset(map, end - 1, true);
|
|
463
|
+
}
|
|
464
|
+
return { start, end };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/lib/pdf-selection.ts
|
|
468
|
+
var sameAnchor = (a, b) => a.pageNumber === b.pageNumber && a.offset === b.offset;
|
|
469
|
+
var compare = (a, b) => a.pageNumber - b.pageNumber || a.offset - b.offset;
|
|
470
|
+
var samePoint = (a, node, offset3) => a.node === node && a.offset === offset3;
|
|
471
|
+
var selectionOwners = /* @__PURE__ */ new WeakMap();
|
|
472
|
+
var PDFSelectionController = class {
|
|
473
|
+
constructor(options) {
|
|
474
|
+
this.options = options;
|
|
475
|
+
}
|
|
476
|
+
snapshot = null;
|
|
477
|
+
listeners = /* @__PURE__ */ new Set();
|
|
478
|
+
layers = /* @__PURE__ */ new Map();
|
|
479
|
+
pages = /* @__PURE__ */ new Map();
|
|
480
|
+
container = null;
|
|
481
|
+
projection = null;
|
|
482
|
+
frame = null;
|
|
483
|
+
work = null;
|
|
484
|
+
structuralChange = false;
|
|
485
|
+
drag = null;
|
|
486
|
+
connectedCleanup = null;
|
|
487
|
+
generation = 0;
|
|
488
|
+
keyboardWork = null;
|
|
489
|
+
keyboardMoves = [];
|
|
490
|
+
getSnapshot = () => this.snapshot;
|
|
491
|
+
subscribe = (listener) => {
|
|
492
|
+
this.listeners.add(listener);
|
|
493
|
+
return () => {
|
|
494
|
+
this.listeners.delete(listener);
|
|
495
|
+
};
|
|
496
|
+
};
|
|
497
|
+
get isDragging() {
|
|
498
|
+
return this.drag !== null;
|
|
499
|
+
}
|
|
500
|
+
includesPage(page) {
|
|
501
|
+
const range = this.ordered();
|
|
502
|
+
return Boolean(
|
|
503
|
+
range && page >= range.start.pageNumber && page <= range.end.pageNumber
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
publish(snapshot) {
|
|
507
|
+
this.snapshot = snapshot;
|
|
508
|
+
for (const listener of this.listeners) listener();
|
|
509
|
+
}
|
|
510
|
+
ordered() {
|
|
511
|
+
if (!this.snapshot) return null;
|
|
512
|
+
const { anchor, focus } = this.snapshot;
|
|
513
|
+
return compare(anchor, focus) <= 0 ? { start: anchor, end: focus } : { start: focus, end: anchor };
|
|
514
|
+
}
|
|
515
|
+
bounds(page, length) {
|
|
516
|
+
const range = this.ordered();
|
|
517
|
+
return {
|
|
518
|
+
from: page === range.start.pageNumber ? Math.min(range.start.offset, length) : 0,
|
|
519
|
+
to: page === range.end.pageNumber ? Math.min(range.end.offset, length) : length
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
remember(page) {
|
|
523
|
+
if (!this.includesPage(page.pageNumber)) return;
|
|
524
|
+
for (const point of [this.snapshot.anchor, this.snapshot.focus]) {
|
|
525
|
+
if (point.pageNumber === page.pageNumber && point.offset > page.content.text.length)
|
|
526
|
+
throw new RangeError("Selection offset exceeds page text length");
|
|
527
|
+
}
|
|
528
|
+
const { from, to } = this.bounds(page.pageNumber, page.content.text.length);
|
|
529
|
+
const cached = this.pages.get(page.pageNumber);
|
|
530
|
+
if (cached?.content.text === page.content.text && cached.from === from && cached.to === to)
|
|
531
|
+
return;
|
|
532
|
+
this.pages.set(page.pageNumber, {
|
|
533
|
+
content: page.content,
|
|
534
|
+
from,
|
|
535
|
+
to,
|
|
536
|
+
rects: page.rects(from, to)
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
complete() {
|
|
540
|
+
const range = this.ordered();
|
|
541
|
+
if (!range) return false;
|
|
542
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++) {
|
|
543
|
+
const page = this.pages.get(n);
|
|
544
|
+
if (!page) return false;
|
|
545
|
+
const bounds = this.bounds(n, page.content.text.length);
|
|
546
|
+
if (page.from !== bounds.from || page.to !== bounds.to) return false;
|
|
547
|
+
}
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
setSelection = (anchor, focus) => {
|
|
551
|
+
for (const point of [anchor, focus]) {
|
|
552
|
+
const text = this.layers.get(point.pageNumber)?.content.text ?? this.pages.get(point.pageNumber)?.content.text;
|
|
553
|
+
if (text !== void 0 && point.offset > text.length)
|
|
554
|
+
throw new RangeError("Selection offset exceeds page text length");
|
|
555
|
+
if (!Number.isInteger(point.pageNumber) || point.pageNumber < 1 || point.pageNumber > this.options.pageCount || !Number.isInteger(point.offset) || point.offset < 0)
|
|
556
|
+
throw new RangeError(
|
|
557
|
+
"Selection anchors need a valid page and nonnegative UTF-16 offset"
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
if (sameAnchor(anchor, focus)) {
|
|
561
|
+
const drag = this.drag;
|
|
562
|
+
this.clear();
|
|
563
|
+
this.drag = drag;
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (this.snapshot && this.snapshot.status !== "error" && sameAnchor(this.snapshot.anchor, anchor) && sameAnchor(this.snapshot.focus, focus))
|
|
567
|
+
return;
|
|
568
|
+
if (this.container) {
|
|
569
|
+
const owner = this.container.ownerDocument;
|
|
570
|
+
const previous = selectionOwners.get(owner);
|
|
571
|
+
if (previous !== this) previous?.clear();
|
|
572
|
+
selectionOwners.set(owner, this);
|
|
573
|
+
}
|
|
574
|
+
this.work?.abort();
|
|
575
|
+
this.keyboardWork?.abort();
|
|
576
|
+
this.keyboardWork = null;
|
|
577
|
+
this.generation++;
|
|
578
|
+
this.snapshot = {
|
|
579
|
+
anchor: { ...anchor },
|
|
580
|
+
focus: { ...focus },
|
|
581
|
+
status: "loading"
|
|
582
|
+
};
|
|
583
|
+
for (const n of this.pages.keys())
|
|
584
|
+
if (!this.includesPage(n)) this.pages.delete(n);
|
|
585
|
+
for (const page of this.layers.values()) this.remember(page);
|
|
586
|
+
this.publish({
|
|
587
|
+
...this.snapshot,
|
|
588
|
+
status: this.complete() ? "ready" : "loading"
|
|
589
|
+
});
|
|
590
|
+
this.project();
|
|
591
|
+
this.schedule();
|
|
592
|
+
this.hydrate();
|
|
593
|
+
};
|
|
594
|
+
clear = () => {
|
|
595
|
+
const owner = this.container?.ownerDocument;
|
|
596
|
+
if (owner && selectionOwners.get(owner) === this)
|
|
597
|
+
selectionOwners.delete(owner);
|
|
598
|
+
this.work?.abort();
|
|
599
|
+
this.keyboardWork?.abort();
|
|
600
|
+
this.keyboardWork = null;
|
|
601
|
+
this.generation++;
|
|
602
|
+
this.pages.clear();
|
|
603
|
+
this.keyboardMoves = [];
|
|
604
|
+
this.drag = null;
|
|
605
|
+
const selection = this.container?.ownerDocument.getSelection();
|
|
606
|
+
if (this.projection && selection && samePoint(
|
|
607
|
+
this.projection.anchor,
|
|
608
|
+
selection.anchorNode,
|
|
609
|
+
selection.anchorOffset
|
|
610
|
+
) && samePoint(
|
|
611
|
+
this.projection.focus,
|
|
612
|
+
selection.focusNode,
|
|
613
|
+
selection.focusOffset
|
|
614
|
+
))
|
|
615
|
+
selection.removeAllRanges();
|
|
616
|
+
this.projection = null;
|
|
617
|
+
if (this.snapshot) this.publish(null);
|
|
618
|
+
};
|
|
619
|
+
getText = (options = {}) => {
|
|
620
|
+
const range = this.ordered();
|
|
621
|
+
if (!range || !this.complete()) return null;
|
|
622
|
+
const chunks = [];
|
|
623
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++) {
|
|
624
|
+
const page = this.pages.get(n);
|
|
625
|
+
const pieces = [];
|
|
626
|
+
let offset3 = page.from;
|
|
627
|
+
for (const end of page.content.lineBreaks) {
|
|
628
|
+
if (end <= page.from || end >= page.to) continue;
|
|
629
|
+
pieces.push(page.content.text.slice(offset3, end), "\n");
|
|
630
|
+
offset3 = end;
|
|
631
|
+
}
|
|
632
|
+
pieces.push(page.content.text.slice(offset3, page.to));
|
|
633
|
+
chunks.push(pieces.join(""));
|
|
634
|
+
}
|
|
635
|
+
return normalizeSelectionText(chunks.join("\n\n"), options);
|
|
636
|
+
};
|
|
637
|
+
getSelection = (options) => {
|
|
638
|
+
const text = this.getText(options);
|
|
639
|
+
if (text === null || !this.snapshot) return null;
|
|
640
|
+
const range = this.ordered();
|
|
641
|
+
const highlights = [];
|
|
642
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++)
|
|
643
|
+
highlights.push(...this.pages.get(n).rects.map((rect) => ({ ...rect })));
|
|
644
|
+
return {
|
|
645
|
+
anchor: { ...this.snapshot.anchor },
|
|
646
|
+
focus: { ...this.snapshot.focus },
|
|
647
|
+
text,
|
|
648
|
+
highlights,
|
|
649
|
+
underlines: highlights.map((rect) => ({
|
|
650
|
+
...rect,
|
|
651
|
+
top: rect.top + rect.height - 1,
|
|
652
|
+
height: 1
|
|
653
|
+
})),
|
|
654
|
+
isCollapsed: false
|
|
655
|
+
};
|
|
656
|
+
};
|
|
657
|
+
getSelectionAsync = async (options) => {
|
|
658
|
+
const generation = this.generation;
|
|
659
|
+
await new Promise((resolve) => {
|
|
660
|
+
if (this.snapshot?.status !== "loading") {
|
|
661
|
+
resolve();
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const unsubscribe = this.subscribe(() => {
|
|
665
|
+
if (generation !== this.generation || this.snapshot?.status !== "loading") {
|
|
666
|
+
unsubscribe();
|
|
667
|
+
resolve();
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
if (generation !== this.generation)
|
|
672
|
+
throw new DOMException("Selection changed", "AbortError");
|
|
673
|
+
if (this.snapshot?.status === "error") throw this.snapshot.error;
|
|
674
|
+
return this.getSelection(options);
|
|
675
|
+
};
|
|
676
|
+
finishSelection() {
|
|
677
|
+
if (this.snapshot?.status !== "loading" || !this.complete()) return;
|
|
678
|
+
this.work?.abort();
|
|
679
|
+
this.work = null;
|
|
680
|
+
this.publish({ ...this.snapshot, status: "ready" });
|
|
681
|
+
}
|
|
682
|
+
registerLayer(element, pageNumber, width, height) {
|
|
683
|
+
const page = new SelectionPage(element, pageNumber, width, height);
|
|
684
|
+
this.layers.set(pageNumber, page);
|
|
685
|
+
try {
|
|
686
|
+
this.remember(page);
|
|
687
|
+
this.finishSelection();
|
|
688
|
+
} catch (error) {
|
|
689
|
+
this.work?.abort();
|
|
690
|
+
if (this.snapshot)
|
|
691
|
+
this.publish({ ...this.snapshot, status: "error", error });
|
|
692
|
+
}
|
|
693
|
+
this.schedule();
|
|
694
|
+
return () => {
|
|
695
|
+
if (this.layers.get(pageNumber) !== page) return;
|
|
696
|
+
this.structuralChange = true;
|
|
697
|
+
this.layers.delete(pageNumber);
|
|
698
|
+
this.projection = null;
|
|
699
|
+
this.schedule();
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
anchorFor(node, offset3) {
|
|
703
|
+
if (!node) return null;
|
|
704
|
+
for (const [pageNumber, page] of this.layers) {
|
|
705
|
+
const index = page.offset(node, offset3);
|
|
706
|
+
if (index !== null) return { pageNumber, offset: index };
|
|
707
|
+
}
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
capture = () => {
|
|
711
|
+
const owner = this.container?.ownerDocument;
|
|
712
|
+
const active = owner?.activeElement;
|
|
713
|
+
if (active && active !== owner?.body && !this.container?.contains(active) && !active.closest(
|
|
714
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
715
|
+
))
|
|
716
|
+
return;
|
|
717
|
+
const selection = owner?.getSelection();
|
|
718
|
+
if (!selection || selection.rangeCount > 1) return;
|
|
719
|
+
if (this.projection && samePoint(
|
|
720
|
+
this.projection.anchor,
|
|
721
|
+
selection.anchorNode,
|
|
722
|
+
selection.anchorOffset
|
|
723
|
+
) && samePoint(
|
|
724
|
+
this.projection.focus,
|
|
725
|
+
selection.focusNode,
|
|
726
|
+
selection.focusOffset
|
|
727
|
+
))
|
|
728
|
+
return;
|
|
729
|
+
if (this.structuralChange) {
|
|
730
|
+
this.schedule();
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (selection.isCollapsed) {
|
|
734
|
+
if (this.structuralChange || this.drag || !this.projection && this.snapshot && (!this.layers.has(this.snapshot.anchor.pageNumber) || !this.layers.has(this.snapshot.focus.pageNumber))) {
|
|
735
|
+
this.schedule();
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (this.snapshot) this.clear();
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const anchor = this.anchorFor(selection.anchorNode, selection.anchorOffset);
|
|
742
|
+
const focus = this.anchorFor(selection.focusNode, selection.focusOffset);
|
|
743
|
+
if (anchor && focus) {
|
|
744
|
+
if (this.drag && !this.drag.started) {
|
|
745
|
+
if (!this.drag.shift && !this.drag.moved) this.drag.anchor = anchor;
|
|
746
|
+
this.drag.started = true;
|
|
747
|
+
}
|
|
748
|
+
const savedAnchor = this.projection && samePoint(
|
|
749
|
+
this.projection.anchor,
|
|
750
|
+
selection.anchorNode,
|
|
751
|
+
selection.anchorOffset
|
|
752
|
+
) ? this.snapshot?.anchor : void 0;
|
|
753
|
+
this.setSelection(this.drag?.anchor ?? savedAnchor ?? anchor, focus);
|
|
754
|
+
this.projection = {
|
|
755
|
+
anchor: { node: selection.anchorNode, offset: selection.anchorOffset },
|
|
756
|
+
focus: { node: selection.focusNode, offset: selection.focusOffset }
|
|
757
|
+
};
|
|
758
|
+
} else if (!this.structuralChange && !this.drag && (selection.anchorNode || selection.focusNode))
|
|
759
|
+
this.clear();
|
|
760
|
+
};
|
|
761
|
+
schedule() {
|
|
762
|
+
if (!this.container || this.frame !== null) return;
|
|
763
|
+
this.frame = this.container.ownerDocument.defaultView.requestAnimationFrame(() => {
|
|
764
|
+
this.frame = null;
|
|
765
|
+
this.project();
|
|
766
|
+
this.structuralChange = false;
|
|
767
|
+
this.flushKeyboard();
|
|
768
|
+
if (this.drag) this.autoScroll();
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
project() {
|
|
772
|
+
const range = this.ordered();
|
|
773
|
+
if (!range || !this.container) return;
|
|
774
|
+
const visible = [...this.layers.values()].filter((page) => this.includesPage(page.pageNumber) && page.nodes.length).sort((a, b) => a.pageNumber - b.pageNumber);
|
|
775
|
+
const first = visible[0];
|
|
776
|
+
const last = visible.at(-1);
|
|
777
|
+
if (!first || !last) return;
|
|
778
|
+
const start = first.point(
|
|
779
|
+
this.bounds(first.pageNumber, first.content.text.length).from
|
|
780
|
+
);
|
|
781
|
+
const end = last.point(
|
|
782
|
+
this.bounds(last.pageNumber, last.content.text.length).to,
|
|
783
|
+
true
|
|
784
|
+
);
|
|
785
|
+
if (!start || !end) return;
|
|
786
|
+
const backwards = compare(this.snapshot.anchor, this.snapshot.focus) > 0;
|
|
787
|
+
const anchor = backwards ? end : start;
|
|
788
|
+
const focus = backwards ? start : end;
|
|
789
|
+
const selection = this.container.ownerDocument.getSelection();
|
|
790
|
+
this.projection = { anchor, focus };
|
|
791
|
+
if (!samePoint(anchor, selection.anchorNode, selection.anchorOffset) || !samePoint(focus, selection.focusNode, selection.focusOffset))
|
|
792
|
+
selection.setBaseAndExtent(
|
|
793
|
+
anchor.node,
|
|
794
|
+
anchor.offset,
|
|
795
|
+
focus.node,
|
|
796
|
+
focus.offset
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
hydrate() {
|
|
800
|
+
if (!this.snapshot || this.complete() || !this.container) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const range = this.ordered();
|
|
804
|
+
const owner = this.container.ownerDocument;
|
|
805
|
+
const controller = new AbortController();
|
|
806
|
+
this.work = controller;
|
|
807
|
+
const generation = this.generation;
|
|
808
|
+
let next = range.start.pageNumber;
|
|
809
|
+
const worker = async () => {
|
|
810
|
+
while (next <= range.end.pageNumber) {
|
|
811
|
+
controller.signal.throwIfAborted();
|
|
812
|
+
const n = next++;
|
|
813
|
+
const cached = this.pages.get(n);
|
|
814
|
+
if (cached) {
|
|
815
|
+
const bounds = this.bounds(n, cached.content.text.length);
|
|
816
|
+
if (bounds.from === cached.from && bounds.to === cached.to) continue;
|
|
817
|
+
}
|
|
818
|
+
const page = await this.options.loadPage(n);
|
|
819
|
+
controller.signal.throwIfAborted();
|
|
820
|
+
await withSelectionPage(
|
|
821
|
+
page,
|
|
822
|
+
owner,
|
|
823
|
+
controller.signal,
|
|
824
|
+
(page2) => this.remember(page2)
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
void Promise.all([worker(), worker()]).then(() => {
|
|
829
|
+
if (!controller.signal.aborted && generation === this.generation) {
|
|
830
|
+
this.finishSelection();
|
|
831
|
+
this.schedule();
|
|
832
|
+
}
|
|
833
|
+
}).catch((error) => {
|
|
834
|
+
if (!controller.signal.aborted && generation === this.generation && this.snapshot) {
|
|
835
|
+
this.publish({ ...this.snapshot, status: "error", error });
|
|
836
|
+
controller.abort();
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
caret(x, y) {
|
|
841
|
+
const owner = this.container.ownerDocument;
|
|
842
|
+
const doc = owner;
|
|
843
|
+
const position = doc.caretPositionFromPoint?.(x, y);
|
|
844
|
+
if (position) return this.anchorFor(position.offsetNode, position.offset);
|
|
845
|
+
const range = doc.caretRangeFromPoint?.(x, y);
|
|
846
|
+
return range ? this.anchorFor(range.startContainer, range.startOffset) : null;
|
|
847
|
+
}
|
|
848
|
+
autoScroll() {
|
|
849
|
+
if (!this.drag?.moved || !this.container) return;
|
|
850
|
+
const bounds = this.container.getBoundingClientRect();
|
|
851
|
+
const { x, y } = this.drag;
|
|
852
|
+
const edge = Math.min(40, bounds.height / 4);
|
|
853
|
+
const delta = y < bounds.top + edge ? -Math.min(24, (bounds.top + edge - y) * 0.5) : y > bounds.bottom - edge ? Math.min(24, (y - bounds.bottom + edge) * 0.5) : 0;
|
|
854
|
+
if (delta) this.container.scrollTop += delta;
|
|
855
|
+
const focus = this.caret(
|
|
856
|
+
Math.max(bounds.left + 1, Math.min(x, bounds.right - 1)),
|
|
857
|
+
Math.max(bounds.top + 1, Math.min(y, bounds.bottom - 1))
|
|
858
|
+
);
|
|
859
|
+
if (focus) this.setSelection(this.drag.anchor, focus);
|
|
860
|
+
if (delta) this.schedule();
|
|
861
|
+
}
|
|
862
|
+
flushKeyboard() {
|
|
863
|
+
if (!this.snapshot || !this.keyboardMoves.length || this.keyboardWork)
|
|
864
|
+
return;
|
|
865
|
+
const page = this.layers.get(this.snapshot.focus.pageNumber);
|
|
866
|
+
if (!page) {
|
|
867
|
+
this.options.revealPage?.(this.snapshot.focus.pageNumber);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const move = this.keyboardMoves.shift();
|
|
871
|
+
const native = this.container.ownerDocument.getSelection();
|
|
872
|
+
if (!native?.modify) return;
|
|
873
|
+
const { anchor, focus } = this.snapshot;
|
|
874
|
+
native.modify("extend", move.direction, move.granularity);
|
|
875
|
+
const next = this.anchorFor(native.focusNode, native.focusOffset);
|
|
876
|
+
if (next && !sameAnchor(next, focus)) {
|
|
877
|
+
this.setSelection(anchor, next);
|
|
878
|
+
} else {
|
|
879
|
+
const point = page.point(focus.offset);
|
|
880
|
+
const rtl = point && this.container.ownerDocument.defaultView.getComputedStyle(
|
|
881
|
+
point.node.parentElement
|
|
882
|
+
).direction === "rtl";
|
|
883
|
+
const backwards = move.direction === "backward" || move.direction === (rtl ? "right" : "left");
|
|
884
|
+
if (move.granularity === "lineboundary" || (backwards ? focus.offset > (move.granularity === "line" ? page.content.lineBreaks[0] ?? 0 : 0) : focus.offset < (move.granularity === "line" ? page.content.lineBreaks.at(-1) ?? page.content.text.length : page.content.text.length))) {
|
|
885
|
+
this.schedule();
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const n = focus.pageNumber + (backwards ? -1 : 1);
|
|
889
|
+
if (n >= 1 && n <= this.options.pageCount) {
|
|
890
|
+
if (!backwards) {
|
|
891
|
+
this.setSelection(anchor, { pageNumber: n, offset: 0 });
|
|
892
|
+
this.options.revealPage?.(n);
|
|
893
|
+
} else {
|
|
894
|
+
const generation = this.generation;
|
|
895
|
+
const controller = new AbortController();
|
|
896
|
+
this.keyboardWork = controller;
|
|
897
|
+
const owner = this.container.ownerDocument;
|
|
898
|
+
void this.options.loadPage(n).then(
|
|
899
|
+
(proxy) => withSelectionPage(
|
|
900
|
+
proxy,
|
|
901
|
+
owner,
|
|
902
|
+
controller.signal,
|
|
903
|
+
(page2) => page2.content.text.length
|
|
904
|
+
)
|
|
905
|
+
).then((length) => {
|
|
906
|
+
if (controller.signal.aborted || generation !== this.generation || !this.container)
|
|
907
|
+
return;
|
|
908
|
+
this.setSelection(anchor, { pageNumber: n, offset: length });
|
|
909
|
+
this.options.revealPage?.(n);
|
|
910
|
+
}).catch(() => {
|
|
911
|
+
}).finally(() => {
|
|
912
|
+
if (this.keyboardWork === controller) this.keyboardWork = null;
|
|
913
|
+
this.schedule();
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (this.keyboardMoves.length) this.schedule();
|
|
919
|
+
}
|
|
920
|
+
connect(container) {
|
|
921
|
+
this.connectedCleanup?.();
|
|
922
|
+
this.container = container;
|
|
923
|
+
const owner = container.ownerDocument;
|
|
924
|
+
const events = new AbortController();
|
|
925
|
+
const { signal } = events;
|
|
926
|
+
const down = (event) => {
|
|
927
|
+
if (event.defaultPrevented) return;
|
|
928
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
929
|
+
if (target?.closest("button, a, [role=button], [data-selection-tooltip]"))
|
|
930
|
+
return;
|
|
931
|
+
if (!container.contains(event.target)) {
|
|
932
|
+
this.clear();
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (event.defaultPrevented || event.button !== 0 || event.pointerType === "touch" || !container.contains(event.target))
|
|
936
|
+
return;
|
|
937
|
+
const point = this.caret(event.clientX, event.clientY);
|
|
938
|
+
if (!point) return;
|
|
939
|
+
const anchor = event.shiftKey && this.snapshot ? this.snapshot.anchor : point;
|
|
940
|
+
if (!event.shiftKey) this.clear();
|
|
941
|
+
this.drag = {
|
|
942
|
+
anchor,
|
|
943
|
+
x: event.clientX,
|
|
944
|
+
y: event.clientY,
|
|
945
|
+
pointerId: event.pointerId,
|
|
946
|
+
moved: false,
|
|
947
|
+
started: false,
|
|
948
|
+
shift: event.shiftKey
|
|
949
|
+
};
|
|
950
|
+
this.schedule();
|
|
951
|
+
};
|
|
952
|
+
const move = (event) => {
|
|
953
|
+
if (!this.drag || event.pointerId !== this.drag.pointerId) return;
|
|
954
|
+
this.drag.moved ||= Math.abs(this.drag.x - event.clientX) + Math.abs(this.drag.y - event.clientY) > 2;
|
|
955
|
+
this.drag.x = event.clientX;
|
|
956
|
+
this.drag.y = event.clientY;
|
|
957
|
+
this.schedule();
|
|
958
|
+
};
|
|
959
|
+
const up = () => {
|
|
960
|
+
this.capture();
|
|
961
|
+
this.drag = null;
|
|
962
|
+
};
|
|
963
|
+
owner.addEventListener("selectionchange", this.capture, { signal });
|
|
964
|
+
owner.addEventListener("pointerdown", down, { signal });
|
|
965
|
+
owner.addEventListener("pointermove", move, { signal });
|
|
966
|
+
owner.addEventListener("pointerup", up, { signal });
|
|
967
|
+
owner.addEventListener("pointercancel", up, { signal });
|
|
968
|
+
owner.defaultView.addEventListener("blur", up, { signal });
|
|
969
|
+
const cancelKeyboard = () => {
|
|
970
|
+
this.keyboardMoves = [];
|
|
971
|
+
this.keyboardWork?.abort();
|
|
972
|
+
this.keyboardWork = null;
|
|
973
|
+
};
|
|
974
|
+
container.addEventListener(
|
|
975
|
+
"focusout",
|
|
976
|
+
(event) => {
|
|
977
|
+
if (!container.contains(event.relatedTarget))
|
|
978
|
+
cancelKeyboard();
|
|
979
|
+
},
|
|
980
|
+
{ signal }
|
|
981
|
+
);
|
|
982
|
+
owner.defaultView.addEventListener("blur", cancelKeyboard, { signal });
|
|
983
|
+
container.addEventListener(
|
|
984
|
+
"keydown",
|
|
985
|
+
(event) => {
|
|
986
|
+
if (event.defaultPrevented || event.target instanceof Element && event.target.closest(
|
|
987
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
988
|
+
))
|
|
989
|
+
return;
|
|
990
|
+
if (event.key === "Escape" && this.snapshot) {
|
|
991
|
+
this.clear();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (!event.shiftKey || !this.snapshot) return;
|
|
995
|
+
const directions = {
|
|
996
|
+
ArrowLeft: "left",
|
|
997
|
+
ArrowRight: "right",
|
|
998
|
+
ArrowUp: "backward",
|
|
999
|
+
ArrowDown: "forward",
|
|
1000
|
+
Home: "backward",
|
|
1001
|
+
End: "forward"
|
|
1002
|
+
};
|
|
1003
|
+
const direction = directions[event.key];
|
|
1004
|
+
if (!direction) return;
|
|
1005
|
+
const native = owner.getSelection();
|
|
1006
|
+
if (!native?.modify) return;
|
|
1007
|
+
event.preventDefault();
|
|
1008
|
+
const boundary = event.key === "Home" || event.key === "End" || event.metaKey && (event.key === "ArrowLeft" || event.key === "ArrowRight");
|
|
1009
|
+
const granularity = boundary ? "lineboundary" : event.key === "ArrowUp" || event.key === "ArrowDown" ? "line" : event.ctrlKey || event.altKey ? "word" : "character";
|
|
1010
|
+
this.keyboardMoves.push({ direction, granularity });
|
|
1011
|
+
this.schedule();
|
|
1012
|
+
},
|
|
1013
|
+
{ signal }
|
|
1014
|
+
);
|
|
1015
|
+
const cleanup = () => {
|
|
1016
|
+
events.abort();
|
|
1017
|
+
if (this.frame !== null)
|
|
1018
|
+
owner.defaultView.cancelAnimationFrame(this.frame);
|
|
1019
|
+
this.frame = null;
|
|
1020
|
+
this.clear();
|
|
1021
|
+
this.container = null;
|
|
1022
|
+
this.connectedCleanup = null;
|
|
1023
|
+
};
|
|
1024
|
+
this.connectedCleanup = cleanup;
|
|
1025
|
+
this.hydrate();
|
|
1026
|
+
this.schedule();
|
|
1027
|
+
return cleanup;
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
|
|
222
1031
|
// src/lib/viewport-zoom.ts
|
|
223
1032
|
var viewportZoomHandlers = /* @__PURE__ */ new WeakMap();
|
|
224
1033
|
function applyViewportZoom(viewport, zoom) {
|
|
@@ -268,6 +1077,11 @@ var PDFStore = createZustandContext(
|
|
|
268
1077
|
renderColorMapRef.current = initialColorScheme === "dark" ? createDarkModeColorMap(initialDarkModeColors) : null;
|
|
269
1078
|
return createStore((set, get) => ({
|
|
270
1079
|
pdfDocumentProxy: initialState.pdfDocumentProxy,
|
|
1080
|
+
selection: new PDFSelectionController({
|
|
1081
|
+
pageCount: initialState.pdfDocumentProxy.numPages,
|
|
1082
|
+
loadPage: (page) => get().loadPdfPageProxy(page),
|
|
1083
|
+
revealPage: (page) => get().virtualizer?.scrollToIndex(page - 1, { align: "auto" })
|
|
1084
|
+
}),
|
|
271
1085
|
zoom: initialState.zoom,
|
|
272
1086
|
isZoomFitWidth: initialState.isZoomFitWidth ?? false,
|
|
273
1087
|
zoomOptions: {
|
|
@@ -3104,6 +3918,114 @@ var mergeTextRuns = (rects) => {
|
|
|
3104
3918
|
});
|
|
3105
3919
|
};
|
|
3106
3920
|
|
|
3921
|
+
// src/lib/selection-text.ts
|
|
3922
|
+
function textLayer(node) {
|
|
3923
|
+
const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
3924
|
+
return element?.closest(".textLayer[data-page-number]") ?? null;
|
|
3925
|
+
}
|
|
3926
|
+
function getPdfSelectionText(selection, container, options = {}) {
|
|
3927
|
+
if (!selection || selection.isCollapsed || selection.rangeCount !== 1)
|
|
3928
|
+
return null;
|
|
3929
|
+
const range = selection.getRangeAt(0);
|
|
3930
|
+
const first = textLayer(range.startContainer);
|
|
3931
|
+
const last = textLayer(range.endContainer);
|
|
3932
|
+
if (!first || !last || !container.contains(first) || !container.contains(last))
|
|
3933
|
+
return null;
|
|
3934
|
+
const layers = [
|
|
3935
|
+
...container.querySelectorAll(".textLayer[data-page-number]")
|
|
3936
|
+
].filter((layer) => range.intersectsNode(layer));
|
|
3937
|
+
let previousPage;
|
|
3938
|
+
for (const layer of layers) {
|
|
3939
|
+
const page = Number(layer.dataset.pageNumber);
|
|
3940
|
+
if (!Number.isInteger(page) || page < 1 || previousPage !== void 0 && page !== previousPage + 1)
|
|
3941
|
+
return null;
|
|
3942
|
+
previousPage = page;
|
|
3943
|
+
}
|
|
3944
|
+
const selectedLayers = new Set(layers);
|
|
3945
|
+
const root = range.commonAncestorContainer;
|
|
3946
|
+
const walker = container.ownerDocument.createTreeWalker(
|
|
3947
|
+
root,
|
|
3948
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
|
3949
|
+
{
|
|
3950
|
+
acceptNode(node2) {
|
|
3951
|
+
if (node2.nodeType !== Node.TEXT_NODE && node2.tagName !== "BR")
|
|
3952
|
+
return NodeFilter.FILTER_SKIP;
|
|
3953
|
+
const layer = textLayer(node2);
|
|
3954
|
+
return layer && selectedLayers.has(layer) && range.intersectsNode(node2) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
);
|
|
3958
|
+
const chunks = [];
|
|
3959
|
+
let previousLayer = null;
|
|
3960
|
+
let trailingLineBreaks = 0;
|
|
3961
|
+
let node = root.nodeType === Node.TEXT_NODE ? root : walker.nextNode();
|
|
3962
|
+
while (node) {
|
|
3963
|
+
const layer = textLayer(node);
|
|
3964
|
+
const start = node === range.startContainer ? range.startOffset : 0;
|
|
3965
|
+
const end = node === range.endContainer ? range.endOffset : node.textContent?.length ?? 0;
|
|
3966
|
+
const text = node.nodeType === Node.TEXT_NODE ? node.textContent.slice(start, end) : "\n";
|
|
3967
|
+
if (text) {
|
|
3968
|
+
if (previousLayer && previousLayer !== layer && trailingLineBreaks < 2) {
|
|
3969
|
+
chunks.push("\n".repeat(2 - trailingLineBreaks));
|
|
3970
|
+
trailingLineBreaks = 2;
|
|
3971
|
+
}
|
|
3972
|
+
chunks.push(text);
|
|
3973
|
+
let trailing = 0;
|
|
3974
|
+
for (let i = text.length - 1; i >= 0 && text[i] === "\n"; i--) trailing++;
|
|
3975
|
+
trailingLineBreaks = trailing === text.length ? trailingLineBreaks + trailing : trailing;
|
|
3976
|
+
previousLayer = layer;
|
|
3977
|
+
}
|
|
3978
|
+
node = walker.nextNode();
|
|
3979
|
+
}
|
|
3980
|
+
return normalizeSelectionText(chunks.join(""), options);
|
|
3981
|
+
}
|
|
3982
|
+
var documents = /* @__PURE__ */ new WeakMap();
|
|
3983
|
+
function registerPdfCopy(container, options, read) {
|
|
3984
|
+
const owner = container.ownerDocument;
|
|
3985
|
+
let binding = documents.get(owner);
|
|
3986
|
+
if (!binding) {
|
|
3987
|
+
const registrations = /* @__PURE__ */ new Set();
|
|
3988
|
+
const listener = (event) => {
|
|
3989
|
+
if (event.defaultPrevented || !event.clipboardData) return;
|
|
3990
|
+
const target = event.target;
|
|
3991
|
+
if (target instanceof Element && target.closest(
|
|
3992
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
3993
|
+
))
|
|
3994
|
+
return;
|
|
3995
|
+
for (const registration2 of registrations) {
|
|
3996
|
+
const settings = registration2.options();
|
|
3997
|
+
if (settings === false) continue;
|
|
3998
|
+
const persistent = registration2.read?.(settings);
|
|
3999
|
+
if (registration2.read && persistent === void 0) {
|
|
4000
|
+
event.preventDefault();
|
|
4001
|
+
break;
|
|
4002
|
+
}
|
|
4003
|
+
const text = persistent ?? getPdfSelectionText(
|
|
4004
|
+
owner.getSelection(),
|
|
4005
|
+
registration2.container,
|
|
4006
|
+
settings
|
|
4007
|
+
);
|
|
4008
|
+
if (text === null) continue;
|
|
4009
|
+
event.clipboardData.setData("text/plain", text);
|
|
4010
|
+
event.preventDefault();
|
|
4011
|
+
break;
|
|
4012
|
+
}
|
|
4013
|
+
};
|
|
4014
|
+
binding = { registrations, listener };
|
|
4015
|
+
documents.set(owner, binding);
|
|
4016
|
+
owner.addEventListener("copy", listener);
|
|
4017
|
+
}
|
|
4018
|
+
const registration = { container, options, read };
|
|
4019
|
+
binding.registrations.add(registration);
|
|
4020
|
+
return () => {
|
|
4021
|
+
binding.registrations.delete(registration);
|
|
4022
|
+
if (!binding.registrations.size) {
|
|
4023
|
+
owner.removeEventListener("copy", binding.listener);
|
|
4024
|
+
documents.delete(owner);
|
|
4025
|
+
}
|
|
4026
|
+
};
|
|
4027
|
+
}
|
|
4028
|
+
|
|
3107
4029
|
// src/hooks/useSelectionDimensions.tsx
|
|
3108
4030
|
var MERGE_THRESHOLD = 2;
|
|
3109
4031
|
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
@@ -3151,7 +4073,19 @@ var mapSelectionRectsToLayers = (range) => {
|
|
|
3151
4073
|
};
|
|
3152
4074
|
var useSelectionDimensions = () => {
|
|
3153
4075
|
const store = PDFStore.useContext();
|
|
4076
|
+
const getText = (options) => {
|
|
4077
|
+
if (store.getState().selection?.getSnapshot())
|
|
4078
|
+
return store.getState().selection.getText(options);
|
|
4079
|
+
const container = store.getState().viewportRef.current;
|
|
4080
|
+
return container ? getPdfSelectionText(
|
|
4081
|
+
container.ownerDocument.getSelection(),
|
|
4082
|
+
container,
|
|
4083
|
+
options
|
|
4084
|
+
) : null;
|
|
4085
|
+
};
|
|
3154
4086
|
const getAnnotationDimension = () => {
|
|
4087
|
+
if (store.getState().selection?.getSnapshot())
|
|
4088
|
+
return store.getState().selection.getSelection() ?? void 0;
|
|
3155
4089
|
const selection = window.getSelection();
|
|
3156
4090
|
if (!selection || selection.isCollapsed) return;
|
|
3157
4091
|
const range = selection.getRangeAt(0);
|
|
@@ -3289,7 +4223,7 @@ var useSelectionDimensions = () => {
|
|
|
3289
4223
|
underlines: consolidateUnderlines(underlineRects).sort(
|
|
3290
4224
|
(a, b) => a.pageNumber - b.pageNumber
|
|
3291
4225
|
),
|
|
3292
|
-
text: range.toString().trim(),
|
|
4226
|
+
text: (getText() ?? range.toString()).trim(),
|
|
3293
4227
|
isCollapsed: false
|
|
3294
4228
|
};
|
|
3295
4229
|
};
|
|
@@ -3346,6 +4280,8 @@ var useSelectionDimensions = () => {
|
|
|
3346
4280
|
return consolidated;
|
|
3347
4281
|
};
|
|
3348
4282
|
const getDimension = () => {
|
|
4283
|
+
if (store.getState().selection?.getSnapshot())
|
|
4284
|
+
return store.getState().selection.getSelection() ?? void 0;
|
|
3349
4285
|
const selection = window.getSelection();
|
|
3350
4286
|
if (!selection || selection.isCollapsed) return;
|
|
3351
4287
|
const range = selection.getRangeAt(0);
|
|
@@ -3374,12 +4310,12 @@ var useSelectionDimensions = () => {
|
|
|
3374
4310
|
});
|
|
3375
4311
|
return {
|
|
3376
4312
|
highlights: highlights.sort((a, b) => a.pageNumber - b.pageNumber),
|
|
3377
|
-
text: range.toString().trim(),
|
|
4313
|
+
text: (getText() ?? range.toString()).trim(),
|
|
3378
4314
|
isCollapsed: false
|
|
3379
4315
|
};
|
|
3380
4316
|
};
|
|
3381
4317
|
const getSelection = () => getDimension();
|
|
3382
|
-
return { getDimension, getSelection, getAnnotationDimension };
|
|
4318
|
+
return { getDimension, getSelection, getAnnotationDimension, getText };
|
|
3383
4319
|
};
|
|
3384
4320
|
var SelectionTooltip = ({ children }) => {
|
|
3385
4321
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -3514,6 +4450,7 @@ var SelectionTooltip = ({ children }) => {
|
|
|
3514
4450
|
return /* @__PURE__ */ jsx(Fragment, { children: isOpen && /* @__PURE__ */ jsx(
|
|
3515
4451
|
"div",
|
|
3516
4452
|
{
|
|
4453
|
+
"data-selection-tooltip": "",
|
|
3517
4454
|
ref: refs.setFloating,
|
|
3518
4455
|
style: {
|
|
3519
4456
|
...floatingStyles
|
|
@@ -3929,13 +4866,13 @@ var createTextSelectionManager = () => {
|
|
|
3929
4866
|
}
|
|
3930
4867
|
selectionChangeAbortController = new AbortController();
|
|
3931
4868
|
const { signal } = selectionChangeAbortController;
|
|
3932
|
-
const reset = (endDiv,
|
|
3933
|
-
if (endDiv.parentNode !==
|
|
3934
|
-
|
|
4869
|
+
const reset = (endDiv, textLayer2) => {
|
|
4870
|
+
if (endDiv.parentNode !== textLayer2) {
|
|
4871
|
+
textLayer2.appendChild(endDiv);
|
|
3935
4872
|
}
|
|
3936
4873
|
endDiv.style.width = "";
|
|
3937
4874
|
endDiv.style.height = "";
|
|
3938
|
-
|
|
4875
|
+
textLayer2.classList.remove("selecting");
|
|
3939
4876
|
};
|
|
3940
4877
|
document.addEventListener(
|
|
3941
4878
|
"pointerdown",
|
|
@@ -4061,12 +4998,14 @@ var useTextLayer = () => {
|
|
|
4061
4998
|
const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
|
|
4062
4999
|
const viewportRef = usePdf((state) => state.viewportRef);
|
|
4063
5000
|
const store = PDFStore.useContext();
|
|
5001
|
+
const selection = store.getState().selection;
|
|
4064
5002
|
useEffect(() => {
|
|
4065
5003
|
const textContainer = textContainerRef.current;
|
|
4066
5004
|
if (!textContainer) {
|
|
4067
5005
|
return;
|
|
4068
5006
|
}
|
|
4069
5007
|
let isCancelled = false;
|
|
5008
|
+
let unregisterSelection;
|
|
4070
5009
|
let buildTimer = null;
|
|
4071
5010
|
let unsubscribe = null;
|
|
4072
5011
|
let built = false;
|
|
@@ -4083,13 +5022,13 @@ var useTextLayer = () => {
|
|
|
4083
5022
|
void import('pdfjs-dist/legacy/build/pdf.mjs').then(({ TextLayer: TextLayer2 }) => {
|
|
4084
5023
|
if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer)
|
|
4085
5024
|
return;
|
|
4086
|
-
const
|
|
5025
|
+
const textLayer2 = new TextLayer2({
|
|
4087
5026
|
textContentSource: pdfPageProxy.streamTextContent(),
|
|
4088
5027
|
container: textContainer,
|
|
4089
5028
|
viewport: pdfPageProxy.getViewport({ scale: 1 })
|
|
4090
5029
|
});
|
|
4091
|
-
textLayerRef.current =
|
|
4092
|
-
return
|
|
5030
|
+
textLayerRef.current = textLayer2;
|
|
5031
|
+
return textLayer2.render();
|
|
4093
5032
|
}).then(() => {
|
|
4094
5033
|
if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer) {
|
|
4095
5034
|
return;
|
|
@@ -4098,6 +5037,16 @@ var useTextLayer = () => {
|
|
|
4098
5037
|
endOfContent.className = "endOfContent";
|
|
4099
5038
|
textContainer.appendChild(endOfContent);
|
|
4100
5039
|
bindMouseEvents(textContainer, endOfContent);
|
|
5040
|
+
const viewport = pdfPageProxy.getViewport({ scale: 1 });
|
|
5041
|
+
const unrotated = pdfPageProxy.getViewport({ scale: 1, rotation: 0 });
|
|
5042
|
+
textContainer.style.width = `${unrotated.width}px`;
|
|
5043
|
+
textContainer.style.height = `${unrotated.height}px`;
|
|
5044
|
+
unregisterSelection = selection?.registerLayer(
|
|
5045
|
+
textContainer,
|
|
5046
|
+
pageNumber,
|
|
5047
|
+
viewport.width,
|
|
5048
|
+
viewport.height
|
|
5049
|
+
);
|
|
4101
5050
|
built = true;
|
|
4102
5051
|
building = false;
|
|
4103
5052
|
unsubscribe?.();
|
|
@@ -4111,6 +5060,12 @@ var useTextLayer = () => {
|
|
|
4111
5060
|
};
|
|
4112
5061
|
const schedule = () => {
|
|
4113
5062
|
if (built) return;
|
|
5063
|
+
if (selection?.isDragging || selection?.includesPage(pageNumber)) {
|
|
5064
|
+
if (buildTimer) clearTimeout(buildTimer);
|
|
5065
|
+
buildTimer = null;
|
|
5066
|
+
if (!building) build();
|
|
5067
|
+
return;
|
|
5068
|
+
}
|
|
4114
5069
|
if (building) {
|
|
4115
5070
|
buildGen++;
|
|
4116
5071
|
building = false;
|
|
@@ -4140,9 +5095,15 @@ var useTextLayer = () => {
|
|
|
4140
5095
|
unsubscribe = subscribeToViewportInvalidation(scrollViewport, schedule);
|
|
4141
5096
|
};
|
|
4142
5097
|
attachScrollSubscription();
|
|
5098
|
+
const unsubscribeSelection = selection?.subscribe(() => {
|
|
5099
|
+
if (selection.isDragging || selection.includesPage(pageNumber))
|
|
5100
|
+
schedule();
|
|
5101
|
+
});
|
|
4143
5102
|
schedule();
|
|
4144
5103
|
return () => {
|
|
4145
5104
|
isCancelled = true;
|
|
5105
|
+
unregisterSelection?.();
|
|
5106
|
+
unsubscribeSelection?.();
|
|
4146
5107
|
if (buildTimer) clearTimeout(buildTimer);
|
|
4147
5108
|
if (attachRaf !== null) cancelAnimationFrame(attachRaf);
|
|
4148
5109
|
unsubscribe?.();
|
|
@@ -4155,7 +5116,13 @@ var useTextLayer = () => {
|
|
|
4155
5116
|
delete textContainer._cleanupTextSelection;
|
|
4156
5117
|
}
|
|
4157
5118
|
};
|
|
4158
|
-
}, [
|
|
5119
|
+
}, [
|
|
5120
|
+
pdfPageProxy.streamTextContent,
|
|
5121
|
+
pdfPageProxy.getViewport,
|
|
5122
|
+
viewportRef,
|
|
5123
|
+
selection,
|
|
5124
|
+
pageNumber
|
|
5125
|
+
]);
|
|
4159
5126
|
useEffect(() => {
|
|
4160
5127
|
const textContainer = textContainerRef.current;
|
|
4161
5128
|
if (!textContainer) return;
|
|
@@ -4174,7 +5141,10 @@ var useTextLayer = () => {
|
|
|
4174
5141
|
show();
|
|
4175
5142
|
};
|
|
4176
5143
|
const hideAndDebounce = () => {
|
|
4177
|
-
if (selecting)
|
|
5144
|
+
if (selecting || selection?.isDragging || selection?.includesPage(pageNumber)) {
|
|
5145
|
+
show();
|
|
5146
|
+
return;
|
|
5147
|
+
}
|
|
4178
5148
|
textContainer.style.visibility = "hidden";
|
|
4179
5149
|
if (settleTimer) clearTimeout(settleTimer);
|
|
4180
5150
|
settleTimer = setTimeout(restoreWhenSettled, TEXT_SCROLL_SETTLE_MS);
|
|
@@ -4242,7 +5212,7 @@ var useTextLayer = () => {
|
|
|
4242
5212
|
if (settleTimer) clearTimeout(settleTimer);
|
|
4243
5213
|
show();
|
|
4244
5214
|
};
|
|
4245
|
-
}, [viewportRef, store]);
|
|
5215
|
+
}, [viewportRef, store, selection, pageNumber]);
|
|
4246
5216
|
return {
|
|
4247
5217
|
textContainerRef,
|
|
4248
5218
|
pageNumber: pdfPageProxy.pageNumber
|
|
@@ -6988,16 +7958,34 @@ var Pages = ({
|
|
|
6988
7958
|
virtualizerOptions = DEFAULT_VIRTUALIZER_OPTIONS,
|
|
6989
7959
|
initialOffset,
|
|
6990
7960
|
onOffsetChange,
|
|
7961
|
+
copyOptions,
|
|
6991
7962
|
...props
|
|
6992
7963
|
}) => {
|
|
6993
7964
|
const [tempItems, setTempItems] = useState([]);
|
|
6994
7965
|
const viewports = usePdf((state) => state.viewports);
|
|
7966
|
+
const selection = usePdf((state) => state.selection);
|
|
6995
7967
|
const numPages = usePdf((state) => state.pdfDocumentProxy.numPages);
|
|
6996
7968
|
const initialPage = usePdf((state) => state.initialPage);
|
|
6997
7969
|
const isPinching = usePdf((state) => state.isPinching);
|
|
6998
7970
|
const elementWrapperRef = useRef(null);
|
|
6999
7971
|
const elementRef = useRef(null);
|
|
7000
7972
|
const containerRef = useRef(null);
|
|
7973
|
+
const copyOptionsRef = useRef(copyOptions);
|
|
7974
|
+
copyOptionsRef.current = copyOptions;
|
|
7975
|
+
useEffect(() => {
|
|
7976
|
+
const container = containerRef.current;
|
|
7977
|
+
if (!container) return;
|
|
7978
|
+
const disconnect = selection.connect(container);
|
|
7979
|
+
const unregisterCopy = registerPdfCopy(
|
|
7980
|
+
container,
|
|
7981
|
+
() => copyOptionsRef.current ?? {},
|
|
7982
|
+
(options) => selection.getSnapshot() ? selection.getText(options) ?? void 0 : null
|
|
7983
|
+
);
|
|
7984
|
+
return () => {
|
|
7985
|
+
unregisterCopy();
|
|
7986
|
+
disconnect();
|
|
7987
|
+
};
|
|
7988
|
+
}, [selection]);
|
|
7001
7989
|
useViewportContainer({
|
|
7002
7990
|
elementRef,
|
|
7003
7991
|
elementWrapperRef,
|
|
@@ -7115,6 +8103,7 @@ var Pages = ({
|
|
|
7115
8103
|
Primitive.div,
|
|
7116
8104
|
{
|
|
7117
8105
|
ref: containerRef,
|
|
8106
|
+
tabIndex: 0,
|
|
7118
8107
|
...props,
|
|
7119
8108
|
style: {
|
|
7120
8109
|
display: "flex",
|
|
@@ -7759,6 +8748,8 @@ function acquireDocumentText(pages) {
|
|
|
7759
8748
|
const page = pages[index];
|
|
7760
8749
|
const reader = page.streamTextContent().getReader();
|
|
7761
8750
|
const chunks = [];
|
|
8751
|
+
const lineBreaks = [];
|
|
8752
|
+
let offset3 = 0;
|
|
7762
8753
|
let complete = false;
|
|
7763
8754
|
try {
|
|
7764
8755
|
while (true) {
|
|
@@ -7768,9 +8759,13 @@ function acquireDocumentText(pages) {
|
|
|
7768
8759
|
complete = true;
|
|
7769
8760
|
break;
|
|
7770
8761
|
}
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
8762
|
+
for (const item of value.items) {
|
|
8763
|
+
if (!("str" in item)) continue;
|
|
8764
|
+
chunks.push(item.str);
|
|
8765
|
+
offset3 += item.str.length;
|
|
8766
|
+
if (item.hasEOL && lineBreaks.at(-1) !== offset3)
|
|
8767
|
+
lineBreaks.push(offset3);
|
|
8768
|
+
}
|
|
7774
8769
|
}
|
|
7775
8770
|
} finally {
|
|
7776
8771
|
if (!complete)
|
|
@@ -7778,7 +8773,11 @@ function acquireDocumentText(pages) {
|
|
|
7778
8773
|
});
|
|
7779
8774
|
reader.releaseLock();
|
|
7780
8775
|
}
|
|
7781
|
-
result[index] = {
|
|
8776
|
+
result[index] = {
|
|
8777
|
+
pageNumber: page.pageNumber,
|
|
8778
|
+
text: chunks.join(""),
|
|
8779
|
+
...lineBreaks.length ? { lineBreaks } : {}
|
|
8780
|
+
};
|
|
7782
8781
|
}
|
|
7783
8782
|
};
|
|
7784
8783
|
const created = {
|
|
@@ -8187,33 +9186,31 @@ var CurrentZoom = ({ ...props }) => {
|
|
|
8187
9186
|
|
|
8188
9187
|
// src/lib/search.ts
|
|
8189
9188
|
var normalizedPages = /* @__PURE__ */ new WeakMap();
|
|
8190
|
-
function normalize(page) {
|
|
8191
|
-
|
|
8192
|
-
if (cached?.text
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
original += character.length;
|
|
8207
|
-
normalized += length;
|
|
8208
|
-
}
|
|
8209
|
-
}
|
|
8210
|
-
normalizedPages.set(page, result);
|
|
9189
|
+
function normalize(page, options) {
|
|
9190
|
+
let cached = normalizedPages.get(page);
|
|
9191
|
+
if (cached?.text !== page.text || cached.lineBreaks !== page.lineBreaks) {
|
|
9192
|
+
cached = {
|
|
9193
|
+
text: page.text,
|
|
9194
|
+
lineBreaks: page.lineBreaks,
|
|
9195
|
+
variants: /* @__PURE__ */ new Map()
|
|
9196
|
+
};
|
|
9197
|
+
normalizedPages.set(page, cached);
|
|
9198
|
+
}
|
|
9199
|
+
const key = (options.matchDiacritics === false ? 1 : 0) | (options.ignoreHyphenation ? 2 : 0);
|
|
9200
|
+
let result = cached.variants.get(key);
|
|
9201
|
+
if (!result) {
|
|
9202
|
+
result = normalizeSearchText(page.text, options, page.lineBreaks);
|
|
9203
|
+
cached.variants.set(key, result);
|
|
9204
|
+
}
|
|
8211
9205
|
return result;
|
|
8212
9206
|
}
|
|
8213
9207
|
function createMatch(page, normalized, start, length, searchText, textSize, score) {
|
|
8214
|
-
const end = Math.min(normalized.
|
|
8215
|
-
const matchIndex
|
|
8216
|
-
|
|
9208
|
+
const end = Math.min(normalized.text.length, start + length);
|
|
9209
|
+
const { start: matchIndex, end: matchEnd } = originalTextRange(
|
|
9210
|
+
normalized,
|
|
9211
|
+
start,
|
|
9212
|
+
end
|
|
9213
|
+
);
|
|
8217
9214
|
const matchLength = matchEnd - matchIndex;
|
|
8218
9215
|
return {
|
|
8219
9216
|
pageNumber: page.pageNumber,
|
|
@@ -8276,7 +9273,8 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
8276
9273
|
const threshold = Math.max(0, Math.min(1, options.threshold ?? 0.7));
|
|
8277
9274
|
const limit = Number.isFinite(options.limit) ? Math.max(0, Math.floor(options.limit)) : 10;
|
|
8278
9275
|
const textSize = options.textSize ?? 100;
|
|
8279
|
-
const query = searchText.
|
|
9276
|
+
const query = normalizeSearchText(searchText, options).text;
|
|
9277
|
+
if (!query.trim()) return empty;
|
|
8280
9278
|
const maxDistance = Math.floor(query.length * (1 - threshold));
|
|
8281
9279
|
const distance = maxDistance > 0 ? createBoundedDistance(query, maxDistance) : null;
|
|
8282
9280
|
const exactMatches = [];
|
|
@@ -8286,10 +9284,12 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
8286
9284
|
let steps = 0;
|
|
8287
9285
|
let deadline = cooperative ? performance.now() + budget : 0;
|
|
8288
9286
|
for (const page of pages) {
|
|
8289
|
-
const normalized = normalize(page);
|
|
8290
|
-
const lower = normalized.
|
|
9287
|
+
const normalized = normalize(page, options);
|
|
9288
|
+
const lower = normalized.text;
|
|
8291
9289
|
const exactStarts = [];
|
|
8292
9290
|
let index = 0;
|
|
9291
|
+
let lastStart = -1;
|
|
9292
|
+
let lastEnd = -1;
|
|
8293
9293
|
while (true) {
|
|
8294
9294
|
if (cooperative && (++steps & 127) === 0 && performance.now() >= deadline) {
|
|
8295
9295
|
yield;
|
|
@@ -8297,8 +9297,17 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
8297
9297
|
}
|
|
8298
9298
|
const matchIndex = lower.indexOf(query, index);
|
|
8299
9299
|
if (matchIndex === -1) break;
|
|
8300
|
-
|
|
9300
|
+
const span = originalTextRange(
|
|
9301
|
+
normalized,
|
|
9302
|
+
matchIndex,
|
|
9303
|
+
matchIndex + query.length
|
|
9304
|
+
);
|
|
9305
|
+
index = matchIndex + query.length;
|
|
8301
9306
|
if (maxDistance > 0) exactStarts.push(matchIndex);
|
|
9307
|
+
if (span.start === lastStart && span.end === lastEnd) continue;
|
|
9308
|
+
lastStart = span.start;
|
|
9309
|
+
lastEnd = span.end;
|
|
9310
|
+
exactCount++;
|
|
8302
9311
|
if (exactMatches.length < limit) {
|
|
8303
9312
|
exactMatches.push(
|
|
8304
9313
|
createMatch(
|
|
@@ -8579,5 +9588,24 @@ function mergeAdjacentRects(rects) {
|
|
|
8579
9588
|
|
|
8580
9589
|
// src/hooks/usePageRendered.ts
|
|
8581
9590
|
var usePageRendered = (pageNumber) => usePdf((state) => !!state.renderedPages[pageNumber]);
|
|
9591
|
+
function usePdfSelection() {
|
|
9592
|
+
const controller = usePdf((state) => state.selection);
|
|
9593
|
+
const selection = useSyncExternalStore(
|
|
9594
|
+
controller.subscribe,
|
|
9595
|
+
controller.getSnapshot,
|
|
9596
|
+
controller.getSnapshot
|
|
9597
|
+
);
|
|
9598
|
+
return useMemo(
|
|
9599
|
+
() => ({
|
|
9600
|
+
selection,
|
|
9601
|
+
setSelection: controller.setSelection,
|
|
9602
|
+
clearSelection: controller.clear,
|
|
9603
|
+
getText: controller.getText,
|
|
9604
|
+
getSelection: controller.getSelection,
|
|
9605
|
+
getSelectionAsync: controller.getSelectionAsync
|
|
9606
|
+
}),
|
|
9607
|
+
[controller, selection]
|
|
9608
|
+
);
|
|
9609
|
+
}
|
|
8582
9610
|
|
|
8583
|
-
export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, AnnotationsStoreProvider, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, DEFAULT_DARK_MODE_COLORS, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, createDarkModeColorMap, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageAnnotations, usePageRendered, usePdf, usePdfJump, useSearch, useSelectionDimensions };
|
|
9611
|
+
export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, AnnotationsStoreProvider, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, DEFAULT_DARK_MODE_COLORS, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, createDarkModeColorMap, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageAnnotations, usePageRendered, usePdf, usePdfJump, usePdfSelection, useSearch, useSelectionDimensions };
|