@anaralabs/lector 3.14.10 → 3.14.12
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 +47 -70
- package/dist/_tsup-dts-rollup.d.ts +99 -7
- package/dist/index.d.ts +3 -0
- package/dist/index.js +761 -455
- package/esm-only.cjs +8 -0
- package/package.json +11 -4
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef, useState, useCallback, useContext, useLayoutEffect, createRef } from 'react';
|
|
3
|
+
import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef, useState, useCallback, useContext, useLayoutEffect, createRef, useSyncExternalStore, useImperativeHandle } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import { useFloating, autoUpdate, offset, flip, shift, useDismiss, useInteractions, inline } from '@floating-ui/react';
|
|
6
6
|
import { useStore, createStore } from 'zustand';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
8
9
|
import { useDebounce } from 'use-debounce';
|
|
9
10
|
import { v4 } from 'uuid';
|
|
10
11
|
import { Slot } from '@radix-ui/react-slot';
|
|
@@ -265,12 +266,12 @@ var PDFStore = createZustandContext(
|
|
|
265
266
|
updateZoom: (zoom, isZoomFitWidth = false) => {
|
|
266
267
|
const { minZoom, maxZoom } = get().zoomOptions;
|
|
267
268
|
set((state) => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
return { zoom: newZoom, isZoomFitWidth };
|
|
269
|
+
const newZoom = clamp(
|
|
270
|
+
typeof zoom === "function" ? zoom(state.zoom) : zoom,
|
|
271
|
+
minZoom,
|
|
272
|
+
maxZoom
|
|
273
|
+
);
|
|
274
|
+
return newZoom === state.zoom && isZoomFitWidth === state.isZoomFitWidth ? state : { zoom: newZoom, isZoomFitWidth };
|
|
274
275
|
});
|
|
275
276
|
},
|
|
276
277
|
zoomFitWidth: () => {
|
|
@@ -281,23 +282,20 @@ var PDFStore = createZustandContext(
|
|
|
281
282
|
viewports,
|
|
282
283
|
zoomOptions
|
|
283
284
|
);
|
|
284
|
-
|
|
285
|
-
zoom: clampedZoom,
|
|
286
|
-
isZoomFitWidth: true
|
|
287
|
-
});
|
|
285
|
+
get().updateZoom(clampedZoom, true);
|
|
288
286
|
return clampedZoom;
|
|
289
287
|
},
|
|
290
288
|
currentPage: 1,
|
|
291
289
|
setCurrentPage: (val) => {
|
|
292
|
-
set(
|
|
293
|
-
currentPage: val
|
|
294
|
-
|
|
290
|
+
set(
|
|
291
|
+
(state) => state.currentPage === val ? state : { currentPage: val }
|
|
292
|
+
);
|
|
295
293
|
},
|
|
296
294
|
isPinching: false,
|
|
297
295
|
setIsPinching: (val) => {
|
|
298
|
-
set(
|
|
299
|
-
isPinching: val
|
|
300
|
-
|
|
296
|
+
set(
|
|
297
|
+
(state) => state.isPinching === val ? state : { isPinching: val }
|
|
298
|
+
);
|
|
301
299
|
},
|
|
302
300
|
isResizing: false,
|
|
303
301
|
setIsResizing: (val) => {
|
|
@@ -307,9 +305,9 @@ var PDFStore = createZustandContext(
|
|
|
307
305
|
},
|
|
308
306
|
virtualizer: null,
|
|
309
307
|
setVirtualizer: (val) => {
|
|
310
|
-
set(
|
|
311
|
-
virtualizer: val
|
|
312
|
-
|
|
308
|
+
set(
|
|
309
|
+
(state) => state.virtualizer === val ? state : { virtualizer: val }
|
|
310
|
+
);
|
|
313
311
|
},
|
|
314
312
|
pageProxies: initialState.pageProxies,
|
|
315
313
|
getPdfPageProxy: (pageNumber) => {
|
|
@@ -319,21 +317,21 @@ var PDFStore = createZustandContext(
|
|
|
319
317
|
},
|
|
320
318
|
textContent: [],
|
|
321
319
|
setTextContent: (val) => {
|
|
322
|
-
set(
|
|
323
|
-
textContent: val
|
|
324
|
-
|
|
320
|
+
set(
|
|
321
|
+
(state) => state.textContent === val ? state : { textContent: val }
|
|
322
|
+
);
|
|
325
323
|
},
|
|
326
324
|
highlights: [],
|
|
327
325
|
setHighlight: (val) => {
|
|
328
|
-
set(
|
|
329
|
-
highlights: val
|
|
330
|
-
|
|
326
|
+
set(
|
|
327
|
+
(state) => state.highlights === val ? state : { highlights: val }
|
|
328
|
+
);
|
|
331
329
|
},
|
|
332
330
|
customSelectionRects: [],
|
|
333
331
|
setCustomSelectionRects: (val) => {
|
|
334
|
-
set(
|
|
335
|
-
customSelectionRects: val
|
|
336
|
-
|
|
332
|
+
set(
|
|
333
|
+
(state) => state.customSelectionRects === val ? state : { customSelectionRects: val }
|
|
334
|
+
);
|
|
337
335
|
},
|
|
338
336
|
coloredHighlights: [],
|
|
339
337
|
addColoredHighlight: (value) => set((prevState) => ({
|
|
@@ -700,10 +698,43 @@ var AnnotationsStoreProvider = ({
|
|
|
700
698
|
{ initialValue: void 0 },
|
|
701
699
|
children
|
|
702
700
|
);
|
|
703
|
-
var
|
|
701
|
+
var identity = (state) => state;
|
|
702
|
+
function useAnnotations(selector) {
|
|
704
703
|
const ctx = AnnotationsStore.useContext();
|
|
705
|
-
return useStore(
|
|
706
|
-
|
|
704
|
+
return useStore(
|
|
705
|
+
ctx ?? getFallbackStore(),
|
|
706
|
+
selector ?? identity
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
var emptyAnnotations = [];
|
|
710
|
+
var indexes = /* @__PURE__ */ new WeakMap();
|
|
711
|
+
function annotationsByPage(annotations) {
|
|
712
|
+
let index = indexes.get(annotations);
|
|
713
|
+
if (index) return index;
|
|
714
|
+
index = /* @__PURE__ */ new Map();
|
|
715
|
+
for (const annotation of annotations) {
|
|
716
|
+
const pages = /* @__PURE__ */ new Set([annotation.pageNumber]);
|
|
717
|
+
for (const rect of annotation.highlights) pages.add(rect.pageNumber);
|
|
718
|
+
for (const rect of annotation.underlines ?? []) pages.add(rect.pageNumber);
|
|
719
|
+
for (const page of pages) {
|
|
720
|
+
let values = index.get(page);
|
|
721
|
+
if (!values) {
|
|
722
|
+
values = [];
|
|
723
|
+
index.set(page, values);
|
|
724
|
+
}
|
|
725
|
+
values.push(annotation);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
indexes.set(annotations, index);
|
|
729
|
+
return index;
|
|
730
|
+
}
|
|
731
|
+
function usePageAnnotations(pageNumber) {
|
|
732
|
+
return useAnnotations(
|
|
733
|
+
useShallow(
|
|
734
|
+
(state) => annotationsByPage(state.annotations).get(pageNumber) ?? emptyAnnotations
|
|
735
|
+
)
|
|
736
|
+
);
|
|
737
|
+
}
|
|
707
738
|
var PDFPageNumberContext = createContext(0);
|
|
708
739
|
var usePDFPageNumber = () => {
|
|
709
740
|
return useContext(PDFPageNumberContext);
|
|
@@ -725,15 +756,9 @@ var AnnotationHighlightLayer = ({
|
|
|
725
756
|
onAnnotationTooltipClose,
|
|
726
757
|
hoverTooltipClassName
|
|
727
758
|
}) => {
|
|
728
|
-
const { annotations } = useAnnotations();
|
|
729
759
|
const pageNumber = usePDFPageNumber();
|
|
730
760
|
const isPageRendered = usePdf((state) => !!state.renderedPages[pageNumber]);
|
|
731
|
-
const pageAnnotations =
|
|
732
|
-
() => annotations.filter(
|
|
733
|
-
(a) => a.pageNumber === pageNumber || a.highlights.some((h) => h.pageNumber === pageNumber) || a.underlines?.some((u) => u.pageNumber === pageNumber)
|
|
734
|
-
),
|
|
735
|
-
[annotations, pageNumber]
|
|
736
|
-
);
|
|
761
|
+
const pageAnnotations = usePageAnnotations(pageNumber);
|
|
737
762
|
if (!isPageRendered) return null;
|
|
738
763
|
const getCommentIconPosition = (highlights) => {
|
|
739
764
|
if (!highlights.length) return { top: 0, right: 10 };
|
|
@@ -1220,20 +1245,42 @@ var PDFLinkServiceContext = createContext(defaultLinkService);
|
|
|
1220
1245
|
var usePDFLinkService = () => {
|
|
1221
1246
|
return useContext(PDFLinkServiceContext);
|
|
1222
1247
|
};
|
|
1248
|
+
var targets = /* @__PURE__ */ new Map();
|
|
1249
|
+
var observer;
|
|
1223
1250
|
var useVisibility = ({
|
|
1224
1251
|
elementRef
|
|
1225
1252
|
}) => {
|
|
1226
1253
|
const [visible, setVisible] = useState(false);
|
|
1227
1254
|
useEffect(() => {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1255
|
+
const element = elementRef.current;
|
|
1256
|
+
if (!element) return;
|
|
1257
|
+
observer ??= new IntersectionObserver((entries) => {
|
|
1258
|
+
for (const entry of entries) {
|
|
1259
|
+
const target2 = targets.get(entry.target);
|
|
1260
|
+
if (!target2) continue;
|
|
1261
|
+
target2.visible = entry.isIntersecting;
|
|
1262
|
+
for (const listener of target2.listeners) listener(entry.isIntersecting);
|
|
1263
|
+
}
|
|
1233
1264
|
});
|
|
1234
|
-
|
|
1265
|
+
let target = targets.get(element);
|
|
1266
|
+
if (!target) {
|
|
1267
|
+
target = { listeners: /* @__PURE__ */ new Set(), visible: false };
|
|
1268
|
+
targets.set(element, target);
|
|
1269
|
+
observer.observe(element);
|
|
1270
|
+
}
|
|
1271
|
+
const { listeners: listeners2 } = target;
|
|
1272
|
+
listeners2.add(setVisible);
|
|
1273
|
+
setVisible(target.visible);
|
|
1235
1274
|
return () => {
|
|
1236
|
-
|
|
1275
|
+
listeners2.delete(setVisible);
|
|
1276
|
+
if (listeners2.size === 0) {
|
|
1277
|
+
observer?.unobserve(element);
|
|
1278
|
+
targets.delete(element);
|
|
1279
|
+
}
|
|
1280
|
+
if (targets.size === 0) {
|
|
1281
|
+
observer?.disconnect();
|
|
1282
|
+
observer = void 0;
|
|
1283
|
+
}
|
|
1237
1284
|
};
|
|
1238
1285
|
}, [elementRef]);
|
|
1239
1286
|
return { visible };
|
|
@@ -2061,52 +2108,41 @@ function applyContextRecolor(ctx, map, options) {
|
|
|
2061
2108
|
function removeContextRecolor(ctx) {
|
|
2062
2109
|
ctx[RECOLOR_CLEANUP]?.();
|
|
2063
2110
|
}
|
|
2064
|
-
var
|
|
2065
|
-
var
|
|
2066
|
-
var
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2111
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
2112
|
+
var media;
|
|
2113
|
+
var getSnapshot = () => Math.min(window.devicePixelRatio || 1, 3);
|
|
2114
|
+
var getServerSnapshot = () => 1;
|
|
2115
|
+
function unlisten() {
|
|
2116
|
+
if (!media) return;
|
|
2117
|
+
if (typeof media.removeEventListener === "function")
|
|
2118
|
+
media.removeEventListener("change", onChange);
|
|
2119
|
+
else media.removeListener(onChange);
|
|
2120
|
+
}
|
|
2121
|
+
function watchResolution() {
|
|
2122
|
+
unlisten();
|
|
2123
|
+
media = window.matchMedia(
|
|
2124
|
+
`(resolution: ${window.devicePixelRatio || 1}dppx), (-webkit-device-pixel-ratio: ${window.devicePixelRatio || 1})`
|
|
2125
|
+
);
|
|
2126
|
+
if (typeof media.addEventListener === "function")
|
|
2127
|
+
media.addEventListener("change", onChange);
|
|
2128
|
+
else media.addListener(onChange);
|
|
2129
|
+
}
|
|
2130
|
+
function onChange() {
|
|
2131
|
+
watchResolution();
|
|
2132
|
+
for (const listener of listeners) listener();
|
|
2133
|
+
}
|
|
2134
|
+
function subscribe(listener) {
|
|
2135
|
+
listeners.add(listener);
|
|
2136
|
+
if (listeners.size === 1) watchResolution();
|
|
2137
|
+
return () => {
|
|
2138
|
+
listeners.delete(listener);
|
|
2139
|
+
if (listeners.size === 0) {
|
|
2140
|
+
unlisten();
|
|
2141
|
+
media = void 0;
|
|
2071
2142
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
if (cancelled) {
|
|
2076
|
-
return;
|
|
2077
|
-
}
|
|
2078
|
-
setDpr(readDpr());
|
|
2079
|
-
subscribe();
|
|
2080
|
-
};
|
|
2081
|
-
const unlisten = (mql) => {
|
|
2082
|
-
if (!mql) return;
|
|
2083
|
-
if (typeof mql.removeEventListener === "function") {
|
|
2084
|
-
mql.removeEventListener("change", handleChange);
|
|
2085
|
-
} else {
|
|
2086
|
-
mql.removeListener(handleChange);
|
|
2087
|
-
}
|
|
2088
|
-
};
|
|
2089
|
-
const subscribe = () => {
|
|
2090
|
-
unlisten(mediaQuery);
|
|
2091
|
-
const ratio = window.devicePixelRatio || 1;
|
|
2092
|
-
mediaQuery = window.matchMedia(
|
|
2093
|
-
`(resolution: ${ratio}dppx), (-webkit-device-pixel-ratio: ${ratio})`
|
|
2094
|
-
);
|
|
2095
|
-
if (typeof mediaQuery.addEventListener === "function") {
|
|
2096
|
-
mediaQuery.addEventListener("change", handleChange, { once: true });
|
|
2097
|
-
} else {
|
|
2098
|
-
mediaQuery.addListener(handleChange);
|
|
2099
|
-
}
|
|
2100
|
-
};
|
|
2101
|
-
setDpr(readDpr());
|
|
2102
|
-
subscribe();
|
|
2103
|
-
return () => {
|
|
2104
|
-
cancelled = true;
|
|
2105
|
-
unlisten(mediaQuery);
|
|
2106
|
-
};
|
|
2107
|
-
}, []);
|
|
2108
|
-
return dpr;
|
|
2109
|
-
};
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
var useDpr = () => useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
2110
2146
|
|
|
2111
2147
|
// src/hooks/layers/useCanvasLayer.tsx
|
|
2112
2148
|
var CACHE_MAX_ENTRIES = 60;
|
|
@@ -2858,9 +2894,7 @@ var CanvasLayer = memo(
|
|
|
2858
2894
|
);
|
|
2859
2895
|
CanvasLayer.displayName = "CanvasLayer";
|
|
2860
2896
|
|
|
2861
|
-
// src/
|
|
2862
|
-
var MERGE_THRESHOLD = 2;
|
|
2863
|
-
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
2897
|
+
// src/lib/selection-rects.ts
|
|
2864
2898
|
var getTextNodeClientRects = (range) => {
|
|
2865
2899
|
const root = range.commonAncestorContainer;
|
|
2866
2900
|
const ownerDoc = root.ownerDocument ?? document;
|
|
@@ -2912,6 +2946,39 @@ var getTextNodeClientRects = (range) => {
|
|
|
2912
2946
|
}
|
|
2913
2947
|
return results;
|
|
2914
2948
|
};
|
|
2949
|
+
var connected = (a, b) => {
|
|
2950
|
+
const overlap = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top);
|
|
2951
|
+
const gap = Math.max(a.left, b.left) - Math.min(a.left + a.width, b.left + b.width);
|
|
2952
|
+
return overlap >= Math.min(a.height, b.height) / 2 && gap <= Math.min(a.height, b.height) * 0.5;
|
|
2953
|
+
};
|
|
2954
|
+
var mergeTextRuns = (rects) => {
|
|
2955
|
+
const groups = [];
|
|
2956
|
+
for (const rect of rects) {
|
|
2957
|
+
const group = [rect];
|
|
2958
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
2959
|
+
if (groups[i].some((member) => connected(member, rect))) {
|
|
2960
|
+
group.push(...groups[i]);
|
|
2961
|
+
groups.splice(i, 1);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
groups.push(group);
|
|
2965
|
+
}
|
|
2966
|
+
return groups.map((group) => {
|
|
2967
|
+
const left = Math.min(...group.map((rect) => rect.left));
|
|
2968
|
+
const top = Math.min(...group.map((rect) => rect.top));
|
|
2969
|
+
return {
|
|
2970
|
+
...group[0],
|
|
2971
|
+
left,
|
|
2972
|
+
top,
|
|
2973
|
+
width: Math.max(...group.map((rect) => rect.left + rect.width)) - left,
|
|
2974
|
+
height: Math.max(...group.map((rect) => rect.top + rect.height)) - top
|
|
2975
|
+
};
|
|
2976
|
+
});
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2979
|
+
// src/hooks/useSelectionDimensions.tsx
|
|
2980
|
+
var MERGE_THRESHOLD = 2;
|
|
2981
|
+
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
2915
2982
|
var mapSelectionRectsToLayers = (range) => {
|
|
2916
2983
|
const clientRects = getTextNodeClientRects(range).filter(
|
|
2917
2984
|
({ rect }) => rect.width > 2 && rect.height > 2
|
|
@@ -2954,122 +3021,6 @@ var mapSelectionRectsToLayers = (range) => {
|
|
|
2954
3021
|
}
|
|
2955
3022
|
return result;
|
|
2956
3023
|
};
|
|
2957
|
-
var shouldMergeRects = (rect1, rect2) => {
|
|
2958
|
-
const verticalOverlap = !(rect1.top > rect2.top + rect2.height || rect2.top > rect1.top + rect1.height);
|
|
2959
|
-
const horizontallyConnected = Math.abs(rect1.left + rect1.width - rect2.left) <= MERGE_THRESHOLD || Math.abs(rect2.left + rect2.width - rect1.left) <= MERGE_THRESHOLD || rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
|
|
2960
|
-
return verticalOverlap && horizontallyConnected;
|
|
2961
|
-
};
|
|
2962
|
-
var consolidateHighlightRects = (rects) => {
|
|
2963
|
-
if (rects.length <= 1) return rects;
|
|
2964
|
-
const consolidated = [];
|
|
2965
|
-
const sorted = [...rects].sort((a, b) => {
|
|
2966
|
-
const pageCompare = a.pageNumber - b.pageNumber;
|
|
2967
|
-
if (pageCompare !== 0) return pageCompare;
|
|
2968
|
-
const topDiff = a.top - b.top;
|
|
2969
|
-
return Math.abs(topDiff) < 2 ? a.left - b.left : topDiff;
|
|
2970
|
-
});
|
|
2971
|
-
let current = sorted[0];
|
|
2972
|
-
if (!current) return rects;
|
|
2973
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
2974
|
-
const next = sorted[i];
|
|
2975
|
-
if (!next) continue;
|
|
2976
|
-
const samePageAndLine = current.pageNumber === next.pageNumber && Math.abs(current.top - next.top) < Math.max(current.height, next.height) * 0.5;
|
|
2977
|
-
const horizontallyConnected = samePageAndLine && // Adjacent (touching or very close)
|
|
2978
|
-
(Math.abs(current.left + current.width - next.left) <= MERGE_THRESHOLD || Math.abs(next.left + next.width - current.left) <= MERGE_THRESHOLD || // Overlapping
|
|
2979
|
-
current.left < next.left + next.width && next.left < current.left + current.width || // Very close (small gap)
|
|
2980
|
-
Math.abs(current.left + current.width - next.left) <= current.height * 0.2);
|
|
2981
|
-
if (horizontallyConnected) {
|
|
2982
|
-
const newLeft = Math.min(current.left, next.left);
|
|
2983
|
-
const newRight = Math.max(
|
|
2984
|
-
current.left + current.width,
|
|
2985
|
-
next.left + next.width
|
|
2986
|
-
);
|
|
2987
|
-
const newTop = Math.min(current.top, next.top);
|
|
2988
|
-
const newBottom = Math.max(
|
|
2989
|
-
current.top + current.height,
|
|
2990
|
-
next.top + next.height
|
|
2991
|
-
);
|
|
2992
|
-
current = {
|
|
2993
|
-
left: newLeft,
|
|
2994
|
-
top: newTop,
|
|
2995
|
-
width: newRight - newLeft,
|
|
2996
|
-
height: newBottom - newTop,
|
|
2997
|
-
pageNumber: current.pageNumber
|
|
2998
|
-
};
|
|
2999
|
-
} else {
|
|
3000
|
-
consolidated.push(current);
|
|
3001
|
-
current = next;
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
if (current) {
|
|
3005
|
-
consolidated.push(current);
|
|
3006
|
-
}
|
|
3007
|
-
return consolidated;
|
|
3008
|
-
};
|
|
3009
|
-
var consolidateRects = (rects) => {
|
|
3010
|
-
if (rects.length <= 1) return rects;
|
|
3011
|
-
const result = [];
|
|
3012
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3013
|
-
for (let i = 0; i < rects.length; i++) {
|
|
3014
|
-
if (visited.has(i)) continue;
|
|
3015
|
-
const currentRect = rects[i];
|
|
3016
|
-
if (!currentRect) continue;
|
|
3017
|
-
const currentGroup = [currentRect];
|
|
3018
|
-
visited.add(i);
|
|
3019
|
-
let foundNew = true;
|
|
3020
|
-
while (foundNew) {
|
|
3021
|
-
foundNew = false;
|
|
3022
|
-
for (let j = 0; j < rects.length; j++) {
|
|
3023
|
-
if (visited.has(j)) continue;
|
|
3024
|
-
const candidateRect = rects[j];
|
|
3025
|
-
if (!candidateRect) continue;
|
|
3026
|
-
const shouldMergeWithGroup = currentGroup.some(
|
|
3027
|
-
(groupRect) => doRectsOverlap(groupRect, candidateRect)
|
|
3028
|
-
);
|
|
3029
|
-
if (shouldMergeWithGroup) {
|
|
3030
|
-
currentGroup.push(candidateRect);
|
|
3031
|
-
visited.add(j);
|
|
3032
|
-
foundNew = true;
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
|
-
result.push(mergeRectGroup(currentGroup));
|
|
3037
|
-
}
|
|
3038
|
-
return result;
|
|
3039
|
-
};
|
|
3040
|
-
var doRectsOverlap = (rect1, rect2) => {
|
|
3041
|
-
const horizontalOverlap = rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
|
|
3042
|
-
const verticalOverlap = rect1.top < rect2.top + rect2.height && rect2.top < rect1.top + rect1.height;
|
|
3043
|
-
const closeEnough = shouldMergeRects(rect1, rect2);
|
|
3044
|
-
return horizontalOverlap && verticalOverlap || closeEnough;
|
|
3045
|
-
};
|
|
3046
|
-
var mergeRectGroup = (rects) => {
|
|
3047
|
-
if (rects.length === 1) {
|
|
3048
|
-
const rect = rects[0];
|
|
3049
|
-
if (!rect) throw new Error("Invalid rect in group");
|
|
3050
|
-
return rect;
|
|
3051
|
-
}
|
|
3052
|
-
const firstRect = rects[0];
|
|
3053
|
-
if (!firstRect) throw new Error("Invalid first rect in group");
|
|
3054
|
-
let minLeft = firstRect.left;
|
|
3055
|
-
let minTop = firstRect.top;
|
|
3056
|
-
let maxRight = firstRect.left + firstRect.width;
|
|
3057
|
-
let maxBottom = firstRect.top + firstRect.height;
|
|
3058
|
-
rects.forEach((rect) => {
|
|
3059
|
-
if (!rect) return;
|
|
3060
|
-
minLeft = Math.min(minLeft, rect.left);
|
|
3061
|
-
minTop = Math.min(minTop, rect.top);
|
|
3062
|
-
maxRight = Math.max(maxRight, rect.left + rect.width);
|
|
3063
|
-
maxBottom = Math.max(maxBottom, rect.top + rect.height);
|
|
3064
|
-
});
|
|
3065
|
-
return {
|
|
3066
|
-
left: minLeft,
|
|
3067
|
-
top: minTop,
|
|
3068
|
-
width: maxRight - minLeft,
|
|
3069
|
-
height: maxBottom - minTop,
|
|
3070
|
-
pageNumber: firstRect.pageNumber
|
|
3071
|
-
};
|
|
3072
|
-
};
|
|
3073
3024
|
var useSelectionDimensions = () => {
|
|
3074
3025
|
const store = PDFStore.useContext();
|
|
3075
3026
|
const getAnnotationDimension = () => {
|
|
@@ -3142,14 +3093,7 @@ var useSelectionDimensions = () => {
|
|
|
3142
3093
|
}
|
|
3143
3094
|
});
|
|
3144
3095
|
textLayerMapHighlight.forEach((rects) => {
|
|
3145
|
-
|
|
3146
|
-
if (rects.length === 1) {
|
|
3147
|
-
highlightRects.push(...rects);
|
|
3148
|
-
} else {
|
|
3149
|
-
const consolidated = consolidateHighlightRects(rects);
|
|
3150
|
-
highlightRects.push(...consolidated);
|
|
3151
|
-
}
|
|
3152
|
-
}
|
|
3096
|
+
highlightRects.push(...mergeTextRuns(rects));
|
|
3153
3097
|
});
|
|
3154
3098
|
textLayerMapUnderline.forEach((rects) => {
|
|
3155
3099
|
if (rects.length > 0) {
|
|
@@ -3296,7 +3240,7 @@ var useSelectionDimensions = () => {
|
|
|
3296
3240
|
});
|
|
3297
3241
|
textLayerMap.forEach((rects) => {
|
|
3298
3242
|
if (rects.length > 0) {
|
|
3299
|
-
const consolidated =
|
|
3243
|
+
const consolidated = mergeTextRuns(rects);
|
|
3300
3244
|
highlights.push(...consolidated);
|
|
3301
3245
|
}
|
|
3302
3246
|
});
|
|
@@ -3741,13 +3685,110 @@ var HighlightLayer = forwardRef(({ asChild, className, style, ...props }, ref) =
|
|
|
3741
3685
|
}) });
|
|
3742
3686
|
});
|
|
3743
3687
|
HighlightLayer.displayName = "HighlightLayer";
|
|
3688
|
+
|
|
3689
|
+
// src/lib/selection-background.ts
|
|
3690
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3691
|
+
var createSelectionBackground = () => {
|
|
3692
|
+
const overlays = /* @__PURE__ */ new Map();
|
|
3693
|
+
let style;
|
|
3694
|
+
const clear = (layer) => {
|
|
3695
|
+
overlays.get(layer)?.remove();
|
|
3696
|
+
overlays.delete(layer);
|
|
3697
|
+
layer.removeAttribute("data-lector-selection-active");
|
|
3698
|
+
if (overlays.size === 0) {
|
|
3699
|
+
style?.remove();
|
|
3700
|
+
style = void 0;
|
|
3701
|
+
}
|
|
3702
|
+
};
|
|
3703
|
+
const update = (selection, layers) => {
|
|
3704
|
+
const registered = new Set(layers);
|
|
3705
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
3706
|
+
if (selection && !selection.isCollapsed) {
|
|
3707
|
+
for (let i = 0; i < selection.rangeCount; i++) {
|
|
3708
|
+
for (const { rect, element } of getTextNodeClientRects(
|
|
3709
|
+
selection.getRangeAt(i)
|
|
3710
|
+
)) {
|
|
3711
|
+
const layer = element?.closest(".textLayer");
|
|
3712
|
+
if (!layer || !element || !registered.has(layer) || rect.width <= 0 || rect.height <= 0)
|
|
3713
|
+
continue;
|
|
3714
|
+
let entry = byLayer.get(layer);
|
|
3715
|
+
if (!entry) {
|
|
3716
|
+
entry = { rects: [], element };
|
|
3717
|
+
byLayer.set(layer, entry);
|
|
3718
|
+
}
|
|
3719
|
+
entry.rects.push(rect);
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
for (const layer of registered) {
|
|
3724
|
+
const entry = byLayer.get(layer);
|
|
3725
|
+
if (!entry) {
|
|
3726
|
+
clear(layer);
|
|
3727
|
+
continue;
|
|
3728
|
+
}
|
|
3729
|
+
layer.removeAttribute("data-lector-selection-active");
|
|
3730
|
+
const color = getComputedStyle(
|
|
3731
|
+
entry.element,
|
|
3732
|
+
"::selection"
|
|
3733
|
+
).backgroundColor;
|
|
3734
|
+
if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)") {
|
|
3735
|
+
clear(layer);
|
|
3736
|
+
continue;
|
|
3737
|
+
}
|
|
3738
|
+
const bounds = layer.getBoundingClientRect();
|
|
3739
|
+
if (!bounds.width || !bounds.height || !layer.clientWidth || !layer.clientHeight) {
|
|
3740
|
+
clear(layer);
|
|
3741
|
+
continue;
|
|
3742
|
+
}
|
|
3743
|
+
const scaleX = bounds.width / layer.clientWidth;
|
|
3744
|
+
const scaleY = bounds.height / layer.clientHeight;
|
|
3745
|
+
const rects = mergeTextRuns(
|
|
3746
|
+
entry.rects.map((rect) => ({
|
|
3747
|
+
left: (rect.left - bounds.left) / scaleX,
|
|
3748
|
+
top: (rect.top - bounds.top) / scaleY,
|
|
3749
|
+
width: rect.width / scaleX,
|
|
3750
|
+
height: rect.height / scaleY
|
|
3751
|
+
}))
|
|
3752
|
+
);
|
|
3753
|
+
let overlay = overlays.get(layer);
|
|
3754
|
+
if (!overlay) {
|
|
3755
|
+
overlay = document.createElementNS(SVG_NS, "svg");
|
|
3756
|
+
overlay.dataset.lectorSelection = "";
|
|
3757
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
3758
|
+
overlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;pointer-events:none;user-select:none;transform:none;z-index:0;overflow:visible";
|
|
3759
|
+
overlay.append(document.createElementNS(SVG_NS, "path"));
|
|
3760
|
+
layer.append(overlay);
|
|
3761
|
+
overlays.set(layer, overlay);
|
|
3762
|
+
}
|
|
3763
|
+
const path = overlay.firstElementChild;
|
|
3764
|
+
path.setAttribute(
|
|
3765
|
+
"d",
|
|
3766
|
+
rects.map(
|
|
3767
|
+
(rect) => `M${rect.left} ${rect.top}h${rect.width}v${rect.height}h${-rect.width}Z`
|
|
3768
|
+
).join(" ")
|
|
3769
|
+
);
|
|
3770
|
+
path.setAttribute("fill", color);
|
|
3771
|
+
if (!style) {
|
|
3772
|
+
style = document.createElement("style");
|
|
3773
|
+
style.textContent = ".textLayer[data-lector-selection-active] ::selection { background-color: transparent !important; }";
|
|
3774
|
+
document.head.append(style);
|
|
3775
|
+
}
|
|
3776
|
+
layer.setAttribute("data-lector-selection-active", "");
|
|
3777
|
+
}
|
|
3778
|
+
};
|
|
3779
|
+
return { update, clear };
|
|
3780
|
+
};
|
|
3781
|
+
|
|
3782
|
+
// src/hooks/layers/useTextLayer.tsx
|
|
3744
3783
|
var createTextSelectionManager = () => {
|
|
3745
3784
|
const textLayers = /* @__PURE__ */ new Map();
|
|
3785
|
+
const selectionBackground = createSelectionBackground();
|
|
3746
3786
|
let selectionChangeAbortController = null;
|
|
3747
3787
|
let isPointerDown = false;
|
|
3748
3788
|
let prevRange = null;
|
|
3749
3789
|
let isFirefox;
|
|
3750
3790
|
const removeGlobalSelectionListener = (textLayerDiv) => {
|
|
3791
|
+
selectionBackground.clear(textLayerDiv);
|
|
3751
3792
|
textLayers.delete(textLayerDiv);
|
|
3752
3793
|
if (textLayers.size === 0) {
|
|
3753
3794
|
selectionChangeAbortController?.abort();
|
|
@@ -3804,6 +3845,7 @@ var createTextSelectionManager = () => {
|
|
|
3804
3845
|
"selectionchange",
|
|
3805
3846
|
() => {
|
|
3806
3847
|
const selection = document.getSelection();
|
|
3848
|
+
selectionBackground.update(selection, textLayers.keys());
|
|
3807
3849
|
if (!selection || selection.rangeCount === 0) {
|
|
3808
3850
|
textLayers.forEach(reset);
|
|
3809
3851
|
return;
|
|
@@ -4492,9 +4534,9 @@ var useVisiblePage = ({
|
|
|
4492
4534
|
window.addEventListener("resize", measure);
|
|
4493
4535
|
return () => window.removeEventListener("resize", measure);
|
|
4494
4536
|
}
|
|
4495
|
-
const
|
|
4496
|
-
|
|
4497
|
-
return () =>
|
|
4537
|
+
const observer2 = new ResizeObserver(measure);
|
|
4538
|
+
observer2.observe(scrollElement);
|
|
4539
|
+
return () => observer2.disconnect();
|
|
4498
4540
|
}, [scrollElement]);
|
|
4499
4541
|
const calculateVisiblePageIndex = useCallback(
|
|
4500
4542
|
(virtualItems) => {
|
|
@@ -5051,7 +5093,7 @@ var CoordinatesEngine = class extends Engine {
|
|
|
5051
5093
|
}
|
|
5052
5094
|
}
|
|
5053
5095
|
};
|
|
5054
|
-
var
|
|
5096
|
+
var identity2 = (v) => v;
|
|
5055
5097
|
var DEFAULT_RUBBERBAND = 0.15;
|
|
5056
5098
|
var commonConfigResolver = {
|
|
5057
5099
|
enabled(value = true) {
|
|
@@ -5084,7 +5126,7 @@ var commonConfigResolver = {
|
|
|
5084
5126
|
const transform = value || config.shared.transform;
|
|
5085
5127
|
this.hasCustomTransform = !!transform;
|
|
5086
5128
|
if (process.env.NODE_ENV === "development") {
|
|
5087
|
-
const originalTransform = transform ||
|
|
5129
|
+
const originalTransform = transform || identity2;
|
|
5088
5130
|
return (v) => {
|
|
5089
5131
|
const r2 = originalTransform(v);
|
|
5090
5132
|
if (!isFinite(r2[0]) || !isFinite(r2[1])) {
|
|
@@ -5093,7 +5135,7 @@ var commonConfigResolver = {
|
|
|
5093
5135
|
return r2;
|
|
5094
5136
|
};
|
|
5095
5137
|
}
|
|
5096
|
-
return transform ||
|
|
5138
|
+
return transform || identity2;
|
|
5097
5139
|
},
|
|
5098
5140
|
threshold(value) {
|
|
5099
5141
|
return V.toVector(value, 0);
|
|
@@ -6167,16 +6209,16 @@ var EventStore = class {
|
|
|
6167
6209
|
this._gestureKey = gestureKey;
|
|
6168
6210
|
}
|
|
6169
6211
|
add(element, device, action, handler, options) {
|
|
6170
|
-
const
|
|
6212
|
+
const listeners2 = this._listeners;
|
|
6171
6213
|
const type = toDomEventType(device, action);
|
|
6172
6214
|
const _options = this._gestureKey ? this._ctrl.config[this._gestureKey].eventOptions : {};
|
|
6173
6215
|
const eventOptions = _objectSpread2(_objectSpread2({}, _options), options);
|
|
6174
6216
|
element.addEventListener(type, handler, eventOptions);
|
|
6175
6217
|
const remove = () => {
|
|
6176
6218
|
element.removeEventListener(type, handler, eventOptions);
|
|
6177
|
-
|
|
6219
|
+
listeners2.delete(remove);
|
|
6178
6220
|
};
|
|
6179
|
-
|
|
6221
|
+
listeners2.add(remove);
|
|
6180
6222
|
return remove;
|
|
6181
6223
|
}
|
|
6182
6224
|
clean() {
|
|
@@ -6661,7 +6703,6 @@ var useViewportContainer = ({
|
|
|
6661
6703
|
origin: originRef.current
|
|
6662
6704
|
};
|
|
6663
6705
|
};
|
|
6664
|
-
var selectLargestPageWidth = (state) => state.viewports.reduce((max, vp) => Math.max(max, vp.width), 0);
|
|
6665
6706
|
var DEFAULT_HEIGHT = 600;
|
|
6666
6707
|
var EXTRA_HEIGHT = 0;
|
|
6667
6708
|
var DEFAULT_VIRTUALIZER_OPTIONS = { overscan: 1 };
|
|
@@ -6776,7 +6817,10 @@ var Pages = ({
|
|
|
6776
6817
|
scrollOffset: virtualizer.scrollOffset ?? null
|
|
6777
6818
|
});
|
|
6778
6819
|
useFitWidth({ viewportRef: containerRef });
|
|
6779
|
-
const largestPageWidth =
|
|
6820
|
+
const largestPageWidth = useMemo(
|
|
6821
|
+
() => viewports.reduce((max, viewport) => Math.max(max, viewport.width), 0),
|
|
6822
|
+
[viewports]
|
|
6823
|
+
);
|
|
6780
6824
|
useEffect(() => {
|
|
6781
6825
|
virtualizer.getOffsetForAlignment = (toOffset, align, itemSize = 0) => {
|
|
6782
6826
|
const size = virtualizer.getSize();
|
|
@@ -6814,7 +6858,9 @@ var Pages = ({
|
|
|
6814
6858
|
{
|
|
6815
6859
|
ref: elementWrapperRef,
|
|
6816
6860
|
style: {
|
|
6817
|
-
width
|
|
6861
|
+
// The absolute page list contributes no intrinsic width. Reserve
|
|
6862
|
+
// its width before any zoom effect runs (including zoom === 1).
|
|
6863
|
+
width: largestPageWidth,
|
|
6818
6864
|
marginLeft: "auto",
|
|
6819
6865
|
marginRight: "auto",
|
|
6820
6866
|
flexShrink: 0
|
|
@@ -6855,6 +6901,36 @@ var Pages = ({
|
|
|
6855
6901
|
);
|
|
6856
6902
|
};
|
|
6857
6903
|
|
|
6904
|
+
// src/lib/map-concurrent.ts
|
|
6905
|
+
async function mapConcurrent(items, concurrency, map, isCancelled = () => false) {
|
|
6906
|
+
if (!Number.isFinite(concurrency) || concurrency < 1)
|
|
6907
|
+
throw new RangeError("Concurrency must be positive and finite");
|
|
6908
|
+
const results = new Array(items.length);
|
|
6909
|
+
let next = 0;
|
|
6910
|
+
let failed = false;
|
|
6911
|
+
const worker = async () => {
|
|
6912
|
+
while (next < items.length) {
|
|
6913
|
+
if (failed || isCancelled())
|
|
6914
|
+
throw new DOMException("Operation aborted", "AbortError");
|
|
6915
|
+
const index = next++;
|
|
6916
|
+
try {
|
|
6917
|
+
results[index] = await map(items[index], index);
|
|
6918
|
+
} catch (error) {
|
|
6919
|
+
failed = true;
|
|
6920
|
+
throw error;
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
};
|
|
6924
|
+
await Promise.all(
|
|
6925
|
+
Array.from(
|
|
6926
|
+
{ length: Math.min(Math.max(1, Math.floor(concurrency)), items.length) },
|
|
6927
|
+
worker
|
|
6928
|
+
)
|
|
6929
|
+
);
|
|
6930
|
+
if (isCancelled()) throw new DOMException("Operation aborted", "AbortError");
|
|
6931
|
+
return results;
|
|
6932
|
+
}
|
|
6933
|
+
|
|
6858
6934
|
// src/lib/pdfjs.ts
|
|
6859
6935
|
var pdfJsPromise = null;
|
|
6860
6936
|
var loadPdfJs = () => {
|
|
@@ -6987,7 +7063,6 @@ var usePDFDocumentContext = ({
|
|
|
6987
7063
|
colorScheme,
|
|
6988
7064
|
darkModeColors
|
|
6989
7065
|
}) => {
|
|
6990
|
-
const [_, setProgress] = useState(0);
|
|
6991
7066
|
const [initialState, setInitialState] = useState();
|
|
6992
7067
|
const [rotation] = useState(initialRotation);
|
|
6993
7068
|
const [renderColorMapRef] = useState(() => ({
|
|
@@ -7002,30 +7077,30 @@ var usePDFDocumentContext = ({
|
|
|
7002
7077
|
const darkModeColorsRef = useRef(darkModeColors);
|
|
7003
7078
|
darkModeColorsRef.current = darkModeColors;
|
|
7004
7079
|
useEffect(() => {
|
|
7080
|
+
let isDisposed = false;
|
|
7005
7081
|
const generateViewports = async (pdf) => {
|
|
7006
7082
|
const pageProxies = [];
|
|
7007
|
-
const
|
|
7008
|
-
|
|
7009
|
-
|
|
7083
|
+
const viewports = await mapConcurrent(
|
|
7084
|
+
Array.from({ length: pdf.numPages }, (_, index) => index),
|
|
7085
|
+
16,
|
|
7086
|
+
async (index) => {
|
|
7010
7087
|
const page = await pdf.getPage(index + 1);
|
|
7011
7088
|
const deltaRotate = page.rotate || 0;
|
|
7012
7089
|
const viewport = page.getViewport({
|
|
7013
7090
|
scale: 1,
|
|
7014
7091
|
rotation: rotation + deltaRotate
|
|
7015
7092
|
});
|
|
7016
|
-
pageProxies
|
|
7017
|
-
rotations.push(page.rotate);
|
|
7093
|
+
pageProxies[index] = page;
|
|
7018
7094
|
return viewport;
|
|
7019
|
-
}
|
|
7020
|
-
|
|
7021
|
-
const sortedPageProxies = pageProxies.toSorted(
|
|
7022
|
-
(a, b) => a.pageNumber - b.pageNumber
|
|
7095
|
+
},
|
|
7096
|
+
() => isDisposed
|
|
7023
7097
|
);
|
|
7098
|
+
if (isDisposed) return;
|
|
7024
7099
|
setInitialState((prev) => ({
|
|
7025
7100
|
...prev,
|
|
7026
7101
|
isZoomFitWidth,
|
|
7027
7102
|
viewports,
|
|
7028
|
-
pageProxies
|
|
7103
|
+
pageProxies,
|
|
7029
7104
|
pdfDocumentProxy: pdf,
|
|
7030
7105
|
zoom,
|
|
7031
7106
|
zoomOptions,
|
|
@@ -7036,9 +7111,7 @@ var usePDFDocumentContext = ({
|
|
|
7036
7111
|
};
|
|
7037
7112
|
const loadDocument = () => {
|
|
7038
7113
|
setInitialState(null);
|
|
7039
|
-
setProgress(0);
|
|
7040
7114
|
let loadingTask = null;
|
|
7041
|
-
let isDisposed = false;
|
|
7042
7115
|
void loadPdfJs().then(({ getDocument, version }) => {
|
|
7043
7116
|
if (isDisposed) {
|
|
7044
7117
|
return;
|
|
@@ -7051,18 +7124,11 @@ var usePDFDocumentContext = ({
|
|
|
7051
7124
|
documentOptionsRef.current
|
|
7052
7125
|
)
|
|
7053
7126
|
);
|
|
7054
|
-
loadingTask.onProgress = (progressEvent) => {
|
|
7055
|
-
if (progressEvent.loaded === progressEvent.total) {
|
|
7056
|
-
return;
|
|
7057
|
-
}
|
|
7058
|
-
setProgress(progressEvent.loaded / progressEvent.total);
|
|
7059
|
-
};
|
|
7060
7127
|
return loadingTask.promise.then(async (proxy) => {
|
|
7061
7128
|
if (isDisposed || loadingTask?.destroyed) {
|
|
7062
7129
|
return;
|
|
7063
7130
|
}
|
|
7064
7131
|
onDocumentLoad?.({ proxy, source });
|
|
7065
|
-
setProgress(1);
|
|
7066
7132
|
try {
|
|
7067
7133
|
await generateViewports(proxy);
|
|
7068
7134
|
} catch (error) {
|
|
@@ -7191,31 +7257,122 @@ var Root = forwardRef(
|
|
|
7191
7257
|
}
|
|
7192
7258
|
);
|
|
7193
7259
|
Root.displayName = "Root";
|
|
7194
|
-
|
|
7195
|
-
|
|
7260
|
+
|
|
7261
|
+
// src/lib/document-text.ts
|
|
7262
|
+
var jobs = /* @__PURE__ */ new WeakMap();
|
|
7263
|
+
var CONCURRENCY = 4;
|
|
7264
|
+
function acquireDocumentText(pages) {
|
|
7265
|
+
let job = jobs.get(pages);
|
|
7266
|
+
if (!job) {
|
|
7267
|
+
const controller = new AbortController();
|
|
7268
|
+
const { signal } = controller;
|
|
7269
|
+
let next = 0;
|
|
7270
|
+
const result = new Array(pages.length);
|
|
7271
|
+
const worker = async () => {
|
|
7272
|
+
while (next < pages.length) {
|
|
7273
|
+
signal.throwIfAborted();
|
|
7274
|
+
const index = next++;
|
|
7275
|
+
const page = pages[index];
|
|
7276
|
+
const reader = page.streamTextContent().getReader();
|
|
7277
|
+
const chunks = [];
|
|
7278
|
+
let complete = false;
|
|
7279
|
+
try {
|
|
7280
|
+
while (true) {
|
|
7281
|
+
const { value, done } = await reader.read();
|
|
7282
|
+
signal.throwIfAborted();
|
|
7283
|
+
if (done) {
|
|
7284
|
+
complete = true;
|
|
7285
|
+
break;
|
|
7286
|
+
}
|
|
7287
|
+
chunks.push(
|
|
7288
|
+
value.items.map((item) => "str" in item ? item.str : "").join("")
|
|
7289
|
+
);
|
|
7290
|
+
}
|
|
7291
|
+
} finally {
|
|
7292
|
+
if (!complete)
|
|
7293
|
+
void reader.cancel(new Error("Text indexing stopped")).catch(() => {
|
|
7294
|
+
});
|
|
7295
|
+
reader.releaseLock();
|
|
7296
|
+
}
|
|
7297
|
+
result[index] = { pageNumber: page.pageNumber, text: chunks.join("") };
|
|
7298
|
+
}
|
|
7299
|
+
};
|
|
7300
|
+
const created = {
|
|
7301
|
+
controller,
|
|
7302
|
+
users: 0,
|
|
7303
|
+
settled: false,
|
|
7304
|
+
promise: Promise.resolve([])
|
|
7305
|
+
};
|
|
7306
|
+
created.promise = Promise.all(
|
|
7307
|
+
Array.from({ length: Math.min(CONCURRENCY, pages.length) }, worker)
|
|
7308
|
+
).then(() => result).catch((error) => {
|
|
7309
|
+
controller.abort();
|
|
7310
|
+
if (jobs.get(pages) === created) jobs.delete(pages);
|
|
7311
|
+
throw error;
|
|
7312
|
+
}).finally(() => {
|
|
7313
|
+
created.settled = true;
|
|
7314
|
+
});
|
|
7315
|
+
jobs.set(pages, created);
|
|
7316
|
+
job = created;
|
|
7317
|
+
}
|
|
7318
|
+
job.users++;
|
|
7319
|
+
const acquired = job;
|
|
7320
|
+
let released = false;
|
|
7321
|
+
return {
|
|
7322
|
+
promise: acquired.promise,
|
|
7323
|
+
release() {
|
|
7324
|
+
if (released) return;
|
|
7325
|
+
released = true;
|
|
7326
|
+
acquired.users--;
|
|
7327
|
+
queueMicrotask(() => {
|
|
7328
|
+
if (acquired.users || acquired.settled) return;
|
|
7329
|
+
if (jobs.get(pages) === acquired) jobs.delete(pages);
|
|
7330
|
+
acquired.controller.abort();
|
|
7331
|
+
});
|
|
7332
|
+
}
|
|
7333
|
+
};
|
|
7334
|
+
}
|
|
7335
|
+
var Search = ({
|
|
7336
|
+
children,
|
|
7337
|
+
loading = "Loading...",
|
|
7338
|
+
errorFallback
|
|
7339
|
+
}) => {
|
|
7340
|
+
const [result, setResult] = useState(null);
|
|
7341
|
+
const [attempt, setAttempt] = useState(0);
|
|
7196
7342
|
const proxies = usePdf((state) => state.pageProxies);
|
|
7197
7343
|
const setTextContent = usePdf((state) => state.setTextContent);
|
|
7198
|
-
const
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
const content = await proxy.getTextContent();
|
|
7203
|
-
const text2 = content.items.map((item) => item?.str || "").join("");
|
|
7204
|
-
return Promise.resolve({
|
|
7205
|
-
pageNumber: proxy.pageNumber,
|
|
7206
|
-
text: text2
|
|
7207
|
-
});
|
|
7208
|
-
});
|
|
7209
|
-
const text = await Promise.all(promises);
|
|
7210
|
-
setIsLoading(false);
|
|
7211
|
-
setTextContent(text);
|
|
7212
|
-
},
|
|
7213
|
-
[setTextContent]
|
|
7214
|
-
);
|
|
7344
|
+
const retry = useCallback(() => {
|
|
7345
|
+
setResult(null);
|
|
7346
|
+
setAttempt((value) => value + 1);
|
|
7347
|
+
}, []);
|
|
7215
7348
|
useEffect(() => {
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7349
|
+
let disposed = false;
|
|
7350
|
+
setResult(null);
|
|
7351
|
+
const task = acquireDocumentText(proxies);
|
|
7352
|
+
void task.promise.then((text) => {
|
|
7353
|
+
if (disposed) return;
|
|
7354
|
+
setTextContent(text);
|
|
7355
|
+
setResult({ pages: proxies, status: "ready" });
|
|
7356
|
+
}).catch((error) => {
|
|
7357
|
+
if (disposed) return;
|
|
7358
|
+
console.error("Error extracting PDF text", error);
|
|
7359
|
+
setResult({ pages: proxies, status: "error", error });
|
|
7360
|
+
});
|
|
7361
|
+
return () => {
|
|
7362
|
+
disposed = true;
|
|
7363
|
+
task.release();
|
|
7364
|
+
};
|
|
7365
|
+
}, [proxies, setTextContent, attempt]);
|
|
7366
|
+
if (!result || result.pages !== proxies) return loading;
|
|
7367
|
+
if (result.status === "error") {
|
|
7368
|
+
if (errorFallback) return errorFallback({ error: result.error, retry });
|
|
7369
|
+
return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
|
|
7370
|
+
"Unable to index this document for search.",
|
|
7371
|
+
" ",
|
|
7372
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: retry, children: "Retry" })
|
|
7373
|
+
] });
|
|
7374
|
+
}
|
|
7375
|
+
return children;
|
|
7219
7376
|
};
|
|
7220
7377
|
var DEFAULT_CONFIG = {
|
|
7221
7378
|
maxHeight: 800,
|
|
@@ -7308,10 +7465,11 @@ var useThumbnail = (pageNumber, config = {}) => {
|
|
|
7308
7465
|
};
|
|
7309
7466
|
var Thumbnail = ({
|
|
7310
7467
|
pageNumber = 1,
|
|
7468
|
+
eager = false,
|
|
7311
7469
|
...props
|
|
7312
7470
|
}) => {
|
|
7313
7471
|
const { canvasRef, containerRef, isVisible } = useThumbnail(pageNumber, {
|
|
7314
|
-
isFirstPage: pageNumber < 5
|
|
7472
|
+
isFirstPage: eager || pageNumber < 5
|
|
7315
7473
|
});
|
|
7316
7474
|
const { jumpToPage } = usePdfJump();
|
|
7317
7475
|
return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { minHeight: "150px", minWidth: "10px" }, children: isVisible && /* @__PURE__ */ jsx(
|
|
@@ -7338,16 +7496,107 @@ var Thumbnail = ({
|
|
|
7338
7496
|
}
|
|
7339
7497
|
) });
|
|
7340
7498
|
};
|
|
7499
|
+
function VirtualizedThumbnails({
|
|
7500
|
+
children,
|
|
7501
|
+
count,
|
|
7502
|
+
virtualize,
|
|
7503
|
+
style,
|
|
7504
|
+
onKeyDown,
|
|
7505
|
+
ref,
|
|
7506
|
+
...props
|
|
7507
|
+
}) {
|
|
7508
|
+
const containerRef = useRef(null);
|
|
7509
|
+
useImperativeHandle(ref, () => containerRef.current);
|
|
7510
|
+
const [focusPage, setFocusPage] = useState(null);
|
|
7511
|
+
const itemHeight = Number.isFinite(virtualize.itemHeight) ? Math.max(1, virtualize.itemHeight) : 150;
|
|
7512
|
+
const virtualizer = useVirtualizer({
|
|
7513
|
+
count,
|
|
7514
|
+
getScrollElement: () => containerRef.current,
|
|
7515
|
+
estimateSize: () => itemHeight,
|
|
7516
|
+
overscan: virtualize.overscan ?? 2
|
|
7517
|
+
});
|
|
7518
|
+
const items = virtualizer.getVirtualItems();
|
|
7519
|
+
useLayoutEffect(() => {
|
|
7520
|
+
if (focusPage === null) return;
|
|
7521
|
+
const canvas = containerRef.current?.querySelector(
|
|
7522
|
+
`[data-thumbnail-page="${focusPage}"] canvas`
|
|
7523
|
+
);
|
|
7524
|
+
if (canvas) {
|
|
7525
|
+
canvas.focus({ preventScroll: true });
|
|
7526
|
+
setFocusPage(null);
|
|
7527
|
+
}
|
|
7528
|
+
}, [focusPage, items]);
|
|
7529
|
+
return /* @__PURE__ */ jsx(
|
|
7530
|
+
Primitive.div,
|
|
7531
|
+
{
|
|
7532
|
+
...props,
|
|
7533
|
+
ref: containerRef,
|
|
7534
|
+
style: {
|
|
7535
|
+
height: "100%",
|
|
7536
|
+
overflow: "auto",
|
|
7537
|
+
position: "relative",
|
|
7538
|
+
...style
|
|
7539
|
+
},
|
|
7540
|
+
onKeyDown: (event) => {
|
|
7541
|
+
onKeyDown?.(event);
|
|
7542
|
+
if (event.defaultPrevented || !count) return;
|
|
7543
|
+
const row = event.target.closest(
|
|
7544
|
+
"[data-thumbnail-page]"
|
|
7545
|
+
);
|
|
7546
|
+
if (!row) return;
|
|
7547
|
+
const current = Number(row.dataset.thumbnailPage) - 1;
|
|
7548
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? count - 1 : event.key === "ArrowDown" ? Math.min(count - 1, current + 1) : event.key === "ArrowUp" ? Math.max(0, current - 1) : null;
|
|
7549
|
+
if (next === null) return;
|
|
7550
|
+
event.preventDefault();
|
|
7551
|
+
virtualizer.scrollToIndex(next, { align: "auto" });
|
|
7552
|
+
setFocusPage(next + 1);
|
|
7553
|
+
},
|
|
7554
|
+
children: /* @__PURE__ */ jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative" }, children: items.map((item) => /* @__PURE__ */ jsx(
|
|
7555
|
+
"div",
|
|
7556
|
+
{
|
|
7557
|
+
"data-thumbnail-page": item.index + 1,
|
|
7558
|
+
style: {
|
|
7559
|
+
position: "absolute",
|
|
7560
|
+
top: 0,
|
|
7561
|
+
left: 0,
|
|
7562
|
+
width: "100%",
|
|
7563
|
+
height: itemHeight,
|
|
7564
|
+
transform: `translateY(${item.start}px)`
|
|
7565
|
+
},
|
|
7566
|
+
children: cloneElement(
|
|
7567
|
+
children,
|
|
7568
|
+
{ pageNumber: item.index + 1, eager: true }
|
|
7569
|
+
)
|
|
7570
|
+
},
|
|
7571
|
+
item.key
|
|
7572
|
+
)) })
|
|
7573
|
+
}
|
|
7574
|
+
);
|
|
7575
|
+
}
|
|
7341
7576
|
var Thumbnails = ({
|
|
7342
7577
|
children,
|
|
7578
|
+
virtualize,
|
|
7343
7579
|
...props
|
|
7344
7580
|
}) => {
|
|
7345
7581
|
const pageCount = usePdf((state) => state.pdfDocumentProxy.numPages);
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7582
|
+
if (virtualize)
|
|
7583
|
+
return /* @__PURE__ */ jsx(
|
|
7584
|
+
VirtualizedThumbnails,
|
|
7585
|
+
{
|
|
7586
|
+
...props,
|
|
7587
|
+
virtualize,
|
|
7588
|
+
count: pageCount,
|
|
7589
|
+
children
|
|
7590
|
+
}
|
|
7591
|
+
);
|
|
7592
|
+
return /* @__PURE__ */ jsx(Primitive.div, { ...props, children: Array.from(
|
|
7593
|
+
{ length: pageCount },
|
|
7594
|
+
(_, index) => cloneElement(children, {
|
|
7595
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: a PDF page has a stable ordinal within this document
|
|
7596
|
+
key: index,
|
|
7597
|
+
pageNumber: index + 1
|
|
7598
|
+
})
|
|
7599
|
+
) });
|
|
7351
7600
|
};
|
|
7352
7601
|
var ZoomIn = ({ ...props }) => {
|
|
7353
7602
|
const setZoom = usePdf((state) => state.updateZoom);
|
|
@@ -7405,220 +7654,277 @@ var CurrentZoom = ({ ...props }) => {
|
|
|
7405
7654
|
}
|
|
7406
7655
|
);
|
|
7407
7656
|
};
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7657
|
+
|
|
7658
|
+
// src/lib/search.ts
|
|
7659
|
+
var normalizedPages = /* @__PURE__ */ new WeakMap();
|
|
7660
|
+
function normalize(page) {
|
|
7661
|
+
let cached = normalizedPages.get(page);
|
|
7662
|
+
if (!cached || cached.text !== page.text) {
|
|
7663
|
+
cached = { text: page.text, lower: page.text.toLowerCase() };
|
|
7664
|
+
normalizedPages.set(page, cached);
|
|
7665
|
+
}
|
|
7666
|
+
return cached.lower;
|
|
7667
|
+
}
|
|
7668
|
+
function createBoundedDistance(query, maxDistance) {
|
|
7669
|
+
const length = query.length;
|
|
7670
|
+
let previous = new Float64Array(length + 1);
|
|
7671
|
+
let current = new Float64Array(length + 1);
|
|
7672
|
+
const exceeded = maxDistance + 1;
|
|
7673
|
+
return (text, offset3) => {
|
|
7674
|
+
const size = Math.min(length, text.length - offset3);
|
|
7675
|
+
if (length - size > maxDistance) return exceeded;
|
|
7676
|
+
for (let j = 0; j <= length; j++) previous[j] = j;
|
|
7677
|
+
for (let i = 1; i <= size; i++) {
|
|
7678
|
+
const start = Math.max(1, i - maxDistance);
|
|
7679
|
+
const end = Math.min(length, i + maxDistance);
|
|
7680
|
+
current[0] = i;
|
|
7681
|
+
if (start > 1) current[start - 1] = exceeded;
|
|
7682
|
+
let minimum = exceeded;
|
|
7683
|
+
const code = text.charCodeAt(offset3 + i - 1);
|
|
7684
|
+
for (let j = start; j <= end; j++) {
|
|
7685
|
+
const value = Math.min(
|
|
7686
|
+
previous[j] + 1,
|
|
7687
|
+
current[j - 1] + 1,
|
|
7688
|
+
previous[j - 1] + (query.charCodeAt(j - 1) === code ? 0 : 1)
|
|
7425
7689
|
);
|
|
7690
|
+
current[j] = value;
|
|
7691
|
+
minimum = Math.min(minimum, value);
|
|
7426
7692
|
}
|
|
7693
|
+
if (end < length) current[end + 1] = exceeded;
|
|
7694
|
+
if (minimum > maxDistance) return exceeded;
|
|
7695
|
+
[previous, current] = [current, previous];
|
|
7427
7696
|
}
|
|
7697
|
+
return previous[length];
|
|
7698
|
+
};
|
|
7699
|
+
}
|
|
7700
|
+
function insertMatch(matches, match, limit) {
|
|
7701
|
+
if (!limit || matches.length === limit && matches[limit - 1].score >= match.score)
|
|
7702
|
+
return;
|
|
7703
|
+
let low = 0;
|
|
7704
|
+
let high = matches.length;
|
|
7705
|
+
while (low < high) {
|
|
7706
|
+
const mid = low + high >>> 1;
|
|
7707
|
+
if (matches[mid].score >= match.score) low = mid + 1;
|
|
7708
|
+
else high = mid;
|
|
7709
|
+
}
|
|
7710
|
+
matches.splice(low, 0, match);
|
|
7711
|
+
if (matches.length > limit) matches.pop();
|
|
7712
|
+
}
|
|
7713
|
+
function* runSearch(pages, searchText, options = {}, cooperative = false, budget = 8) {
|
|
7714
|
+
const empty = { exactMatches: [], fuzzyMatches: [], hasMoreResults: false };
|
|
7715
|
+
if (!searchText.trim()) return empty;
|
|
7716
|
+
const threshold = Math.max(0, Math.min(1, options.threshold ?? 0.7));
|
|
7717
|
+
const limit = Number.isFinite(options.limit) ? Math.max(0, Math.floor(options.limit)) : 10;
|
|
7718
|
+
const textSize = options.textSize ?? 100;
|
|
7719
|
+
const query = searchText.toLowerCase();
|
|
7720
|
+
const maxDistance = Math.floor(query.length * (1 - threshold));
|
|
7721
|
+
const distance = maxDistance > 0 ? createBoundedDistance(query, maxDistance) : null;
|
|
7722
|
+
const exactMatches = [];
|
|
7723
|
+
const fuzzyMatches = [];
|
|
7724
|
+
let exactCount = 0;
|
|
7725
|
+
let fuzzyCount = 0;
|
|
7726
|
+
let steps = 0;
|
|
7727
|
+
let deadline = cooperative ? performance.now() + budget : 0;
|
|
7728
|
+
for (const page of pages) {
|
|
7729
|
+
const lower = normalize(page);
|
|
7730
|
+
const exactStarts = [];
|
|
7731
|
+
let index = 0;
|
|
7732
|
+
while (true) {
|
|
7733
|
+
if (cooperative && (++steps & 127) === 0 && performance.now() >= deadline) {
|
|
7734
|
+
yield;
|
|
7735
|
+
deadline = performance.now() + budget;
|
|
7736
|
+
}
|
|
7737
|
+
const matchIndex = lower.indexOf(query, index);
|
|
7738
|
+
if (matchIndex === -1) break;
|
|
7739
|
+
exactCount++;
|
|
7740
|
+
if (maxDistance > 0) exactStarts.push(matchIndex);
|
|
7741
|
+
if (exactMatches.length < limit) {
|
|
7742
|
+
exactMatches.push({
|
|
7743
|
+
pageNumber: page.pageNumber,
|
|
7744
|
+
text: page.text.substr(matchIndex, searchText.length + textSize),
|
|
7745
|
+
score: 1,
|
|
7746
|
+
matchIndex,
|
|
7747
|
+
isExactMatch: true,
|
|
7748
|
+
searchText
|
|
7749
|
+
});
|
|
7750
|
+
}
|
|
7751
|
+
index = matchIndex + searchText.length;
|
|
7752
|
+
}
|
|
7753
|
+
if (!(maxDistance > 0)) continue;
|
|
7754
|
+
index = 0;
|
|
7755
|
+
let exactIndex = 0;
|
|
7756
|
+
while (index < lower.length) {
|
|
7757
|
+
if (cooperative && (++steps & 127) === 0 && performance.now() >= deadline) {
|
|
7758
|
+
yield;
|
|
7759
|
+
deadline = performance.now() + budget;
|
|
7760
|
+
}
|
|
7761
|
+
while (exactIndex < exactStarts.length && exactStarts[exactIndex] + searchText.length <= index)
|
|
7762
|
+
exactIndex++;
|
|
7763
|
+
const exactStart = exactStarts[exactIndex];
|
|
7764
|
+
if (exactStart !== void 0 && index >= exactStart) {
|
|
7765
|
+
index = exactStart + searchText.length;
|
|
7766
|
+
continue;
|
|
7767
|
+
}
|
|
7768
|
+
const edits = distance(lower, index);
|
|
7769
|
+
if (edits > 0 && edits <= maxDistance) {
|
|
7770
|
+
fuzzyCount++;
|
|
7771
|
+
const score = 1 - edits / query.length;
|
|
7772
|
+
if (fuzzyMatches.length < limit || score > (fuzzyMatches[limit - 1]?.score ?? Infinity)) {
|
|
7773
|
+
insertMatch(
|
|
7774
|
+
fuzzyMatches,
|
|
7775
|
+
{
|
|
7776
|
+
pageNumber: page.pageNumber,
|
|
7777
|
+
text: page.text.substr(index, query.length + textSize),
|
|
7778
|
+
score,
|
|
7779
|
+
matchIndex: index,
|
|
7780
|
+
isExactMatch: false,
|
|
7781
|
+
searchText
|
|
7782
|
+
},
|
|
7783
|
+
limit
|
|
7784
|
+
);
|
|
7785
|
+
}
|
|
7786
|
+
index += query.length;
|
|
7787
|
+
} else index++;
|
|
7788
|
+
}
|
|
7789
|
+
}
|
|
7790
|
+
const exactLimit = Math.min(
|
|
7791
|
+
exactCount,
|
|
7792
|
+
Math.max(Math.ceil(limit / 2), limit - fuzzyCount)
|
|
7793
|
+
);
|
|
7794
|
+
return {
|
|
7795
|
+
exactMatches: exactMatches.slice(0, exactLimit),
|
|
7796
|
+
fuzzyMatches: fuzzyMatches.slice(0, limit - exactLimit),
|
|
7797
|
+
hasMoreResults: exactCount + fuzzyCount > limit
|
|
7798
|
+
};
|
|
7799
|
+
}
|
|
7800
|
+
function searchDocument(pages, searchText, options = {}) {
|
|
7801
|
+
return runSearch(pages, searchText, options).next().value;
|
|
7802
|
+
}
|
|
7803
|
+
async function searchDocumentAsync(pages, searchText, options = {}) {
|
|
7804
|
+
const budget = Number.isFinite(options.timeSliceMs) ? Math.max(1, options.timeSliceMs) : 8;
|
|
7805
|
+
const iterator = runSearch(pages, searchText, options, true, budget);
|
|
7806
|
+
while (true) {
|
|
7807
|
+
if (options.signal?.aborted)
|
|
7808
|
+
throw new DOMException("Search aborted", "AbortError");
|
|
7809
|
+
const result = iterator.next();
|
|
7810
|
+
if (result.done) return result.value;
|
|
7811
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
7428
7812
|
}
|
|
7429
|
-
return matrix[b.length][a.length];
|
|
7430
7813
|
}
|
|
7814
|
+
|
|
7815
|
+
// src/hooks/search/useSearch.tsx
|
|
7431
7816
|
var useSearch = () => {
|
|
7432
7817
|
const textContent = usePdf((state) => state.textContent);
|
|
7818
|
+
const activeSearch = useRef(null);
|
|
7819
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
7433
7820
|
const [keywords] = useState([]);
|
|
7434
7821
|
const [searchResults, setSearchResults] = useState({
|
|
7435
7822
|
exactMatches: [],
|
|
7436
7823
|
fuzzyMatches: [],
|
|
7437
7824
|
hasMoreResults: false
|
|
7438
7825
|
});
|
|
7439
|
-
|
|
7440
|
-
(
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7826
|
+
useEffect(() => {
|
|
7827
|
+
setIsSearching(false);
|
|
7828
|
+
return () => {
|
|
7829
|
+
activeSearch.current?.abort();
|
|
7830
|
+
activeSearch.current = null;
|
|
7831
|
+
};
|
|
7832
|
+
}, [textContent]);
|
|
7833
|
+
const cancelSearch = useCallback(() => {
|
|
7834
|
+
activeSearch.current?.abort();
|
|
7835
|
+
activeSearch.current = null;
|
|
7836
|
+
setIsSearching(false);
|
|
7837
|
+
}, []);
|
|
7838
|
+
const searchAsync = useCallback(
|
|
7839
|
+
async (searchText, options = {}) => {
|
|
7840
|
+
activeSearch.current?.abort();
|
|
7841
|
+
const controller = new AbortController();
|
|
7842
|
+
activeSearch.current = controller;
|
|
7843
|
+
const abort = () => controller.abort();
|
|
7844
|
+
if (options.signal?.aborted) abort();
|
|
7845
|
+
else options.signal?.addEventListener("abort", abort, { once: true });
|
|
7846
|
+
setIsSearching(true);
|
|
7847
|
+
try {
|
|
7848
|
+
const results = await searchDocumentAsync(textContent, searchText, {
|
|
7849
|
+
...options,
|
|
7850
|
+
signal: controller.signal
|
|
7455
7851
|
});
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
const textLower = text.toLowerCase();
|
|
7466
|
-
const searchLower = searchText.toLowerCase();
|
|
7467
|
-
let index = 0;
|
|
7468
|
-
while (index < textLower.length) {
|
|
7469
|
-
if (excludeIndices.has(index)) {
|
|
7470
|
-
index++;
|
|
7471
|
-
continue;
|
|
7472
|
-
}
|
|
7473
|
-
const chunk = textLower.substr(index, searchLower.length + 10);
|
|
7474
|
-
const distance = levenshteinDistance(
|
|
7475
|
-
searchLower,
|
|
7476
|
-
chunk.substr(0, searchLower.length)
|
|
7477
|
-
);
|
|
7478
|
-
const maxDistance = Math.floor(searchLower.length * (1 - threshold));
|
|
7479
|
-
if (distance <= maxDistance && distance > 0) {
|
|
7480
|
-
const score = 1 - distance / searchLower.length;
|
|
7481
|
-
results.push({
|
|
7482
|
-
pageNumber,
|
|
7483
|
-
text: text.substr(index, searchLower.length + textSize),
|
|
7484
|
-
score,
|
|
7485
|
-
matchIndex: index,
|
|
7486
|
-
isExactMatch: false,
|
|
7487
|
-
searchText
|
|
7488
|
-
});
|
|
7489
|
-
index += searchLower.length;
|
|
7490
|
-
} else {
|
|
7491
|
-
index++;
|
|
7852
|
+
if (controller.signal.aborted)
|
|
7853
|
+
throw new DOMException("Search aborted", "AbortError");
|
|
7854
|
+
if (activeSearch.current === controller) setSearchResults(results);
|
|
7855
|
+
return results;
|
|
7856
|
+
} finally {
|
|
7857
|
+
options.signal?.removeEventListener("abort", abort);
|
|
7858
|
+
if (activeSearch.current === controller) {
|
|
7859
|
+
activeSearch.current = null;
|
|
7860
|
+
setIsSearching(false);
|
|
7492
7861
|
}
|
|
7493
7862
|
}
|
|
7494
|
-
return results;
|
|
7495
7863
|
},
|
|
7496
|
-
[]
|
|
7864
|
+
[textContent]
|
|
7497
7865
|
);
|
|
7498
7866
|
const search = useCallback(
|
|
7499
7867
|
(searchText, options = {}) => {
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
fuzzyMatches: [],
|
|
7505
|
-
hasMoreResults: false
|
|
7506
|
-
};
|
|
7507
|
-
setSearchResults(emptyResults);
|
|
7508
|
-
return emptyResults;
|
|
7509
|
-
}
|
|
7510
|
-
let exactMatches = [];
|
|
7511
|
-
let fuzzyMatches = [];
|
|
7512
|
-
const exactMatchIndices = /* @__PURE__ */ new Set();
|
|
7513
|
-
textContent.forEach(({ pageNumber, text }) => {
|
|
7514
|
-
const matches = findExactMatches(
|
|
7515
|
-
searchText,
|
|
7516
|
-
text,
|
|
7517
|
-
pageNumber,
|
|
7518
|
-
textSize
|
|
7519
|
-
);
|
|
7520
|
-
exactMatches = [...exactMatches, ...matches];
|
|
7521
|
-
matches.forEach((match) => {
|
|
7522
|
-
for (let i = 0; i < searchText.length; i++) {
|
|
7523
|
-
exactMatchIndices.add(match.matchIndex + i);
|
|
7524
|
-
}
|
|
7525
|
-
});
|
|
7526
|
-
});
|
|
7527
|
-
textContent.forEach(({ pageNumber, text }) => {
|
|
7528
|
-
const matches = findFuzzyMatches(
|
|
7529
|
-
searchText,
|
|
7530
|
-
text,
|
|
7531
|
-
pageNumber,
|
|
7532
|
-
threshold,
|
|
7533
|
-
exactMatchIndices,
|
|
7534
|
-
textSize
|
|
7535
|
-
);
|
|
7536
|
-
fuzzyMatches = [...fuzzyMatches, ...matches];
|
|
7537
|
-
});
|
|
7538
|
-
exactMatches.sort((a, b) => b.score - a.score);
|
|
7539
|
-
fuzzyMatches.sort((a, b) => b.score - a.score);
|
|
7540
|
-
const exactLimit = Math.min(exactMatches.length, Math.ceil(limit / 2));
|
|
7541
|
-
const fuzzyLimit = Math.min(fuzzyMatches.length, limit - exactLimit);
|
|
7542
|
-
const limitedResults = {
|
|
7543
|
-
exactMatches: exactMatches.slice(0, exactLimit),
|
|
7544
|
-
fuzzyMatches: fuzzyMatches.slice(0, fuzzyLimit),
|
|
7545
|
-
hasMoreResults: exactMatches.length + fuzzyMatches.length > limit
|
|
7546
|
-
};
|
|
7547
|
-
setSearchResults(limitedResults);
|
|
7548
|
-
return limitedResults;
|
|
7868
|
+
cancelSearch();
|
|
7869
|
+
const results = searchDocument(textContent, searchText, options);
|
|
7870
|
+
setSearchResults(results);
|
|
7871
|
+
return results;
|
|
7549
7872
|
},
|
|
7550
|
-
[textContent,
|
|
7873
|
+
[textContent, cancelSearch]
|
|
7551
7874
|
);
|
|
7552
7875
|
return {
|
|
7553
7876
|
textContent,
|
|
7554
7877
|
keywords,
|
|
7555
7878
|
searchResults,
|
|
7556
|
-
search
|
|
7879
|
+
search,
|
|
7880
|
+
searchAsync,
|
|
7881
|
+
cancelSearch,
|
|
7882
|
+
isSearching
|
|
7557
7883
|
};
|
|
7558
7884
|
};
|
|
7559
7885
|
|
|
7560
7886
|
// src/hooks/search/useSearchPosition.tsx
|
|
7561
7887
|
async function calculateHighlightRects(pageProxy, textMatch) {
|
|
7888
|
+
const matchLength = textMatch.searchText ? textMatch.searchText.length : textMatch.text.length;
|
|
7889
|
+
if (matchLength === 0 || !Number.isFinite(textMatch.matchIndex) || textMatch.matchIndex < 0)
|
|
7890
|
+
return [];
|
|
7891
|
+
const matchEnd = textMatch.matchIndex + matchLength;
|
|
7562
7892
|
const reader = pageProxy.streamTextContent().getReader();
|
|
7563
|
-
const
|
|
7893
|
+
const matchRects = [];
|
|
7894
|
+
const viewport = pageProxy.getViewport({ scale: 1 });
|
|
7895
|
+
let offset3 = 0;
|
|
7896
|
+
let foundEnd = false;
|
|
7564
7897
|
try {
|
|
7565
|
-
|
|
7898
|
+
read: while (true) {
|
|
7566
7899
|
const { value, done } = await reader.read();
|
|
7567
7900
|
if (done) break;
|
|
7568
|
-
items
|
|
7569
|
-
|
|
7570
|
-
|
|
7901
|
+
for (const item of value.items) {
|
|
7902
|
+
if (!("str" in item) || item.str.length === 0) continue;
|
|
7903
|
+
const length = item.str.length;
|
|
7904
|
+
const start = Math.max(0, textMatch.matchIndex - offset3);
|
|
7905
|
+
const end = Math.min(length, matchEnd - offset3);
|
|
7906
|
+
if (end > start) {
|
|
7907
|
+
matchRects.push({
|
|
7908
|
+
pageNumber: textMatch.pageNumber,
|
|
7909
|
+
left: item.transform[4] + start * (item.width / length),
|
|
7910
|
+
top: viewport.height - (item.transform[5] + item.height),
|
|
7911
|
+
width: (end - start) * (item.width / length),
|
|
7912
|
+
height: item.height
|
|
7913
|
+
});
|
|
7914
|
+
}
|
|
7915
|
+
offset3 += length;
|
|
7916
|
+
if (offset3 >= matchEnd) {
|
|
7917
|
+
foundEnd = true;
|
|
7918
|
+
break read;
|
|
7919
|
+
}
|
|
7920
|
+
}
|
|
7571
7921
|
}
|
|
7572
7922
|
} finally {
|
|
7923
|
+
if (foundEnd)
|
|
7924
|
+
void reader.cancel(new Error("Highlight range found")).catch(() => {
|
|
7925
|
+
});
|
|
7573
7926
|
reader.releaseLock();
|
|
7574
7927
|
}
|
|
7575
|
-
const matchLength = textMatch.searchText ? textMatch.searchText.length : textMatch.text.length;
|
|
7576
|
-
const matchRects = [];
|
|
7577
|
-
let currentIndex = 0;
|
|
7578
|
-
let remainingMatchLength = matchLength;
|
|
7579
|
-
let foundStart = false;
|
|
7580
|
-
const viewport = pageProxy.getViewport({ scale: 1 });
|
|
7581
|
-
for (let i = 0; i < items.length; i++) {
|
|
7582
|
-
const item = items[i];
|
|
7583
|
-
if (!item) continue;
|
|
7584
|
-
const itemLength = item.str.length;
|
|
7585
|
-
if (!foundStart && currentIndex <= textMatch.matchIndex && textMatch.matchIndex < currentIndex + itemLength) {
|
|
7586
|
-
foundStart = true;
|
|
7587
|
-
const matchStartInItem = textMatch.matchIndex - currentIndex;
|
|
7588
|
-
const matchLengthInItem = Math.min(
|
|
7589
|
-
itemLength - matchStartInItem,
|
|
7590
|
-
remainingMatchLength
|
|
7591
|
-
);
|
|
7592
|
-
const transform = item.transform;
|
|
7593
|
-
const y = viewport.height - (transform[5] + item.height);
|
|
7594
|
-
const rect = {
|
|
7595
|
-
pageNumber: textMatch.pageNumber,
|
|
7596
|
-
left: transform[4] + matchStartInItem * (item.width / itemLength),
|
|
7597
|
-
top: y,
|
|
7598
|
-
width: matchLengthInItem * (item.width / itemLength),
|
|
7599
|
-
height: item.height
|
|
7600
|
-
};
|
|
7601
|
-
matchRects.push(rect);
|
|
7602
|
-
remainingMatchLength -= matchLengthInItem;
|
|
7603
|
-
} else if (foundStart && remainingMatchLength > 0) {
|
|
7604
|
-
const matchLengthInItem = Math.min(itemLength, remainingMatchLength);
|
|
7605
|
-
const transform = item.transform;
|
|
7606
|
-
const y = viewport.height - (transform[5] + item.height);
|
|
7607
|
-
const rect = {
|
|
7608
|
-
pageNumber: textMatch.pageNumber,
|
|
7609
|
-
left: transform[4],
|
|
7610
|
-
top: y,
|
|
7611
|
-
width: matchLengthInItem * (item.width / itemLength),
|
|
7612
|
-
height: item.height
|
|
7613
|
-
};
|
|
7614
|
-
matchRects.push(rect);
|
|
7615
|
-
remainingMatchLength -= matchLengthInItem;
|
|
7616
|
-
}
|
|
7617
|
-
if (remainingMatchLength <= 0 && foundStart) {
|
|
7618
|
-
break;
|
|
7619
|
-
}
|
|
7620
|
-
currentIndex += itemLength;
|
|
7621
|
-
}
|
|
7622
7928
|
return mergeAdjacentRects(matchRects);
|
|
7623
7929
|
}
|
|
7624
7930
|
function mergeAdjacentRects(rects) {
|
|
@@ -7646,4 +7952,4 @@ function mergeAdjacentRects(rects) {
|
|
|
7646
7952
|
// src/hooks/usePageRendered.ts
|
|
7647
7953
|
var usePageRendered = (pageNumber) => usePdf((state) => !!state.renderedPages[pageNumber]);
|
|
7648
7954
|
|
|
7649
|
-
export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, AnnotationsStoreProvider, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, DEFAULT_DARK_MODE_COLORS, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, createDarkModeColorMap, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageRendered, usePdf, usePdfJump, useSearch, useSelectionDimensions };
|
|
7955
|
+
export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, AnnotationsStoreProvider, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, DEFAULT_DARK_MODE_COLORS, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, createDarkModeColorMap, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageAnnotations, usePageRendered, usePdf, usePdfJump, useSearch, useSelectionDimensions };
|