@anaralabs/lector 3.14.13 → 3.15.1
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 +1136 -54
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -219,6 +219,825 @@ 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
|
+
for (const match of text.matchAll(/\p{ASCII}?\P{ASCII}+/gu)) {
|
|
405
|
+
if (normalizeUnicode(match[0], matchDiacritics) === match[0]) continue;
|
|
406
|
+
const prefix = match[0].charCodeAt(0) < 128 ? 1 : 0;
|
|
407
|
+
const code = match[0].charCodeAt(prefix);
|
|
408
|
+
if (match[0].length === prefix + 1 && code >= 64256 && code <= 64262) {
|
|
409
|
+
yield {
|
|
410
|
+
index: match.index + prefix,
|
|
411
|
+
length: 1,
|
|
412
|
+
text: ligatures[code - 64256]
|
|
413
|
+
};
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
segmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
417
|
+
for (const { segment, index } of segmenter.segment(match[0])) {
|
|
418
|
+
yield {
|
|
419
|
+
index: match.index + index,
|
|
420
|
+
length: segment.length,
|
|
421
|
+
text: normalizeUnicode(segment, matchDiacritics)
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function normalizeSearchText(text, options = {}, lineBreaks = []) {
|
|
427
|
+
let result = { text, maps: [] };
|
|
428
|
+
if (lineBreaks.length)
|
|
429
|
+
result = replace(result, insertLineBreaks(text, lineBreaks));
|
|
430
|
+
const matchDiacritics = options.matchDiacritics !== false;
|
|
431
|
+
if (normalizeUnicode(result.text, matchDiacritics) !== result.text) {
|
|
432
|
+
result = replace(result, unicodeReplacements(result.text, matchDiacritics));
|
|
433
|
+
}
|
|
434
|
+
result = replace(result, replacements(result.text, softHyphen, ""));
|
|
435
|
+
if (options.ignoreHyphenation) {
|
|
436
|
+
result = replace(result, replacements(result.text, wrappedHyphen, ""));
|
|
437
|
+
}
|
|
438
|
+
result = replace(result, replacements(result.text, cjkLineBreak, ""));
|
|
439
|
+
result = replace(result, replacements(result.text, /\s+/gu, " "));
|
|
440
|
+
const lower = result.text.toLowerCase();
|
|
441
|
+
if (lower.length !== result.text.length) {
|
|
442
|
+
function* caseReplacements(value) {
|
|
443
|
+
let index = 0;
|
|
444
|
+
for (const char of value) {
|
|
445
|
+
const folded = char.toLowerCase();
|
|
446
|
+
if (folded.length !== char.length)
|
|
447
|
+
yield { index, length: char.length, text: folded };
|
|
448
|
+
index += char.length;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
result = replace(result, caseReplacements(result.text));
|
|
452
|
+
}
|
|
453
|
+
return { ...result, text: lower };
|
|
454
|
+
}
|
|
455
|
+
function sourceOffset(edits, offset3, end) {
|
|
456
|
+
let low = 0;
|
|
457
|
+
let high = edits.length;
|
|
458
|
+
while (low < high) {
|
|
459
|
+
const middle = low + high >>> 1;
|
|
460
|
+
if (edits[middle].start <= offset3) low = middle + 1;
|
|
461
|
+
else high = middle;
|
|
462
|
+
}
|
|
463
|
+
const edit = edits[low - 1];
|
|
464
|
+
if (!edit) return offset3 + (end ? 1 : 0);
|
|
465
|
+
if (offset3 < edit.end) return end ? edit.sourceEnd : edit.sourceStart;
|
|
466
|
+
return offset3 + edit.sourceEnd - edit.end + (end ? 1 : 0);
|
|
467
|
+
}
|
|
468
|
+
function originalTextRange(normalized, start, end) {
|
|
469
|
+
for (let i = normalized.maps.length - 1; i >= 0; i--) {
|
|
470
|
+
const map = normalized.maps[i];
|
|
471
|
+
start = sourceOffset(map, start, false);
|
|
472
|
+
end = sourceOffset(map, end - 1, true);
|
|
473
|
+
}
|
|
474
|
+
return { start, end };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/lib/pdf-selection.ts
|
|
478
|
+
var sameAnchor = (a, b) => a.pageNumber === b.pageNumber && a.offset === b.offset;
|
|
479
|
+
var compare = (a, b) => a.pageNumber - b.pageNumber || a.offset - b.offset;
|
|
480
|
+
var samePoint = (a, node, offset3) => a.node === node && a.offset === offset3;
|
|
481
|
+
var selectionOwners = /* @__PURE__ */ new WeakMap();
|
|
482
|
+
var PDFSelectionController = class {
|
|
483
|
+
constructor(options) {
|
|
484
|
+
this.options = options;
|
|
485
|
+
}
|
|
486
|
+
snapshot = null;
|
|
487
|
+
listeners = /* @__PURE__ */ new Set();
|
|
488
|
+
layers = /* @__PURE__ */ new Map();
|
|
489
|
+
pages = /* @__PURE__ */ new Map();
|
|
490
|
+
container = null;
|
|
491
|
+
projection = null;
|
|
492
|
+
frame = null;
|
|
493
|
+
work = null;
|
|
494
|
+
structuralChange = false;
|
|
495
|
+
drag = null;
|
|
496
|
+
connectedCleanup = null;
|
|
497
|
+
generation = 0;
|
|
498
|
+
keyboardWork = null;
|
|
499
|
+
keyboardMoves = [];
|
|
500
|
+
getSnapshot = () => this.snapshot;
|
|
501
|
+
subscribe = (listener) => {
|
|
502
|
+
this.listeners.add(listener);
|
|
503
|
+
return () => {
|
|
504
|
+
this.listeners.delete(listener);
|
|
505
|
+
};
|
|
506
|
+
};
|
|
507
|
+
get isDragging() {
|
|
508
|
+
return this.drag !== null;
|
|
509
|
+
}
|
|
510
|
+
includesPage(page) {
|
|
511
|
+
const range = this.ordered();
|
|
512
|
+
return Boolean(
|
|
513
|
+
range && page >= range.start.pageNumber && page <= range.end.pageNumber
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
publish(snapshot) {
|
|
517
|
+
this.snapshot = snapshot;
|
|
518
|
+
for (const listener of this.listeners) listener();
|
|
519
|
+
}
|
|
520
|
+
ordered() {
|
|
521
|
+
if (!this.snapshot) return null;
|
|
522
|
+
const { anchor, focus } = this.snapshot;
|
|
523
|
+
return compare(anchor, focus) <= 0 ? { start: anchor, end: focus } : { start: focus, end: anchor };
|
|
524
|
+
}
|
|
525
|
+
bounds(page, length) {
|
|
526
|
+
const range = this.ordered();
|
|
527
|
+
return {
|
|
528
|
+
from: page === range.start.pageNumber ? Math.min(range.start.offset, length) : 0,
|
|
529
|
+
to: page === range.end.pageNumber ? Math.min(range.end.offset, length) : length
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
remember(page) {
|
|
533
|
+
if (!this.includesPage(page.pageNumber)) return;
|
|
534
|
+
for (const point of [this.snapshot.anchor, this.snapshot.focus]) {
|
|
535
|
+
if (point.pageNumber === page.pageNumber && point.offset > page.content.text.length)
|
|
536
|
+
throw new RangeError("Selection offset exceeds page text length");
|
|
537
|
+
}
|
|
538
|
+
const { from, to } = this.bounds(page.pageNumber, page.content.text.length);
|
|
539
|
+
const cached = this.pages.get(page.pageNumber);
|
|
540
|
+
if (cached?.content.text === page.content.text && cached.from === from && cached.to === to)
|
|
541
|
+
return;
|
|
542
|
+
this.pages.set(page.pageNumber, {
|
|
543
|
+
content: page.content,
|
|
544
|
+
from,
|
|
545
|
+
to,
|
|
546
|
+
rects: page.rects(from, to)
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
complete() {
|
|
550
|
+
const range = this.ordered();
|
|
551
|
+
if (!range) return false;
|
|
552
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++) {
|
|
553
|
+
const page = this.pages.get(n);
|
|
554
|
+
if (!page) return false;
|
|
555
|
+
const bounds = this.bounds(n, page.content.text.length);
|
|
556
|
+
if (page.from !== bounds.from || page.to !== bounds.to) return false;
|
|
557
|
+
}
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
setSelection = (anchor, focus) => {
|
|
561
|
+
for (const point of [anchor, focus]) {
|
|
562
|
+
const text = this.layers.get(point.pageNumber)?.content.text ?? this.pages.get(point.pageNumber)?.content.text;
|
|
563
|
+
if (text !== void 0 && point.offset > text.length)
|
|
564
|
+
throw new RangeError("Selection offset exceeds page text length");
|
|
565
|
+
if (!Number.isInteger(point.pageNumber) || point.pageNumber < 1 || point.pageNumber > this.options.pageCount || !Number.isInteger(point.offset) || point.offset < 0)
|
|
566
|
+
throw new RangeError(
|
|
567
|
+
"Selection anchors need a valid page and nonnegative UTF-16 offset"
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
if (sameAnchor(anchor, focus)) {
|
|
571
|
+
const drag = this.drag;
|
|
572
|
+
this.clear();
|
|
573
|
+
this.drag = drag;
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (this.snapshot && this.snapshot.status !== "error" && sameAnchor(this.snapshot.anchor, anchor) && sameAnchor(this.snapshot.focus, focus))
|
|
577
|
+
return;
|
|
578
|
+
if (this.container) {
|
|
579
|
+
const owner = this.container.ownerDocument;
|
|
580
|
+
const previous = selectionOwners.get(owner);
|
|
581
|
+
if (previous !== this) previous?.clear();
|
|
582
|
+
selectionOwners.set(owner, this);
|
|
583
|
+
}
|
|
584
|
+
this.work?.abort();
|
|
585
|
+
this.keyboardWork?.abort();
|
|
586
|
+
this.keyboardWork = null;
|
|
587
|
+
this.generation++;
|
|
588
|
+
this.snapshot = {
|
|
589
|
+
anchor: { ...anchor },
|
|
590
|
+
focus: { ...focus },
|
|
591
|
+
status: "loading"
|
|
592
|
+
};
|
|
593
|
+
for (const n of this.pages.keys())
|
|
594
|
+
if (!this.includesPage(n)) this.pages.delete(n);
|
|
595
|
+
for (const page of this.layers.values()) this.remember(page);
|
|
596
|
+
this.publish({
|
|
597
|
+
...this.snapshot,
|
|
598
|
+
status: this.complete() ? "ready" : "loading"
|
|
599
|
+
});
|
|
600
|
+
this.project();
|
|
601
|
+
this.schedule();
|
|
602
|
+
this.hydrate();
|
|
603
|
+
};
|
|
604
|
+
clear = () => {
|
|
605
|
+
const owner = this.container?.ownerDocument;
|
|
606
|
+
if (owner && selectionOwners.get(owner) === this)
|
|
607
|
+
selectionOwners.delete(owner);
|
|
608
|
+
this.work?.abort();
|
|
609
|
+
this.keyboardWork?.abort();
|
|
610
|
+
this.keyboardWork = null;
|
|
611
|
+
this.generation++;
|
|
612
|
+
this.pages.clear();
|
|
613
|
+
this.keyboardMoves = [];
|
|
614
|
+
this.drag = null;
|
|
615
|
+
const selection = this.container?.ownerDocument.getSelection();
|
|
616
|
+
if (this.projection && selection && samePoint(
|
|
617
|
+
this.projection.anchor,
|
|
618
|
+
selection.anchorNode,
|
|
619
|
+
selection.anchorOffset
|
|
620
|
+
) && samePoint(
|
|
621
|
+
this.projection.focus,
|
|
622
|
+
selection.focusNode,
|
|
623
|
+
selection.focusOffset
|
|
624
|
+
))
|
|
625
|
+
selection.removeAllRanges();
|
|
626
|
+
this.projection = null;
|
|
627
|
+
if (this.snapshot) this.publish(null);
|
|
628
|
+
};
|
|
629
|
+
getText = (options = {}) => {
|
|
630
|
+
const range = this.ordered();
|
|
631
|
+
if (!range || !this.complete()) return null;
|
|
632
|
+
const chunks = [];
|
|
633
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++) {
|
|
634
|
+
const page = this.pages.get(n);
|
|
635
|
+
const pieces = [];
|
|
636
|
+
let offset3 = page.from;
|
|
637
|
+
for (const end of page.content.lineBreaks) {
|
|
638
|
+
if (end <= page.from || end >= page.to) continue;
|
|
639
|
+
pieces.push(page.content.text.slice(offset3, end), "\n");
|
|
640
|
+
offset3 = end;
|
|
641
|
+
}
|
|
642
|
+
pieces.push(page.content.text.slice(offset3, page.to));
|
|
643
|
+
chunks.push(pieces.join(""));
|
|
644
|
+
}
|
|
645
|
+
return normalizeSelectionText(chunks.join("\n\n"), options);
|
|
646
|
+
};
|
|
647
|
+
getSelection = (options) => {
|
|
648
|
+
const text = this.getText(options);
|
|
649
|
+
if (text === null || !this.snapshot) return null;
|
|
650
|
+
const range = this.ordered();
|
|
651
|
+
const highlights = [];
|
|
652
|
+
for (let n = range.start.pageNumber; n <= range.end.pageNumber; n++)
|
|
653
|
+
highlights.push(...this.pages.get(n).rects.map((rect) => ({ ...rect })));
|
|
654
|
+
return {
|
|
655
|
+
anchor: { ...this.snapshot.anchor },
|
|
656
|
+
focus: { ...this.snapshot.focus },
|
|
657
|
+
text,
|
|
658
|
+
highlights,
|
|
659
|
+
underlines: highlights.map((rect) => ({
|
|
660
|
+
...rect,
|
|
661
|
+
top: rect.top + rect.height - 1,
|
|
662
|
+
height: 1
|
|
663
|
+
})),
|
|
664
|
+
isCollapsed: false
|
|
665
|
+
};
|
|
666
|
+
};
|
|
667
|
+
getSelectionAsync = async (options) => {
|
|
668
|
+
const generation = this.generation;
|
|
669
|
+
await new Promise((resolve) => {
|
|
670
|
+
if (this.snapshot?.status !== "loading") {
|
|
671
|
+
resolve();
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const unsubscribe = this.subscribe(() => {
|
|
675
|
+
if (generation !== this.generation || this.snapshot?.status !== "loading") {
|
|
676
|
+
unsubscribe();
|
|
677
|
+
resolve();
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
if (generation !== this.generation)
|
|
682
|
+
throw new DOMException("Selection changed", "AbortError");
|
|
683
|
+
if (this.snapshot?.status === "error") throw this.snapshot.error;
|
|
684
|
+
return this.getSelection(options);
|
|
685
|
+
};
|
|
686
|
+
finishSelection() {
|
|
687
|
+
if (this.snapshot?.status !== "loading" || !this.complete()) return;
|
|
688
|
+
this.work?.abort();
|
|
689
|
+
this.work = null;
|
|
690
|
+
this.publish({ ...this.snapshot, status: "ready" });
|
|
691
|
+
}
|
|
692
|
+
registerLayer(element, pageNumber, width, height) {
|
|
693
|
+
const page = new SelectionPage(element, pageNumber, width, height);
|
|
694
|
+
this.layers.set(pageNumber, page);
|
|
695
|
+
try {
|
|
696
|
+
this.remember(page);
|
|
697
|
+
this.finishSelection();
|
|
698
|
+
} catch (error) {
|
|
699
|
+
this.work?.abort();
|
|
700
|
+
if (this.snapshot)
|
|
701
|
+
this.publish({ ...this.snapshot, status: "error", error });
|
|
702
|
+
}
|
|
703
|
+
this.schedule();
|
|
704
|
+
return () => {
|
|
705
|
+
if (this.layers.get(pageNumber) !== page) return;
|
|
706
|
+
this.structuralChange = true;
|
|
707
|
+
this.layers.delete(pageNumber);
|
|
708
|
+
this.projection = null;
|
|
709
|
+
this.schedule();
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
anchorFor(node, offset3) {
|
|
713
|
+
if (!node) return null;
|
|
714
|
+
for (const [pageNumber, page] of this.layers) {
|
|
715
|
+
const index = page.offset(node, offset3);
|
|
716
|
+
if (index !== null) return { pageNumber, offset: index };
|
|
717
|
+
}
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
capture = () => {
|
|
721
|
+
const owner = this.container?.ownerDocument;
|
|
722
|
+
const active = owner?.activeElement;
|
|
723
|
+
if (active && active !== owner?.body && !this.container?.contains(active) && !active.closest(
|
|
724
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
725
|
+
))
|
|
726
|
+
return;
|
|
727
|
+
const selection = owner?.getSelection();
|
|
728
|
+
if (!selection || selection.rangeCount > 1) return;
|
|
729
|
+
if (this.projection && samePoint(
|
|
730
|
+
this.projection.anchor,
|
|
731
|
+
selection.anchorNode,
|
|
732
|
+
selection.anchorOffset
|
|
733
|
+
) && samePoint(
|
|
734
|
+
this.projection.focus,
|
|
735
|
+
selection.focusNode,
|
|
736
|
+
selection.focusOffset
|
|
737
|
+
))
|
|
738
|
+
return;
|
|
739
|
+
if (this.structuralChange) {
|
|
740
|
+
this.schedule();
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (selection.isCollapsed) {
|
|
744
|
+
if (this.structuralChange || this.drag || !this.projection && this.snapshot && (!this.layers.has(this.snapshot.anchor.pageNumber) || !this.layers.has(this.snapshot.focus.pageNumber))) {
|
|
745
|
+
this.schedule();
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (this.snapshot) this.clear();
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
const anchor = this.anchorFor(selection.anchorNode, selection.anchorOffset);
|
|
752
|
+
const focus = this.anchorFor(selection.focusNode, selection.focusOffset);
|
|
753
|
+
if (anchor && focus) {
|
|
754
|
+
if (this.drag && !this.drag.started) {
|
|
755
|
+
if (!this.drag.shift && !this.drag.moved) this.drag.anchor = anchor;
|
|
756
|
+
this.drag.started = true;
|
|
757
|
+
}
|
|
758
|
+
const savedAnchor = this.projection && samePoint(
|
|
759
|
+
this.projection.anchor,
|
|
760
|
+
selection.anchorNode,
|
|
761
|
+
selection.anchorOffset
|
|
762
|
+
) ? this.snapshot?.anchor : void 0;
|
|
763
|
+
this.setSelection(this.drag?.anchor ?? savedAnchor ?? anchor, focus);
|
|
764
|
+
this.projection = {
|
|
765
|
+
anchor: { node: selection.anchorNode, offset: selection.anchorOffset },
|
|
766
|
+
focus: { node: selection.focusNode, offset: selection.focusOffset }
|
|
767
|
+
};
|
|
768
|
+
} else if (!this.structuralChange && !this.drag && (selection.anchorNode || selection.focusNode))
|
|
769
|
+
this.clear();
|
|
770
|
+
};
|
|
771
|
+
schedule() {
|
|
772
|
+
if (!this.container || this.frame !== null) return;
|
|
773
|
+
this.frame = this.container.ownerDocument.defaultView.requestAnimationFrame(() => {
|
|
774
|
+
this.frame = null;
|
|
775
|
+
this.project();
|
|
776
|
+
this.structuralChange = false;
|
|
777
|
+
this.flushKeyboard();
|
|
778
|
+
if (this.drag) this.autoScroll();
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
project() {
|
|
782
|
+
const range = this.ordered();
|
|
783
|
+
if (!range || !this.container) return;
|
|
784
|
+
const visible = [...this.layers.values()].filter((page) => this.includesPage(page.pageNumber) && page.nodes.length).sort((a, b) => a.pageNumber - b.pageNumber);
|
|
785
|
+
const first = visible[0];
|
|
786
|
+
const last = visible.at(-1);
|
|
787
|
+
if (!first || !last) return;
|
|
788
|
+
const start = first.point(
|
|
789
|
+
this.bounds(first.pageNumber, first.content.text.length).from
|
|
790
|
+
);
|
|
791
|
+
const end = last.point(
|
|
792
|
+
this.bounds(last.pageNumber, last.content.text.length).to,
|
|
793
|
+
true
|
|
794
|
+
);
|
|
795
|
+
if (!start || !end) return;
|
|
796
|
+
const backwards = compare(this.snapshot.anchor, this.snapshot.focus) > 0;
|
|
797
|
+
const anchor = backwards ? end : start;
|
|
798
|
+
const focus = backwards ? start : end;
|
|
799
|
+
const selection = this.container.ownerDocument.getSelection();
|
|
800
|
+
this.projection = { anchor, focus };
|
|
801
|
+
if (!samePoint(anchor, selection.anchorNode, selection.anchorOffset) || !samePoint(focus, selection.focusNode, selection.focusOffset))
|
|
802
|
+
selection.setBaseAndExtent(
|
|
803
|
+
anchor.node,
|
|
804
|
+
anchor.offset,
|
|
805
|
+
focus.node,
|
|
806
|
+
focus.offset
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
hydrate() {
|
|
810
|
+
if (!this.snapshot || this.complete() || !this.container) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const range = this.ordered();
|
|
814
|
+
const owner = this.container.ownerDocument;
|
|
815
|
+
const controller = new AbortController();
|
|
816
|
+
this.work = controller;
|
|
817
|
+
const generation = this.generation;
|
|
818
|
+
let next = range.start.pageNumber;
|
|
819
|
+
const worker = async () => {
|
|
820
|
+
while (next <= range.end.pageNumber) {
|
|
821
|
+
controller.signal.throwIfAborted();
|
|
822
|
+
const n = next++;
|
|
823
|
+
const cached = this.pages.get(n);
|
|
824
|
+
if (cached) {
|
|
825
|
+
const bounds = this.bounds(n, cached.content.text.length);
|
|
826
|
+
if (bounds.from === cached.from && bounds.to === cached.to) continue;
|
|
827
|
+
}
|
|
828
|
+
const page = await this.options.loadPage(n);
|
|
829
|
+
controller.signal.throwIfAborted();
|
|
830
|
+
await withSelectionPage(
|
|
831
|
+
page,
|
|
832
|
+
owner,
|
|
833
|
+
controller.signal,
|
|
834
|
+
(page2) => this.remember(page2)
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
void Promise.all([worker(), worker()]).then(() => {
|
|
839
|
+
if (!controller.signal.aborted && generation === this.generation) {
|
|
840
|
+
this.finishSelection();
|
|
841
|
+
this.schedule();
|
|
842
|
+
}
|
|
843
|
+
}).catch((error) => {
|
|
844
|
+
if (!controller.signal.aborted && generation === this.generation && this.snapshot) {
|
|
845
|
+
this.publish({ ...this.snapshot, status: "error", error });
|
|
846
|
+
controller.abort();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
caret(x, y) {
|
|
851
|
+
const owner = this.container.ownerDocument;
|
|
852
|
+
const doc = owner;
|
|
853
|
+
const position = doc.caretPositionFromPoint?.(x, y);
|
|
854
|
+
if (position) return this.anchorFor(position.offsetNode, position.offset);
|
|
855
|
+
const range = doc.caretRangeFromPoint?.(x, y);
|
|
856
|
+
return range ? this.anchorFor(range.startContainer, range.startOffset) : null;
|
|
857
|
+
}
|
|
858
|
+
autoScroll() {
|
|
859
|
+
if (!this.drag?.moved || !this.container) return;
|
|
860
|
+
const bounds = this.container.getBoundingClientRect();
|
|
861
|
+
const { x, y } = this.drag;
|
|
862
|
+
const edge = Math.min(40, bounds.height / 4);
|
|
863
|
+
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;
|
|
864
|
+
if (delta) this.container.scrollTop += delta;
|
|
865
|
+
const focus = this.caret(
|
|
866
|
+
Math.max(bounds.left + 1, Math.min(x, bounds.right - 1)),
|
|
867
|
+
Math.max(bounds.top + 1, Math.min(y, bounds.bottom - 1))
|
|
868
|
+
);
|
|
869
|
+
if (focus) this.setSelection(this.drag.anchor, focus);
|
|
870
|
+
if (delta) this.schedule();
|
|
871
|
+
}
|
|
872
|
+
flushKeyboard() {
|
|
873
|
+
if (!this.snapshot || !this.keyboardMoves.length || this.keyboardWork)
|
|
874
|
+
return;
|
|
875
|
+
const page = this.layers.get(this.snapshot.focus.pageNumber);
|
|
876
|
+
if (!page) {
|
|
877
|
+
this.options.revealPage?.(this.snapshot.focus.pageNumber);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const move = this.keyboardMoves.shift();
|
|
881
|
+
const native = this.container.ownerDocument.getSelection();
|
|
882
|
+
if (!native?.modify) return;
|
|
883
|
+
const { anchor, focus } = this.snapshot;
|
|
884
|
+
native.modify("extend", move.direction, move.granularity);
|
|
885
|
+
const next = this.anchorFor(native.focusNode, native.focusOffset);
|
|
886
|
+
if (next && !sameAnchor(next, focus)) {
|
|
887
|
+
this.setSelection(anchor, next);
|
|
888
|
+
} else {
|
|
889
|
+
const point = page.point(focus.offset);
|
|
890
|
+
const rtl = point && this.container.ownerDocument.defaultView.getComputedStyle(
|
|
891
|
+
point.node.parentElement
|
|
892
|
+
).direction === "rtl";
|
|
893
|
+
const backwards = move.direction === "backward" || move.direction === (rtl ? "right" : "left");
|
|
894
|
+
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))) {
|
|
895
|
+
this.schedule();
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const n = focus.pageNumber + (backwards ? -1 : 1);
|
|
899
|
+
if (n >= 1 && n <= this.options.pageCount) {
|
|
900
|
+
if (!backwards) {
|
|
901
|
+
this.setSelection(anchor, { pageNumber: n, offset: 0 });
|
|
902
|
+
this.options.revealPage?.(n);
|
|
903
|
+
} else {
|
|
904
|
+
const generation = this.generation;
|
|
905
|
+
const controller = new AbortController();
|
|
906
|
+
this.keyboardWork = controller;
|
|
907
|
+
const owner = this.container.ownerDocument;
|
|
908
|
+
void this.options.loadPage(n).then(
|
|
909
|
+
(proxy) => withSelectionPage(
|
|
910
|
+
proxy,
|
|
911
|
+
owner,
|
|
912
|
+
controller.signal,
|
|
913
|
+
(page2) => page2.content.text.length
|
|
914
|
+
)
|
|
915
|
+
).then((length) => {
|
|
916
|
+
if (controller.signal.aborted || generation !== this.generation || !this.container)
|
|
917
|
+
return;
|
|
918
|
+
this.setSelection(anchor, { pageNumber: n, offset: length });
|
|
919
|
+
this.options.revealPage?.(n);
|
|
920
|
+
}).catch(() => {
|
|
921
|
+
}).finally(() => {
|
|
922
|
+
if (this.keyboardWork === controller) this.keyboardWork = null;
|
|
923
|
+
this.schedule();
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (this.keyboardMoves.length) this.schedule();
|
|
929
|
+
}
|
|
930
|
+
connect(container) {
|
|
931
|
+
this.connectedCleanup?.();
|
|
932
|
+
this.container = container;
|
|
933
|
+
const owner = container.ownerDocument;
|
|
934
|
+
const events = new AbortController();
|
|
935
|
+
const { signal } = events;
|
|
936
|
+
const down = (event) => {
|
|
937
|
+
if (event.defaultPrevented) return;
|
|
938
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
939
|
+
if (target?.closest("button, a, [role=button], [data-selection-tooltip]"))
|
|
940
|
+
return;
|
|
941
|
+
if (!container.contains(event.target)) {
|
|
942
|
+
this.clear();
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (event.defaultPrevented || event.button !== 0 || event.pointerType === "touch" || !container.contains(event.target))
|
|
946
|
+
return;
|
|
947
|
+
const point = this.caret(event.clientX, event.clientY);
|
|
948
|
+
if (!point) return;
|
|
949
|
+
const anchor = event.shiftKey && this.snapshot ? this.snapshot.anchor : point;
|
|
950
|
+
if (!event.shiftKey) this.clear();
|
|
951
|
+
this.drag = {
|
|
952
|
+
anchor,
|
|
953
|
+
x: event.clientX,
|
|
954
|
+
y: event.clientY,
|
|
955
|
+
pointerId: event.pointerId,
|
|
956
|
+
moved: false,
|
|
957
|
+
started: false,
|
|
958
|
+
shift: event.shiftKey
|
|
959
|
+
};
|
|
960
|
+
this.schedule();
|
|
961
|
+
};
|
|
962
|
+
const move = (event) => {
|
|
963
|
+
if (!this.drag || event.pointerId !== this.drag.pointerId) return;
|
|
964
|
+
this.drag.moved ||= Math.abs(this.drag.x - event.clientX) + Math.abs(this.drag.y - event.clientY) > 2;
|
|
965
|
+
this.drag.x = event.clientX;
|
|
966
|
+
this.drag.y = event.clientY;
|
|
967
|
+
this.schedule();
|
|
968
|
+
};
|
|
969
|
+
const up = () => {
|
|
970
|
+
this.capture();
|
|
971
|
+
this.drag = null;
|
|
972
|
+
};
|
|
973
|
+
owner.addEventListener("selectionchange", this.capture, { signal });
|
|
974
|
+
owner.addEventListener("pointerdown", down, { signal });
|
|
975
|
+
owner.addEventListener("pointermove", move, { signal });
|
|
976
|
+
owner.addEventListener("pointerup", up, { signal });
|
|
977
|
+
owner.addEventListener("pointercancel", up, { signal });
|
|
978
|
+
owner.defaultView.addEventListener("blur", up, { signal });
|
|
979
|
+
const cancelKeyboard = () => {
|
|
980
|
+
this.keyboardMoves = [];
|
|
981
|
+
this.keyboardWork?.abort();
|
|
982
|
+
this.keyboardWork = null;
|
|
983
|
+
};
|
|
984
|
+
container.addEventListener(
|
|
985
|
+
"focusout",
|
|
986
|
+
(event) => {
|
|
987
|
+
if (!container.contains(event.relatedTarget))
|
|
988
|
+
cancelKeyboard();
|
|
989
|
+
},
|
|
990
|
+
{ signal }
|
|
991
|
+
);
|
|
992
|
+
owner.defaultView.addEventListener("blur", cancelKeyboard, { signal });
|
|
993
|
+
container.addEventListener(
|
|
994
|
+
"keydown",
|
|
995
|
+
(event) => {
|
|
996
|
+
if (event.defaultPrevented || event.target instanceof Element && event.target.closest(
|
|
997
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
998
|
+
))
|
|
999
|
+
return;
|
|
1000
|
+
if (event.key === "Escape" && this.snapshot) {
|
|
1001
|
+
this.clear();
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
if (!event.shiftKey || !this.snapshot) return;
|
|
1005
|
+
const directions = {
|
|
1006
|
+
ArrowLeft: "left",
|
|
1007
|
+
ArrowRight: "right",
|
|
1008
|
+
ArrowUp: "backward",
|
|
1009
|
+
ArrowDown: "forward",
|
|
1010
|
+
Home: "backward",
|
|
1011
|
+
End: "forward"
|
|
1012
|
+
};
|
|
1013
|
+
const direction = directions[event.key];
|
|
1014
|
+
if (!direction) return;
|
|
1015
|
+
const native = owner.getSelection();
|
|
1016
|
+
if (!native?.modify) return;
|
|
1017
|
+
event.preventDefault();
|
|
1018
|
+
const boundary = event.key === "Home" || event.key === "End" || event.metaKey && (event.key === "ArrowLeft" || event.key === "ArrowRight");
|
|
1019
|
+
const granularity = boundary ? "lineboundary" : event.key === "ArrowUp" || event.key === "ArrowDown" ? "line" : event.ctrlKey || event.altKey ? "word" : "character";
|
|
1020
|
+
this.keyboardMoves.push({ direction, granularity });
|
|
1021
|
+
this.schedule();
|
|
1022
|
+
},
|
|
1023
|
+
{ signal }
|
|
1024
|
+
);
|
|
1025
|
+
const cleanup = () => {
|
|
1026
|
+
events.abort();
|
|
1027
|
+
if (this.frame !== null)
|
|
1028
|
+
owner.defaultView.cancelAnimationFrame(this.frame);
|
|
1029
|
+
this.frame = null;
|
|
1030
|
+
this.clear();
|
|
1031
|
+
this.container = null;
|
|
1032
|
+
this.connectedCleanup = null;
|
|
1033
|
+
};
|
|
1034
|
+
this.connectedCleanup = cleanup;
|
|
1035
|
+
this.hydrate();
|
|
1036
|
+
this.schedule();
|
|
1037
|
+
return cleanup;
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
|
|
222
1041
|
// src/lib/viewport-zoom.ts
|
|
223
1042
|
var viewportZoomHandlers = /* @__PURE__ */ new WeakMap();
|
|
224
1043
|
function applyViewportZoom(viewport, zoom) {
|
|
@@ -268,6 +1087,11 @@ var PDFStore = createZustandContext(
|
|
|
268
1087
|
renderColorMapRef.current = initialColorScheme === "dark" ? createDarkModeColorMap(initialDarkModeColors) : null;
|
|
269
1088
|
return createStore((set, get) => ({
|
|
270
1089
|
pdfDocumentProxy: initialState.pdfDocumentProxy,
|
|
1090
|
+
selection: new PDFSelectionController({
|
|
1091
|
+
pageCount: initialState.pdfDocumentProxy.numPages,
|
|
1092
|
+
loadPage: (page) => get().loadPdfPageProxy(page),
|
|
1093
|
+
revealPage: (page) => get().virtualizer?.scrollToIndex(page - 1, { align: "auto" })
|
|
1094
|
+
}),
|
|
271
1095
|
zoom: initialState.zoom,
|
|
272
1096
|
isZoomFitWidth: initialState.isZoomFitWidth ?? false,
|
|
273
1097
|
zoomOptions: {
|
|
@@ -3104,6 +3928,114 @@ var mergeTextRuns = (rects) => {
|
|
|
3104
3928
|
});
|
|
3105
3929
|
};
|
|
3106
3930
|
|
|
3931
|
+
// src/lib/selection-text.ts
|
|
3932
|
+
function textLayer(node) {
|
|
3933
|
+
const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
3934
|
+
return element?.closest(".textLayer[data-page-number]") ?? null;
|
|
3935
|
+
}
|
|
3936
|
+
function getPdfSelectionText(selection, container, options = {}) {
|
|
3937
|
+
if (!selection || selection.isCollapsed || selection.rangeCount !== 1)
|
|
3938
|
+
return null;
|
|
3939
|
+
const range = selection.getRangeAt(0);
|
|
3940
|
+
const first = textLayer(range.startContainer);
|
|
3941
|
+
const last = textLayer(range.endContainer);
|
|
3942
|
+
if (!first || !last || !container.contains(first) || !container.contains(last))
|
|
3943
|
+
return null;
|
|
3944
|
+
const layers = [
|
|
3945
|
+
...container.querySelectorAll(".textLayer[data-page-number]")
|
|
3946
|
+
].filter((layer) => range.intersectsNode(layer));
|
|
3947
|
+
let previousPage;
|
|
3948
|
+
for (const layer of layers) {
|
|
3949
|
+
const page = Number(layer.dataset.pageNumber);
|
|
3950
|
+
if (!Number.isInteger(page) || page < 1 || previousPage !== void 0 && page !== previousPage + 1)
|
|
3951
|
+
return null;
|
|
3952
|
+
previousPage = page;
|
|
3953
|
+
}
|
|
3954
|
+
const selectedLayers = new Set(layers);
|
|
3955
|
+
const root = range.commonAncestorContainer;
|
|
3956
|
+
const walker = container.ownerDocument.createTreeWalker(
|
|
3957
|
+
root,
|
|
3958
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
|
3959
|
+
{
|
|
3960
|
+
acceptNode(node2) {
|
|
3961
|
+
if (node2.nodeType !== Node.TEXT_NODE && node2.tagName !== "BR")
|
|
3962
|
+
return NodeFilter.FILTER_SKIP;
|
|
3963
|
+
const layer = textLayer(node2);
|
|
3964
|
+
return layer && selectedLayers.has(layer) && range.intersectsNode(node2) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
);
|
|
3968
|
+
const chunks = [];
|
|
3969
|
+
let previousLayer = null;
|
|
3970
|
+
let trailingLineBreaks = 0;
|
|
3971
|
+
let node = root.nodeType === Node.TEXT_NODE ? root : walker.nextNode();
|
|
3972
|
+
while (node) {
|
|
3973
|
+
const layer = textLayer(node);
|
|
3974
|
+
const start = node === range.startContainer ? range.startOffset : 0;
|
|
3975
|
+
const end = node === range.endContainer ? range.endOffset : node.textContent?.length ?? 0;
|
|
3976
|
+
const text = node.nodeType === Node.TEXT_NODE ? node.textContent.slice(start, end) : "\n";
|
|
3977
|
+
if (text) {
|
|
3978
|
+
if (previousLayer && previousLayer !== layer && trailingLineBreaks < 2) {
|
|
3979
|
+
chunks.push("\n".repeat(2 - trailingLineBreaks));
|
|
3980
|
+
trailingLineBreaks = 2;
|
|
3981
|
+
}
|
|
3982
|
+
chunks.push(text);
|
|
3983
|
+
let trailing = 0;
|
|
3984
|
+
for (let i = text.length - 1; i >= 0 && text[i] === "\n"; i--) trailing++;
|
|
3985
|
+
trailingLineBreaks = trailing === text.length ? trailingLineBreaks + trailing : trailing;
|
|
3986
|
+
previousLayer = layer;
|
|
3987
|
+
}
|
|
3988
|
+
node = walker.nextNode();
|
|
3989
|
+
}
|
|
3990
|
+
return normalizeSelectionText(chunks.join(""), options);
|
|
3991
|
+
}
|
|
3992
|
+
var documents = /* @__PURE__ */ new WeakMap();
|
|
3993
|
+
function registerPdfCopy(container, options, read) {
|
|
3994
|
+
const owner = container.ownerDocument;
|
|
3995
|
+
let binding = documents.get(owner);
|
|
3996
|
+
if (!binding) {
|
|
3997
|
+
const registrations = /* @__PURE__ */ new Set();
|
|
3998
|
+
const listener = (event) => {
|
|
3999
|
+
if (event.defaultPrevented || !event.clipboardData) return;
|
|
4000
|
+
const target = event.target;
|
|
4001
|
+
if (target instanceof Element && target.closest(
|
|
4002
|
+
'input, textarea, [contenteditable]:not([contenteditable="false"])'
|
|
4003
|
+
))
|
|
4004
|
+
return;
|
|
4005
|
+
for (const registration2 of registrations) {
|
|
4006
|
+
const settings = registration2.options();
|
|
4007
|
+
if (settings === false) continue;
|
|
4008
|
+
const persistent = registration2.read?.(settings);
|
|
4009
|
+
if (registration2.read && persistent === void 0) {
|
|
4010
|
+
event.preventDefault();
|
|
4011
|
+
break;
|
|
4012
|
+
}
|
|
4013
|
+
const text = persistent ?? getPdfSelectionText(
|
|
4014
|
+
owner.getSelection(),
|
|
4015
|
+
registration2.container,
|
|
4016
|
+
settings
|
|
4017
|
+
);
|
|
4018
|
+
if (text === null) continue;
|
|
4019
|
+
event.clipboardData.setData("text/plain", text);
|
|
4020
|
+
event.preventDefault();
|
|
4021
|
+
break;
|
|
4022
|
+
}
|
|
4023
|
+
};
|
|
4024
|
+
binding = { registrations, listener };
|
|
4025
|
+
documents.set(owner, binding);
|
|
4026
|
+
owner.addEventListener("copy", listener);
|
|
4027
|
+
}
|
|
4028
|
+
const registration = { container, options, read };
|
|
4029
|
+
binding.registrations.add(registration);
|
|
4030
|
+
return () => {
|
|
4031
|
+
binding.registrations.delete(registration);
|
|
4032
|
+
if (!binding.registrations.size) {
|
|
4033
|
+
owner.removeEventListener("copy", binding.listener);
|
|
4034
|
+
documents.delete(owner);
|
|
4035
|
+
}
|
|
4036
|
+
};
|
|
4037
|
+
}
|
|
4038
|
+
|
|
3107
4039
|
// src/hooks/useSelectionDimensions.tsx
|
|
3108
4040
|
var MERGE_THRESHOLD = 2;
|
|
3109
4041
|
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
@@ -3151,7 +4083,19 @@ var mapSelectionRectsToLayers = (range) => {
|
|
|
3151
4083
|
};
|
|
3152
4084
|
var useSelectionDimensions = () => {
|
|
3153
4085
|
const store = PDFStore.useContext();
|
|
4086
|
+
const getText = (options) => {
|
|
4087
|
+
if (store.getState().selection?.getSnapshot())
|
|
4088
|
+
return store.getState().selection.getText(options);
|
|
4089
|
+
const container = store.getState().viewportRef.current;
|
|
4090
|
+
return container ? getPdfSelectionText(
|
|
4091
|
+
container.ownerDocument.getSelection(),
|
|
4092
|
+
container,
|
|
4093
|
+
options
|
|
4094
|
+
) : null;
|
|
4095
|
+
};
|
|
3154
4096
|
const getAnnotationDimension = () => {
|
|
4097
|
+
if (store.getState().selection?.getSnapshot())
|
|
4098
|
+
return store.getState().selection.getSelection() ?? void 0;
|
|
3155
4099
|
const selection = window.getSelection();
|
|
3156
4100
|
if (!selection || selection.isCollapsed) return;
|
|
3157
4101
|
const range = selection.getRangeAt(0);
|
|
@@ -3289,7 +4233,7 @@ var useSelectionDimensions = () => {
|
|
|
3289
4233
|
underlines: consolidateUnderlines(underlineRects).sort(
|
|
3290
4234
|
(a, b) => a.pageNumber - b.pageNumber
|
|
3291
4235
|
),
|
|
3292
|
-
text: range.toString().trim(),
|
|
4236
|
+
text: (getText() ?? range.toString()).trim(),
|
|
3293
4237
|
isCollapsed: false
|
|
3294
4238
|
};
|
|
3295
4239
|
};
|
|
@@ -3346,6 +4290,8 @@ var useSelectionDimensions = () => {
|
|
|
3346
4290
|
return consolidated;
|
|
3347
4291
|
};
|
|
3348
4292
|
const getDimension = () => {
|
|
4293
|
+
if (store.getState().selection?.getSnapshot())
|
|
4294
|
+
return store.getState().selection.getSelection() ?? void 0;
|
|
3349
4295
|
const selection = window.getSelection();
|
|
3350
4296
|
if (!selection || selection.isCollapsed) return;
|
|
3351
4297
|
const range = selection.getRangeAt(0);
|
|
@@ -3374,12 +4320,12 @@ var useSelectionDimensions = () => {
|
|
|
3374
4320
|
});
|
|
3375
4321
|
return {
|
|
3376
4322
|
highlights: highlights.sort((a, b) => a.pageNumber - b.pageNumber),
|
|
3377
|
-
text: range.toString().trim(),
|
|
4323
|
+
text: (getText() ?? range.toString()).trim(),
|
|
3378
4324
|
isCollapsed: false
|
|
3379
4325
|
};
|
|
3380
4326
|
};
|
|
3381
4327
|
const getSelection = () => getDimension();
|
|
3382
|
-
return { getDimension, getSelection, getAnnotationDimension };
|
|
4328
|
+
return { getDimension, getSelection, getAnnotationDimension, getText };
|
|
3383
4329
|
};
|
|
3384
4330
|
var SelectionTooltip = ({ children }) => {
|
|
3385
4331
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -3514,6 +4460,7 @@ var SelectionTooltip = ({ children }) => {
|
|
|
3514
4460
|
return /* @__PURE__ */ jsx(Fragment, { children: isOpen && /* @__PURE__ */ jsx(
|
|
3515
4461
|
"div",
|
|
3516
4462
|
{
|
|
4463
|
+
"data-selection-tooltip": "",
|
|
3517
4464
|
ref: refs.setFloating,
|
|
3518
4465
|
style: {
|
|
3519
4466
|
...floatingStyles
|
|
@@ -3929,13 +4876,13 @@ var createTextSelectionManager = () => {
|
|
|
3929
4876
|
}
|
|
3930
4877
|
selectionChangeAbortController = new AbortController();
|
|
3931
4878
|
const { signal } = selectionChangeAbortController;
|
|
3932
|
-
const reset = (endDiv,
|
|
3933
|
-
if (endDiv.parentNode !==
|
|
3934
|
-
|
|
4879
|
+
const reset = (endDiv, textLayer2) => {
|
|
4880
|
+
if (endDiv.parentNode !== textLayer2) {
|
|
4881
|
+
textLayer2.appendChild(endDiv);
|
|
3935
4882
|
}
|
|
3936
4883
|
endDiv.style.width = "";
|
|
3937
4884
|
endDiv.style.height = "";
|
|
3938
|
-
|
|
4885
|
+
textLayer2.classList.remove("selecting");
|
|
3939
4886
|
};
|
|
3940
4887
|
document.addEventListener(
|
|
3941
4888
|
"pointerdown",
|
|
@@ -4061,12 +5008,14 @@ var useTextLayer = () => {
|
|
|
4061
5008
|
const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
|
|
4062
5009
|
const viewportRef = usePdf((state) => state.viewportRef);
|
|
4063
5010
|
const store = PDFStore.useContext();
|
|
5011
|
+
const selection = store.getState().selection;
|
|
4064
5012
|
useEffect(() => {
|
|
4065
5013
|
const textContainer = textContainerRef.current;
|
|
4066
5014
|
if (!textContainer) {
|
|
4067
5015
|
return;
|
|
4068
5016
|
}
|
|
4069
5017
|
let isCancelled = false;
|
|
5018
|
+
let unregisterSelection;
|
|
4070
5019
|
let buildTimer = null;
|
|
4071
5020
|
let unsubscribe = null;
|
|
4072
5021
|
let built = false;
|
|
@@ -4083,13 +5032,13 @@ var useTextLayer = () => {
|
|
|
4083
5032
|
void import('pdfjs-dist/legacy/build/pdf.mjs').then(({ TextLayer: TextLayer2 }) => {
|
|
4084
5033
|
if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer)
|
|
4085
5034
|
return;
|
|
4086
|
-
const
|
|
5035
|
+
const textLayer2 = new TextLayer2({
|
|
4087
5036
|
textContentSource: pdfPageProxy.streamTextContent(),
|
|
4088
5037
|
container: textContainer,
|
|
4089
5038
|
viewport: pdfPageProxy.getViewport({ scale: 1 })
|
|
4090
5039
|
});
|
|
4091
|
-
textLayerRef.current =
|
|
4092
|
-
return
|
|
5040
|
+
textLayerRef.current = textLayer2;
|
|
5041
|
+
return textLayer2.render();
|
|
4093
5042
|
}).then(() => {
|
|
4094
5043
|
if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer) {
|
|
4095
5044
|
return;
|
|
@@ -4098,6 +5047,16 @@ var useTextLayer = () => {
|
|
|
4098
5047
|
endOfContent.className = "endOfContent";
|
|
4099
5048
|
textContainer.appendChild(endOfContent);
|
|
4100
5049
|
bindMouseEvents(textContainer, endOfContent);
|
|
5050
|
+
const viewport = pdfPageProxy.getViewport({ scale: 1 });
|
|
5051
|
+
const unrotated = pdfPageProxy.getViewport({ scale: 1, rotation: 0 });
|
|
5052
|
+
textContainer.style.width = `${unrotated.width}px`;
|
|
5053
|
+
textContainer.style.height = `${unrotated.height}px`;
|
|
5054
|
+
unregisterSelection = selection?.registerLayer(
|
|
5055
|
+
textContainer,
|
|
5056
|
+
pageNumber,
|
|
5057
|
+
viewport.width,
|
|
5058
|
+
viewport.height
|
|
5059
|
+
);
|
|
4101
5060
|
built = true;
|
|
4102
5061
|
building = false;
|
|
4103
5062
|
unsubscribe?.();
|
|
@@ -4111,6 +5070,12 @@ var useTextLayer = () => {
|
|
|
4111
5070
|
};
|
|
4112
5071
|
const schedule = () => {
|
|
4113
5072
|
if (built) return;
|
|
5073
|
+
if (selection?.isDragging || selection?.includesPage(pageNumber)) {
|
|
5074
|
+
if (buildTimer) clearTimeout(buildTimer);
|
|
5075
|
+
buildTimer = null;
|
|
5076
|
+
if (!building) build();
|
|
5077
|
+
return;
|
|
5078
|
+
}
|
|
4114
5079
|
if (building) {
|
|
4115
5080
|
buildGen++;
|
|
4116
5081
|
building = false;
|
|
@@ -4140,9 +5105,15 @@ var useTextLayer = () => {
|
|
|
4140
5105
|
unsubscribe = subscribeToViewportInvalidation(scrollViewport, schedule);
|
|
4141
5106
|
};
|
|
4142
5107
|
attachScrollSubscription();
|
|
5108
|
+
const unsubscribeSelection = selection?.subscribe(() => {
|
|
5109
|
+
if (selection.isDragging || selection.includesPage(pageNumber))
|
|
5110
|
+
schedule();
|
|
5111
|
+
});
|
|
4143
5112
|
schedule();
|
|
4144
5113
|
return () => {
|
|
4145
5114
|
isCancelled = true;
|
|
5115
|
+
unregisterSelection?.();
|
|
5116
|
+
unsubscribeSelection?.();
|
|
4146
5117
|
if (buildTimer) clearTimeout(buildTimer);
|
|
4147
5118
|
if (attachRaf !== null) cancelAnimationFrame(attachRaf);
|
|
4148
5119
|
unsubscribe?.();
|
|
@@ -4155,7 +5126,13 @@ var useTextLayer = () => {
|
|
|
4155
5126
|
delete textContainer._cleanupTextSelection;
|
|
4156
5127
|
}
|
|
4157
5128
|
};
|
|
4158
|
-
}, [
|
|
5129
|
+
}, [
|
|
5130
|
+
pdfPageProxy.streamTextContent,
|
|
5131
|
+
pdfPageProxy.getViewport,
|
|
5132
|
+
viewportRef,
|
|
5133
|
+
selection,
|
|
5134
|
+
pageNumber
|
|
5135
|
+
]);
|
|
4159
5136
|
useEffect(() => {
|
|
4160
5137
|
const textContainer = textContainerRef.current;
|
|
4161
5138
|
if (!textContainer) return;
|
|
@@ -4174,7 +5151,10 @@ var useTextLayer = () => {
|
|
|
4174
5151
|
show();
|
|
4175
5152
|
};
|
|
4176
5153
|
const hideAndDebounce = () => {
|
|
4177
|
-
if (selecting)
|
|
5154
|
+
if (selecting || selection?.isDragging || selection?.includesPage(pageNumber)) {
|
|
5155
|
+
show();
|
|
5156
|
+
return;
|
|
5157
|
+
}
|
|
4178
5158
|
textContainer.style.visibility = "hidden";
|
|
4179
5159
|
if (settleTimer) clearTimeout(settleTimer);
|
|
4180
5160
|
settleTimer = setTimeout(restoreWhenSettled, TEXT_SCROLL_SETTLE_MS);
|
|
@@ -4242,7 +5222,7 @@ var useTextLayer = () => {
|
|
|
4242
5222
|
if (settleTimer) clearTimeout(settleTimer);
|
|
4243
5223
|
show();
|
|
4244
5224
|
};
|
|
4245
|
-
}, [viewportRef, store]);
|
|
5225
|
+
}, [viewportRef, store, selection, pageNumber]);
|
|
4246
5226
|
return {
|
|
4247
5227
|
textContainerRef,
|
|
4248
5228
|
pageNumber: pdfPageProxy.pageNumber
|
|
@@ -6829,6 +7809,10 @@ var useViewportContainer = ({
|
|
|
6829
7809
|
if (!st.active) {
|
|
6830
7810
|
return;
|
|
6831
7811
|
}
|
|
7812
|
+
if (Math.abs(event.deltaX) > abs) {
|
|
7813
|
+
st.active = false;
|
|
7814
|
+
return;
|
|
7815
|
+
}
|
|
6832
7816
|
if (now - st.lastTime > WHEEL_INERTIA_GAP_MS) {
|
|
6833
7817
|
st.active = false;
|
|
6834
7818
|
return;
|
|
@@ -6888,12 +7872,11 @@ var useViewportContainer = ({
|
|
|
6888
7872
|
contentPosition,
|
|
6889
7873
|
containerPosition,
|
|
6890
7874
|
originZoom: currentZoom,
|
|
7875
|
+
origin: [...origin],
|
|
6891
7876
|
lastZoom: currentZoom
|
|
6892
7877
|
};
|
|
6893
7878
|
});
|
|
6894
|
-
if (first)
|
|
6895
|
-
return newMemo;
|
|
6896
|
-
}
|
|
7879
|
+
if (first && ms === 1) return newMemo;
|
|
6897
7880
|
const gestureValuesValid = Number.isFinite(ms) && ms > 0;
|
|
6898
7881
|
let effectiveScale = ms;
|
|
6899
7882
|
if (!gestureValuesValid) {
|
|
@@ -6912,9 +7895,18 @@ var useViewportContainer = ({
|
|
|
6912
7895
|
minZoom,
|
|
6913
7896
|
maxZoom
|
|
6914
7897
|
);
|
|
7898
|
+
if (event.type === "wheel") {
|
|
7899
|
+
const previousScale = newMemo.lastZoom / newMemo.originZoom;
|
|
7900
|
+
for (const axis of [0, 1]) {
|
|
7901
|
+
const movement2 = origin[axis] - newMemo.origin[axis];
|
|
7902
|
+
newMemo.contentPosition[axis] += movement2 / previousScale;
|
|
7903
|
+
newMemo.containerPosition[axis] += movement2;
|
|
7904
|
+
newMemo.origin[axis] = origin[axis];
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
6915
7907
|
const realMs = newZoom / newMemo.originZoom;
|
|
6916
|
-
const newTranslateX = newMemo.contentPosition[0] * realMs - newMemo.containerPosition[0];
|
|
6917
|
-
const newTranslateY = newMemo.contentPosition[1] * realMs - newMemo.containerPosition[1];
|
|
7908
|
+
const newTranslateX = newMemo.contentPosition[0] * realMs - newMemo.containerPosition[0] - (origin[0] - newMemo.origin[0]);
|
|
7909
|
+
const newTranslateY = newMemo.contentPosition[1] * realMs - newMemo.containerPosition[1] - (origin[1] - newMemo.origin[1]);
|
|
6918
7910
|
transformations.current = {
|
|
6919
7911
|
zoom: newZoom,
|
|
6920
7912
|
translateX: newTranslateX,
|
|
@@ -6988,16 +7980,34 @@ var Pages = ({
|
|
|
6988
7980
|
virtualizerOptions = DEFAULT_VIRTUALIZER_OPTIONS,
|
|
6989
7981
|
initialOffset,
|
|
6990
7982
|
onOffsetChange,
|
|
7983
|
+
copyOptions,
|
|
6991
7984
|
...props
|
|
6992
7985
|
}) => {
|
|
6993
7986
|
const [tempItems, setTempItems] = useState([]);
|
|
6994
7987
|
const viewports = usePdf((state) => state.viewports);
|
|
7988
|
+
const selection = usePdf((state) => state.selection);
|
|
6995
7989
|
const numPages = usePdf((state) => state.pdfDocumentProxy.numPages);
|
|
6996
7990
|
const initialPage = usePdf((state) => state.initialPage);
|
|
6997
7991
|
const isPinching = usePdf((state) => state.isPinching);
|
|
6998
7992
|
const elementWrapperRef = useRef(null);
|
|
6999
7993
|
const elementRef = useRef(null);
|
|
7000
7994
|
const containerRef = useRef(null);
|
|
7995
|
+
const copyOptionsRef = useRef(copyOptions);
|
|
7996
|
+
copyOptionsRef.current = copyOptions;
|
|
7997
|
+
useEffect(() => {
|
|
7998
|
+
const container = containerRef.current;
|
|
7999
|
+
if (!container) return;
|
|
8000
|
+
const disconnect = selection.connect(container);
|
|
8001
|
+
const unregisterCopy = registerPdfCopy(
|
|
8002
|
+
container,
|
|
8003
|
+
() => copyOptionsRef.current ?? {},
|
|
8004
|
+
(options) => selection.getSnapshot() ? selection.getText(options) ?? void 0 : null
|
|
8005
|
+
);
|
|
8006
|
+
return () => {
|
|
8007
|
+
unregisterCopy();
|
|
8008
|
+
disconnect();
|
|
8009
|
+
};
|
|
8010
|
+
}, [selection]);
|
|
7001
8011
|
useViewportContainer({
|
|
7002
8012
|
elementRef,
|
|
7003
8013
|
elementWrapperRef,
|
|
@@ -7078,7 +8088,14 @@ var Pages = ({
|
|
|
7078
8088
|
};
|
|
7079
8089
|
}, [isPinching, virtualizer?.measure, virtualizer?.getVirtualItems]);
|
|
7080
8090
|
const virtualizerItems = virtualizer?.getVirtualItems() ?? [];
|
|
7081
|
-
const items =
|
|
8091
|
+
const items = useMemo(() => {
|
|
8092
|
+
if (!tempItems.length) return virtualizerItems;
|
|
8093
|
+
const combined = new Map(
|
|
8094
|
+
virtualizerItems.map((item) => [item.index, item])
|
|
8095
|
+
);
|
|
8096
|
+
for (const item of tempItems) combined.set(item.index, item);
|
|
8097
|
+
return [...combined.values()].sort((a, b) => a.index - b.index);
|
|
8098
|
+
}, [tempItems, virtualizerItems]);
|
|
7082
8099
|
useVisiblePage({
|
|
7083
8100
|
items,
|
|
7084
8101
|
// Pass through nullish (pre-measure) rather than coercing to 0, so the
|
|
@@ -7115,6 +8132,7 @@ var Pages = ({
|
|
|
7115
8132
|
Primitive.div,
|
|
7116
8133
|
{
|
|
7117
8134
|
ref: containerRef,
|
|
8135
|
+
tabIndex: 0,
|
|
7118
8136
|
...props,
|
|
7119
8137
|
style: {
|
|
7120
8138
|
display: "flex",
|
|
@@ -7759,6 +8777,8 @@ function acquireDocumentText(pages) {
|
|
|
7759
8777
|
const page = pages[index];
|
|
7760
8778
|
const reader = page.streamTextContent().getReader();
|
|
7761
8779
|
const chunks = [];
|
|
8780
|
+
const lineBreaks = [];
|
|
8781
|
+
let offset3 = 0;
|
|
7762
8782
|
let complete = false;
|
|
7763
8783
|
try {
|
|
7764
8784
|
while (true) {
|
|
@@ -7768,9 +8788,13 @@ function acquireDocumentText(pages) {
|
|
|
7768
8788
|
complete = true;
|
|
7769
8789
|
break;
|
|
7770
8790
|
}
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
8791
|
+
for (const item of value.items) {
|
|
8792
|
+
if (!("str" in item)) continue;
|
|
8793
|
+
chunks.push(item.str);
|
|
8794
|
+
offset3 += item.str.length;
|
|
8795
|
+
if (item.hasEOL && lineBreaks.at(-1) !== offset3)
|
|
8796
|
+
lineBreaks.push(offset3);
|
|
8797
|
+
}
|
|
7774
8798
|
}
|
|
7775
8799
|
} finally {
|
|
7776
8800
|
if (!complete)
|
|
@@ -7778,7 +8802,11 @@ function acquireDocumentText(pages) {
|
|
|
7778
8802
|
});
|
|
7779
8803
|
reader.releaseLock();
|
|
7780
8804
|
}
|
|
7781
|
-
result[index] = {
|
|
8805
|
+
result[index] = {
|
|
8806
|
+
pageNumber: page.pageNumber,
|
|
8807
|
+
text: chunks.join(""),
|
|
8808
|
+
...lineBreaks.length ? { lineBreaks } : {}
|
|
8809
|
+
};
|
|
7782
8810
|
}
|
|
7783
8811
|
};
|
|
7784
8812
|
const created = {
|
|
@@ -8187,33 +9215,31 @@ var CurrentZoom = ({ ...props }) => {
|
|
|
8187
9215
|
|
|
8188
9216
|
// src/lib/search.ts
|
|
8189
9217
|
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);
|
|
9218
|
+
function normalize(page, options) {
|
|
9219
|
+
let cached = normalizedPages.get(page);
|
|
9220
|
+
if (cached?.text !== page.text || cached.lineBreaks !== page.lineBreaks) {
|
|
9221
|
+
cached = {
|
|
9222
|
+
text: page.text,
|
|
9223
|
+
lineBreaks: page.lineBreaks,
|
|
9224
|
+
variants: /* @__PURE__ */ new Map()
|
|
9225
|
+
};
|
|
9226
|
+
normalizedPages.set(page, cached);
|
|
9227
|
+
}
|
|
9228
|
+
const key = (options.matchDiacritics === false ? 1 : 0) | (options.ignoreHyphenation ? 2 : 0);
|
|
9229
|
+
let result = cached.variants.get(key);
|
|
9230
|
+
if (!result) {
|
|
9231
|
+
result = normalizeSearchText(page.text, options, page.lineBreaks);
|
|
9232
|
+
cached.variants.set(key, result);
|
|
9233
|
+
}
|
|
8211
9234
|
return result;
|
|
8212
9235
|
}
|
|
8213
9236
|
function createMatch(page, normalized, start, length, searchText, textSize, score) {
|
|
8214
|
-
const end = Math.min(normalized.
|
|
8215
|
-
const matchIndex
|
|
8216
|
-
|
|
9237
|
+
const end = Math.min(normalized.text.length, start + length);
|
|
9238
|
+
const { start: matchIndex, end: matchEnd } = originalTextRange(
|
|
9239
|
+
normalized,
|
|
9240
|
+
start,
|
|
9241
|
+
end
|
|
9242
|
+
);
|
|
8217
9243
|
const matchLength = matchEnd - matchIndex;
|
|
8218
9244
|
return {
|
|
8219
9245
|
pageNumber: page.pageNumber,
|
|
@@ -8276,7 +9302,8 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
8276
9302
|
const threshold = Math.max(0, Math.min(1, options.threshold ?? 0.7));
|
|
8277
9303
|
const limit = Number.isFinite(options.limit) ? Math.max(0, Math.floor(options.limit)) : 10;
|
|
8278
9304
|
const textSize = options.textSize ?? 100;
|
|
8279
|
-
const query = searchText.
|
|
9305
|
+
const query = normalizeSearchText(searchText, options).text;
|
|
9306
|
+
if (!query.trim()) return empty;
|
|
8280
9307
|
const maxDistance = Math.floor(query.length * (1 - threshold));
|
|
8281
9308
|
const distance = maxDistance > 0 ? createBoundedDistance(query, maxDistance) : null;
|
|
8282
9309
|
const exactMatches = [];
|
|
@@ -8286,10 +9313,16 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
8286
9313
|
let steps = 0;
|
|
8287
9314
|
let deadline = cooperative ? performance.now() + budget : 0;
|
|
8288
9315
|
for (const page of pages) {
|
|
8289
|
-
|
|
8290
|
-
|
|
9316
|
+
if (cooperative && performance.now() >= deadline) {
|
|
9317
|
+
yield;
|
|
9318
|
+
deadline = performance.now() + budget;
|
|
9319
|
+
}
|
|
9320
|
+
const normalized = normalize(page, options);
|
|
9321
|
+
const lower = normalized.text;
|
|
8291
9322
|
const exactStarts = [];
|
|
8292
9323
|
let index = 0;
|
|
9324
|
+
let lastStart = -1;
|
|
9325
|
+
let lastEnd = -1;
|
|
8293
9326
|
while (true) {
|
|
8294
9327
|
if (cooperative && (++steps & 127) === 0 && performance.now() >= deadline) {
|
|
8295
9328
|
yield;
|
|
@@ -8297,8 +9330,20 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
8297
9330
|
}
|
|
8298
9331
|
const matchIndex = lower.indexOf(query, index);
|
|
8299
9332
|
if (matchIndex === -1) break;
|
|
8300
|
-
|
|
9333
|
+
const span = originalTextRange(
|
|
9334
|
+
normalized,
|
|
9335
|
+
matchIndex,
|
|
9336
|
+
matchIndex + query.length
|
|
9337
|
+
);
|
|
9338
|
+
index = matchIndex + query.length;
|
|
8301
9339
|
if (maxDistance > 0) exactStarts.push(matchIndex);
|
|
9340
|
+
if (span.start === lastStart && span.end === lastEnd) continue;
|
|
9341
|
+
lastStart = span.start;
|
|
9342
|
+
lastEnd = span.end;
|
|
9343
|
+
exactCount++;
|
|
9344
|
+
if (maxDistance === 0 && exactCount > limit) {
|
|
9345
|
+
return { exactMatches, fuzzyMatches, hasMoreResults: true };
|
|
9346
|
+
}
|
|
8302
9347
|
if (exactMatches.length < limit) {
|
|
8303
9348
|
exactMatches.push(
|
|
8304
9349
|
createMatch(
|
|
@@ -8373,7 +9418,19 @@ async function searchDocumentAsync(pages, searchText, options = {}) {
|
|
|
8373
9418
|
throw new DOMException("Search aborted", "AbortError");
|
|
8374
9419
|
const result = iterator.next();
|
|
8375
9420
|
if (result.done) return result.value;
|
|
8376
|
-
await new Promise((resolve) =>
|
|
9421
|
+
await new Promise((resolve) => {
|
|
9422
|
+
if (typeof MessageChannel === "undefined") {
|
|
9423
|
+
setTimeout(resolve, 0);
|
|
9424
|
+
return;
|
|
9425
|
+
}
|
|
9426
|
+
const channel = new MessageChannel();
|
|
9427
|
+
channel.port1.onmessage = () => {
|
|
9428
|
+
channel.port1.close();
|
|
9429
|
+
channel.port2.close();
|
|
9430
|
+
resolve();
|
|
9431
|
+
};
|
|
9432
|
+
channel.port2.postMessage(null);
|
|
9433
|
+
});
|
|
8377
9434
|
}
|
|
8378
9435
|
}
|
|
8379
9436
|
|
|
@@ -8471,10 +9528,16 @@ async function calculateHighlightRects(pageProxy, textMatch) {
|
|
|
8471
9528
|
const start = Math.max(0, textMatch.matchIndex - offset3);
|
|
8472
9529
|
const end = Math.min(length, matchEnd - offset3);
|
|
8473
9530
|
if (end > start && item.str.slice(start, end).trim()) {
|
|
9531
|
+
let style = styles[item.fontName];
|
|
9532
|
+
if (style?.fontFamily && !style.vertical && (start > 0 || end < length)) {
|
|
9533
|
+
const { TextLayer: TextLayer2 } = await loadPdfJs();
|
|
9534
|
+
const fontFamily = TextLayer2.fontFamilyMap.get(style.fontFamily);
|
|
9535
|
+
if (fontFamily) style = { ...style, fontFamily };
|
|
9536
|
+
}
|
|
8474
9537
|
matchRects.push(
|
|
8475
9538
|
getItemRect(
|
|
8476
9539
|
item,
|
|
8477
|
-
|
|
9540
|
+
style,
|
|
8478
9541
|
viewport,
|
|
8479
9542
|
start,
|
|
8480
9543
|
end,
|
|
@@ -8579,5 +9642,24 @@ function mergeAdjacentRects(rects) {
|
|
|
8579
9642
|
|
|
8580
9643
|
// src/hooks/usePageRendered.ts
|
|
8581
9644
|
var usePageRendered = (pageNumber) => usePdf((state) => !!state.renderedPages[pageNumber]);
|
|
9645
|
+
function usePdfSelection() {
|
|
9646
|
+
const controller = usePdf((state) => state.selection);
|
|
9647
|
+
const selection = useSyncExternalStore(
|
|
9648
|
+
controller.subscribe,
|
|
9649
|
+
controller.getSnapshot,
|
|
9650
|
+
controller.getSnapshot
|
|
9651
|
+
);
|
|
9652
|
+
return useMemo(
|
|
9653
|
+
() => ({
|
|
9654
|
+
selection,
|
|
9655
|
+
setSelection: controller.setSelection,
|
|
9656
|
+
clearSelection: controller.clear,
|
|
9657
|
+
getText: controller.getText,
|
|
9658
|
+
getSelection: controller.getSelection,
|
|
9659
|
+
getSelectionAsync: controller.getSelectionAsync
|
|
9660
|
+
}),
|
|
9661
|
+
[controller, selection]
|
|
9662
|
+
);
|
|
9663
|
+
}
|
|
8582
9664
|
|
|
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 };
|
|
9665
|
+
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 };
|