@anaralabs/lector 3.5.8 → 3.5.9

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.
Files changed (2) hide show
  1. package/dist/index.js +213 -35
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1055,39 +1055,69 @@ var shouldMergeRects = (rect1, rect2) => {
1055
1055
  const horizontallyClose = Math.abs(rect1.left + rect1.width - rect2.left) < MERGE_THRESHOLD || Math.abs(rect2.left + rect2.width - rect1.left) < MERGE_THRESHOLD || rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
1056
1056
  return verticalOverlap && horizontallyClose;
1057
1057
  };
1058
- var mergeRects = (rect1, rect2) => {
1059
- return {
1060
- left: Math.min(rect1.left, rect2.left),
1061
- top: Math.min(rect1.top, rect2.top),
1062
- width: Math.max(rect1.left + rect1.width, rect2.left + rect2.width) - Math.min(rect1.left, rect2.left),
1063
- height: Math.max(rect1.top + rect1.height, rect2.top + rect2.height) - Math.min(rect1.top, rect2.top),
1064
- pageNumber: rect1.pageNumber
1065
- };
1066
- };
1067
1058
  var consolidateRects = (rects) => {
1068
1059
  if (rects.length <= 1) return rects;
1069
- const sortedRects = [...rects].sort((a, b) => a.top - b.top);
1070
- let hasChanges;
1071
- do {
1072
- hasChanges = false;
1073
- let currentRect = sortedRects[0];
1074
- const tempResult = [];
1075
- for (let i = 1; i < sortedRects.length; i++) {
1076
- const sorted = sortedRects[i];
1077
- if (!currentRect || !sorted) continue;
1078
- if (shouldMergeRects(currentRect, sorted)) {
1079
- currentRect = mergeRects(currentRect, sorted);
1080
- hasChanges = true;
1081
- } else {
1082
- tempResult.push(currentRect);
1083
- currentRect = sorted;
1060
+ const result = [];
1061
+ const visited = /* @__PURE__ */ new Set();
1062
+ for (let i = 0; i < rects.length; i++) {
1063
+ if (visited.has(i)) continue;
1064
+ const currentRect = rects[i];
1065
+ if (!currentRect) continue;
1066
+ const currentGroup = [currentRect];
1067
+ visited.add(i);
1068
+ let foundNew = true;
1069
+ while (foundNew) {
1070
+ foundNew = false;
1071
+ for (let j = 0; j < rects.length; j++) {
1072
+ if (visited.has(j)) continue;
1073
+ const candidateRect = rects[j];
1074
+ if (!candidateRect) continue;
1075
+ const shouldMergeWithGroup = currentGroup.some(
1076
+ (groupRect) => doRectsOverlap(groupRect, candidateRect)
1077
+ );
1078
+ if (shouldMergeWithGroup) {
1079
+ currentGroup.push(candidateRect);
1080
+ visited.add(j);
1081
+ foundNew = true;
1082
+ }
1084
1083
  }
1085
1084
  }
1086
- if (currentRect) tempResult.push(currentRect);
1087
- sortedRects.length = 0;
1088
- sortedRects.push(...tempResult);
1089
- } while (hasChanges);
1090
- return sortedRects;
1085
+ result.push(mergeRectGroup(currentGroup));
1086
+ }
1087
+ return result;
1088
+ };
1089
+ var doRectsOverlap = (rect1, rect2) => {
1090
+ const horizontalOverlap = rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
1091
+ const verticalOverlap = rect1.top < rect2.top + rect2.height && rect2.top < rect1.top + rect1.height;
1092
+ const closeEnough = shouldMergeRects(rect1, rect2);
1093
+ return horizontalOverlap && verticalOverlap || closeEnough;
1094
+ };
1095
+ var mergeRectGroup = (rects) => {
1096
+ if (rects.length === 1) {
1097
+ const rect = rects[0];
1098
+ if (!rect) throw new Error("Invalid rect in group");
1099
+ return rect;
1100
+ }
1101
+ const firstRect = rects[0];
1102
+ if (!firstRect) throw new Error("Invalid first rect in group");
1103
+ let minLeft = firstRect.left;
1104
+ let minTop = firstRect.top;
1105
+ let maxRight = firstRect.left + firstRect.width;
1106
+ let maxBottom = firstRect.top + firstRect.height;
1107
+ rects.forEach((rect) => {
1108
+ if (!rect) return;
1109
+ minLeft = Math.min(minLeft, rect.left);
1110
+ minTop = Math.min(minTop, rect.top);
1111
+ maxRight = Math.max(maxRight, rect.left + rect.width);
1112
+ maxBottom = Math.max(maxBottom, rect.top + rect.height);
1113
+ });
1114
+ return {
1115
+ left: minLeft,
1116
+ top: minTop,
1117
+ width: maxRight - minLeft,
1118
+ height: maxBottom - minTop,
1119
+ pageNumber: firstRect.pageNumber
1120
+ };
1091
1121
  };
1092
1122
  var useSelectionDimensions = () => {
1093
1123
  const store = PDFStore.useContext();
@@ -1127,7 +1157,8 @@ var useSelectionDimensions = () => {
1127
1157
  });
1128
1158
  textLayerMap.forEach((rects) => {
1129
1159
  if (rects.length > 0) {
1130
- highlights.push(...consolidateRects(rects));
1160
+ const consolidated = consolidateRects(rects);
1161
+ highlights.push(...consolidated);
1131
1162
  }
1132
1163
  });
1133
1164
  return {
@@ -1522,24 +1553,171 @@ var HighlightLayer = forwardRef(({ asChild, className, style, ...props }, ref) =
1522
1553
  }) });
