@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.
@@ -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("button, a, [role=button], [data-selection-tooltip]"))
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
- const endOfContent = document.createElement("div");
5037
- endOfContent.className = "endOfContent";
5038
- textContainer.appendChild(endOfContent);
5039
- bindMouseEvents(textContainer, endOfContent);
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
- if (textContainer?._cleanupTextSelection) {
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 distance = Math.abs(itemCenter - viewportCenter);
5721
- if (distance < smallestDistance * 0.8) {
5722
- smallestDistance = distance;
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(distance, dimension, constant) {
5783
- if (dimension === 0 || Math.abs(dimension) === Infinity) return Math.pow(distance, constant * 5);
5784
- return distance * dimension * constant / (dimension + constant * distance);
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 distance = Math.hypot(dx, dy);
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(distance)),
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 = tempItems.length ? tempItems : virtualizerItems;
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 distance = maxDistance > 0 ? createBoundedDistance(query, maxDistance) : null;
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 = distance(lower, index);
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) => setTimeout(resolve, 0));
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
- styles[item.fontName],
9687
+ style,
9487
9688
  viewport,
9488
9689
  start,
9489
9690
  end,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anaralabs/lector",
3
- "version": "3.15.0",
3
+ "version": "3.15.2",
4
4
  "description": "A headless PDF viewer for React",
5
5
  "author": "andrewdr (https://github.com/andrewdoro)",
6
6
  "license": "MIT",