@anaralabs/lector 3.10.0 → 3.10.2

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.
@@ -38,7 +38,7 @@ declare interface Annotation {
38
38
  export { Annotation }
39
39
  export { Annotation as Annotation_alias_1 }
40
40
 
41
- export declare const AnnotationHighlightLayer: ({ className, style, renderTooltipContent, renderHoverTooltipContent, tooltipClassName, highlightClassName, underlineClassName, commentIconPosition, commmentIcon, commentIconClassName, focusedAnnotationId, focusedHoverAnnotationId, onAnnotationClick, onAnnotationTooltipClose, hoverTooltipClassName, }: AnnotationHighlightLayerProps) => JSX.Element;
41
+ export declare const AnnotationHighlightLayer: ({ className, style, renderTooltipContent, renderHoverTooltipContent, tooltipClassName, highlightClassName, underlineClassName, commentIconPosition, commmentIcon, commentIconClassName, focusedAnnotationId, focusedHoverAnnotationId, onAnnotationClick, onAnnotationTooltipClose, hoverTooltipClassName, }: AnnotationHighlightLayerProps) => JSX.Element | null;
42
42
 
43
43
  declare interface AnnotationHighlightLayerProps {
44
44
  className?: string;
@@ -124,6 +124,12 @@ declare interface AnnotationTooltipProps {
124
124
  }) => React.ReactNode;
125
125
  }
126
126
 
127
+ declare type CacheEntry = {
128
+ bitmap: ImageBitmap;
129
+ width: number;
130
+ height: number;
131
+ };
132
+
127
133
  /**
128
134
  * Calculates the highlight rectangles for a given text match.
129
135
  *
@@ -149,6 +155,8 @@ background?: string;
149
155
 
150
156
  export declare const clamp: (value: number, minimum: number, maximum: number) => number;
151
157
 
158
+ export declare function clampScaleForPage(targetScale: number, pageWidth: number, pageHeight: number): number;
159
+
152
160
  declare type CollapsibleSelection = {
153
161
  highlights: HighlightRect[];
154
162
  underlines?: HighlightRect[];
@@ -257,6 +265,10 @@ export declare class LinkService {
257
265
 
258
266
  export declare const loadPdfJs: () => Promise<pdfjsDist>;
259
267
 
268
+ export declare const MAX_CANVAS_DIMENSION = 32767;
269
+
270
+ export declare const MAX_CANVAS_PIXELS = 16777216;
271
+
260
272
  export declare const NextPage: () => void;
261
273
 
262
274
  export declare const Outline: ({ children, ...props }: HTMLProps<HTMLUListElement> & {
@@ -321,6 +333,9 @@ declare interface PDFState {
321
333
  coloredHighlights: ColoredHighlight[];
322
334
  addColoredHighlight: (value: ColoredHighlight) => void;
323
335
  deleteColoredHighlight: (uuid: string) => void;
336
+ renderedPages: Record<number, true>;
337
+ markPageRendered: (pageNumber: number) => void;
338
+ unmarkPageRendered: (pageNumber: number) => void;
324
339
  }
325
340
 
326
341
  export declare const PDFStore: {
@@ -336,6 +351,51 @@ export declare type PDFVirtualizer = Virtualizer<any, any>;
336
351
 
337
352
  export declare const PreviousPage: () => void;
338
353
 
354
+ /**
355
+ * LRU cache for rendered page bitmaps. When scrolling back to a previously
356
+ * rendered page at the same scale, we can draw from cache instantly instead
357
+ * of re-rendering through PDF.js.
358
+ *
359
+ * Cache keys include a document ID to prevent collisions when multiple
360
+ * <Root> instances render different PDFs on the same page.
361
+ */
362
+ declare class RenderCache {
363
+ private cache;
364
+ private maxEntries;
365
+ private invalidatedDocs;
366
+ constructor(maxEntries?: number);
367
+ private key;
368
+ get(documentId: string, pageNumber: number, scale: number, background?: string): CacheEntry | undefined;
369
+ set(documentId: string, pageNumber: number, scale: number, canvas: HTMLCanvasElement, background?: string): Promise<void>;
370
+ /** Remove all cached entries for a specific document (call on Root unmount). */
371
+ invalidateDocument(documentId: string): void;
372
+ clear(): void;
373
+ }
374
+
375
+ export declare const renderCache: RenderCache;
376
+
377
+ /**
378
+ * Serializes PDF page render operations so only one runs at a time.
379
+ * This prevents multiple heavy pages from competing for CPU and blocking the UI.
380
+ */
381
+ declare class RenderQueue {
382
+ private queue;
383
+ private running;
384
+ enqueue(execute: () => Promise<void>): {
385
+ promise: Promise<void>;
386
+ cancel: () => void;
387
+ };
388
+ private flush;
389
+ }
390
+
391
+ /**
392
+ * Global singleton — intentionally shared across all <Root> instances.
393
+ * Serializing renders across documents prevents overall GPU/CPU pressure
394
+ * on the main thread (CanvasGraphics is single-threaded). The trade-off
395
+ * is increased latency in multi-viewer setups.
396
+ */
397
+ export declare const renderQueue: RenderQueue;
398
+
339
399
  export declare const Root: ForwardRefExoticComponent<Omit<HTMLProps<HTMLDivElement> & usePDFDocumentParams & {
340
400
  loader?: ReactNode;
341
401
  }, "ref"> & RefAttributes<unknown>>;
@@ -426,11 +486,15 @@ declare interface UseAnnotationTooltipReturn {
426
486
 
427
487
  export declare const useCreatePDFLinkService: (pdfDocumentProxy: PDFDocumentProxy | null) => LinkService;
428
488
 
489
+ declare const usePageRendered: (pageNumber: number) => boolean;
490
+ export { usePageRendered }
491
+ export { usePageRendered as usePageRendered_alias_1 }
492
+
429
493
  declare const usePdf: <T>(selector: (state: PDFState) => T) => T;
430
494
  export { usePdf }
431
495
  export { usePdf as usePdf_alias_1 }
432
496
 
433
- export declare const usePDFDocumentContext: ({ onDocumentLoad, source, initialRotation, isZoomFitWidth, zoom, zoomOptions, }: usePDFDocumentParams) => {
497
+ export declare const usePDFDocumentContext: ({ onDocumentLoad, source, initialRotation, isZoomFitWidth, zoom, zoomOptions, documentOptions, }: usePDFDocumentParams) => {
434
498
  initialState: InitialPDFState | null | undefined;
435
499
  };
436
500
 
@@ -447,6 +511,12 @@ export declare interface usePDFDocumentParams {
447
511
  isZoomFitWidth?: boolean;
448
512
  zoom?: number;
449
513
  zoomOptions?: ZoomOptions;
514
+ /**
515
+ * Override or extend the PDF.js DocumentInitParameters passed to getDocument().
516
+ * These take highest precedence over both the source object and lector's defaults.
517
+ * Must be a stable reference (module-level constant or useMemo) to avoid reloading the document.
518
+ */
519
+ documentOptions?: Partial<DocumentInitParameters>;
450
520
  }
451
521
 
452
522
  export declare const usePdfJump: () => {
package/dist/index.d.ts CHANGED
@@ -31,6 +31,7 @@ export { useSearch } from './_tsup-dts-rollup.js';
31
31
  export { calculateHighlightRects } from './_tsup-dts-rollup.js';
32
32
  export { Annotation } from './_tsup-dts-rollup.js';
33
33
  export { useAnnotations } from './_tsup-dts-rollup.js';
34
+ export { usePageRendered } from './_tsup-dts-rollup.js';
34
35
  export { LinkService } from './_tsup-dts-rollup.js';
35
36
  export { PDFLinkServiceContext } from './_tsup-dts-rollup.js';
36
37
  export { useCreatePDFLinkService } from './_tsup-dts-rollup.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import React, { createContext, memo, forwardRef, cloneElement, useRef, useState, useCallback, useContext, useEffect, useMemo, useLayoutEffect, createRef } from 'react';
3
+ import React, { createContext, memo, forwardRef, useMemo, cloneElement, useEffect, useRef, useState, useCallback, useContext, useLayoutEffect, createRef } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { useFloating, autoUpdate, offset, shift, useDismiss, useInteractions, inline, flip } from '@floating-ui/react';
6
6
  import { create, useStore, createStore } from 'zustand';
@@ -124,7 +124,19 @@ var PDFStore = createZustandContext(
124
124
  coloredHighlights: prevState.coloredHighlights.filter(
125
125
  (rect) => rect.uuid !== uuid
126
126
  )
127
- }))
127
+ })),
128
+ renderedPages: {},
129
+ markPageRendered: (pageNumber) => {
130
+ const current = get().renderedPages;
131
+ if (current[pageNumber]) return;
132
+ set({ renderedPages: { ...current, [pageNumber]: true } });
133
+ },
134
+ unmarkPageRendered: (pageNumber) => {
135
+ const current = get().renderedPages;
136
+ if (!current[pageNumber]) return;
137
+ const { [pageNumber]: _, ...rest } = current;
138
+ set({ renderedPages: rest });
139
+ }
128
140
  }));