1523
1554
  });
1524
1555
  HighlightLayer.displayName = "HighlightLayer";
1556
+ var createTextSelectionManager = () => {
1557
+ const textLayers = /* @__PURE__ */ new Map();
1558
+ let selectionChangeAbortController = null;
1559
+ let isPointerDown = false;
1560
+ let prevRange = null;
1561
+ let isFirefox;
1562
+ const removeGlobalSelectionListener = (textLayerDiv) => {
1563
+ textLayers.delete(textLayerDiv);
1564
+ if (textLayers.size === 0) {
1565
+ selectionChangeAbortController?.abort();
1566
+ selectionChangeAbortController = null;
1567
+ }
1568
+ };
1569
+ const enableGlobalSelectionListener = () => {
1570
+ if (selectionChangeAbortController) {
1571
+ return;
1572
+ }
1573
+ selectionChangeAbortController = new AbortController();
1574
+ const { signal } = selectionChangeAbortController;
1575
+ const reset = (endDiv, textLayer) => {
1576
+ if (endDiv.parentNode !== textLayer) {
1577
+ textLayer.appendChild(endDiv);
1578
+ }
1579
+ endDiv.style.width = "";
1580
+ endDiv.style.height = "";
1581
+ textLayer.classList.remove("selecting");
1582
+ };
1583
+ document.addEventListener("pointerdown", () => {
1584
+ isPointerDown = true;
1585
+ }, { signal });
1586
+ document.addEventListener("pointerup", () => {
1587
+ isPointerDown = false;
1588
+ textLayers.forEach(reset);
1589
+ }, { signal });
1590
+ window.addEventListener("blur", () => {
1591
+ isPointerDown = false;
1592
+ textLayers.forEach(reset);
1593
+ }, { signal });
1594
+ document.addEventListener("keyup", () => {
1595
+ if (!isPointerDown) {
1596
+ textLayers.forEach(reset);
1597
+ }
1598
+ }, { signal });
1599
+ document.addEventListener("selectionchange", () => {
1600
+ const selection = document.getSelection();
1601
+ if (!selection || selection.rangeCount === 0) {
1602
+ textLayers.forEach(reset);
1603
+ return;
1604
+ }
1605
+ const activeTextLayers = /* @__PURE__ */ new Set();
1606
+ for (let i = 0; i < selection.rangeCount; i++) {
1607
+ const range = selection.getRangeAt(i);
1608
+ for (const textLayerDiv of textLayers.keys()) {
1609
+ if (!activeTextLayers.has(textLayerDiv) && range.intersectsNode(textLayerDiv)) {
1610
+ activeTextLayers.add(textLayerDiv);
1611
+ }
1612
+ }
1613
+ }
1614
+ for (const [textLayerDiv, endDiv] of textLayers) {
1615
+ if (activeTextLayers.has(textLayerDiv)) {
1616
+ textLayerDiv.classList.add("selecting");
1617
+ } else {
1618
+ reset(endDiv, textLayerDiv);
1619
+ }
1620
+ }
1621
+ if (isFirefox === void 0) {
1622
+ const firstTextLayer = textLayers.keys().next().value;
1623
+ if (firstTextLayer) {
1624
+ isFirefox = getComputedStyle(firstTextLayer).getPropertyValue("-moz-user-select") === "none";
1625
+ }
1626
+ }
1627
+ if (isFirefox) {
1628
+ return;
1629
+ }
1630
+ try {
1631
+ const range = selection.getRangeAt(0);
1632
+ const modifyStart = prevRange && (range.compareBoundaryPoints(Range.END_TO_END, prevRange) === 0 || range.compareBoundaryPoints(Range.START_TO_END, prevRange) === 0);
1633
+ let anchor = modifyStart ? range.startContainer : range.endContainer;
1634
+ if (anchor.nodeType === Node.TEXT_NODE) {
1635
+ anchor = anchor.parentNode;
1636
+ }
1637
+ const parentTextLayer = anchor.parentElement?.closest(".textLayer");
1638
+ const endDiv = textLayers.get(parentTextLayer);
1639
+ if (endDiv && parentTextLayer) {
1640
+ endDiv.style.width = parentTextLayer.style.width;
1641
+ endDiv.style.height = parentTextLayer.style.height;
1642
+ const insertTarget = modifyStart ? anchor : anchor.nextSibling;
1643
+ if (anchor.parentElement && insertTarget) {
1644
+ anchor.parentElement.insertBefore(endDiv, insertTarget);
1645
+ }
1646
+ }
1647
+ prevRange = range.cloneRange();
1648
+ } catch {
1649
+ }
1650
+ }, { signal });
1651
+ };
1652
+ const bindMouseEvents2 = (textLayerDiv, endOfContent) => {
1653
+ if (textLayerDiv._textSelectionBound) {
1654
+ return;
1655
+ }
1656
+ textLayerDiv._textSelectionBound = true;
1657
+ textLayers.set(textLayerDiv, endOfContent);
1658
+ enableGlobalSelectionListener();
1659
+ const handleMouseDown = () => {
1660
+ textLayerDiv.classList.add("selecting");
1661
+ };
1662
+ textLayerDiv.addEventListener("mousedown", handleMouseDown);
1663
+ textLayerDiv._cleanupTextSelection = () => {
1664
+ textLayerDiv.removeEventListener("mousedown", handleMouseDown);
1665
+ removeGlobalSelectionListener(textLayerDiv);
1666
+ delete textLayerDiv._textSelectionBound;
1667
+ };
1668
+ };
1669
+ return bindMouseEvents2;
1670
+ };
1671
+ var bindMouseEvents = createTextSelectionManager();
1525
1672
  var useTextLayer = () => {
1526
1673
  const textContainerRef = useRef(null);
1674
+ const textLayerRef = useRef(null);
1675
+ const isRenderingRef = useRef(false);
1527
1676
  const pageNumber = usePDFPageNumber();
1528
1677
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
1529
1678
  useEffect(() => {
1530
- if (!textContainerRef.current) {
1679
+ const textContainer = textContainerRef.current;
1680
+ if (!textContainer || isRenderingRef.current) {
1531
1681
  return;
1532
1682
  }
1683
+ isRenderingRef.current = true;
1684
+ textContainer.innerHTML = "";
1685
+ if (textLayerRef.current) {
1686
+ textLayerRef.current.cancel();
1687
+ textLayerRef.current = null;
1688
+ }
1533
1689
  const textLayer = new TextLayer({
1534
1690
  textContentSource: pdfPageProxy.streamTextContent(),
1535
- container: textContainerRef.current,
1691
+ container: textContainer,
1536
1692
  viewport: pdfPageProxy.getViewport({ scale: 1 })
1537
1693
  });
1538
- void textLayer.render();
1694
+ textLayerRef.current = textLayer;
1695
+ textLayer.render().then(() => {
1696
+ if (textLayerRef.current === textLayer && textContainer) {
1697
+ const endOfContent = document.createElement("div");
1698
+ endOfContent.className = "endOfContent";
1699
+ textContainer.appendChild(endOfContent);
1700
+ bindMouseEvents(textContainer, endOfContent);
1701
+ }
1702
+ }).catch((error) => {
1703
+ if (error.name !== "AbortException") {
1704
+ console.error("TextLayer rendering error:", error);
1705
+ }
1706
+ }).finally(() => {
1707
+ isRenderingRef.current = false;
1708
+ });
1539
1709
  return () => {
1540
- textLayer.cancel();
1710
+ isRenderingRef.current = false;
1711
+ if (textLayerRef.current) {
1712
+ textLayerRef.current.cancel();
1713
+ textLayerRef.current = null;
1714
+ }
1715
+ if (textContainer?._cleanupTextSelection) {
1716
+ textContainer._cleanupTextSelection();
1717
+ delete textContainer._cleanupTextSelection;
1718
+ }
1541
1719
  };
1542
- }, [pdfPageProxy]);
1720
+ }, [pdfPageProxy.pageNumber]);
1543
1721
  return {
1544
1722
  textContainerRef,
1545
1723
  pageNumber: pdfPageProxy.pageNumber
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anaralabs/lector",
3
- "version": "3.5.8",
3
+ "version": "3.5.9",
4
4
  "description": "Headless PDF viewer for React",
5
5
  "author": "andrewdr (https://github.com/andrewdoro)",
6
6
  "license": "MIT",