react-pdf-highlighter-plus 1.1.2 → 1.1.4
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/README.md +83 -32
- package/dist/esm/export-pdf-W2QGWADM.js +403 -0
- package/dist/esm/export-pdf-W2QGWADM.js.map +1 -0
- package/dist/esm/index.d.ts +213 -85
- package/dist/esm/index.js +1426 -723
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pdf.worker.min.mjs +21 -0
- package/dist/esm/style/AreaHighlight.css +141 -0
- package/dist/esm/style/DrawingCanvas.css +62 -0
- package/dist/esm/style/DrawingHighlight.css +194 -0
- package/dist/esm/style/FreetextHighlight.css +309 -0
- package/dist/esm/style/ImageHighlight.css +98 -0
- package/dist/esm/style/MouseSelection.css +15 -0
- package/dist/esm/style/PdfHighlighter.css +146 -0
- package/dist/esm/style/ShapeCanvas.css +47 -0
- package/dist/esm/style/ShapeHighlight.css +182 -0
- package/dist/esm/style/SignaturePad.css +83 -0
- package/dist/esm/style/TextHighlight.css +206 -0
- package/dist/esm/style/pdf_viewer.css +41 -0
- package/dist/esm/style/style.css +13 -0
- package/package.json +15 -5
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/components/PdfHighlighter.tsx
|
|
2
|
-
import debounce from "lodash.debounce";
|
|
3
2
|
import React6, {
|
|
4
3
|
useEffect as useEffect4,
|
|
5
4
|
useLayoutEffect as useLayoutEffect2,
|
|
@@ -659,9 +658,12 @@ var HighlightLayer = ({
|
|
|
659
658
|
scrolledToHighlightId,
|
|
660
659
|
viewer,
|
|
661
660
|
highlightBindings,
|
|
662
|
-
children
|
|
661
|
+
children,
|
|
662
|
+
shouldRenderHighlight
|
|
663
663
|
}) => {
|
|
664
|
-
const currentHighlights = highlightsByPage[pageNumber] || []
|
|
664
|
+
const currentHighlights = (highlightsByPage[pageNumber] || []).filter(
|
|
665
|
+
(highlight) => shouldRenderHighlight?.(highlight) ?? true
|
|
666
|
+
);
|
|
665
667
|
return /* @__PURE__ */ React2.createElement("div", null, currentHighlights.map((highlight, index) => {
|
|
666
668
|
const viewportHighlight = {
|
|
667
669
|
...highlight,
|
|
@@ -1111,11 +1113,13 @@ var TipContainer = ({
|
|
|
1111
1113
|
|
|
1112
1114
|
// src/components/PdfHighlighter.tsx
|
|
1113
1115
|
var EventBus;
|
|
1116
|
+
var PDFFindController;
|
|
1114
1117
|
var PDFLinkService;
|
|
1115
1118
|
var PDFViewer;
|
|
1116
1119
|
(async () => {
|
|
1117
1120
|
const pdfjs = await import("pdfjs-dist/web/pdf_viewer.mjs");
|
|
1118
1121
|
EventBus = pdfjs.EventBus;
|
|
1122
|
+
PDFFindController = pdfjs.PDFFindController;
|
|
1119
1123
|
PDFLinkService = pdfjs.PDFLinkService;
|
|
1120
1124
|
PDFViewer = pdfjs.PDFViewer;
|
|
1121
1125
|
})();
|
|
@@ -1143,6 +1147,22 @@ var findOrCreateHighlightLayer = (textLayer) => {
|
|
|
1143
1147
|
"PdfHighlighter__highlight-layer"
|
|
1144
1148
|
);
|
|
1145
1149
|
};
|
|
1150
|
+
var findOrCreateNoteLayer = (textLayer) => {
|
|
1151
|
+
const pageLayer = textLayer.closest(".page");
|
|
1152
|
+
const container = pageLayer;
|
|
1153
|
+
if (!container) return null;
|
|
1154
|
+
const doc = getDocument(container);
|
|
1155
|
+
let layer = Array.from(container.children).find(
|
|
1156
|
+
(child) => child.classList.contains("PdfHighlighter__note-layer")
|
|
1157
|
+
);
|
|
1158
|
+
if (!layer && container.children.length) {
|
|
1159
|
+
layer = doc.createElement("div");
|
|
1160
|
+
layer.className = "PdfHighlighter__note-layer";
|
|
1161
|
+
container.appendChild(layer);
|
|
1162
|
+
}
|
|
1163
|
+
return layer;
|
|
1164
|
+
};
|
|
1165
|
+
var isFreetextHighlight = (highlight) => "type" in highlight && highlight.type === "freetext";
|
|
1146
1166
|
var disableTextSelection = (viewer, flag) => {
|
|
1147
1167
|
viewer.viewer?.classList.toggle("PdfHighlighter--disable-selection", flag);
|
|
1148
1168
|
};
|
|
@@ -1189,6 +1209,9 @@ var PdfHighlighter = ({
|
|
|
1189
1209
|
const highlightBindingsRef = useRef5(
|
|
1190
1210
|
{}
|
|
1191
1211
|
);
|
|
1212
|
+
const noteBindingsRef = useRef5({});
|
|
1213
|
+
const highlightsRef = useRef5(highlights);
|
|
1214
|
+
const childrenRef = useRef5(children);
|
|
1192
1215
|
const ghostHighlightRef = useRef5(null);
|
|
1193
1216
|
const selectionRef = useRef5(null);
|
|
1194
1217
|
const scrolledToHighlightIdRef = useRef5(null);
|
|
@@ -1204,41 +1227,59 @@ var PdfHighlighter = ({
|
|
|
1204
1227
|
})
|
|
1205
1228
|
);
|
|
1206
1229
|
const resizeObserverRef = useRef5(null);
|
|
1230
|
+
const renderRetryTimeoutsRef = useRef5(
|
|
1231
|
+
[]
|
|
1232
|
+
);
|
|
1233
|
+
const resumeScrollAwayTimeoutRef = useRef5(
|
|
1234
|
+
null
|
|
1235
|
+
);
|
|
1236
|
+
const findControllerRef = useRef5(null);
|
|
1207
1237
|
const viewerRef = useRef5(null);
|
|
1238
|
+
highlightsRef.current = highlights;
|
|
1239
|
+
childrenRef.current = children;
|
|
1208
1240
|
useLayoutEffect2(() => {
|
|
1209
1241
|
if (!containerNodeRef.current) return;
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
linkServiceRef.current
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
}, [document]);
|
|
1242
|
+
findControllerRef.current = findControllerRef.current || new PDFFindController({
|
|
1243
|
+
eventBus: eventBusRef.current,
|
|
1244
|
+
linkService: linkServiceRef.current
|
|
1245
|
+
});
|
|
1246
|
+
viewerRef.current = viewerRef.current || new PDFViewer({
|
|
1247
|
+
container: containerNodeRef.current,
|
|
1248
|
+
eventBus: eventBusRef.current,
|
|
1249
|
+
findController: findControllerRef.current,
|
|
1250
|
+
textLayerMode: 2,
|
|
1251
|
+
removePageBorders: true,
|
|
1252
|
+
linkService: linkServiceRef.current
|
|
1253
|
+
});
|
|
1254
|
+
viewerRef.current.setDocument(pdfDocument);
|
|
1255
|
+
linkServiceRef.current.setDocument(pdfDocument);
|
|
1256
|
+
linkServiceRef.current.setViewer(viewerRef.current);
|
|
1257
|
+
setIsViewerReady(true);
|
|
1258
|
+
}, [pdfDocument]);
|
|
1228
1259
|
useLayoutEffect2(() => {
|
|
1229
1260
|
if (!containerNodeRef.current) return;
|
|
1230
1261
|
resizeObserverRef.current = new ResizeObserver(handleScaleValue);
|
|
1231
1262
|
resizeObserverRef.current.observe(containerNodeRef.current);
|
|
1232
1263
|
const doc = containerNodeRef.current.ownerDocument;
|
|
1233
|
-
eventBusRef.current.on("textlayerrendered",
|
|
1264
|
+
eventBusRef.current.on("textlayerrendered", scheduleRenderHighlightLayers);
|
|
1265
|
+
eventBusRef.current.on("pagerendered", scheduleRenderHighlightLayers);
|
|
1234
1266
|
eventBusRef.current.on("pagesinit", handleScaleValue);
|
|
1235
1267
|
doc.addEventListener("keydown", handleKeyDown);
|
|
1236
|
-
|
|
1268
|
+
doc.addEventListener("copy", handleCopy, true);
|
|
1269
|
+
scheduleRenderHighlightLayers();
|
|
1237
1270
|
return () => {
|
|
1238
1271
|
eventBusRef.current.off("pagesinit", handleScaleValue);
|
|
1239
|
-
eventBusRef.current.off("
|
|
1272
|
+
eventBusRef.current.off("pagerendered", scheduleRenderHighlightLayers);
|
|
1273
|
+
eventBusRef.current.off("textlayerrendered", scheduleRenderHighlightLayers);
|
|
1240
1274
|
doc.removeEventListener("keydown", handleKeyDown);
|
|
1275
|
+
doc.removeEventListener("copy", handleCopy, true);
|
|
1241
1276
|
resizeObserverRef.current?.disconnect();
|
|
1277
|
+
renderRetryTimeoutsRef.current.forEach(clearTimeout);
|
|
1278
|
+
renderRetryTimeoutsRef.current = [];
|
|
1279
|
+
if (resumeScrollAwayTimeoutRef.current) {
|
|
1280
|
+
clearTimeout(resumeScrollAwayTimeoutRef.current);
|
|
1281
|
+
resumeScrollAwayTimeoutRef.current = null;
|
|
1282
|
+
}
|
|
1242
1283
|
};
|
|
1243
1284
|
}, [selectionTip, highlights, onSelectionFinished]);
|
|
1244
1285
|
const handleScroll = () => {
|
|
@@ -1358,26 +1399,46 @@ var PdfHighlighter = ({
|
|
|
1358
1399
|
setTip(null);
|
|
1359
1400
|
}
|
|
1360
1401
|
};
|
|
1402
|
+
const handleCopy = (event) => {
|
|
1403
|
+
const container = containerNodeRef.current;
|
|
1404
|
+
if (!container || !event.clipboardData) return;
|
|
1405
|
+
const target = event.target;
|
|
1406
|
+
const targetElement = target instanceof HTMLElement ? target : target instanceof Node ? target.parentElement : null;
|
|
1407
|
+
if (targetElement && (targetElement.closest("input, textarea, [contenteditable='true']") || targetElement.closest(".PdfHighlighter__tip-container"))) {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
const selection = getWindow(container).getSelection();
|
|
1411
|
+
const range = selection?.rangeCount ? selection.getRangeAt(0) : null;
|
|
1412
|
+
if (!selection || selection.isCollapsed || !range || !container.contains(range.commonAncestorContainer)) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
const text = selectionRef.current?.content.text?.trim() || selection.toString().split("\n").join(" ").trim();
|
|
1416
|
+
if (!text) return;
|
|
1417
|
+
event.clipboardData.setData("text/plain", text);
|
|
1418
|
+
event.preventDefault();
|
|
1419
|
+
event.stopPropagation();
|
|
1420
|
+
};
|
|
1361
1421
|
const handleScaleValue = () => {
|
|
1362
1422
|
if (viewerRef.current) {
|
|
1363
1423
|
viewerRef.current.currentScaleValue = pdfScaleValue.toString();
|
|
1364
1424
|
}
|
|
1365
1425
|
};
|
|
1366
|
-
const renderHighlightLayer = (highlightBindings, pageNumber) => {
|
|
1426
|
+
const renderHighlightLayer = (highlightBindings, pageNumber, shouldRenderHighlight) => {
|
|
1367
1427
|
if (!viewerRef.current) return;
|
|
1368
1428
|
highlightBindings.reactRoot.render(
|
|
1369
1429
|
/* @__PURE__ */ React6.createElement(PdfHighlighterContext.Provider, { value: pdfHighlighterUtils }, /* @__PURE__ */ React6.createElement(
|
|
1370
1430
|
HighlightLayer,
|
|
1371
1431
|
{
|
|
1372
1432
|
highlightsByPage: group_highlights_by_page_default([
|
|
1373
|
-
...
|
|
1433
|
+
...highlightsRef.current,
|
|
1374
1434
|
ghostHighlightRef.current
|
|
1375
1435
|
]),
|
|
1376
1436
|
pageNumber,
|
|
1377
1437
|
scrolledToHighlightId: scrolledToHighlightIdRef.current,
|
|
1378
1438
|
viewer: viewerRef.current,
|
|
1379
1439
|
highlightBindings,
|
|
1380
|
-
|
|
1440
|
+
shouldRenderHighlight,
|
|
1441
|
+
children: childrenRef.current
|
|
1381
1442
|
}
|
|
1382
1443
|
))
|
|
1383
1444
|
);
|
|
@@ -1385,31 +1446,61 @@ var PdfHighlighter = ({
|
|
|
1385
1446
|
const renderHighlightLayers = () => {
|
|
1386
1447
|
if (!viewerRef.current) return;
|
|
1387
1448
|
for (let pageNumber = 1; pageNumber <= pdfDocument.numPages; pageNumber++) {
|
|
1388
|
-
const
|
|
1389
|
-
if (
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
const reactRoot = createRoot(highlightLayer);
|
|
1399
|
-
highlightBindingsRef.current[pageNumber] = {
|
|
1400
|
-
reactRoot,
|
|
1449
|
+
const { textLayer } = viewerRef.current.getPageView(pageNumber - 1) || {};
|
|
1450
|
+
if (!textLayer) continue;
|
|
1451
|
+
const textLayerDiv = textLayer.div;
|
|
1452
|
+
const highlightLayer = findOrCreateHighlightLayer(textLayerDiv);
|
|
1453
|
+
const noteLayer = findOrCreateNoteLayer(textLayerDiv);
|
|
1454
|
+
if (highlightLayer) {
|
|
1455
|
+
let highlightBindings = highlightBindingsRef.current[pageNumber];
|
|
1456
|
+
if (!highlightBindings?.container?.isConnected) {
|
|
1457
|
+
highlightBindings = {
|
|
1458
|
+
reactRoot: createRoot(highlightLayer),
|
|
1401
1459
|
container: highlightLayer,
|
|
1402
|
-
textLayer:
|
|
1403
|
-
// textLayer.div for version >=3.0 and textLayer.textLayerDiv otherwise.
|
|
1460
|
+
textLayer: textLayerDiv
|
|
1404
1461
|
};
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1462
|
+
highlightBindingsRef.current[pageNumber] = highlightBindings;
|
|
1463
|
+
}
|
|
1464
|
+
renderHighlightLayer(
|
|
1465
|
+
highlightBindings,
|
|
1466
|
+
pageNumber,
|
|
1467
|
+
(highlight) => !isFreetextHighlight(highlight)
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
if (noteLayer) {
|
|
1471
|
+
let noteBindings = noteBindingsRef.current[pageNumber];
|
|
1472
|
+
if (!noteBindings?.container?.isConnected) {
|
|
1473
|
+
noteBindings = {
|
|
1474
|
+
reactRoot: createRoot(noteLayer),
|
|
1475
|
+
container: noteLayer,
|
|
1476
|
+
textLayer: textLayerDiv
|
|
1477
|
+
};
|
|
1478
|
+
noteBindingsRef.current[pageNumber] = noteBindings;
|
|
1409
1479
|
}
|
|
1480
|
+
renderHighlightLayer(noteBindings, pageNumber, isFreetextHighlight);
|
|
1410
1481
|
}
|
|
1411
1482
|
}
|
|
1412
1483
|
};
|
|
1484
|
+
const scheduleRenderHighlightLayers = () => {
|
|
1485
|
+
renderHighlightLayers();
|
|
1486
|
+
renderRetryTimeoutsRef.current.forEach(clearTimeout);
|
|
1487
|
+
renderRetryTimeoutsRef.current = [50, 150, 350, 750, 1200].map(
|
|
1488
|
+
(delay) => setTimeout(renderHighlightLayers, delay)
|
|
1489
|
+
);
|
|
1490
|
+
};
|
|
1491
|
+
const resumeScrollAwayListenerAfterNavigation = () => {
|
|
1492
|
+
const container = viewerRef.current?.container;
|
|
1493
|
+
if (!container) return;
|
|
1494
|
+
if (resumeScrollAwayTimeoutRef.current) {
|
|
1495
|
+
clearTimeout(resumeScrollAwayTimeoutRef.current);
|
|
1496
|
+
}
|
|
1497
|
+
resumeScrollAwayTimeoutRef.current = setTimeout(() => {
|
|
1498
|
+
container.addEventListener("scroll", handleScroll, {
|
|
1499
|
+
once: true
|
|
1500
|
+
});
|
|
1501
|
+
resumeScrollAwayTimeoutRef.current = null;
|
|
1502
|
+
}, 1200);
|
|
1503
|
+
};
|
|
1413
1504
|
const isEditingOrHighlighting = () => {
|
|
1414
1505
|
return Boolean(selectionRef.current) || Boolean(ghostHighlightRef.current) || isAreaSelectionInProgressRef.current || isEditInProgressRef.current;
|
|
1415
1506
|
};
|
|
@@ -1461,12 +1552,49 @@ var PdfHighlighter = ({
|
|
|
1461
1552
|
]
|
|
1462
1553
|
});
|
|
1463
1554
|
scrolledToHighlightIdRef.current = highlight.id;
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1555
|
+
scheduleRenderHighlightLayers();
|
|
1556
|
+
resumeScrollAwayListenerAfterNavigation();
|
|
1557
|
+
};
|
|
1558
|
+
const dispatchFind = (query, findPrevious2, options = {}, type) => {
|
|
1559
|
+
eventBusRef.current.dispatch("find", {
|
|
1560
|
+
source: findControllerRef.current || viewerRef.current,
|
|
1561
|
+
type,
|
|
1562
|
+
query,
|
|
1563
|
+
phraseSearch: true,
|
|
1564
|
+
caseSensitive: options.caseSensitive ?? false,
|
|
1565
|
+
entireWord: options.entireWord ?? false,
|
|
1566
|
+
highlightAll: options.highlightAll ?? true,
|
|
1567
|
+
findPrevious: findPrevious2,
|
|
1568
|
+
matchDiacritics: options.matchDiacritics ?? false
|
|
1569
|
+
});
|
|
1570
|
+
};
|
|
1571
|
+
const currentSearchRef = useRef5({
|
|
1572
|
+
query: "",
|
|
1573
|
+
options: {}
|
|
1574
|
+
});
|
|
1575
|
+
const search = (query, options = {}) => {
|
|
1576
|
+
currentSearchRef.current = { query, options };
|
|
1577
|
+
if (!query.trim()) {
|
|
1578
|
+
clearSearch();
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
dispatchFind(query, false, options);
|
|
1582
|
+
};
|
|
1583
|
+
const findNext = () => {
|
|
1584
|
+
const { query, options } = currentSearchRef.current;
|
|
1585
|
+
if (!query.trim()) return;
|
|
1586
|
+
dispatchFind(query, false, options, "again");
|
|
1587
|
+
};
|
|
1588
|
+
const findPrevious = () => {
|
|
1589
|
+
const { query, options } = currentSearchRef.current;
|
|
1590
|
+
if (!query.trim()) return;
|
|
1591
|
+
dispatchFind(query, true, options, "again");
|
|
1592
|
+
};
|
|
1593
|
+
const clearSearch = () => {
|
|
1594
|
+
currentSearchRef.current = { query: "", options: {} };
|
|
1595
|
+
eventBusRef.current.dispatch("findbarclose", {
|
|
1596
|
+
source: findControllerRef.current || viewerRef.current
|
|
1597
|
+
});
|
|
1470
1598
|
};
|
|
1471
1599
|
const pdfHighlighterUtils = {
|
|
1472
1600
|
isEditingOrHighlighting,
|
|
@@ -1483,6 +1611,10 @@ var PdfHighlighter = ({
|
|
|
1483
1611
|
updateTipPosition: updateTipPositionRef.current,
|
|
1484
1612
|
getLinkService: () => linkServiceRef.current,
|
|
1485
1613
|
getEventBus: () => eventBusRef.current,
|
|
1614
|
+
search,
|
|
1615
|
+
findNext,
|
|
1616
|
+
findPrevious,
|
|
1617
|
+
clearSearch,
|
|
1486
1618
|
goToPage: (pageNumber) => {
|
|
1487
1619
|
console.log("[PdfHighlighter] goToPage called with page:", pageNumber);
|
|
1488
1620
|
const viewer = viewerRef.current;
|
|
@@ -1591,6 +1723,9 @@ var PdfHighlighter = ({
|
|
|
1591
1723
|
.PdfHighlighter--dark .PdfHighlighter__highlight-layer {
|
|
1592
1724
|
filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(0.95);
|
|
1593
1725
|
}
|
|
1726
|
+
.PdfHighlighter--dark .PdfHighlighter__note-layer {
|
|
1727
|
+
filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(0.95);
|
|
1728
|
+
}
|
|
1594
1729
|
` : ""}
|
|
1595
1730
|
`),
|
|
1596
1731
|
isViewerReady && /* @__PURE__ */ React6.createElement(
|
|
@@ -1676,10 +1811,78 @@ var PdfHighlighter = ({
|
|
|
1676
1811
|
import React7, {
|
|
1677
1812
|
useState as useState6,
|
|
1678
1813
|
useRef as useRef6,
|
|
1679
|
-
useEffect as useEffect5
|
|
1814
|
+
useEffect as useEffect5,
|
|
1815
|
+
useLayoutEffect as useLayoutEffect3
|
|
1680
1816
|
} from "react";
|
|
1817
|
+
import { createPortal } from "react-dom";
|
|
1818
|
+
|
|
1819
|
+
// src/lib/copy-highlight-content.ts
|
|
1820
|
+
var intersects = (a, b) => a.left < b.left + b.width && a.left + a.width > b.left && a.top < b.top + b.height && a.top + a.height > b.top;
|
|
1821
|
+
var copyTextToClipboard = async (text) => {
|
|
1822
|
+
if (!text) return;
|
|
1823
|
+
if (navigator.clipboard?.writeText) {
|
|
1824
|
+
await navigator.clipboard.writeText(text);
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
const textarea = document.createElement("textarea");
|
|
1828
|
+
textarea.value = text;
|
|
1829
|
+
textarea.style.position = "fixed";
|
|
1830
|
+
textarea.style.opacity = "0";
|
|
1831
|
+
document.body.appendChild(textarea);
|
|
1832
|
+
textarea.select();
|
|
1833
|
+
document.execCommand("copy");
|
|
1834
|
+
textarea.remove();
|
|
1835
|
+
};
|
|
1836
|
+
var extractTextFromHighlightRect = (anchor, rect) => {
|
|
1837
|
+
const page = anchor.closest(".page");
|
|
1838
|
+
const textLayer = page?.querySelector(".textLayer");
|
|
1839
|
+
if (!page || !textLayer) return "";
|
|
1840
|
+
const pageRect = page.getBoundingClientRect();
|
|
1841
|
+
const targetRect = {
|
|
1842
|
+
left: pageRect.left + rect.left,
|
|
1843
|
+
top: pageRect.top + rect.top,
|
|
1844
|
+
width: rect.width,
|
|
1845
|
+
height: rect.height
|
|
1846
|
+
};
|
|
1847
|
+
const matches = Array.from(textLayer.querySelectorAll("span")).map((span) => {
|
|
1848
|
+
const spanRect = span.getBoundingClientRect();
|
|
1849
|
+
return {
|
|
1850
|
+
text: span.textContent || "",
|
|
1851
|
+
rect: {
|
|
1852
|
+
left: spanRect.left,
|
|
1853
|
+
top: spanRect.top,
|
|
1854
|
+
width: spanRect.width,
|
|
1855
|
+
height: spanRect.height
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
}).filter(({ text, rect: rect2 }) => text.trim() && intersects(rect2, targetRect)).sort((a, b) => {
|
|
1859
|
+
const lineDelta = a.rect.top - b.rect.top;
|
|
1860
|
+
return Math.abs(lineDelta) > 4 ? lineDelta : a.rect.left - b.rect.left;
|
|
1861
|
+
});
|
|
1862
|
+
return matches.map(({ text }) => text).join(" ").replace(/\s+/g, " ").trim();
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1865
|
+
// src/lib/highlight-config-layer.ts
|
|
1866
|
+
var findOrCreateHighlightConfigLayer = (anchor) => {
|
|
1867
|
+
const pageLayer = anchor.closest(".page");
|
|
1868
|
+
if (!pageLayer) return null;
|
|
1869
|
+
const doc = getDocument(pageLayer);
|
|
1870
|
+
let layer = Array.from(pageLayer.children).find(
|
|
1871
|
+
(child) => child.classList.contains("PdfHighlighter__config-layer")
|
|
1872
|
+
);
|
|
1873
|
+
if (!layer) {
|
|
1874
|
+
layer = doc.createElement("div");
|
|
1875
|
+
layer.className = "PdfHighlighter__config-layer";
|
|
1876
|
+
pageLayer.appendChild(layer);
|
|
1877
|
+
}
|
|
1878
|
+
return layer;
|
|
1879
|
+
};
|
|
1880
|
+
|
|
1881
|
+
// src/components/TextHighlight.tsx
|
|
1681
1882
|
var DefaultStyleIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
|
|
1682
1883
|
var DefaultDeleteIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
|
|
1884
|
+
var DefaultCopyIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }));
|
|
1885
|
+
var DefaultCopiedIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" }));
|
|
1683
1886
|
var HighlightIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M6 14l3 3v5h6v-5l3-3V9H6v5zm5-12h2v3h-2V2zM3.5 5.875L4.914 4.46l2.12 2.122L5.622 8 3.5 5.875zm13.46.71l2.123-2.12 1.414 1.414L18.375 8l-1.414-1.414z" }));
|
|
1684
1887
|
var UnderlineIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z" }));
|
|
1685
1888
|
var StrikethroughIcon = () => /* @__PURE__ */ React7.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React7.createElement("path", { d: "M10 19h4v-3h-4v3zM5 4v3h5v3h4V7h5V4H5zM3 14h18v-2H3v2z" }));
|
|
@@ -1709,12 +1912,28 @@ var TextHighlight = ({
|
|
|
1709
1912
|
onDelete,
|
|
1710
1913
|
styleIcon,
|
|
1711
1914
|
deleteIcon,
|
|
1915
|
+
copyText,
|
|
1712
1916
|
colorPresets = DEFAULT_COLOR_PRESETS
|
|
1713
1917
|
}) => {
|
|
1714
1918
|
const [isStylePanelOpen, setIsStylePanelOpen] = useState6(false);
|
|
1715
1919
|
const [isHovered, setIsHovered] = useState6(false);
|
|
1920
|
+
const [isCopied, setIsCopied] = useState6(false);
|
|
1921
|
+
const [configLayer, setConfigLayer] = useState6(null);
|
|
1716
1922
|
const stylePanelRef = useRef6(null);
|
|
1717
1923
|
const containerRef = useRef6(null);
|
|
1924
|
+
const copyResetTimeoutRef = useRef6(null);
|
|
1925
|
+
useLayoutEffect3(() => {
|
|
1926
|
+
if (containerRef.current) {
|
|
1927
|
+
setConfigLayer(findOrCreateHighlightConfigLayer(containerRef.current));
|
|
1928
|
+
}
|
|
1929
|
+
}, []);
|
|
1930
|
+
useEffect5(() => {
|
|
1931
|
+
return () => {
|
|
1932
|
+
if (copyResetTimeoutRef.current) {
|
|
1933
|
+
window.clearTimeout(copyResetTimeoutRef.current);
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
}, []);
|
|
1718
1937
|
useEffect5(() => {
|
|
1719
1938
|
if (!isStylePanelOpen) return;
|
|
1720
1939
|
const handleClickOutside = (e) => {
|
|
@@ -1753,6 +1972,19 @@ var TextHighlight = ({
|
|
|
1753
1972
|
}
|
|
1754
1973
|
return baseStyle;
|
|
1755
1974
|
};
|
|
1975
|
+
const handleCopy = async (event) => {
|
|
1976
|
+
event.stopPropagation();
|
|
1977
|
+
const text = copyText || highlight.content?.text || (containerRef.current && firstRect ? extractTextFromHighlightRect(containerRef.current, firstRect) : "");
|
|
1978
|
+
await copyTextToClipboard(text);
|
|
1979
|
+
setIsCopied(true);
|
|
1980
|
+
if (copyResetTimeoutRef.current) {
|
|
1981
|
+
window.clearTimeout(copyResetTimeoutRef.current);
|
|
1982
|
+
}
|
|
1983
|
+
copyResetTimeoutRef.current = window.setTimeout(() => {
|
|
1984
|
+
setIsCopied(false);
|
|
1985
|
+
copyResetTimeoutRef.current = null;
|
|
1986
|
+
}, 1500);
|
|
1987
|
+
};
|
|
1756
1988
|
return /* @__PURE__ */ React7.createElement(
|
|
1757
1989
|
"div",
|
|
1758
1990
|
{
|
|
@@ -1760,107 +1992,120 @@ var TextHighlight = ({
|
|
|
1760
1992
|
onContextMenu,
|
|
1761
1993
|
ref: containerRef
|
|
1762
1994
|
},
|
|
1763
|
-
(onStyleChange || onDelete) && firstRect &&
|
|
1764
|
-
"div",
|
|
1765
|
-
{
|
|
1766
|
-
className: "TextHighlight__toolbar-wrapper",
|
|
1767
|
-
style: {
|
|
1768
|
-
position: "absolute",
|
|
1769
|
-
left: firstRect.left,
|
|
1770
|
-
top: firstRect.top - 28,
|
|
1771
|
-
paddingBottom: 12
|
|
1772
|
-
},
|
|
1773
|
-
onMouseEnter: () => setIsHovered(true),
|
|
1774
|
-
onMouseLeave: () => setIsHovered(false)
|
|
1775
|
-
},
|
|
1995
|
+
configLayer && (onStyleChange || onDelete) && firstRect && createPortal(
|
|
1776
1996
|
/* @__PURE__ */ React7.createElement(
|
|
1777
1997
|
"div",
|
|
1778
1998
|
{
|
|
1779
|
-
className:
|
|
1999
|
+
className: "TextHighlight__toolbar-wrapper",
|
|
2000
|
+
style: {
|
|
2001
|
+
position: "absolute",
|
|
2002
|
+
left: firstRect.left,
|
|
2003
|
+
top: firstRect.top - 28,
|
|
2004
|
+
paddingBottom: 12
|
|
2005
|
+
},
|
|
2006
|
+
onMouseEnter: () => setIsHovered(true),
|
|
2007
|
+
onMouseLeave: () => setIsHovered(false)
|
|
1780
2008
|
},
|
|
1781
|
-
|
|
1782
|
-
"
|
|
2009
|
+
/* @__PURE__ */ React7.createElement(
|
|
2010
|
+
"div",
|
|
1783
2011
|
{
|
|
1784
|
-
className: "
|
|
1785
|
-
onClick: (e) => {
|
|
1786
|
-
e.stopPropagation();
|
|
1787
|
-
setIsStylePanelOpen(!isStylePanelOpen);
|
|
1788
|
-
},
|
|
1789
|
-
title: "Change style",
|
|
1790
|
-
type: "button"
|
|
2012
|
+
className: `TextHighlight__toolbar ${isHovered || isScrolledTo || isStylePanelOpen ? "TextHighlight__toolbar--visible" : ""}`
|
|
1791
2013
|
},
|
|
1792
|
-
|
|
2014
|
+
onStyleChange && /* @__PURE__ */ React7.createElement(
|
|
2015
|
+
"button",
|
|
2016
|
+
{
|
|
2017
|
+
className: "TextHighlight__style-button",
|
|
2018
|
+
onClick: (e) => {
|
|
2019
|
+
e.stopPropagation();
|
|
2020
|
+
setIsStylePanelOpen(!isStylePanelOpen);
|
|
2021
|
+
},
|
|
2022
|
+
title: "Change style",
|
|
2023
|
+
type: "button"
|
|
2024
|
+
},
|
|
2025
|
+
styleIcon || /* @__PURE__ */ React7.createElement(DefaultStyleIcon, null)
|
|
2026
|
+
),
|
|
2027
|
+
/* @__PURE__ */ React7.createElement(
|
|
2028
|
+
"button",
|
|
2029
|
+
{
|
|
2030
|
+
className: "TextHighlight__copy-button",
|
|
2031
|
+
onClick: handleCopy,
|
|
2032
|
+
title: isCopied ? "Copied" : "Copy text",
|
|
2033
|
+
type: "button"
|
|
2034
|
+
},
|
|
2035
|
+
isCopied ? /* @__PURE__ */ React7.createElement(DefaultCopiedIcon, null) : /* @__PURE__ */ React7.createElement(DefaultCopyIcon, null)
|
|
2036
|
+
),
|
|
2037
|
+
onDelete && /* @__PURE__ */ React7.createElement(
|
|
2038
|
+
"button",
|
|
2039
|
+
{
|
|
2040
|
+
className: "TextHighlight__delete-button",
|
|
2041
|
+
onClick: (e) => {
|
|
2042
|
+
e.stopPropagation();
|
|
2043
|
+
onDelete();
|
|
2044
|
+
},
|
|
2045
|
+
title: "Delete",
|
|
2046
|
+
type: "button"
|
|
2047
|
+
},
|
|
2048
|
+
deleteIcon || /* @__PURE__ */ React7.createElement(DefaultDeleteIcon, null)
|
|
2049
|
+
)
|
|
1793
2050
|
),
|
|
1794
|
-
|
|
1795
|
-
"
|
|
2051
|
+
isStylePanelOpen && onStyleChange && /* @__PURE__ */ React7.createElement(
|
|
2052
|
+
"div",
|
|
1796
2053
|
{
|
|
1797
|
-
className: "
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
onDelete();
|
|
1801
|
-
},
|
|
1802
|
-
title: "Delete",
|
|
1803
|
-
type: "button"
|
|
2054
|
+
className: "TextHighlight__style-panel",
|
|
2055
|
+
ref: stylePanelRef,
|
|
2056
|
+
onClick: (e) => e.stopPropagation()
|
|
1804
2057
|
},
|
|
1805
|
-
|
|
2058
|
+
/* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Style"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-buttons" }, /* @__PURE__ */ React7.createElement(
|
|
2059
|
+
"button",
|
|
2060
|
+
{
|
|
2061
|
+
type: "button",
|
|
2062
|
+
className: `TextHighlight__style-type-button ${highlightStyle === "highlight" ? "active" : ""}`,
|
|
2063
|
+
onClick: () => onStyleChange({ highlightStyle: "highlight" }),
|
|
2064
|
+
title: "Highlight"
|
|
2065
|
+
},
|
|
2066
|
+
/* @__PURE__ */ React7.createElement(HighlightIcon, null)
|
|
2067
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2068
|
+
"button",
|
|
2069
|
+
{
|
|
2070
|
+
type: "button",
|
|
2071
|
+
className: `TextHighlight__style-type-button ${highlightStyle === "underline" ? "active" : ""}`,
|
|
2072
|
+
onClick: () => onStyleChange({ highlightStyle: "underline" }),
|
|
2073
|
+
title: "Underline"
|
|
2074
|
+
},
|
|
2075
|
+
/* @__PURE__ */ React7.createElement(UnderlineIcon, null)
|
|
2076
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2077
|
+
"button",
|
|
2078
|
+
{
|
|
2079
|
+
type: "button",
|
|
2080
|
+
className: `TextHighlight__style-type-button ${highlightStyle === "strikethrough" ? "active" : ""}`,
|
|
2081
|
+
onClick: () => onStyleChange({ highlightStyle: "strikethrough" }),
|
|
2082
|
+
title: "Strikethrough"
|
|
2083
|
+
},
|
|
2084
|
+
/* @__PURE__ */ React7.createElement(StrikethroughIcon, null)
|
|
2085
|
+
))),
|
|
2086
|
+
/* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Color"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-options" }, /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React7.createElement(
|
|
2087
|
+
"button",
|
|
2088
|
+
{
|
|
2089
|
+
key: c,
|
|
2090
|
+
type: "button",
|
|
2091
|
+
className: `TextHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
|
|
2092
|
+
style: { backgroundColor: c },
|
|
2093
|
+
onClick: () => onStyleChange({ highlightColor: c }),
|
|
2094
|
+
title: c
|
|
2095
|
+
}
|
|
2096
|
+
))), /* @__PURE__ */ React7.createElement(
|
|
2097
|
+
"input",
|
|
2098
|
+
{
|
|
2099
|
+
type: "color",
|
|
2100
|
+
value: highlightColor,
|
|
2101
|
+
onChange: (e) => {
|
|
2102
|
+
onStyleChange({ highlightColor: e.target.value });
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
)))
|
|
1806
2106
|
)
|
|
1807
2107
|
),
|
|
1808
|
-
|
|
1809
|
-
"div",
|
|
1810
|
-
{
|
|
1811
|
-
className: "TextHighlight__style-panel",
|
|
1812
|
-
ref: stylePanelRef,
|
|
1813
|
-
onClick: (e) => e.stopPropagation()
|
|
1814
|
-
},
|
|
1815
|
-
/* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Style"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-buttons" }, /* @__PURE__ */ React7.createElement(
|
|
1816
|
-
"button",
|
|
1817
|
-
{
|
|
1818
|
-
type: "button",
|
|
1819
|
-
className: `TextHighlight__style-type-button ${highlightStyle === "highlight" ? "active" : ""}`,
|
|
1820
|
-
onClick: () => onStyleChange({ highlightStyle: "highlight" }),
|
|
1821
|
-
title: "Highlight"
|
|
1822
|
-
},
|
|
1823
|
-
/* @__PURE__ */ React7.createElement(HighlightIcon, null)
|
|
1824
|
-
), /* @__PURE__ */ React7.createElement(
|
|
1825
|
-
"button",
|
|
1826
|
-
{
|
|
1827
|
-
type: "button",
|
|
1828
|
-
className: `TextHighlight__style-type-button ${highlightStyle === "underline" ? "active" : ""}`,
|
|
1829
|
-
onClick: () => onStyleChange({ highlightStyle: "underline" }),
|
|
1830
|
-
title: "Underline"
|
|
1831
|
-
},
|
|
1832
|
-
/* @__PURE__ */ React7.createElement(UnderlineIcon, null)
|
|
1833
|
-
), /* @__PURE__ */ React7.createElement(
|
|
1834
|
-
"button",
|
|
1835
|
-
{
|
|
1836
|
-
type: "button",
|
|
1837
|
-
className: `TextHighlight__style-type-button ${highlightStyle === "strikethrough" ? "active" : ""}`,
|
|
1838
|
-
onClick: () => onStyleChange({ highlightStyle: "strikethrough" }),
|
|
1839
|
-
title: "Strikethrough"
|
|
1840
|
-
},
|
|
1841
|
-
/* @__PURE__ */ React7.createElement(StrikethroughIcon, null)
|
|
1842
|
-
))),
|
|
1843
|
-
/* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__style-row" }, /* @__PURE__ */ React7.createElement("label", null, "Color"), /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-options" }, /* @__PURE__ */ React7.createElement("div", { className: "TextHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React7.createElement(
|
|
1844
|
-
"button",
|
|
1845
|
-
{
|
|
1846
|
-
key: c,
|
|
1847
|
-
type: "button",
|
|
1848
|
-
className: `TextHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
|
|
1849
|
-
style: { backgroundColor: c },
|
|
1850
|
-
onClick: () => onStyleChange({ highlightColor: c }),
|
|
1851
|
-
title: c
|
|
1852
|
-
}
|
|
1853
|
-
))), /* @__PURE__ */ React7.createElement(
|
|
1854
|
-
"input",
|
|
1855
|
-
{
|
|
1856
|
-
type: "color",
|
|
1857
|
-
value: highlightColor,
|
|
1858
|
-
onChange: (e) => {
|
|
1859
|
-
onStyleChange({ highlightColor: e.target.value });
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
)))
|
|
1863
|
-
)
|
|
2108
|
+
configLayer
|
|
1864
2109
|
),
|
|
1865
2110
|
/* @__PURE__ */ React7.createElement(
|
|
1866
2111
|
"div",
|
|
@@ -1966,11 +2211,15 @@ var MonitoredHighlightContainer = ({
|
|
|
1966
2211
|
import React10, {
|
|
1967
2212
|
useState as useState7,
|
|
1968
2213
|
useRef as useRef9,
|
|
1969
|
-
useEffect as useEffect7
|
|
2214
|
+
useEffect as useEffect7,
|
|
2215
|
+
useLayoutEffect as useLayoutEffect4
|
|
1970
2216
|
} from "react";
|
|
2217
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
1971
2218
|
import { Rnd } from "react-rnd";
|
|
1972
2219
|
var DefaultStyleIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
|
|
1973
2220
|
var DefaultDeleteIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
|
|
2221
|
+
var DefaultCopyIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v12h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }));
|
|
2222
|
+
var DefaultCopiedIcon2 = () => /* @__PURE__ */ React10.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React10.createElement("path", { d: "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" }));
|
|
1974
2223
|
var DEFAULT_COLOR_PRESETS2 = [
|
|
1975
2224
|
"rgba(255, 226, 143, 1)",
|
|
1976
2225
|
// Yellow (default)
|
|
@@ -1996,11 +2245,28 @@ var AreaHighlight = ({
|
|
|
1996
2245
|
onDelete,
|
|
1997
2246
|
styleIcon,
|
|
1998
2247
|
deleteIcon,
|
|
2248
|
+
copyText,
|
|
1999
2249
|
colorPresets = DEFAULT_COLOR_PRESETS2
|
|
2000
2250
|
}) => {
|
|
2001
2251
|
const [isStylePanelOpen, setIsStylePanelOpen] = useState7(false);
|
|
2002
2252
|
const [isHovered, setIsHovered] = useState7(false);
|
|
2253
|
+
const [isCopied, setIsCopied] = useState7(false);
|
|
2254
|
+
const [configLayer, setConfigLayer] = useState7(null);
|
|
2003
2255
|
const stylePanelRef = useRef9(null);
|
|
2256
|
+
const containerRef = useRef9(null);
|
|
2257
|
+
const copyResetTimeoutRef = useRef9(null);
|
|
2258
|
+
useLayoutEffect4(() => {
|
|
2259
|
+
if (containerRef.current) {
|
|
2260
|
+
setConfigLayer(findOrCreateHighlightConfigLayer(containerRef.current));
|
|
2261
|
+
}
|
|
2262
|
+
}, []);
|
|
2263
|
+
useEffect7(() => {
|
|
2264
|
+
return () => {
|
|
2265
|
+
if (copyResetTimeoutRef.current) {
|
|
2266
|
+
window.clearTimeout(copyResetTimeoutRef.current);
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
}, []);
|
|
2004
2270
|
useEffect7(() => {
|
|
2005
2271
|
if (!isStylePanelOpen) return;
|
|
2006
2272
|
const handleClickOutside = (e) => {
|
|
@@ -2022,85 +2288,115 @@ var AreaHighlight = ({
|
|
|
2022
2288
|
...style,
|
|
2023
2289
|
backgroundColor: highlightColor
|
|
2024
2290
|
};
|
|
2291
|
+
const handleCopy = async (event) => {
|
|
2292
|
+
event.stopPropagation();
|
|
2293
|
+
const text = copyText || highlight.content?.text || (containerRef.current ? extractTextFromHighlightRect(
|
|
2294
|
+
containerRef.current,
|
|
2295
|
+
highlight.position.boundingRect
|
|
2296
|
+
) : "");
|
|
2297
|
+
await copyTextToClipboard(text);
|
|
2298
|
+
setIsCopied(true);
|
|
2299
|
+
if (copyResetTimeoutRef.current) {
|
|
2300
|
+
window.clearTimeout(copyResetTimeoutRef.current);
|
|
2301
|
+
}
|
|
2302
|
+
copyResetTimeoutRef.current = window.setTimeout(() => {
|
|
2303
|
+
setIsCopied(false);
|
|
2304
|
+
copyResetTimeoutRef.current = null;
|
|
2305
|
+
}, 1500);
|
|
2306
|
+
};
|
|
2025
2307
|
return /* @__PURE__ */ React10.createElement(
|
|
2026
2308
|
"div",
|
|
2027
2309
|
{
|
|
2028
2310
|
className: `AreaHighlight ${highlightClass}`,
|
|
2029
|
-
onContextMenu
|
|
2311
|
+
onContextMenu,
|
|
2312
|
+
ref: containerRef
|
|
2030
2313
|
},
|
|
2031
|
-
(onStyleChange || onDelete) &&
|
|
2032
|
-
"div",
|
|
2033
|
-
{
|
|
2034
|
-
className: "AreaHighlight__toolbar-wrapper",
|
|
2035
|
-
style: {
|
|
2036
|
-
position: "absolute",
|
|
2037
|
-
left: highlight.position.boundingRect.left,
|
|
2038
|
-
top: highlight.position.boundingRect.top - 28,
|
|
2039
|
-
paddingBottom: 12
|
|
2040
|
-
},
|
|
2041
|
-
onMouseEnter: () => setIsHovered(true),
|
|
2042
|
-
onMouseLeave: () => setIsHovered(false)
|
|
2043
|
-
},
|
|
2314
|
+
configLayer && (onStyleChange || onDelete) && createPortal2(
|
|
2044
2315
|
/* @__PURE__ */ React10.createElement(
|
|
2045
2316
|
"div",
|
|
2046
2317
|
{
|
|
2047
|
-
className:
|
|
2318
|
+
className: "AreaHighlight__toolbar-wrapper",
|
|
2319
|
+
style: {
|
|
2320
|
+
position: "absolute",
|
|
2321
|
+
left: highlight.position.boundingRect.left,
|
|
2322
|
+
top: highlight.position.boundingRect.top - 28,
|
|
2323
|
+
paddingBottom: 12
|
|
2324
|
+
},
|
|
2325
|
+
onMouseEnter: () => setIsHovered(true),
|
|
2326
|
+
onMouseLeave: () => setIsHovered(false)
|
|
2048
2327
|
},
|
|
2049
|
-
|
|
2050
|
-
"
|
|
2328
|
+
/* @__PURE__ */ React10.createElement(
|
|
2329
|
+
"div",
|
|
2051
2330
|
{
|
|
2052
|
-
className: "
|
|
2053
|
-
onClick: (e) => {
|
|
2054
|
-
e.stopPropagation();
|
|
2055
|
-
setIsStylePanelOpen(!isStylePanelOpen);
|
|
2056
|
-
},
|
|
2057
|
-
title: "Change color",
|
|
2058
|
-
type: "button"
|
|
2331
|
+
className: `AreaHighlight__toolbar ${isHovered || isScrolledTo || isStylePanelOpen ? "AreaHighlight__toolbar--visible" : ""}`
|
|
2059
2332
|
},
|
|
2060
|
-
|
|
2333
|
+
onStyleChange && /* @__PURE__ */ React10.createElement(
|
|
2334
|
+
"button",
|
|
2335
|
+
{
|
|
2336
|
+
className: "AreaHighlight__style-button",
|
|
2337
|
+
onClick: (e) => {
|
|
2338
|
+
e.stopPropagation();
|
|
2339
|
+
setIsStylePanelOpen(!isStylePanelOpen);
|
|
2340
|
+
},
|
|
2341
|
+
title: "Change color",
|
|
2342
|
+
type: "button"
|
|
2343
|
+
},
|
|
2344
|
+
styleIcon || /* @__PURE__ */ React10.createElement(DefaultStyleIcon2, null)
|
|
2345
|
+
),
|
|
2346
|
+
/* @__PURE__ */ React10.createElement(
|
|
2347
|
+
"button",
|
|
2348
|
+
{
|
|
2349
|
+
className: "AreaHighlight__copy-button",
|
|
2350
|
+
onClick: handleCopy,
|
|
2351
|
+
title: isCopied ? "Copied" : "Copy text",
|
|
2352
|
+
type: "button"
|
|
2353
|
+
},
|
|
2354
|
+
isCopied ? /* @__PURE__ */ React10.createElement(DefaultCopiedIcon2, null) : /* @__PURE__ */ React10.createElement(DefaultCopyIcon2, null)
|
|
2355
|
+
),
|
|
2356
|
+
onDelete && /* @__PURE__ */ React10.createElement(
|
|
2357
|
+
"button",
|
|
2358
|
+
{
|
|
2359
|
+
className: "AreaHighlight__delete-button",
|
|
2360
|
+
onClick: (e) => {
|
|
2361
|
+
e.stopPropagation();
|
|
2362
|
+
onDelete();
|
|
2363
|
+
},
|
|
2364
|
+
title: "Delete",
|
|
2365
|
+
type: "button"
|
|
2366
|
+
},
|
|
2367
|
+
deleteIcon || /* @__PURE__ */ React10.createElement(DefaultDeleteIcon2, null)
|
|
2368
|
+
)
|
|
2061
2369
|
),
|
|
2062
|
-
|
|
2063
|
-
"
|
|
2370
|
+
isStylePanelOpen && onStyleChange && /* @__PURE__ */ React10.createElement(
|
|
2371
|
+
"div",
|
|
2064
2372
|
{
|
|
2065
|
-
className: "
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
onDelete();
|
|
2069
|
-
},
|
|
2070
|
-
title: "Delete",
|
|
2071
|
-
type: "button"
|
|
2373
|
+
className: "AreaHighlight__style-panel",
|
|
2374
|
+
ref: stylePanelRef,
|
|
2375
|
+
onClick: (e) => e.stopPropagation()
|
|
2072
2376
|
},
|
|
2073
|
-
|
|
2377
|
+
/* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__style-row" }, /* @__PURE__ */ React10.createElement("label", null, "Color"), /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-options" }, /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React10.createElement(
|
|
2378
|
+
"button",
|
|
2379
|
+
{
|
|
2380
|
+
key: c,
|
|
2381
|
+
type: "button",
|
|
2382
|
+
className: `AreaHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
|
|
2383
|
+
style: { backgroundColor: c },
|
|
2384
|
+
onClick: () => onStyleChange({ highlightColor: c }),
|
|
2385
|
+
title: c
|
|
2386
|
+
}
|
|
2387
|
+
))), /* @__PURE__ */ React10.createElement(
|
|
2388
|
+
"input",
|
|
2389
|
+
{
|
|
2390
|
+
type: "color",
|
|
2391
|
+
value: highlightColor,
|
|
2392
|
+
onChange: (e) => {
|
|
2393
|
+
onStyleChange({ highlightColor: e.target.value });
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
)))
|
|
2074
2397
|
)
|
|
2075
2398
|
),
|
|
2076
|
-
|
|
2077
|
-
"div",
|
|
2078
|
-
{
|
|
2079
|
-
className: "AreaHighlight__style-panel",
|
|
2080
|
-
ref: stylePanelRef,
|
|
2081
|
-
onClick: (e) => e.stopPropagation()
|
|
2082
|
-
},
|
|
2083
|
-
/* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__style-row" }, /* @__PURE__ */ React10.createElement("label", null, "Color"), /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-options" }, /* @__PURE__ */ React10.createElement("div", { className: "AreaHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React10.createElement(
|
|
2084
|
-
"button",
|
|
2085
|
-
{
|
|
2086
|
-
key: c,
|
|
2087
|
-
type: "button",
|
|
2088
|
-
className: `AreaHighlight__color-preset ${highlightColor === c ? "active" : ""}`,
|
|
2089
|
-
style: { backgroundColor: c },
|
|
2090
|
-
onClick: () => onStyleChange({ highlightColor: c }),
|
|
2091
|
-
title: c
|
|
2092
|
-
}
|
|
2093
|
-
))), /* @__PURE__ */ React10.createElement(
|
|
2094
|
-
"input",
|
|
2095
|
-
{
|
|
2096
|
-
type: "color",
|
|
2097
|
-
value: highlightColor,
|
|
2098
|
-
onChange: (e) => {
|
|
2099
|
-
onStyleChange({ highlightColor: e.target.value });
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
)))
|
|
2103
|
-
)
|
|
2399
|
+
configLayer
|
|
2104
2400
|
),
|
|
2105
2401
|
/* @__PURE__ */ React10.createElement(
|
|
2106
2402
|
Rnd,
|
|
@@ -2157,6 +2453,8 @@ var DefaultDragIcon = () => /* @__PURE__ */ React11.createElement("svg", { width
|
|
|
2157
2453
|
var DefaultEditIcon = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }));
|
|
2158
2454
|
var DefaultStyleIcon3 = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
|
|
2159
2455
|
var DefaultDeleteIcon3 = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
|
|
2456
|
+
var DefaultCompactIcon = () => /* @__PURE__ */ React11.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M6 3h9l5 5v13H6V3zm8 1.5V9h4.5L14 4.5zM8 12h8v1.5H8V12zm0 3h8v1.5H8V15zm0 3h5v1.5H8V18z" }));
|
|
2457
|
+
var DefaultCollapseIcon = () => /* @__PURE__ */ React11.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React11.createElement("path", { d: "M7 10v2h10v-2H7zm-2-7h14c1.1 0 2 .9 2 2v14c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2zm0 2v14h14V5H5z" }));
|
|
2160
2458
|
var DEFAULT_BACKGROUND_PRESETS = ["transparent", "#ffffc8", "#ffcdd2", "#c8e6c9", "#bbdefb", "#e1bee7"];
|
|
2161
2459
|
var DEFAULT_TEXT_PRESETS = ["#333333", "#d32f2f", "#1976d2", "#388e3c", "#7b1fa2"];
|
|
2162
2460
|
var FreetextHighlight = ({
|
|
@@ -2180,9 +2478,13 @@ var FreetextHighlight = ({
|
|
|
2180
2478
|
backgroundColorPresets = DEFAULT_BACKGROUND_PRESETS,
|
|
2181
2479
|
textColorPresets = DEFAULT_TEXT_PRESETS,
|
|
2182
2480
|
onDelete,
|
|
2183
|
-
deleteIcon
|
|
2481
|
+
deleteIcon,
|
|
2482
|
+
compact = false,
|
|
2483
|
+
compactSize = 32,
|
|
2484
|
+
compactIcon
|
|
2184
2485
|
}) => {
|
|
2185
2486
|
const [isEditing, setIsEditing] = useState8(false);
|
|
2487
|
+
const [isExpanded, setIsExpanded] = useState8(!compact);
|
|
2186
2488
|
const [isStylePanelOpen, setIsStylePanelOpen] = useState8(false);
|
|
2187
2489
|
const [text, setText] = useState8(highlight.content?.text || "");
|
|
2188
2490
|
const textareaRef = useRef10(null);
|
|
@@ -2190,6 +2492,13 @@ var FreetextHighlight = ({
|
|
|
2190
2492
|
useEffect8(() => {
|
|
2191
2493
|
setText(highlight.content?.text || "");
|
|
2192
2494
|
}, [highlight.content?.text]);
|
|
2495
|
+
useEffect8(() => {
|
|
2496
|
+
setIsExpanded(!compact);
|
|
2497
|
+
setIsStylePanelOpen(false);
|
|
2498
|
+
if (!compact) {
|
|
2499
|
+
setIsEditing(false);
|
|
2500
|
+
}
|
|
2501
|
+
}, [compact]);
|
|
2193
2502
|
useEffect8(() => {
|
|
2194
2503
|
if (isEditing && textareaRef.current) {
|
|
2195
2504
|
textareaRef.current.focus();
|
|
@@ -2213,10 +2522,14 @@ var FreetextHighlight = ({
|
|
|
2213
2522
|
}, [isStylePanelOpen]);
|
|
2214
2523
|
const highlightClass = isScrolledTo ? "FreetextHighlight--scrolledTo" : "";
|
|
2215
2524
|
const editingClass = isEditing ? "FreetextHighlight--editing" : "";
|
|
2216
|
-
const
|
|
2525
|
+
const compactClass = compact ? "FreetextHighlight--compact" : "";
|
|
2526
|
+
const isCompactCollapsed = compact && !isExpanded && !isEditing && !isStylePanelOpen;
|
|
2527
|
+
const collapsedClass = isCompactCollapsed ? "FreetextHighlight--collapsed" : "";
|
|
2528
|
+
const key = `${highlight.position.boundingRect.width}${highlight.position.boundingRect.height}${highlight.position.boundingRect.left}${highlight.position.boundingRect.top}${isCompactCollapsed ? "collapsed" : "expanded"}`;
|
|
2217
2529
|
const handleTextClick = (e) => {
|
|
2218
2530
|
e.stopPropagation();
|
|
2219
2531
|
if (!isEditing) {
|
|
2532
|
+
setIsExpanded(true);
|
|
2220
2533
|
setIsEditing(true);
|
|
2221
2534
|
onEditStart?.();
|
|
2222
2535
|
}
|
|
@@ -2251,7 +2564,7 @@ var FreetextHighlight = ({
|
|
|
2251
2564
|
return /* @__PURE__ */ React11.createElement(
|
|
2252
2565
|
"div",
|
|
2253
2566
|
{
|
|
2254
|
-
className: `FreetextHighlight ${highlightClass} ${editingClass}`,
|
|
2567
|
+
className: `FreetextHighlight ${highlightClass} ${editingClass} ${compactClass} ${collapsedClass}`,
|
|
2255
2568
|
onContextMenu
|
|
2256
2569
|
},
|
|
2257
2570
|
/* @__PURE__ */ React11.createElement(
|
|
@@ -2274,14 +2587,14 @@ var FreetextHighlight = ({
|
|
|
2274
2587
|
default: {
|
|
2275
2588
|
x: highlight.position.boundingRect.left,
|
|
2276
2589
|
y: highlight.position.boundingRect.top,
|
|
2277
|
-
width: highlight.position.boundingRect.width || 150,
|
|
2278
|
-
height: highlight.position.boundingRect.height || 80
|
|
2590
|
+
width: isCompactCollapsed ? compactSize : highlight.position.boundingRect.width || 150,
|
|
2591
|
+
height: isCompactCollapsed ? compactSize : highlight.position.boundingRect.height || 80
|
|
2279
2592
|
},
|
|
2280
|
-
minWidth: 100,
|
|
2281
|
-
minHeight: 50,
|
|
2593
|
+
minWidth: isCompactCollapsed ? compactSize : 100,
|
|
2594
|
+
minHeight: isCompactCollapsed ? compactSize : 50,
|
|
2282
2595
|
key,
|
|
2283
2596
|
bounds,
|
|
2284
|
-
enableResizing: {
|
|
2597
|
+
enableResizing: isCompactCollapsed ? false : {
|
|
2285
2598
|
top: false,
|
|
2286
2599
|
right: true,
|
|
2287
2600
|
bottom: true,
|
|
@@ -2306,9 +2619,21 @@ var FreetextHighlight = ({
|
|
|
2306
2619
|
onEditStart?.();
|
|
2307
2620
|
}
|
|
2308
2621
|
},
|
|
2309
|
-
cancel: ".FreetextHighlight__text, .FreetextHighlight__input, .FreetextHighlight__edit-button, .FreetextHighlight__style-button, .FreetextHighlight__style-panel, .FreetextHighlight__delete-button"
|
|
2622
|
+
cancel: ".FreetextHighlight__text, .FreetextHighlight__input, .FreetextHighlight__edit-button, .FreetextHighlight__style-button, .FreetextHighlight__style-panel, .FreetextHighlight__delete-button, .FreetextHighlight__collapse-button, .FreetextHighlight__compact-button"
|
|
2310
2623
|
},
|
|
2311
|
-
/* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__container", style: containerStyle },
|
|
2624
|
+
/* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__container", style: containerStyle }, isCompactCollapsed ? /* @__PURE__ */ React11.createElement(
|
|
2625
|
+
"button",
|
|
2626
|
+
{
|
|
2627
|
+
className: "FreetextHighlight__compact-button",
|
|
2628
|
+
type: "button",
|
|
2629
|
+
title: text || "Open note",
|
|
2630
|
+
onClick: (event) => {
|
|
2631
|
+
event.stopPropagation();
|
|
2632
|
+
setIsExpanded(true);
|
|
2633
|
+
}
|
|
2634
|
+
},
|
|
2635
|
+
compactIcon || /* @__PURE__ */ React11.createElement(DefaultCompactIcon, null)
|
|
2636
|
+
) : /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__toolbar" }, /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__drag-handle", title: "Drag to move" }, dragIcon || /* @__PURE__ */ React11.createElement(DefaultDragIcon, null)), /* @__PURE__ */ React11.createElement(
|
|
2312
2637
|
"button",
|
|
2313
2638
|
{
|
|
2314
2639
|
className: "FreetextHighlight__edit-button",
|
|
@@ -2329,6 +2654,19 @@ var FreetextHighlight = ({
|
|
|
2329
2654
|
type: "button"
|
|
2330
2655
|
},
|
|
2331
2656
|
styleIcon || /* @__PURE__ */ React11.createElement(DefaultStyleIcon3, null)
|
|
2657
|
+
), compact && /* @__PURE__ */ React11.createElement(
|
|
2658
|
+
"button",
|
|
2659
|
+
{
|
|
2660
|
+
className: "FreetextHighlight__collapse-button",
|
|
2661
|
+
onClick: (e) => {
|
|
2662
|
+
e.stopPropagation();
|
|
2663
|
+
setIsExpanded(false);
|
|
2664
|
+
setIsStylePanelOpen(false);
|
|
2665
|
+
},
|
|
2666
|
+
title: "Collapse note",
|
|
2667
|
+
type: "button"
|
|
2668
|
+
},
|
|
2669
|
+
/* @__PURE__ */ React11.createElement(DefaultCollapseIcon, null)
|
|
2332
2670
|
), onDelete && /* @__PURE__ */ React11.createElement(
|
|
2333
2671
|
"button",
|
|
2334
2672
|
{
|
|
@@ -2433,7 +2771,7 @@ var FreetextHighlight = ({
|
|
|
2433
2771
|
onKeyDown: handleKeyDown,
|
|
2434
2772
|
onClick: (e) => e.stopPropagation()
|
|
2435
2773
|
}
|
|
2436
|
-
) : /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__text" }, text || "New note")))
|
|
2774
|
+
) : /* @__PURE__ */ React11.createElement("div", { className: "FreetextHighlight__text" }, text || "New note"))))
|
|
2437
2775
|
)
|
|
2438
2776
|
);
|
|
2439
2777
|
};
|
|
@@ -2687,7 +3025,8 @@ var SignaturePad = ({
|
|
|
2687
3025
|
};
|
|
2688
3026
|
|
|
2689
3027
|
// src/components/DrawingHighlight.tsx
|
|
2690
|
-
import React14, { useState as useState9, useCallback as useCallback4, useEffect as useEffect10, useRef as useRef12 } from "react";
|
|
3028
|
+
import React14, { useState as useState9, useCallback as useCallback4, useEffect as useEffect10, useLayoutEffect as useLayoutEffect5, useRef as useRef12 } from "react";
|
|
3029
|
+
import { createPortal as createPortal3 } from "react-dom";
|
|
2691
3030
|
import { Rnd as Rnd4 } from "react-rnd";
|
|
2692
3031
|
var DRAWING_COLORS = ["#000000", "#FF0000", "#0000FF", "#00FF00", "#FFFF00"];
|
|
2693
3032
|
var STROKE_WIDTHS = [
|
|
@@ -2734,7 +3073,15 @@ var DrawingHighlight = ({
|
|
|
2734
3073
|
}) => {
|
|
2735
3074
|
const highlightClass = isScrolledTo ? "DrawingHighlight--scrolledTo" : "";
|
|
2736
3075
|
const [showStyleControls, setShowStyleControls] = useState9(false);
|
|
3076
|
+
const [isHovered, setIsHovered] = useState9(false);
|
|
3077
|
+
const [configLayer, setConfigLayer] = useState9(null);
|
|
2737
3078
|
const styleControlsRef = useRef12(null);
|
|
3079
|
+
const containerRef = useRef12(null);
|
|
3080
|
+
useLayoutEffect5(() => {
|
|
3081
|
+
if (containerRef.current) {
|
|
3082
|
+
setConfigLayer(findOrCreateHighlightConfigLayer(containerRef.current));
|
|
3083
|
+
}
|
|
3084
|
+
}, []);
|
|
2738
3085
|
useEffect10(() => {
|
|
2739
3086
|
if (!showStyleControls) return;
|
|
2740
3087
|
const handleClickOutside = (e) => {
|
|
@@ -2787,8 +3134,78 @@ var DrawingHighlight = ({
|
|
|
2787
3134
|
"div",
|
|
2788
3135
|
{
|
|
2789
3136
|
className: `DrawingHighlight ${highlightClass}`,
|
|
2790
|
-
onContextMenu
|
|
3137
|
+
onContextMenu,
|
|
3138
|
+
ref: containerRef
|
|
2791
3139
|
},
|
|
3140
|
+
configLayer && createPortal3(
|
|
3141
|
+
/* @__PURE__ */ React14.createElement(
|
|
3142
|
+
"div",
|
|
3143
|
+
{
|
|
3144
|
+
className: `DrawingHighlight__toolbar DrawingHighlight__toolbar--floating ${isHovered || isScrolledTo || showStyleControls ? "DrawingHighlight__toolbar--visible" : ""}`,
|
|
3145
|
+
style: {
|
|
3146
|
+
left: highlight.position.boundingRect.left + 4,
|
|
3147
|
+
top: highlight.position.boundingRect.top + 4
|
|
3148
|
+
},
|
|
3149
|
+
onMouseEnter: () => setIsHovered(true),
|
|
3150
|
+
onMouseLeave: () => setIsHovered(false)
|
|
3151
|
+
},
|
|
3152
|
+
/* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__drag-handle", title: "Drag to move" }, dragIcon || /* @__PURE__ */ React14.createElement(DefaultDragIcon3, null)),
|
|
3153
|
+
strokes && strokes.length > 0 && onStyleChange && /* @__PURE__ */ React14.createElement(
|
|
3154
|
+
"button",
|
|
3155
|
+
{
|
|
3156
|
+
type: "button",
|
|
3157
|
+
className: "DrawingHighlight__style-button",
|
|
3158
|
+
title: "Edit style",
|
|
3159
|
+
onClick: (e) => {
|
|
3160
|
+
e.stopPropagation();
|
|
3161
|
+
setShowStyleControls(!showStyleControls);
|
|
3162
|
+
}
|
|
3163
|
+
},
|
|
3164
|
+
/* @__PURE__ */ React14.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React14.createElement("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }))
|
|
3165
|
+
),
|
|
3166
|
+
onDelete && /* @__PURE__ */ React14.createElement(
|
|
3167
|
+
"button",
|
|
3168
|
+
{
|
|
3169
|
+
className: "DrawingHighlight__delete-button",
|
|
3170
|
+
onClick: (e) => {
|
|
3171
|
+
e.stopPropagation();
|
|
3172
|
+
onDelete();
|
|
3173
|
+
},
|
|
3174
|
+
title: "Delete",
|
|
3175
|
+
type: "button"
|
|
3176
|
+
},
|
|
3177
|
+
deleteIcon || /* @__PURE__ */ React14.createElement(DefaultDeleteIcon5, null)
|
|
3178
|
+
),
|
|
3179
|
+
showStyleControls && strokes && strokes.length > 0 && onStyleChange && /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__style-controls", ref: styleControlsRef }, /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__color-picker" }, DRAWING_COLORS.map((color) => /* @__PURE__ */ React14.createElement(
|
|
3180
|
+
"button",
|
|
3181
|
+
{
|
|
3182
|
+
key: color,
|
|
3183
|
+
type: "button",
|
|
3184
|
+
className: `DrawingHighlight__color-button ${currentColor === color ? "active" : ""}`,
|
|
3185
|
+
style: { backgroundColor: color },
|
|
3186
|
+
onClick: (e) => {
|
|
3187
|
+
e.stopPropagation();
|
|
3188
|
+
handleColorChange(color);
|
|
3189
|
+
},
|
|
3190
|
+
title: `Color: ${color}`
|
|
3191
|
+
}
|
|
3192
|
+
))), /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__width-picker" }, STROKE_WIDTHS.map((w) => /* @__PURE__ */ React14.createElement(
|
|
3193
|
+
"button",
|
|
3194
|
+
{
|
|
3195
|
+
key: w.value,
|
|
3196
|
+
type: "button",
|
|
3197
|
+
className: `DrawingHighlight__width-button ${currentWidth === w.value ? "active" : ""}`,
|
|
3198
|
+
onClick: (e) => {
|
|
3199
|
+
e.stopPropagation();
|
|
3200
|
+
handleWidthChange(w.value);
|
|
3201
|
+
},
|
|
3202
|
+
title: w.label
|
|
3203
|
+
},
|
|
3204
|
+
w.label
|
|
3205
|
+
))))
|
|
3206
|
+
),
|
|
3207
|
+
configLayer
|
|
3208
|
+
),
|
|
2792
3209
|
/* @__PURE__ */ React14.createElement(
|
|
2793
3210
|
Rnd4,
|
|
2794
3211
|
{
|
|
@@ -2826,72 +3243,29 @@ var DrawingHighlight = ({
|
|
|
2826
3243
|
key,
|
|
2827
3244
|
bounds,
|
|
2828
3245
|
lockAspectRatio: false,
|
|
2829
|
-
dragHandleClassName: "DrawingHighlight__drag-handle",
|
|
2830
3246
|
onClick: (event) => {
|
|
2831
3247
|
event.stopPropagation();
|
|
2832
3248
|
event.preventDefault();
|
|
2833
3249
|
},
|
|
2834
3250
|
style
|
|
2835
3251
|
},
|
|
2836
|
-
/* @__PURE__ */ React14.createElement(
|
|
2837
|
-
"
|
|
2838
|
-
{
|
|
2839
|
-
type: "button",
|
|
2840
|
-
className: "DrawingHighlight__style-button",
|
|
2841
|
-
title: "Edit style",
|
|
2842
|
-
onClick: (e) => {
|
|
2843
|
-
e.stopPropagation();
|
|
2844
|
-
setShowStyleControls(!showStyleControls);
|
|
2845
|
-
}
|
|
2846
|
-
},
|
|
2847
|
-
/* @__PURE__ */ React14.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React14.createElement("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }))
|
|
2848
|
-
), onDelete && /* @__PURE__ */ React14.createElement(
|
|
2849
|
-
"button",
|
|
2850
|
-
{
|
|
2851
|
-
className: "DrawingHighlight__delete-button",
|
|
2852
|
-
onClick: (e) => {
|
|
2853
|
-
e.stopPropagation();
|
|
2854
|
-
onDelete();
|
|
2855
|
-
},
|
|
2856
|
-
title: "Delete",
|
|
2857
|
-
type: "button"
|
|
2858
|
-
},
|
|
2859
|
-
deleteIcon || /* @__PURE__ */ React14.createElement(DefaultDeleteIcon5, null)
|
|
2860
|
-
)), showStyleControls && strokes && strokes.length > 0 && onStyleChange && /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__style-controls", ref: styleControlsRef }, /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__color-picker" }, DRAWING_COLORS.map((color) => /* @__PURE__ */ React14.createElement(
|
|
2861
|
-
"button",
|
|
2862
|
-
{
|
|
2863
|
-
key: color,
|
|
2864
|
-
type: "button",
|
|
2865
|
-
className: `DrawingHighlight__color-button ${currentColor === color ? "active" : ""}`,
|
|
2866
|
-
style: { backgroundColor: color },
|
|
2867
|
-
onClick: (e) => {
|
|
2868
|
-
e.stopPropagation();
|
|
2869
|
-
handleColorChange(color);
|
|
2870
|
-
},
|
|
2871
|
-
title: `Color: ${color}`
|
|
2872
|
-
}
|
|
2873
|
-
))), /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__width-picker" }, STROKE_WIDTHS.map((w) => /* @__PURE__ */ React14.createElement(
|
|
2874
|
-
"button",
|
|
3252
|
+
/* @__PURE__ */ React14.createElement(
|
|
3253
|
+
"div",
|
|
2875
3254
|
{
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
onClick: (e) => {
|
|
2880
|
-
e.stopPropagation();
|
|
2881
|
-
handleWidthChange(w.value);
|
|
2882
|
-
},
|
|
2883
|
-
title: w.label
|
|
3255
|
+
className: "DrawingHighlight__container",
|
|
3256
|
+
onMouseEnter: () => setIsHovered(true),
|
|
3257
|
+
onMouseLeave: () => setIsHovered(false)
|
|
2884
3258
|
},
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
}
|
|
2894
|
-
)
|
|
3259
|
+
/* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__content" }, imageUrl ? /* @__PURE__ */ React14.createElement(
|
|
3260
|
+
"img",
|
|
3261
|
+
{
|
|
3262
|
+
src: imageUrl,
|
|
3263
|
+
alt: "Drawing",
|
|
3264
|
+
className: "DrawingHighlight__image",
|
|
3265
|
+
draggable: false
|
|
3266
|
+
}
|
|
3267
|
+
) : /* @__PURE__ */ React14.createElement("div", { className: "DrawingHighlight__placeholder" }, "No drawing"))
|
|
3268
|
+
)
|
|
2895
3269
|
)
|
|
2896
3270
|
);
|
|
2897
3271
|
};
|
|
@@ -2900,8 +3274,10 @@ var DrawingHighlight = ({
|
|
|
2900
3274
|
import React15, {
|
|
2901
3275
|
useState as useState10,
|
|
2902
3276
|
useRef as useRef13,
|
|
2903
|
-
useEffect as useEffect11
|
|
3277
|
+
useEffect as useEffect11,
|
|
3278
|
+
useLayoutEffect as useLayoutEffect6
|
|
2904
3279
|
} from "react";
|
|
3280
|
+
import { createPortal as createPortal4 } from "react-dom";
|
|
2905
3281
|
import { Rnd as Rnd5 } from "react-rnd";
|
|
2906
3282
|
var DefaultStyleIcon4 = () => /* @__PURE__ */ React15.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React15.createElement("path", { d: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z" }));
|
|
2907
3283
|
var DefaultDeleteIcon6 = () => /* @__PURE__ */ React15.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React15.createElement("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }));
|
|
@@ -2944,7 +3320,14 @@ var ShapeHighlight = ({
|
|
|
2944
3320
|
}) => {
|
|
2945
3321
|
const [isStylePanelOpen, setIsStylePanelOpen] = useState10(false);
|
|
2946
3322
|
const [isHovered, setIsHovered] = useState10(false);
|
|
3323
|
+
const [configLayer, setConfigLayer] = useState10(null);
|
|
2947
3324
|
const stylePanelRef = useRef13(null);
|
|
3325
|
+
const containerRef = useRef13(null);
|
|
3326
|
+
useLayoutEffect6(() => {
|
|
3327
|
+
if (containerRef.current) {
|
|
3328
|
+
setConfigLayer(findOrCreateHighlightConfigLayer(containerRef.current));
|
|
3329
|
+
}
|
|
3330
|
+
}, []);
|
|
2948
3331
|
useEffect11(() => {
|
|
2949
3332
|
if (!isStylePanelOpen) return;
|
|
2950
3333
|
const handleClickOutside = (e) => {
|
|
@@ -3056,92 +3439,96 @@ var ShapeHighlight = ({
|
|
|
3056
3439
|
"div",
|
|
3057
3440
|
{
|
|
3058
3441
|
className: `ShapeHighlight ${highlightClass}`,
|
|
3059
|
-
onContextMenu
|
|
3442
|
+
onContextMenu,
|
|
3443
|
+
ref: containerRef
|
|
3060
3444
|
},
|
|
3061
|
-
(onStyleChange || onDelete) &&
|
|
3062
|
-
"div",
|
|
3063
|
-
{
|
|
3064
|
-
className: "ShapeHighlight__toolbar-wrapper",
|
|
3065
|
-
style: {
|
|
3066
|
-
position: "absolute",
|
|
3067
|
-
left: highlight.position.boundingRect.left,
|
|
3068
|
-
top: highlight.position.boundingRect.top - 28,
|
|
3069
|
-
paddingBottom: 12
|
|
3070
|
-
},
|
|
3071
|
-
onMouseEnter: () => setIsHovered(true),
|
|
3072
|
-
onMouseLeave: () => setIsHovered(false)
|
|
3073
|
-
},
|
|
3445
|
+
configLayer && (onStyleChange || onDelete) && createPortal4(
|
|
3074
3446
|
/* @__PURE__ */ React15.createElement(
|
|
3075
3447
|
"div",
|
|
3076
3448
|
{
|
|
3077
|
-
className:
|
|
3449
|
+
className: "ShapeHighlight__toolbar-wrapper",
|
|
3450
|
+
style: {
|
|
3451
|
+
position: "absolute",
|
|
3452
|
+
left: highlight.position.boundingRect.left,
|
|
3453
|
+
top: highlight.position.boundingRect.top - 28,
|
|
3454
|
+
paddingBottom: 12
|
|
3455
|
+
},
|
|
3456
|
+
onMouseEnter: () => setIsHovered(true),
|
|
3457
|
+
onMouseLeave: () => setIsHovered(false)
|
|
3078
3458
|
},
|
|
3079
|
-
|
|
3080
|
-
"
|
|
3459
|
+
/* @__PURE__ */ React15.createElement(
|
|
3460
|
+
"div",
|
|
3081
3461
|
{
|
|
3082
|
-
className: "
|
|
3083
|
-
onClick: (e) => {
|
|
3084
|
-
e.stopPropagation();
|
|
3085
|
-
setIsStylePanelOpen(!isStylePanelOpen);
|
|
3086
|
-
},
|
|
3087
|
-
title: "Change style",
|
|
3088
|
-
type: "button"
|
|
3462
|
+
className: `ShapeHighlight__toolbar ${isHovered || isScrolledTo || isStylePanelOpen ? "ShapeHighlight__toolbar--visible" : ""}`
|
|
3089
3463
|
},
|
|
3090
|
-
|
|
3464
|
+
onStyleChange && /* @__PURE__ */ React15.createElement(
|
|
3465
|
+
"button",
|
|
3466
|
+
{
|
|
3467
|
+
className: "ShapeHighlight__style-button",
|
|
3468
|
+
onClick: (e) => {
|
|
3469
|
+
e.stopPropagation();
|
|
3470
|
+
setIsStylePanelOpen(!isStylePanelOpen);
|
|
3471
|
+
},
|
|
3472
|
+
title: "Change style",
|
|
3473
|
+
type: "button"
|
|
3474
|
+
},
|
|
3475
|
+
styleIcon || /* @__PURE__ */ React15.createElement(DefaultStyleIcon4, null)
|
|
3476
|
+
),
|
|
3477
|
+
onDelete && /* @__PURE__ */ React15.createElement(
|
|
3478
|
+
"button",
|
|
3479
|
+
{
|
|
3480
|
+
className: "ShapeHighlight__delete-button",
|
|
3481
|
+
onClick: (e) => {
|
|
3482
|
+
e.stopPropagation();
|
|
3483
|
+
onDelete();
|
|
3484
|
+
},
|
|
3485
|
+
title: "Delete",
|
|
3486
|
+
type: "button"
|
|
3487
|
+
},
|
|
3488
|
+
deleteIcon || /* @__PURE__ */ React15.createElement(DefaultDeleteIcon6, null)
|
|
3489
|
+
)
|
|
3091
3490
|
),
|
|
3092
|
-
|
|
3093
|
-
"
|
|
3491
|
+
isStylePanelOpen && onStyleChange && /* @__PURE__ */ React15.createElement(
|
|
3492
|
+
"div",
|
|
3094
3493
|
{
|
|
3095
|
-
className: "
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
onDelete();
|
|
3099
|
-
},
|
|
3100
|
-
title: "Delete",
|
|
3101
|
-
type: "button"
|
|
3494
|
+
className: "ShapeHighlight__style-panel",
|
|
3495
|
+
ref: stylePanelRef,
|
|
3496
|
+
onClick: (e) => e.stopPropagation()
|
|
3102
3497
|
},
|
|
3103
|
-
|
|
3498
|
+
/* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Color"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-options" }, /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React15.createElement(
|
|
3499
|
+
"button",
|
|
3500
|
+
{
|
|
3501
|
+
key: c,
|
|
3502
|
+
type: "button",
|
|
3503
|
+
className: `ShapeHighlight__color-preset ${strokeColor === c ? "active" : ""}`,
|
|
3504
|
+
style: { backgroundColor: c },
|
|
3505
|
+
onClick: () => onStyleChange({ strokeColor: c }),
|
|
3506
|
+
title: c
|
|
3507
|
+
}
|
|
3508
|
+
))), /* @__PURE__ */ React15.createElement(
|
|
3509
|
+
"input",
|
|
3510
|
+
{
|
|
3511
|
+
type: "color",
|
|
3512
|
+
value: strokeColor,
|
|
3513
|
+
onChange: (e) => {
|
|
3514
|
+
onStyleChange({ strokeColor: e.target.value });
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
))),
|
|
3518
|
+
/* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Width"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__width-options" }, STROKE_WIDTHS2.map((w) => /* @__PURE__ */ React15.createElement(
|
|
3519
|
+
"button",
|
|
3520
|
+
{
|
|
3521
|
+
key: w.value,
|
|
3522
|
+
type: "button",
|
|
3523
|
+
className: `ShapeHighlight__width-button ${strokeWidth === w.value ? "active" : ""}`,
|
|
3524
|
+
onClick: () => onStyleChange({ strokeWidth: w.value }),
|
|
3525
|
+
title: w.label
|
|
3526
|
+
},
|
|
3527
|
+
w.label
|
|
3528
|
+
))))
|
|
3104
3529
|
)
|
|
3105
3530
|
),
|
|
3106
|
-
|
|
3107
|
-
"div",
|
|
3108
|
-
{
|
|
3109
|
-
className: "ShapeHighlight__style-panel",
|
|
3110
|
-
ref: stylePanelRef,
|
|
3111
|
-
onClick: (e) => e.stopPropagation()
|
|
3112
|
-
},
|
|
3113
|
-
/* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Color"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-options" }, /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__color-presets" }, colorPresets.map((c) => /* @__PURE__ */ React15.createElement(
|
|
3114
|
-
"button",
|
|
3115
|
-
{
|
|
3116
|
-
key: c,
|
|
3117
|
-
type: "button",
|
|
3118
|
-
className: `ShapeHighlight__color-preset ${strokeColor === c ? "active" : ""}`,
|
|
3119
|
-
style: { backgroundColor: c },
|
|
3120
|
-
onClick: () => onStyleChange({ strokeColor: c }),
|
|
3121
|
-
title: c
|
|
3122
|
-
}
|
|
3123
|
-
))), /* @__PURE__ */ React15.createElement(
|
|
3124
|
-
"input",
|
|
3125
|
-
{
|
|
3126
|
-
type: "color",
|
|
3127
|
-
value: strokeColor,
|
|
3128
|
-
onChange: (e) => {
|
|
3129
|
-
onStyleChange({ strokeColor: e.target.value });
|
|
3130
|
-
}
|
|
3131
|
-
}
|
|
3132
|
-
))),
|
|
3133
|
-
/* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__style-row" }, /* @__PURE__ */ React15.createElement("label", null, "Width"), /* @__PURE__ */ React15.createElement("div", { className: "ShapeHighlight__width-options" }, STROKE_WIDTHS2.map((w) => /* @__PURE__ */ React15.createElement(
|
|
3134
|
-
"button",
|
|
3135
|
-
{
|
|
3136
|
-
key: w.value,
|
|
3137
|
-
type: "button",
|
|
3138
|
-
className: `ShapeHighlight__width-button ${strokeWidth === w.value ? "active" : ""}`,
|
|
3139
|
-
onClick: () => onStyleChange({ strokeWidth: w.value }),
|
|
3140
|
-
title: w.label
|
|
3141
|
-
},
|
|
3142
|
-
w.label
|
|
3143
|
-
))))
|
|
3144
|
-
)
|
|
3531
|
+
configLayer
|
|
3145
3532
|
),
|
|
3146
3533
|
/* @__PURE__ */ React15.createElement(
|
|
3147
3534
|
Rnd5,
|
|
@@ -3204,7 +3591,10 @@ var DEFAULT_ERROR_MESSAGE = (error) => /* @__PURE__ */ React16.createElement("di
|
|
|
3204
3591
|
var DEFAULT_ON_ERROR = (error) => {
|
|
3205
3592
|
throw new Error(`Error loading PDF document: ${error.message}!`);
|
|
3206
3593
|
};
|
|
3207
|
-
var DEFAULT_WORKER_SRC =
|
|
3594
|
+
var DEFAULT_WORKER_SRC = new URL(
|
|
3595
|
+
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
3596
|
+
import.meta.url
|
|
3597
|
+
).toString();
|
|
3208
3598
|
var PdfLoader = ({
|
|
3209
3599
|
document: document2,
|
|
3210
3600
|
beforeLoad = DEFAULT_BEFORE_LOAD,
|
|
@@ -3245,405 +3635,708 @@ var PdfLoader = ({
|
|
|
3245
3635
|
return error ? errorMessage(error) : loadingProgress ? beforeLoad(loadingProgress) : pdfDocumentRef.current && children(pdfDocumentRef.current);
|
|
3246
3636
|
};
|
|
3247
3637
|
|
|
3248
|
-
// src/lib/
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3638
|
+
// src/lib/extract-sentences.ts
|
|
3639
|
+
var DEFAULT_OPTIONS = {
|
|
3640
|
+
pages: "all",
|
|
3641
|
+
includePositions: false,
|
|
3642
|
+
includeSources: false,
|
|
3643
|
+
normalize: true,
|
|
3644
|
+
locale: "en",
|
|
3645
|
+
idPrefix: "",
|
|
3646
|
+
includeTextUnitTypes: ["paragraph"],
|
|
3647
|
+
readingOrder: "auto",
|
|
3648
|
+
columnDetection: "auto"
|
|
3649
|
+
};
|
|
3650
|
+
var isTextItem = (item) => {
|
|
3651
|
+
return typeof item === "object" && item !== null && "str" in item && typeof item.str === "string" && "transform" in item && Array.isArray(item.transform);
|
|
3652
|
+
};
|
|
3653
|
+
var shouldInsertSpace = (previous, next) => {
|
|
3654
|
+
if (!previous || !next) return false;
|
|
3655
|
+
if (/\s$/.test(previous) || /^\s/.test(next)) return false;
|
|
3656
|
+
if (/^[,.;:!?)]/.test(next)) return false;
|
|
3657
|
+
if (/[([{]$/.test(previous)) return false;
|
|
3658
|
+
return true;
|
|
3659
|
+
};
|
|
3660
|
+
var getLastNonWhitespace = (text) => {
|
|
3661
|
+
const match = text.match(/\S(?=\s*$)/);
|
|
3662
|
+
return match?.[0] ?? "";
|
|
3663
|
+
};
|
|
3664
|
+
var getTrimmedChars = (text) => Array.from(text.trim());
|
|
3665
|
+
var getItemTextKind = (text) => {
|
|
3666
|
+
if (!text.trim()) return "space";
|
|
3667
|
+
if (/^[,.;:!?)\]}]+$/.test(text.trim())) return "punctuation";
|
|
3668
|
+
const chars = getTrimmedChars(text);
|
|
3669
|
+
if (chars.length === 1 && /[\p{L}\p{N}]/u.test(chars[0])) {
|
|
3670
|
+
return "singleGlyph";
|
|
3261
3671
|
}
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3672
|
+
if (/[\p{L}\p{N}]/u.test(text)) return "wordLike";
|
|
3673
|
+
return "other";
|
|
3674
|
+
};
|
|
3675
|
+
var getMedianGlyphWidth = (textItems) => {
|
|
3676
|
+
const glyphWidths = textItems.map((item) => {
|
|
3677
|
+
const textLength = Math.max(getTrimmedChars(item.text).length, 1);
|
|
3678
|
+
return item.rect.width / textLength;
|
|
3679
|
+
}).filter((width) => Number.isFinite(width) && width > 0).sort((a, b) => a - b);
|
|
3680
|
+
if (glyphWidths.length === 0) return 0;
|
|
3681
|
+
return glyphWidths[Math.floor(glyphWidths.length / 2)];
|
|
3682
|
+
};
|
|
3683
|
+
var getHorizontalGap = (previousItem, nextItem, medianGlyphWidth) => {
|
|
3684
|
+
const previousRight = previousItem.rect.left + previousItem.rect.width;
|
|
3685
|
+
const gap = nextItem.rect.left - previousRight;
|
|
3686
|
+
const zeroTolerance = Math.max(medianGlyphWidth * 0.08, 0.35);
|
|
3687
|
+
return Math.abs(gap) <= zeroTolerance ? 0 : gap;
|
|
3688
|
+
};
|
|
3689
|
+
var shouldInsertSpaceBetweenItems = (previousItem, nextItem, currentText, averageHeight, medianGlyphWidth) => {
|
|
3690
|
+
if (!previousItem || !currentText || !nextItem.text) return false;
|
|
3691
|
+
if (/\s$/.test(currentText) || /^\s/.test(nextItem.text)) return false;
|
|
3692
|
+
const previousChar = getLastNonWhitespace(currentText);
|
|
3693
|
+
const nextChar = Array.from(nextItem.text.trim())[0] ?? "";
|
|
3694
|
+
const previousKind = getItemTextKind(previousItem.text);
|
|
3695
|
+
const nextKind = getItemTextKind(nextItem.text);
|
|
3696
|
+
if (!nextChar) return false;
|
|
3697
|
+
if (nextKind === "punctuation" || /^[,.;:!?)]/.test(nextChar)) return false;
|
|
3698
|
+
if (/[([{]$/.test(previousChar)) return false;
|
|
3699
|
+
const gap = getHorizontalGap(previousItem, nextItem, medianGlyphWidth);
|
|
3700
|
+
if (gap < 0) return false;
|
|
3701
|
+
const glyphWidth = Math.max(
|
|
3702
|
+
medianGlyphWidth,
|
|
3703
|
+
averageHeight * 0.18,
|
|
3704
|
+
1
|
|
3705
|
+
);
|
|
3706
|
+
const singleGlyphPair = previousKind === "singleGlyph" && nextKind === "singleGlyph";
|
|
3707
|
+
if (singleGlyphPair) {
|
|
3708
|
+
return gap > Math.max(glyphWidth * 0.35, 1);
|
|
3270
3709
|
}
|
|
3271
|
-
if (
|
|
3272
|
-
return
|
|
3273
|
-
r: parseInt(hex.slice(0, 2), 16) / 255,
|
|
3274
|
-
g: parseInt(hex.slice(2, 4), 16) / 255,
|
|
3275
|
-
b: parseInt(hex.slice(4, 6), 16) / 255,
|
|
3276
|
-
a: 1
|
|
3277
|
-
};
|
|
3710
|
+
if ((previousKind === "wordLike" || previousKind === "singleGlyph") && (nextKind === "wordLike" || nextKind === "singleGlyph")) {
|
|
3711
|
+
return gap > Math.max(glyphWidth * 0.08, 0.35);
|
|
3278
3712
|
}
|
|
3279
|
-
return
|
|
3280
|
-
}
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3713
|
+
return shouldInsertSpace(currentText, nextItem.text);
|
|
3714
|
+
};
|
|
3715
|
+
var appendTextItem = (currentText, item, previousItem, averageHeight, medianGlyphWidth) => {
|
|
3716
|
+
return `${currentText}${shouldInsertSpaceBetweenItems(
|
|
3717
|
+
previousItem,
|
|
3718
|
+
item,
|
|
3719
|
+
currentText,
|
|
3720
|
+
averageHeight,
|
|
3721
|
+
medianGlyphWidth
|
|
3722
|
+
) ? " " : ""}${item.text}`;
|
|
3723
|
+
};
|
|
3724
|
+
var getAverageItemHeight = (textItems) => {
|
|
3725
|
+
const heights = textItems.map((item) => item.rect.height).filter((height) => height > 0).sort((a, b) => a - b);
|
|
3726
|
+
if (heights.length === 0) return 0;
|
|
3727
|
+
return heights[Math.floor(heights.length / 2)];
|
|
3728
|
+
};
|
|
3729
|
+
var areTextItemsOnSameLine = (previous, next, averageHeight) => {
|
|
3730
|
+
const previousMiddle = previous.rect.top + previous.rect.height / 2;
|
|
3731
|
+
const nextMiddle = next.rect.top + next.rect.height / 2;
|
|
3732
|
+
const tolerance = Math.max(averageHeight * 0.45, 2);
|
|
3733
|
+
return Math.abs(previousMiddle - nextMiddle) <= tolerance;
|
|
3734
|
+
};
|
|
3735
|
+
var getTextItemRect = (item, viewport, pageNumber) => {
|
|
3736
|
+
const [, , , , x, y] = item.transform;
|
|
3737
|
+
const width = Math.abs(item.width ?? 0);
|
|
3738
|
+
const height = Math.abs(item.height ?? 0);
|
|
3739
|
+
const [x1, y1, x2, y2] = viewport.convertToViewportRectangle([
|
|
3740
|
+
x,
|
|
3741
|
+
y,
|
|
3742
|
+
x + width,
|
|
3743
|
+
y + height
|
|
3744
|
+
]);
|
|
3745
|
+
return {
|
|
3746
|
+
left: Math.min(x1, x2),
|
|
3747
|
+
top: Math.min(y1, y2),
|
|
3748
|
+
width: Math.abs(x2 - x1),
|
|
3749
|
+
height: Math.abs(y2 - y1),
|
|
3750
|
+
pageNumber
|
|
3751
|
+
};
|
|
3752
|
+
};
|
|
3753
|
+
var resolvePageNumbers = (pdfDocument, pages) => {
|
|
3754
|
+
if (pages === "all" || pages === void 0) {
|
|
3755
|
+
return Array.from({ length: pdfDocument.numPages }, (_, index) => index + 1);
|
|
3298
3756
|
}
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
const
|
|
3305
|
-
const
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3757
|
+
return [...new Set(pages)].filter((pageNumber) => pageNumber >= 1 && pageNumber <= pdfDocument.numPages).sort((a, b) => a - b);
|
|
3758
|
+
};
|
|
3759
|
+
var buildPageTextStream = (textItems) => {
|
|
3760
|
+
let text = "";
|
|
3761
|
+
const charItemIndexes = [];
|
|
3762
|
+
const averageHeight = getAverageItemHeight(textItems);
|
|
3763
|
+
const medianGlyphWidth = getMedianGlyphWidth(textItems);
|
|
3764
|
+
let previousItem;
|
|
3765
|
+
textItems.forEach((item) => {
|
|
3766
|
+
if (!item.text) return;
|
|
3767
|
+
if (shouldInsertSpaceBetweenItems(
|
|
3768
|
+
previousItem,
|
|
3769
|
+
item,
|
|
3770
|
+
text,
|
|
3771
|
+
averageHeight,
|
|
3772
|
+
medianGlyphWidth
|
|
3773
|
+
)) {
|
|
3774
|
+
text += " ";
|
|
3775
|
+
charItemIndexes.push(null);
|
|
3310
3776
|
}
|
|
3311
|
-
const
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
3315
|
-
const testWidth = font.widthOfTextAtSize(testLine, fontSize);
|
|
3316
|
-
if (testWidth <= maxWidth) {
|
|
3317
|
-
currentLine = testLine;
|
|
3318
|
-
} else {
|
|
3319
|
-
if (currentLine) {
|
|
3320
|
-
lines.push(currentLine);
|
|
3321
|
-
currentLine = "";
|
|
3322
|
-
}
|
|
3323
|
-
if (font.widthOfTextAtSize(word, fontSize) > maxWidth) {
|
|
3324
|
-
let remaining = word;
|
|
3325
|
-
while (remaining.length > 0) {
|
|
3326
|
-
let charCount = 1;
|
|
3327
|
-
while (charCount < remaining.length && font.widthOfTextAtSize(remaining.substring(0, charCount + 1), fontSize) <= maxWidth) {
|
|
3328
|
-
charCount++;
|
|
3329
|
-
}
|
|
3330
|
-
const chunk = remaining.substring(0, charCount);
|
|
3331
|
-
remaining = remaining.substring(charCount);
|
|
3332
|
-
if (remaining.length > 0) {
|
|
3333
|
-
lines.push(chunk);
|
|
3334
|
-
} else {
|
|
3335
|
-
currentLine = chunk;
|
|
3336
|
-
}
|
|
3337
|
-
}
|
|
3338
|
-
} else {
|
|
3339
|
-
currentLine = word;
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3777
|
+
for (const char of item.text) {
|
|
3778
|
+
text += char;
|
|
3779
|
+
charItemIndexes.push(item.index);
|
|
3342
3780
|
}
|
|
3343
|
-
|
|
3344
|
-
}
|
|
3345
|
-
return
|
|
3346
|
-
}
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3781
|
+
previousItem = item;
|
|
3782
|
+
});
|
|
3783
|
+
return { text, charItemIndexes };
|
|
3784
|
+
};
|
|
3785
|
+
var normalizePdfLigatures = (text) => {
|
|
3786
|
+
return text.replace(/ffi/g, "ffi").replace(/ffl/g, "ffl").replace(/fi/g, "fi").replace(/fl/g, "fl").replace(/ff/g, "ff");
|
|
3787
|
+
};
|
|
3788
|
+
var cleanupPdfHyphenSpacing = (text) => {
|
|
3789
|
+
return text.replace(
|
|
3790
|
+
/\b([\p{L}\p{N}]{2,})\s+-\s+([\p{L}\p{N}]{2,})\b/gu,
|
|
3791
|
+
"$1-$2"
|
|
3792
|
+
);
|
|
3793
|
+
};
|
|
3794
|
+
var normalizePdfSentenceText = (text) => {
|
|
3795
|
+
return cleanupPdfHyphenSpacing(normalizePdfLigatures(text)).replace(/(\p{L}+)-\s+(\p{L}+)/gu, "$1-$2").replace(/\[\s*([^\]]*?)\s*\]/g, (_, citation) => {
|
|
3796
|
+
const normalizedCitation = citation.replace(/\s*,\s*/g, ", ").replace(/\s+/g, " ").trim();
|
|
3797
|
+
return `[${normalizedCitation}]`;
|
|
3798
|
+
}).replace(/\s+/g, " ").trim();
|
|
3799
|
+
};
|
|
3800
|
+
var splitPdfSentences = (text, locale = DEFAULT_OPTIONS.locale) => {
|
|
3801
|
+
const segmenterConstructor = Intl.Segmenter;
|
|
3802
|
+
if (segmenterConstructor) {
|
|
3803
|
+
const segmenter = new segmenterConstructor(locale, {
|
|
3804
|
+
granularity: "sentence"
|
|
3805
|
+
});
|
|
3806
|
+
return Array.from(segmenter.segment(text)).map((segment) => ({
|
|
3807
|
+
text: segment.segment.trim(),
|
|
3808
|
+
start: segment.index + segment.segment.search(/\S/),
|
|
3809
|
+
end: segment.index + segment.segment.trimEnd().length
|
|
3810
|
+
})).filter((sentence) => sentence.text.length > 0 && sentence.start >= 0);
|
|
3353
3811
|
}
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
const
|
|
3363
|
-
if (
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
const lineThickness = Math.max(1, height * 0.1);
|
|
3374
|
-
page.drawRectangle({
|
|
3375
|
-
x,
|
|
3376
|
-
y,
|
|
3377
|
-
width,
|
|
3378
|
-
height: lineThickness,
|
|
3379
|
-
color: rgb(color.r, color.g, color.b),
|
|
3380
|
-
opacity: color.a
|
|
3381
|
-
});
|
|
3382
|
-
} else if (highlightStyle === "strikethrough") {
|
|
3383
|
-
const lineThickness = Math.max(1, height * 0.1);
|
|
3384
|
-
const lineY = y + height / 2 - lineThickness / 2;
|
|
3385
|
-
page.drawRectangle({
|
|
3386
|
-
x,
|
|
3387
|
-
y: lineY,
|
|
3388
|
-
width,
|
|
3389
|
-
height: lineThickness,
|
|
3390
|
-
color: rgb(color.r, color.g, color.b),
|
|
3391
|
-
opacity: color.a
|
|
3812
|
+
const ranges = [];
|
|
3813
|
+
let start = 0;
|
|
3814
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
3815
|
+
const char = text[index];
|
|
3816
|
+
if (!/[.!?]/.test(char)) continue;
|
|
3817
|
+
const nextText = text.slice(index + 1);
|
|
3818
|
+
const nextNonSpace = nextText.match(/\S/);
|
|
3819
|
+
const nextChar = nextNonSpace?.[0] ?? "";
|
|
3820
|
+
const shouldSplit = nextChar === "" || /[A-Z([{"']/.test(nextChar);
|
|
3821
|
+
if (!shouldSplit) continue;
|
|
3822
|
+
const rawSentence = text.slice(start, index + 1);
|
|
3823
|
+
const leadingWhitespace2 = rawSentence.search(/\S/);
|
|
3824
|
+
const sentenceStart = start + Math.max(leadingWhitespace2, 0);
|
|
3825
|
+
const sentenceText = rawSentence.trim();
|
|
3826
|
+
if (sentenceText) {
|
|
3827
|
+
ranges.push({
|
|
3828
|
+
text: sentenceText,
|
|
3829
|
+
start: sentenceStart,
|
|
3830
|
+
end: start + rawSentence.trimEnd().length
|
|
3392
3831
|
});
|
|
3393
3832
|
}
|
|
3833
|
+
start = index + 1;
|
|
3394
3834
|
}
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
const
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3835
|
+
const finalRawSentence = text.slice(start);
|
|
3836
|
+
const leadingWhitespace = finalRawSentence.search(/\S/);
|
|
3837
|
+
const finalText = finalRawSentence.trim();
|
|
3838
|
+
if (finalText) {
|
|
3839
|
+
ranges.push({
|
|
3840
|
+
text: finalText,
|
|
3841
|
+
start: start + Math.max(leadingWhitespace, 0),
|
|
3842
|
+
end: start + finalRawSentence.trimEnd().length
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
3845
|
+
return ranges;
|
|
3846
|
+
};
|
|
3847
|
+
var getSentenceTextItemIndexes = (charItemIndexes, start, end) => {
|
|
3848
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
3849
|
+
for (let index = start; index < end; index += 1) {
|
|
3850
|
+
const itemIndex = charItemIndexes[index];
|
|
3851
|
+
if (itemIndex !== null && itemIndex !== void 0) {
|
|
3852
|
+
indexes.add(itemIndex);
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
return Array.from(indexes).sort((a, b) => a - b);
|
|
3856
|
+
};
|
|
3857
|
+
var getScaledPosition = (textItems, textItemIndexes, page) => {
|
|
3858
|
+
const indexSet = new Set(textItemIndexes);
|
|
3859
|
+
const rects = textItems.filter((item) => indexSet.has(item.index)).map((item) => item.rect).filter((rect) => rect.width > 0 && rect.height > 0);
|
|
3860
|
+
if (rects.length === 0) return void 0;
|
|
3861
|
+
const boundingRect = get_bounding_rect_default(rects);
|
|
3862
|
+
return {
|
|
3863
|
+
boundingRect: viewportToScaled(boundingRect, page),
|
|
3864
|
+
rects: rects.map((rect) => viewportToScaled(rect, page))
|
|
3865
|
+
};
|
|
3866
|
+
};
|
|
3867
|
+
var getTextItemsPosition = (textItems, textItemIndexes, page, includePositions) => {
|
|
3868
|
+
return includePositions ? getScaledPosition(textItems, textItemIndexes, page) : void 0;
|
|
3869
|
+
};
|
|
3870
|
+
var getTextItemsBoundingRect = (textItems) => {
|
|
3871
|
+
const rects = textItems.map((item) => item.rect).filter((rect) => rect.width > 0 && rect.height > 0);
|
|
3872
|
+
return rects.length > 0 ? get_bounding_rect_default(rects) : void 0;
|
|
3873
|
+
};
|
|
3874
|
+
var getOrderedTextItems = (textItems, readingOrder) => {
|
|
3875
|
+
if (readingOrder === "document") return [...textItems];
|
|
3876
|
+
const averageHeight = getAverageItemHeight(textItems);
|
|
3877
|
+
return [...textItems].sort((a, b) => {
|
|
3878
|
+
const topDelta = a.rect.top - b.rect.top;
|
|
3879
|
+
if (Math.abs(topDelta) > Math.max(averageHeight * 0.45, 2)) {
|
|
3880
|
+
return topDelta;
|
|
3881
|
+
}
|
|
3882
|
+
return a.rect.left - b.rect.left;
|
|
3410
3883
|
});
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
const
|
|
3414
|
-
const
|
|
3415
|
-
|
|
3416
|
-
)
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
console.log("Freetext export:", {
|
|
3426
|
-
storedFontSize,
|
|
3427
|
-
yRatio,
|
|
3428
|
-
fontSize,
|
|
3429
|
-
boxDimensions: { x, y, width, height },
|
|
3430
|
-
text: text.substring(0, 50)
|
|
3884
|
+
};
|
|
3885
|
+
var groupTextItemsIntoLines = (textItems) => {
|
|
3886
|
+
const averageHeight = getAverageItemHeight(textItems);
|
|
3887
|
+
const sortedItems = getOrderedTextItems(textItems, "position");
|
|
3888
|
+
const lines = [];
|
|
3889
|
+
sortedItems.forEach((item) => {
|
|
3890
|
+
const currentLine = lines[lines.length - 1];
|
|
3891
|
+
const previousItem = currentLine?.[currentLine.length - 1];
|
|
3892
|
+
if (currentLine && previousItem && areTextItemsOnSameLine(previousItem, item, averageHeight)) {
|
|
3893
|
+
currentLine.push(item);
|
|
3894
|
+
currentLine.sort((a, b) => a.rect.left - b.rect.left);
|
|
3895
|
+
return;
|
|
3896
|
+
}
|
|
3897
|
+
lines.push([item]);
|
|
3431
3898
|
});
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
height,
|
|
3440
|
-
color: rgb(bgColor.r, bgColor.g, bgColor.b),
|
|
3441
|
-
opacity: bgColor.a
|
|
3442
|
-
});
|
|
3899
|
+
return lines;
|
|
3900
|
+
};
|
|
3901
|
+
var isLikelyBodyTextItem = (item, page) => {
|
|
3902
|
+
const text = item.text.trim();
|
|
3903
|
+
if (!text) return false;
|
|
3904
|
+
if (item.rect.top < page.height * 0.12 && page.pageNumber === 1 || item.rect.top > page.height * 0.88) {
|
|
3905
|
+
return false;
|
|
3443
3906
|
}
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3907
|
+
if (isLikelyFootnoteOrReference(text)) return false;
|
|
3908
|
+
return true;
|
|
3909
|
+
};
|
|
3910
|
+
var getColumnRanges = (textItems, page) => {
|
|
3911
|
+
if (textItems.length < 24) return void 0;
|
|
3912
|
+
const minLeft = Math.min(...textItems.map((item) => item.rect.left));
|
|
3913
|
+
const maxRight = Math.max(
|
|
3914
|
+
...textItems.map((item) => item.rect.left + item.rect.width)
|
|
3915
|
+
);
|
|
3916
|
+
const pageSpan = maxRight - minLeft;
|
|
3917
|
+
const binCount = 80;
|
|
3918
|
+
const bins = Array.from({ length: binCount }, () => 0);
|
|
3919
|
+
textItems.forEach((item) => {
|
|
3920
|
+
const left = item.rect.left;
|
|
3921
|
+
const right = item.rect.left + item.rect.width;
|
|
3922
|
+
const startBin = Math.max(
|
|
3923
|
+
0,
|
|
3924
|
+
Math.floor((left - minLeft) / pageSpan * binCount)
|
|
3925
|
+
);
|
|
3926
|
+
const endBin = Math.min(
|
|
3927
|
+
binCount - 1,
|
|
3928
|
+
Math.ceil((right - minLeft) / pageSpan * binCount)
|
|
3929
|
+
);
|
|
3930
|
+
for (let index = startBin; index <= endBin; index += 1) {
|
|
3931
|
+
bins[index] += 1;
|
|
3932
|
+
}
|
|
3933
|
+
});
|
|
3934
|
+
const centerStart = Math.floor(binCount * 0.35);
|
|
3935
|
+
const centerEnd = Math.ceil(binCount * 0.65);
|
|
3936
|
+
let bestGapStart = -1;
|
|
3937
|
+
let bestGapEnd = -1;
|
|
3938
|
+
let currentGapStart = -1;
|
|
3939
|
+
for (let index = centerStart; index <= centerEnd; index += 1) {
|
|
3940
|
+
if (bins[index] === 0) {
|
|
3941
|
+
if (currentGapStart === -1) currentGapStart = index;
|
|
3942
|
+
continue;
|
|
3943
|
+
}
|
|
3944
|
+
if (currentGapStart !== -1 && index - currentGapStart > bestGapEnd - bestGapStart) {
|
|
3945
|
+
bestGapStart = currentGapStart;
|
|
3946
|
+
bestGapEnd = index;
|
|
3462
3947
|
}
|
|
3948
|
+
currentGapStart = -1;
|
|
3463
3949
|
}
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
const
|
|
3469
|
-
|
|
3950
|
+
if (currentGapStart !== -1 && centerEnd + 1 - currentGapStart > bestGapEnd - bestGapStart) {
|
|
3951
|
+
bestGapStart = currentGapStart;
|
|
3952
|
+
bestGapEnd = centerEnd + 1;
|
|
3953
|
+
}
|
|
3954
|
+
const gapBins = bestGapEnd - bestGapStart;
|
|
3955
|
+
const gapWidth = gapBins / binCount * pageSpan;
|
|
3956
|
+
const minGapWidth = Math.max(page.width * 0.035, 14);
|
|
3957
|
+
if (bestGapStart < 0 || gapWidth < minGapWidth) return void 0;
|
|
3958
|
+
const gapLeft = minLeft + bestGapStart / binCount * pageSpan;
|
|
3959
|
+
const gapRight = minLeft + bestGapEnd / binCount * pageSpan;
|
|
3960
|
+
const splitX = (gapLeft + gapRight) / 2;
|
|
3961
|
+
const leftItems = textItems.filter(
|
|
3962
|
+
(item) => item.rect.left + item.rect.width / 2 < splitX
|
|
3963
|
+
);
|
|
3964
|
+
const rightItems = textItems.filter(
|
|
3965
|
+
(item) => item.rect.left + item.rect.width / 2 >= splitX
|
|
3966
|
+
);
|
|
3967
|
+
if (leftItems.length < 10 || rightItems.length < 10) return void 0;
|
|
3968
|
+
const leftRange = {
|
|
3969
|
+
left: Math.min(...leftItems.map((item) => item.rect.left)),
|
|
3970
|
+
right: Math.max(...leftItems.map((item) => item.rect.left + item.rect.width))
|
|
3971
|
+
};
|
|
3972
|
+
const rightRange = {
|
|
3973
|
+
left: Math.min(...rightItems.map((item) => item.rect.left)),
|
|
3974
|
+
right: Math.max(...rightItems.map((item) => item.rect.left + item.rect.width))
|
|
3975
|
+
};
|
|
3976
|
+
const leftHeight = getAverageItemHeight(leftItems);
|
|
3977
|
+
const rightHeight = getAverageItemHeight(rightItems);
|
|
3978
|
+
if (leftHeight > 0 && rightHeight > 0 && Math.max(leftHeight, rightHeight) / Math.min(leftHeight, rightHeight) > 1.45) {
|
|
3979
|
+
return void 0;
|
|
3980
|
+
}
|
|
3981
|
+
return [leftRange, rightRange];
|
|
3982
|
+
};
|
|
3983
|
+
var assignColumnsToPage = (page, columnDetection) => {
|
|
3984
|
+
if (columnDetection === "none") {
|
|
3470
3985
|
return {
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3986
|
+
...page,
|
|
3987
|
+
textItems: page.textItems.map((item) => ({ ...item, columnIndex: 0 })),
|
|
3988
|
+
columns: [
|
|
3989
|
+
{
|
|
3990
|
+
index: 0,
|
|
3991
|
+
left: 0,
|
|
3992
|
+
right: page.width,
|
|
3993
|
+
width: page.width,
|
|
3994
|
+
textItemIndexes: page.textItems.map((item) => item.index)
|
|
3995
|
+
}
|
|
3996
|
+
]
|
|
3475
3997
|
};
|
|
3476
|
-
}
|
|
3998
|
+
}
|
|
3999
|
+
const bodyItems = page.textItems.filter(
|
|
4000
|
+
(item) => isLikelyBodyTextItem(item, page)
|
|
4001
|
+
);
|
|
4002
|
+
const ranges = getColumnRanges(bodyItems, page);
|
|
4003
|
+
if (!ranges) {
|
|
3477
4004
|
return {
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
4005
|
+
...page,
|
|
4006
|
+
textItems: page.textItems.map((item) => ({ ...item, columnIndex: 0 })),
|
|
4007
|
+
columns: [
|
|
4008
|
+
{
|
|
4009
|
+
index: 0,
|
|
4010
|
+
left: 0,
|
|
4011
|
+
right: page.width,
|
|
4012
|
+
width: page.width,
|
|
4013
|
+
textItemIndexes: page.textItems.map((item) => item.index)
|
|
4014
|
+
}
|
|
4015
|
+
]
|
|
3482
4016
|
};
|
|
3483
|
-
}
|
|
4017
|
+
}
|
|
4018
|
+
const splitX = (ranges[0].right + ranges[1].left) / 2;
|
|
4019
|
+
const textItems = page.textItems.map((item) => {
|
|
4020
|
+
const centerX = item.rect.left + item.rect.width / 2;
|
|
3484
4021
|
return {
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
width: height,
|
|
3488
|
-
height: width
|
|
4022
|
+
...item,
|
|
4023
|
+
columnIndex: centerX < splitX ? 0 : 1
|
|
3489
4024
|
};
|
|
3490
|
-
}
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
4025
|
+
});
|
|
4026
|
+
const columns = ranges.map((range, index) => ({
|
|
4027
|
+
index,
|
|
4028
|
+
left: range.left,
|
|
4029
|
+
right: range.right,
|
|
4030
|
+
width: range.right - range.left,
|
|
4031
|
+
textItemIndexes: textItems.filter((item) => item.columnIndex === index).map((item) => item.index)
|
|
4032
|
+
}));
|
|
4033
|
+
return { ...page, textItems, columns };
|
|
4034
|
+
};
|
|
4035
|
+
var isFullWidthBlock = (block, page, detectedColumnCount) => {
|
|
4036
|
+
if (detectedColumnCount < 2) return false;
|
|
4037
|
+
const textItems = block.flat();
|
|
4038
|
+
const boundingRect = getTextItemsBoundingRect(textItems);
|
|
4039
|
+
if (!boundingRect) return false;
|
|
4040
|
+
return boundingRect.width > page.width * 0.72;
|
|
4041
|
+
};
|
|
4042
|
+
var buildBlocksForItems = (textItems, page) => {
|
|
4043
|
+
const lines = groupTextItemsIntoLines(textItems);
|
|
4044
|
+
const averageHeight = getAverageItemHeight(textItems);
|
|
4045
|
+
const blocks = [];
|
|
4046
|
+
lines.forEach((line) => {
|
|
4047
|
+
const currentBlock = blocks[blocks.length - 1];
|
|
4048
|
+
const previousLine = currentBlock?.[currentBlock.length - 1];
|
|
4049
|
+
if (currentBlock && previousLine && !shouldStartNewBlock(previousLine, line, page, averageHeight)) {
|
|
4050
|
+
currentBlock.push(line);
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
blocks.push([line]);
|
|
4054
|
+
});
|
|
4055
|
+
return blocks;
|
|
4056
|
+
};
|
|
4057
|
+
var compareBlocksByPosition = (left, right) => {
|
|
4058
|
+
const leftRect = getTextItemsBoundingRect(left.block.flat());
|
|
4059
|
+
const rightRect = getTextItemsBoundingRect(right.block.flat());
|
|
4060
|
+
return (leftRect?.top ?? 0) - (rightRect?.top ?? 0);
|
|
4061
|
+
};
|
|
4062
|
+
var compareBlocksByColumnReadingOrder = (left, right) => {
|
|
4063
|
+
const columnDelta = (left.columnIndex ?? 0) - (right.columnIndex ?? 0);
|
|
4064
|
+
if (columnDelta !== 0) return columnDelta;
|
|
4065
|
+
return compareBlocksByPosition(left, right);
|
|
4066
|
+
};
|
|
4067
|
+
var buildTextUnit = ({
|
|
4068
|
+
block,
|
|
4069
|
+
columnIndex,
|
|
4070
|
+
indexInPage,
|
|
4071
|
+
includePositions,
|
|
4072
|
+
idPrefix,
|
|
4073
|
+
page,
|
|
4074
|
+
shouldNormalize
|
|
4075
|
+
}) => {
|
|
4076
|
+
const textItems = block.flat();
|
|
4077
|
+
const rawText = block.map(getLineText).join(" ");
|
|
4078
|
+
const textItemIndexes = textItems.map((item) => item.index);
|
|
4079
|
+
const type = classifyTextUnit(rawText, textItemIndexes, page);
|
|
4080
|
+
const position = getTextItemsPosition(
|
|
4081
|
+
page.textItems,
|
|
4082
|
+
textItemIndexes,
|
|
4083
|
+
page,
|
|
4084
|
+
includePositions
|
|
4085
|
+
);
|
|
4086
|
+
return {
|
|
4087
|
+
id: `${idPrefix}p${page.pageNumber}-u${indexInPage}`,
|
|
4088
|
+
type,
|
|
4089
|
+
text: shouldNormalize ? normalizePdfSentenceText(rawText) : rawText.trim(),
|
|
4090
|
+
rawText: rawText.trim(),
|
|
4091
|
+
pageNumber: page.pageNumber,
|
|
4092
|
+
indexInPage,
|
|
4093
|
+
...columnIndex !== void 0 ? { columnIndex } : {},
|
|
4094
|
+
...position ? { position } : {},
|
|
4095
|
+
source: {
|
|
4096
|
+
textItemIndexes
|
|
4097
|
+
}
|
|
4098
|
+
};
|
|
4099
|
+
};
|
|
4100
|
+
var getLineText = (line) => {
|
|
4101
|
+
const averageHeight = getAverageItemHeight(line);
|
|
4102
|
+
const medianGlyphWidth = getMedianGlyphWidth(line);
|
|
4103
|
+
return line.reduce((text, item, index) => {
|
|
4104
|
+
return appendTextItem(
|
|
4105
|
+
text,
|
|
4106
|
+
item,
|
|
4107
|
+
line[index - 1],
|
|
4108
|
+
averageHeight,
|
|
4109
|
+
medianGlyphWidth
|
|
3509
4110
|
);
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
4111
|
+
}, "");
|
|
4112
|
+
};
|
|
4113
|
+
var getLineRect = (line) => get_bounding_rect_default(line.map((item) => item.rect));
|
|
4114
|
+
var shouldStartNewBlock = (previousLine, nextLine, page, averageHeight) => {
|
|
4115
|
+
const previousRect = getLineRect(previousLine);
|
|
4116
|
+
const nextRect = getLineRect(nextLine);
|
|
4117
|
+
const verticalGap = nextRect.top - (previousRect.top + previousRect.height);
|
|
4118
|
+
const leftDelta = Math.abs(nextRect.left - previousRect.left);
|
|
4119
|
+
const previousText = getLineText(previousLine).trim();
|
|
4120
|
+
const nextText = getLineText(nextLine).trim();
|
|
4121
|
+
if (verticalGap > Math.max(averageHeight * 1.15, 7)) return true;
|
|
4122
|
+
if (leftDelta > page.width * 0.18) return true;
|
|
4123
|
+
if (isLikelyStandaloneBlock(previousText) || isLikelyStandaloneBlock(nextText)) {
|
|
4124
|
+
return true;
|
|
3523
4125
|
}
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
const
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
4126
|
+
return false;
|
|
4127
|
+
};
|
|
4128
|
+
var isLikelyStandaloneBlock = (text) => {
|
|
4129
|
+
const normalizedText = normalizePdfSentenceText(text);
|
|
4130
|
+
if (!normalizedText) return false;
|
|
4131
|
+
if (isLikelySectionHeading(normalizedText)) return true;
|
|
4132
|
+
if (isLikelyFootnoteOrReference(normalizedText)) return true;
|
|
4133
|
+
return false;
|
|
4134
|
+
};
|
|
4135
|
+
var isLikelySectionHeading = (text) => {
|
|
4136
|
+
if (text.length > 80) return false;
|
|
4137
|
+
if (/^\d+(\.\d+)*\.?\s+[A-Z]/.test(text)) return true;
|
|
4138
|
+
const letters = text.replace(/[^A-Za-z]/g, "");
|
|
4139
|
+
if (letters.length < 3) return false;
|
|
4140
|
+
const uppercaseLetters = text.replace(/[^A-Z]/g, "");
|
|
4141
|
+
return uppercaseLetters.length / letters.length > 0.75;
|
|
4142
|
+
};
|
|
4143
|
+
var isLikelyFootnoteOrReference = (text) => {
|
|
4144
|
+
return /^\d+\s*https?:\/\//i.test(text) || /^\[\d+\]/.test(text) || /https?:\/\/|doi\.org|arxiv\.org/i.test(text);
|
|
4145
|
+
};
|
|
4146
|
+
var isLikelyAuthor = (text) => {
|
|
4147
|
+
return text.length <= 80 && !/[.!?]$/.test(text) && /^[A-Z][A-Za-z'.-]+(?:\s+[A-Z][A-Za-z'.-]+){1,4}$/.test(text);
|
|
4148
|
+
};
|
|
4149
|
+
var isLikelyAffiliation = (text) => {
|
|
4150
|
+
return text.length <= 160 && /university|institute|department|school|college|faculty|laboratory|germany|usa|china|france|italy|spain|canada|japan|korea/i.test(
|
|
4151
|
+
text
|
|
3533
4152
|
);
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
4153
|
+
};
|
|
4154
|
+
var classifyTextUnit = (rawText, textItemIndexes, page) => {
|
|
4155
|
+
const text = normalizePdfSentenceText(rawText);
|
|
4156
|
+
const textItems = page.textItems.filter(
|
|
4157
|
+
(item) => textItemIndexes.includes(item.index)
|
|
4158
|
+
);
|
|
4159
|
+
const rects = textItems.map((item) => item.rect);
|
|
4160
|
+
const boundingRect = rects.length > 0 ? get_bounding_rect_default(rects) : void 0;
|
|
4161
|
+
const averagePageHeight = getAverageItemHeight(page.textItems);
|
|
4162
|
+
const averageUnitHeight = getAverageItemHeight(textItems);
|
|
4163
|
+
const isTopOfFirstPage = page.pageNumber === 1 && (boundingRect?.top ?? page.height) < page.height * 0.2;
|
|
4164
|
+
const isBottomOfPage = (boundingRect?.top ?? 0) > page.height * 0.78;
|
|
4165
|
+
const isLargeText = averagePageHeight > 0 && averageUnitHeight > averagePageHeight * 1.25;
|
|
4166
|
+
const isCentered = boundingRect !== void 0 && Math.abs(boundingRect.left + boundingRect.width / 2 - page.width / 2) < page.width * 0.16;
|
|
4167
|
+
if (isLikelyFootnoteOrReference(text) || isBottomOfPage && /^\d+\s/.test(text)) {
|
|
4168
|
+
return text.includes("://") || /^\d+\s*https?:\/\//i.test(text) ? "footnote" : "reference";
|
|
4169
|
+
}
|
|
4170
|
+
if (isLikelySectionHeading(text)) return "heading";
|
|
4171
|
+
if (isTopOfFirstPage && isLargeText && isCentered) return "title";
|
|
4172
|
+
if (isTopOfFirstPage && isLikelyAuthor(text)) return "author";
|
|
4173
|
+
if (isTopOfFirstPage && isLikelyAffiliation(text)) return "affiliation";
|
|
4174
|
+
return "paragraph";
|
|
4175
|
+
};
|
|
4176
|
+
var extractTextUnitsFromPages = (pages, options = {}) => {
|
|
4177
|
+
const includePositions = options.includePositions ?? DEFAULT_OPTIONS.includePositions;
|
|
4178
|
+
const includeSources = options.includeSources ?? DEFAULT_OPTIONS.includeSources;
|
|
4179
|
+
const shouldNormalize = options.normalize ?? DEFAULT_OPTIONS.normalize;
|
|
4180
|
+
const idPrefix = options.idPrefix ?? DEFAULT_OPTIONS.idPrefix;
|
|
4181
|
+
const readingOrder = options.readingOrder ?? DEFAULT_OPTIONS.readingOrder;
|
|
4182
|
+
const columnDetection = options.columnDetection ?? DEFAULT_OPTIONS.columnDetection;
|
|
4183
|
+
const units = [];
|
|
4184
|
+
pages.forEach((page) => {
|
|
4185
|
+
const pageWithColumns = assignColumnsToPage(page, columnDetection);
|
|
4186
|
+
const detectedColumnCount = pageWithColumns.columns?.length ?? 1;
|
|
4187
|
+
const columnIndexes = detectedColumnCount > 1 && readingOrder !== "document" ? pageWithColumns.columns?.map((column) => column.index) ?? [0] : [0];
|
|
4188
|
+
const blocks = [];
|
|
4189
|
+
if (readingOrder === "document") {
|
|
4190
|
+
blocks.push(
|
|
4191
|
+
...buildBlocksForItems(pageWithColumns.textItems, pageWithColumns).map(
|
|
4192
|
+
(block) => ({ block })
|
|
4193
|
+
)
|
|
4194
|
+
);
|
|
4195
|
+
} else {
|
|
4196
|
+
const allBlocks = columnIndexes.flatMap(
|
|
4197
|
+
(columnIndex) => buildBlocksForItems(
|
|
4198
|
+
pageWithColumns.textItems.filter(
|
|
4199
|
+
(item) => item.columnIndex === columnIndex
|
|
4200
|
+
),
|
|
4201
|
+
pageWithColumns
|
|
4202
|
+
).map((block) => ({ block, columnIndex }))
|
|
4203
|
+
);
|
|
4204
|
+
const fullWidthBlocks = allBlocks.filter(
|
|
4205
|
+
({ block }) => isFullWidthBlock(block, pageWithColumns, detectedColumnCount)
|
|
4206
|
+
);
|
|
4207
|
+
const columnBlocks = allBlocks.filter(
|
|
4208
|
+
({ block }) => !isFullWidthBlock(block, pageWithColumns, detectedColumnCount)
|
|
4209
|
+
);
|
|
4210
|
+
const topFullWidthBlocks = fullWidthBlocks.filter(({ block }) => {
|
|
4211
|
+
const rect = getTextItemsBoundingRect(block.flat());
|
|
4212
|
+
return (rect?.top ?? 0) < pageWithColumns.height * 0.3;
|
|
3583
4213
|
});
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
y: endY - arrowSize * Math.sin(angle + arrowAngle)
|
|
3588
|
-
},
|
|
3589
|
-
end: { x: endX, y: endY },
|
|
3590
|
-
color: rgb(color.r, color.g, color.b),
|
|
3591
|
-
thickness: strokeWidth,
|
|
3592
|
-
opacity: color.a
|
|
4214
|
+
const bottomFullWidthBlocks = fullWidthBlocks.filter(({ block }) => {
|
|
4215
|
+
const rect = getTextItemsBoundingRect(block.flat());
|
|
4216
|
+
return (rect?.top ?? 0) >= pageWithColumns.height * 0.3;
|
|
3593
4217
|
});
|
|
3594
|
-
|
|
4218
|
+
blocks.push(
|
|
4219
|
+
...topFullWidthBlocks.sort(compareBlocksByPosition),
|
|
4220
|
+
...columnBlocks.sort(compareBlocksByColumnReadingOrder),
|
|
4221
|
+
...bottomFullWidthBlocks.sort(compareBlocksByPosition)
|
|
4222
|
+
);
|
|
3595
4223
|
}
|
|
4224
|
+
blocks.forEach(({ block, columnIndex }, indexInPage) => {
|
|
4225
|
+
units.push(
|
|
4226
|
+
buildTextUnit({
|
|
4227
|
+
block,
|
|
4228
|
+
columnIndex,
|
|
4229
|
+
indexInPage,
|
|
4230
|
+
includePositions,
|
|
4231
|
+
idPrefix,
|
|
4232
|
+
page: pageWithColumns,
|
|
4233
|
+
shouldNormalize
|
|
4234
|
+
})
|
|
4235
|
+
);
|
|
4236
|
+
});
|
|
4237
|
+
});
|
|
4238
|
+
if (!includeSources) {
|
|
4239
|
+
return units.map(({ source: _source, ...rest }) => rest);
|
|
3596
4240
|
}
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
4241
|
+
return units;
|
|
4242
|
+
};
|
|
4243
|
+
var extractTextUnits = async (pdfDocument, options = {}) => {
|
|
4244
|
+
const resolvedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
4245
|
+
const pages = await extractPageTextItems(pdfDocument, {
|
|
4246
|
+
pages: resolvedOptions.pages,
|
|
4247
|
+
columnDetection: resolvedOptions.columnDetection
|
|
4248
|
+
});
|
|
4249
|
+
return extractTextUnitsFromPages(pages, resolvedOptions);
|
|
4250
|
+
};
|
|
4251
|
+
var extractPageTextItems = async (pdfDocument, options = {}) => {
|
|
4252
|
+
const pageNumbers = resolvePageNumbers(pdfDocument, options.pages);
|
|
4253
|
+
const columnDetection = options.columnDetection ?? DEFAULT_OPTIONS.columnDetection;
|
|
4254
|
+
const extractedPages = [];
|
|
4255
|
+
for (const pageNumber of pageNumbers) {
|
|
4256
|
+
const page = await pdfDocument.getPage(pageNumber);
|
|
4257
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
4258
|
+
const textContent = await page.getTextContent();
|
|
4259
|
+
const textItems = [];
|
|
4260
|
+
textContent.items.forEach((item, index) => {
|
|
4261
|
+
if (!isTextItem(item) || item.str.length === 0) return;
|
|
4262
|
+
textItems.push({
|
|
4263
|
+
text: item.str,
|
|
4264
|
+
index,
|
|
4265
|
+
pageNumber,
|
|
4266
|
+
rect: getTextItemRect(item, viewport, pageNumber)
|
|
4267
|
+
});
|
|
4268
|
+
});
|
|
4269
|
+
extractedPages.push(assignColumnsToPage({
|
|
4270
|
+
pageNumber,
|
|
4271
|
+
width: viewport.width,
|
|
4272
|
+
height: viewport.height,
|
|
4273
|
+
textItems
|
|
4274
|
+
}, columnDetection));
|
|
3608
4275
|
}
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
const
|
|
3613
|
-
const
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
4276
|
+
return extractedPages;
|
|
4277
|
+
};
|
|
4278
|
+
var extractSentences = async (pdfDocument, options = {}) => {
|
|
4279
|
+
const resolvedOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
4280
|
+
const pages = await extractPageTextItems(pdfDocument, {
|
|
4281
|
+
pages: resolvedOptions.pages,
|
|
4282
|
+
columnDetection: resolvedOptions.columnDetection
|
|
4283
|
+
});
|
|
4284
|
+
const textUnits = extractTextUnitsFromPages(pages, { ...resolvedOptions, includeSources: true });
|
|
4285
|
+
const sentences = [];
|
|
4286
|
+
let globalIndex = 0;
|
|
4287
|
+
textUnits.filter((unit) => resolvedOptions.includeTextUnitTypes.includes(unit.type)).forEach((unit) => {
|
|
4288
|
+
const page = pages.find((candidatePage) => candidatePage.pageNumber === unit.pageNumber);
|
|
4289
|
+
if (!page) return;
|
|
4290
|
+
const pageTextItemsByIndex = new Map(
|
|
4291
|
+
page.textItems.map((item) => [item.index, item])
|
|
4292
|
+
);
|
|
4293
|
+
const stream = buildPageTextStream(
|
|
4294
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
4295
|
+
unit.source.textItemIndexes.map((textItemIndex) => pageTextItemsByIndex.get(textItemIndex)).filter((item) => item !== void 0)
|
|
4296
|
+
);
|
|
4297
|
+
if (!stream.text.trim()) return;
|
|
4298
|
+
const sentenceRanges = splitPdfSentences(
|
|
4299
|
+
stream.text,
|
|
4300
|
+
resolvedOptions.locale
|
|
4301
|
+
);
|
|
4302
|
+
sentenceRanges.forEach((sentenceRange, indexInPage) => {
|
|
4303
|
+
const textItemIndexes = getSentenceTextItemIndexes(
|
|
4304
|
+
stream.charItemIndexes,
|
|
4305
|
+
sentenceRange.start,
|
|
4306
|
+
sentenceRange.end
|
|
4307
|
+
);
|
|
4308
|
+
const position = resolvedOptions.includePositions ? getScaledPosition(page.textItems, textItemIndexes, page) : void 0;
|
|
4309
|
+
const id = `${resolvedOptions.idPrefix}p${page.pageNumber}-u${unit.indexInPage}-s${indexInPage}`;
|
|
4310
|
+
const rawText = sentenceRange.text;
|
|
4311
|
+
sentences.push({
|
|
4312
|
+
id,
|
|
4313
|
+
text: resolvedOptions.normalize ? normalizePdfSentenceText(rawText) : rawText,
|
|
4314
|
+
rawText,
|
|
4315
|
+
pageNumber: page.pageNumber,
|
|
4316
|
+
indexInPage,
|
|
4317
|
+
globalIndex,
|
|
4318
|
+
...unit.columnIndex !== void 0 ? { columnIndex: unit.columnIndex } : {},
|
|
4319
|
+
...position ? { position } : {},
|
|
4320
|
+
...resolvedOptions.includeSources ? { source: { startOffset: sentenceRange.start, endOffset: sentenceRange.end, textItemIndexes } } : {}
|
|
4321
|
+
});
|
|
4322
|
+
globalIndex += 1;
|
|
4323
|
+
});
|
|
4324
|
+
});
|
|
4325
|
+
return sentences;
|
|
4326
|
+
};
|
|
4327
|
+
var sentenceToHighlight = (sentence, options = {}) => {
|
|
4328
|
+
if (!sentence.position) {
|
|
4329
|
+
throw new Error(
|
|
4330
|
+
"Cannot convert sentence to highlight because it has no position. Call extractSentences with includePositions: true."
|
|
4331
|
+
);
|
|
3644
4332
|
}
|
|
3645
|
-
return
|
|
3646
|
-
|
|
4333
|
+
return {
|
|
4334
|
+
id: options.id ?? sentence.id,
|
|
4335
|
+
type: "text",
|
|
4336
|
+
content: { text: sentence.text },
|
|
4337
|
+
position: sentence.position
|
|
4338
|
+
};
|
|
4339
|
+
};
|
|
3647
4340
|
|
|
3648
4341
|
// src/components/leftpanel/LeftPanel.tsx
|
|
3649
4342
|
import React21, { useState as useState17, useMemo as useMemo3, useCallback as useCallback8, useRef as useRef19, useEffect as useEffect18 } from "react";
|
|
@@ -4971,6 +5664,12 @@ var LeftPanel = ({
|
|
|
4971
5664
|
)
|
|
4972
5665
|
));
|
|
4973
5666
|
};
|
|
5667
|
+
|
|
5668
|
+
// src/index.ts
|
|
5669
|
+
var exportPdf = async (...args) => {
|
|
5670
|
+
const { exportPdf: exportPdf2 } = await import("./export-pdf-W2QGWADM.js");
|
|
5671
|
+
return exportPdf2(...args);
|
|
5672
|
+
};
|
|
4974
5673
|
export {
|
|
4975
5674
|
AreaHighlight,
|
|
4976
5675
|
DocumentOutline,
|
|
@@ -4990,7 +5689,11 @@ export {
|
|
|
4990
5689
|
ThumbnailItem,
|
|
4991
5690
|
ThumbnailPanel,
|
|
4992
5691
|
exportPdf,
|
|
5692
|
+
extractPageTextItems,
|
|
5693
|
+
extractSentences,
|
|
5694
|
+
extractTextUnits,
|
|
4993
5695
|
scaledPositionToViewport,
|
|
5696
|
+
sentenceToHighlight,
|
|
4994
5697
|
useDocumentOutline,
|
|
4995
5698
|
useHighlightContainerContext,
|
|
4996
5699
|
useLeftPanelContext,
|