@anaralabs/lector 3.15.0 → 3.15.2
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 +15 -0
- package/dist/index.js +552 -351
- package/package.json +1 -1
|
@@ -177,6 +177,9 @@ declare interface AsyncSearchOptions extends SearchOptions {
|
|
|
177
177
|
export { AsyncSearchOptions }
|
|
178
178
|
export { AsyncSearchOptions as AsyncSearchOptions_alias_1 }
|
|
179
179
|
|
|
180
|
+
/** One mouse-selection controller per viewer, shared by its mounted pages. */
|
|
181
|
+
export declare function bindTextSelection(layer: HTMLDivElement, viewport: ViewportRef, isPersistent?: () => boolean): () => void;
|
|
182
|
+
|
|
180
183
|
export declare function calculateHighlightRects(pageProxy: PDFPageProxy, textMatch: TextPosition): Promise<HighlightRect[]>;
|
|
181
184
|
|
|
182
185
|
export declare const cancellable: <T extends Promise<unknown>>(promise: T) => {
|
|
@@ -195,6 +198,11 @@ export declare const CanvasLayer: MemoExoticComponent<({ style, background, ...p
|
|
|
195
198
|
background?: string;
|
|
196
199
|
}) => JSX.Element>;
|
|
197
200
|
|
|
201
|
+
declare type Caret = {
|
|
202
|
+
node: Node;
|
|
203
|
+
offset: number;
|
|
204
|
+
};
|
|
205
|
+
|
|
198
206
|
export declare const clamp: (value: number, minimum: number, maximum: number) => number;
|
|
199
207
|
|
|
200
208
|
export declare function clampScaleForPage(targetScale: number, pageWidth: number, pageHeight: number, maxPixels?: number): number;
|
|
@@ -569,6 +577,7 @@ export declare class PDFSelectionController {
|
|
|
569
577
|
});
|
|
570
578
|
getSnapshot: () => PDFTextSelection | null;
|
|
571
579
|
subscribe: (listener: () => void) => () => void;
|
|
580
|
+
get isConnected(): boolean;
|
|
572
581
|
get isDragging(): boolean;
|
|
573
582
|
includesPage(page: number): boolean;
|
|
574
583
|
private publish;
|
|
@@ -729,6 +738,8 @@ export declare interface RenderColorMapRef {
|
|
|
729
738
|
current: RenderColorMap | null;
|
|
730
739
|
}
|
|
731
740
|
|
|
741
|
+
export declare function resolveTextSelectionCaret(viewport: HTMLElement, x: number, y: number): Caret | null | undefined;
|
|
742
|
+
|
|
732
743
|
export declare const Root: ForwardRefExoticComponent<Omit<Omit<HTMLProps<HTMLDivElement>, "onError"> & usePDFDocumentParams & {
|
|
733
744
|
loader?: ReactNode;
|
|
734
745
|
}, "ref"> & RefAttributes<unknown>>;
|
|
@@ -1098,6 +1109,10 @@ export declare const useSelectionDimensions: () => {
|
|
|
1098
1109
|
|
|
1099
1110
|
declare type ViewportCallback = () => void;
|
|
1100
1111
|
|
|
1112
|
+
declare type ViewportRef = {
|
|
1113
|
+
current: HTMLDivElement | null;
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1101
1116
|
/** Materialize one missing selected page as text only, never as a page canvas. */
|
|
1102
1117
|
export declare function withSelectionPage<T>(proxy: PDFPageProxy, owner: Document, signal: AbortSignal, read: (page: SelectionPage) => T): Promise<T>;
|
|
1103
1118
|
|
package/dist/index.js
CHANGED
|
@@ -401,9 +401,19 @@ function normalizeUnicode(text, matchDiacritics = true) {
|
|
|
401
401
|
}
|
|
402
402
|
var segmenter;
|
|
403
403
|
function* unicodeReplacements(text, matchDiacritics) {
|
|
404
|
-
segmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
405
404
|
for (const match of text.matchAll(/\p{ASCII}?\P{ASCII}+/gu)) {
|
|
406
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" });
|
|
407
417
|
for (const { segment, index } of segmenter.segment(match[0])) {
|
|
408
418
|
yield {
|
|
409
419
|
index: match.index + index,
|
|
@@ -464,6 +474,467 @@ function originalTextRange(normalized, start, end) {
|
|
|
464
474
|
return { start, end };
|
|
465
475
|
}
|
|
466
476
|
|
|
477
|
+
// src/lib/selection-rects.ts
|
|
478
|
+
var getTextNodeClientRects = (range) => {
|
|
479
|
+
const root = range.commonAncestorContainer;
|
|
480
|
+
const ownerDoc = root.ownerDocument ?? document;
|
|
481
|
+
if (root.nodeType === Node.TEXT_NODE) {
|
|
482
|
+
const parentElement = root.parentElement;
|
|
483
|
+
return Array.from(range.getClientRects()).map((rect) => ({
|
|
484
|
+
rect,
|
|
485
|
+
element: parentElement
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
const walker = ownerDoc.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
489
|
+
acceptNode(node) {
|
|
490
|
+
if (!node.nodeValue || node.nodeValue.length === 0) {
|
|
491
|
+
return NodeFilter.FILTER_REJECT;
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
return range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
495
|
+
} catch {
|
|
496
|
+
return NodeFilter.FILTER_REJECT;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
const results = [];
|
|
501
|
+
let current = walker.nextNode();
|
|
502
|
+
while (current) {
|
|
503
|
+
const textNode = current;
|
|
504
|
+
const parentElement = textNode.parentElement;
|
|
505
|
+
const length = textNode.nodeValue?.length ?? 0;
|
|
506
|
+
const isStartNode = textNode === range.startContainer;
|
|
507
|
+
const isEndNode = textNode === range.endContainer;
|
|
508
|
+
const start = isStartNode ? range.startOffset : 0;
|
|
509
|
+
const end = isEndNode ? range.endOffset : length;
|
|
510
|
+
if (end > start) {
|
|
511
|
+
const sub = ownerDoc.createRange();
|
|
512
|
+
try {
|
|
513
|
+
sub.setStart(textNode, start);
|
|
514
|
+
sub.setEnd(textNode, end);
|
|
515
|
+
const subRects = sub.getClientRects();
|
|
516
|
+
for (let i = 0; i < subRects.length; i++) {
|
|
517
|
+
const r2 = subRects[i];
|
|
518
|
+
if (r2) results.push({ rect: r2, element: parentElement });
|
|
519
|
+
}
|
|
520
|
+
} catch {
|
|
521
|
+
} finally {
|
|
522
|
+
sub.detach?.();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
current = walker.nextNode();
|
|
526
|
+
}
|
|
527
|
+
return results;
|
|
528
|
+
};
|
|
529
|
+
var connected = (a, b) => {
|
|
530
|
+
const overlap = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top);
|
|
531
|
+
const gap = Math.max(a.left, b.left) - Math.min(a.left + a.width, b.left + b.width);
|
|
532
|
+
return overlap >= Math.min(a.height, b.height) / 2 && gap <= Math.min(a.height, b.height) * 0.5;
|
|
533
|
+
};
|
|
534
|
+
var mergeTextRuns = (rects) => {
|
|
535
|
+
const groups = [];
|
|
536
|
+
for (const rect of rects) {
|
|
537
|
+
const group = [rect];
|
|
538
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
539
|
+
if (groups[i].some((member) => connected(member, rect))) {
|
|
540
|
+
group.push(...groups[i]);
|
|
541
|
+
groups.splice(i, 1);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
groups.push(group);
|
|
545
|
+
}
|
|
546
|
+
return groups.map((group) => {
|
|
547
|
+
const left = Math.min(...group.map((rect) => rect.left));
|
|
548
|
+
const top = Math.min(...group.map((rect) => rect.top));
|
|
549
|
+
return {
|
|
550
|
+
...group[0],
|
|
551
|
+
left,
|
|
552
|
+
top,
|
|
553
|
+
width: Math.max(...group.map((rect) => rect.left + rect.width)) - left,
|
|
554
|
+
height: Math.max(...group.map((rect) => rect.top + rect.height)) - top
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// src/lib/selection-background.ts
|
|
560
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
561
|
+
var createSelectionBackground = () => {
|
|
562
|
+
const overlays = /* @__PURE__ */ new Map();
|
|
563
|
+
let style;
|
|
564
|
+
const clear = (layer) => {
|
|
565
|
+
overlays.get(layer)?.remove();
|
|
566
|
+
overlays.delete(layer);
|
|
567
|
+
layer.removeAttribute("data-lector-selection-active");
|
|
568
|
+
if (overlays.size === 0) {
|
|
569
|
+
style?.remove();
|
|
570
|
+
style = void 0;
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
const update = (selection, layers) => {
|
|
574
|
+
const registered = new Set(layers);
|
|
575
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
576
|
+
if (selection && !selection.isCollapsed) {
|
|
577
|
+
for (let i = 0; i < selection.rangeCount; i++) {
|
|
578
|
+
for (const { rect, element } of getTextNodeClientRects(
|
|
579
|
+
selection.getRangeAt(i)
|
|
580
|
+
)) {
|
|
581
|
+
const layer = element?.closest(".textLayer");
|
|
582
|
+
if (!layer || !element || !registered.has(layer) || rect.width <= 0 || rect.height <= 0)
|
|
583
|
+
continue;
|
|
584
|
+
let entry = byLayer.get(layer);
|
|
585
|
+
if (!entry) {
|
|
586
|
+
entry = { rects: [], element };
|
|
587
|
+
byLayer.set(layer, entry);
|
|
588
|
+
}
|
|
589
|
+
entry.rects.push(rect);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
for (const layer of registered) {
|
|
594
|
+
const entry = byLayer.get(layer);
|
|
595
|
+
if (!entry) {
|
|
596
|
+
clear(layer);
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
layer.removeAttribute("data-lector-selection-active");
|
|
600
|
+
const color = getComputedStyle(
|
|
601
|
+
entry.element,
|
|
602
|
+
"::selection"
|
|
603
|
+
).backgroundColor;
|
|
604
|
+
if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)") {
|
|
605
|
+
clear(layer);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
const bounds = layer.getBoundingClientRect();
|
|
609
|
+
if (!bounds.width || !bounds.height || !layer.clientWidth || !layer.clientHeight) {
|
|
610
|
+
clear(layer);
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
const scaleX = bounds.width / layer.clientWidth;
|
|
614
|
+
const scaleY = bounds.height / layer.clientHeight;
|
|
615
|
+
const rects = mergeTextRuns(
|
|
616
|
+
entry.rects.map((rect) => ({
|
|
617
|
+
left: (rect.left - bounds.left) / scaleX,
|
|
618
|
+
top: (rect.top - bounds.top) / scaleY,
|
|
619
|
+
width: rect.width / scaleX,
|
|
620
|
+
height: rect.height / scaleY
|
|
621
|
+
}))
|
|
622
|
+
);
|
|
623
|
+
let overlay = overlays.get(layer);
|
|
624
|
+
if (!overlay) {
|
|
625
|
+
overlay = document.createElementNS(SVG_NS, "svg");
|
|
626
|
+
overlay.dataset.lectorSelection = "";
|
|
627
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
628
|
+
overlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;pointer-events:none;user-select:none;transform:none;z-index:0;overflow:visible";
|
|
629
|
+
overlay.append(document.createElementNS(SVG_NS, "path"));
|
|
630
|
+
layer.append(overlay);
|
|
631
|
+
overlays.set(layer, overlay);
|
|
632
|
+
}
|
|
633
|
+
const path = overlay.firstElementChild;
|
|
634
|
+
path.setAttribute(
|
|
635
|
+
"d",
|
|
636
|
+
rects.map(
|
|
637
|
+
(rect) => `M${rect.left} ${rect.top}h${rect.width}v${rect.height}h${-rect.width}Z`
|
|
638
|
+
).join(" ")
|
|
639
|
+
);
|
|
640
|
+
path.setAttribute("fill", color);
|
|
641
|
+
if (!style) {
|
|
642
|
+
style = document.createElement("style");
|
|
643
|
+
style.textContent = ".textLayer[data-lector-selection-active] ::selection { background-color: transparent !important; }";
|
|
644
|
+
document.head.append(style);
|
|
645
|
+
}
|
|
646
|
+
layer.setAttribute("data-lector-selection-active", "");
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
return { update, clear };
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// src/lib/text-selection.ts
|
|
653
|
+
var interactive = 'a, button, input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="button"], [role="link"], [role="textbox"], [data-lector-selection-ignore]';
|
|
654
|
+
var distance = (value, start, end) => Math.max(start - value, 0, value - end);
|
|
655
|
+
function nearestBox(boxes, x, y) {
|
|
656
|
+
let nearest;
|
|
657
|
+
let best = Number.POSITIVE_INFINITY;
|
|
658
|
+
for (const box of boxes) {
|
|
659
|
+
const { rect } = box;
|
|
660
|
+
const dx = distance(x, rect.left, rect.right);
|
|
661
|
+
const dy = distance(y, rect.top, rect.bottom);
|
|
662
|
+
const score = dy * dy + Math.min(dx, 30) ** 2 / 100 + dx / 1e4;
|
|
663
|
+
if (score < best) {
|
|
664
|
+
best = score;
|
|
665
|
+
nearest = box;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return nearest;
|
|
669
|
+
}
|
|
670
|
+
function caretInBox({ node, rect }, x, y) {
|
|
671
|
+
const doc = node.ownerDocument;
|
|
672
|
+
const px = Math.min(Math.max(x, rect.left + 0.1), rect.right - 0.1);
|
|
673
|
+
const py = Math.min(Math.max(y, rect.top + 0.1), rect.bottom - 0.1);
|
|
674
|
+
const caretDoc = doc;
|
|
675
|
+
const position = caretDoc.caretPositionFromPoint?.(px, py);
|
|
676
|
+
if (position?.offsetNode === node) return { node, offset: position.offset };
|
|
677
|
+
const range = caretDoc.caretRangeFromPoint?.(px, py);
|
|
678
|
+
if (range?.startContainer === node)
|
|
679
|
+
return { node, offset: range.startOffset };
|
|
680
|
+
const probe = doc.createRange();
|
|
681
|
+
let offset3 = 0;
|
|
682
|
+
let best = Number.POSITIVE_INFINITY;
|
|
683
|
+
let previous;
|
|
684
|
+
for (let i = 0; i <= node.length; ) {
|
|
685
|
+
probe.setStart(node, i);
|
|
686
|
+
probe.collapse(true);
|
|
687
|
+
const rects = Array.from(probe.getClientRects());
|
|
688
|
+
if (!rects.length && i === node.length && previous) {
|
|
689
|
+
probe.setStart(node, previous.offset);
|
|
690
|
+
probe.setEnd(node, i);
|
|
691
|
+
const glyph = probe.getBoundingClientRect();
|
|
692
|
+
const prior = previous.rect;
|
|
693
|
+
rects.push(
|
|
694
|
+
new DOMRect(
|
|
695
|
+
glyph.left + glyph.right - prior.right,
|
|
696
|
+
glyph.top + glyph.bottom - prior.bottom,
|
|
697
|
+
prior.width,
|
|
698
|
+
prior.height
|
|
699
|
+
)
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
for (const r2 of rects) {
|
|
703
|
+
const score = distance(x, r2.left, r2.right) ** 2 + distance(y, r2.top, r2.bottom) ** 2;
|
|
704
|
+
if (score < best) {
|
|
705
|
+
best = score;
|
|
706
|
+
offset3 = i;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (rects[0]) previous = { offset: i, rect: rects[0] };
|
|
710
|
+
i += (node.data.codePointAt(i) ?? 0) > 65535 ? 2 : 1;
|
|
711
|
+
}
|
|
712
|
+
return { node, offset: offset3 };
|
|
713
|
+
}
|
|
714
|
+
function createSelectionManager(viewport, doc, isPersistent) {
|
|
715
|
+
const layers = /* @__PURE__ */ new Set();
|
|
716
|
+
const background = createSelectionBackground();
|
|
717
|
+
const container = viewport.current;
|
|
718
|
+
const win = doc.defaultView;
|
|
719
|
+
const controller = new AbortController();
|
|
720
|
+
const { signal } = controller;
|
|
721
|
+
let drag = null;
|
|
722
|
+
let boxes = null;
|
|
723
|
+
let scrollLeft = container?.scrollLeft;
|
|
724
|
+
let scrollTop = container?.scrollTop;
|
|
725
|
+
let frame = 0;
|
|
726
|
+
let mousePointer = true;
|
|
727
|
+
const invalidate = () => {
|
|
728
|
+
boxes = null;
|
|
729
|
+
if (drag && !frame) frame = win.requestAnimationFrame(tick);
|
|
730
|
+
};
|
|
731
|
+
const contains = (node) => !!node && [...layers].some((layer) => layer.contains(node));
|
|
732
|
+
const reset = () => {
|
|
733
|
+
drag = null;
|
|
734
|
+
boxes = null;
|
|
735
|
+
win.cancelAnimationFrame(frame);
|
|
736
|
+
frame = 0;
|
|
737
|
+
for (const layer of layers) layer.classList.remove("selecting");
|
|
738
|
+
};
|
|
739
|
+
const readBoxes = () => {
|
|
740
|
+
if (boxes) return boxes;
|
|
741
|
+
boxes = [];
|
|
742
|
+
for (const layer of layers) {
|
|
743
|
+
const walker = doc.createTreeWalker(layer, NodeFilter.SHOW_TEXT);
|
|
744
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
745
|
+
const parent = node.parentElement;
|
|
746
|
+
if (!node.textContent?.trim() || !parent || parent.closest('[role="img"], .endOfContent') || win.getComputedStyle(parent).userSelect === "none")
|
|
747
|
+
continue;
|
|
748
|
+
const range = doc.createRange();
|
|
749
|
+
range.selectNodeContents(node);
|
|
750
|
+
for (const rect of range.getClientRects()) {
|
|
751
|
+
if (rect.width > 0 && rect.height > 0)
|
|
752
|
+
boxes.push({ node, rect });
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return boxes;
|
|
757
|
+
};
|
|
758
|
+
const resolve = (x, y) => {
|
|
759
|
+
if (scrollLeft !== container?.scrollLeft || scrollTop !== container?.scrollTop) {
|
|
760
|
+
boxes = null;
|
|
761
|
+
scrollLeft = container?.scrollLeft;
|
|
762
|
+
scrollTop = container?.scrollTop;
|
|
763
|
+
}
|
|
764
|
+
const box = nearestBox(readBoxes(), x, y);
|
|
765
|
+
return box ? caretInBox(box, x, y) : null;
|
|
766
|
+
};
|
|
767
|
+
const update = () => {
|
|
768
|
+
if (!drag) return;
|
|
769
|
+
if (!drag.anchor.node.isConnected) {
|
|
770
|
+
reset();
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const focus = resolve(drag.x, drag.y);
|
|
774
|
+
const selection = doc.getSelection();
|
|
775
|
+
if (!focus || !selection) return;
|
|
776
|
+
if (selection.anchorNode === drag.anchor.node && selection.anchorOffset === drag.anchor.offset && selection.focusNode === focus.node && selection.focusOffset === focus.offset)
|
|
777
|
+
return;
|
|
778
|
+
selection.setBaseAndExtent(
|
|
779
|
+
drag.anchor.node,
|
|
780
|
+
drag.anchor.offset,
|
|
781
|
+
focus.node,
|
|
782
|
+
focus.offset
|
|
783
|
+
);
|
|
784
|
+
};
|
|
785
|
+
const tick = () => {
|
|
786
|
+
frame = 0;
|
|
787
|
+
if (!drag) return;
|
|
788
|
+
let scrolled = false;
|
|
789
|
+
const container2 = viewport.current;
|
|
790
|
+
if (container2 && !isPersistent()) {
|
|
791
|
+
const r2 = container2.getBoundingClientRect();
|
|
792
|
+
const edge = 24;
|
|
793
|
+
const speed = (point, start, end) => point < start + edge ? -Math.min(18, (start + edge - point) / 2) : point > end - edge ? Math.min(18, (point - end + edge) / 2) : 0;
|
|
794
|
+
const dx = speed(drag.x, r2.left, r2.right);
|
|
795
|
+
const dy = speed(drag.y, r2.top, r2.bottom);
|
|
796
|
+
if (dx || dy) {
|
|
797
|
+
const left = container2.scrollLeft;
|
|
798
|
+
const top = container2.scrollTop;
|
|
799
|
+
container2.scrollBy(dx, dy);
|
|
800
|
+
if (container2.scrollLeft !== left || container2.scrollTop !== top) {
|
|
801
|
+
boxes = null;
|
|
802
|
+
scrolled = true;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
update();
|
|
807
|
+
if (drag && scrolled && !frame) frame = win.requestAnimationFrame(tick);
|
|
808
|
+
};
|
|
809
|
+
doc.addEventListener(
|
|
810
|
+
"pointerdown",
|
|
811
|
+
(event) => {
|
|
812
|
+
mousePointer = event.pointerType === "mouse";
|
|
813
|
+
if (!mousePointer) reset();
|
|
814
|
+
},
|
|
815
|
+
{ signal }
|
|
816
|
+
);
|
|
817
|
+
doc.addEventListener(
|
|
818
|
+
"mousedown",
|
|
819
|
+
(event) => {
|
|
820
|
+
reset();
|
|
821
|
+
if (!mousePointer || event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.altKey || event.detail > 1)
|
|
822
|
+
return;
|
|
823
|
+
const target = event.target;
|
|
824
|
+
if (!(target instanceof Element) || target.closest(interactive)) return;
|
|
825
|
+
const onPage = [...layers].some(
|
|
826
|
+
(layer) => layer.parentElement?.contains(target)
|
|
827
|
+
);
|
|
828
|
+
if (!onPage && !(viewport.current === target || viewport.current?.contains(target) && target.contains([...layers][0] ?? null)))
|
|
829
|
+
return;
|
|
830
|
+
if (target instanceof HTMLElement && /auto|scroll/.test(win.getComputedStyle(target).overflow)) {
|
|
831
|
+
const r2 = target.getBoundingClientRect();
|
|
832
|
+
const scrollbarX = (target.offsetWidth - target.clientWidth - target.clientLeft * 2) * r2.width / target.offsetWidth;
|
|
833
|
+
const scrollbarY = (target.offsetHeight - target.clientHeight - target.clientTop * 2) * r2.height / target.offsetHeight;
|
|
834
|
+
if (event.clientX >= r2.right - scrollbarX || event.clientY >= r2.bottom - scrollbarY)
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const focus = resolve(event.clientX, event.clientY);
|
|
838
|
+
const selection = doc.getSelection();
|
|
839
|
+
if (!focus || !selection) return;
|
|
840
|
+
const anchor = event.shiftKey && contains(selection.anchorNode) ? { node: selection.anchorNode, offset: selection.anchorOffset } : focus;
|
|
841
|
+
event.preventDefault();
|
|
842
|
+
if (viewport.current && viewport.current.tabIndex >= 0)
|
|
843
|
+
viewport.current.focus({ preventScroll: true });
|
|
844
|
+
drag = { anchor, x: event.clientX, y: event.clientY };
|
|
845
|
+
for (const layer of layers) layer.classList.add("selecting");
|
|
846
|
+
update();
|
|
847
|
+
},
|
|
848
|
+
{ signal }
|
|
849
|
+
);
|
|
850
|
+
doc.addEventListener(
|
|
851
|
+
"mousemove",
|
|
852
|
+
(event) => {
|
|
853
|
+
if (!drag) return;
|
|
854
|
+
if (!(event.buttons & 1)) {
|
|
855
|
+
reset();
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
event.preventDefault();
|
|
859
|
+
drag.x = event.clientX;
|
|
860
|
+
drag.y = event.clientY;
|
|
861
|
+
update();
|
|
862
|
+
if (!frame) frame = win.requestAnimationFrame(tick);
|
|
863
|
+
},
|
|
864
|
+
{ signal }
|
|
865
|
+
);
|
|
866
|
+
doc.addEventListener(
|
|
867
|
+
"mouseup",
|
|
868
|
+
(event) => {
|
|
869
|
+
if (drag && event.button === 0) {
|
|
870
|
+
drag.x = event.clientX;
|
|
871
|
+
drag.y = event.clientY;
|
|
872
|
+
update();
|
|
873
|
+
reset();
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
{ signal }
|
|
877
|
+
);
|
|
878
|
+
doc.addEventListener(
|
|
879
|
+
"selectionchange",
|
|
880
|
+
() => background.update(doc.getSelection(), layers),
|
|
881
|
+
{ signal }
|
|
882
|
+
);
|
|
883
|
+
doc.addEventListener("scroll", invalidate, {
|
|
884
|
+
signal,
|
|
885
|
+
capture: true,
|
|
886
|
+
passive: true
|
|
887
|
+
});
|
|
888
|
+
win.addEventListener("resize", invalidate, { signal });
|
|
889
|
+
win.addEventListener("blur", reset, { signal });
|
|
890
|
+
doc.addEventListener("pointercancel", reset, { signal });
|
|
891
|
+
doc.addEventListener(
|
|
892
|
+
"keydown",
|
|
893
|
+
(event) => {
|
|
894
|
+
if (event.key === "Escape") reset();
|
|
895
|
+
},
|
|
896
|
+
{ signal }
|
|
897
|
+
);
|
|
898
|
+
return {
|
|
899
|
+
resolve,
|
|
900
|
+
add(layer) {
|
|
901
|
+
layers.add(layer);
|
|
902
|
+
if (container) viewportManagers.set(container, { resolve });
|
|
903
|
+
invalidate();
|
|
904
|
+
return () => {
|
|
905
|
+
background.clear(layer);
|
|
906
|
+
layers.delete(layer);
|
|
907
|
+
layer.classList.remove("selecting");
|
|
908
|
+
invalidate();
|
|
909
|
+
if (drag && layer.contains(drag.anchor.node)) reset();
|
|
910
|
+
if (!layers.size) {
|
|
911
|
+
reset();
|
|
912
|
+
controller.abort();
|
|
913
|
+
managers.delete(viewport);
|
|
914
|
+
if (container) viewportManagers.delete(container);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
var viewportManagers = /* @__PURE__ */ new WeakMap();
|
|
921
|
+
function resolveTextSelectionCaret(viewport, x, y) {
|
|
922
|
+
return viewportManagers.get(viewport)?.resolve(x, y);
|
|
923
|
+
}
|
|
924
|
+
var managers = /* @__PURE__ */ new WeakMap();
|
|
925
|
+
function bindTextSelection(layer, viewport, isPersistent = () => false) {
|
|
926
|
+
let manager = managers.get(viewport);
|
|
927
|
+
if (!manager) {
|
|
928
|
+
manager = createSelectionManager(
|
|
929
|
+
viewport,
|
|
930
|
+
layer.ownerDocument,
|
|
931
|
+
isPersistent
|
|
932
|
+
);
|
|
933
|
+
managers.set(viewport, manager);
|
|
934
|
+
}
|
|
935
|
+
return manager.add(layer);
|
|
936
|
+
}
|
|
937
|
+
|
|
467
938
|
// src/lib/pdf-selection.ts
|
|
468
939
|
var sameAnchor = (a, b) => a.pageNumber === b.pageNumber && a.offset === b.offset;
|
|
469
940
|
var compare = (a, b) => a.pageNumber - b.pageNumber || a.offset - b.offset;
|
|
@@ -494,6 +965,9 @@ var PDFSelectionController = class {
|
|
|
494
965
|
this.listeners.delete(listener);
|
|
495
966
|
};
|
|
496
967
|
};
|
|
968
|
+
get isConnected() {
|
|
969
|
+
return this.container !== null;
|
|
970
|
+
}
|
|
497
971
|
get isDragging() {
|
|
498
972
|
return this.drag !== null;
|
|
499
973
|
}
|
|
@@ -838,6 +1312,8 @@ var PDFSelectionController = class {
|
|
|
838
1312
|
});
|
|
839
1313
|
}
|
|
840
1314
|
caret(x, y) {
|
|
1315
|
+
const geometry = resolveTextSelectionCaret(this.container, x, y);
|
|
1316
|
+
if (geometry) return this.anchorFor(geometry.node, geometry.offset);
|
|
841
1317
|
const owner = this.container.ownerDocument;
|
|
842
1318
|
const doc = owner;
|
|
843
1319
|
const position = doc.caretPositionFromPoint?.(x, y);
|
|
@@ -926,13 +1402,15 @@ var PDFSelectionController = class {
|
|
|
926
1402
|
const down = (event) => {
|
|
927
1403
|
if (event.defaultPrevented) return;
|
|
928
1404
|
const target = event.target instanceof Element ? event.target : null;
|
|
929
|
-
if (target?.closest(
|
|
1405
|
+
if (target?.closest(
|
|
1406
|
+
'button, a, input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role=button], [role=link], [role=textbox], [data-selection-tooltip], [data-lector-selection-ignore]'
|
|
1407
|
+
))
|
|
930
1408
|
return;
|
|
931
1409
|
if (!container.contains(event.target)) {
|
|
932
1410
|
this.clear();
|
|
933
1411
|
return;
|
|
934
1412
|
}
|
|
935
|
-
if (event.defaultPrevented || event.button !== 0 || event.pointerType === "touch" || !container.contains(event.target))
|
|
1413
|
+
if (event.defaultPrevented || event.button !== 0 || event.pointerType === "touch" || event.ctrlKey || event.metaKey || event.altKey || !container.contains(event.target))
|
|
936
1414
|
return;
|
|
937
1415
|
const point = this.caret(event.clientX, event.clientY);
|
|
938
1416
|
if (!point) return;
|
|
@@ -3836,88 +4314,6 @@ var CanvasLayer = memo(
|
|
|
3836
4314
|
);
|
|
3837
4315
|
CanvasLayer.displayName = "CanvasLayer";
|
|
3838
4316
|
|
|
3839
|
-
// src/lib/selection-rects.ts
|
|
3840
|
-
var getTextNodeClientRects = (range) => {
|
|
3841
|
-
const root = range.commonAncestorContainer;
|
|
3842
|
-
const ownerDoc = root.ownerDocument ?? document;
|
|
3843
|
-
if (root.nodeType === Node.TEXT_NODE) {
|
|
3844
|
-
const parentElement = root.parentElement;
|
|
3845
|
-
return Array.from(range.getClientRects()).map((rect) => ({
|
|
3846
|
-
rect,
|
|
3847
|
-
element: parentElement
|
|
3848
|
-
}));
|
|
3849
|
-
}
|
|
3850
|
-
const walker = ownerDoc.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
3851
|
-
acceptNode(node) {
|
|
3852
|
-
if (!node.nodeValue || node.nodeValue.length === 0) {
|
|
3853
|
-
return NodeFilter.FILTER_REJECT;
|
|
3854
|
-
}
|
|
3855
|
-
try {
|
|
3856
|
-
return range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
3857
|
-
} catch {
|
|
3858
|
-
return NodeFilter.FILTER_REJECT;
|
|
3859
|
-
}
|
|
3860
|
-
}
|
|
3861
|
-
});
|
|
3862
|
-
const results = [];
|
|
3863
|
-
let current = walker.nextNode();
|
|
3864
|
-
while (current) {
|
|
3865
|
-
const textNode = current;
|
|
3866
|
-
const parentElement = textNode.parentElement;
|
|
3867
|
-
const length = textNode.nodeValue?.length ?? 0;
|
|
3868
|
-
const isStartNode = textNode === range.startContainer;
|
|
3869
|
-
const isEndNode = textNode === range.endContainer;
|
|
3870
|
-
const start = isStartNode ? range.startOffset : 0;
|
|
3871
|
-
const end = isEndNode ? range.endOffset : length;
|
|
3872
|
-
if (end > start) {
|
|
3873
|
-
const sub = ownerDoc.createRange();
|
|
3874
|
-
try {
|
|
3875
|
-
sub.setStart(textNode, start);
|
|
3876
|
-
sub.setEnd(textNode, end);
|
|
3877
|
-
const subRects = sub.getClientRects();
|
|
3878
|
-
for (let i = 0; i < subRects.length; i++) {
|
|
3879
|
-
const r2 = subRects[i];
|
|
3880
|
-
if (r2) results.push({ rect: r2, element: parentElement });
|
|
3881
|
-
}
|
|
3882
|
-
} catch {
|
|
3883
|
-
} finally {
|
|
3884
|
-
sub.detach?.();
|
|
3885
|
-
}
|
|
3886
|
-
}
|
|
3887
|
-
current = walker.nextNode();
|
|
3888
|
-
}
|
|
3889
|
-
return results;
|
|
3890
|
-
};
|
|
3891
|
-
var connected = (a, b) => {
|
|
3892
|
-
const overlap = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top);
|
|
3893
|
-
const gap = Math.max(a.left, b.left) - Math.min(a.left + a.width, b.left + b.width);
|
|
3894
|
-
return overlap >= Math.min(a.height, b.height) / 2 && gap <= Math.min(a.height, b.height) * 0.5;
|
|
3895
|
-
};
|
|
3896
|
-
var mergeTextRuns = (rects) => {
|
|
3897
|
-
const groups = [];
|
|
3898
|
-
for (const rect of rects) {
|
|
3899
|
-
const group = [rect];
|
|
3900
|
-
for (let i = groups.length - 1; i >= 0; i--) {
|
|
3901
|
-
if (groups[i].some((member) => connected(member, rect))) {
|
|
3902
|
-
group.push(...groups[i]);
|
|
3903
|
-
groups.splice(i, 1);
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
groups.push(group);
|
|
3907
|
-
}
|
|
3908
|
-
return groups.map((group) => {
|
|
3909
|
-
const left = Math.min(...group.map((rect) => rect.left));
|
|
3910
|
-
const top = Math.min(...group.map((rect) => rect.top));
|
|
3911
|
-
return {
|
|
3912
|
-
...group[0],
|
|
3913
|
-
left,
|
|
3914
|
-
top,
|
|
3915
|
-
width: Math.max(...group.map((rect) => rect.left + rect.width)) - left,
|
|
3916
|
-
height: Math.max(...group.map((rect) => rect.top + rect.height)) - top
|
|
3917
|
-
};
|
|
3918
|
-
});
|
|
3919
|
-
};
|
|
3920
|
-
|
|
3921
4317
|
// src/lib/selection-text.ts
|
|
3922
4318
|
function textLayer(node) {
|
|
3923
4319
|
const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
@@ -4750,244 +5146,6 @@ var HighlightLayer = forwardRef(({ asChild, className, style, ...props }, ref) =
|
|
|
4750
5146
|
}) });
|
|
4751
5147
|
});
|
|
4752
5148
|
HighlightLayer.displayName = "HighlightLayer";
|
|
4753
|
-
|
|
4754
|
-
// src/lib/selection-background.ts
|
|
4755
|
-
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
4756
|
-
var createSelectionBackground = () => {
|
|
4757
|
-
const overlays = /* @__PURE__ */ new Map();
|
|
4758
|
-
let style;
|
|
4759
|
-
const clear = (layer) => {
|
|
4760
|
-
overlays.get(layer)?.remove();
|
|
4761
|
-
overlays.delete(layer);
|
|
4762
|
-
layer.removeAttribute("data-lector-selection-active");
|
|
4763
|
-
if (overlays.size === 0) {
|
|
4764
|
-
style?.remove();
|
|
4765
|
-
style = void 0;
|
|
4766
|
-
}
|
|
4767
|
-
};
|
|
4768
|
-
const update = (selection, layers) => {
|
|
4769
|
-
const registered = new Set(layers);
|
|
4770
|
-
const byLayer = /* @__PURE__ */ new Map();
|
|
4771
|
-
if (selection && !selection.isCollapsed) {
|
|
4772
|
-
for (let i = 0; i < selection.rangeCount; i++) {
|
|
4773
|
-
for (const { rect, element } of getTextNodeClientRects(
|
|
4774
|
-
selection.getRangeAt(i)
|
|
4775
|
-
)) {
|
|
4776
|
-
const layer = element?.closest(".textLayer");
|
|
4777
|
-
if (!layer || !element || !registered.has(layer) || rect.width <= 0 || rect.height <= 0)
|
|
4778
|
-
continue;
|
|
4779
|
-
let entry = byLayer.get(layer);
|
|
4780
|
-
if (!entry) {
|
|
4781
|
-
entry = { rects: [], element };
|
|
4782
|
-
byLayer.set(layer, entry);
|
|
4783
|
-
}
|
|
4784
|
-
entry.rects.push(rect);
|
|
4785
|
-
}
|
|
4786
|
-
}
|
|
4787
|
-
}
|
|
4788
|
-
for (const layer of registered) {
|
|
4789
|
-
const entry = byLayer.get(layer);
|
|
4790
|
-
if (!entry) {
|
|
4791
|
-
clear(layer);
|
|
4792
|
-
continue;
|
|
4793
|
-
}
|
|
4794
|
-
layer.removeAttribute("data-lector-selection-active");
|
|
4795
|
-
const color = getComputedStyle(
|
|
4796
|
-
entry.element,
|
|
4797
|
-
"::selection"
|
|
4798
|
-
).backgroundColor;
|
|
4799
|
-
if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)") {
|
|
4800
|
-
clear(layer);
|
|
4801
|
-
continue;
|
|
4802
|
-
}
|
|
4803
|
-
const bounds = layer.getBoundingClientRect();
|
|
4804
|
-
if (!bounds.width || !bounds.height || !layer.clientWidth || !layer.clientHeight) {
|
|
4805
|
-
clear(layer);
|
|
4806
|
-
continue;
|
|
4807
|
-
}
|
|
4808
|
-
const scaleX = bounds.width / layer.clientWidth;
|
|
4809
|
-
const scaleY = bounds.height / layer.clientHeight;
|
|
4810
|
-
const rects = mergeTextRuns(
|
|
4811
|
-
entry.rects.map((rect) => ({
|
|
4812
|
-
left: (rect.left - bounds.left) / scaleX,
|
|
4813
|
-
top: (rect.top - bounds.top) / scaleY,
|
|
4814
|
-
width: rect.width / scaleX,
|
|
4815
|
-
height: rect.height / scaleY
|
|
4816
|
-
}))
|
|
4817
|
-
);
|
|
4818
|
-
let overlay = overlays.get(layer);
|
|
4819
|
-
if (!overlay) {
|
|
4820
|
-
overlay = document.createElementNS(SVG_NS, "svg");
|
|
4821
|
-
overlay.dataset.lectorSelection = "";
|
|
4822
|
-
overlay.setAttribute("aria-hidden", "true");
|
|
4823
|
-
overlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;pointer-events:none;user-select:none;transform:none;z-index:0;overflow:visible";
|
|
4824
|
-
overlay.append(document.createElementNS(SVG_NS, "path"));
|
|
4825
|
-
layer.append(overlay);
|
|
4826
|
-
overlays.set(layer, overlay);
|
|
4827
|
-
}
|
|
4828
|
-
const path = overlay.firstElementChild;
|
|
4829
|
-
path.setAttribute(
|
|
4830
|
-
"d",
|
|
4831
|
-
rects.map(
|
|
4832
|
-
(rect) => `M${rect.left} ${rect.top}h${rect.width}v${rect.height}h${-rect.width}Z`
|
|
4833
|
-
).join(" ")
|
|
4834
|
-
);
|
|
4835
|
-
path.setAttribute("fill", color);
|
|
4836
|
-
if (!style) {
|
|
4837
|
-
style = document.createElement("style");
|
|
4838
|
-
style.textContent = ".textLayer[data-lector-selection-active] ::selection { background-color: transparent !important; }";
|
|
4839
|
-
document.head.append(style);
|
|
4840
|
-
}
|
|
4841
|
-
layer.setAttribute("data-lector-selection-active", "");
|
|
4842
|
-
}
|
|
4843
|
-
};
|
|
4844
|
-
return { update, clear };
|
|
4845
|
-
};
|
|
4846
|
-
|
|
4847
|
-
// src/hooks/layers/useTextLayer.tsx
|
|
4848
|
-
var createTextSelectionManager = () => {
|
|
4849
|
-
const textLayers = /* @__PURE__ */ new Map();
|
|
4850
|
-
const selectionBackground = createSelectionBackground();
|
|
4851
|
-
let selectionChangeAbortController = null;
|
|
4852
|
-
let isPointerDown = false;
|
|
4853
|
-
let prevRange = null;
|
|
4854
|
-
let isFirefox;
|
|
4855
|
-
const removeGlobalSelectionListener = (textLayerDiv) => {
|
|
4856
|
-
selectionBackground.clear(textLayerDiv);
|
|
4857
|
-
textLayers.delete(textLayerDiv);
|
|
4858
|
-
if (textLayers.size === 0) {
|
|
4859
|
-
selectionChangeAbortController?.abort();
|
|
4860
|
-
selectionChangeAbortController = null;
|
|
4861
|
-
}
|
|
4862
|
-
};
|
|
4863
|
-
const enableGlobalSelectionListener = () => {
|
|
4864
|
-
if (selectionChangeAbortController) {
|
|
4865
|
-
return;
|
|
4866
|
-
}
|
|
4867
|
-
selectionChangeAbortController = new AbortController();
|
|
4868
|
-
const { signal } = selectionChangeAbortController;
|
|
4869
|
-
const reset = (endDiv, textLayer2) => {
|
|
4870
|
-
if (endDiv.parentNode !== textLayer2) {
|
|
4871
|
-
textLayer2.appendChild(endDiv);
|
|
4872
|
-
}
|
|
4873
|
-
endDiv.style.width = "";
|
|
4874
|
-
endDiv.style.height = "";
|
|
4875
|
-
textLayer2.classList.remove("selecting");
|
|
4876
|
-
};
|
|
4877
|
-
document.addEventListener(
|
|
4878
|
-
"pointerdown",
|
|
4879
|
-
() => {
|
|
4880
|
-
isPointerDown = true;
|
|
4881
|
-
},
|
|
4882
|
-
{ signal }
|
|
4883
|
-
);
|
|
4884
|
-
document.addEventListener(
|
|
4885
|
-
"pointerup",
|
|
4886
|
-
() => {
|
|
4887
|
-
isPointerDown = false;
|
|
4888
|
-
textLayers.forEach(reset);
|
|
4889
|
-
},
|
|
4890
|
-
{ signal }
|
|
4891
|
-
);
|
|
4892
|
-
window.addEventListener(
|
|
4893
|
-
"blur",
|
|
4894
|
-
() => {
|
|
4895
|
-
isPointerDown = false;
|
|
4896
|
-
textLayers.forEach(reset);
|
|
4897
|
-
},
|
|
4898
|
-
{ signal }
|
|
4899
|
-
);
|
|
4900
|
-
document.addEventListener(
|
|
4901
|
-
"keyup",
|
|
4902
|
-
() => {
|
|
4903
|
-
if (!isPointerDown) {
|
|
4904
|
-
textLayers.forEach(reset);
|
|
4905
|
-
}
|
|
4906
|
-
},
|
|
4907
|
-
{ signal }
|
|
4908
|
-
);
|
|
4909
|
-
document.addEventListener(
|
|
4910
|
-
"selectionchange",
|
|
4911
|
-
() => {
|
|
4912
|
-
const selection = document.getSelection();
|
|
4913
|
-
selectionBackground.update(selection, textLayers.keys());
|
|
4914
|
-
if (!selection || selection.rangeCount === 0) {
|
|
4915
|
-
textLayers.forEach(reset);
|
|
4916
|
-
return;
|
|
4917
|
-
}
|
|
4918
|
-
const activeTextLayers = /* @__PURE__ */ new Set();
|
|
4919
|
-
for (let i = 0; i < selection.rangeCount; i++) {
|
|
4920
|
-
const range = selection.getRangeAt(i);
|
|
4921
|
-
for (const textLayerDiv of textLayers.keys()) {
|
|
4922
|
-
if (!activeTextLayers.has(textLayerDiv) && range.intersectsNode(textLayerDiv)) {
|
|
4923
|
-
activeTextLayers.add(textLayerDiv);
|
|
4924
|
-
}
|
|
4925
|
-
}
|
|
4926
|
-
}
|
|
4927
|
-
for (const [textLayerDiv, endDiv] of textLayers) {
|
|
4928
|
-
if (activeTextLayers.has(textLayerDiv)) {
|
|
4929
|
-
textLayerDiv.classList.add("selecting");
|
|
4930
|
-
} else {
|
|
4931
|
-
reset(endDiv, textLayerDiv);
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
if (isFirefox === void 0) {
|
|
4935
|
-
const firstTextLayer = textLayers.keys().next().value;
|
|
4936
|
-
if (firstTextLayer) {
|
|
4937
|
-
isFirefox = getComputedStyle(firstTextLayer).getPropertyValue(
|
|
4938
|
-
"-moz-user-select"
|
|
4939
|
-
) === "none";
|
|
4940
|
-
}
|
|
4941
|
-
}
|
|
4942
|
-
if (isFirefox) {
|
|
4943
|
-
return;
|
|
4944
|
-
}
|
|
4945
|
-
try {
|
|
4946
|
-
const range = selection.getRangeAt(0);
|
|
4947
|
-
const modifyStart = prevRange && (range.compareBoundaryPoints(Range.END_TO_END, prevRange) === 0 || range.compareBoundaryPoints(Range.START_TO_END, prevRange) === 0);
|
|
4948
|
-
let anchor = modifyStart ? range.startContainer : range.endContainer;
|
|
4949
|
-
if (anchor.nodeType === Node.TEXT_NODE) {
|
|
4950
|
-
anchor = anchor.parentNode;
|
|
4951
|
-
}
|
|
4952
|
-
const parentTextLayer = anchor.parentElement?.closest(
|
|
4953
|
-
".textLayer"
|
|
4954
|
-
);
|
|
4955
|
-
const endDiv = textLayers.get(parentTextLayer);
|
|
4956
|
-
if (endDiv && parentTextLayer) {
|
|
4957
|
-
endDiv.style.width = parentTextLayer.style.width;
|
|
4958
|
-
endDiv.style.height = parentTextLayer.style.height;
|
|
4959
|
-
const insertTarget = modifyStart ? anchor : anchor.nextSibling;
|
|
4960
|
-
if (anchor.parentElement && insertTarget) {
|
|
4961
|
-
anchor.parentElement.insertBefore(endDiv, insertTarget);
|
|
4962
|
-
}
|
|
4963
|
-
}
|
|
4964
|
-
prevRange = range.cloneRange();
|
|
4965
|
-
} catch {
|
|
4966
|
-
}
|
|
4967
|
-
},
|
|
4968
|
-
{ signal }
|
|
4969
|
-
);
|
|
4970
|
-
};
|
|
4971
|
-
const bindMouseEvents2 = (textLayerDiv, endOfContent) => {
|
|
4972
|
-
if (textLayerDiv._textSelectionBound) {
|
|
4973
|
-
return;
|
|
4974
|
-
}
|
|
4975
|
-
textLayerDiv._textSelectionBound = true;
|
|
4976
|
-
textLayers.set(textLayerDiv, endOfContent);
|
|
4977
|
-
enableGlobalSelectionListener();
|
|
4978
|
-
const handleMouseDown = () => {
|
|
4979
|
-
textLayerDiv.classList.add("selecting");
|
|
4980
|
-
};
|
|
4981
|
-
textLayerDiv.addEventListener("mousedown", handleMouseDown);
|
|
4982
|
-
textLayerDiv._cleanupTextSelection = () => {
|
|
4983
|
-
textLayerDiv.removeEventListener("mousedown", handleMouseDown);
|
|
4984
|
-
removeGlobalSelectionListener(textLayerDiv);
|
|
4985
|
-
delete textLayerDiv._textSelectionBound;
|
|
4986
|
-
};
|
|
4987
|
-
};
|
|
4988
|
-
return bindMouseEvents2;
|
|
4989
|
-
};
|
|
4990
|
-
var bindMouseEvents = createTextSelectionManager();
|
|
4991
5149
|
var TEXT_BUILD_IDLE_MS = 300;
|
|
4992
5150
|
var TEXT_SCROLL_SETTLE_MS = 160;
|
|
4993
5151
|
var VIEWPORT_ATTACH_MAX_FRAMES = 300;
|
|
@@ -5006,6 +5164,7 @@ var useTextLayer = () => {
|
|
|
5006
5164
|
}
|
|
5007
5165
|
let isCancelled = false;
|
|
5008
5166
|
let unregisterSelection;
|
|
5167
|
+
let cleanupMouseSelection;
|
|
5009
5168
|
let buildTimer = null;
|
|
5010
5169
|
let unsubscribe = null;
|
|
5011
5170
|
let built = false;
|
|
@@ -5033,10 +5192,11 @@ var useTextLayer = () => {
|
|
|
5033
5192
|
if (isCancelled || gen !== buildGen || textContainerRef.current !== textContainer) {
|
|
5034
5193
|
return;
|
|
5035
5194
|
}
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5195
|
+
cleanupMouseSelection = bindTextSelection(
|
|
5196
|
+
textContainer,
|
|
5197
|
+
viewportRef,
|
|
5198
|
+
() => selection?.isConnected ?? false
|
|
5199
|
+
);
|
|
5040
5200
|
const viewport = pdfPageProxy.getViewport({ scale: 1 });
|
|
5041
5201
|
const unrotated = pdfPageProxy.getViewport({ scale: 1, rotation: 0 });
|
|
5042
5202
|
textContainer.style.width = `${unrotated.width}px`;
|
|
@@ -5111,10 +5271,7 @@ var useTextLayer = () => {
|
|
|
5111
5271
|
textLayerRef.current.cancel();
|
|
5112
5272
|
textLayerRef.current = null;
|
|
5113
5273
|
}
|
|
5114
|
-
|
|
5115
|
-
textContainer._cleanupTextSelection();
|
|
5116
|
-
delete textContainer._cleanupTextSelection;
|
|
5117
|
-
}
|
|
5274
|
+
cleanupMouseSelection?.();
|
|
5118
5275
|
};
|
|
5119
5276
|
}, [
|
|
5120
5277
|
pdfPageProxy.streamTextContent,
|
|
@@ -5717,9 +5874,9 @@ var useVisiblePage = ({
|
|
|
5717
5874
|
let smallestDistance = Infinity;
|
|
5718
5875
|
for (const item of virtualItems) {
|
|
5719
5876
|
const itemCenter = item.start + item.size / 2;
|
|
5720
|
-
const
|
|
5721
|
-
if (
|
|
5722
|
-
smallestDistance =
|
|
5877
|
+
const distance2 = Math.abs(itemCenter - viewportCenter);
|
|
5878
|
+
if (distance2 < smallestDistance * 0.8) {
|
|
5879
|
+
smallestDistance = distance2;
|
|
5723
5880
|
closestIndex = item.index;
|
|
5724
5881
|
}
|
|
5725
5882
|
}
|
|
@@ -5779,9 +5936,9 @@ var V = {
|
|
|
5779
5936
|
v1[1] -= v2[1];
|
|
5780
5937
|
}
|
|
5781
5938
|
};
|
|
5782
|
-
function rubberband(
|
|
5783
|
-
if (dimension === 0 || Math.abs(dimension) === Infinity) return Math.pow(
|
|
5784
|
-
return
|
|
5939
|
+
function rubberband(distance2, dimension, constant) {
|
|
5940
|
+
if (dimension === 0 || Math.abs(dimension) === Infinity) return Math.pow(distance2, constant * 5);
|
|
5941
|
+
return distance2 * dimension * constant / (dimension + constant * distance2);
|
|
5785
5942
|
}
|
|
5786
5943
|
function rubberbandIfOutOfBounds(position, min, max, constant = 0.15) {
|
|
5787
5944
|
if (constant === 0) return clamp2(position, min, max);
|
|
@@ -5924,12 +6081,12 @@ function distanceAngle(P1, P2) {
|
|
|
5924
6081
|
const dy = P2.clientY - P1.clientY;
|
|
5925
6082
|
const cx = (P2.clientX + P1.clientX) / 2;
|
|
5926
6083
|
const cy = (P2.clientY + P1.clientY) / 2;
|
|
5927
|
-
const
|
|
6084
|
+
const distance2 = Math.hypot(dx, dy);
|
|
5928
6085
|
const angle = -(Math.atan2(dx, dy) * 180) / Math.PI;
|
|
5929
6086
|
const origin = [cx, cy];
|
|
5930
6087
|
return {
|
|
5931
6088
|
angle,
|
|
5932
|
-
distance,
|
|
6089
|
+
distance: distance2,
|
|
5933
6090
|
origin
|
|
5934
6091
|
};
|
|
5935
6092
|
} catch (_unused) {
|
|
@@ -6723,12 +6880,12 @@ var dragConfigResolver = _objectSpread2(_objectSpread2({}, coordinatesConfigReso
|
|
|
6723
6880
|
},
|
|
6724
6881
|
swipe({
|
|
6725
6882
|
velocity = DEFAULT_SWIPE_VELOCITY,
|
|
6726
|
-
distance = DEFAULT_SWIPE_DISTANCE,
|
|
6883
|
+
distance: distance2 = DEFAULT_SWIPE_DISTANCE,
|
|
6727
6884
|
duration = DEFAULT_SWIPE_DURATION
|
|
6728
6885
|
} = {}) {
|
|
6729
6886
|
return {
|
|
6730
6887
|
velocity: this.transform(V.toVector(velocity)),
|
|
6731
|
-
distance: this.transform(V.toVector(
|
|
6888
|
+
distance: this.transform(V.toVector(distance2)),
|
|
6732
6889
|
duration
|
|
6733
6890
|
};
|
|
6734
6891
|
},
|
|
@@ -7799,6 +7956,10 @@ var useViewportContainer = ({
|
|
|
7799
7956
|
if (!st.active) {
|
|
7800
7957
|
return;
|
|
7801
7958
|
}
|
|
7959
|
+
if (Math.abs(event.deltaX) > abs) {
|
|
7960
|
+
st.active = false;
|
|
7961
|
+
return;
|
|
7962
|
+
}
|
|
7802
7963
|
if (now - st.lastTime > WHEEL_INERTIA_GAP_MS) {
|
|
7803
7964
|
st.active = false;
|
|
7804
7965
|
return;
|
|
@@ -7858,12 +8019,11 @@ var useViewportContainer = ({
|
|
|
7858
8019
|
contentPosition,
|
|
7859
8020
|
containerPosition,
|
|
7860
8021
|
originZoom: currentZoom,
|
|
8022
|
+
origin: [...origin],
|
|
7861
8023
|
lastZoom: currentZoom
|
|
7862
8024
|
};
|
|
7863
8025
|
});
|
|
7864
|
-
if (first)
|
|
7865
|
-
return newMemo;
|
|
7866
|
-
}
|
|
8026
|
+
if (first && ms === 1) return newMemo;
|
|
7867
8027
|
const gestureValuesValid = Number.isFinite(ms) && ms > 0;
|
|
7868
8028
|
let effectiveScale = ms;
|
|
7869
8029
|
if (!gestureValuesValid) {
|
|
@@ -7882,9 +8042,18 @@ var useViewportContainer = ({
|
|
|
7882
8042
|
minZoom,
|
|
7883
8043
|
maxZoom
|
|
7884
8044
|
);
|
|
8045
|
+
if (event.type === "wheel") {
|
|
8046
|
+
const previousScale = newMemo.lastZoom / newMemo.originZoom;
|
|
8047
|
+
for (const axis of [0, 1]) {
|
|
8048
|
+
const movement2 = origin[axis] - newMemo.origin[axis];
|
|
8049
|
+
newMemo.contentPosition[axis] += movement2 / previousScale;
|
|
8050
|
+
newMemo.containerPosition[axis] += movement2;
|
|
8051
|
+
newMemo.origin[axis] = origin[axis];
|
|
8052
|
+
}
|
|
8053
|
+
}
|
|
7885
8054
|
const realMs = newZoom / newMemo.originZoom;
|
|
7886
|
-
const newTranslateX = newMemo.contentPosition[0] * realMs - newMemo.containerPosition[0];
|
|
7887
|
-
const newTranslateY = newMemo.contentPosition[1] * realMs - newMemo.containerPosition[1];
|
|
8055
|
+
const newTranslateX = newMemo.contentPosition[0] * realMs - newMemo.containerPosition[0] - (origin[0] - newMemo.origin[0]);
|
|
8056
|
+
const newTranslateY = newMemo.contentPosition[1] * realMs - newMemo.containerPosition[1] - (origin[1] - newMemo.origin[1]);
|
|
7888
8057
|
transformations.current = {
|
|
7889
8058
|
zoom: newZoom,
|
|
7890
8059
|
translateX: newTranslateX,
|
|
@@ -8066,7 +8235,14 @@ var Pages = ({
|
|
|
8066
8235
|
};
|
|
8067
8236
|
}, [isPinching, virtualizer?.measure, virtualizer?.getVirtualItems]);
|
|
8068
8237
|
const virtualizerItems = virtualizer?.getVirtualItems() ?? [];
|
|
8069
|
-
const items =
|
|
8238
|
+
const items = useMemo(() => {
|
|
8239
|
+
if (!tempItems.length) return virtualizerItems;
|
|
8240
|
+
const combined = new Map(
|
|
8241
|
+
virtualizerItems.map((item) => [item.index, item])
|
|
8242
|
+
);
|
|
8243
|
+
for (const item of tempItems) combined.set(item.index, item);
|
|
8244
|
+
return [...combined.values()].sort((a, b) => a.index - b.index);
|
|
8245
|
+
}, [tempItems, virtualizerItems]);
|
|
8070
8246
|
useVisiblePage({
|
|
8071
8247
|
items,
|
|
8072
8248
|
// Pass through nullish (pre-measure) rather than coercing to 0, so the
|
|
@@ -9276,7 +9452,7 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
9276
9452
|
const query = normalizeSearchText(searchText, options).text;
|
|
9277
9453
|
if (!query.trim()) return empty;
|
|
9278
9454
|
const maxDistance = Math.floor(query.length * (1 - threshold));
|
|
9279
|
-
const
|
|
9455
|
+
const distance2 = maxDistance > 0 ? createBoundedDistance(query, maxDistance) : null;
|
|
9280
9456
|
const exactMatches = [];
|
|
9281
9457
|
const fuzzyMatches = [];
|
|
9282
9458
|
let exactCount = 0;
|
|
@@ -9284,6 +9460,10 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
9284
9460
|
let steps = 0;
|
|
9285
9461
|
let deadline = cooperative ? performance.now() + budget : 0;
|
|
9286
9462
|
for (const page of pages) {
|
|
9463
|
+
if (cooperative && performance.now() >= deadline) {
|
|
9464
|
+
yield;
|
|
9465
|
+
deadline = performance.now() + budget;
|
|
9466
|
+
}
|
|
9287
9467
|
const normalized = normalize(page, options);
|
|
9288
9468
|
const lower = normalized.text;
|
|
9289
9469
|
const exactStarts = [];
|
|
@@ -9308,6 +9488,9 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
9308
9488
|
lastStart = span.start;
|
|
9309
9489
|
lastEnd = span.end;
|
|
9310
9490
|
exactCount++;
|
|
9491
|
+
if (maxDistance === 0 && exactCount > limit) {
|
|
9492
|
+
return { exactMatches, fuzzyMatches, hasMoreResults: true };
|
|
9493
|
+
}
|
|
9311
9494
|
if (exactMatches.length < limit) {
|
|
9312
9495
|
exactMatches.push(
|
|
9313
9496
|
createMatch(
|
|
@@ -9338,7 +9521,7 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
9338
9521
|
index = exactStart + query.length;
|
|
9339
9522
|
continue;
|
|
9340
9523
|
}
|
|
9341
|
-
const edits =
|
|
9524
|
+
const edits = distance2(lower, index);
|
|
9342
9525
|
if (edits > 0 && edits <= maxDistance) {
|
|
9343
9526
|
fuzzyCount++;
|
|
9344
9527
|
const score = 1 - edits / query.length;
|
|
@@ -9382,7 +9565,19 @@ async function searchDocumentAsync(pages, searchText, options = {}) {
|
|
|
9382
9565
|
throw new DOMException("Search aborted", "AbortError");
|
|
9383
9566
|
const result = iterator.next();
|
|
9384
9567
|
if (result.done) return result.value;
|
|
9385
|
-
await new Promise((resolve) =>
|
|
9568
|
+
await new Promise((resolve) => {
|
|
9569
|
+
if (typeof MessageChannel === "undefined") {
|
|
9570
|
+
setTimeout(resolve, 0);
|
|
9571
|
+
return;
|
|
9572
|
+
}
|
|
9573
|
+
const channel = new MessageChannel();
|
|
9574
|
+
channel.port1.onmessage = () => {
|
|
9575
|
+
channel.port1.close();
|
|
9576
|
+
channel.port2.close();
|
|
9577
|
+
resolve();
|
|
9578
|
+
};
|
|
9579
|
+
channel.port2.postMessage(null);
|
|
9580
|
+
});
|
|
9386
9581
|
}
|
|
9387
9582
|
}
|
|
9388
9583
|
|
|
@@ -9480,10 +9675,16 @@ async function calculateHighlightRects(pageProxy, textMatch) {
|
|
|
9480
9675
|
const start = Math.max(0, textMatch.matchIndex - offset3);
|
|
9481
9676
|
const end = Math.min(length, matchEnd - offset3);
|
|
9482
9677
|
if (end > start && item.str.slice(start, end).trim()) {
|
|
9678
|
+
let style = styles[item.fontName];
|
|
9679
|
+
if (style?.fontFamily && !style.vertical && (start > 0 || end < length)) {
|
|
9680
|
+
const { TextLayer: TextLayer2 } = await loadPdfJs();
|
|
9681
|
+
const fontFamily = TextLayer2.fontFamilyMap.get(style.fontFamily);
|
|
9682
|
+
if (fontFamily) style = { ...style, fontFamily };
|
|
9683
|
+
}
|
|
9483
9684
|
matchRects.push(
|
|
9484
9685
|
getItemRect(
|
|
9485
9686
|
item,
|
|
9486
|
-
|
|
9687
|
+
style,
|
|
9487
9688
|
viewport,
|
|
9488
9689
|
start,
|
|
9489
9690
|
end,
|