@anaralabs/lector 3.10.1 → 3.10.3

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.
@@ -124,12 +124,6 @@ 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
-
133
127
  /**
134
128
  * Calculates the highlight rectangles for a given text match.
135
129
  *
@@ -351,51 +345,6 @@ export declare type PDFVirtualizer = Virtualizer<any, any>;
351
345
 
352
346
  export declare const PreviousPage: () => void;
353
347
 
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
-
399
348
  export declare const Root: ForwardRefExoticComponent<Omit<HTMLProps<HTMLDivElement> & usePDFDocumentParams & {
400
349
  loader?: ReactNode;
401
350
  }, "ref"> & RefAttributes<unknown>>;
package/dist/index.js CHANGED
@@ -676,40 +676,6 @@ var ensureAnnotationLayerStyles = () => {
676
676
  style.textContent = annotationLayerStyles;
677
677
  document.head.appendChild(style);
678
678
  };
679
-
680
- // src/lib/cancellable.ts
681
- var cancellable = (promise) => {
682
- let isCancelled = false;
683
- const wrappedPromise = new Promise((resolve, reject) => {
684
- promise.then(
685
- (value) => {
686
- if (!isCancelled) {
687
- resolve(value);
688
- }
689
- },
690
- (error) => {
691
- if (!isCancelled) {
692
- reject(error);
693
- }
694
- }
695
- );
696
- });
697
- return {
698
- promise: wrappedPromise,
699
- cancel() {
700
- isCancelled = true;
701
- }
702
- };
703
- };
704
-
705
- // src/lib/pdfjs.ts
706
- var pdfJsPromise = null;
707
- var loadPdfJs = () => {
708
- if (!pdfJsPromise) {
709
- pdfJsPromise = import('pdfjs-dist/legacy/build/pdf.mjs');
710
- }
711
- return pdfJsPromise;
712
- };
713
679
  var usePdfJump = () => {
714
680
  const virtualizer = usePdf((state) => state.virtualizer);
715
681
  const setHighlight = usePdf((state) => state.setHighlight);
@@ -1041,13 +1007,15 @@ var useAnnotationLayer = (params) => {
1041
1007
  if (linkService._pdfDocumentProxy !== pdfDocumentProxy) {
1042
1008
  linkService.setDocument(pdfDocumentProxy);
1043
1009
  }
1044
- annotationLayerRef.current.innerHTML = "";
1045
- annotationLayerRef.current.className = "annotationLayer";
1010
+ const container = annotationLayerRef.current;
1011
+ let cancelled = false;
1012
+ container.replaceChildren();
1013
+ container.className = "annotationLayer";
1046
1014
  const viewport = pdfPageProxy.getViewport({ scale: 1 }).clone({
1047
1015
  dontFlip: true
1048
1016
  });
1049
1017
  const annotationLayerConfig = {
1050
- div: annotationLayerRef.current,
1018
+ div: container,
1051
1019
  viewport,
1052
1020
  page: pdfPageProxy,
1053
1021
  linkService,
@@ -1058,25 +1026,26 @@ var useAnnotationLayer = (params) => {
1058
1026
  structTreeLayer: void 0,
1059
1027
  commentManager: void 0
1060
1028
  };
1061
- const { cancel } = cancellable(
1062
- (async () => {
1063
- try {
1064
- const { AnnotationLayer: AnnotationLayer2 } = await loadPdfJs();
1065
- const annotationLayer = new AnnotationLayer2(annotationLayerConfig);
1066
- annotationLayerObjectRef.current = annotationLayer;
1067
- const annotations = await pdfPageProxy.getAnnotations();
1068
- await annotationLayer.render({
1069
- ...annotationLayerConfig,
1070
- ...mergedParams,
1071
- annotations,
1072
- linkService
1073
- });
1074
- } catch (_error) {
1075
- }
1076
- })()
1077
- );
1029
+ (async () => {
1030
+ try {
1031
+ if (cancelled) return;
1032
+ const { AnnotationLayer: AnnotationLayer2 } = await import('pdfjs-dist/legacy/build/pdf.mjs');
1033
+ if (cancelled) return;
1034
+ const annotationLayer = new AnnotationLayer2(annotationLayerConfig);
1035
+ annotationLayerObjectRef.current = annotationLayer;
1036
+ const annotations = await pdfPageProxy.getAnnotations();
1037
+ if (cancelled) return;
1038
+ await annotationLayer.render({
1039
+ ...annotationLayerConfig,
1040
+ ...mergedParams,
1041
+ annotations,
1042
+ linkService
1043
+ });
1044
+ } catch (_error) {
1045
+ }
1046
+ })();
1078
1047
  return () => {
1079
- cancel();
1048
+ cancelled = true;
1080
1049
  };
1081
1050
  }, [pdfPageProxy, pdfDocumentProxy, mergedParams, linkService]);
1082
1051
  return {
@@ -1135,125 +1104,6 @@ function clampScaleForPage(targetScale, pageWidth, pageHeight) {
1135
1104
  );
1136
1105
  return Math.max(safeScale, 0);
1137
1106
  }
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 = 30) {
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();
1257
1107
  var useDpr = () => {
1258
1108
  const [dpr, setDPR] = useState(
1259
1109
  typeof window === "undefined" ? 1 : Math.min(window.devicePixelRatio, 2)
@@ -1277,116 +1127,149 @@ var useDpr = () => {
1277
1127
  };
1278
1128
 
1279
1129
  // src/hooks/layers/useCanvasLayer.tsx
1130
+ var CACHE_MAX = 60;
1131
+ var cacheEntries = [];
1132
+ var canvasBitmapCache = /* @__PURE__ */ new WeakMap();
1133
+ function clearBitmapCache(documentId) {
1134
+ if (documentId === void 0) {
1135
+ for (const entry of cacheEntries) {
1136
+ entry.bitmap.close();
1137
+ canvasBitmapCache.get(entry.proxy)?.delete(entry.key);
1138
+ }
1139
+ cacheEntries.length = 0;
1140
+ return;
1141
+ }
1142
+ for (let i = cacheEntries.length - 1; i >= 0; i--) {
1143
+ const entry = cacheEntries[i];
1144
+ if (entry.docId !== documentId) continue;
1145
+ entry.bitmap.close();
1146
+ canvasBitmapCache.get(entry.proxy)?.delete(entry.key);
1147
+ cacheEntries.splice(i, 1);
1148
+ }
1149
+ }
1150
+ function cacheKey(scale, background) {
1151
+ const bg = background ?? "white";
1152
+ let hash = Math.round(scale * 1e4);
1153
+ for (let i = 0; i < bg.length; i++) {
1154
+ hash = hash * 31 + bg.charCodeAt(i) | 0;
1155
+ }
1156
+ return hash;
1157
+ }
1158
+ function getCachedBitmap(proxy, scale, background) {
1159
+ const key = cacheKey(scale, background);
1160
+ const bitmap = canvasBitmapCache.get(proxy)?.get(key);
1161
+ if (!bitmap) return null;
1162
+ const idx = cacheEntries.findIndex((e) => e.proxy === proxy && e.key === key);
1163
+ if (idx !== -1 && idx !== cacheEntries.length - 1) {
1164
+ const [entry] = cacheEntries.splice(idx, 1);
1165
+ cacheEntries.push(entry);
1166
+ }
1167
+ return bitmap;
1168
+ }
1169
+ function setCachedBitmap(docId, proxy, scale, background, bitmap) {
1170
+ const key = cacheKey(scale, background);
1171
+ let map = canvasBitmapCache.get(proxy);
1172
+ if (!map) {
1173
+ map = /* @__PURE__ */ new Map();
1174
+ canvasBitmapCache.set(proxy, map);
1175
+ }
1176
+ const existing = map.get(key);
1177
+ if (existing && existing !== bitmap) {
1178
+ existing.close();
1179
+ const idx = cacheEntries.findIndex(
1180
+ (e) => e.proxy === proxy && e.key === key
1181
+ );
1182
+ if (idx !== -1) cacheEntries.splice(idx, 1);
1183
+ }
1184
+ map.set(key, bitmap);
1185
+ cacheEntries.push({ docId, proxy, key, bitmap });
1186
+ while (cacheEntries.length > CACHE_MAX) {
1187
+ const evicted = cacheEntries.shift();
1188
+ const evictedMap = canvasBitmapCache.get(evicted.proxy);
1189
+ if (evictedMap?.get(evicted.key) === evicted.bitmap) {
1190
+ evictedMap.delete(evicted.key);
1191
+ evicted.bitmap.close();
1192
+ }
1193
+ }
1194
+ }
1280
1195
  var useCanvasLayer = ({ background }) => {
1281
1196
  const canvasRef = useRef(null);
1282
1197
  const pageNumber = usePDFPageNumber();
1283
1198
  const dpr = useDpr();
1284
1199
  const bouncyZoom = usePdf((state) => state.zoom);
1200
+ const docId = usePdf((state) => state.pdfDocumentProxy.fingerprints[0] ?? "");
1285
1201
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
1286
1202
  const markPageRendered = usePdf((state) => state.markPageRendered);
1287
1203
  const unmarkPageRendered = usePdf((state) => state.unmarkPageRendered);
1288
- const documentId = usePdf(
1289
- (state) => state.pdfDocumentProxy.fingerprints[0] ?? "default"
1290
- );
1291
1204
  const [zoom] = useDebounce(bouncyZoom, 100);
1292
- const lastRenderedScaleRef = useRef(0);
1293
- const lastBackgroundRef = useRef(background);
1294
- const lastPageProxyRef = useRef(pdfPageProxy);
1295
1205
  useLayoutEffect(() => {
1296
- if (!canvasRef.current) {
1297
- return;
1298
- }
1299
- if (lastPageProxyRef.current !== pdfPageProxy || lastBackgroundRef.current !== background) {
1300
- lastPageProxyRef.current = pdfPageProxy;
1301
- lastBackgroundRef.current = background;
1302
- lastRenderedScaleRef.current = 0;
1303
- }
1206
+ if (!canvasRef.current) return;
1207
+ const baseCanvas = canvasRef.current;
1304
1208
  const baseViewport = pdfPageProxy.getViewport({ scale: 1 });
1305
1209
  const pageWidth = baseViewport.width;
1306
1210
  const pageHeight = baseViewport.height;
1307
- const bgColor = background ?? "white";
1308
1211
  const targetBaseScale = dpr * Math.min(zoom, 1);
1309
1212
  const baseScale = clampScaleForPage(targetBaseScale, pageWidth, pageHeight);
1310
- if (baseScale === lastRenderedScaleRef.current) {
1311
- return;
1312
- }
1313
- const w = Math.floor(pageWidth * baseScale);
1314
- const h = Math.floor(pageHeight * baseScale);
1315
- 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}`;
1316
- const swapToCanvas = (source, sw, sh) => {
1317
- const canvas = canvasRef.current;
1318
- if (!canvas) return;
1319
- canvas.width = sw;
1320
- canvas.height = sh;
1321
- canvas.style.cssText = cssText;
1322
- const ctx = canvas.getContext("2d");
1323
- if (ctx) {
1324
- ctx.drawImage(source, 0, 0);
1325
- }
1326
- lastRenderedScaleRef.current = baseScale;
1327
- };
1328
- const cached = renderCache.get(
1329
- documentId,
1330
- pageNumber,
1331
- baseScale,
1332
- background
1333
- );
1213
+ baseCanvas.width = Math.floor(pageWidth * baseScale);
1214
+ baseCanvas.height = Math.floor(pageHeight * baseScale);
1215
+ baseCanvas.style.position = "absolute";
1216
+ baseCanvas.style.top = "0";
1217
+ baseCanvas.style.left = "0";
1218
+ baseCanvas.style.width = `${pageWidth}px`;
1219
+ baseCanvas.style.height = `${pageHeight}px`;
1220
+ baseCanvas.style.transform = "translate(0px, 0px)";
1221
+ baseCanvas.style.zIndex = "0";
1222
+ baseCanvas.style.pointerEvents = "none";
1223
+ baseCanvas.style.backgroundColor = background ?? "white";
1224
+ baseCanvas.style.visibility = "";
1225
+ const context = baseCanvas.getContext("2d");
1226
+ if (!context) return;
1227
+ const cached = getCachedBitmap(pdfPageProxy, baseScale, background);
1334
1228
  if (cached) {
1335
- swapToCanvas(cached.bitmap, cached.width, cached.height);
1229
+ context.drawImage(cached, 0, 0);
1336
1230
  markPageRendered(pageNumber);
1337
1231
  return;
1338
1232
  }
1233
+ context.setTransform(1, 0, 0, 1, 0, 0);
1234
+ context.clearRect(0, 0, baseCanvas.width, baseCanvas.height);
1235
+ baseCanvas.style.visibility = "hidden";
1339
1236
  let cancelled = false;
1340
- let activeRenderingTask = null;
1341
- const job = renderQueue.enqueue(() => {
1342
- return new Promise((resolve, reject) => {
1343
- if (cancelled || !canvasRef.current) {
1344
- resolve();
1345
- return;
1346
- }
1347
- const buffer = document.createElement("canvas");
1348
- buffer.width = w;
1349
- buffer.height = h;
1350
- const bufferCtx = buffer.getContext("2d");
1351
- if (!bufferCtx) {
1352
- resolve();
1353
- return;
1354
- }
1355
- const viewport = pdfPageProxy.getViewport({ scale: baseScale });
1356
- const renderingTask = pdfPageProxy.render({
1357
- canvas: buffer,
1358
- canvasContext: bufferCtx,
1359
- viewport,
1360
- background
1361
- });
1362
- activeRenderingTask = renderingTask;
1363
- renderingTask.promise.then(() => {
1364
- activeRenderingTask = null;
1365
- if (cancelled || !canvasRef.current) {
1366
- resolve();
1367
- return;
1368
- }
1369
- swapToCanvas(buffer, w, h);
1370
- markPageRendered(pageNumber);
1371
- renderCache.set(documentId, pageNumber, baseScale, buffer, background).finally(() => {
1372
- buffer.width = 0;
1373
- buffer.height = 0;
1374
- });
1375
- resolve();
1376
- }).catch((error) => {
1377
- activeRenderingTask = null;
1378
- if (error.name === "RenderingCancelledException") {
1379
- resolve();
1237
+ const viewport = pdfPageProxy.getViewport({ scale: baseScale });
1238
+ const renderingTask = pdfPageProxy.render({
1239
+ canvas: baseCanvas,
1240
+ canvasContext: context,
1241
+ viewport,
1242
+ background
1243
+ });
1244
+ renderingTask.promise.then(() => {
1245
+ if (cancelled) return;
1246
+ baseCanvas.style.visibility = "";
1247
+ markPageRendered(pageNumber);
1248
+ if (typeof createImageBitmap !== "undefined") {
1249
+ createImageBitmap(baseCanvas).then((bitmap) => {
1250
+ if (cancelled) {
1251
+ bitmap.close();
1380
1252
  return;
1381
1253
  }
1382
- reject(error);
1254
+ setCachedBitmap(
1255
+ docId,
1256
+ pdfPageProxy,
1257
+ baseScale,
1258
+ background,
1259
+ bitmap
1260
+ );
1261
+ }).catch(() => {
1383
1262
  });
1384
- });
1263
+ }
1264
+ }).catch((error) => {
1265
+ if (cancelled) return;
1266
+ if (error?.name === "RenderingCancelledException") return;
1267
+ baseCanvas.style.visibility = "";
1268
+ console.error("PDF render error:", error);
1385
1269
  });
1386
1270
  return () => {
1387
1271
  cancelled = true;
1388
- job.cancel();
1389
- void activeRenderingTask?.cancel();
1272
+ void renderingTask.cancel();
1390
1273
  };
1391
1274
  }, [
1392
1275
  pdfPageProxy,
@@ -1394,16 +1277,16 @@ var useCanvasLayer = ({ background }) => {
1394
1277
  dpr,
1395
1278
  zoom,
1396
1279
  pageNumber,
1397
- documentId,
1398
- markPageRendered
1280
+ markPageRendered,
1281
+ docId
1399
1282
  ]);
1400
1283
  useEffect(() => {
1401
1284
  const canvas = canvasRef.current;
1402
1285
  return () => {
1403
1286
  unmarkPageRendered(pageNumber);
1404
1287
  if (canvas) {
1405
- canvas.width = 1;
1406
- canvas.height = 1;
1288
+ canvas.width = 0;
1289
+ canvas.height = 0;
1407
1290
  }
1408
1291
  };
1409
1292
  }, [pageNumber, unmarkPageRendered]);
@@ -1490,6 +1373,15 @@ var useDetailCanvasLayer = ({
1490
1373
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
1491
1374
  const viewportRef = usePdf((state) => state.viewportRef);
1492
1375
  const [zoom] = useDebounce(bouncyZoom, 200);
1376
+ const pageDimsRef = useRef(null);
1377
+ if (!pageDimsRef.current || pageDimsRef.current.proxy !== pdfPageProxy) {
1378
+ const vp = pdfPageProxy.getViewport({ scale: 1 });
1379
+ pageDimsRef.current = {
1380
+ width: vp.width,
1381
+ height: vp.height,
1382
+ proxy: pdfPageProxy
1383
+ };
1384
+ }
1493
1385
  useLayoutEffect(() => {
1494
1386
  const scrollContainer = viewportRef?.current;
1495
1387
  if (!scrollContainer) {
@@ -1515,9 +1407,7 @@ var useDetailCanvasLayer = ({
1515
1407
  hideDetailCanvas();
1516
1408
  return;
1517
1409
  }
1518
- const baseViewport = pdfPageProxy.getViewport({ scale: 1 });
1519
- const pageWidth = baseViewport.width;
1520
- const pageHeight = baseViewport.height;
1410
+ const { width: pageWidth, height: pageHeight } = pageDimsRef.current;
1521
1411
  const scrollX = scrollContainer.scrollLeft / zoom;
1522
1412
  const scrollY = scrollContainer.scrollTop / zoom;
1523
1413
  const viewportWidth = scrollContainer.clientWidth / zoom;
@@ -2633,10 +2523,8 @@ var useTextLayer = () => {
2633
2523
  textLayerRef.current.cancel();
2634
2524
  textLayerRef.current = null;
2635
2525
  }
2636
- void loadPdfJs().then(({ TextLayer: TextLayer2 }) => {
2637
- if (isCancelled || textContainerRef.current !== textContainer) {
2638
- return;
2639
- }
2526
+ void import('pdfjs-dist/legacy/build/pdf.mjs').then(({ TextLayer: TextLayer2 }) => {
2527
+ if (isCancelled || textContainerRef.current !== textContainer) return;
2640
2528
  const textLayer = new TextLayer2({
2641
2529
  textContentSource: pdfPageProxy.streamTextContent(),
2642
2530
  container: textContainer,
@@ -2645,8 +2533,7 @@ var useTextLayer = () => {
2645
2533
  textLayerRef.current = textLayer;
2646
2534
  return textLayer.render();
2647
2535
  }).then(() => {
2648
- const textLayer = textLayerRef.current;
2649
- if (!textLayer || isCancelled || textContainerRef.current !== textContainer) {
2536
+ if (isCancelled || textContainerRef.current !== textContainer) {
2650
2537
  return;
2651
2538
  }
2652
2539
  const endOfContent = document.createElement("div");
@@ -2654,7 +2541,7 @@ var useTextLayer = () => {
2654
2541
  textContainer.appendChild(endOfContent);
2655
2542
  bindMouseEvents(textContainer, endOfContent);
2656
2543
  }).catch((error) => {
2657
- if (error.name !== "AbortException") {
2544
+ if (error instanceof Error && error.name !== "AbortException") {
2658
2545
  console.error("TextLayer rendering error:", error);
2659
2546
  }
2660
2547
  }).finally(() => {
@@ -2701,6 +2588,33 @@ var TextLayer = memo(
2701
2588
  }
2702
2589
  );
2703
2590
  TextLayer.displayName = "TextLayer";
2591
+
2592
+ // src/lib/cancellable.ts
2593
+ var cancellable = (promise) => {
2594
+ let isCancelled = false;
2595
+ const wrappedPromise = new Promise((resolve, reject) => {
2596
+ promise.then(
2597
+ (value) => {
2598
+ if (!isCancelled) {
2599
+ resolve(value);
2600
+ }
2601
+ },
2602
+ (error) => {
2603
+ if (!isCancelled) {
2604
+ reject(error);
2605
+ }
2606
+ }
2607
+ );
2608
+ });
2609
+ return {
2610
+ promise: wrappedPromise,
2611
+ cancel() {
2612
+ isCancelled = true;
2613
+ }
2614
+ };
2615
+ };
2616
+
2617
+ // src/hooks/usePdfOutline.tsx
2704
2618
  var usePDFOutline = () => {
2705
2619
  const pdfDocumentProxy = usePdf((state) => state.pdfDocumentProxy);
2706
2620
  const [outline, setOutline] = useState();
@@ -4930,7 +4844,7 @@ var useViewportContainer = ({
4930
4844
  elementWrapperRef,
4931
4845
  elementRef
4932
4846
  }) => {
4933
- const [origin, setOrigin] = useState([0, 0]);
4847
+ const originRef = useRef([0, 0]);
4934
4848
  const wheelInertia = useRef({
4935
4849
  active: false,
4936
4850
  lastTime: 0,
@@ -4950,28 +4864,46 @@ var useViewportContainer = ({
4950
4864
  translateY: 0,
4951
4865
  zoom
4952
4866
  });
4867
+ const zoomRafRef = useRef(null);
4868
+ const lastPushedZoomRef = useRef(zoom);
4953
4869
  const updateTransform = useCallback(
4954
4870
  (zoomUpdate) => {
4955
4871
  if (!elementRef.current || !containerRef.current || !elementWrapperRef.current) {
4956
4872
  return;
4957
4873
  }
4958
4874
  const { zoom: zoom2, translateX, translateY } = transformations.current;
4875
+ const naturalWidth = parseFloat(elementRef.current.style.width) || elementRef.current.offsetWidth;
4876
+ const naturalHeight = parseFloat(elementRef.current.style.height) || elementRef.current.offsetHeight;
4959
4877
  elementRef.current.style.transform = `scale3d(${zoom2}, ${zoom2}, 1)`;
4960
- elementRef.current.style.willChange = "scale3d";
4961
- const elementBoundingBox = elementRef.current.getBoundingClientRect();
4962
- const width = elementBoundingBox.width;
4963
- elementWrapperRef.current.style.width = `${width}px`;
4964
- elementWrapperRef.current.style.height = `${elementBoundingBox.height}px`;
4878
+ elementWrapperRef.current.style.width = `${naturalWidth * zoom2}px`;
4879
+ elementWrapperRef.current.style.height = `${naturalHeight * zoom2}px`;
4965
4880
  containerRef.current.scrollTop = translateY;
4966
4881
  containerRef.current.scrollLeft = translateX;
4967
- if (zoomUpdate) updateZoom(() => transformations.current.zoom);
4882
+ if (zoomUpdate && zoomRafRef.current === null) {
4883
+ zoomRafRef.current = requestAnimationFrame(() => {
4884
+ zoomRafRef.current = null;
4885
+ const z = transformations.current.zoom;
4886
+ lastPushedZoomRef.current = z;
4887
+ updateZoom(() => z);
4888
+ });
4889
+ }
4968
4890
  },
4969
4891
  [containerRef, elementRef, elementWrapperRef, updateZoom]
4970
4892
  );
4893
+ useEffect(() => {
4894
+ return () => {
4895
+ if (zoomRafRef.current !== null) {
4896
+ cancelAnimationFrame(zoomRafRef.current);
4897
+ }
4898
+ };
4899
+ }, []);
4971
4900
  useEffect(() => {
4972
4901
  if (transformations.current.zoom === zoom || !containerRef.current) {
4973
4902
  return;
4974
4903
  }
4904
+ if (zoom === lastPushedZoomRef.current) {
4905
+ return;
4906
+ }
4975
4907
  const prevZoom = transformations.current.zoom;
4976
4908
  if (!prevZoom || !Number.isFinite(prevZoom)) {
4977
4909
  transformations.current = {
@@ -5050,7 +4982,7 @@ var useViewportContainer = ({
5050
4982
  useGesture(
5051
4983
  {
5052
4984
  onPinch: (state) => {
5053
- const { origin: origin2, first, movement, delta, event, memo: memo7 } = state;
4985
+ const { origin, first, movement, delta, event, memo: memo7 } = state;
5054
4986
  const [ms] = movement;
5055
4987
  const [deltaScale] = delta;
5056
4988
  const currentElement = elementRef.current;
@@ -5061,17 +4993,17 @@ var useViewportContainer = ({
5061
4993
  const containerRect = currentContainer.getBoundingClientRect();
5062
4994
  const currentZoom = transformations.current.zoom;
5063
4995
  const contentPosition = [
5064
- origin2[0] - elementRect.left,
5065
- origin2[1] - elementRect.top
4996
+ origin[0] - elementRect.left,
4997
+ origin[1] - elementRect.top
5066
4998
  ];
5067
4999
  const containerPosition = [
5068
- origin2[0] - containerRect.left,
5069
- origin2[1] - containerRect.top
5000
+ origin[0] - containerRect.left,
5001
+ origin[1] - containerRect.top
5070
5002
  ];
5071
- setOrigin([
5003
+ originRef.current = [
5072
5004
  contentPosition[0] / currentZoom,
5073
5005
  contentPosition[1] / currentZoom
5074
- ]);
5006
+ ];
5075
5007
  return {
5076
5008
  contentPosition,
5077
5009
  containerPosition,
@@ -5120,7 +5052,7 @@ var useViewportContainer = ({
5120
5052
  }
5121
5053
  );
5122
5054
  return {
5123
- origin
5055
+ origin: originRef.current
5124
5056
  };
5125
5057
  };
5126
5058
  var selectLargestPageWidth = (state) => state.viewports.reduce((max, vp) => Math.max(max, vp.width), 0);
@@ -5280,6 +5212,7 @@ var Pages = ({
5280
5212
  alignItems: "center",
5281
5213
  flexDirection: "column",
5282
5214
  transformOrigin: "0 0",
5215
+ willChange: "transform",
5283
5216
  // width: "max-content",
5284
5217
  width: largestPageWidth,
5285
5218
  margin: "0 auto"
@@ -5304,6 +5237,17 @@ var Pages = ({
5304
5237
  }
5305
5238
  );
5306
5239
  };
5240
+
5241
+ // src/lib/pdfjs.ts
5242
+ var pdfJsPromise = null;
5243
+ var loadPdfJs = () => {
5244
+ if (!pdfJsPromise) {
5245
+ pdfJsPromise = import('pdfjs-dist/legacy/build/pdf.mjs');
5246
+ }
5247
+ return pdfJsPromise;
5248
+ };
5249
+
5250
+ // src/hooks/document/document.ts
5307
5251
  function buildDocumentInitParams(source, overrides) {
5308
5252
  let params;
5309
5253
  if (typeof source === "string" || source instanceof URL) {
@@ -5443,11 +5387,11 @@ var Root = forwardRef(
5443
5387
  const linkService = useCreatePDFLinkService(
5444
5388
  initialState?.pdfDocumentProxy ?? null
5445
5389
  );
5446
- const documentId = initialState?.pdfDocumentProxy.fingerprints[0];
5390
+ const documentId = initialState?.pdfDocumentProxy?.fingerprints?.[0];
5447
5391
  useEffect(() => {
5448
5392
  if (!documentId) return;
5449
5393
  return () => {
5450
- renderCache.invalidateDocument(documentId);
5394
+ clearBitmapCache(documentId);
5451
5395
  };
5452
5396
  }, [documentId]);
5453
5397
  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..." });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anaralabs/lector",
3
- "version": "3.10.1",
3
+ "version": "3.10.3",
4
4
  "description": "A headless PDF viewer for React",
5
5
  "author": "andrewdr (https://github.com/andrewdoro)",
6
6
  "license": "MIT",