@anaralabs/lector 3.14.11 → 3.14.13
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 +15 -15
- package/dist/_tsup-dts-rollup.d.ts +91 -2
- package/dist/index.js +941 -307
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef,
|
|
3
|
+
import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef, useLayoutEffect, useContext, useState, useCallback, useSyncExternalStore, createRef, 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';
|
|
@@ -9,7 +9,7 @@ import { useShallow } from 'zustand/react/shallow';
|
|
|
9
9
|
import { useDebounce } from 'use-debounce';
|
|
10
10
|
import { v4 } from 'uuid';
|
|
11
11
|
import { Slot } from '@radix-ui/react-slot';
|
|
12
|
-
import { useVirtualizer, elementScroll,
|
|
12
|
+
import { useVirtualizer, elementScroll, observeElementRect } from '@tanstack/react-virtual';
|
|
13
13
|
|
|
14
14
|
// src/lib/clamp.ts
|
|
15
15
|
var clamp = (value, minimum, maximum) => {
|
|
@@ -219,6 +219,19 @@ function createDarkModeColorMap(colors) {
|
|
|
219
219
|
return map;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
// src/lib/viewport-zoom.ts
|
|
223
|
+
var viewportZoomHandlers = /* @__PURE__ */ new WeakMap();
|
|
224
|
+
function applyViewportZoom(viewport, zoom) {
|
|
225
|
+
if (viewport) viewportZoomHandlers.get(viewport)?.(zoom);
|
|
226
|
+
}
|
|
227
|
+
function registerViewportZoom(viewport, apply) {
|
|
228
|
+
viewportZoomHandlers.set(viewport, apply);
|
|
229
|
+
return () => {
|
|
230
|
+
if (viewportZoomHandlers.get(viewport) === apply)
|
|
231
|
+
viewportZoomHandlers.delete(viewport);
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
222
235
|
// src/lib/zoom.ts
|
|
223
236
|
var USE_LAYOUT_ZOOM = typeof navigator !== "undefined" && /AppleWebKit/i.test(navigator.userAgent) && !/Chrome|Chromium|Android/i.test(navigator.userAgent);
|
|
224
237
|
var getFitWidthZoom = (containerWidth, viewports, zoomOptions) => {
|
|
@@ -271,6 +284,7 @@ var PDFStore = createZustandContext(
|
|
|
271
284
|
minZoom,
|
|
272
285
|
maxZoom
|
|
273
286
|
);
|
|
287
|
+
applyViewportZoom(state.viewportRef.current, newZoom);
|
|
274
288
|
return newZoom === state.zoom && isZoomFitWidth === state.isZoomFitWidth ? state : { zoom: newZoom, isZoomFitWidth };
|
|
275
289
|
});
|
|
276
290
|
},
|
|
@@ -285,7 +299,8 @@ var PDFStore = createZustandContext(
|
|
|
285
299
|
get().updateZoom(clampedZoom, true);
|
|
286
300
|
return clampedZoom;
|
|
287
301
|
},
|
|
288
|
-
currentPage: 1,
|
|
302
|
+
currentPage: initialState.initialPage ?? 1,
|
|
303
|
+
initialPage: initialState.initialPage ?? 1,
|
|
289
304
|
setCurrentPage: (val) => {
|
|
290
305
|
set(
|
|
291
306
|
(state) => state.currentPage === val ? state : { currentPage: val }
|
|
@@ -310,9 +325,18 @@ var PDFStore = createZustandContext(
|
|
|
310
325
|
);
|
|
311
326
|
},
|
|
312
327
|
pageProxies: initialState.pageProxies,
|
|
328
|
+
pagesLoaded: initialState.pageProxies.length === initialState.pdfDocumentProxy.numPages,
|
|
329
|
+
loadPdfPageProxies: () => initialState.pageResources ? initialState.pageResources.loadAll() : Promise.resolve(get().pageProxies),
|
|
330
|
+
isPageLoaded: (pageNumber) => Boolean(
|
|
331
|
+
initialState.pageResources?.get(pageNumber) ?? get().pageProxies[pageNumber - 1]
|
|
332
|
+
),
|
|
333
|
+
loadPdfPageProxy: (pageNumber) => initialState.pageResources ? initialState.pageResources.load(pageNumber) : Promise.resolve().then(() => get().getPdfPageProxy(pageNumber)),
|
|
313
334
|
getPdfPageProxy: (pageNumber) => {
|
|
314
|
-
const proxy = get().pageProxies[pageNumber - 1];
|
|
315
|
-
if (!proxy)
|
|
335
|
+
const proxy = initialState.pageResources?.get(pageNumber) ?? get().pageProxies[pageNumber - 1];
|
|
336
|
+
if (!proxy)
|
|
337
|
+
throw new Error(
|
|
338
|
+
initialState.pageResources && Number.isInteger(pageNumber) && pageNumber >= 1 && pageNumber <= initialState.pdfDocumentProxy.numPages ? `Page ${pageNumber} is not loaded. Await loadPdfPageProxy(${pageNumber}) first.` : `Page ${pageNumber} does not exist`
|
|
339
|
+
);
|
|
316
340
|
return proxy;
|
|
317
341
|
},
|
|
318
342
|
textContent: [],
|
|
@@ -665,23 +689,44 @@ var AnnotationTooltip = ({
|
|
|
665
689
|
};
|
|
666
690
|
var createAnnotationStore = () => createStore((set) => ({
|
|
667
691
|
annotations: [],
|
|
668
|
-
addAnnotation: (annotation) => set((state) =>
|
|
669
|
-
annotations
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
692
|
+
addAnnotation: (annotation) => set((state) => {
|
|
693
|
+
const annotations = [...state.annotations, annotation];
|
|
694
|
+
updatePageIndex(state.annotations, annotations, /* @__PURE__ */ new Map(), [
|
|
695
|
+
annotation
|
|
696
|
+
]);
|
|
697
|
+
return { annotations };
|
|
698
|
+
}),
|
|
699
|
+
updateAnnotation: (id, updates) => set((state) => {
|
|
700
|
+
const changes = /* @__PURE__ */ new Map();
|
|
701
|
+
const keys = Object.keys(updates);
|
|
702
|
+
const annotations = state.annotations.map((annotation) => {
|
|
703
|
+
if (annotation.id !== id || keys.every((key) => Object.is(annotation[key], updates[key])))
|
|
704
|
+
return annotation;
|
|
705
|
+
const updated = changes.get(annotation) ?? {
|
|
674
706
|
...annotation,
|
|
675
707
|
...updates
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
})
|
|
684
|
-
|
|
708
|
+
};
|
|
709
|
+
changes.set(annotation, updated);
|
|
710
|
+
return updated;
|
|
711
|
+
});
|
|
712
|
+
if (!changes.size) return state;
|
|
713
|
+
updatePageIndex(state.annotations, annotations, changes);
|
|
714
|
+
return { annotations };
|
|
715
|
+
}),
|
|
716
|
+
deleteAnnotation: (id) => set((state) => {
|
|
717
|
+
const changes = /* @__PURE__ */ new Map();
|
|
718
|
+
const annotations = state.annotations.filter((annotation) => {
|
|
719
|
+
if (annotation.id !== id) return true;
|
|
720
|
+
changes.set(annotation, null);
|
|
721
|
+
return false;
|
|
722
|
+
});
|
|
723
|
+
if (!changes.size) return state;
|
|
724
|
+
updatePageIndex(state.annotations, annotations, changes);
|
|
725
|
+
return { annotations };
|
|
726
|
+
}),
|
|
727
|
+
setAnnotations: (annotations) => set(
|
|
728
|
+
(state) => state.annotations === annotations ? state : { annotations }
|
|
729
|
+
)
|
|
685
730
|
}));
|
|
686
731
|
var AnnotationsStore = createZustandContext(
|
|
687
732
|
() => createAnnotationStore()
|
|
@@ -713,9 +758,7 @@ function annotationsByPage(annotations) {
|
|
|
713
758
|
if (index) return index;
|
|
714
759
|
index = /* @__PURE__ */ new Map();
|
|
715
760
|
for (const annotation of annotations) {
|
|
716
|
-
const pages =
|
|
717
|
-
for (const rect of annotation.highlights) pages.add(rect.pageNumber);
|
|
718
|
-
for (const rect of annotation.underlines ?? []) pages.add(rect.pageNumber);
|
|
761
|
+
const pages = annotationPages(annotation);
|
|
719
762
|
for (const page of pages) {
|
|
720
763
|
let values = index.get(page);
|
|
721
764
|
if (!values) {
|
|
@@ -728,6 +771,45 @@ function annotationsByPage(annotations) {
|
|
|
728
771
|
indexes.set(annotations, index);
|
|
729
772
|
return index;
|
|
730
773
|
}
|
|
774
|
+
function annotationPages(annotation) {
|
|
775
|
+
const pages = /* @__PURE__ */ new Set([annotation.pageNumber]);
|
|
776
|
+
for (const rect of annotation.highlights) pages.add(rect.pageNumber);
|
|
777
|
+
for (const rect of annotation.underlines ?? []) pages.add(rect.pageNumber);
|
|
778
|
+
return pages;
|
|
779
|
+
}
|
|
780
|
+
function isOnPage(annotation, page) {
|
|
781
|
+
return annotation.pageNumber === page || annotation.highlights.some((rect) => rect.pageNumber === page) || annotation.underlines?.some((rect) => rect.pageNumber === page);
|
|
782
|
+
}
|
|
783
|
+
function updatePageIndex(previous, next, changes, appended = []) {
|
|
784
|
+
const previousIndex = indexes.get(previous);
|
|
785
|
+
if (!previousIndex) return;
|
|
786
|
+
const index = new Map(previousIndex);
|
|
787
|
+
const affected = /* @__PURE__ */ new Set();
|
|
788
|
+
const movedTo = /* @__PURE__ */ new Set();
|
|
789
|
+
for (const [before, after] of changes) {
|
|
790
|
+
const oldPages = annotationPages(before);
|
|
791
|
+
for (const page of oldPages) affected.add(page);
|
|
792
|
+
if (after)
|
|
793
|
+
for (const page of annotationPages(after)) {
|
|
794
|
+
affected.add(page);
|
|
795
|
+
if (!oldPages.has(page)) movedTo.add(page);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
for (const page of affected) {
|
|
799
|
+
const values = movedTo.has(page) ? next.filter((annotation) => isOnPage(annotation, page)) : (previousIndex.get(page) ?? []).flatMap((annotation) => {
|
|
800
|
+
if (!changes.has(annotation)) return [annotation];
|
|
801
|
+
const updated = changes.get(annotation);
|
|
802
|
+
return updated && isOnPage(updated, page) ? [updated] : [];
|
|
803
|
+
});
|
|
804
|
+
if (values.length) index.set(page, values);
|
|
805
|
+
else index.delete(page);
|
|
806
|
+
}
|
|
807
|
+
for (const annotation of appended)
|
|
808
|
+
for (const page of annotationPages(annotation)) {
|
|
809
|
+
index.set(page, [...index.get(page) ?? [], annotation]);
|
|
810
|
+
}
|
|
811
|
+
indexes.set(next, index);
|
|
812
|
+
}
|
|
731
813
|
function usePageAnnotations(pageNumber) {
|
|
732
814
|
return useAnnotations(
|
|
733
815
|
useShallow(
|
|
@@ -1035,7 +1117,7 @@ var usePdfJump = () => {
|
|
|
1035
1117
|
}
|
|
1036
1118
|
let scrollOffset;
|
|
1037
1119
|
if (align === "center") {
|
|
1038
|
-
const viewportHeight = virtualizer.
|
|
1120
|
+
const viewportHeight = virtualizer.scrollRect?.height ?? 0;
|
|
1039
1121
|
const rectCenter = pageStart + rectTop + rectHeight / 2;
|
|
1040
1122
|
scrollOffset = rectCenter - viewportHeight / 2;
|
|
1041
1123
|
} else {
|
|
@@ -2108,6 +2190,69 @@ function applyContextRecolor(ctx, map, options) {
|
|
|
2108
2190
|
function removeContextRecolor(ctx) {
|
|
2109
2191
|
ctx[RECOLOR_CLEANUP]?.();
|
|
2110
2192
|
}
|
|
2193
|
+
|
|
2194
|
+
// src/lib/viewport-invalidation.ts
|
|
2195
|
+
var viewportInvalidationRegistry = /* @__PURE__ */ new WeakMap();
|
|
2196
|
+
var notifyCallbacks = (entry) => {
|
|
2197
|
+
entry.rafId = null;
|
|
2198
|
+
entry.callbacks.forEach((callback) => {
|
|
2199
|
+
callback();
|
|
2200
|
+
});
|
|
2201
|
+
};
|
|
2202
|
+
var scheduleInvalidation = (entry) => {
|
|
2203
|
+
if (entry.rafId !== null) {
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
entry.rafId = requestAnimationFrame(() => {
|
|
2207
|
+
notifyCallbacks(entry);
|
|
2208
|
+
});
|
|
2209
|
+
};
|
|
2210
|
+
var subscribeToViewportInvalidation = (viewport, callback) => {
|
|
2211
|
+
let entry = viewportInvalidationRegistry.get(viewport);
|
|
2212
|
+
if (!entry) {
|
|
2213
|
+
const callbacks = /* @__PURE__ */ new Set();
|
|
2214
|
+
const schedule = () => {
|
|
2215
|
+
const currentEntry = viewportInvalidationRegistry.get(viewport);
|
|
2216
|
+
if (!currentEntry) {
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
scheduleInvalidation(currentEntry);
|
|
2220
|
+
};
|
|
2221
|
+
entry = {
|
|
2222
|
+
callbacks,
|
|
2223
|
+
scrollListener: schedule,
|
|
2224
|
+
windowResizeListener: schedule,
|
|
2225
|
+
resizeObserver: typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule),
|
|
2226
|
+
rafId: null
|
|
2227
|
+
};
|
|
2228
|
+
viewport.addEventListener("scroll", entry.scrollListener, {
|
|
2229
|
+
passive: true
|
|
2230
|
+
});
|
|
2231
|
+
window.addEventListener("resize", entry.windowResizeListener, {
|
|
2232
|
+
passive: true
|
|
2233
|
+
});
|
|
2234
|
+
entry.resizeObserver?.observe(viewport);
|
|
2235
|
+
viewportInvalidationRegistry.set(viewport, entry);
|
|
2236
|
+
}
|
|
2237
|
+
entry.callbacks.add(callback);
|
|
2238
|
+
return () => {
|
|
2239
|
+
const currentEntry = viewportInvalidationRegistry.get(viewport);
|
|
2240
|
+
if (!currentEntry) {
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
currentEntry.callbacks.delete(callback);
|
|
2244
|
+
if (currentEntry.callbacks.size > 0) {
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
if (currentEntry.rafId !== null) {
|
|
2248
|
+
cancelAnimationFrame(currentEntry.rafId);
|
|
2249
|
+
}
|
|
2250
|
+
viewport.removeEventListener("scroll", currentEntry.scrollListener);
|
|
2251
|
+
window.removeEventListener("resize", currentEntry.windowResizeListener);
|
|
2252
|
+
currentEntry.resizeObserver?.disconnect();
|
|
2253
|
+
viewportInvalidationRegistry.delete(viewport);
|
|
2254
|
+
};
|
|
2255
|
+
};
|
|
2111
2256
|
var listeners = /* @__PURE__ */ new Set();
|
|
2112
2257
|
var media;
|
|
2113
2258
|
var getSnapshot = () => Math.min(window.devicePixelRatio || 1, 3);
|
|
@@ -2369,7 +2514,53 @@ var useCanvasLayer = ({ background }) => {
|
|
|
2369
2514
|
releaseBuffer();
|
|
2370
2515
|
throw error;
|
|
2371
2516
|
}
|
|
2517
|
+
let continuation = null;
|
|
2518
|
+
let idleTimer = null;
|
|
2519
|
+
let unsubscribeViewport = null;
|
|
2520
|
+
let priorityStopped = false;
|
|
2521
|
+
const clearPriorityWait = () => {
|
|
2522
|
+
if (idleTimer !== null) clearTimeout(idleTimer);
|
|
2523
|
+
idleTimer = null;
|
|
2524
|
+
unsubscribeViewport?.();
|
|
2525
|
+
unsubscribeViewport = null;
|
|
2526
|
+
};
|
|
2527
|
+
const resumeWhenVisible = () => {
|
|
2528
|
+
if (priorityStopped || !continuation) return;
|
|
2529
|
+
const { virtualizer, viewportRef } = store.getState();
|
|
2530
|
+
const item = virtualizer?.getVirtualItems().find((candidate) => candidate.index === pageNumber - 1);
|
|
2531
|
+
const offset3 = virtualizer?.scrollOffset;
|
|
2532
|
+
const height = virtualizer?.scrollRect?.height;
|
|
2533
|
+
if (virtualizer?.isScrolling && item && offset3 != null && height && (item.end < offset3 || item.start > offset3 + height)) {
|
|
2534
|
+
if (!unsubscribeViewport && viewportRef.current) {
|
|
2535
|
+
unsubscribeViewport = subscribeToViewportInvalidation(
|
|
2536
|
+
viewportRef.current,
|
|
2537
|
+
resumeWhenVisible
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
if (idleTimer === null) {
|
|
2541
|
+
idleTimer = setTimeout(() => {
|
|
2542
|
+
idleTimer = null;
|
|
2543
|
+
resumeWhenVisible();
|
|
2544
|
+
}, 160);
|
|
2545
|
+
}
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
clearPriorityWait();
|
|
2549
|
+
const proceed = continuation;
|
|
2550
|
+
continuation = null;
|
|
2551
|
+
proceed();
|
|
2552
|
+
};
|
|
2553
|
+
const stopPriority = () => {
|
|
2554
|
+
priorityStopped = true;
|
|
2555
|
+
continuation = null;
|
|
2556
|
+
clearPriorityWait();
|
|
2557
|
+
};
|
|
2558
|
+
renderingTask.onContinue = (proceed) => {
|
|
2559
|
+
continuation = proceed;
|
|
2560
|
+
resumeWhenVisible();
|
|
2561
|
+
};
|
|
2372
2562
|
renderingTask.promise.then(() => {
|
|
2563
|
+
stopPriority();
|
|
2373
2564
|
if (cancelled) {
|
|
2374
2565
|
restoreRecolor?.();
|
|
2375
2566
|
releaseBuffer();
|
|
@@ -2414,6 +2605,7 @@ var useCanvasLayer = ({ background }) => {
|
|
|
2414
2605
|
releaseBuffer();
|
|
2415
2606
|
}
|
|
2416
2607
|
}).catch((error) => {
|
|
2608
|
+
stopPriority();
|
|
2417
2609
|
restoreRecolor?.();
|
|
2418
2610
|
releaseBuffer();
|
|
2419
2611
|
if (cancelled) return;
|
|
@@ -2423,6 +2615,7 @@ var useCanvasLayer = ({ background }) => {
|
|
|
2423
2615
|
});
|
|
2424
2616
|
return () => {
|
|
2425
2617
|
cancelled = true;
|
|
2618
|
+
stopPriority();
|
|
2426
2619
|
void renderingTask.cancel();
|
|
2427
2620
|
};
|
|
2428
2621
|
}, [
|
|
@@ -2454,71 +2647,6 @@ var useCanvasLayer = ({ background }) => {
|
|
|
2454
2647
|
canvasRef
|
|
2455
2648
|
};
|
|
2456
2649
|
};
|
|
2457
|
-
|
|
2458
|
-
// src/lib/viewport-invalidation.ts
|
|
2459
|
-
var viewportInvalidationRegistry = /* @__PURE__ */ new WeakMap();
|
|
2460
|
-
var notifyCallbacks = (entry) => {
|
|
2461
|
-
entry.rafId = null;
|
|
2462
|
-
entry.callbacks.forEach((callback) => {
|
|
2463
|
-
callback();
|
|
2464
|
-
});
|
|
2465
|
-
};
|
|
2466
|
-
var scheduleInvalidation = (entry) => {
|
|
2467
|
-
if (entry.rafId !== null) {
|
|
2468
|
-
return;
|
|
2469
|
-
}
|
|
2470
|
-
entry.rafId = requestAnimationFrame(() => {
|
|
2471
|
-
notifyCallbacks(entry);
|
|
2472
|
-
});
|
|
2473
|
-
};
|
|
2474
|
-
var subscribeToViewportInvalidation = (viewport, callback) => {
|
|
2475
|
-
let entry = viewportInvalidationRegistry.get(viewport);
|
|
2476
|
-
if (!entry) {
|
|
2477
|
-
const callbacks = /* @__PURE__ */ new Set();
|
|
2478
|
-
const schedule = () => {
|
|
2479
|
-
const currentEntry = viewportInvalidationRegistry.get(viewport);
|
|
2480
|
-
if (!currentEntry) {
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
scheduleInvalidation(currentEntry);
|
|
2484
|
-
};
|
|
2485
|
-
entry = {
|
|
2486
|
-
callbacks,
|
|
2487
|
-
scrollListener: schedule,
|
|
2488
|
-
windowResizeListener: schedule,
|
|
2489
|
-
resizeObserver: typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule),
|
|
2490
|
-
rafId: null
|
|
2491
|
-
};
|
|
2492
|
-
viewport.addEventListener("scroll", entry.scrollListener, {
|
|
2493
|
-
passive: true
|
|
2494
|
-
});
|
|
2495
|
-
window.addEventListener("resize", entry.windowResizeListener, {
|
|
2496
|
-
passive: true
|
|
2497
|
-
});
|
|
2498
|
-
entry.resizeObserver?.observe(viewport);
|
|
2499
|
-
viewportInvalidationRegistry.set(viewport, entry);
|
|
2500
|
-
}
|
|
2501
|
-
entry.callbacks.add(callback);
|
|
2502
|
-
return () => {
|
|
2503
|
-
const currentEntry = viewportInvalidationRegistry.get(viewport);
|
|
2504
|
-
if (!currentEntry) {
|
|
2505
|
-
return;
|
|
2506
|
-
}
|
|
2507
|
-
currentEntry.callbacks.delete(callback);
|
|
2508
|
-
if (currentEntry.callbacks.size > 0) {
|
|
2509
|
-
return;
|
|
2510
|
-
}
|
|
2511
|
-
if (currentEntry.rafId !== null) {
|
|
2512
|
-
cancelAnimationFrame(currentEntry.rafId);
|
|
2513
|
-
}
|
|
2514
|
-
viewport.removeEventListener("scroll", currentEntry.scrollListener);
|
|
2515
|
-
window.removeEventListener("resize", currentEntry.windowResizeListener);
|
|
2516
|
-
currentEntry.resizeObserver?.disconnect();
|
|
2517
|
-
viewportInvalidationRegistry.delete(viewport);
|
|
2518
|
-
};
|
|
2519
|
-
};
|
|
2520
|
-
|
|
2521
|
-
// src/hooks/layers/useDetailCanvasLayer.tsx
|
|
2522
2650
|
var DETAIL_RENDER_IDLE_MS = 80;
|
|
2523
2651
|
var SCROLL_POLL_MS = 160;
|
|
2524
2652
|
var ZOOM_DEBOUNCE_MS = 200;
|
|
@@ -2894,9 +3022,7 @@ var CanvasLayer = memo(
|
|
|
2894
3022
|
);
|
|
2895
3023
|
CanvasLayer.displayName = "CanvasLayer";
|
|
2896
3024
|
|
|
2897
|
-
// src/
|
|
2898
|
-
var MERGE_THRESHOLD = 2;
|
|
2899
|
-
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
3025
|
+
// src/lib/selection-rects.ts
|
|
2900
3026
|
var getTextNodeClientRects = (range) => {
|
|
2901
3027
|
const root = range.commonAncestorContainer;
|
|
2902
3028
|
const ownerDoc = root.ownerDocument ?? document;
|
|
@@ -2948,6 +3074,39 @@ var getTextNodeClientRects = (range) => {
|
|
|
2948
3074
|
}
|
|
2949
3075
|
return results;
|
|
2950
3076
|
};
|
|
3077
|
+
var connected = (a, b) => {
|
|
3078
|
+
const overlap = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top);
|
|
3079
|
+
const gap = Math.max(a.left, b.left) - Math.min(a.left + a.width, b.left + b.width);
|
|
3080
|
+
return overlap >= Math.min(a.height, b.height) / 2 && gap <= Math.min(a.height, b.height) * 0.5;
|
|
3081
|
+
};
|
|
3082
|
+
var mergeTextRuns = (rects) => {
|
|
3083
|
+
const groups = [];
|
|
3084
|
+
for (const rect of rects) {
|
|
3085
|
+
const group = [rect];
|
|
3086
|
+
for (let i = groups.length - 1; i >= 0; i--) {
|
|
3087
|
+
if (groups[i].some((member) => connected(member, rect))) {
|
|
3088
|
+
group.push(...groups[i]);
|
|
3089
|
+
groups.splice(i, 1);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
groups.push(group);
|
|
3093
|
+
}
|
|
3094
|
+
return groups.map((group) => {
|
|
3095
|
+
const left = Math.min(...group.map((rect) => rect.left));
|
|
3096
|
+
const top = Math.min(...group.map((rect) => rect.top));
|
|
3097
|
+
return {
|
|
3098
|
+
...group[0],
|
|
3099
|
+
left,
|
|
3100
|
+
top,
|
|
3101
|
+
width: Math.max(...group.map((rect) => rect.left + rect.width)) - left,
|
|
3102
|
+
height: Math.max(...group.map((rect) => rect.top + rect.height)) - top
|
|
3103
|
+
};
|
|
3104
|
+
});
|
|
3105
|
+
};
|
|
3106
|
+
|
|
3107
|
+
// src/hooks/useSelectionDimensions.tsx
|
|
3108
|
+
var MERGE_THRESHOLD = 2;
|
|
3109
|
+
var LAYER_ATTRIBUTION_TOLERANCE_PX = 4;
|
|
2951
3110
|
var mapSelectionRectsToLayers = (range) => {
|
|
2952
3111
|
const clientRects = getTextNodeClientRects(range).filter(
|
|
2953
3112
|
({ rect }) => rect.width > 2 && rect.height > 2
|
|
@@ -2990,122 +3149,6 @@ var mapSelectionRectsToLayers = (range) => {
|
|
|
2990
3149
|
}
|
|
2991
3150
|
return result;
|
|
2992
3151
|
};
|
|
2993
|
-
var shouldMergeRects = (rect1, rect2) => {
|
|
2994
|
-
const verticalOverlap = !(rect1.top > rect2.top + rect2.height || rect2.top > rect1.top + rect1.height);
|
|
2995
|
-
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;
|
|
2996
|
-
return verticalOverlap && horizontallyConnected;
|
|
2997
|
-
};
|
|
2998
|
-
var consolidateHighlightRects = (rects) => {
|
|
2999
|
-
if (rects.length <= 1) return rects;
|
|
3000
|
-
const consolidated = [];
|
|
3001
|
-
const sorted = [...rects].sort((a, b) => {
|
|
3002
|
-
const pageCompare = a.pageNumber - b.pageNumber;
|
|
3003
|
-
if (pageCompare !== 0) return pageCompare;
|
|
3004
|
-
const topDiff = a.top - b.top;
|
|
3005
|
-
return Math.abs(topDiff) < 2 ? a.left - b.left : topDiff;
|
|
3006
|
-
});
|
|
3007
|
-
let current = sorted[0];
|
|
3008
|
-
if (!current) return rects;
|
|
3009
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
3010
|
-
const next = sorted[i];
|
|
3011
|
-
if (!next) continue;
|
|
3012
|
-
const samePageAndLine = current.pageNumber === next.pageNumber && Math.abs(current.top - next.top) < Math.max(current.height, next.height) * 0.5;
|
|
3013
|
-
const horizontallyConnected = samePageAndLine && // Adjacent (touching or very close)
|
|
3014
|
-
(Math.abs(current.left + current.width - next.left) <= MERGE_THRESHOLD || Math.abs(next.left + next.width - current.left) <= MERGE_THRESHOLD || // Overlapping
|
|
3015
|
-
current.left < next.left + next.width && next.left < current.left + current.width || // Very close (small gap)
|
|
3016
|
-
Math.abs(current.left + current.width - next.left) <= current.height * 0.2);
|
|
3017
|
-
if (horizontallyConnected) {
|
|
3018
|
-
const newLeft = Math.min(current.left, next.left);
|
|
3019
|
-
const newRight = Math.max(
|
|
3020
|
-
current.left + current.width,
|
|
3021
|
-
next.left + next.width
|
|
3022
|
-
);
|
|
3023
|
-
const newTop = Math.min(current.top, next.top);
|
|
3024
|
-
const newBottom = Math.max(
|
|
3025
|
-
current.top + current.height,
|
|
3026
|
-
next.top + next.height
|
|
3027
|
-
);
|
|
3028
|
-
current = {
|
|
3029
|
-
left: newLeft,
|
|
3030
|
-
top: newTop,
|
|
3031
|
-
width: newRight - newLeft,
|
|
3032
|
-
height: newBottom - newTop,
|
|
3033
|
-
pageNumber: current.pageNumber
|
|
3034
|
-
};
|
|
3035
|
-
} else {
|
|
3036
|
-
consolidated.push(current);
|
|
3037
|
-
current = next;
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
if (current) {
|
|
3041
|
-
consolidated.push(current);
|
|
3042
|
-
}
|
|
3043
|
-
return consolidated;
|
|
3044
|
-
};
|
|
3045
|
-
var consolidateRects = (rects) => {
|
|
3046
|
-
if (rects.length <= 1) return rects;
|
|
3047
|
-
const result = [];
|
|
3048
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3049
|
-
for (let i = 0; i < rects.length; i++) {
|
|
3050
|
-
if (visited.has(i)) continue;
|
|
3051
|
-
const currentRect = rects[i];
|
|
3052
|
-
if (!currentRect) continue;
|
|
3053
|
-
const currentGroup = [currentRect];
|
|
3054
|
-
visited.add(i);
|
|
3055
|
-
let foundNew = true;
|
|
3056
|
-
while (foundNew) {
|
|
3057
|
-
foundNew = false;
|
|
3058
|
-
for (let j = 0; j < rects.length; j++) {
|
|
3059
|
-
if (visited.has(j)) continue;
|
|
3060
|
-
const candidateRect = rects[j];
|
|
3061
|
-
if (!candidateRect) continue;
|
|
3062
|
-
const shouldMergeWithGroup = currentGroup.some(
|
|
3063
|
-
(groupRect) => doRectsOverlap(groupRect, candidateRect)
|
|
3064
|
-
);
|
|
3065
|
-
if (shouldMergeWithGroup) {
|
|
3066
|
-
currentGroup.push(candidateRect);
|
|
3067
|
-
visited.add(j);
|
|
3068
|
-
foundNew = true;
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
result.push(mergeRectGroup(currentGroup));
|
|
3073
|
-
}
|
|
3074
|
-
return result;
|
|
3075
|
-
};
|
|
3076
|
-
var doRectsOverlap = (rect1, rect2) => {
|
|
3077
|
-
const horizontalOverlap = rect1.left < rect2.left + rect2.width && rect2.left < rect1.left + rect1.width;
|
|
3078
|
-
const verticalOverlap = rect1.top < rect2.top + rect2.height && rect2.top < rect1.top + rect1.height;
|
|
3079
|
-
const closeEnough = shouldMergeRects(rect1, rect2);
|
|
3080
|
-
return horizontalOverlap && verticalOverlap || closeEnough;
|
|
3081
|
-
};
|
|
3082
|
-
var mergeRectGroup = (rects) => {
|
|
3083
|
-
if (rects.length === 1) {
|
|
3084
|
-
const rect = rects[0];
|
|
3085
|
-
if (!rect) throw new Error("Invalid rect in group");
|
|
3086
|
-
return rect;
|
|
3087
|
-
}
|
|
3088
|
-
const firstRect = rects[0];
|
|
3089
|
-
if (!firstRect) throw new Error("Invalid first rect in group");
|
|
3090
|
-
let minLeft = firstRect.left;
|
|
3091
|
-
let minTop = firstRect.top;
|
|
3092
|
-
let maxRight = firstRect.left + firstRect.width;
|
|
3093
|
-
let maxBottom = firstRect.top + firstRect.height;
|
|
3094
|
-
rects.forEach((rect) => {
|
|
3095
|
-
if (!rect) return;
|
|
3096
|
-
minLeft = Math.min(minLeft, rect.left);
|
|
3097
|
-
minTop = Math.min(minTop, rect.top);
|
|
3098
|
-
maxRight = Math.max(maxRight, rect.left + rect.width);
|
|
3099
|
-
maxBottom = Math.max(maxBottom, rect.top + rect.height);
|
|
3100
|
-
});
|
|
3101
|
-
return {
|
|
3102
|
-
left: minLeft,
|
|
3103
|
-
top: minTop,
|
|
3104
|
-
width: maxRight - minLeft,
|
|
3105
|
-
height: maxBottom - minTop,
|
|
3106
|
-
pageNumber: firstRect.pageNumber
|
|
3107
|
-
};
|
|
3108
|
-
};
|
|
3109
3152
|
var useSelectionDimensions = () => {
|
|
3110
3153
|
const store = PDFStore.useContext();
|
|
3111
3154
|
const getAnnotationDimension = () => {
|
|
@@ -3178,14 +3221,7 @@ var useSelectionDimensions = () => {
|
|
|
3178
3221
|
}
|
|
3179
3222
|
});
|
|
3180
3223
|
textLayerMapHighlight.forEach((rects) => {
|
|
3181
|
-
|
|
3182
|
-
if (rects.length === 1) {
|
|
3183
|
-
highlightRects.push(...rects);
|
|
3184
|
-
} else {
|
|
3185
|
-
const consolidated = consolidateHighlightRects(rects);
|
|
3186
|
-
highlightRects.push(...consolidated);
|
|
3187
|
-
}
|
|
3188
|
-
}
|
|
3224
|
+
highlightRects.push(...mergeTextRuns(rects));
|
|
3189
3225
|
});
|
|
3190
3226
|
textLayerMapUnderline.forEach((rects) => {
|
|
3191
3227
|
if (rects.length > 0) {
|
|
@@ -3332,7 +3368,7 @@ var useSelectionDimensions = () => {
|
|
|
3332
3368
|
});
|
|
3333
3369
|
textLayerMap.forEach((rects) => {
|
|
3334
3370
|
if (rects.length > 0) {
|
|
3335
|
-
const consolidated =
|
|
3371
|
+
const consolidated = mergeTextRuns(rects);
|
|
3336
3372
|
highlights.push(...consolidated);
|
|
3337
3373
|
}
|
|
3338
3374
|
});
|
|
@@ -3777,13 +3813,110 @@ var HighlightLayer = forwardRef(({ asChild, className, style, ...props }, ref) =
|
|
|
3777
3813
|
}) });
|
|
3778
3814
|
});
|
|
3779
3815
|
HighlightLayer.displayName = "HighlightLayer";
|
|
3816
|
+
|
|
3817
|
+
// src/lib/selection-background.ts
|
|
3818
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3819
|
+
var createSelectionBackground = () => {
|
|
3820
|
+
const overlays = /* @__PURE__ */ new Map();
|
|
3821
|
+
let style;
|
|
3822
|
+
const clear = (layer) => {
|
|
3823
|
+
overlays.get(layer)?.remove();
|
|
3824
|
+
overlays.delete(layer);
|
|
3825
|
+
layer.removeAttribute("data-lector-selection-active");
|
|
3826
|
+
if (overlays.size === 0) {
|
|
3827
|
+
style?.remove();
|
|
3828
|
+
style = void 0;
|
|
3829
|
+
}
|
|
3830
|
+
};
|
|
3831
|
+
const update = (selection, layers) => {
|
|
3832
|
+
const registered = new Set(layers);
|
|
3833
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
3834
|
+
if (selection && !selection.isCollapsed) {
|
|
3835
|
+
for (let i = 0; i < selection.rangeCount; i++) {
|
|
3836
|
+
for (const { rect, element } of getTextNodeClientRects(
|
|
3837
|
+
selection.getRangeAt(i)
|
|
3838
|
+
)) {
|
|
3839
|
+
const layer = element?.closest(".textLayer");
|
|
3840
|
+
if (!layer || !element || !registered.has(layer) || rect.width <= 0 || rect.height <= 0)
|
|
3841
|
+
continue;
|
|
3842
|
+
let entry = byLayer.get(layer);
|
|
3843
|
+
if (!entry) {
|
|
3844
|
+
entry = { rects: [], element };
|
|
3845
|
+
byLayer.set(layer, entry);
|
|
3846
|
+
}
|
|
3847
|
+
entry.rects.push(rect);
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
for (const layer of registered) {
|
|
3852
|
+
const entry = byLayer.get(layer);
|
|
3853
|
+
if (!entry) {
|
|
3854
|
+
clear(layer);
|
|
3855
|
+
continue;
|
|
3856
|
+
}
|
|
3857
|
+
layer.removeAttribute("data-lector-selection-active");
|
|
3858
|
+
const color = getComputedStyle(
|
|
3859
|
+
entry.element,
|
|
3860
|
+
"::selection"
|
|
3861
|
+
).backgroundColor;
|
|
3862
|
+
if (!color || color === "transparent" || color === "rgba(0, 0, 0, 0)") {
|
|
3863
|
+
clear(layer);
|
|
3864
|
+
continue;
|
|
3865
|
+
}
|
|
3866
|
+
const bounds = layer.getBoundingClientRect();
|
|
3867
|
+
if (!bounds.width || !bounds.height || !layer.clientWidth || !layer.clientHeight) {
|
|
3868
|
+
clear(layer);
|
|
3869
|
+
continue;
|
|
3870
|
+
}
|
|
3871
|
+
const scaleX = bounds.width / layer.clientWidth;
|
|
3872
|
+
const scaleY = bounds.height / layer.clientHeight;
|
|
3873
|
+
const rects = mergeTextRuns(
|
|
3874
|
+
entry.rects.map((rect) => ({
|
|
3875
|
+
left: (rect.left - bounds.left) / scaleX,
|
|
3876
|
+
top: (rect.top - bounds.top) / scaleY,
|
|
3877
|
+
width: rect.width / scaleX,
|
|
3878
|
+
height: rect.height / scaleY
|
|
3879
|
+
}))
|
|
3880
|
+
);
|
|
3881
|
+
let overlay = overlays.get(layer);
|
|
3882
|
+
if (!overlay) {
|
|
3883
|
+
overlay = document.createElementNS(SVG_NS, "svg");
|
|
3884
|
+
overlay.dataset.lectorSelection = "";
|
|
3885
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
3886
|
+
overlay.style.cssText = "position:absolute;inset:0;width:100%;height:100%;pointer-events:none;user-select:none;transform:none;z-index:0;overflow:visible";
|
|
3887
|
+
overlay.append(document.createElementNS(SVG_NS, "path"));
|
|
3888
|
+
layer.append(overlay);
|
|
3889
|
+
overlays.set(layer, overlay);
|
|
3890
|
+
}
|
|
3891
|
+
const path = overlay.firstElementChild;
|
|
3892
|
+
path.setAttribute(
|
|
3893
|
+
"d",
|
|
3894
|
+
rects.map(
|
|
3895
|
+
(rect) => `M${rect.left} ${rect.top}h${rect.width}v${rect.height}h${-rect.width}Z`
|
|
3896
|
+
).join(" ")
|
|
3897
|
+
);
|
|
3898
|
+
path.setAttribute("fill", color);
|
|
3899
|
+
if (!style) {
|
|
3900
|
+
style = document.createElement("style");
|
|
3901
|
+
style.textContent = ".textLayer[data-lector-selection-active] ::selection { background-color: transparent !important; }";
|
|
3902
|
+
document.head.append(style);
|
|
3903
|
+
}
|
|
3904
|
+
layer.setAttribute("data-lector-selection-active", "");
|
|
3905
|
+
}
|
|
3906
|
+
};
|
|
3907
|
+
return { update, clear };
|
|
3908
|
+
};
|
|
3909
|
+
|
|
3910
|
+
// src/hooks/layers/useTextLayer.tsx
|
|
3780
3911
|
var createTextSelectionManager = () => {
|
|
3781
3912
|
const textLayers = /* @__PURE__ */ new Map();
|
|
3913
|
+
const selectionBackground = createSelectionBackground();
|
|
3782
3914
|
let selectionChangeAbortController = null;
|
|
3783
3915
|
let isPointerDown = false;
|
|
3784
3916
|
let prevRange = null;
|
|
3785
3917
|
let isFirefox;
|
|
3786
3918
|
const removeGlobalSelectionListener = (textLayerDiv) => {
|
|
3919
|
+
selectionBackground.clear(textLayerDiv);
|
|
3787
3920
|
textLayers.delete(textLayerDiv);
|
|
3788
3921
|
if (textLayers.size === 0) {
|
|
3789
3922
|
selectionChangeAbortController?.abort();
|
|
@@ -3840,6 +3973,7 @@ var createTextSelectionManager = () => {
|
|
|
3840
3973
|
"selectionchange",
|
|
3841
3974
|
() => {
|
|
3842
3975
|
const selection = document.getSelection();
|
|
3976
|
+
selectionBackground.update(selection, textLayers.keys());
|
|
3843
3977
|
if (!selection || selection.rangeCount === 0) {
|
|
3844
3978
|
textLayers.forEach(reset);
|
|
3845
3979
|
return;
|
|
@@ -4305,6 +4439,37 @@ var Outline = ({
|
|
|
4305
4439
|
});
|
|
4306
4440
|
}) });
|
|
4307
4441
|
};
|
|
4442
|
+
function PageLoadingBoundary({
|
|
4443
|
+
pageNumber,
|
|
4444
|
+
children,
|
|
4445
|
+
fallback = null
|
|
4446
|
+
}) {
|
|
4447
|
+
const loaded = usePdf((state) => state.isPageLoaded(pageNumber));
|
|
4448
|
+
const loadPage = usePdf((state) => state.loadPdfPageProxy);
|
|
4449
|
+
const [failure, setFailure] = useState(null);
|
|
4450
|
+
const [attempt, setAttempt] = useState(0);
|
|
4451
|
+
useEffect(() => {
|
|
4452
|
+
if (loaded) return;
|
|
4453
|
+
let disposed = false;
|
|
4454
|
+
setFailure(null);
|
|
4455
|
+
void loadPage(pageNumber).catch((error) => {
|
|
4456
|
+
if (!disposed) setFailure({ pageNumber, error });
|
|
4457
|
+
});
|
|
4458
|
+
return () => {
|
|
4459
|
+
disposed = true;
|
|
4460
|
+
};
|
|
4461
|
+
}, [pageNumber, loaded, loadPage, attempt]);
|
|
4462
|
+
if (loaded) return children;
|
|
4463
|
+
if (failure?.pageNumber === pageNumber)
|
|
4464
|
+
return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
|
|
4465
|
+
"Unable to load page ",
|
|
4466
|
+
pageNumber,
|
|
4467
|
+
".",
|
|
4468
|
+
" ",
|
|
4469
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => setAttempt((value) => value + 1), children: "Retry" })
|
|
4470
|
+
] });
|
|
4471
|
+
return fallback;
|
|
4472
|
+
}
|
|
4308
4473
|
var Page = memo(
|
|
4309
4474
|
({
|
|
4310
4475
|
children,
|
|
@@ -4312,14 +4477,16 @@ var Page = memo(
|
|
|
4312
4477
|
style,
|
|
4313
4478
|
...props
|
|
4314
4479
|
}) => {
|
|
4315
|
-
const
|
|
4480
|
+
const loaded = usePdf((state) => state.isPageLoaded(pageNumber));
|
|
4481
|
+
const getPage = usePdf((state) => state.getPdfPageProxy);
|
|
4482
|
+
const proxy = loaded ? getPage(pageNumber) : void 0;
|
|
4316
4483
|
const viewport = usePdf((state) => state.viewports[pageNumber - 1]);
|
|
4317
4484
|
const colorScheme = usePdf((state) => state.colorScheme);
|
|
4318
4485
|
const darkModeColors = usePdf((state) => state.darkModeColors);
|
|
4319
4486
|
const pageBackground = colorScheme === "dark" ? createDarkModeColorMap(darkModeColors)("#ffffff") : "white";
|
|
4320
|
-
const width = viewport?.width ?? (
|
|
4321
|
-
const height = viewport?.height ?? (
|
|
4322
|
-
return /* @__PURE__ */ jsx(PDFPageNumberContext.Provider, { value:
|
|
4487
|
+
const width = viewport?.width ?? (proxy?.view[2] ?? 0) - (proxy?.view[0] ?? 0);
|
|
4488
|
+
const height = viewport?.height ?? (proxy?.view[3] ?? 0) - (proxy?.view[1] ?? 0);
|
|
4489
|
+
return /* @__PURE__ */ jsx(PDFPageNumberContext.Provider, { value: pageNumber, children: /* @__PURE__ */ jsx(
|
|
4323
4490
|
Primitive.div,
|
|
4324
4491
|
{
|
|
4325
4492
|
style: {
|
|
@@ -4337,8 +4504,9 @@ var Page = memo(
|
|
|
4337
4504
|
height,
|
|
4338
4505
|
...style
|
|
4339
4506
|
},
|
|
4507
|
+
"aria-busy": !loaded || void 0,
|
|
4340
4508
|
...props,
|
|
4341
|
-
children
|
|
4509
|
+
children: /* @__PURE__ */ jsx(PageLoadingBoundary, { pageNumber, children })
|
|
4342
4510
|
}
|
|
4343
4511
|
)
|
|
4344
4512
|
}
|
|
@@ -4459,6 +4627,28 @@ var addEventListenerOptions = {
|
|
|
4459
4627
|
};
|
|
4460
4628
|
var useObserveElement = () => {
|
|
4461
4629
|
const store = PDFStore.useContext();
|
|
4630
|
+
const observeElementRect$1 = (instance, cb) => {
|
|
4631
|
+
let physicalRect;
|
|
4632
|
+
const publish = () => {
|
|
4633
|
+
if (!physicalRect) return;
|
|
4634
|
+
const zoom = store.getState().zoom;
|
|
4635
|
+
cb({
|
|
4636
|
+
width: physicalRect.width / zoom,
|
|
4637
|
+
height: physicalRect.height / zoom
|
|
4638
|
+
});
|
|
4639
|
+
};
|
|
4640
|
+
const stopObserving = observeElementRect(instance, (rect) => {
|
|
4641
|
+
physicalRect = rect;
|
|
4642
|
+
publish();
|
|
4643
|
+
});
|
|
4644
|
+
const unsubscribe = store.subscribe((state, previous) => {
|
|
4645
|
+
if (state.zoom !== previous.zoom) publish();
|
|
4646
|
+
});
|
|
4647
|
+
return () => {
|
|
4648
|
+
stopObserving?.();
|
|
4649
|
+
unsubscribe();
|
|
4650
|
+
};
|
|
4651
|
+
};
|
|
4462
4652
|
const observeElementOffset = (instance, cb) => {
|
|
4463
4653
|
const element = instance.scrollElement;
|
|
4464
4654
|
if (!element) {
|
|
@@ -4468,33 +4658,46 @@ var useObserveElement = () => {
|
|
|
4468
4658
|
if (!targetWindow) {
|
|
4469
4659
|
return;
|
|
4470
4660
|
}
|
|
4471
|
-
let
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
},
|
|
4477
|
-
instance.options.isScrollingResetDelay
|
|
4478
|
-
);
|
|
4479
|
-
const createHandler = (isScrolling) => () => {
|
|
4661
|
+
let isScrolling = false;
|
|
4662
|
+
let idleTimer;
|
|
4663
|
+
const needsIdleFallback = !instance.options.useScrollendEvent || !supportsScrollend;
|
|
4664
|
+
const publish = (scrolling) => {
|
|
4665
|
+
isScrolling = scrolling;
|
|
4480
4666
|
const { horizontal, isRtl } = instance.options;
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4667
|
+
const physicalOffset = horizontal ? element.scrollLeft * (isRtl ? -1 : 1) : element.scrollTop;
|
|
4668
|
+
cb(physicalOffset / store.getState().zoom, isScrolling);
|
|
4669
|
+
};
|
|
4670
|
+
const clearIdleTimer = () => {
|
|
4671
|
+
if (idleTimer !== void 0) targetWindow.clearTimeout(idleTimer);
|
|
4672
|
+
idleTimer = void 0;
|
|
4673
|
+
};
|
|
4674
|
+
const createHandler = (scrolling) => () => {
|
|
4675
|
+
clearIdleTimer();
|
|
4676
|
+
publish(scrolling);
|
|
4677
|
+
if (scrolling && needsIdleFallback) {
|
|
4678
|
+
idleTimer = targetWindow.setTimeout(() => {
|
|
4679
|
+
idleTimer = void 0;
|
|
4680
|
+
publish(false);
|
|
4681
|
+
}, instance.options.isScrollingResetDelay);
|
|
4682
|
+
}
|
|
4486
4683
|
};
|
|
4487
4684
|
const handler = createHandler(true);
|
|
4488
4685
|
const endHandler = createHandler(false);
|
|
4489
4686
|
endHandler();
|
|
4687
|
+
const unsubscribe = store.subscribe((state, previous) => {
|
|
4688
|
+
if (state.zoom !== previous.zoom) publish(isScrolling);
|
|
4689
|
+
});
|
|
4490
4690
|
element.addEventListener("scroll", handler, addEventListenerOptions);
|
|
4491
4691
|
element.addEventListener("scrollend", endHandler, addEventListenerOptions);
|
|
4492
4692
|
return () => {
|
|
4693
|
+
unsubscribe();
|
|
4694
|
+
clearIdleTimer();
|
|
4493
4695
|
element.removeEventListener("scroll", handler);
|
|
4494
4696
|
element.removeEventListener("scrollend", endHandler);
|
|
4495
4697
|
};
|
|
4496
4698
|
};
|
|
4497
4699
|
return {
|
|
4700
|
+
observeElementRect: observeElementRect$1,
|
|
4498
4701
|
observeElementOffset
|
|
4499
4702
|
};
|
|
4500
4703
|
};
|
|
@@ -4504,7 +4707,11 @@ var useScrollFn = () => {
|
|
|
4504
4707
|
(_offset, canSmooth, instance) => {
|
|
4505
4708
|
const zoom = store.getState().zoom;
|
|
4506
4709
|
const offset3 = _offset * zoom;
|
|
4507
|
-
elementScroll(
|
|
4710
|
+
elementScroll(
|
|
4711
|
+
offset3,
|
|
4712
|
+
{ ...canSmooth, adjustments: (canSmooth.adjustments ?? 0) * zoom },
|
|
4713
|
+
instance
|
|
4714
|
+
);
|
|
4508
4715
|
},
|
|
4509
4716
|
[store]
|
|
4510
4717
|
);
|
|
@@ -4622,7 +4829,7 @@ function _toPrimitive(input, hint) {
|
|
|
4622
4829
|
if (typeof input !== "object" || input === null) return input;
|
|
4623
4830
|
var prim = input[Symbol.toPrimitive];
|
|
4624
4831
|
if (prim !== void 0) {
|
|
4625
|
-
var res = prim.call(input, hint
|
|
4832
|
+
var res = prim.call(input, hint);
|
|
4626
4833
|
if (typeof res !== "object") return res;
|
|
4627
4834
|
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
4628
4835
|
}
|
|
@@ -6479,6 +6686,8 @@ var useViewportContainer = ({
|
|
|
6479
6686
|
});
|
|
6480
6687
|
const zoomRafRef = useRef(null);
|
|
6481
6688
|
const lastPushedZoomRef = useRef(null);
|
|
6689
|
+
const initializedZoomRef = useRef(false);
|
|
6690
|
+
const appliedZoomRef = useRef(1);
|
|
6482
6691
|
const updateTransform = useCallback(
|
|
6483
6692
|
(zoomUpdate) => {
|
|
6484
6693
|
if (!elementRef.current || !containerRef.current || !elementWrapperRef.current) {
|
|
@@ -6508,17 +6717,36 @@ var useViewportContainer = ({
|
|
|
6508
6717
|
elementWrapperRef.current.style.height = `${naturalHeight * zoom2}px`;
|
|
6509
6718
|
containerRef.current.scrollTop = translateY;
|
|
6510
6719
|
containerRef.current.scrollLeft = translateX;
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
lastPushedZoomRef.current = z;
|
|
6516
|
-
updateZoom(() => z);
|
|
6517
|
-
});
|
|
6720
|
+
appliedZoomRef.current = zoom2;
|
|
6721
|
+
if (zoomUpdate) {
|
|
6722
|
+
lastPushedZoomRef.current = zoom2;
|
|
6723
|
+
updateZoom(() => zoom2);
|
|
6518
6724
|
}
|
|
6519
6725
|
},
|
|
6520
6726
|
[containerRef, elementRef, elementWrapperRef, updateZoom, store]
|
|
6521
6727
|
);
|
|
6728
|
+
useLayoutEffect(() => {
|
|
6729
|
+
const viewport = containerRef.current;
|
|
6730
|
+
if (!viewport) return;
|
|
6731
|
+
return registerViewportZoom(viewport, (nextZoom) => {
|
|
6732
|
+
if (initializedZoomRef.current && appliedZoomRef.current === nextZoom && zoomRafRef.current === null)
|
|
6733
|
+
return;
|
|
6734
|
+
if (zoomRafRef.current !== null) {
|
|
6735
|
+
cancelAnimationFrame(zoomRafRef.current);
|
|
6736
|
+
zoomRafRef.current = null;
|
|
6737
|
+
}
|
|
6738
|
+
lastPushedZoomRef.current = null;
|
|
6739
|
+
const previousZoom = initializedZoomRef.current ? appliedZoomRef.current : store.getState().zoom;
|
|
6740
|
+
const ratio = previousZoom > 0 && Number.isFinite(previousZoom) ? nextZoom / previousZoom : 1;
|
|
6741
|
+
transformations.current = {
|
|
6742
|
+
zoom: nextZoom,
|
|
6743
|
+
translateX: viewport.scrollLeft * ratio,
|
|
6744
|
+
translateY: viewport.scrollTop * ratio
|
|
6745
|
+
};
|
|
6746
|
+
initializedZoomRef.current = true;
|
|
6747
|
+
updateTransform();
|
|
6748
|
+
});
|
|
6749
|
+
}, [containerRef, store, updateTransform]);
|
|
6522
6750
|
useEffect(() => {
|
|
6523
6751
|
if (USE_LAYOUT_ZOOM && gestureTransformAppliedRef.current && !isPinching)
|
|
6524
6752
|
updateTransform();
|
|
@@ -6531,6 +6759,16 @@ var useViewportContainer = ({
|
|
|
6531
6759
|
};
|
|
6532
6760
|
}, []);
|
|
6533
6761
|
useEffect(() => {
|
|
6762
|
+
if (!initializedZoomRef.current && containerRef.current) {
|
|
6763
|
+
initializedZoomRef.current = true;
|
|
6764
|
+
transformations.current = {
|
|
6765
|
+
zoom,
|
|
6766
|
+
translateX: containerRef.current.scrollLeft,
|
|
6767
|
+
translateY: containerRef.current.scrollTop
|
|
6768
|
+
};
|
|
6769
|
+
updateTransform();
|
|
6770
|
+
return;
|
|
6771
|
+
}
|
|
6534
6772
|
if (transformations.current.zoom === zoom || !containerRef.current) {
|
|
6535
6773
|
return;
|
|
6536
6774
|
}
|
|
@@ -6543,7 +6781,7 @@ var useViewportContainer = ({
|
|
|
6543
6781
|
zoomRafRef.current = null;
|
|
6544
6782
|
}
|
|
6545
6783
|
lastPushedZoomRef.current = null;
|
|
6546
|
-
const prevZoom =
|
|
6784
|
+
const prevZoom = appliedZoomRef.current;
|
|
6547
6785
|
if (!prevZoom || !Number.isFinite(prevZoom)) {
|
|
6548
6786
|
transformations.current = {
|
|
6549
6787
|
translateX: containerRef.current.scrollLeft,
|
|
@@ -6683,11 +6921,26 @@ var useViewportContainer = ({
|
|
|
6683
6921
|
translateY: newTranslateY
|
|
6684
6922
|
};
|
|
6685
6923
|
newMemo.lastZoom = newZoom;
|
|
6686
|
-
|
|
6924
|
+
if (zoomRafRef.current === null) {
|
|
6925
|
+
zoomRafRef.current = requestAnimationFrame(() => {
|
|
6926
|
+
zoomRafRef.current = null;
|
|
6927
|
+
updateTransform(true);
|
|
6928
|
+
});
|
|
6929
|
+
}
|
|
6687
6930
|
return newMemo;
|
|
6688
6931
|
},
|
|
6689
6932
|
onPinchStart: () => setIsPinching(true),
|
|
6690
|
-
onPinchEnd: () =>
|
|
6933
|
+
onPinchEnd: () => {
|
|
6934
|
+
const pendingFrame = zoomRafRef.current !== null;
|
|
6935
|
+
if (zoomRafRef.current !== null) {
|
|
6936
|
+
cancelAnimationFrame(zoomRafRef.current);
|
|
6937
|
+
zoomRafRef.current = null;
|
|
6938
|
+
}
|
|
6939
|
+
setIsPinching(false);
|
|
6940
|
+
if (pendingFrame || gestureTransformAppliedRef.current) {
|
|
6941
|
+
updateTransform(pendingFrame);
|
|
6942
|
+
}
|
|
6943
|
+
}
|
|
6691
6944
|
},
|
|
6692
6945
|
{
|
|
6693
6946
|
target: containerRef
|
|
@@ -6740,6 +6993,7 @@ var Pages = ({
|
|
|
6740
6993
|
const [tempItems, setTempItems] = useState([]);
|
|
6741
6994
|
const viewports = usePdf((state) => state.viewports);
|
|
6742
6995
|
const numPages = usePdf((state) => state.pdfDocumentProxy.numPages);
|
|
6996
|
+
const initialPage = usePdf((state) => state.initialPage);
|
|
6743
6997
|
const isPinching = usePdf((state) => state.isPinching);
|
|
6744
6998
|
const elementWrapperRef = useRef(null);
|
|
6745
6999
|
const elementRef = useRef(null);
|
|
@@ -6751,7 +7005,7 @@ var Pages = ({
|
|
|
6751
7005
|
});
|
|
6752
7006
|
const setVirtualizer = usePdf((state) => state.setVirtualizer);
|
|
6753
7007
|
const { scrollToFn } = useScrollFn();
|
|
6754
|
-
const { observeElementOffset } = useObserveElement();
|
|
7008
|
+
const { observeElementOffset, observeElementRect } = useObserveElement();
|
|
6755
7009
|
const viewportsRef = useRef(viewports);
|
|
6756
7010
|
viewportsRef.current = viewports;
|
|
6757
7011
|
const estimateSize = useCallback(
|
|
@@ -6763,15 +7017,22 @@ var Pages = ({
|
|
|
6763
7017
|
[]
|
|
6764
7018
|
// Stable — reads from ref
|
|
6765
7019
|
);
|
|
7020
|
+
const [startingOffset] = useState(
|
|
7021
|
+
() => initialOffset ?? viewports.slice(0, initialPage - 1).reduce(
|
|
7022
|
+
(offset3, viewport) => offset3 + viewport.height + EXTRA_HEIGHT + gap,
|
|
7023
|
+
0
|
|
7024
|
+
)
|
|
7025
|
+
);
|
|
6766
7026
|
const virtualizer = useVirtualizer({
|
|
6767
7027
|
count: numPages || 0,
|
|
6768
7028
|
getScrollElement: () => containerRef.current,
|
|
6769
7029
|
estimateSize,
|
|
6770
7030
|
observeElementOffset,
|
|
7031
|
+
observeElementRect,
|
|
6771
7032
|
overscan: virtualizerOptions?.overscan ?? 0,
|
|
6772
7033
|
scrollToFn,
|
|
6773
7034
|
gap,
|
|
6774
|
-
initialOffset,
|
|
7035
|
+
initialOffset: startingOffset,
|
|
6775
7036
|
// Never trust scrollend alone to reset isScrolling: a single missed
|
|
6776
7037
|
// event (a documented browser flake — TanStack flipped this default to
|
|
6777
7038
|
// false in later 3.x) would pin isScrolling=true forever, and every
|
|
@@ -6779,6 +7040,21 @@ var Pages = ({
|
|
|
6779
7040
|
// false keeps the isScrollingResetDelay debounce active as a fallback.
|
|
6780
7041
|
useScrollendEvent: false
|
|
6781
7042
|
});
|
|
7043
|
+
const previousViewports = useRef(viewports);
|
|
7044
|
+
useLayoutEffect(() => {
|
|
7045
|
+
if (isPinching) return;
|
|
7046
|
+
const previous = previousViewports.current;
|
|
7047
|
+
previousViewports.current = viewports;
|
|
7048
|
+
if (previous === viewports) return;
|
|
7049
|
+
let resized = false;
|
|
7050
|
+
for (let index = 0; index < viewports.length; index++) {
|
|
7051
|
+
if (previous[index]?.height !== viewports[index]?.height) {
|
|
7052
|
+
virtualizer.resizeItem(index, viewports[index].height + EXTRA_HEIGHT);
|
|
7053
|
+
resized = true;
|
|
7054
|
+
}
|
|
7055
|
+
}
|
|
7056
|
+
if (resized) setTempItems([]);
|
|
7057
|
+
}, [viewports, virtualizer, isPinching]);
|
|
6782
7058
|
useEffect(() => {
|
|
6783
7059
|
if (onOffsetChange && virtualizer.scrollOffset)
|
|
6784
7060
|
onOffsetChange(virtualizer.scrollOffset);
|
|
@@ -6925,6 +7201,164 @@ async function mapConcurrent(items, concurrency, map, isCancelled = () => false)
|
|
|
6925
7201
|
return results;
|
|
6926
7202
|
}
|
|
6927
7203
|
|
|
7204
|
+
// src/lib/page-resources.ts
|
|
7205
|
+
var PageResources = class {
|
|
7206
|
+
constructor(document2, firstPage, rotation, onError, concurrency = 16) {
|
|
7207
|
+
this.document = document2;
|
|
7208
|
+
this.rotation = rotation;
|
|
7209
|
+
this.onError = onError;
|
|
7210
|
+
this.concurrency = concurrency;
|
|
7211
|
+
this.pages.set(firstPage.pageNumber, firstPage);
|
|
7212
|
+
const viewport = this.viewportFor(firstPage);
|
|
7213
|
+
this.viewports = Array.from({ length: document2.numPages }, () => viewport);
|
|
7214
|
+
if (document2.numPages === 1) this.completePages = [firstPage];
|
|
7215
|
+
}
|
|
7216
|
+
pages = /* @__PURE__ */ new Map();
|
|
7217
|
+
requests = /* @__PURE__ */ new Map();
|
|
7218
|
+
queue = [];
|
|
7219
|
+
active = 0;
|
|
7220
|
+
disposed = false;
|
|
7221
|
+
listeners = /* @__PURE__ */ new Set();
|
|
7222
|
+
notification;
|
|
7223
|
+
viewports;
|
|
7224
|
+
completePages;
|
|
7225
|
+
viewportUpdates = /* @__PURE__ */ new Map();
|
|
7226
|
+
viewportFor(page) {
|
|
7227
|
+
return page.getViewport({
|
|
7228
|
+
scale: 1,
|
|
7229
|
+
rotation: this.rotation + (page.rotate || 0)
|
|
7230
|
+
});
|
|
7231
|
+
}
|
|
7232
|
+
get(pageNumber) {
|
|
7233
|
+
return this.pages.get(pageNumber);
|
|
7234
|
+
}
|
|
7235
|
+
getSnapshot = () => ({
|
|
7236
|
+
viewports: this.viewports,
|
|
7237
|
+
pageProxies: this.completePages
|
|
7238
|
+
});
|
|
7239
|
+
subscribe = (listener) => {
|
|
7240
|
+
this.listeners.add(listener);
|
|
7241
|
+
return () => {
|
|
7242
|
+
this.listeners.delete(listener);
|
|
7243
|
+
};
|
|
7244
|
+
};
|
|
7245
|
+
/** Visible pages move ahead of the background queue; in-flight requests are shared. */
|
|
7246
|
+
load = (pageNumber) => {
|
|
7247
|
+
if (this.disposed)
|
|
7248
|
+
return Promise.reject(new Error("Document page loading was cancelled"));
|
|
7249
|
+
if (!Number.isInteger(pageNumber) || pageNumber < 1 || pageNumber > this.document.numPages)
|
|
7250
|
+
return Promise.reject(
|
|
7251
|
+
new RangeError(`Page ${pageNumber} does not exist`)
|
|
7252
|
+
);
|
|
7253
|
+
const page = this.pages.get(pageNumber);
|
|
7254
|
+
if (page) return Promise.resolve(page);
|
|
7255
|
+
const existing = this.requests.get(pageNumber);
|
|
7256
|
+
if (existing) {
|
|
7257
|
+
const queued = this.queue.indexOf(pageNumber);
|
|
7258
|
+
if (queued > 0) {
|
|
7259
|
+
this.queue.splice(queued, 1);
|
|
7260
|
+
this.queue.unshift(pageNumber);
|
|
7261
|
+
}
|
|
7262
|
+
return existing.promise;
|
|
7263
|
+
}
|
|
7264
|
+
const request = this.createRequest(pageNumber);
|
|
7265
|
+
this.queue.unshift(pageNumber);
|
|
7266
|
+
this.pump();
|
|
7267
|
+
return request.promise;
|
|
7268
|
+
};
|
|
7269
|
+
createRequest(pageNumber) {
|
|
7270
|
+
let resolve;
|
|
7271
|
+
let reject;
|
|
7272
|
+
const promise = new Promise((res, rej) => {
|
|
7273
|
+
resolve = res;
|
|
7274
|
+
reject = rej;
|
|
7275
|
+
});
|
|
7276
|
+
const request = { promise, resolve, reject };
|
|
7277
|
+
this.requests.set(pageNumber, request);
|
|
7278
|
+
return request;
|
|
7279
|
+
}
|
|
7280
|
+
/** Start after the reader mounts, so unrelated page metadata cannot delay its first render. */
|
|
7281
|
+
start = () => {
|
|
7282
|
+
if (this.disposed) return;
|
|
7283
|
+
for (let pageNumber = 1; pageNumber <= this.document.numPages; pageNumber++) {
|
|
7284
|
+
if (this.pages.has(pageNumber) || this.requests.has(pageNumber)) continue;
|
|
7285
|
+
const request = this.createRequest(pageNumber);
|
|
7286
|
+
void request.promise.catch(() => {
|
|
7287
|
+
});
|
|
7288
|
+
this.queue.push(pageNumber);
|
|
7289
|
+
}
|
|
7290
|
+
this.pump();
|
|
7291
|
+
};
|
|
7292
|
+
loadAll = async () => {
|
|
7293
|
+
if (this.disposed) throw new Error("Document page loading was cancelled");
|
|
7294
|
+
if (this.completePages) return this.completePages;
|
|
7295
|
+
this.start();
|
|
7296
|
+
await Promise.all(
|
|
7297
|
+
Array.from({ length: this.document.numPages }, (_, index) => {
|
|
7298
|
+
const pageNumber = index + 1;
|
|
7299
|
+
return this.pages.get(pageNumber) ?? this.requests.get(pageNumber).promise;
|
|
7300
|
+
})
|
|
7301
|
+
);
|
|
7302
|
+
return this.completePages;
|
|
7303
|
+
};
|
|
7304
|
+
pump() {
|
|
7305
|
+
while (!this.disposed && this.active < this.concurrency && this.queue.length) {
|
|
7306
|
+
const pageNumber = this.queue.shift();
|
|
7307
|
+
const request = this.requests.get(pageNumber);
|
|
7308
|
+
this.active++;
|
|
7309
|
+
void Promise.resolve().then(() => {
|
|
7310
|
+
if (this.disposed)
|
|
7311
|
+
throw new Error("Document page loading was cancelled");
|
|
7312
|
+
return this.document.getPage(pageNumber);
|
|
7313
|
+
}).then((page) => {
|
|
7314
|
+
if (this.disposed) return;
|
|
7315
|
+
const viewport = this.viewportFor(page);
|
|
7316
|
+
this.pages.set(pageNumber, page);
|
|
7317
|
+
this.viewportUpdates.set(pageNumber - 1, viewport);
|
|
7318
|
+
if (this.pages.size === this.document.numPages) {
|
|
7319
|
+
this.completePages = Array.from(
|
|
7320
|
+
{ length: this.document.numPages },
|
|
7321
|
+
(_, index) => this.pages.get(index + 1)
|
|
7322
|
+
);
|
|
7323
|
+
}
|
|
7324
|
+
this.requests.delete(pageNumber);
|
|
7325
|
+
request.resolve(page);
|
|
7326
|
+
this.notify();
|
|
7327
|
+
}).catch((error) => {
|
|
7328
|
+
if (!this.disposed) {
|
|
7329
|
+
this.requests.delete(pageNumber);
|
|
7330
|
+
request.reject(error);
|
|
7331
|
+
this.onError(error);
|
|
7332
|
+
}
|
|
7333
|
+
}).finally(() => {
|
|
7334
|
+
this.active--;
|
|
7335
|
+
this.pump();
|
|
7336
|
+
});
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
7339
|
+
notify() {
|
|
7340
|
+
if (this.notification !== void 0) return;
|
|
7341
|
+
this.notification = setTimeout(() => {
|
|
7342
|
+
this.notification = void 0;
|
|
7343
|
+
if (this.disposed) return;
|
|
7344
|
+
this.viewports = this.viewports.slice();
|
|
7345
|
+
for (const [index, viewport] of this.viewportUpdates)
|
|
7346
|
+
this.viewports[index] = viewport;
|
|
7347
|
+
this.viewportUpdates.clear();
|
|
7348
|
+
for (const listener of this.listeners) listener();
|
|
7349
|
+
}, 0);
|
|
7350
|
+
}
|
|
7351
|
+
dispose = () => {
|
|
7352
|
+
this.disposed = true;
|
|
7353
|
+
clearTimeout(this.notification);
|
|
7354
|
+
this.listeners.clear();
|
|
7355
|
+
this.queue = [];
|
|
7356
|
+
for (const request of this.requests.values())
|
|
7357
|
+
request.reject(new Error("Document page loading was cancelled"));
|
|
7358
|
+
this.requests.clear();
|
|
7359
|
+
};
|
|
7360
|
+
};
|
|
7361
|
+
|
|
6928
7362
|
// src/lib/pdfjs.ts
|
|
6929
7363
|
var pdfJsPromise = null;
|
|
6930
7364
|
var loadPdfJs = () => {
|
|
@@ -7050,6 +7484,8 @@ var usePDFDocumentContext = ({
|
|
|
7050
7484
|
onError,
|
|
7051
7485
|
source,
|
|
7052
7486
|
initialRotation = 0,
|
|
7487
|
+
progressive = false,
|
|
7488
|
+
initialPage = 1,
|
|
7053
7489
|
isZoomFitWidth,
|
|
7054
7490
|
zoom = 1,
|
|
7055
7491
|
zoomOptions,
|
|
@@ -7072,7 +7508,32 @@ var usePDFDocumentContext = ({
|
|
|
7072
7508
|
darkModeColorsRef.current = darkModeColors;
|
|
7073
7509
|
useEffect(() => {
|
|
7074
7510
|
let isDisposed = false;
|
|
7511
|
+
let pageResources;
|
|
7075
7512
|
const generateViewports = async (pdf) => {
|
|
7513
|
+
const firstPageNumber = Number.isFinite(initialPage) ? Math.max(1, Math.min(pdf.numPages, Math.trunc(initialPage))) : 1;
|
|
7514
|
+
if (progressive) {
|
|
7515
|
+
const firstPage = await pdf.getPage(firstPageNumber);
|
|
7516
|
+
if (isDisposed) return;
|
|
7517
|
+
pageResources = new PageResources(pdf, firstPage, rotation, (error) => {
|
|
7518
|
+
if (isDisposed) return;
|
|
7519
|
+
console.error("Error generating PDF viewports", error);
|
|
7520
|
+
onErrorRef.current?.({ error, phase: "viewport-generation", source });
|
|
7521
|
+
});
|
|
7522
|
+
setInitialState({
|
|
7523
|
+
isZoomFitWidth,
|
|
7524
|
+
viewports: pageResources.getSnapshot().viewports,
|
|
7525
|
+
pageProxies: pageResources.getSnapshot().pageProxies ?? [],
|
|
7526
|
+
pdfDocumentProxy: pdf,
|
|
7527
|
+
zoom,
|
|
7528
|
+
zoomOptions,
|
|
7529
|
+
pageResources,
|
|
7530
|
+
initialPage: firstPageNumber,
|
|
7531
|
+
colorScheme: colorSchemeRef.current,
|
|
7532
|
+
darkModeColors: darkModeColorsRef.current,
|
|
7533
|
+
renderColorMapRef
|
|
7534
|
+
});
|
|
7535
|
+
return;
|
|
7536
|
+
}
|
|
7076
7537
|
const pageProxies = [];
|
|
7077
7538
|
const viewports = await mapConcurrent(
|
|
7078
7539
|
Array.from({ length: pdf.numPages }, (_, index) => index),
|
|
@@ -7098,6 +7559,7 @@ var usePDFDocumentContext = ({
|
|
|
7098
7559
|
pdfDocumentProxy: pdf,
|
|
7099
7560
|
zoom,
|
|
7100
7561
|
zoomOptions,
|
|
7562
|
+
initialPage: firstPageNumber,
|
|
7101
7563
|
colorScheme: colorSchemeRef.current,
|
|
7102
7564
|
darkModeColors: darkModeColorsRef.current,
|
|
7103
7565
|
renderColorMapRef
|
|
@@ -7160,6 +7622,7 @@ var usePDFDocumentContext = ({
|
|
|
7160
7622
|
});
|
|
7161
7623
|
return () => {
|
|
7162
7624
|
isDisposed = true;
|
|
7625
|
+
pageResources?.dispose();
|
|
7163
7626
|
void loadingTask?.destroy();
|
|
7164
7627
|
};
|
|
7165
7628
|
};
|
|
@@ -7201,6 +7664,28 @@ var ColorSchemeSync = ({
|
|
|
7201
7664
|
]);
|
|
7202
7665
|
return null;
|
|
7203
7666
|
};
|
|
7667
|
+
var PageResourcesSync = ({ resources }) => {
|
|
7668
|
+
const store = PDFStore.useContext();
|
|
7669
|
+
useEffect(() => {
|
|
7670
|
+
if (!resources) return;
|
|
7671
|
+
const update = () => {
|
|
7672
|
+
const snapshot = resources.getSnapshot();
|
|
7673
|
+
store.setState((state) => ({
|
|
7674
|
+
viewports: snapshot.viewports,
|
|
7675
|
+
pageProxies: snapshot.pageProxies ?? state.pageProxies,
|
|
7676
|
+
pagesLoaded: Boolean(snapshot.pageProxies)
|
|
7677
|
+
}));
|
|
7678
|
+
};
|
|
7679
|
+
const unsubscribe = resources.subscribe(update);
|
|
7680
|
+
update();
|
|
7681
|
+
const timer = setTimeout(resources.start, 0);
|
|
7682
|
+
return () => {
|
|
7683
|
+
clearTimeout(timer);
|
|
7684
|
+
unsubscribe();
|
|
7685
|
+
};
|
|
7686
|
+
}, [resources, store]);
|
|
7687
|
+
return null;
|
|
7688
|
+
};
|
|
7204
7689
|
var Root = forwardRef(
|
|
7205
7690
|
({
|
|
7206
7691
|
children,
|
|
@@ -7212,6 +7697,8 @@ var Root = forwardRef(
|
|
|
7212
7697
|
zoom,
|
|
7213
7698
|
zoomOptions,
|
|
7214
7699
|
documentOptions,
|
|
7700
|
+
progressive,
|
|
7701
|
+
initialPage,
|
|
7215
7702
|
colorScheme,
|
|
7216
7703
|
darkModeColors,
|
|
7217
7704
|
...props
|
|
@@ -7224,6 +7711,8 @@ var Root = forwardRef(
|
|
|
7224
7711
|
zoom,
|
|
7225
7712
|
zoomOptions,
|
|
7226
7713
|
documentOptions,
|
|
7714
|
+
progressive,
|
|
7715
|
+
initialPage,
|
|
7227
7716
|
colorScheme,
|
|
7228
7717
|
darkModeColors
|
|
7229
7718
|
});
|
|
@@ -7239,6 +7728,7 @@ var Root = forwardRef(
|
|
|
7239
7728
|
};
|
|
7240
7729
|
}, [documentId]);
|
|
7241
7730
|
return /* @__PURE__ */ jsx(Primitive.div, { ref, ...props, children: initialState ? /* @__PURE__ */ jsxs(PDFStore.Provider, { initialValue: initialState, children: [
|
|
7731
|
+
/* @__PURE__ */ jsx(PageResourcesSync, { resources: initialState.pageResources }),
|
|
7242
7732
|
/* @__PURE__ */ jsx(
|
|
7243
7733
|
ColorSchemeSync,
|
|
7244
7734
|
{
|
|
@@ -7333,7 +7823,10 @@ var Search = ({
|
|
|
7333
7823
|
}) => {
|
|
7334
7824
|
const [result, setResult] = useState(null);
|
|
7335
7825
|
const [attempt, setAttempt] = useState(0);
|
|
7826
|
+
const document2 = usePdf((state) => state.pdfDocumentProxy);
|
|
7336
7827
|
const proxies = usePdf((state) => state.pageProxies);
|
|
7828
|
+
const pagesLoaded = usePdf((state) => state.pagesLoaded);
|
|
7829
|
+
const loadPages = usePdf((state) => state.loadPdfPageProxies);
|
|
7337
7830
|
const setTextContent = usePdf((state) => state.setTextContent);
|
|
7338
7831
|
const retry = useCallback(() => {
|
|
7339
7832
|
setResult(null);
|
|
@@ -7342,22 +7835,27 @@ var Search = ({
|
|
|
7342
7835
|
useEffect(() => {
|
|
7343
7836
|
let disposed = false;
|
|
7344
7837
|
setResult(null);
|
|
7345
|
-
|
|
7346
|
-
void
|
|
7347
|
-
if (disposed) return;
|
|
7838
|
+
let task;
|
|
7839
|
+
void loadPages().then((pages) => {
|
|
7840
|
+
if (disposed || !pagesLoaded) return;
|
|
7841
|
+
task = acquireDocumentText(pages);
|
|
7842
|
+
return task.promise;
|
|
7843
|
+
}).then((text) => {
|
|
7844
|
+
if (disposed || !text) return;
|
|
7348
7845
|
setTextContent(text);
|
|
7349
|
-
setResult({ pages: proxies, status: "ready" });
|
|
7846
|
+
setResult({ document: document2, pages: proxies, status: "ready" });
|
|
7350
7847
|
}).catch((error) => {
|
|
7351
7848
|
if (disposed) return;
|
|
7352
7849
|
console.error("Error extracting PDF text", error);
|
|
7353
|
-
setResult({ pages: proxies, status: "error", error });
|
|
7850
|
+
setResult({ document: document2, pages: proxies, status: "error", error });
|
|
7354
7851
|
});
|
|
7355
7852
|
return () => {
|
|
7356
7853
|
disposed = true;
|
|
7357
|
-
task
|
|
7854
|
+
task?.release();
|
|
7358
7855
|
};
|
|
7359
|
-
}, [proxies, setTextContent, attempt]);
|
|
7360
|
-
if (!result || result.pages !== proxies)
|
|
7856
|
+
}, [document2, proxies, pagesLoaded, loadPages, setTextContent, attempt]);
|
|
7857
|
+
if (!result || result.document !== document2 || result.pages !== proxies)
|
|
7858
|
+
return loading;
|
|
7361
7859
|
if (result.status === "error") {
|
|
7362
7860
|
if (errorFallback) return errorFallback({ error: result.error, retry });
|
|
7363
7861
|
return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
|
|
@@ -7457,7 +7955,7 @@ var useThumbnail = (pageNumber, config = {}) => {
|
|
|
7457
7955
|
isVisible
|
|
7458
7956
|
};
|
|
7459
7957
|
};
|
|
7460
|
-
var
|
|
7958
|
+
var LoadedThumbnail = ({
|
|
7461
7959
|
pageNumber = 1,
|
|
7462
7960
|
eager = false,
|
|
7463
7961
|
...props
|
|
@@ -7466,30 +7964,65 @@ var Thumbnail = ({
|
|
|
7466
7964
|
isFirstPage: eager || pageNumber < 5
|
|
7467
7965
|
});
|
|
7468
7966
|
const { jumpToPage } = usePdfJump();
|
|
7967
|
+
const isCurrentPage = usePdf((state) => state.currentPage === pageNumber);
|
|
7968
|
+
const spacePressed = useRef(false);
|
|
7469
7969
|
return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { minHeight: "150px", minWidth: "10px" }, children: isVisible && /* @__PURE__ */ jsx(
|
|
7470
7970
|
Primitive.canvas,
|
|
7471
7971
|
{
|
|
7972
|
+
"aria-label": `Page ${pageNumber}`,
|
|
7973
|
+
"aria-current": isCurrentPage ? "page" : void 0,
|
|
7472
7974
|
...props,
|
|
7473
7975
|
role: "button",
|
|
7474
7976
|
tabIndex: 0,
|
|
7475
|
-
onClick: (
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
jumpToPage(pageNumber, { behavior: "auto" });
|
|
7977
|
+
onClick: (event) => {
|
|
7978
|
+
props.onClick?.(event);
|
|
7979
|
+
if (!event.defaultPrevented)
|
|
7980
|
+
jumpToPage(pageNumber, { behavior: "auto" });
|
|
7480
7981
|
},
|
|
7481
|
-
onKeyDown: (
|
|
7482
|
-
|
|
7483
|
-
|
|
7982
|
+
onKeyDown: (event) => {
|
|
7983
|
+
props.onKeyDown?.(event);
|
|
7984
|
+
if (event.defaultPrevented) return;
|
|
7985
|
+
if (event.key === "Enter") {
|
|
7986
|
+
event.preventDefault();
|
|
7987
|
+
jumpToPage(pageNumber, { behavior: "auto" });
|
|
7988
|
+
} else if (event.key === " ") {
|
|
7989
|
+
event.preventDefault();
|
|
7990
|
+
spacePressed.current = true;
|
|
7484
7991
|
}
|
|
7485
|
-
|
|
7992
|
+
},
|
|
7993
|
+
onKeyUp: (event) => {
|
|
7994
|
+
props.onKeyUp?.(event);
|
|
7995
|
+
if (event.key !== " ") return;
|
|
7996
|
+
const activate = spacePressed.current && !event.defaultPrevented;
|
|
7997
|
+
spacePressed.current = false;
|
|
7998
|
+
if (activate) {
|
|
7999
|
+
event.preventDefault();
|
|
7486
8000
|
jumpToPage(pageNumber, { behavior: "auto" });
|
|
7487
8001
|
}
|
|
7488
8002
|
},
|
|
8003
|
+
onBlur: (event) => {
|
|
8004
|
+
spacePressed.current = false;
|
|
8005
|
+
props.onBlur?.(event);
|
|
8006
|
+
},
|
|
7489
8007
|
ref: canvasRef
|
|
7490
8008
|
}
|
|
7491
8009
|
) });
|
|
7492
8010
|
};
|
|
8011
|
+
var Thumbnail = (props) => /* @__PURE__ */ jsx(
|
|
8012
|
+
PageLoadingBoundary,
|
|
8013
|
+
{
|
|
8014
|
+
pageNumber: props.pageNumber ?? 1,
|
|
8015
|
+
fallback: /* @__PURE__ */ jsx(
|
|
8016
|
+
"div",
|
|
8017
|
+
{
|
|
8018
|
+
"aria-label": `Loading page ${props.pageNumber ?? 1}`,
|
|
8019
|
+
"aria-busy": "true",
|
|
8020
|
+
style: { minHeight: 150 }
|
|
8021
|
+
}
|
|
8022
|
+
),
|
|
8023
|
+
children: /* @__PURE__ */ jsx(LoadedThumbnail, { ...props })
|
|
8024
|
+
}
|
|
8025
|
+
);
|
|
7493
8026
|
function VirtualizedThumbnails({
|
|
7494
8027
|
children,
|
|
7495
8028
|
count,
|
|
@@ -7502,6 +8035,9 @@ function VirtualizedThumbnails({
|
|
|
7502
8035
|
const containerRef = useRef(null);
|
|
7503
8036
|
useImperativeHandle(ref, () => containerRef.current);
|
|
7504
8037
|
const [focusPage, setFocusPage] = useState(null);
|
|
8038
|
+
const focusPageLoaded = usePdf(
|
|
8039
|
+
(state) => focusPage === null ? false : state.isPageLoaded(focusPage)
|
|
8040
|
+
);
|
|
7505
8041
|
const itemHeight = Number.isFinite(virtualize.itemHeight) ? Math.max(1, virtualize.itemHeight) : 150;
|
|
7506
8042
|
const virtualizer = useVirtualizer({
|
|
7507
8043
|
count,
|
|
@@ -7519,7 +8055,7 @@ function VirtualizedThumbnails({
|
|
|
7519
8055
|
canvas.focus({ preventScroll: true });
|
|
7520
8056
|
setFocusPage(null);
|
|
7521
8057
|
}
|
|
7522
|
-
}, [focusPage, items]);
|
|
8058
|
+
}, [focusPage, focusPageLoaded, items]);
|
|
7523
8059
|
return /* @__PURE__ */ jsx(
|
|
7524
8060
|
Primitive.div,
|
|
7525
8061
|
{
|
|
@@ -7652,12 +8188,42 @@ var CurrentZoom = ({ ...props }) => {
|
|
|
7652
8188
|
// src/lib/search.ts
|
|
7653
8189
|
var normalizedPages = /* @__PURE__ */ new WeakMap();
|
|
7654
8190
|
function normalize(page) {
|
|
7655
|
-
|
|
7656
|
-
if (
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
8191
|
+
const cached = normalizedPages.get(page);
|
|
8192
|
+
if (cached?.text === page.text) return cached;
|
|
8193
|
+
const lower = page.text.toLowerCase();
|
|
8194
|
+
const result = { text: page.text, lower };
|
|
8195
|
+
if (lower.length !== page.text.length) {
|
|
8196
|
+
result.starts = new Uint32Array(lower.length);
|
|
8197
|
+
result.ends = new Uint32Array(lower.length);
|
|
8198
|
+
let original = 0;
|
|
8199
|
+
let normalized = 0;
|
|
8200
|
+
for (const character of page.text) {
|
|
8201
|
+
const length = character.toLowerCase().length;
|
|
8202
|
+
for (let i = 0; i < length; i++) {
|
|
8203
|
+
result.starts[normalized + i] = original + (length === character.length ? i : 0);
|
|
8204
|
+
result.ends[normalized + i] = original + (length === character.length ? i + 1 : character.length);
|
|
8205
|
+
}
|
|
8206
|
+
original += character.length;
|
|
8207
|
+
normalized += length;
|
|
8208
|
+
}
|
|
8209
|
+
}
|
|
8210
|
+
normalizedPages.set(page, result);
|
|
8211
|
+
return result;
|
|
8212
|
+
}
|
|
8213
|
+
function createMatch(page, normalized, start, length, searchText, textSize, score) {
|
|
8214
|
+
const end = Math.min(normalized.lower.length, start + length);
|
|
8215
|
+
const matchIndex = normalized.starts?.[start] ?? start;
|
|
8216
|
+
const matchEnd = normalized.ends?.[end - 1] ?? end;
|
|
8217
|
+
const matchLength = matchEnd - matchIndex;
|
|
8218
|
+
return {
|
|
8219
|
+
pageNumber: page.pageNumber,
|
|
8220
|
+
text: page.text.slice(matchIndex, matchEnd + textSize),
|
|
8221
|
+
score,
|
|
8222
|
+
matchIndex,
|
|
8223
|
+
isExactMatch: score === 1,
|
|
8224
|
+
searchText,
|
|
8225
|
+
...matchLength !== searchText.length ? { matchLength } : {}
|
|
8226
|
+
};
|
|
7661
8227
|
}
|
|
7662
8228
|
function createBoundedDistance(query, maxDistance) {
|
|
7663
8229
|
const length = query.length;
|
|
@@ -7720,7 +8286,8 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7720
8286
|
let steps = 0;
|
|
7721
8287
|
let deadline = cooperative ? performance.now() + budget : 0;
|
|
7722
8288
|
for (const page of pages) {
|
|
7723
|
-
const
|
|
8289
|
+
const normalized = normalize(page);
|
|
8290
|
+
const lower = normalized.lower;
|
|
7724
8291
|
const exactStarts = [];
|
|
7725
8292
|
let index = 0;
|
|
7726
8293
|
while (true) {
|
|
@@ -7733,16 +8300,19 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7733
8300
|
exactCount++;
|
|
7734
8301
|
if (maxDistance > 0) exactStarts.push(matchIndex);
|
|
7735
8302
|
if (exactMatches.length < limit) {
|
|
7736
|
-
exactMatches.push(
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
8303
|
+
exactMatches.push(
|
|
8304
|
+
createMatch(
|
|
8305
|
+
page,
|
|
8306
|
+
normalized,
|
|
8307
|
+
matchIndex,
|
|
8308
|
+
query.length,
|
|
8309
|
+
searchText,
|
|
8310
|
+
textSize,
|
|
8311
|
+
1
|
|
8312
|
+
)
|
|
8313
|
+
);
|
|
7744
8314
|
}
|
|
7745
|
-
index = matchIndex +
|
|
8315
|
+
index = matchIndex + query.length;
|
|
7746
8316
|
}
|
|
7747
8317
|
if (!(maxDistance > 0)) continue;
|
|
7748
8318
|
index = 0;
|
|
@@ -7752,11 +8322,11 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7752
8322
|
yield;
|
|
7753
8323
|
deadline = performance.now() + budget;
|
|
7754
8324
|
}
|
|
7755
|
-
while (exactIndex < exactStarts.length && exactStarts[exactIndex] +
|
|
8325
|
+
while (exactIndex < exactStarts.length && exactStarts[exactIndex] + query.length <= index)
|
|
7756
8326
|
exactIndex++;
|
|
7757
8327
|
const exactStart = exactStarts[exactIndex];
|
|
7758
8328
|
if (exactStart !== void 0 && index >= exactStart) {
|
|
7759
|
-
index = exactStart +
|
|
8329
|
+
index = exactStart + query.length;
|
|
7760
8330
|
continue;
|
|
7761
8331
|
}
|
|
7762
8332
|
const edits = distance(lower, index);
|
|
@@ -7766,14 +8336,15 @@ function* runSearch(pages, searchText, options = {}, cooperative = false, budget
|
|
|
7766
8336
|
if (fuzzyMatches.length < limit || score > (fuzzyMatches[limit - 1]?.score ?? Infinity)) {
|
|
7767
8337
|
insertMatch(
|
|
7768
8338
|
fuzzyMatches,
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
8339
|
+
createMatch(
|
|
8340
|
+
page,
|
|
8341
|
+
normalized,
|
|
8342
|
+
index,
|
|
8343
|
+
query.length,
|
|
8344
|
+
searchText,
|
|
8345
|
+
textSize,
|
|
8346
|
+
score
|
|
8347
|
+
),
|
|
7777
8348
|
limit
|
|
7778
8349
|
);
|
|
7779
8350
|
}
|
|
@@ -7879,32 +8450,37 @@ var useSearch = () => {
|
|
|
7879
8450
|
|
|
7880
8451
|
// src/hooks/search/useSearchPosition.tsx
|
|
7881
8452
|
async function calculateHighlightRects(pageProxy, textMatch) {
|
|
7882
|
-
const matchLength = textMatch.searchText ? textMatch.searchText.length : textMatch.text.length;
|
|
7883
|
-
if (matchLength
|
|
8453
|
+
const matchLength = textMatch.matchLength ?? (textMatch.searchText ? textMatch.searchText.length : textMatch.text.length);
|
|
8454
|
+
if (!Number.isFinite(matchLength) || matchLength <= 0 || !Number.isFinite(textMatch.matchIndex) || textMatch.matchIndex < 0)
|
|
7884
8455
|
return [];
|
|
7885
8456
|
const matchEnd = textMatch.matchIndex + matchLength;
|
|
7886
8457
|
const reader = pageProxy.streamTextContent().getReader();
|
|
7887
8458
|
const matchRects = [];
|
|
7888
8459
|
const viewport = pageProxy.getViewport({ scale: 1 });
|
|
8460
|
+
const styles = {};
|
|
7889
8461
|
let offset3 = 0;
|
|
7890
8462
|
let foundEnd = false;
|
|
7891
8463
|
try {
|
|
7892
8464
|
read: while (true) {
|
|
7893
8465
|
const { value, done } = await reader.read();
|
|
7894
8466
|
if (done) break;
|
|
8467
|
+
Object.assign(styles, value.styles);
|
|
7895
8468
|
for (const item of value.items) {
|
|
7896
8469
|
if (!("str" in item) || item.str.length === 0) continue;
|
|
7897
8470
|
const length = item.str.length;
|
|
7898
8471
|
const start = Math.max(0, textMatch.matchIndex - offset3);
|
|
7899
8472
|
const end = Math.min(length, matchEnd - offset3);
|
|
7900
|
-
if (end > start) {
|
|
7901
|
-
matchRects.push(
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
8473
|
+
if (end > start && item.str.slice(start, end).trim()) {
|
|
8474
|
+
matchRects.push(
|
|
8475
|
+
getItemRect(
|
|
8476
|
+
item,
|
|
8477
|
+
styles[item.fontName],
|
|
8478
|
+
viewport,
|
|
8479
|
+
start,
|
|
8480
|
+
end,
|
|
8481
|
+
textMatch.pageNumber
|
|
8482
|
+
)
|
|
8483
|
+
);
|
|
7908
8484
|
}
|
|
7909
8485
|
offset3 += length;
|
|
7910
8486
|
if (offset3 >= matchEnd) {
|
|
@@ -7921,6 +8497,61 @@ async function calculateHighlightRects(pageProxy, textMatch) {
|
|
|
7921
8497
|
}
|
|
7922
8498
|
return mergeAdjacentRects(matchRects);
|
|
7923
8499
|
}
|
|
8500
|
+
var measurementContext;
|
|
8501
|
+
function getTextFractions(item, style, start, end) {
|
|
8502
|
+
const length = item.str.length;
|
|
8503
|
+
let from = start / length;
|
|
8504
|
+
let to = end / length;
|
|
8505
|
+
if ((start > 0 || end < length) && style?.fontFamily && !style.vertical && typeof document !== "undefined") {
|
|
8506
|
+
if (measurementContext === void 0) {
|
|
8507
|
+
const canvas = document.createElement("canvas");
|
|
8508
|
+
canvas.width = canvas.height = 0;
|
|
8509
|
+
measurementContext = canvas.getContext("2d");
|
|
8510
|
+
}
|
|
8511
|
+
if (measurementContext) {
|
|
8512
|
+
measurementContext.font = `100px ${style.fontFamily}`;
|
|
8513
|
+
const width = measurementContext.measureText(item.str).width;
|
|
8514
|
+
if (width > 0) {
|
|
8515
|
+
from = start === 0 ? 0 : measurementContext.measureText(item.str.slice(0, start)).width / width;
|
|
8516
|
+
to = end === length ? 1 : measurementContext.measureText(item.str.slice(0, end)).width / width;
|
|
8517
|
+
}
|
|
8518
|
+
}
|
|
8519
|
+
}
|
|
8520
|
+
return item.dir === "rtl" ? [1 - to, 1 - from] : [from, to];
|
|
8521
|
+
}
|
|
8522
|
+
function getItemRect(item, style, viewport, start, end, pageNumber) {
|
|
8523
|
+
const angle = Math.atan2(item.transform[1], item.transform[0]) - (style?.vertical ? Math.PI / 2 : 0);
|
|
8524
|
+
const cosine = Math.cos(angle);
|
|
8525
|
+
const sine = Math.sin(angle);
|
|
8526
|
+
const height = Math.hypot(item.transform[2], item.transform[3]);
|
|
8527
|
+
const ascent = (style?.ascent ?? (style?.descent ? 1 + style.descent : 1)) * height;
|
|
8528
|
+
const width = style?.vertical ? item.height : item.width;
|
|
8529
|
+
const [startFraction, endFraction] = getTextFractions(
|
|
8530
|
+
item,
|
|
8531
|
+
style,
|
|
8532
|
+
start,
|
|
8533
|
+
end
|
|
8534
|
+
);
|
|
8535
|
+
const from = startFraction * width;
|
|
8536
|
+
const to = endFraction * width;
|
|
8537
|
+
let left = Infinity;
|
|
8538
|
+
let top = Infinity;
|
|
8539
|
+
let right = -Infinity;
|
|
8540
|
+
let bottom = -Infinity;
|
|
8541
|
+
for (const along of [from, to]) {
|
|
8542
|
+
for (const above of [ascent, ascent - height]) {
|
|
8543
|
+
const [x, y] = viewport.convertToViewportPoint(
|
|
8544
|
+
item.transform[4] + along * cosine - above * sine,
|
|
8545
|
+
item.transform[5] + along * sine + above * cosine
|
|
8546
|
+
);
|
|
8547
|
+
left = Math.min(left, x);
|
|
8548
|
+
top = Math.min(top, y);
|
|
8549
|
+
right = Math.max(right, x);
|
|
8550
|
+
bottom = Math.max(bottom, y);
|
|
8551
|
+
}
|
|
8552
|
+
}
|
|
8553
|
+
return { pageNumber, left, top, width: right - left, height: bottom - top };
|
|
8554
|
+
}
|
|
7924
8555
|
function mergeAdjacentRects(rects) {
|
|
7925
8556
|
if (rects.length <= 1) return rects;
|
|
7926
8557
|
const merged = [];
|
|
@@ -7929,10 +8560,13 @@ function mergeAdjacentRects(rects) {
|
|
|
7929
8560
|
for (let i = 1; i < rects.length; i++) {
|
|
7930
8561
|
const next = rects[i];
|
|
7931
8562
|
if (!next) continue;
|
|
7932
|
-
if (Math.abs(current.top - next.top) < 2 && Math.abs(current.height - next.height) < 2) {
|
|
8563
|
+
if (Math.abs(current.top - next.top) < 2 && Math.abs(current.height - next.height) < 2 && Math.max(current.left, next.left) <= Math.min(current.left + current.width, next.left + next.width) + 2) {
|
|
7933
8564
|
current = {
|
|
7934
8565
|
...current,
|
|
7935
|
-
|
|
8566
|
+
left: Math.min(current.left, next.left),
|
|
8567
|
+
top: Math.min(current.top, next.top),
|
|
8568
|
+
width: Math.max(current.left + current.width, next.left + next.width) - Math.min(current.left, next.left),
|
|
8569
|
+
height: Math.max(current.top + current.height, next.top + next.height) - Math.min(current.top, next.top)
|
|
7936
8570
|
};
|
|
7937
8571
|
} else {
|
|
7938
8572
|
merged.push(current);
|