@anaralabs/lector 3.12.3 → 3.12.5

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.
Files changed (2) hide show
  1. package/dist/index.js +125 -56
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1285,18 +1285,36 @@ var useCanvasLayer = ({ background }) => {
1285
1285
  baseCanvas.style.visibility = "hidden";
1286
1286
  let cancelled = false;
1287
1287
  const viewport = pdfPageProxy.getViewport({ scale: baseScale });
1288
+ const buffer = document.createElement("canvas");
1289
+ buffer.width = baseCanvas.width;
1290
+ buffer.height = baseCanvas.height;
1291
+ const bufferCtx = buffer.getContext("2d");
1292
+ const useBuffer = bufferCtx !== null;
1293
+ const renderCanvas = useBuffer ? buffer : baseCanvas;
1294
+ const renderCtx = bufferCtx ?? context;
1288
1295
  const renderingTask = pdfPageProxy.render({
1289
- canvas: baseCanvas,
1290
- canvasContext: context,
1296
+ canvas: renderCanvas,
1297
+ canvasContext: renderCtx,
1291
1298
  viewport,
1292
1299
  background
1293
1300
  });
1301
+ const releaseBuffer = () => {
1302
+ buffer.width = 0;
1303
+ buffer.height = 0;
1304
+ };
1294
1305
  renderingTask.promise.then(() => {
1295
- if (cancelled) return;
1306
+ if (cancelled) {
1307
+ releaseBuffer();
1308
+ return;
1309
+ }
1310
+ if (useBuffer) {
1311
+ context.clearRect(0, 0, baseCanvas.width, baseCanvas.height);
1312
+ context.drawImage(buffer, 0, 0);
1313
+ }
1296
1314
  baseCanvas.style.visibility = "";
1297
1315
  markPageRendered(pageNumber);
1298
1316
  if (typeof createImageBitmap !== "undefined") {
1299
- createImageBitmap(baseCanvas).then((bitmap) => {
1317
+ createImageBitmap(renderCanvas).then((bitmap) => {
1300
1318
  if (cancelled) {
1301
1319
  bitmap.close();
1302
1320
  return;
@@ -1309,9 +1327,12 @@ var useCanvasLayer = ({ background }) => {
1309
1327
  bitmap
1310
1328
  );
1311
1329
  }).catch(() => {
1312
- });
1330
+ }).finally(releaseBuffer);
1331
+ } else {
1332
+ releaseBuffer();
1313
1333
  }
1314
1334
  }).catch((error) => {
1335
+ releaseBuffer();
1315
1336
  if (cancelled) return;
1316
1337
  if (error?.name === "RenderingCancelledException") return;
1317
1338
  baseCanvas.style.visibility = "";
@@ -1418,6 +1439,7 @@ var useDetailCanvasLayer = ({
1418
1439
  const detailCanvasRef = useRef(null);
1419
1440
  const pageNumber = usePDFPageNumber();
1420
1441
  const dpr = useDpr();
1442
+ const store = PDFStore.useContext();
1421
1443
  const bouncyZoom = usePdf((state) => state.zoom);
1422
1444
  const isPinching = usePdf((state) => state.isPinching);
1423
1445
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
@@ -1452,12 +1474,29 @@ var useDetailCanvasLayer = ({
1452
1474
  container.style.cssText = "";
1453
1475
  };
1454
1476
  const renderDetailCanvas = () => {
1477
+ const virtualizer = store.getState().virtualizer;
1478
+ if (virtualizer?.isScrolling) {
1479
+ scheduleRender(160);
1480
+ return;
1481
+ }
1455
1482
  const pageContainer = baseCanvasRef.current?.parentElement ?? null;
1456
1483
  if (!pageContainer) {
1457
1484
  hideDetailCanvas();
1458
1485
  return;
1459
1486
  }
1460
1487
  const { width: pageWidth, height: pageHeight } = pageDimsRef.current;
1488
+ const targetDetailScale = dpr * zoom * 1.3;
1489
+ const baseTargetScale = dpr * Math.min(zoom, 1);
1490
+ const baseScale = clampScaleForPage(
1491
+ baseTargetScale,
1492
+ pageWidth,
1493
+ pageHeight
1494
+ );
1495
+ const needsDetail = zoom > 1 && targetDetailScale - baseScale > 1e-3;
1496
+ if (isPinching || !needsDetail) {
1497
+ hideDetailCanvas();
1498
+ return;
1499
+ }
1461
1500
  const scrollX = scrollContainer.scrollLeft / zoom;
1462
1501
  const scrollY = scrollContainer.scrollTop / zoom;
1463
1502
  const viewportWidth = scrollContainer.clientWidth / zoom;
@@ -1478,15 +1517,7 @@ var useDetailCanvasLayer = ({
1478
1517
  );
1479
1518
  const visibleWidth = Math.max(0, visibleRight - visibleLeft);
1480
1519
  const visibleHeight = Math.max(0, visibleBottom - visibleTop);
1481
- const targetDetailScale = dpr * zoom * 1.3;
1482
- const baseTargetScale = dpr * Math.min(zoom, 1);
1483
- const baseScale = clampScaleForPage(
1484
- baseTargetScale,
1485
- pageWidth,
1486
- pageHeight
1487
- );
1488
- const needsDetail = zoom > 1 && targetDetailScale - baseScale > 1e-3;
1489
- if (isPinching || !needsDetail || visibleWidth <= 0 || visibleHeight <= 0) {
1520
+ if (visibleWidth <= 0 || visibleHeight <= 0) {
1490
1521
  hideDetailCanvas();
1491
1522
  return;
1492
1523
  }
@@ -1579,7 +1610,8 @@ var useDetailCanvasLayer = ({
1579
1610
  background,
1580
1611
  dpr,
1581
1612
  viewportRef,
1582
- baseCanvasRef
1613
+ baseCanvasRef,
1614
+ store
1583
1615
  ]);
1584
1616
  useEffect(() => {
1585
1617
  const canvas = detailCanvasRef.current;
@@ -2612,51 +2644,51 @@ var createTextSelectionManager = () => {
2612
2644
  return bindMouseEvents2;
2613
2645
  };
2614
2646
  var bindMouseEvents = createTextSelectionManager();
2647
+ var TEXT_BUILD_IDLE_MS = 300;
2615
2648
  var useTextLayer = () => {
2616
2649
  const textContainerRef = useRef(null);
2617
2650
  const textLayerRef = useRef(null);
2618
- const isRenderingRef = useRef(false);
2619
2651
  const pageNumber = usePDFPageNumber();
2620
2652
  const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber));
2621
2653
  useEffect(() => {
2622
2654
  const textContainer = textContainerRef.current;
2623
- if (!textContainer || isRenderingRef.current) {
2655
+ if (!textContainer) {
2624
2656
  return;
2625
2657
  }
2626
2658
  let isCancelled = false;
2627
- isRenderingRef.current = true;
2628
- textContainer.innerHTML = "";
2629
- if (textLayerRef.current) {
2630
- textLayerRef.current.cancel();
2631
- textLayerRef.current = null;
2632
- }
2633
- void import('pdfjs-dist/legacy/build/pdf.mjs').then(({ TextLayer: TextLayer2 }) => {
2659
+ const buildTimer = setTimeout(() => {
2634
2660
  if (isCancelled || textContainerRef.current !== textContainer) return;
2635
- const textLayer = new TextLayer2({
2636
- textContentSource: pdfPageProxy.streamTextContent(),
2637
- container: textContainer,
2638
- viewport: pdfPageProxy.getViewport({ scale: 1 })
2639
- });
2640
- textLayerRef.current = textLayer;
2641
- return textLayer.render();
2642
- }).then(() => {
2643
- if (isCancelled || textContainerRef.current !== textContainer) {
2644
- return;
2645
- }
2646
- const endOfContent = document.createElement("div");
2647
- endOfContent.className = "endOfContent";
2648
- textContainer.appendChild(endOfContent);
2649
- bindMouseEvents(textContainer, endOfContent);
2650
- }).catch((error) => {
2651
- if (error instanceof Error && error.name !== "AbortException") {
2652
- console.error("TextLayer rendering error:", error);
2661
+ textContainer.innerHTML = "";
2662
+ if (textLayerRef.current) {
2663
+ textLayerRef.current.cancel();
2664
+ textLayerRef.current = null;
2653
2665
  }
2654
- }).finally(() => {
2655
- isRenderingRef.current = false;
2656
- });
2666
+ void import('pdfjs-dist/legacy/build/pdf.mjs').then(({ TextLayer: TextLayer2 }) => {
2667
+ if (isCancelled || textContainerRef.current !== textContainer) return;
2668
+ const textLayer = new TextLayer2({
2669
+ textContentSource: pdfPageProxy.streamTextContent(),
2670
+ container: textContainer,
2671
+ viewport: pdfPageProxy.getViewport({ scale: 1 })
2672
+ });
2673
+ textLayerRef.current = textLayer;
2674
+ return textLayer.render();
2675
+ }).then(() => {
2676
+ if (isCancelled || textContainerRef.current !== textContainer) {
2677
+ return;
2678
+ }
2679
+ const endOfContent = document.createElement("div");
2680
+ endOfContent.className = "endOfContent";
2681
+ textContainer.appendChild(endOfContent);
2682
+ bindMouseEvents(textContainer, endOfContent);
2683
+ }).catch((error) => {
2684
+ if (error instanceof Error && error.name !== "AbortException") {
2685
+ console.error("TextLayer rendering error:", error);
2686
+ }
2687
+ });
2688
+ }, TEXT_BUILD_IDLE_MS);
2657
2689
  return () => {
2658
2690
  isCancelled = true;
2659
- isRenderingRef.current = false;
2691
+ clearTimeout(buildTimer);
2660
2692
  if (textLayerRef.current) {
2661
2693
  textLayerRef.current.cancel();
2662
2694
  textLayerRef.current = null;
@@ -2683,7 +2715,11 @@ var TextLayer = memo(
2683
2715
  ...style,
2684
2716
  position: "absolute",
2685
2717
  top: 0,
2686
- left: 0
2718
+ left: 0,
2719
+ // Isolate the span subtree's layout so a getBoundingClientRect on
2720
+ // an ancestor (e.g. renderDetailCanvas) doesn't reflow every text
2721
+ // span on this page during scroll.
2722
+ contain: "layout style"
2687
2723
  },
2688
2724
  ...props,
2689
2725
  ...{
@@ -3036,18 +3072,32 @@ var useScrollFn = () => {
3036
3072
  );
3037
3073
  return { scrollToFn };
3038
3074
  };
3039
- var useVisiblePage = ({ items }) => {
3075
+ var useVisiblePage = ({
3076
+ items,
3077
+ scrollOffset
3078
+ }) => {
3040
3079
  const zoomLevel = usePdf((state) => state.zoom);
3041
3080
  const isPinching = usePdf((state) => state.isPinching);
3042
3081
  const setCurrentPage = usePdf((state) => state.setCurrentPage);
3043
3082
  const scrollElement = usePdf((state) => state.viewportRef?.current);
3044
3083
  const lastPageRef = useRef(0);
3084
+ const [viewportHeight, setViewportHeight] = useState(0);
3085
+ useEffect(() => {
3086
+ if (!scrollElement) return;
3087
+ const measure = () => setViewportHeight(scrollElement.clientHeight);
3088
+ measure();
3089
+ if (typeof ResizeObserver === "undefined") {
3090
+ window.addEventListener("resize", measure);
3091
+ return () => window.removeEventListener("resize", measure);
3092
+ }
3093
+ const observer = new ResizeObserver(measure);
3094
+ observer.observe(scrollElement);
3095
+ return () => observer.disconnect();
3096
+ }, [scrollElement]);
3045
3097
  const calculateVisiblePageIndex = useCallback(
3046
3098
  (virtualItems) => {
3047
- if (!scrollElement || virtualItems.length === 0) return 0;
3048
- const scrollTop = scrollElement.scrollTop / zoomLevel;
3049
- const viewportHeight = scrollElement.clientHeight / zoomLevel;
3050
- const viewportCenter = scrollTop + viewportHeight / 2;
3099
+ if (virtualItems.length === 0 || scrollOffset == null) return 0;
3100
+ const viewportCenter = scrollOffset + viewportHeight / zoomLevel / 2;
3051
3101
  let closestIndex = 0;
3052
3102
  let smallestDistance = Infinity;
3053
3103
  for (const item of virtualItems) {
@@ -3060,10 +3110,17 @@ var useVisiblePage = ({ items }) => {
3060
3110
  }
3061
3111
  return closestIndex;
3062
3112
  },
3063
- [scrollElement, zoomLevel]
3113
+ [scrollOffset, zoomLevel, viewportHeight]
3064
3114
  );
3065
3115
  useEffect(() => {
3066
- if (!isPinching && items.length > 0) {
3116
+ if (viewportHeight === 0 && scrollElement) {
3117
+ const h = scrollElement.clientHeight;
3118
+ if (h > 0) {
3119
+ setViewportHeight(h);
3120
+ return;
3121
+ }
3122
+ }
3123
+ if (!isPinching && items.length > 0 && viewportHeight > 0 && scrollOffset != null) {
3067
3124
  const mostVisibleIndex = calculateVisiblePageIndex(items);
3068
3125
  const page = mostVisibleIndex + 1;
3069
3126
  if (page !== lastPageRef.current) {
@@ -3071,7 +3128,15 @@ var useVisiblePage = ({ items }) => {
3071
3128
  setCurrentPage?.(page);
3072
3129
  }
3073
3130
  }
3074
- }, [items, isPinching, calculateVisiblePageIndex, setCurrentPage]);
3131
+ }, [
3132
+ items,
3133
+ isPinching,
3134
+ calculateVisiblePageIndex,
3135
+ setCurrentPage,
3136
+ viewportHeight,
3137
+ scrollOffset,
3138
+ scrollElement
3139
+ ]);
3075
3140
  return null;
3076
3141
  };
3077
3142
 
@@ -5267,7 +5332,11 @@ var Pages = ({
5267
5332
  const virtualizerItems = virtualizer?.getVirtualItems() ?? [];
5268
5333
  const items = tempItems.length ? tempItems : virtualizerItems;
5269
5334
  useVisiblePage({
5270
- items
5335
+ items,
5336
+ // Pass through nullish (pre-measure) rather than coercing to 0, so the
5337
+ // hook doesn't publish a top-of-document page before the real offset
5338
+ // (incl. a restored/deep-linked one) arrives.
5339
+ scrollOffset: virtualizer.scrollOffset ?? null
5271
5340
  });
5272
5341
  useFitWidth({ viewportRef: containerRef });
5273
5342
  const largestPageWidth = usePdf(selectLargestPageWidth);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anaralabs/lector",
3
- "version": "3.12.3",
3
+ "version": "3.12.5",
4
4
  "description": "A headless PDF viewer for React",
5
5
  "author": "andrewdr (https://github.com/andrewdoro)",
6
6
  "license": "MIT",