129
141
  }
130
142
  );
@@ -462,12 +474,15 @@ var AnnotationHighlightLayer = ({
462
474
  }) => {
463
475
  const { annotations } = useAnnotations();
464
476
  const pageNumber = usePDFPageNumber();
465
- const pageAnnotations = annotations.filter(
466
- (annotation) => annotation.pageNumber === pageNumber
477
+ const isPageRendered = usePdf((state) => !!state.renderedPages[pageNumber]);
478
+ const pageAnnotations = useMemo(
479
+ () => annotations.filter((a) => a.pageNumber === pageNumber),
480
+ [annotations, pageNumber]
467
481
  );
482
+ if (!isPageRendered) return null;
468
483
  const getCommentIconPosition = (highlights) => {
469
484
  if (!highlights.length) return { top: 0, right: 10 };
470
- const sortedHighlights = [...highlights].sort((a, b) => {
485
+ const sortedHighlights = highlights.toSorted((a, b) => {
471
486
  const topDiff = a.top - b.top;
472
487
  return Math.abs(topDiff) < 3 ? a.left - b.left : topDiff;
473
488
  });
@@ -691,7 +706,7 @@ var cancellable = (promise) => {
691
706
  var pdfJsPromise = null;
692
707
  var loadPdfJs = () => {
693
708
  if (!pdfJsPromise) {
694
- pdfJsPromise = import('pdfjs-dist');
709
+ pdfJsPromise = import('pdfjs-dist/legacy/build/pdf.mjs');
695
710
  }
696
711
  return pdfJsPromise;
697
712
  };
@@ -1099,6 +1114,146 @@ var AnnotationLayer = memo(
1099
1114
  }
1100
1115
  );
1101
1116
  AnnotationLayer.displayName = "AnnotationLayer";
1117
+
1118
+ // src/lib/canvas-utils.ts
1119
+ var MAX_CANVAS_PIXELS = 16777216;
1120
+ var MAX_CANVAS_DIMENSION = 32767;
1121
+ function clampScaleForPage(targetScale, pageWidth, pageHeight) {
1122
+ if (!targetScale) {
1123
+ return 0;
1124
+ }
1125
+ const areaLimit = Math.sqrt(
1126
+ MAX_CANVAS_PIXELS / Math.max(pageWidth * pageHeight, 1)
1127
+ );
1128
+ const widthLimit = MAX_CANVAS_DIMENSION / Math.max(pageWidth, 1);
1129
+ const heightLimit = MAX_CANVAS_DIMENSION / Math.max(pageHeight, 1);
1130
+ const safeScale = Math.min(
1131
+ targetScale,
1132
+ Number.isFinite(areaLimit) ? areaLimit : targetScale,
1133
+ Number.isFinite(widthLimit) ? widthLimit : targetScale,
1134
+ Number.isFinite(heightLimit) ? heightLimit : targetScale
1135
+ );
1136
+ return Math.max(safeScale, 0);
1137
+ }
1138
+
1139
+ // src/lib/render-cache.ts
1140
+ var RenderCache = class {
1141
+ cache = /* @__PURE__ */ new Map();
1142
+ maxEntries;
1143
+ // Tracks invalidated documents so in-flight createImageBitmap calls
1144
+ // don't insert orphaned entries after invalidateDocument() runs.
1145
+ invalidatedDocs = /* @__PURE__ */ new Set();
1146
+ constructor(maxEntries = 60) {
1147
+ this.maxEntries = maxEntries;
1148
+ }
1149
+ key(documentId, pageNumber, scale, background) {
1150
+ return `${documentId}-${pageNumber}-${scale}-${background ?? "white"}`;
1151
+ }
1152
+ get(documentId, pageNumber, scale, background) {
1153
+ const k = this.key(documentId, pageNumber, scale, background);
1154
+ const entry = this.cache.get(k);
1155
+ if (!entry) return void 0;
1156
+ this.cache.delete(k);
1157
+ this.cache.set(k, entry);
1158
+ return entry;
1159
+ }
1160
+ async set(documentId, pageNumber, scale, canvas, background) {
1161
+ const k = this.key(documentId, pageNumber, scale, background);
1162
+ const w = canvas.width;
1163
+ const h = canvas.height;
1164
+ if (w === 0 || h === 0) return;
1165
+ while (this.cache.size >= this.maxEntries) {
1166
+ const oldest = this.cache.keys().next().value;
1167
+ if (oldest !== void 0) {
1168
+ const entry = this.cache.get(oldest);
1169
+ entry?.bitmap.close();
1170
+ this.cache.delete(oldest);
1171
+ }
1172
+ }
1173
+ try {
1174
+ const bitmap = await createImageBitmap(canvas);
1175
+ if (this.invalidatedDocs.has(documentId)) {
1176
+ bitmap.close();
1177
+ return;
1178
+ }
1179
+ this.cache.set(k, {
1180
+ bitmap,
1181
+ width: w,
1182
+ height: h
1183
+ });
1184
+ } catch {
1185
+ }
1186
+ }
1187
+ /** Remove all cached entries for a specific document (call on Root unmount). */
1188
+ invalidateDocument(documentId) {
1189
+ this.invalidatedDocs.add(documentId);
1190
+ for (const [k, entry] of this.cache) {
1191
+ if (k.startsWith(`${documentId}-`)) {
1192
+ entry.bitmap.close();
1193
+ this.cache.delete(k);
1194
+ }
1195
+ }
1196
+ setTimeout(() => {
1197
+ this.invalidatedDocs.delete(documentId);
1198
+ }, 1e3);
1199
+ }
1200
+ clear() {
1201
+ for (const entry of this.cache.values()) {
1202
+ entry.bitmap.close();
1203
+ }
1204
+ this.cache.clear();
1205
+ this.invalidatedDocs.clear();
1206
+ }
1207
+ };
1208
+ var renderCache = new RenderCache();
1209
+
1210
+ // src/lib/render-queue.ts
1211
+ var RenderQueue = class {
1212
+ queue = [];
1213
+ running = false;
1214
+ enqueue(execute) {
1215
+ let resolve;
1216
+ let reject;
1217
+ const promise = new Promise((res, rej) => {
1218
+ resolve = res;
1219
+ reject = rej;
1220
+ });
1221
+ const job = {
1222
+ execute,
1223
+ resolve,
1224
+ reject,
1225
+ cancelled: false
1226
+ };
1227
+ this.queue.push(job);
1228
+ this.flush();
1229
+ return {
1230
+ promise,
1231
+ cancel: () => {
1232
+ job.cancelled = true;
1233
+ job.resolve();
1234
+ }
1235
+ };
1236
+ }
1237
+ async flush() {
1238
+ if (this.running) return;
1239
+ this.running = true;
1240
+ while (this.queue.length > 0) {
1241
+ const job = this.queue.shift();
1242
+ if (job.cancelled) {
1243
+ job.resolve();
1244
+ continue;
1245
+ }
1246
+ try {
1247
+ await job.execute();
1248
+ job.resolve();
1249
+ } catch (error) {
1250
+ job.reject(error);
1251
+ }
1252
+ }
1253
+ this.running = false;
1254
+ }
1255
+ };
1256
+ var renderQueue = new RenderQueue();
1102
1257
  var useDpr = () => {
1103
1258
  const [dpr, setDPR] = useState(
1104
1259
  typeof window === "undefined" ? 1 : Math.min(window.devicePixelRatio, 2)
@@ -1122,79 +1277,143 @@ var useDpr = () => {
1122
1277
  };
1123
1278
 
1124
1279
  // src/hooks/layers/useCanvasLayer.tsx
1125
- var MAX_CANVAS_PIXELS = 16777216;
1126
- var MAX_CANVAS_DIMENSION = 32767;
1127
1280
  var useCanvasLayer = ({ background }) => {
1128
1281
  const canvasRef = useRef(null);
1129
1282
  const pageNumber = usePDFPageNumber();
1130
1283
  const dpr = useDpr();
1131
1284
  const bouncyZoom = usePdf((state) => state.zoom);
1132
1285
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
1133
- const [zoom] = useDebounce(bouncyZoom, 100);
1134
- const clampScaleForPage = useCallback(
1135
- (targetScale, pageWidth, pageHeight) => {
1136
- if (!targetScale) {
1137
- return 0;
1138
- }
1139
- const areaLimit = Math.sqrt(
1140
- MAX_CANVAS_PIXELS / Math.max(pageWidth * pageHeight, 1)
1141
- );
1142
- const widthLimit = MAX_CANVAS_DIMENSION / Math.max(pageWidth, 1);
1143
- const heightLimit = MAX_CANVAS_DIMENSION / Math.max(pageHeight, 1);
1144
- const safeScale = Math.min(
1145
- targetScale,
1146
- Number.isFinite(areaLimit) ? areaLimit : targetScale,
1147
- Number.isFinite(widthLimit) ? widthLimit : targetScale,
1148
- Number.isFinite(heightLimit) ? heightLimit : targetScale
1149
- );
1150
- return Math.max(safeScale, 0);
1151
- },
1152
- []
1286
+ const markPageRendered = usePdf((state) => state.markPageRendered);
1287
+ const unmarkPageRendered = usePdf((state) => state.unmarkPageRendered);
1288
+ const documentId = usePdf(
1289
+ (state) => state.pdfDocumentProxy.fingerprints[0] ?? "default"
1153
1290
  );
1291
+ const [zoom] = useDebounce(bouncyZoom, 100);
1292
+ const lastRenderedScaleRef = useRef(0);
1293
+ const lastBackgroundRef = useRef(background);
1294
+ const lastPageProxyRef = useRef(pdfPageProxy);
1295
+ const pageDimsRef = useRef(null);
1296
+ if (!pageDimsRef.current || pageDimsRef.current.proxy !== pdfPageProxy) {
1297
+ const vp = pdfPageProxy.getViewport({ scale: 1 });
1298
+ pageDimsRef.current = {
1299
+ width: vp.width,
1300
+ height: vp.height,
1301
+ proxy: pdfPageProxy
1302
+ };
1303
+ }
1154
1304
  useLayoutEffect(() => {
1155
1305
  if (!canvasRef.current) {
1156
1306
  return;
1157
1307
  }
1158
- const baseCanvas = canvasRef.current;
1159
- const baseViewport = pdfPageProxy.getViewport({ scale: 1 });
1160
- const pageWidth = baseViewport.width;
1161
- const pageHeight = baseViewport.height;
1308
+ if (lastPageProxyRef.current !== pdfPageProxy || lastBackgroundRef.current !== background) {
1309
+ lastPageProxyRef.current = pdfPageProxy;
1310
+ lastBackgroundRef.current = background;
1311
+ lastRenderedScaleRef.current = 0;
1312
+ }
1313
+ const { width: pageWidth, height: pageHeight } = pageDimsRef.current;
1314
+ const bgColor = background ?? "white";
1162
1315
  const targetBaseScale = dpr * Math.min(zoom, 1);
1163
1316
  const baseScale = clampScaleForPage(targetBaseScale, pageWidth, pageHeight);
1164
- baseCanvas.width = Math.floor(pageWidth * baseScale);
1165
- baseCanvas.height = Math.floor(pageHeight * baseScale);
1166
- baseCanvas.style.position = "absolute";
1167
- baseCanvas.style.top = "0";
1168
- baseCanvas.style.left = "0";
1169
- baseCanvas.style.width = `${pageWidth}px`;
1170
- baseCanvas.style.height = `${pageHeight}px`;
1171
- baseCanvas.style.transform = "translate(0px, 0px)";
1172
- baseCanvas.style.zIndex = "0";
1173
- baseCanvas.style.pointerEvents = "none";
1174
- baseCanvas.style.backgroundColor = background ?? "white";
1175
- const context = baseCanvas.getContext("2d");
1176
- if (!context) {
1317
+ if (baseScale === lastRenderedScaleRef.current) {
1177
1318
  return;
1178
1319
  }
1179
- context.setTransform(1, 0, 0, 1, 0, 0);
1180
- context.clearRect(0, 0, baseCanvas.width, baseCanvas.height);
1181
- const viewport = pdfPageProxy.getViewport({ scale: baseScale });
1182
- const renderingTask = pdfPageProxy.render({
1183
- canvas: baseCanvas,
1184
- canvasContext: context,
1185
- viewport,
1186
- background
1187
- });
1188
- renderingTask.promise.catch((error) => {
1189
- if (error.name === "RenderingCancelledException") {
1190
- return;
1320
+ const w = Math.floor(pageWidth * baseScale);
1321
+ const h = Math.floor(pageHeight * baseScale);
1322
+ const cssText = `position:absolute;top:0;left:0;width:${pageWidth}px;height:${pageHeight}px;transform:translate(0px,0px);z-index:0;pointer-events:none;background-color:${bgColor}`;
1323
+ const swapToCanvas = (source, sw, sh) => {
1324
+ const canvas = canvasRef.current;
1325
+ if (!canvas) return;
1326
+ canvas.width = sw;
1327
+ canvas.height = sh;
1328
+ canvas.style.cssText = cssText;
1329
+ const ctx = canvas.getContext("2d");
1330
+ if (ctx) {
1331
+ ctx.drawImage(source, 0, 0);
1191
1332
  }
1192
- throw error;
1333
+ lastRenderedScaleRef.current = baseScale;
1334
+ };
1335
+ const cached = renderCache.get(
1336
+ documentId,
1337
+ pageNumber,
1338
+ baseScale,
1339
+ background
1340
+ );
1341
+ if (cached) {
1342
+ swapToCanvas(cached.bitmap, cached.width, cached.height);
1343
+ markPageRendered(pageNumber);
1344
+ return;
1345
+ }
1346
+ let cancelled = false;
1347
+ let activeRenderingTask = null;
1348
+ const job = renderQueue.enqueue(() => {
1349
+ return new Promise((resolve, reject) => {
1350
+ if (cancelled || !canvasRef.current) {
1351
+ resolve();
1352
+ return;
1353
+ }
1354
+ const buffer = document.createElement("canvas");
1355
+ buffer.width = w;
1356
+ buffer.height = h;
1357
+ const bufferCtx = buffer.getContext("2d");
1358
+ if (!bufferCtx) {
1359
+ resolve();
1360
+ return;
1361
+ }
1362
+ const viewport = pdfPageProxy.getViewport({ scale: baseScale });
1363
+ const renderingTask = pdfPageProxy.render({
1364
+ canvas: buffer,
1365
+ canvasContext: bufferCtx,
1366
+ viewport,
1367
+ background
1368
+ });
1369
+ activeRenderingTask = renderingTask;
1370
+ renderingTask.promise.then(() => {
1371
+ activeRenderingTask = null;
1372
+ if (cancelled || !canvasRef.current) {
1373
+ resolve();
1374
+ return;
1375
+ }
1376
+ swapToCanvas(buffer, w, h);
1377
+ markPageRendered(pageNumber);
1378
+ renderCache.set(documentId, pageNumber, baseScale, buffer, background).finally(() => {
1379
+ buffer.width = 0;
1380
+ buffer.height = 0;
1381
+ });
1382
+ resolve();
1383
+ }).catch((error) => {
1384
+ activeRenderingTask = null;
1385
+ if (error.name === "RenderingCancelledException") {
1386
+ resolve();
1387
+ return;
1388
+ }
1389
+ reject(error);
1390
+ });
1391
+ });
1193
1392
  });
1194
1393
  return () => {
1195
- void renderingTask.cancel();
1394
+ cancelled = true;
1395
+ job.cancel();
1396
+ void activeRenderingTask?.cancel();
1397
+ };
1398
+ }, [
1399
+ pdfPageProxy,
1400
+ background,
1401
+ dpr,
1402
+ zoom,
1403
+ pageNumber,
1404
+ documentId,
1405
+ markPageRendered
1406
+ ]);
1407
+ useEffect(() => {
1408
+ const canvas = canvasRef.current;
1409
+ return () => {
1410
+ unmarkPageRendered(pageNumber);
1411
+ if (canvas) {
1412
+ canvas.width = 1;
1413
+ canvas.height = 1;
1414
+ }
1196
1415
  };
1197
- }, [pdfPageProxy, background, dpr, zoom, clampScaleForPage]);
1416
+ }, [pageNumber, unmarkPageRendered]);
1198
1417
  return {
1199
1418
  canvasRef
1200
1419
  };
@@ -1264,8 +1483,6 @@ var subscribeToViewportInvalidation = (viewport, callback) => {
1264
1483
  };
1265
1484
 
1266
1485
  // src/hooks/layers/useDetailCanvasLayer.tsx
1267
- var MAX_CANVAS_PIXELS2 = 16777216;
1268
- var MAX_CANVAS_DIMENSION2 = 32767;
1269
1486
  var DETAIL_RENDER_IDLE_MS = 80;
1270
1487
  var useDetailCanvasLayer = ({
1271
1488
  background,
@@ -1280,57 +1497,33 @@ var useDetailCanvasLayer = ({
1280
1497
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
1281
1498
  const viewportRef = usePdf((state) => state.viewportRef);
1282
1499
  const [zoom] = useDebounce(bouncyZoom, 200);
1283
- const getDetailCanvas = useCallback(() => {
1284
- const detailCanvas = detailCanvasRef.current;
1285
- if (!detailCanvas) {
1286
- return null;
1287
- }
1288
- detailCanvas.style.position = "absolute";
1289
- detailCanvas.style.top = "0";
1290
- detailCanvas.style.left = "0";
1291
- detailCanvas.style.pointerEvents = "none";
1292
- detailCanvas.style.zIndex = "1";
1293
- detailCanvas.style.backgroundColor = background ?? "white";
1294
- return detailCanvas;
1295
- }, [background]);
1296
- const clampScaleForPage = useCallback(
1297
- (targetScale, pageWidth, pageHeight) => {
1298
- if (!targetScale) {
1299
- return 0;
1300
- }
1301
- const areaLimit = Math.sqrt(
1302
- MAX_CANVAS_PIXELS2 / Math.max(pageWidth * pageHeight, 1)
1303
- );
1304
- const widthLimit = MAX_CANVAS_DIMENSION2 / Math.max(pageWidth, 1);
1305
- const heightLimit = MAX_CANVAS_DIMENSION2 / Math.max(pageHeight, 1);
1306
- const safeScale = Math.min(
1307
- targetScale,
1308
- Number.isFinite(areaLimit) ? areaLimit : targetScale,
1309
- Number.isFinite(widthLimit) ? widthLimit : targetScale,
1310
- Number.isFinite(heightLimit) ? heightLimit : targetScale
1311
- );
1312
- return Math.max(safeScale, 0);
1313
- },
1314
- []
1315
- );
1500
+ const pageDimsRef = useRef(null);
1501
+ if (!pageDimsRef.current || pageDimsRef.current.proxy !== pdfPageProxy) {
1502
+ const vp = pdfPageProxy.getViewport({ scale: 1 });
1503
+ pageDimsRef.current = {
1504
+ width: vp.width,
1505
+ height: vp.height,
1506
+ proxy: pdfPageProxy
1507
+ };
1508
+ }
1316
1509
  useLayoutEffect(() => {
1317
1510
  const scrollContainer = viewportRef?.current;
1318
1511
  if (!scrollContainer) {
1319
1512
  return;
1320
1513
  }
1321
- const detailCanvas = getDetailCanvas();
1514
+ const detailCanvas = detailCanvasRef.current;
1322
1515
  const container = containerRef.current;
1323
1516
  if (!detailCanvas || !container) {
1324
1517
  return;
1325
1518
  }
1326
1519
  let renderingTask = null;
1327
1520
  let renderTimeoutId = null;
1521
+ const bgColor = background ?? "white";
1522
+ const detailBaseStyle = `position:absolute;top:0;left:0;pointer-events:none;z-index:1;background-color:${bgColor}`;
1328
1523
  const hideDetailCanvas = () => {
1329
1524
  renderingTask?.cancel();
1330
- detailCanvas.style.display = "block";
1331
- detailCanvas.style.opacity = "0";
1332
- container.style.transform = "";
1333
- container.style.transformOrigin = "";
1525
+ detailCanvas.style.cssText = `${detailBaseStyle};display:block;opacity:0`;
1526
+ container.style.cssText = "";
1334
1527
  };
1335
1528
  const renderDetailCanvas = () => {
1336
1529
  const pageContainer = baseCanvasRef.current?.parentElement ?? null;
@@ -1338,9 +1531,7 @@ var useDetailCanvasLayer = ({
1338
1531
  hideDetailCanvas();
1339
1532
  return;
1340
1533
  }
1341
- const baseViewport = pdfPageProxy.getViewport({ scale: 1 });
1342
- const pageWidth = baseViewport.width;
1343
- const pageHeight = baseViewport.height;
1534
+ const { width: pageWidth, height: pageHeight } = pageDimsRef.current;
1344
1535
  const scrollX = scrollContainer.scrollLeft / zoom;
1345
1536
  const scrollY = scrollContainer.scrollTop / zoom;
1346
1537
  const viewportWidth = scrollContainer.clientWidth / zoom;
@@ -1374,27 +1565,19 @@ var useDetailCanvasLayer = ({
1374
1565
  return;
1375
1566
  }
1376
1567
  renderingTask?.cancel();
1377
- detailCanvas.style.display = "block";
1378
- detailCanvas.style.opacity = "0";
1379
1568
  const pdfOffsetX = visibleLeft;
1380
1569
  const pdfOffsetY = visibleTop;
1381
1570
  const effectiveScale = targetDetailScale;
1382
1571
  const actualWidth = visibleWidth * effectiveScale;
1383
1572
  const actualHeight = visibleHeight * effectiveScale;
1384
- detailCanvas.width = actualWidth;
1385
- detailCanvas.height = actualHeight;
1386
- detailCanvas.style.width = `${visibleWidth * zoom}px`;
1387
- detailCanvas.style.height = `${visibleHeight * zoom}px`;
1388
- detailCanvas.style.transformOrigin = "center center";
1389
- detailCanvas.style.transform = `translate(${visibleLeft * zoom}px, ${visibleTop * zoom}px)`;
1390
- container.style.transform = `scale3d(${1 / zoom}, ${1 / zoom}, 1)`;
1391
- container.style.transformOrigin = "0 0";
1392
- const context = detailCanvas.getContext("2d");
1393
- if (!context) {
1573
+ const buffer = document.createElement("canvas");
1574
+ buffer.width = actualWidth;
1575
+ buffer.height = actualHeight;
1576
+ const bufferCtx = buffer.getContext("2d");
1577
+ if (!bufferCtx) {
1394
1578
  return;
1395
1579
  }
1396
- context.setTransform(1, 0, 0, 1, 0, 0);
1397
- context.clearRect(0, 0, detailCanvas.width, detailCanvas.height);
1580
+ bufferCtx.setTransform(1, 0, 0, 1, 0, 0);
1398
1581
  const transform = [
1399
1582
  1,
1400
1583
  0,
@@ -1407,16 +1590,22 @@ var useDetailCanvasLayer = ({
1407
1590
  scale: effectiveScale
1408
1591
  });
1409
1592
  const currentRenderingTask = pdfPageProxy.render({
1410
- canvas: detailCanvas,
1411
- canvasContext: context,
1593
+ canvas: buffer,
1594
+ canvasContext: bufferCtx,
1412
1595
  viewport: detailViewport,
1413
1596
  background,
1414
1597
  transform
1415
1598
  });
1416
1599
  renderingTask = currentRenderingTask;
1417
1600
  currentRenderingTask.promise.then(() => {
1418
- if (renderingTask === currentRenderingTask) {
1419
- detailCanvas.style.opacity = "1";
1601
+ if (renderingTask !== currentRenderingTask) return;
1602
+ detailCanvas.width = actualWidth;
1603
+ detailCanvas.height = actualHeight;
1604
+ detailCanvas.style.cssText = `${detailBaseStyle};display:block;opacity:1;width:${visibleWidth * zoom}px;height:${visibleHeight * zoom}px;transform-origin:center center;transform:translate(${visibleLeft * zoom}px,${visibleTop * zoom}px)`;
1605
+ container.style.cssText = `transform:scale3d(${1 / zoom},${1 / zoom},1);transform-origin:0 0`;
1606
+ const ctx = detailCanvas.getContext("2d");
1607
+ if (ctx) {
1608
+ ctx.drawImage(buffer, 0, 0);
1420
1609
  }
1421
1610
  }).catch((error) => {
1422
1611
  if (error.name === "RenderingCancelledException") {
@@ -1427,13 +1616,15 @@ var useDetailCanvasLayer = ({
1427
1616
  if (renderingTask === currentRenderingTask) {
1428
1617
  renderingTask = null;
1429
1618
  }
1619
+ buffer.width = 0;
1620
+ buffer.height = 0;
1430
1621
  });
1431
1622
  };
1432
1623
  const scheduleRender = (delay = DETAIL_RENDER_IDLE_MS) => {
1433
1624
  if (renderTimeoutId !== null) {
1434
1625
  clearTimeout(renderTimeoutId);
1435
1626
  }
1436
- hideDetailCanvas();
1627
+ renderingTask?.cancel();
1437
1628
  renderTimeoutId = setTimeout(() => {
1438
1629
  renderTimeoutId = null;
1439
1630
  renderDetailCanvas();
@@ -1443,7 +1634,11 @@ var useDetailCanvasLayer = ({
1443
1634
  scrollContainer,
1444
1635
  scheduleRender
1445
1636
  );
1446
- scheduleRender(isPinching ? DETAIL_RENDER_IDLE_MS * 2 : 0);
1637
+ if (zoom <= 1 || isPinching) {
1638
+ hideDetailCanvas();
1639
+ } else {
1640
+ scheduleRender(0);
1641
+ }
1447
1642
  return () => {
1448
1643
  unsubscribe();
1449
1644
  if (renderTimeoutId !== null) {
@@ -1458,10 +1653,17 @@ var useDetailCanvasLayer = ({
1458
1653
  background,
1459
1654
  dpr,
1460
1655
  viewportRef,
1461
- getDetailCanvas,
1462
- clampScaleForPage,
1463
1656
  baseCanvasRef
1464
1657
  ]);
1658
+ useEffect(() => {
1659
+ const canvas = detailCanvasRef.current;
1660
+ return () => {
1661
+ if (canvas) {
1662
+ canvas.width = 1;
1663
+ canvas.height = 1;
1664
+ }
1665
+ };
1666
+ }, []);
1465
1667
  return {
1466
1668
  detailCanvasRef,
1467
1669
  containerRef
@@ -2201,6 +2403,10 @@ var ColoredHighlightLayer = ({
2201
2403
  (state) => state.coloredHighlights
2202
2404
  );
2203
2405
  const addColoredHighlight = usePdf((state) => state.addColoredHighlight);
2406
+ const pageHighlights = useMemo(
2407
+ () => highlights.filter((s) => s.pageNumber === pageNumber),
2408
+ [highlights, pageNumber]
2409
+ );
2204
2410
  const handleHighlighting = useCallback(
2205
2411
  (color) => {
2206
2412
  const dimension = getDimension();
@@ -2222,13 +2428,7 @@ var ColoredHighlightLayer = ({
2222
2428
  [onHighlight, getDimension, addColoredHighlight]
2223
2429
  );
2224
2430
  return /* @__PURE__ */ jsxs("div", { className: "colored-highlights-layer", children: [
2225
- highlights.filter((selection) => selection.pageNumber === pageNumber).map((selection) => /* @__PURE__ */ jsx(
2226
- ColoredHighlightComponent,
2227
- {
2228
- selection
2229
- },
2230
- selection.uuid
2231
- )),
2431
+ pageHighlights.map((selection) => /* @__PURE__ */ jsx(ColoredHighlightComponent, { selection }, selection.uuid)),
2232
2432
  /* @__PURE__ */ jsx(
2233
2433
  ColorSelectionTool,
2234
2434
  {
@@ -2256,7 +2456,10 @@ var HighlightLayer = forwardRef(({ asChild, className, style, ...props }, ref) =
2256
2456
  const pageNumber = usePDFPageNumber();
2257
2457
  const highlights = usePdf((state) => state.highlights);
2258
2458
  const Comp = asChild ? Slot : "div";
2259
- const rects = highlights.filter((area) => area.pageNumber === pageNumber);
2459
+ const rects = useMemo(
2460
+ () => highlights.filter((area) => area.pageNumber === pageNumber),
2461
+ [highlights, pageNumber]
2462
+ );
2260
2463
  if (!rects?.length) return null;
2261
2464
  return /* @__PURE__ */ jsx(Fragment, { children: rects.map((rect, index) => {
2262
2465
  const { pageNumber: pageNumber2, type, style: customStyle, ...coordinates } = rect;
@@ -2831,6 +3034,7 @@ var useVisiblePage = ({ items }) => {
2831
3034
  const isPinching = usePdf((state) => state.isPinching);
2832
3035
  const setCurrentPage = usePdf((state) => state.setCurrentPage);
2833
3036
  const scrollElement = usePdf((state) => state.viewportRef?.current);
3037
+ const lastPageRef = useRef(0);
2834
3038
  const calculateVisiblePageIndex = useCallback(
2835
3039
  (virtualItems) => {
2836
3040
  if (!scrollElement || virtualItems.length === 0) return 0;
@@ -2854,7 +3058,11 @@ var useVisiblePage = ({ items }) => {
2854
3058
  useEffect(() => {
2855
3059
  if (!isPinching && items.length > 0) {
2856
3060
  const mostVisibleIndex = calculateVisiblePageIndex(items);
2857
- setCurrentPage?.(mostVisibleIndex + 1);
3061
+ const page = mostVisibleIndex + 1;
3062
+ if (page !== lastPageRef.current) {
3063
+ lastPageRef.current = page;
3064
+ setCurrentPage?.(page);
3065
+ }
2858
3066
  }
2859
3067
  }, [items, isPinching, calculateVisiblePageIndex, setCurrentPage]);
2860
3068
  return null;
@@ -4736,7 +4944,7 @@ var useViewportContainer = ({
4736
4944
  elementWrapperRef,
4737
4945
  elementRef
4738
4946
  }) => {
4739
- const [origin, setOrigin] = useState([0, 0]);
4947
+ const originRef = useRef([0, 0]);
4740
4948
  const wheelInertia = useRef({
4741
4949
  active: false,
4742
4950
  lastTime: 0,
@@ -4756,28 +4964,46 @@ var useViewportContainer = ({
4756
4964
  translateY: 0,
4757
4965
  zoom
4758
4966
  });
4967
+ const zoomRafRef = useRef(null);
4968
+ const lastPushedZoomRef = useRef(zoom);
4759
4969
  const updateTransform = useCallback(
4760
4970
  (zoomUpdate) => {
4761
4971
  if (!elementRef.current || !containerRef.current || !elementWrapperRef.current) {
4762
4972
  return;
4763
4973
  }
4764
4974
  const { zoom: zoom2, translateX, translateY } = transformations.current;
4975
+ const naturalWidth = parseFloat(elementRef.current.style.width) || elementRef.current.offsetWidth;
4976
+ const naturalHeight = parseFloat(elementRef.current.style.height) || elementRef.current.offsetHeight;
4765
4977
  elementRef.current.style.transform = `scale3d(${zoom2}, ${zoom2}, 1)`;
4766
- elementRef.current.style.willChange = "scale3d";
4767
- const elementBoundingBox = elementRef.current.getBoundingClientRect();
4768
- const width = elementBoundingBox.width;
4769
- elementWrapperRef.current.style.width = `${width}px`;
4770
- elementWrapperRef.current.style.height = `${elementBoundingBox.height}px`;
4978
+ elementWrapperRef.current.style.width = `${naturalWidth * zoom2}px`;
4979
+ elementWrapperRef.current.style.height = `${naturalHeight * zoom2}px`;
4771
4980
  containerRef.current.scrollTop = translateY;
4772
4981
  containerRef.current.scrollLeft = translateX;
4773
- if (zoomUpdate) updateZoom(() => transformations.current.zoom);
4982
+ if (zoomUpdate && zoomRafRef.current === null) {
4983
+ zoomRafRef.current = requestAnimationFrame(() => {
4984
+ zoomRafRef.current = null;
4985
+ const z = transformations.current.zoom;
4986
+ lastPushedZoomRef.current = z;
4987
+ updateZoom(() => z);
4988
+ });
4989
+ }
4774
4990
  },
4775
4991
  [containerRef, elementRef, elementWrapperRef, updateZoom]
4776
4992
  );
4993
+ useEffect(() => {
4994
+ return () => {
4995
+ if (zoomRafRef.current !== null) {
4996
+ cancelAnimationFrame(zoomRafRef.current);
4997
+ }
4998
+ };
4999
+ }, []);
4777
5000
  useEffect(() => {
4778
5001
  if (transformations.current.zoom === zoom || !containerRef.current) {
4779
5002
  return;
4780
5003
  }
5004
+ if (zoom === lastPushedZoomRef.current) {
5005
+ return;
5006
+ }
4781
5007
  const prevZoom = transformations.current.zoom;
4782
5008
  if (!prevZoom || !Number.isFinite(prevZoom)) {
4783
5009
  transformations.current = {
@@ -4856,7 +5082,7 @@ var useViewportContainer = ({
4856
5082
  useGesture(
4857
5083
  {
4858
5084
  onPinch: (state) => {
4859
- const { origin: origin2, first, movement, delta, event, memo: memo7 } = state;
5085
+ const { origin, first, movement, delta, event, memo: memo7 } = state;
4860
5086
  const [ms] = movement;
4861
5087
  const [deltaScale] = delta;
4862
5088
  const currentElement = elementRef.current;
@@ -4867,17 +5093,17 @@ var useViewportContainer = ({
4867
5093
  const containerRect = currentContainer.getBoundingClientRect();
4868
5094
  const currentZoom = transformations.current.zoom;
4869
5095
  const contentPosition = [
4870
- origin2[0] - elementRect.left,
4871
- origin2[1] - elementRect.top
5096
+ origin[0] - elementRect.left,
5097
+ origin[1] - elementRect.top
4872
5098
  ];
4873
5099
  const containerPosition = [
4874
- origin2[0] - containerRect.left,
4875
- origin2[1] - containerRect.top
5100
+ origin[0] - containerRect.left,
5101
+ origin[1] - containerRect.top
4876
5102
  ];
4877
- setOrigin([
5103
+ originRef.current = [
4878
5104
  contentPosition[0] / currentZoom,
4879
5105
  contentPosition[1] / currentZoom
4880
- ]);
5106
+ ];
4881
5107
  return {
4882
5108
  contentPosition,
4883
5109
  containerPosition,
@@ -4926,11 +5152,13 @@ var useViewportContainer = ({
4926
5152
  }
4927
5153
  );
4928
5154
  return {
4929
- origin
5155
+ origin: originRef.current
4930
5156
  };
4931
5157
  };
5158
+ var selectLargestPageWidth = (state) => state.viewports.reduce((max, vp) => Math.max(max, vp.width), 0);
4932
5159
  var DEFAULT_HEIGHT = 600;
4933
5160
  var EXTRA_HEIGHT = 0;
5161
+ var DEFAULT_VIRTUALIZER_OPTIONS = { overscan: 1 };
4934
5162
  var VirtualizedPageItem = memo(
4935
5163
  ({ child, virtualItem, innerBoxWidth }) => {
4936
5164
  return /* @__PURE__ */ jsx(
@@ -4963,7 +5191,7 @@ VirtualizedPageItem.displayName = "VirtualizedPageItem";
4963
5191
  var Pages = ({
4964
5192
  children,
4965
5193
  gap = 10,
4966
- virtualizerOptions = { overscan: 1 },
5194
+ virtualizerOptions = DEFAULT_VIRTUALIZER_OPTIONS,
4967
5195
  initialOffset,
4968
5196
  onOffsetChange,
4969
5197
  ...props
@@ -4983,12 +5211,16 @@ var Pages = ({
4983
5211
  const setVirtualizer = usePdf((state) => state.setVirtualizer);
4984
5212
  const { scrollToFn } = useScrollFn();
4985
5213
  const { observeElementOffset } = useObserveElement();
5214
+ const viewportsRef = useRef(viewports);
5215
+ viewportsRef.current = viewports;
4986
5216
  const estimateSize = useCallback(
4987
5217
  (index) => {
4988
- if (!viewports || !viewports[index]) return DEFAULT_HEIGHT;
4989
- return viewports[index].height + EXTRA_HEIGHT;
5218
+ const vp = viewportsRef.current;
5219
+ if (!vp || !vp[index]) return DEFAULT_HEIGHT;
5220
+ return vp[index].height + EXTRA_HEIGHT;
4990
5221
  },
4991
- [viewports]
5222
+ []
5223
+ // Stable — reads from ref
4992
5224
  );
4993
5225
  const virtualizer = useVirtualizer({
4994
5226
  count: numPages || 0,
@@ -5028,11 +5260,7 @@ var Pages = ({
5028
5260
  items
5029
5261
  });
5030
5262
  useFitWidth({ viewportRef: containerRef });
5031
- const largestPageWidth = usePdf(
5032
- (state) => state.viewports.reduce((largestWidth, viewport) => {
5033
- return Math.max(largestWidth, viewport.width);
5034
- }, 0)
5035
- );
5263
+ const largestPageWidth = usePdf(selectLargestPageWidth);
5036
5264
  useEffect(() => {
5037
5265
  virtualizer.getOffsetForAlignment = (toOffset, align, itemSize = 0) => {
5038
5266
  const size = virtualizer.getSize();
@@ -5084,6 +5312,7 @@ var Pages = ({
5084
5312
  alignItems: "center",
5085
5313
  flexDirection: "column",
5086
5314
  transformOrigin: "0 0",
5315
+ willChange: "transform",
5087
5316
  // width: "max-content",
5088
5317
  width: largestPageWidth,
5089
5318
  margin: "0 auto"
@@ -5108,17 +5337,44 @@ var Pages = ({
5108
5337
  }
5109
5338
  );
5110
5339
  };
5340
+ function buildDocumentInitParams(source, overrides) {
5341
+ let params;
5342
+ if (typeof source === "string" || source instanceof URL) {
5343
+ params = { url: source };
5344
+ } else if (source instanceof ArrayBuffer || ArrayBuffer.isView(source)) {
5345
+ params = { data: source };
5346
+ } else {
5347
+ params = { ...source };
5348
+ }
5349
+ const defaults = {
5350
+ // Skip the slow _guessMax binary-search auto-detection of max canvas area.
5351
+ // 1 GiB = 256 megapixels at 4 bytes/pixel. If the device can't allocate
5352
+ // this, pdfjs falls back gracefully via try/catch.
5353
+ canvasMaxAreaInBytes: 1024 * 1024 * 1024,
5354
+ // Enable GPU-backed canvases (willReadFrequently: false). PDF viewers are
5355
+ // render-dominated — they don't call getImageData() — so GPU compositing
5356
+ // is the correct path. This is the browser's default when no hint is given.
5357
+ enableHWA: true,
5358
+ // Explicitly opt-in to OffscreenCanvas in the worker for image processing.
5359
+ // Already the browser default, but being explicit avoids edge cases.
5360
+ isOffscreenCanvasSupported: true
5361
+ };
5362
+ return { ...defaults, ...params, ...overrides };
5363
+ }
5111
5364
  var usePDFDocumentContext = ({
5112
5365
  onDocumentLoad,
5113
5366
  source,
5114
5367
  initialRotation = 0,
5115
5368
  isZoomFitWidth,
5116
5369
  zoom = 1,
5117
- zoomOptions
5370
+ zoomOptions,
5371
+ documentOptions
5118
5372
  }) => {
5119
5373
  const [_, setProgress] = useState(0);
5120
5374
  const [initialState, setInitialState] = useState();
5121
5375
  const [rotation] = useState(initialRotation);
5376
+ const documentOptionsRef = useRef(documentOptions);
5377
+ documentOptionsRef.current = documentOptions;
5122
5378
  useEffect(() => {
5123
5379
  const generateViewports = async (pdf) => {
5124
5380
  const pageProxies = [];
@@ -5136,9 +5392,9 @@ var usePDFDocumentContext = ({
5136
5392
  return viewport;
5137
5393
  })
5138
5394
  );
5139
- const sortedPageProxies = pageProxies.sort((a, b) => {
5140
- return a.pageNumber - b.pageNumber;
5141
- });
5395
+ const sortedPageProxies = pageProxies.toSorted(
5396
+ (a, b) => a.pageNumber - b.pageNumber
5397
+ );
5142
5398
  setInitialState((prev) => ({
5143
5399
  ...prev,
5144
5400
  isZoomFitWidth,
@@ -5158,7 +5414,9 @@ var usePDFDocumentContext = ({
5158
5414
  if (isDisposed) {
5159
5415
  return;
5160
5416
  }
5161
- loadingTask = getDocument(source);
5417
+ loadingTask = getDocument(
5418
+ buildDocumentInitParams(source, documentOptionsRef.current)
5419
+ );
5162
5420
  loadingTask.onProgress = (progressEvent) => {
5163
5421
  if (progressEvent.loaded === progressEvent.total) {
5164
5422
  return;
@@ -5204,6 +5462,7 @@ var Root = forwardRef(
5204
5462
  isZoomFitWidth,
5205
5463
  zoom,
5206
5464
  zoomOptions,
5465
+ documentOptions,
5207
5466
  ...props
5208
5467
  }, ref) => {
5209
5468
  const { initialState } = usePDFDocumentContext({
@@ -5211,12 +5470,20 @@ var Root = forwardRef(
5211
5470
  onDocumentLoad,
5212
5471
  isZoomFitWidth,
5213
5472
  zoom,
5214
- zoomOptions
5473
+ zoomOptions,
5474
+ documentOptions
5215
5475
  });
5216
5476
  const linkService = useCreatePDFLinkService(
5217
5477
  initialState?.pdfDocumentProxy ?? null
5218
5478
  );
5219
- return /* @__PURE__ */ jsx(Primitive.div, { ref, ...props, children: initialState ? /* @__PURE__ */ jsx(PDFStore.Provider, { initialValue: initialState, children: /* @__PURE__ */ jsx(PDFLinkServiceContext.Provider, { value: linkService, children }) }) : loader || "Loading..." });
5479
+ const documentId = initialState?.pdfDocumentProxy.fingerprints[0];
5480
+ useEffect(() => {
5481
+ if (!documentId) return;
5482
+ return () => {
5483
+ renderCache.invalidateDocument(documentId);
5484
+ };
5485
+ }, [documentId]);
5486
+ return /* @__PURE__ */ jsx(Primitive.div, { ref, ...props, children: initialState ? /* @__PURE__ */ jsx(PDFStore.Provider, { initialValue: initialState, children: /* @__PURE__ */ jsx(PDFLinkServiceContext.Provider, { value: linkService, children }) }) : loader ?? "Loading..." });
5220
5487
  }
5221
5488
  );
5222
5489
  Root.displayName = "Root";
@@ -5266,8 +5533,8 @@ var useThumbnail = (pageNumber, config = {}) => {
5266
5533
  const isVisible = isFirstPage || debouncedVisible;
5267
5534
  useEffect(() => {
5268
5535
  const renderThumbnail = async () => {
5269
- const canvas = canvasRef.current;
5270
- if (!canvas || !pageProxy) return;
5536
+ const canvas2 = canvasRef.current;
5537
+ if (!canvas2 || !pageProxy) return;
5271
5538
  try {
5272
5539
  if (renderTaskRef.current) {
5273
5540
  renderTaskRef.current.cancel();
@@ -5275,12 +5542,12 @@ var useThumbnail = (pageNumber, config = {}) => {
5275
5542
  const viewport = pageProxy.getViewport({ scale: 1 });
5276
5543
  const scale = Math.min(maxWidth / viewport.width, maxHeight / viewport.height) * (isVisible ? dpr : 0.5);
5277
5544
  const scaledViewport = pageProxy.getViewport({ scale });
5278
- canvas.width = scaledViewport.width;
5279
- canvas.height = scaledViewport.height;
5280
- const context = canvas.getContext("2d");
5545
+ canvas2.width = scaledViewport.width;
5546
+ canvas2.height = scaledViewport.height;
5547
+ const context = canvas2.getContext("2d");
5281
5548
  if (!context) return;
5282
5549
  const renderTask = pageProxy.render({
5283
- canvas,
5550
+ canvas: canvas2,
5284
5551
  canvasContext: context,
5285
5552
  viewport: scaledViewport
5286
5553
  });
@@ -5295,8 +5562,13 @@ var useThumbnail = (pageNumber, config = {}) => {
5295
5562
  }
5296
5563
  };
5297
5564
  renderThumbnail();
5565
+ const canvas = canvasRef.current;
5298
5566
  return () => {
5299
5567
  renderTaskRef.current?.cancel();
5568
+ if (canvas) {
5569
+ canvas.width = 1;
5570
+ canvas.height = 1;
5571
+ }
5300
5572
  };
5301
5573
  }, [pageProxy, isVisible, dpr, maxHeight, maxWidth]);
5302
5574
  return {
@@ -5375,12 +5647,12 @@ var ZoomOut = ({ ...props }) => {
5375
5647
  );
5376
5648
  };
5377
5649
  var CurrentZoom = ({ ...props }) => {
5378
- const setRealZoom = usePdf((state) => state.updateZoom);
5650
+ const updateZoom = usePdf((state) => state.updateZoom);
5379
5651
  const realZoom = usePdf((state) => state.zoom);
5380
5652
  const [zoom, setZoom] = useState((realZoom * 100).toFixed(0));
5381
- const isSelected = useRef(false);
5653
+ const isInputFocused = useRef(false);
5382
5654
  useEffect(() => {
5383
- if (isSelected.current) {
5655
+ if (isInputFocused.current) {
5384
5656
  return;
5385
5657
  }
5386
5658
  setZoom((realZoom * 100).toFixed(0));
@@ -5391,14 +5663,14 @@ var CurrentZoom = ({ ...props }) => {
5391
5663
  ...props,
5392
5664
  value: zoom,
5393
5665
  onClick: () => {
5394
- isSelected.current = true;
5666
+ isInputFocused.current = true;
5395
5667
  },
5396
5668
  onChange: (e) => {
5397
- setRealZoom(Number(e.target.value) / 100);
5669
+ updateZoom(Number(e.target.value) / 100);
5398
5670
  setZoom(e.target.value);
5399
5671
  },
5400
5672
  onBlur: () => {
5401
- isSelected.current = false;
5673
+ isInputFocused.current = false;
5402
5674
  setZoom((realZoom * 100).toFixed(0));
5403
5675
  }
5404
5676
  }
@@ -5631,4 +5903,7 @@ function mergeAdjacentRects(rects) {
5631
5903
  return merged;
5632
5904
  }
5633
5905
 
5634
- export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePdf, usePdfJump, useSearch, useSelectionDimensions };
5906
+ // src/hooks/usePageRendered.ts
5907
+ var usePageRendered = (pageNumber) => usePdf((state) => !!state.renderedPages[pageNumber]);
5908
+
5909
+ export { AnnotationHighlightLayer, AnnotationLayer, AnnotationTooltip, CanvasLayer, ColoredHighlightLayer, CurrentPage, CurrentZoom, CustomLayer, HighlightLayer, LinkService, NextPage, Outline, OutlineChildItems, OutlineItem, PDFLinkServiceContext, Page, Pages, PreviousPage, Root, Search, SelectionTooltip, TextLayer, Thumbnail, Thumbnails, TotalPages, ZoomIn, ZoomOut, calculateHighlightRects, useAnnotations, useCreatePDFLinkService, usePDFLinkService, usePDFPageNumber, usePageRendered, usePdf, usePdfJump, useSearch, useSelectionDimensions };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@anaralabs/lector",
3
- "version": "3.10.0",
4
- "description": "Headless PDF viewer for React",
3
+ "version": "3.10.2",
4
+ "description": "A headless PDF viewer for React",
5
5
  "author": "andrewdr (https://github.com/andrewdoro)",
6
6
  "license": "MIT",
7
7
  "type": "module",