@anaralabs/lector 3.14.9 → 3.14.11

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/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
- if (typeof zoom === "function") {
269
- const newZoom2 = clamp(zoom(state.zoom), minZoom, maxZoom);
270
- return { zoom: newZoom2, isZoomFitWidth };
271
- }
272
- const newZoom = clamp(zoom, minZoom, maxZoom);
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
- set({
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 useAnnotations = () => {
701
+ var identity = (state) => state;
702
+ function useAnnotations(selector) {
704
703
  const ctx = AnnotationsStore.useContext();
705
- return useStore(ctx ?? getFallbackStore());
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 = useMemo(
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
- if (!elementRef.current) {
1229
- return;
1230
- }
1231
- const observer = new IntersectionObserver(([entry]) => {
1232
- setVisible(entry?.isIntersecting ?? false);
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
- observer.observe(elementRef.current);
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
- observer.disconnect();
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 DPR_CAP = 3;
2065
- var readDpr = () => typeof window === "undefined" ? 1 : Math.min(window.devicePixelRatio || 1, DPR_CAP);
2066
- var useDpr = () => {
2067
- const [dpr, setDpr] = useState(readDpr);
2068
- useEffect(() => {
2069
- if (typeof window === "undefined") {
2070
- return;
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
- let mediaQuery = null;
2073
- let cancelled = false;
2074
- const handleChange = () => {
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;
@@ -4492,9 +4528,9 @@ var useVisiblePage = ({
4492
4528
  window.addEventListener("resize", measure);
4493
4529
  return () => window.removeEventListener("resize", measure);
4494
4530
  }
4495
- const observer = new ResizeObserver(measure);
4496
- observer.observe(scrollElement);
4497
- return () => observer.disconnect();
4531
+ const observer2 = new ResizeObserver(measure);
4532
+ observer2.observe(scrollElement);
4533
+ return () => observer2.disconnect();
4498
4534
  }, [scrollElement]);
4499
4535
  const calculateVisiblePageIndex = useCallback(
4500
4536
  (virtualItems) => {
@@ -5051,7 +5087,7 @@ var CoordinatesEngine = class extends Engine {
5051
5087
  }
5052
5088
  }
5053
5089
  };
5054
- var identity = (v) => v;
5090
+ var identity2 = (v) => v;
5055
5091
  var DEFAULT_RUBBERBAND = 0.15;
5056
5092
  var commonConfigResolver = {
5057
5093
  enabled(value = true) {
@@ -5084,7 +5120,7 @@ var commonConfigResolver = {
5084
5120
  const transform = value || config.shared.transform;
5085
5121
  this.hasCustomTransform = !!transform;
5086
5122
  if (process.env.NODE_ENV === "development") {
5087
- const originalTransform = transform || identity;
5123
+ const originalTransform = transform || identity2;
5088
5124
  return (v) => {
5089
5125
  const r2 = originalTransform(v);
5090
5126
  if (!isFinite(r2[0]) || !isFinite(r2[1])) {
@@ -5093,7 +5129,7 @@ var commonConfigResolver = {
5093
5129
  return r2;
5094
5130
  };
5095
5131
  }
5096
- return transform || identity;
5132
+ return transform || identity2;
5097
5133
  },
5098
5134
  threshold(value) {
5099
5135
  return V.toVector(value, 0);
@@ -6167,16 +6203,16 @@ var EventStore = class {
6167
6203
  this._gestureKey = gestureKey;
6168
6204
  }
6169
6205
  add(element, device, action, handler, options) {
6170
- const listeners = this._listeners;
6206
+ const listeners2 = this._listeners;
6171
6207
  const type = toDomEventType(device, action);
6172
6208
  const _options = this._gestureKey ? this._ctrl.config[this._gestureKey].eventOptions : {};
6173
6209
  const eventOptions = _objectSpread2(_objectSpread2({}, _options), options);
6174
6210
  element.addEventListener(type, handler, eventOptions);
6175
6211
  const remove = () => {
6176
6212
  element.removeEventListener(type, handler, eventOptions);
6177
- listeners.delete(remove);
6213
+ listeners2.delete(remove);
6178
6214
  };
6179
- listeners.add(remove);
6215
+ listeners2.add(remove);
6180
6216
  return remove;
6181
6217
  }
6182
6218
  clean() {
@@ -6661,7 +6697,6 @@ var useViewportContainer = ({
6661
6697
  origin: originRef.current
6662
6698
  };
6663
6699
  };
6664
- var selectLargestPageWidth = (state) => state.viewports.reduce((max, vp) => Math.max(max, vp.width), 0);
6665
6700
  var DEFAULT_HEIGHT = 600;
6666
6701
  var EXTRA_HEIGHT = 0;
6667
6702
  var DEFAULT_VIRTUALIZER_OPTIONS = { overscan: 1 };
@@ -6776,7 +6811,10 @@ var Pages = ({
6776
6811
  scrollOffset: virtualizer.scrollOffset ?? null
6777
6812
  });
6778
6813
  useFitWidth({ viewportRef: containerRef });
6779
- const largestPageWidth = usePdf(selectLargestPageWidth);
6814
+ const largestPageWidth = useMemo(
6815
+ () => viewports.reduce((max, viewport) => Math.max(max, viewport.width), 0),
6816
+ [viewports]
6817
+ );
6780
6818
  useEffect(() => {
6781
6819
  virtualizer.getOffsetForAlignment = (toOffset, align, itemSize = 0) => {
6782
6820
  const size = virtualizer.getSize();
@@ -6814,7 +6852,9 @@ var Pages = ({
6814
6852
  {
6815
6853
  ref: elementWrapperRef,
6816
6854
  style: {
6817
- width: "max-content",
6855
+ // The absolute page list contributes no intrinsic width. Reserve
6856
+ // its width before any zoom effect runs (including zoom === 1).
6857
+ width: largestPageWidth,
6818
6858
  marginLeft: "auto",
6819
6859
  marginRight: "auto",
6820
6860
  flexShrink: 0
@@ -6855,6 +6895,36 @@ var Pages = ({
6855
6895
  );
6856
6896
  };
6857
6897
 
6898
+ // src/lib/map-concurrent.ts
6899
+ async function mapConcurrent(items, concurrency, map, isCancelled = () => false) {
6900
+ if (!Number.isFinite(concurrency) || concurrency < 1)
6901
+ throw new RangeError("Concurrency must be positive and finite");
6902
+ const results = new Array(items.length);
6903
+ let next = 0;
6904
+ let failed = false;
6905
+ const worker = async () => {
6906
+ while (next < items.length) {
6907
+ if (failed || isCancelled())
6908
+ throw new DOMException("Operation aborted", "AbortError");
6909
+ const index = next++;
6910
+ try {
6911
+ results[index] = await map(items[index], index);
6912
+ } catch (error) {
6913
+ failed = true;
6914
+ throw error;
6915
+ }
6916
+ }
6917
+ };
6918
+ await Promise.all(
6919
+ Array.from(
6920
+ { length: Math.min(Math.max(1, Math.floor(concurrency)), items.length) },
6921
+ worker
6922
+ )
6923
+ );
6924
+ if (isCancelled()) throw new DOMException("Operation aborted", "AbortError");
6925
+ return results;
6926
+ }
6927
+
6858
6928
  // src/lib/pdfjs.ts
6859
6929
  var pdfJsPromise = null;
6860
6930
  var loadPdfJs = () => {
@@ -6987,7 +7057,6 @@ var usePDFDocumentContext = ({
6987
7057
  colorScheme,
6988
7058
  darkModeColors
6989
7059
  }) => {
6990
- const [_, setProgress] = useState(0);
6991
7060
  const [initialState, setInitialState] = useState();
6992
7061
  const [rotation] = useState(initialRotation);
6993
7062
  const [renderColorMapRef] = useState(() => ({
@@ -7002,30 +7071,30 @@ var usePDFDocumentContext = ({
7002
7071
  const darkModeColorsRef = useRef(darkModeColors);
7003
7072
  darkModeColorsRef.current = darkModeColors;
7004
7073
  useEffect(() => {
7074
+ let isDisposed = false;
7005
7075
  const generateViewports = async (pdf) => {
7006
7076
  const pageProxies = [];
7007
- const rotations = [];
7008
- const viewports = await Promise.all(
7009
- Array.from({ length: pdf.numPages }, async (_2, index) => {
7077
+ const viewports = await mapConcurrent(
7078
+ Array.from({ length: pdf.numPages }, (_, index) => index),
7079
+ 16,
7080
+ async (index) => {
7010
7081
  const page = await pdf.getPage(index + 1);
7011
7082
  const deltaRotate = page.rotate || 0;
7012
7083
  const viewport = page.getViewport({
7013
7084
  scale: 1,
7014
7085
  rotation: rotation + deltaRotate
7015
7086
  });
7016
- pageProxies.push(page);
7017
- rotations.push(page.rotate);
7087
+ pageProxies[index] = page;
7018
7088
  return viewport;
7019
- })
7020
- );
7021
- const sortedPageProxies = pageProxies.toSorted(
7022
- (a, b) => a.pageNumber - b.pageNumber
7089
+ },
7090
+ () => isDisposed
7023
7091
  );
7092
+ if (isDisposed) return;
7024
7093
  setInitialState((prev) => ({
7025
7094
  ...prev,
7026
7095
  isZoomFitWidth,
7027
7096
  viewports,
7028
- pageProxies: sortedPageProxies,
7097
+ pageProxies,
7029
7098
  pdfDocumentProxy: pdf,
7030
7099
  zoom,
7031
7100
  zoomOptions,
@@ -7036,9 +7105,7 @@ var usePDFDocumentContext = ({
7036
7105
  };
7037
7106
  const loadDocument = () => {
7038
7107
  setInitialState(null);
7039
- setProgress(0);
7040
7108
  let loadingTask = null;
7041
- let isDisposed = false;
7042
7109
  void loadPdfJs().then(({ getDocument, version }) => {
7043
7110
  if (isDisposed) {
7044
7111
  return;
@@ -7051,18 +7118,11 @@ var usePDFDocumentContext = ({
7051
7118
  documentOptionsRef.current
7052
7119
  )
7053
7120
  );
7054
- loadingTask.onProgress = (progressEvent) => {
7055
- if (progressEvent.loaded === progressEvent.total) {
7056
- return;
7057
- }
7058
- setProgress(progressEvent.loaded / progressEvent.total);
7059
- };
7060
7121
  return loadingTask.promise.then(async (proxy) => {
7061
7122
  if (isDisposed || loadingTask?.destroyed) {
7062
7123
  return;
7063
7124
  }
7064
7125
  onDocumentLoad?.({ proxy, source });
7065
- setProgress(1);
7066
7126
  try {
7067
7127
  await generateViewports(proxy);
7068
7128
  } catch (error) {
@@ -7191,31 +7251,122 @@ var Root = forwardRef(
7191
7251
  }
7192
7252
  );
7193
7253
  Root.displayName = "Root";
7194
- var Search = ({ children, loading = "Loading..." }) => {
7195
- const [isLoading, setIsLoading] = useState(false);
7254
+
7255
+ // src/lib/document-text.ts
7256
+ var jobs = /* @__PURE__ */ new WeakMap();
7257
+ var CONCURRENCY = 4;
7258
+ function acquireDocumentText(pages) {
7259
+ let job = jobs.get(pages);
7260
+ if (!job) {
7261
+ const controller = new AbortController();
7262
+ const { signal } = controller;
7263
+ let next = 0;
7264
+ const result = new Array(pages.length);
7265
+ const worker = async () => {
7266
+ while (next < pages.length) {
7267
+ signal.throwIfAborted();
7268
+ const index = next++;
7269
+ const page = pages[index];
7270
+ const reader = page.streamTextContent().getReader();
7271
+ const chunks = [];
7272
+ let complete = false;
7273
+ try {
7274
+ while (true) {
7275
+ const { value, done } = await reader.read();
7276
+ signal.throwIfAborted();
7277
+ if (done) {
7278
+ complete = true;
7279
+ break;
7280
+ }
7281
+ chunks.push(
7282
+ value.items.map((item) => "str" in item ? item.str : "").join("")
7283
+ );
7284
+ }
7285
+ } finally {
7286
+ if (!complete)
7287
+ void reader.cancel(new Error("Text indexing stopped")).catch(() => {
7288
+ });
7289
+ reader.releaseLock();
7290
+ }
7291
+ result[index] = { pageNumber: page.pageNumber, text: chunks.join("") };
7292
+ }
7293
+ };
7294
+ const created = {
7295
+ controller,
7296
+ users: 0,
7297
+ settled: false,
7298
+ promise: Promise.resolve([])
7299
+ };
7300
+ created.promise = Promise.all(
7301
+ Array.from({ length: Math.min(CONCURRENCY, pages.length) }, worker)
7302
+ ).then(() => result).catch((error) => {
7303
+ controller.abort();
7304
+ if (jobs.get(pages) === created) jobs.delete(pages);
7305
+ throw error;
7306
+ }).finally(() => {
7307
+ created.settled = true;
7308
+ });
7309
+ jobs.set(pages, created);
7310
+ job = created;
7311
+ }
7312
+ job.users++;
7313
+ const acquired = job;
7314
+ let released = false;
7315
+ return {
7316
+ promise: acquired.promise,
7317
+ release() {
7318
+ if (released) return;
7319
+ released = true;
7320
+ acquired.users--;
7321
+ queueMicrotask(() => {
7322
+ if (acquired.users || acquired.settled) return;
7323
+ if (jobs.get(pages) === acquired) jobs.delete(pages);
7324
+ acquired.controller.abort();
7325
+ });
7326
+ }
7327
+ };
7328
+ }
7329
+ var Search = ({
7330
+ children,
7331
+ loading = "Loading...",
7332
+ errorFallback
7333
+ }) => {
7334
+ const [result, setResult] = useState(null);
7335
+ const [attempt, setAttempt] = useState(0);
7196
7336
  const proxies = usePdf((state) => state.pageProxies);
7197
7337
  const setTextContent = usePdf((state) => state.setTextContent);
7198
- const getTextContent = useCallback(
7199
- async (pages) => {
7200
- setIsLoading(true);
7201
- const promises = pages.map(async (proxy) => {
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
- );
7338
+ const retry = useCallback(() => {
7339
+ setResult(null);
7340
+ setAttempt((value) => value + 1);
7341
+ }, []);
7215
7342
  useEffect(() => {
7216
- getTextContent(proxies);
7217
- }, [proxies, getTextContent]);
7218
- return isLoading ? loading : children;
7343
+ let disposed = false;
7344
+ setResult(null);
7345
+ const task = acquireDocumentText(proxies);
7346
+ void task.promise.then((text) => {
7347
+ if (disposed) return;
7348
+ setTextContent(text);
7349
+ setResult({ pages: proxies, status: "ready" });
7350
+ }).catch((error) => {
7351
+ if (disposed) return;
7352
+ console.error("Error extracting PDF text", error);
7353
+ setResult({ pages: proxies, status: "error", error });
7354
+ });
7355
+ return () => {
7356
+ disposed = true;
7357
+ task.release();
7358
+ };
7359
+ }, [proxies, setTextContent, attempt]);
7360
+ if (!result || result.pages !== proxies) return loading;
7361
+ if (result.status === "error") {
7362
+ if (errorFallback) return errorFallback({ error: result.error, retry });
7363
+ return /* @__PURE__ */ jsxs("div", { role: "alert", children: [
7364
+ "Unable to index this document for search.",
7365
+ " ",
7366
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: retry, children: "Retry" })
7367
+ ] });
7368
+ }
7369
+ return children;
7219
7370
  };
7220
7371
  var DEFAULT_CONFIG = {
7221
7372
  maxHeight: 800,
@@ -7308,10 +7459,11 @@ var useThumbnail = (pageNumber, config = {}) => {
7308
7459
  };
7309
7460
  var Thumbnail = ({
7310
7461
  pageNumber = 1,
7462
+ eager = false,
7311
7463
  ...props
7312
7464
  }) => {
7313
7465
  const { canvasRef, containerRef, isVisible } = useThumbnail(pageNumber, {
7314
- isFirstPage: pageNumber < 5
7466
+ isFirstPage: eager || pageNumber < 5
7315
7467
  });
7316
7468
  const { jumpToPage } = usePdfJump();
7317
7469
  return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { minHeight: "150px", minWidth: "10px" }, children: isVisible && /* @__PURE__ */ jsx(
@@ -7338,16 +7490,107 @@ var Thumbnail = ({
7338
7490
  }
7339
7491
  ) });
7340
7492
  };
7493
+ function VirtualizedThumbnails({
7494
+ children,
7495
+ count,
7496
+ virtualize,
7497
+ style,
7498
+ onKeyDown,
7499
+ ref,
7500
+ ...props
7501
+ }) {
7502
+ const containerRef = useRef(null);
7503
+ useImperativeHandle(ref, () => containerRef.current);
7504
+ const [focusPage, setFocusPage] = useState(null);
7505
+ const itemHeight = Number.isFinite(virtualize.itemHeight) ? Math.max(1, virtualize.itemHeight) : 150;
7506
+ const virtualizer = useVirtualizer({
7507
+ count,
7508
+ getScrollElement: () => containerRef.current,
7509
+ estimateSize: () => itemHeight,
7510
+ overscan: virtualize.overscan ?? 2
7511
+ });
7512
+ const items = virtualizer.getVirtualItems();
7513
+ useLayoutEffect(() => {
7514
+ if (focusPage === null) return;
7515
+ const canvas = containerRef.current?.querySelector(
7516
+ `[data-thumbnail-page="${focusPage}"] canvas`
7517
+ );
7518
+ if (canvas) {
7519
+ canvas.focus({ preventScroll: true });
7520
+ setFocusPage(null);
7521
+ }
7522
+ }, [focusPage, items]);
7523
+ return /* @__PURE__ */ jsx(
7524
+ Primitive.div,
7525
+ {
7526
+ ...props,
7527
+ ref: containerRef,
7528
+ style: {
7529
+ height: "100%",
7530
+ overflow: "auto",
7531
+ position: "relative",
7532
+ ...style
7533
+ },
7534
+ onKeyDown: (event) => {
7535
+ onKeyDown?.(event);
7536
+ if (event.defaultPrevented || !count) return;
7537
+ const row = event.target.closest(
7538
+ "[data-thumbnail-page]"
7539
+ );
7540
+ if (!row) return;
7541
+ const current = Number(row.dataset.thumbnailPage) - 1;
7542
+ 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;
7543
+ if (next === null) return;
7544
+ event.preventDefault();
7545
+ virtualizer.scrollToIndex(next, { align: "auto" });
7546
+ setFocusPage(next + 1);
7547
+ },
7548
+ children: /* @__PURE__ */ jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative" }, children: items.map((item) => /* @__PURE__ */ jsx(
7549
+ "div",
7550
+ {
7551
+ "data-thumbnail-page": item.index + 1,
7552
+ style: {
7553
+ position: "absolute",
7554
+ top: 0,
7555
+ left: 0,
7556
+ width: "100%",
7557
+ height: itemHeight,
7558
+ transform: `translateY(${item.start}px)`
7559
+ },
7560
+ children: cloneElement(
7561
+ children,
7562
+ { pageNumber: item.index + 1, eager: true }
7563
+ )
7564
+ },
7565
+ item.key
7566
+ )) })
7567
+ }
7568
+ );
7569
+ }
7341
7570
  var Thumbnails = ({
7342
7571
  children,
7572
+ virtualize,
7343
7573
  ...props
7344
7574
  }) => {
7345
7575
  const pageCount = usePdf((state) => state.pdfDocumentProxy.numPages);
7346
- return /* @__PURE__ */ jsx(Primitive.div, { ...props, children: Array.from({
7347
- length: pageCount
7348
- }).map((_, index) => {
7349
- return cloneElement(children, { key: index, pageNumber: index + 1 });
7350
- }) });
7576
+ if (virtualize)
7577
+ return /* @__PURE__ */ jsx(
7578
+ VirtualizedThumbnails,
7579
+ {
7580
+ ...props,
7581
+ virtualize,
7582
+ count: pageCount,
7583
+ children
7584
+ }
7585
+ );
7586
+ return /* @__PURE__ */ jsx(Primitive.div, { ...props, children: Array.from(
7587
+ { length: pageCount },
7588
+ (_, index) => cloneElement(children, {
7589
+ // biome-ignore lint/suspicious/noArrayIndexKey: a PDF page has a stable ordinal within this document
7590
+ key: index,
7591
+ pageNumber: index + 1
7592
+ })
7593
+ ) });
7351
7594
  };
7352
7595
  var ZoomIn = ({ ...props }) => {
7353
7596
  const setZoom = usePdf((state) => state.updateZoom);
@@ -7405,220 +7648,277 @@ var CurrentZoom = ({ ...props }) => {
7405
7648
  }
7406
7649
  );
7407
7650
  };
7408
- function levenshteinDistance(a, b) {
7409
- const matrix = [];
7410
- for (let i = 0; i <= b.length; i++) {
7411
- matrix[i] = [i];
7412
- }
7413
- for (let j = 0; j <= a.length; j++) {
7414
- matrix[0][j] = j;
7415
- }
7416
- for (let i = 1; i <= b.length; i++) {
7417
- for (let j = 1; j <= a.length; j++) {
7418
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
7419
- matrix[i][j] = matrix[i - 1][j - 1];
7420
- } else {
7421
- matrix[i][j] = Math.min(
7422
- matrix[i - 1][j - 1] + 1,
7423
- matrix[i][j - 1] + 1,
7424
- matrix[i - 1][j] + 1
7651
+
7652
+ // src/lib/search.ts
7653
+ var normalizedPages = /* @__PURE__ */ new WeakMap();
7654
+ function normalize(page) {
7655
+ let cached = normalizedPages.get(page);
7656
+ if (!cached || cached.text !== page.text) {
7657
+ cached = { text: page.text, lower: page.text.toLowerCase() };
7658
+ normalizedPages.set(page, cached);
7659
+ }
7660
+ return cached.lower;
7661
+ }
7662
+ function createBoundedDistance(query, maxDistance) {
7663
+ const length = query.length;
7664
+ let previous = new Float64Array(length + 1);
7665
+ let current = new Float64Array(length + 1);
7666
+ const exceeded = maxDistance + 1;
7667
+ return (text, offset3) => {
7668
+ const size = Math.min(length, text.length - offset3);
7669
+ if (length - size > maxDistance) return exceeded;
7670
+ for (let j = 0; j <= length; j++) previous[j] = j;
7671
+ for (let i = 1; i <= size; i++) {
7672
+ const start = Math.max(1, i - maxDistance);
7673
+ const end = Math.min(length, i + maxDistance);
7674
+ current[0] = i;
7675
+ if (start > 1) current[start - 1] = exceeded;
7676
+ let minimum = exceeded;
7677
+ const code = text.charCodeAt(offset3 + i - 1);
7678
+ for (let j = start; j <= end; j++) {
7679
+ const value = Math.min(
7680
+ previous[j] + 1,
7681
+ current[j - 1] + 1,
7682
+ previous[j - 1] + (query.charCodeAt(j - 1) === code ? 0 : 1)
7425
7683
  );
7684
+ current[j] = value;
7685
+ minimum = Math.min(minimum, value);
7426
7686
  }
7687
+ if (end < length) current[end + 1] = exceeded;
7688
+ if (minimum > maxDistance) return exceeded;
7689
+ [previous, current] = [current, previous];
7690
+ }
7691
+ return previous[length];
7692
+ };
7693
+ }
7694
+ function insertMatch(matches, match, limit) {
7695
+ if (!limit || matches.length === limit && matches[limit - 1].score >= match.score)
7696
+ return;
7697
+ let low = 0;
7698
+ let high = matches.length;
7699
+ while (low < high) {
7700
+ const mid = low + high >>> 1;
7701
+ if (matches[mid].score >= match.score) low = mid + 1;
7702
+ else high = mid;
7703
+ }
7704
+ matches.splice(low, 0, match);
7705
+ if (matches.length > limit) matches.pop();
7706
+ }
7707
+ function* runSearch(pages, searchText, options = {}, cooperative = false, budget = 8) {
7708
+ const empty = { exactMatches: [], fuzzyMatches: [], hasMoreResults: false };
7709
+ if (!searchText.trim()) return empty;
7710
+ const threshold = Math.max(0, Math.min(1, options.threshold ?? 0.7));
7711
+ const limit = Number.isFinite(options.limit) ? Math.max(0, Math.floor(options.limit)) : 10;
7712
+ const textSize = options.textSize ?? 100;
7713
+ const query = searchText.toLowerCase();
7714
+ const maxDistance = Math.floor(query.length * (1 - threshold));
7715
+ const distance = maxDistance > 0 ? createBoundedDistance(query, maxDistance) : null;
7716
+ const exactMatches = [];
7717
+ const fuzzyMatches = [];
7718
+ let exactCount = 0;
7719
+ let fuzzyCount = 0;
7720
+ let steps = 0;
7721
+ let deadline = cooperative ? performance.now() + budget : 0;
7722
+ for (const page of pages) {
7723
+ const lower = normalize(page);
7724
+ const exactStarts = [];
7725
+ let index = 0;
7726
+ while (true) {
7727
+ if (cooperative && (++steps & 127) === 0 && performance.now() >= deadline) {
7728
+ yield;
7729
+ deadline = performance.now() + budget;
7730
+ }
7731
+ const matchIndex = lower.indexOf(query, index);
7732
+ if (matchIndex === -1) break;
7733
+ exactCount++;
7734
+ if (maxDistance > 0) exactStarts.push(matchIndex);
7735
+ if (exactMatches.length < limit) {
7736
+ exactMatches.push({
7737
+ pageNumber: page.pageNumber,
7738
+ text: page.text.substr(matchIndex, searchText.length + textSize),
7739
+ score: 1,
7740
+ matchIndex,
7741
+ isExactMatch: true,
7742
+ searchText
7743
+ });
7744
+ }
7745
+ index = matchIndex + searchText.length;
7746
+ }
7747
+ if (!(maxDistance > 0)) continue;
7748
+ index = 0;
7749
+ let exactIndex = 0;
7750
+ while (index < lower.length) {
7751
+ if (cooperative && (++steps & 127) === 0 && performance.now() >= deadline) {
7752
+ yield;
7753
+ deadline = performance.now() + budget;
7754
+ }
7755
+ while (exactIndex < exactStarts.length && exactStarts[exactIndex] + searchText.length <= index)
7756
+ exactIndex++;
7757
+ const exactStart = exactStarts[exactIndex];
7758
+ if (exactStart !== void 0 && index >= exactStart) {
7759
+ index = exactStart + searchText.length;
7760
+ continue;
7761
+ }
7762
+ const edits = distance(lower, index);
7763
+ if (edits > 0 && edits <= maxDistance) {
7764
+ fuzzyCount++;
7765
+ const score = 1 - edits / query.length;
7766
+ if (fuzzyMatches.length < limit || score > (fuzzyMatches[limit - 1]?.score ?? Infinity)) {
7767
+ insertMatch(
7768
+ fuzzyMatches,
7769
+ {
7770
+ pageNumber: page.pageNumber,
7771
+ text: page.text.substr(index, query.length + textSize),
7772
+ score,
7773
+ matchIndex: index,
7774
+ isExactMatch: false,
7775
+ searchText
7776
+ },
7777
+ limit
7778
+ );
7779
+ }
7780
+ index += query.length;
7781
+ } else index++;
7427
7782
  }
7428
7783
  }
7429
- return matrix[b.length][a.length];
7784
+ const exactLimit = Math.min(
7785
+ exactCount,
7786
+ Math.max(Math.ceil(limit / 2), limit - fuzzyCount)
7787
+ );
7788
+ return {
7789
+ exactMatches: exactMatches.slice(0, exactLimit),
7790
+ fuzzyMatches: fuzzyMatches.slice(0, limit - exactLimit),
7791
+ hasMoreResults: exactCount + fuzzyCount > limit
7792
+ };
7793
+ }
7794
+ function searchDocument(pages, searchText, options = {}) {
7795
+ return runSearch(pages, searchText, options).next().value;
7796
+ }
7797
+ async function searchDocumentAsync(pages, searchText, options = {}) {
7798
+ const budget = Number.isFinite(options.timeSliceMs) ? Math.max(1, options.timeSliceMs) : 8;
7799
+ const iterator = runSearch(pages, searchText, options, true, budget);
7800
+ while (true) {
7801
+ if (options.signal?.aborted)
7802
+ throw new DOMException("Search aborted", "AbortError");
7803
+ const result = iterator.next();
7804
+ if (result.done) return result.value;
7805
+ await new Promise((resolve) => setTimeout(resolve, 0));
7806
+ }
7430
7807
  }
7808
+
7809
+ // src/hooks/search/useSearch.tsx
7431
7810
  var useSearch = () => {
7432
7811
  const textContent = usePdf((state) => state.textContent);
7812
+ const activeSearch = useRef(null);
7813
+ const [isSearching, setIsSearching] = useState(false);
7433
7814
  const [keywords] = useState([]);
7434
7815
  const [searchResults, setSearchResults] = useState({
7435
7816
  exactMatches: [],
7436
7817
  fuzzyMatches: [],
7437
7818
  hasMoreResults: false
7438
7819
  });
7439
- const findExactMatches = useCallback(
7440
- (searchText, text, pageNumber, textSize) => {
7441
- const results = [];
7442
- const textLower = text.toLowerCase();
7443
- const searchLower = searchText.toLowerCase();
7444
- let index = 0;
7445
- while (true) {
7446
- const matchIndex = textLower.indexOf(searchLower, index);
7447
- if (matchIndex === -1) break;
7448
- results.push({
7449
- pageNumber,
7450
- text: text.substr(matchIndex, searchText.length + textSize),
7451
- score: 1,
7452
- matchIndex,
7453
- isExactMatch: true,
7454
- searchText
7820
+ useEffect(() => {
7821
+ setIsSearching(false);
7822
+ return () => {
7823
+ activeSearch.current?.abort();
7824
+ activeSearch.current = null;
7825
+ };
7826
+ }, [textContent]);
7827
+ const cancelSearch = useCallback(() => {
7828
+ activeSearch.current?.abort();
7829
+ activeSearch.current = null;
7830
+ setIsSearching(false);
7831
+ }, []);
7832
+ const searchAsync = useCallback(
7833
+ async (searchText, options = {}) => {
7834
+ activeSearch.current?.abort();
7835
+ const controller = new AbortController();
7836
+ activeSearch.current = controller;
7837
+ const abort = () => controller.abort();
7838
+ if (options.signal?.aborted) abort();
7839
+ else options.signal?.addEventListener("abort", abort, { once: true });
7840
+ setIsSearching(true);
7841
+ try {
7842
+ const results = await searchDocumentAsync(textContent, searchText, {
7843
+ ...options,
7844
+ signal: controller.signal
7455
7845
  });
7456
- index = matchIndex + searchText.length;
7457
- }
7458
- return results;
7459
- },
7460
- []
7461
- );
7462
- const findFuzzyMatches = useCallback(
7463
- (searchText, text, pageNumber, threshold, excludeIndices, textSize) => {
7464
- const results = [];
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++;
7846
+ if (controller.signal.aborted)
7847
+ throw new DOMException("Search aborted", "AbortError");
7848
+ if (activeSearch.current === controller) setSearchResults(results);
7849
+ return results;
7850
+ } finally {
7851
+ options.signal?.removeEventListener("abort", abort);
7852
+ if (activeSearch.current === controller) {
7853
+ activeSearch.current = null;
7854
+ setIsSearching(false);
7492
7855
  }
7493
7856
  }
7494
- return results;
7495
7857
  },
7496
- []
7858
+ [textContent]
7497
7859
  );
7498
7860
  const search = useCallback(
7499
7861
  (searchText, options = {}) => {
7500
- const { threshold = 0.7, limit = 10, textSize = 100 } = options;
7501
- if (!searchText.trim()) {
7502
- const emptyResults = {
7503
- exactMatches: [],
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;
7862
+ cancelSearch();
7863
+ const results = searchDocument(textContent, searchText, options);
7864
+ setSearchResults(results);
7865
+ return results;
7549
7866
  },
7550
- [textContent, findExactMatches, findFuzzyMatches]
7867
+ [textContent, cancelSearch]
7551
7868
  );
7552
7869
  return {
7553
7870
  textContent,
7554
7871
  keywords,
7555
7872
  searchResults,
7556
- search
7873
+ search,
7874
+ searchAsync,
7875
+ cancelSearch,
7876
+ isSearching
7557
7877
  };
7558
7878
  };
7559
7879
 
7560
7880
  // src/hooks/search/useSearchPosition.tsx
7561
7881
  async function calculateHighlightRects(pageProxy, textMatch) {
7882
+ const matchLength = textMatch.searchText ? textMatch.searchText.length : textMatch.text.length;
7883
+ if (matchLength === 0 || !Number.isFinite(textMatch.matchIndex) || textMatch.matchIndex < 0)
7884
+ return [];
7885
+ const matchEnd = textMatch.matchIndex + matchLength;
7562
7886
  const reader = pageProxy.streamTextContent().getReader();
7563
- const items = [];
7887
+ const matchRects = [];
7888
+ const viewport = pageProxy.getViewport({ scale: 1 });
7889
+ let offset3 = 0;
7890
+ let foundEnd = false;
7564
7891
  try {
7565
- for (; ; ) {
7892
+ read: while (true) {
7566
7893
  const { value, done } = await reader.read();
7567
7894
  if (done) break;
7568
- items.push(
7569
- ...value.items.filter((item) => "str" in item)
7570
- );
7895
+ for (const item of value.items) {
7896
+ if (!("str" in item) || item.str.length === 0) continue;
7897
+ const length = item.str.length;
7898
+ const start = Math.max(0, textMatch.matchIndex - offset3);
7899
+ const end = Math.min(length, matchEnd - offset3);
7900
+ if (end > start) {
7901
+ matchRects.push({
7902
+ pageNumber: textMatch.pageNumber,
7903
+ left: item.transform[4] + start * (item.width / length),
7904
+ top: viewport.height - (item.transform[5] + item.height),
7905
+ width: (end - start) * (item.width / length),
7906
+ height: item.height
7907
+ });
7908
+ }
7909
+ offset3 += length;
7910
+ if (offset3 >= matchEnd) {
7911
+ foundEnd = true;
7912
+ break read;
7913
+ }
7914
+ }
7571
7915
  }
7572
7916
  } finally {
7917
+ if (foundEnd)
7918
+ void reader.cancel(new Error("Highlight range found")).catch(() => {
7919
+ });
7573
7920
  reader.releaseLock();
7574
7921
  }
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
7922
  return mergeAdjacentRects(matchRects);
7623
7923
  }
7624
7924
  function mergeAdjacentRects(rects) {
@@ -7646,4 +7946,4 @@ function mergeAdjacentRects(rects) {
7646
7946
  // src/hooks/usePageRendered.ts
7647
7947
  var usePageRendered = (pageNumber) => usePdf((state) => !!state.renderedPages[pageNumber]);
7648
7948
 
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 };
7949
+ 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 };