pdfjs-reader-core 0.1.5 → 0.2.1

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.cjs CHANGED
@@ -332,6 +332,9 @@ var init_highlight_storage = __esm({
332
332
  });
333
333
 
334
334
  // src/store/viewer-store.ts
335
+ function generateRequestId() {
336
+ return `scroll-${Date.now()}-${++requestCounter}`;
337
+ }
335
338
  function createViewerStore(initialOverrides = {}) {
336
339
  return (0, import_vanilla.createStore)()((set, get) => ({
337
340
  ...initialState,
@@ -384,6 +387,46 @@ function createViewerStore(initialOverrides = {}) {
384
387
  set({ currentPage: currentPage - 1 });
385
388
  }
386
389
  },
390
+ // Scroll coordination actions
391
+ requestScrollToPage: (page, behavior = "smooth") => {
392
+ const { numPages } = get();
393
+ const validPage = Math.max(1, Math.min(page, numPages));
394
+ return new Promise((resolve) => {
395
+ const requestId = generateRequestId();
396
+ const timeoutId = setTimeout(() => {
397
+ const callback = scrollCallbacks.get(requestId);
398
+ if (callback) {
399
+ scrollCallbacks.delete(requestId);
400
+ callback.resolve();
401
+ }
402
+ const currentRequest = get().scrollToPageRequest;
403
+ if (currentRequest?.requestId === requestId) {
404
+ set({ scrollToPageRequest: null });
405
+ }
406
+ }, SCROLL_TIMEOUT_MS);
407
+ scrollCallbacks.set(requestId, { resolve, timeoutId });
408
+ set({
409
+ currentPage: validPage,
410
+ scrollToPageRequest: {
411
+ page: validPage,
412
+ requestId,
413
+ behavior
414
+ }
415
+ });
416
+ });
417
+ },
418
+ completeScrollRequest: (requestId) => {
419
+ const callback = scrollCallbacks.get(requestId);
420
+ if (callback) {
421
+ clearTimeout(callback.timeoutId);
422
+ scrollCallbacks.delete(requestId);
423
+ callback.resolve();
424
+ }
425
+ const currentRequest = get().scrollToPageRequest;
426
+ if (currentRequest?.requestId === requestId) {
427
+ set({ scrollToPageRequest: null });
428
+ }
429
+ },
387
430
  // Zoom actions
388
431
  setScale: (scale) => {
389
432
  const clampedScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
@@ -450,7 +493,7 @@ function createViewerStore(initialOverrides = {}) {
450
493
  }
451
494
  }));
452
495
  }
453
- var import_vanilla, ZOOM_LEVELS, MIN_SCALE, MAX_SCALE, initialState;
496
+ var import_vanilla, ZOOM_LEVELS, MIN_SCALE, MAX_SCALE, SCROLL_TIMEOUT_MS, scrollCallbacks, requestCounter, initialState;
454
497
  var init_viewer_store = __esm({
455
498
  "src/store/viewer-store.ts"() {
456
499
  "use strict";
@@ -458,6 +501,9 @@ var init_viewer_store = __esm({
458
501
  ZOOM_LEVELS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];
459
502
  MIN_SCALE = 0.1;
460
503
  MAX_SCALE = 10;
504
+ SCROLL_TIMEOUT_MS = 3e3;
505
+ scrollCallbacks = /* @__PURE__ */ new Map();
506
+ requestCounter = 0;
461
507
  initialState = {
462
508
  // Document state
463
509
  document: null,
@@ -468,6 +514,8 @@ var init_viewer_store = __esm({
468
514
  currentPage: 1,
469
515
  scale: 1,
470
516
  rotation: 0,
517
+ // Scroll coordination
518
+ scrollToPageRequest: null,
471
519
  // UI state
472
520
  viewMode: "single",
473
521
  scrollMode: "single",
@@ -1277,6 +1325,267 @@ var init_agent_api = __esm({
1277
1325
  }
1278
1326
  });
1279
1327
 
1328
+ // src/utils/text-search.ts
1329
+ async function extractPageText(document2, pageNumber) {
1330
+ const page = await document2.getPage(pageNumber);
1331
+ const textContent = await page.getTextContent();
1332
+ const viewport = page.getViewport({ scale: 1 });
1333
+ let fullText = "";
1334
+ const textItems = [];
1335
+ for (const item of textContent.items) {
1336
+ if ("str" in item && item.str) {
1337
+ textItems.push({
1338
+ text: item.str,
1339
+ transform: item.transform,
1340
+ width: item.width ?? 0,
1341
+ height: item.height ?? 12
1342
+ });
1343
+ fullText += item.str;
1344
+ }
1345
+ }
1346
+ return { fullText, textItems, viewport };
1347
+ }
1348
+ function calculateMatchRects(textItems, startOffset, length, viewport) {
1349
+ const rects = [];
1350
+ let currentOffset = 0;
1351
+ for (const item of textItems) {
1352
+ const itemStart = currentOffset;
1353
+ const itemEnd = currentOffset + item.text.length;
1354
+ if (itemEnd > startOffset && itemStart < startOffset + length) {
1355
+ const [, , c, d, tx, ty] = item.transform;
1356
+ const x = tx;
1357
+ const y = viewport.height - ty;
1358
+ const height = Math.sqrt(c * c + d * d);
1359
+ const matchStartInItem = Math.max(0, startOffset - itemStart);
1360
+ const matchEndInItem = Math.min(item.text.length, startOffset + length - itemStart);
1361
+ const charWidth = item.text.length > 0 ? item.width / item.text.length : item.width;
1362
+ const matchWidth = charWidth * (matchEndInItem - matchStartInItem);
1363
+ const matchX = x + charWidth * matchStartInItem;
1364
+ const yOffset = height * 0.15;
1365
+ rects.push({
1366
+ x: matchX,
1367
+ y: y - height + yOffset,
1368
+ width: matchWidth,
1369
+ height
1370
+ });
1371
+ }
1372
+ currentOffset = itemEnd;
1373
+ }
1374
+ return rects;
1375
+ }
1376
+ async function findTextOnPage(document2, pageNumber, query, options = {}) {
1377
+ const { caseSensitive = false, wholeWord = false } = options;
1378
+ if (!query || pageNumber < 1 || pageNumber > document2.numPages) {
1379
+ return [];
1380
+ }
1381
+ const { fullText, textItems, viewport } = await extractPageText(document2, pageNumber);
1382
+ const matches = [];
1383
+ const searchText = caseSensitive ? query : query.toLowerCase();
1384
+ const textToSearch = caseSensitive ? fullText : fullText.toLowerCase();
1385
+ let startIndex = 0;
1386
+ while (true) {
1387
+ const matchIndex = textToSearch.indexOf(searchText, startIndex);
1388
+ if (matchIndex === -1) break;
1389
+ if (wholeWord) {
1390
+ const beforeChar = matchIndex > 0 ? textToSearch[matchIndex - 1] : " ";
1391
+ const afterChar = matchIndex + query.length < textToSearch.length ? textToSearch[matchIndex + query.length] : " ";
1392
+ if (/\w/.test(beforeChar) || /\w/.test(afterChar)) {
1393
+ startIndex = matchIndex + 1;
1394
+ continue;
1395
+ }
1396
+ }
1397
+ const matchRects = calculateMatchRects(textItems, matchIndex, query.length, viewport);
1398
+ if (matchRects.length > 0) {
1399
+ matches.push({
1400
+ text: fullText.substring(matchIndex, matchIndex + query.length),
1401
+ rects: matchRects,
1402
+ pageNumber,
1403
+ startIndex: matchIndex
1404
+ });
1405
+ }
1406
+ startIndex = matchIndex + 1;
1407
+ }
1408
+ return matches;
1409
+ }
1410
+ async function findTextInDocument(document2, query, options = {}) {
1411
+ const { pageRange, ...findOptions } = options;
1412
+ const pagesToSearch = pageRange ?? Array.from({ length: document2.numPages }, (_, i) => i + 1);
1413
+ const allMatches = [];
1414
+ for (const pageNum of pagesToSearch) {
1415
+ if (pageNum < 1 || pageNum > document2.numPages) continue;
1416
+ try {
1417
+ const matches = await findTextOnPage(document2, pageNum, query, findOptions);
1418
+ allMatches.push(...matches);
1419
+ } catch {
1420
+ }
1421
+ }
1422
+ return allMatches;
1423
+ }
1424
+ function mergeAdjacentRects(rects) {
1425
+ if (rects.length === 0) return [];
1426
+ const sorted = [...rects].sort((a, b) => a.y - b.y || a.x - b.x);
1427
+ const merged = [];
1428
+ let current = { ...sorted[0] };
1429
+ for (let i = 1; i < sorted.length; i++) {
1430
+ const rect = sorted[i];
1431
+ if (Math.abs(rect.y - current.y) < 2 && rect.x <= current.x + current.width + 2) {
1432
+ const newRight = Math.max(current.x + current.width, rect.x + rect.width);
1433
+ current.width = newRight - current.x;
1434
+ current.height = Math.max(current.height, rect.height);
1435
+ } else {
1436
+ merged.push(current);
1437
+ current = { ...rect };
1438
+ }
1439
+ }
1440
+ merged.push(current);
1441
+ return merged;
1442
+ }
1443
+ async function getPageText(document2, pageNumber) {
1444
+ if (pageNumber < 1 || pageNumber > document2.numPages) {
1445
+ return "";
1446
+ }
1447
+ const page = await document2.getPage(pageNumber);
1448
+ const textContent = await page.getTextContent();
1449
+ return textContent.items.filter((item) => "str" in item).map((item) => item.str).join("");
1450
+ }
1451
+ async function countTextOnPage(document2, pageNumber, query, options = {}) {
1452
+ const { caseSensitive = false, wholeWord = false } = options;
1453
+ if (!query || pageNumber < 1 || pageNumber > document2.numPages) {
1454
+ return 0;
1455
+ }
1456
+ const text = await getPageText(document2, pageNumber);
1457
+ const searchText = caseSensitive ? query : query.toLowerCase();
1458
+ const textToSearch = caseSensitive ? text : text.toLowerCase();
1459
+ let count = 0;
1460
+ let startIndex = 0;
1461
+ while (true) {
1462
+ const matchIndex = textToSearch.indexOf(searchText, startIndex);
1463
+ if (matchIndex === -1) break;
1464
+ if (wholeWord) {
1465
+ const beforeChar = matchIndex > 0 ? textToSearch[matchIndex - 1] : " ";
1466
+ const afterChar = matchIndex + query.length < textToSearch.length ? textToSearch[matchIndex + query.length] : " ";
1467
+ if (/\w/.test(beforeChar) || /\w/.test(afterChar)) {
1468
+ startIndex = matchIndex + 1;
1469
+ continue;
1470
+ }
1471
+ }
1472
+ count++;
1473
+ startIndex = matchIndex + 1;
1474
+ }
1475
+ return count;
1476
+ }
1477
+ var init_text_search = __esm({
1478
+ "src/utils/text-search.ts"() {
1479
+ "use strict";
1480
+ }
1481
+ });
1482
+
1483
+ // src/utils/coordinates.ts
1484
+ function pdfToViewport(x, y, scale, pageHeight) {
1485
+ return {
1486
+ x: x * scale,
1487
+ y: (pageHeight - y) * scale
1488
+ };
1489
+ }
1490
+ function viewportToPDF(x, y, scale, pageHeight) {
1491
+ return {
1492
+ x: x / scale,
1493
+ y: pageHeight - y / scale
1494
+ };
1495
+ }
1496
+ function percentToPDF(xPercent, yPercent, pageWidth, pageHeight) {
1497
+ return {
1498
+ x: xPercent / 100 * pageWidth,
1499
+ y: yPercent / 100 * pageHeight
1500
+ };
1501
+ }
1502
+ function pdfToPercent(x, y, pageWidth, pageHeight) {
1503
+ return {
1504
+ x: x / pageWidth * 100,
1505
+ y: y / pageHeight * 100
1506
+ };
1507
+ }
1508
+ function percentToViewport(xPercent, yPercent, pageWidth, pageHeight, scale) {
1509
+ return {
1510
+ x: xPercent / 100 * pageWidth * scale,
1511
+ y: yPercent / 100 * pageHeight * scale
1512
+ };
1513
+ }
1514
+ function viewportToPercent(x, y, pageWidth, pageHeight, scale) {
1515
+ return {
1516
+ x: x / (pageWidth * scale) * 100,
1517
+ y: y / (pageHeight * scale) * 100
1518
+ };
1519
+ }
1520
+ function applyRotation(x, y, rotation, pageWidth, pageHeight) {
1521
+ const normalizedRotation = (rotation % 360 + 360) % 360;
1522
+ switch (normalizedRotation) {
1523
+ case 90:
1524
+ return { x: y, y: pageWidth - x };
1525
+ case 180:
1526
+ return { x: pageWidth - x, y: pageHeight - y };
1527
+ case 270:
1528
+ return { x: pageHeight - y, y: x };
1529
+ default:
1530
+ return { x, y };
1531
+ }
1532
+ }
1533
+ function removeRotation(x, y, rotation, pageWidth, pageHeight) {
1534
+ const normalizedRotation = (rotation % 360 + 360) % 360;
1535
+ switch (normalizedRotation) {
1536
+ case 90:
1537
+ return { x: pageWidth - y, y: x };
1538
+ case 180:
1539
+ return { x: pageWidth - x, y: pageHeight - y };
1540
+ case 270:
1541
+ return { x: y, y: pageHeight - x };
1542
+ default:
1543
+ return { x, y };
1544
+ }
1545
+ }
1546
+ function getRotatedDimensions(width, height, rotation) {
1547
+ const normalizedRotation = (rotation % 360 + 360) % 360;
1548
+ if (normalizedRotation === 90 || normalizedRotation === 270) {
1549
+ return { width: height, height: width };
1550
+ }
1551
+ return { width, height };
1552
+ }
1553
+ function scaleRect(rect, fromScale, toScale) {
1554
+ const ratio = toScale / fromScale;
1555
+ return {
1556
+ x: rect.x * ratio,
1557
+ y: rect.y * ratio,
1558
+ width: rect.width * ratio,
1559
+ height: rect.height * ratio
1560
+ };
1561
+ }
1562
+ function isPointInRect(point, rect) {
1563
+ return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
1564
+ }
1565
+ function doRectsIntersect(rectA, rectB) {
1566
+ return !(rectA.x + rectA.width < rectB.x || rectB.x + rectB.width < rectA.x || rectA.y + rectA.height < rectB.y || rectB.y + rectB.height < rectA.y);
1567
+ }
1568
+ function getRectIntersection(rectA, rectB) {
1569
+ const x = Math.max(rectA.x, rectB.x);
1570
+ const y = Math.max(rectA.y, rectB.y);
1571
+ const right = Math.min(rectA.x + rectA.width, rectB.x + rectB.width);
1572
+ const bottom = Math.min(rectA.y + rectA.height, rectB.y + rectB.height);
1573
+ if (right <= x || bottom <= y) {
1574
+ return null;
1575
+ }
1576
+ return {
1577
+ x,
1578
+ y,
1579
+ width: right - x,
1580
+ height: bottom - y
1581
+ };
1582
+ }
1583
+ var init_coordinates = __esm({
1584
+ "src/utils/coordinates.ts"() {
1585
+ "use strict";
1586
+ }
1587
+ });
1588
+
1280
1589
  // src/utils/index.ts
1281
1590
  var init_utils = __esm({
1282
1591
  "src/utils/index.ts"() {
@@ -1289,6 +1598,8 @@ var init_utils = __esm({
1289
1598
  init_export_annotations();
1290
1599
  init_student_storage();
1291
1600
  init_agent_api();
1601
+ init_text_search();
1602
+ init_coordinates();
1292
1603
  }
1293
1604
  });
1294
1605
 
@@ -1344,7 +1655,7 @@ function createSearchStore(initialOverrides = {}) {
1344
1655
  }
1345
1656
  }
1346
1657
  const matchText = pageText.substring(startIndex, startIndex + query.length);
1347
- const rects = calculateMatchRects(textItems, startIndex, query.length, viewport);
1658
+ const rects = calculateMatchRects2(textItems, startIndex, query.length, viewport);
1348
1659
  results.push({
1349
1660
  pageNumber: pageNum,
1350
1661
  matchIndex: matchIndex++,
@@ -1405,7 +1716,7 @@ function createSearchStore(initialOverrides = {}) {
1405
1716
  }
1406
1717
  }));
1407
1718
  }
1408
- function calculateMatchRects(textItems, startOffset, length, viewport) {
1719
+ function calculateMatchRects2(textItems, startOffset, length, viewport) {
1409
1720
  const rects = [];
1410
1721
  let currentOffset = 0;
1411
1722
  for (const item of textItems) {
@@ -1420,9 +1731,10 @@ function calculateMatchRects(textItems, startOffset, length, viewport) {
1420
1731
  const matchEndInItem = Math.min(item.text.length, startOffset + length - itemStart);
1421
1732
  const matchWidth = item.width / item.text.length * (matchEndInItem - matchStartInItem);
1422
1733
  const matchX = x + item.width / item.text.length * matchStartInItem;
1734
+ const yOffset = height * 0.15;
1423
1735
  rects.push({
1424
1736
  x: matchX,
1425
- y: y - height,
1737
+ y: y - height + yOffset,
1426
1738
  width: matchWidth,
1427
1739
  height
1428
1740
  });
@@ -7926,6 +8238,8 @@ var init_DocumentContainer = __esm({
7926
8238
  nextPage,
7927
8239
  previousPage
7928
8240
  } = usePDFViewer();
8241
+ const scrollToPageRequest = useViewerStore((s) => s.scrollToPageRequest);
8242
+ const { viewerStore } = usePDFViewerStores();
7929
8243
  const [currentPageObj, setCurrentPageObj] = (0, import_react35.useState)(null);
7930
8244
  const [isLoadingPage, setIsLoadingPage] = (0, import_react35.useState)(false);
7931
8245
  const containerRef = (0, import_react35.useRef)(null);
@@ -7991,6 +8305,11 @@ var init_DocumentContainer = __esm({
7991
8305
  const page = await document2.getPage(currentPage);
7992
8306
  if (!cancelled && document2 === documentRef.current) {
7993
8307
  setCurrentPageObj(page);
8308
+ if (scrollToPageRequest && scrollToPageRequest.page === currentPage) {
8309
+ requestAnimationFrame(() => {
8310
+ viewerStore.getState().completeScrollRequest(scrollToPageRequest.requestId);
8311
+ });
8312
+ }
7994
8313
  }
7995
8314
  } catch (error) {
7996
8315
  if (!cancelled) {
@@ -8009,7 +8328,7 @@ var init_DocumentContainer = __esm({
8009
8328
  return () => {
8010
8329
  cancelled = true;
8011
8330
  };
8012
- }, [document2, currentPage]);
8331
+ }, [document2, currentPage, scrollToPageRequest, viewerStore]);
8013
8332
  const getPageElement = (0, import_react35.useCallback)(() => {
8014
8333
  return containerRef.current?.querySelector(`[data-page-number="${currentPage}"]`);
8015
8334
  }, [currentPage]);
@@ -8151,6 +8470,8 @@ var init_VirtualizedDocumentContainer = __esm({
8151
8470
  nextPage,
8152
8471
  previousPage
8153
8472
  } = usePDFViewer();
8473
+ const scrollToPageRequest = useViewerStore((s) => s.scrollToPageRequest);
8474
+ const { viewerStore } = usePDFViewerStores();
8154
8475
  const containerRef = (0, import_react36.useRef)(null);
8155
8476
  const scrollContainerRef = (0, import_react36.useRef)(null);
8156
8477
  const documentRef = (0, import_react36.useRef)(null);
@@ -8288,6 +8609,45 @@ var init_VirtualizedDocumentContainer = __esm({
8288
8609
  loadPages();
8289
8610
  }, [document2, visiblePages, pageObjects]);
8290
8611
  (0, import_react36.useEffect)(() => {
8612
+ if (!scrollToPageRequest || !scrollContainerRef.current || pageInfos.length === 0) return;
8613
+ const { page, requestId, behavior } = scrollToPageRequest;
8614
+ const pageInfo = pageInfos.find((p) => p.pageNumber === page);
8615
+ if (!pageInfo) {
8616
+ viewerStore.getState().completeScrollRequest(requestId);
8617
+ return;
8618
+ }
8619
+ const container = scrollContainerRef.current;
8620
+ const targetScroll = pageInfo.top - pageGap;
8621
+ const scrollTop = container.scrollTop;
8622
+ const viewportHeight = container.clientHeight;
8623
+ const isVisible = targetScroll >= scrollTop && pageInfo.top + pageInfo.height <= scrollTop + viewportHeight;
8624
+ if (isVisible) {
8625
+ viewerStore.getState().completeScrollRequest(requestId);
8626
+ return;
8627
+ }
8628
+ container.scrollTo({
8629
+ top: targetScroll,
8630
+ behavior
8631
+ });
8632
+ if (behavior === "instant") {
8633
+ requestAnimationFrame(() => {
8634
+ viewerStore.getState().completeScrollRequest(requestId);
8635
+ });
8636
+ } else {
8637
+ let scrollEndTimeout;
8638
+ const handleScrollEnd = () => {
8639
+ clearTimeout(scrollEndTimeout);
8640
+ scrollEndTimeout = setTimeout(() => {
8641
+ container.removeEventListener("scroll", handleScrollEnd);
8642
+ viewerStore.getState().completeScrollRequest(requestId);
8643
+ }, 100);
8644
+ };
8645
+ container.addEventListener("scroll", handleScrollEnd, { passive: true });
8646
+ handleScrollEnd();
8647
+ }
8648
+ }, [scrollToPageRequest, pageInfos, pageGap, viewerStore]);
8649
+ (0, import_react36.useEffect)(() => {
8650
+ if (scrollToPageRequest) return;
8291
8651
  if (!scrollContainerRef.current || pageInfos.length === 0) return;
8292
8652
  const pageInfo = pageInfos.find((p) => p.pageNumber === currentPage);
8293
8653
  if (pageInfo) {
@@ -8302,7 +8662,7 @@ var init_VirtualizedDocumentContainer = __esm({
8302
8662
  });
8303
8663
  }
8304
8664
  }
8305
- }, [currentPage, pageInfos, pageGap]);
8665
+ }, [currentPage, pageInfos, pageGap, scrollToPageRequest]);
8306
8666
  const handlePinchZoom = (0, import_react36.useCallback)(
8307
8667
  (pinchScale) => {
8308
8668
  const newScale = Math.max(0.25, Math.min(4, baseScaleRef.current * pinchScale));
@@ -8509,6 +8869,8 @@ var init_DualPageContainer = __esm({
8509
8869
  setScale,
8510
8870
  goToPage
8511
8871
  } = usePDFViewer();
8872
+ const scrollToPageRequest = useViewerStore((s) => s.scrollToPageRequest);
8873
+ const { viewerStore } = usePDFViewerStores();
8512
8874
  const containerRef = (0, import_react38.useRef)(null);
8513
8875
  const documentRef = (0, import_react38.useRef)(null);
8514
8876
  const baseScaleRef = (0, import_react38.useRef)(scale);
@@ -8594,6 +8956,14 @@ var init_DualPageContainer = __esm({
8594
8956
  if (!cancelled) {
8595
8957
  setLeftPage(left);
8596
8958
  setRightPage(right);
8959
+ if (scrollToPageRequest) {
8960
+ const requestedPage = scrollToPageRequest.page;
8961
+ if (requestedPage === spread2.left || requestedPage === spread2.right) {
8962
+ requestAnimationFrame(() => {
8963
+ viewerStore.getState().completeScrollRequest(scrollToPageRequest.requestId);
8964
+ });
8965
+ }
8966
+ }
8597
8967
  }
8598
8968
  } catch (error) {
8599
8969
  if (!cancelled) {
@@ -8609,7 +8979,7 @@ var init_DualPageContainer = __esm({
8609
8979
  return () => {
8610
8980
  cancelled = true;
8611
8981
  };
8612
- }, [document2, currentPage, getSpreadPages]);
8982
+ }, [document2, currentPage, getSpreadPages, scrollToPageRequest, viewerStore]);
8613
8983
  const goToPreviousSpread = (0, import_react38.useCallback)(() => {
8614
8984
  const spread2 = getSpreadPages(currentPage);
8615
8985
  const leftmostPage = spread2.left || spread2.right || currentPage;
@@ -8806,10 +9176,14 @@ var init_FloatingZoomControls = __esm({
8806
9176
  const scale = useViewerStore((s) => s.scale);
8807
9177
  const document2 = useViewerStore((s) => s.document);
8808
9178
  const handleZoomIn = (0, import_react39.useCallback)(() => {
8809
- viewerStore.getState().zoomIn();
9179
+ const currentScale = viewerStore.getState().scale;
9180
+ const newScale = Math.min(4, currentScale + 0.05);
9181
+ viewerStore.getState().setScale(newScale);
8810
9182
  }, [viewerStore]);
8811
9183
  const handleZoomOut = (0, import_react39.useCallback)(() => {
8812
- viewerStore.getState().zoomOut();
9184
+ const currentScale = viewerStore.getState().scale;
9185
+ const newScale = Math.max(0.1, currentScale - 0.05);
9186
+ viewerStore.getState().setScale(newScale);
8813
9187
  }, [viewerStore]);
8814
9188
  const handleFitToWidth = (0, import_react39.useCallback)(() => {
8815
9189
  viewerStore.getState().setScale(1);
@@ -8937,24 +9311,33 @@ function getSrcIdentifier(src) {
8937
9311
  const last = Array.from(data.slice(-4)).map((b) => b.toString(16).padStart(2, "0")).join("");
8938
9312
  return `binary:${len}:${first}:${last}`;
8939
9313
  }
8940
- function mergeRects2(rects) {
8941
- if (rects.length === 0) return [];
8942
- const sorted = [...rects].sort((a, b) => a.y - b.y || a.x - b.x);
8943
- const merged = [];
8944
- let current = { ...sorted[0] };
8945
- for (let i = 1; i < sorted.length; i++) {
8946
- const rect = sorted[i];
8947
- if (Math.abs(rect.y - current.y) < 2 && rect.x <= current.x + current.width + 2) {
8948
- const newRight = Math.max(current.x + current.width, rect.x + rect.width);
8949
- current.width = newRight - current.x;
8950
- current.height = Math.max(current.height, rect.height);
8951
- } else {
8952
- merged.push(current);
8953
- current = { ...rect };
9314
+ function calculateMatchRects3(textItems, startOffset, length, viewport) {
9315
+ const rects = [];
9316
+ let currentOffset = 0;
9317
+ for (const item of textItems) {
9318
+ const itemStart = currentOffset;
9319
+ const itemEnd = currentOffset + item.text.length;
9320
+ if (itemEnd > startOffset && itemStart < startOffset + length) {
9321
+ const [, , c, d, tx, ty] = item.transform;
9322
+ const x = tx;
9323
+ const y = viewport.height - ty;
9324
+ const height = Math.sqrt(c * c + d * d);
9325
+ const matchStartInItem = Math.max(0, startOffset - itemStart);
9326
+ const matchEndInItem = Math.min(item.text.length, startOffset + length - itemStart);
9327
+ const charWidth = item.text.length > 0 ? item.width / item.text.length : item.width;
9328
+ const matchWidth = charWidth * (matchEndInItem - matchStartInItem);
9329
+ const matchX = x + charWidth * matchStartInItem;
9330
+ const yOffset = height * 0.15;
9331
+ rects.push({
9332
+ x: matchX,
9333
+ y: y - height + yOffset,
9334
+ width: matchWidth,
9335
+ height
9336
+ });
8954
9337
  }
9338
+ currentOffset = itemEnd;
8955
9339
  }
8956
- merged.push(current);
8957
- return merged;
9340
+ return rects;
8958
9341
  }
8959
9342
  var import_react40, import_jsx_runtime26, PDFViewerInner, PDFViewerInnerWithRef, PDFViewerClient;
8960
9343
  var init_PDFViewerClient = __esm({
@@ -8975,6 +9358,7 @@ var init_PDFViewerClient = __esm({
8975
9358
  PDFViewerInner = (0, import_react40.memo)(function PDFViewerInner2({
8976
9359
  src,
8977
9360
  initialPage = 1,
9361
+ page: controlledPage,
8978
9362
  initialScale = "auto",
8979
9363
  showToolbar = true,
8980
9364
  showSidebar = true,
@@ -8984,9 +9368,17 @@ var init_PDFViewerClient = __esm({
8984
9368
  onDocumentLoad,
8985
9369
  onPageChange,
8986
9370
  onScaleChange,
9371
+ onZoomChange,
8987
9372
  onError,
9373
+ onPageRenderStart,
9374
+ onPageRenderComplete,
9375
+ onHighlightAdded,
9376
+ onHighlightRemoved,
9377
+ onAnnotationAdded,
8988
9378
  workerSrc,
8989
9379
  className,
9380
+ loadingComponent,
9381
+ errorComponent,
8990
9382
  onReady
8991
9383
  }) {
8992
9384
  const { viewerStore, annotationStore, searchStore } = usePDFViewerStores();
@@ -8996,12 +9388,26 @@ var init_PDFViewerClient = __esm({
8996
9388
  const onErrorRef = (0, import_react40.useRef)(onError);
8997
9389
  const onPageChangeRef = (0, import_react40.useRef)(onPageChange);
8998
9390
  const onScaleChangeRef = (0, import_react40.useRef)(onScaleChange);
9391
+ const onZoomChangeRef = (0, import_react40.useRef)(onZoomChange);
9392
+ const onPageRenderStartRef = (0, import_react40.useRef)(onPageRenderStart);
9393
+ const onPageRenderCompleteRef = (0, import_react40.useRef)(onPageRenderComplete);
9394
+ const onHighlightAddedRef = (0, import_react40.useRef)(onHighlightAdded);
9395
+ const onHighlightRemovedRef = (0, import_react40.useRef)(onHighlightRemoved);
9396
+ const onAnnotationAddedRef = (0, import_react40.useRef)(onAnnotationAdded);
8999
9397
  const onReadyRef = (0, import_react40.useRef)(onReady);
9000
9398
  onDocumentLoadRef.current = onDocumentLoad;
9001
9399
  onErrorRef.current = onError;
9002
9400
  onPageChangeRef.current = onPageChange;
9003
9401
  onScaleChangeRef.current = onScaleChange;
9402
+ onZoomChangeRef.current = onZoomChange;
9403
+ onPageRenderStartRef.current = onPageRenderStart;
9404
+ onPageRenderCompleteRef.current = onPageRenderComplete;
9405
+ onHighlightAddedRef.current = onHighlightAdded;
9406
+ onHighlightRemovedRef.current = onHighlightRemoved;
9407
+ onAnnotationAddedRef.current = onAnnotationAdded;
9004
9408
  onReadyRef.current = onReady;
9409
+ const isControlled = controlledPage !== void 0;
9410
+ const prevControlledPageRef = (0, import_react40.useRef)(controlledPage);
9005
9411
  const srcIdRef = (0, import_react40.useRef)(null);
9006
9412
  const currentPage = useViewerStore((s) => s.currentPage);
9007
9413
  const scale = useViewerStore((s) => s.scale);
@@ -9030,26 +9436,15 @@ var init_PDFViewerClient = __esm({
9030
9436
  const textContent = await page.getTextContent();
9031
9437
  const viewport = page.getViewport({ scale: 1 });
9032
9438
  let fullText = "";
9033
- const charPositions = [];
9439
+ const textItems = [];
9034
9440
  for (const item of textContent.items) {
9035
9441
  if ("str" in item && item.str) {
9036
- const tx = item.transform;
9037
- const x = tx[4];
9038
- const y = viewport.height - tx[5];
9039
- const width = item.width ?? 0;
9040
- const height = item.height ?? 12;
9041
- const charWidth = item.str.length > 0 ? width / item.str.length : width;
9042
- for (let i = 0; i < item.str.length; i++) {
9043
- charPositions.push({
9044
- char: item.str[i],
9045
- rect: {
9046
- x: x + i * charWidth,
9047
- y: y - height,
9048
- width: charWidth,
9049
- height
9050
- }
9051
- });
9052
- }
9442
+ textItems.push({
9443
+ text: item.str,
9444
+ transform: item.transform,
9445
+ width: item.width ?? 0,
9446
+ height: item.height ?? 12
9447
+ });
9053
9448
  fullText += item.str;
9054
9449
  }
9055
9450
  }
@@ -9058,18 +9453,16 @@ var init_PDFViewerClient = __esm({
9058
9453
  while (true) {
9059
9454
  const matchIndex = textToSearch.indexOf(searchText, startIndex);
9060
9455
  if (matchIndex === -1) break;
9061
- const matchRects = [];
9062
- for (let i = matchIndex; i < matchIndex + text.length && i < charPositions.length; i++) {
9063
- matchRects.push(charPositions[i].rect);
9456
+ const matchRects = calculateMatchRects3(textItems, matchIndex, text.length, viewport);
9457
+ if (matchRects.length > 0) {
9458
+ const highlight = annotationStore.getState().addHighlight({
9459
+ pageNumber: pageNum,
9460
+ rects: matchRects,
9461
+ color,
9462
+ text: fullText.substring(matchIndex, matchIndex + text.length)
9463
+ });
9464
+ highlightIds.push(highlight.id);
9064
9465
  }
9065
- const mergedRects = mergeRects2(matchRects);
9066
- const highlight = annotationStore.getState().addHighlight({
9067
- pageNumber: pageNum,
9068
- rects: mergedRects,
9069
- color,
9070
- text: fullText.substring(matchIndex, matchIndex + text.length)
9071
- });
9072
- highlightIds.push(highlight.id);
9073
9466
  startIndex = matchIndex + 1;
9074
9467
  }
9075
9468
  } catch {
@@ -9142,8 +9535,9 @@ var init_PDFViewerClient = __esm({
9142
9535
  }
9143
9536
  },
9144
9537
  // ==================== Navigation ====================
9145
- goToPage: (page) => {
9146
- viewerStore.getState().goToPage(page);
9538
+ goToPage: async (page, options) => {
9539
+ const behavior = options?.behavior ?? "smooth";
9540
+ await viewerStore.getState().requestScrollToPage(page, behavior);
9147
9541
  },
9148
9542
  nextPage: () => {
9149
9543
  viewerStore.getState().nextPage();
@@ -9203,6 +9597,233 @@ var init_PDFViewerClient = __esm({
9203
9597
  clearSearch: () => {
9204
9598
  searchStore.getState().clearSearch();
9205
9599
  },
9600
+ // ==================== Combined Search & Highlight ====================
9601
+ searchAndHighlight: async (query, options) => {
9602
+ const doc = viewerStore.getState().document;
9603
+ if (!doc) {
9604
+ return { matchCount: 0, highlightIds: [], matches: [] };
9605
+ }
9606
+ const color = options?.color ?? "yellow";
9607
+ const caseSensitive = options?.caseSensitive ?? false;
9608
+ const wholeWord = options?.wholeWord ?? false;
9609
+ const scrollToFirst = options?.scrollToFirst ?? true;
9610
+ const clearPrevious = options?.clearPrevious ?? true;
9611
+ if (clearPrevious) {
9612
+ const existingHighlights = annotationStore.getState().highlights;
9613
+ for (const h of existingHighlights) {
9614
+ if (h.source === "search") {
9615
+ annotationStore.getState().removeHighlight(h.id);
9616
+ }
9617
+ }
9618
+ }
9619
+ let pagesToSearch;
9620
+ if (options?.pageRange) {
9621
+ if (Array.isArray(options.pageRange)) {
9622
+ pagesToSearch = options.pageRange;
9623
+ } else {
9624
+ const { start, end } = options.pageRange;
9625
+ pagesToSearch = Array.from({ length: end - start + 1 }, (_, i) => start + i);
9626
+ }
9627
+ } else {
9628
+ pagesToSearch = Array.from({ length: doc.numPages }, (_, i) => i + 1);
9629
+ }
9630
+ const result = {
9631
+ matchCount: 0,
9632
+ highlightIds: [],
9633
+ matches: []
9634
+ };
9635
+ const searchText = caseSensitive ? query : query.toLowerCase();
9636
+ for (const pageNum of pagesToSearch) {
9637
+ if (pageNum < 1 || pageNum > doc.numPages) continue;
9638
+ try {
9639
+ const page = await doc.getPage(pageNum);
9640
+ const textContent = await page.getTextContent();
9641
+ const viewport = page.getViewport({ scale: 1 });
9642
+ let fullText = "";
9643
+ const textItems = [];
9644
+ for (const item of textContent.items) {
9645
+ if ("str" in item && item.str) {
9646
+ textItems.push({
9647
+ text: item.str,
9648
+ transform: item.transform,
9649
+ width: item.width ?? 0,
9650
+ height: item.height ?? 12
9651
+ });
9652
+ fullText += item.str;
9653
+ }
9654
+ }
9655
+ const textToSearch = caseSensitive ? fullText : fullText.toLowerCase();
9656
+ let startIndex = 0;
9657
+ while (true) {
9658
+ const matchIndex = textToSearch.indexOf(searchText, startIndex);
9659
+ if (matchIndex === -1) break;
9660
+ if (wholeWord) {
9661
+ const beforeChar = matchIndex > 0 ? textToSearch[matchIndex - 1] : " ";
9662
+ const afterChar = matchIndex + query.length < textToSearch.length ? textToSearch[matchIndex + query.length] : " ";
9663
+ if (/\w/.test(beforeChar) || /\w/.test(afterChar)) {
9664
+ startIndex = matchIndex + 1;
9665
+ continue;
9666
+ }
9667
+ }
9668
+ const matchRects = calculateMatchRects3(textItems, matchIndex, query.length, viewport);
9669
+ if (matchRects.length > 0) {
9670
+ const highlight = annotationStore.getState().addHighlight({
9671
+ pageNumber: pageNum,
9672
+ rects: matchRects,
9673
+ color,
9674
+ text: fullText.substring(matchIndex, matchIndex + query.length),
9675
+ source: "search"
9676
+ });
9677
+ result.matchCount++;
9678
+ result.highlightIds.push(highlight.id);
9679
+ result.matches.push({
9680
+ pageNumber: pageNum,
9681
+ text: fullText.substring(matchIndex, matchIndex + query.length),
9682
+ highlightId: highlight.id,
9683
+ rects: matchRects
9684
+ });
9685
+ }
9686
+ startIndex = matchIndex + 1;
9687
+ }
9688
+ } catch {
9689
+ }
9690
+ }
9691
+ if (scrollToFirst && result.matches.length > 0) {
9692
+ const firstMatch = result.matches[0];
9693
+ await viewerStore.getState().requestScrollToPage(firstMatch.pageNumber, "smooth");
9694
+ }
9695
+ return result;
9696
+ },
9697
+ // ==================== Agent Tools ====================
9698
+ agentTools: {
9699
+ navigateToPage: async (page) => {
9700
+ try {
9701
+ const { currentPage: currentPage2, numPages } = viewerStore.getState();
9702
+ if (numPages === 0) {
9703
+ return {
9704
+ success: false,
9705
+ error: { code: "NO_DOCUMENT", message: "No document is loaded" }
9706
+ };
9707
+ }
9708
+ if (page < 1 || page > numPages) {
9709
+ return {
9710
+ success: false,
9711
+ error: { code: "INVALID_PAGE", message: `Page ${page} is out of range (1-${numPages})` }
9712
+ };
9713
+ }
9714
+ const previousPage = currentPage2;
9715
+ await viewerStore.getState().requestScrollToPage(page, "smooth");
9716
+ return {
9717
+ success: true,
9718
+ data: { previousPage, currentPage: page }
9719
+ };
9720
+ } catch (err) {
9721
+ return {
9722
+ success: false,
9723
+ error: { code: "NAVIGATION_FAILED", message: err instanceof Error ? err.message : "Unknown error" }
9724
+ };
9725
+ }
9726
+ },
9727
+ highlightText: async (text, options) => {
9728
+ try {
9729
+ const highlightIds = await handle.highlightText(text, options);
9730
+ return {
9731
+ success: true,
9732
+ data: { matchCount: highlightIds.length, highlightIds }
9733
+ };
9734
+ } catch (err) {
9735
+ return {
9736
+ success: false,
9737
+ error: { code: "HIGHLIGHT_FAILED", message: err instanceof Error ? err.message : "Unknown error" }
9738
+ };
9739
+ }
9740
+ },
9741
+ getPageContent: async (page) => {
9742
+ try {
9743
+ const doc = viewerStore.getState().document;
9744
+ if (!doc) {
9745
+ return {
9746
+ success: false,
9747
+ error: { code: "NO_DOCUMENT", message: "No document is loaded" }
9748
+ };
9749
+ }
9750
+ if (page < 1 || page > doc.numPages) {
9751
+ return {
9752
+ success: false,
9753
+ error: { code: "INVALID_PAGE", message: `Page ${page} is out of range (1-${doc.numPages})` }
9754
+ };
9755
+ }
9756
+ const pageObj = await doc.getPage(page);
9757
+ const textContent = await pageObj.getTextContent();
9758
+ const text = textContent.items.filter((item) => "str" in item).map((item) => item.str).join("");
9759
+ return {
9760
+ success: true,
9761
+ data: { text }
9762
+ };
9763
+ } catch (err) {
9764
+ return {
9765
+ success: false,
9766
+ error: { code: "CONTENT_FETCH_FAILED", message: err instanceof Error ? err.message : "Unknown error" }
9767
+ };
9768
+ }
9769
+ },
9770
+ clearAllVisuals: async () => {
9771
+ try {
9772
+ const highlights = annotationStore.getState().highlights;
9773
+ for (const h of highlights) {
9774
+ annotationStore.getState().removeHighlight(h.id);
9775
+ }
9776
+ const annotations = annotationStore.getState().annotations;
9777
+ for (const a of annotations) {
9778
+ annotationStore.getState().removeAnnotation(a.id);
9779
+ }
9780
+ return { success: true };
9781
+ } catch (err) {
9782
+ return {
9783
+ success: false,
9784
+ error: { code: "CLEAR_FAILED", message: err instanceof Error ? err.message : "Unknown error" }
9785
+ };
9786
+ }
9787
+ }
9788
+ },
9789
+ // ==================== Coordinate Helpers ====================
9790
+ coordinates: {
9791
+ getPageDimensions: (page) => {
9792
+ const doc = viewerStore.getState().document;
9793
+ if (!doc || page < 1 || page > doc.numPages) {
9794
+ return null;
9795
+ }
9796
+ try {
9797
+ return {
9798
+ width: 612,
9799
+ // Default US Letter width
9800
+ height: 792,
9801
+ // Default US Letter height
9802
+ rotation: viewerStore.getState().rotation
9803
+ };
9804
+ } catch {
9805
+ return null;
9806
+ }
9807
+ },
9808
+ percentToPixels: (xPercent, yPercent, page) => {
9809
+ const dimensions = handle.coordinates.getPageDimensions(page);
9810
+ if (!dimensions) return null;
9811
+ const scale2 = viewerStore.getState().scale;
9812
+ return {
9813
+ x: xPercent / 100 * dimensions.width * scale2,
9814
+ y: yPercent / 100 * dimensions.height * scale2
9815
+ };
9816
+ },
9817
+ pixelsToPercent: (x, y, page) => {
9818
+ const dimensions = handle.coordinates.getPageDimensions(page);
9819
+ if (!dimensions) return null;
9820
+ const scale2 = viewerStore.getState().scale;
9821
+ return {
9822
+ x: x / (dimensions.width * scale2) * 100,
9823
+ y: y / (dimensions.height * scale2) * 100
9824
+ };
9825
+ }
9826
+ },
9206
9827
  // ==================== Document ====================
9207
9828
  getDocument: () => {
9208
9829
  return viewerStore.getState().document;
@@ -9289,10 +9910,36 @@ var init_PDFViewerClient = __esm({
9289
9910
  if (prevScaleRef.current !== scale) {
9290
9911
  prevScaleRef.current = scale;
9291
9912
  onScaleChangeRef.current?.(scale);
9913
+ onZoomChangeRef.current?.(scale);
9292
9914
  }
9293
9915
  }, [scale]);
9916
+ (0, import_react40.useEffect)(() => {
9917
+ if (!isControlled || controlledPage === void 0) return;
9918
+ if (prevControlledPageRef.current === controlledPage) return;
9919
+ prevControlledPageRef.current = controlledPage;
9920
+ const { numPages, currentPage: currentPage2 } = viewerStore.getState();
9921
+ if (numPages > 0 && controlledPage !== currentPage2) {
9922
+ viewerStore.getState().requestScrollToPage(controlledPage, "smooth");
9923
+ }
9924
+ }, [controlledPage, isControlled, viewerStore]);
9294
9925
  const themeClass = theme === "dark" ? "dark" : "";
9295
9926
  if (error) {
9927
+ if (errorComponent) {
9928
+ const errorContent = typeof errorComponent === "function" ? errorComponent(error, handleRetry) : errorComponent;
9929
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9930
+ "div",
9931
+ {
9932
+ className: cn(
9933
+ "pdf-viewer pdf-viewer-error",
9934
+ "flex flex-col h-full",
9935
+ "bg-white dark:bg-gray-900",
9936
+ themeClass,
9937
+ className
9938
+ ),
9939
+ children: errorContent
9940
+ }
9941
+ );
9942
+ }
9296
9943
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
9297
9944
  "div",
9298
9945
  {
@@ -9348,7 +9995,7 @@ var init_PDFViewerClient = __esm({
9348
9995
  renderContainer()
9349
9996
  ] }),
9350
9997
  showFloatingZoom && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(FloatingZoomControls, { position: "bottom-right" }),
9351
- isLoading && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-900/80", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col items-center", children: [
9998
+ isLoading && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-900/80", children: loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex flex-col items-center", children: [
9352
9999
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" }),
9353
10000
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-2 text-sm text-gray-500", children: "Loading PDF..." })
9354
10001
  ] }) })
@@ -9461,6 +10108,7 @@ __export(index_exports, {
9461
10108
  OutlinePanel: () => OutlinePanel,
9462
10109
  PDFErrorBoundary: () => PDFErrorBoundary,
9463
10110
  PDFPage: () => PDFPage,
10111
+ PDFThumbnailNav: () => PDFThumbnailNav,
9464
10112
  PDFViewer: () => PDFViewer,
9465
10113
  PDFViewerClient: () => PDFViewerClient,
9466
10114
  PDFViewerContext: () => PDFViewerContext,
@@ -9479,9 +10127,11 @@ __export(index_exports, {
9479
10127
  ThumbnailPanel: () => ThumbnailPanel,
9480
10128
  Toolbar: () => Toolbar,
9481
10129
  VirtualizedDocumentContainer: () => VirtualizedDocumentContainer,
10130
+ applyRotation: () => applyRotation,
9482
10131
  clearHighlights: () => clearHighlights,
9483
10132
  clearStudentData: () => clearStudentData,
9484
10133
  cn: () => cn,
10134
+ countTextOnPage: () => countTextOnPage,
9485
10135
  createAgentAPI: () => createAgentAPI,
9486
10136
  createAgentStore: () => createAgentStore,
9487
10137
  createAnnotationStore: () => createAnnotationStore,
@@ -9490,6 +10140,7 @@ __export(index_exports, {
9490
10140
  createSearchStore: () => createSearchStore,
9491
10141
  createStudentStore: () => createStudentStore,
9492
10142
  createViewerStore: () => createViewerStore,
10143
+ doRectsIntersect: () => doRectsIntersect,
9493
10144
  downloadAnnotationsAsJSON: () => downloadAnnotationsAsJSON,
9494
10145
  downloadAnnotationsAsMarkdown: () => downloadAnnotationsAsMarkdown,
9495
10146
  downloadFile: () => downloadFile,
@@ -9497,25 +10148,39 @@ __export(index_exports, {
9497
10148
  exportAnnotationsAsMarkdown: () => exportAnnotationsAsMarkdown,
9498
10149
  exportHighlightsAsJSON: () => exportHighlightsAsJSON,
9499
10150
  exportHighlightsAsMarkdown: () => exportHighlightsAsMarkdown,
10151
+ extractPageText: () => extractPageText,
10152
+ findTextInDocument: () => findTextInDocument,
10153
+ findTextOnPage: () => findTextOnPage,
9500
10154
  generateDocumentId: () => generateDocumentId,
9501
10155
  getAllDocumentIds: () => getAllDocumentIds,
9502
10156
  getAllStudentDataDocumentIds: () => getAllStudentDataDocumentIds,
9503
10157
  getMetadata: () => getMetadata,
9504
10158
  getOutline: () => getOutline,
9505
10159
  getPage: () => getPage,
10160
+ getPageText: () => getPageText,
9506
10161
  getPageTextContent: () => getPageTextContent,
9507
10162
  getPluginManager: () => getPluginManager,
10163
+ getRectIntersection: () => getRectIntersection,
10164
+ getRotatedDimensions: () => getRotatedDimensions,
9508
10165
  getStorageStats: () => getStorageStats,
9509
10166
  importHighlightsFromJSON: () => importHighlightsFromJSON,
9510
10167
  initializePDFJS: () => initializePDFJS,
9511
10168
  isPDFJSInitialized: () => isPDFJSInitialized,
10169
+ isPointInRect: () => isPointInRect,
9512
10170
  loadDocument: () => loadDocument,
9513
10171
  loadHighlights: () => loadHighlights,
9514
10172
  loadStudentData: () => loadStudentData,
10173
+ mergeAdjacentRects: () => mergeAdjacentRects,
10174
+ pdfToPercent: () => pdfToPercent,
10175
+ pdfToViewport: () => pdfToViewport,
9515
10176
  pdfjsLib: () => pdfjsLib,
10177
+ percentToPDF: () => percentToPDF,
10178
+ percentToViewport: () => percentToViewport,
9516
10179
  quickViewer: () => quickViewer,
10180
+ removeRotation: () => removeRotation,
9517
10181
  saveHighlights: () => saveHighlights,
9518
10182
  saveStudentData: () => saveStudentData,
10183
+ scaleRect: () => scaleRect,
9519
10184
  useAgentContext: () => useAgentContext,
9520
10185
  useAgentStore: () => useAgentStore,
9521
10186
  useAnnotationStore: () => useAnnotationStore,
@@ -9537,6 +10202,8 @@ __export(index_exports, {
9537
10202
  useTouchGestures: () => useTouchGestures,
9538
10203
  useViewerStore: () => useViewerStore,
9539
10204
  useZoom: () => useZoom,
10205
+ viewportToPDF: () => viewportToPDF,
10206
+ viewportToPercent: () => viewportToPercent,
9540
10207
  withErrorBoundary: () => withErrorBoundary
9541
10208
  });
9542
10209
  module.exports = __toCommonJS(index_exports);
@@ -10826,11 +11493,248 @@ var Minimap = (0, import_react49.memo)(function Minimap2({
10826
11493
  // src/components/index.ts
10827
11494
  init_FloatingZoomControls2();
10828
11495
 
10829
- // src/components/ErrorBoundary/PDFErrorBoundary.tsx
11496
+ // src/components/PDFThumbnailNav/PDFThumbnailNav.tsx
10830
11497
  var import_react50 = require("react");
11498
+ init_hooks();
10831
11499
  init_utils();
10832
11500
  var import_jsx_runtime36 = require("react/jsx-runtime");
10833
- var PDFErrorBoundary = class extends import_react50.Component {
11501
+ var DEFAULT_WIDTH = 612;
11502
+ var DEFAULT_HEIGHT = 792;
11503
+ var PDFThumbnailNav = (0, import_react50.memo)(function PDFThumbnailNav2({
11504
+ thumbnailScale = 0.15,
11505
+ orientation = "vertical",
11506
+ maxVisible = 10,
11507
+ className,
11508
+ onThumbnailClick,
11509
+ gap = 8,
11510
+ showPageNumbers = true
11511
+ }) {
11512
+ const { document: document2, numPages, currentPage } = usePDFViewer();
11513
+ const { viewerStore } = usePDFViewerStores();
11514
+ const containerRef = (0, import_react50.useRef)(null);
11515
+ const [thumbnails, setThumbnails] = (0, import_react50.useState)(/* @__PURE__ */ new Map());
11516
+ const [visibleRange, setVisibleRange] = (0, import_react50.useState)({ start: 1, end: maxVisible });
11517
+ const renderQueueRef = (0, import_react50.useRef)(/* @__PURE__ */ new Set());
11518
+ const pageCache = (0, import_react50.useRef)(/* @__PURE__ */ new Map());
11519
+ const thumbnailWidth = Math.floor(DEFAULT_WIDTH * thumbnailScale);
11520
+ const thumbnailHeight = Math.floor(DEFAULT_HEIGHT * thumbnailScale);
11521
+ const updateVisibleRange = (0, import_react50.useCallback)(() => {
11522
+ if (!containerRef.current || numPages === 0) return;
11523
+ const container = containerRef.current;
11524
+ const isHorizontal2 = orientation === "horizontal";
11525
+ const scrollPosition = isHorizontal2 ? container.scrollLeft : container.scrollTop;
11526
+ const viewportSize = isHorizontal2 ? container.clientWidth : container.clientHeight;
11527
+ const itemSize = (isHorizontal2 ? thumbnailWidth : thumbnailHeight) + gap;
11528
+ const firstVisible = Math.max(1, Math.floor(scrollPosition / itemSize) + 1);
11529
+ const visibleCount = Math.ceil(viewportSize / itemSize) + 2;
11530
+ const lastVisible = Math.min(numPages, firstVisible + visibleCount);
11531
+ setVisibleRange({ start: firstVisible, end: lastVisible });
11532
+ }, [numPages, orientation, thumbnailWidth, thumbnailHeight, gap]);
11533
+ (0, import_react50.useEffect)(() => {
11534
+ const container = containerRef.current;
11535
+ if (!container) return;
11536
+ const handleScroll = () => {
11537
+ requestAnimationFrame(updateVisibleRange);
11538
+ };
11539
+ container.addEventListener("scroll", handleScroll, { passive: true });
11540
+ updateVisibleRange();
11541
+ return () => container.removeEventListener("scroll", handleScroll);
11542
+ }, [updateVisibleRange]);
11543
+ (0, import_react50.useEffect)(() => {
11544
+ if (!document2) {
11545
+ setThumbnails(/* @__PURE__ */ new Map());
11546
+ pageCache.current.clear();
11547
+ return;
11548
+ }
11549
+ const renderThumbnails = async () => {
11550
+ const newThumbnails = new Map(thumbnails);
11551
+ const pagesToRender = [];
11552
+ for (let i = visibleRange.start; i <= visibleRange.end; i++) {
11553
+ if (!newThumbnails.has(i) && !renderQueueRef.current.has(i)) {
11554
+ pagesToRender.push(i);
11555
+ renderQueueRef.current.add(i);
11556
+ }
11557
+ }
11558
+ for (const pageNum of pagesToRender) {
11559
+ try {
11560
+ let page = pageCache.current.get(pageNum);
11561
+ if (!page) {
11562
+ page = await document2.getPage(pageNum);
11563
+ pageCache.current.set(pageNum, page);
11564
+ }
11565
+ const viewport = page.getViewport({ scale: thumbnailScale });
11566
+ const canvas = window.document.createElement("canvas");
11567
+ canvas.width = Math.floor(viewport.width);
11568
+ canvas.height = Math.floor(viewport.height);
11569
+ const ctx = canvas.getContext("2d");
11570
+ if (ctx) {
11571
+ await page.render({
11572
+ canvasContext: ctx,
11573
+ viewport
11574
+ }).promise;
11575
+ newThumbnails.set(pageNum, {
11576
+ pageNumber: pageNum,
11577
+ canvas,
11578
+ width: canvas.width,
11579
+ height: canvas.height
11580
+ });
11581
+ }
11582
+ } catch (error) {
11583
+ console.error(`Failed to render thumbnail for page ${pageNum}:`, error);
11584
+ } finally {
11585
+ renderQueueRef.current.delete(pageNum);
11586
+ }
11587
+ }
11588
+ if (pagesToRender.length > 0) {
11589
+ setThumbnails(newThumbnails);
11590
+ }
11591
+ };
11592
+ renderThumbnails();
11593
+ }, [document2, visibleRange, thumbnailScale, thumbnails]);
11594
+ (0, import_react50.useEffect)(() => {
11595
+ if (!containerRef.current || numPages === 0) return;
11596
+ const container = containerRef.current;
11597
+ const isHorizontal2 = orientation === "horizontal";
11598
+ const itemSize = (isHorizontal2 ? thumbnailWidth : thumbnailHeight) + gap;
11599
+ const targetPosition = (currentPage - 1) * itemSize;
11600
+ const scrollPosition = isHorizontal2 ? container.scrollLeft : container.scrollTop;
11601
+ const viewportSize = isHorizontal2 ? container.clientWidth : container.clientHeight;
11602
+ if (targetPosition < scrollPosition || targetPosition + itemSize > scrollPosition + viewportSize) {
11603
+ const targetScroll = targetPosition - (viewportSize - itemSize) / 2;
11604
+ container.scrollTo({
11605
+ [isHorizontal2 ? "left" : "top"]: Math.max(0, targetScroll),
11606
+ behavior: "smooth"
11607
+ });
11608
+ }
11609
+ }, [currentPage, numPages, orientation, thumbnailWidth, thumbnailHeight, gap]);
11610
+ const handleThumbnailClick = (0, import_react50.useCallback)((pageNum) => {
11611
+ onThumbnailClick?.(pageNum);
11612
+ viewerStore.getState().requestScrollToPage(pageNum, "smooth");
11613
+ }, [onThumbnailClick, viewerStore]);
11614
+ if (!document2 || numPages === 0) {
11615
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11616
+ "div",
11617
+ {
11618
+ className: cn(
11619
+ "pdf-thumbnail-nav",
11620
+ "flex items-center justify-center",
11621
+ "bg-gray-100 dark:bg-gray-800",
11622
+ "text-gray-500 dark:text-gray-400",
11623
+ "text-sm",
11624
+ className
11625
+ ),
11626
+ style: {
11627
+ width: orientation === "vertical" ? thumbnailWidth + 24 : "100%",
11628
+ height: orientation === "horizontal" ? thumbnailHeight + 40 : "100%"
11629
+ },
11630
+ children: "No document"
11631
+ }
11632
+ );
11633
+ }
11634
+ const isHorizontal = orientation === "horizontal";
11635
+ const totalSize = numPages * ((isHorizontal ? thumbnailWidth : thumbnailHeight) + gap) - gap;
11636
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11637
+ "div",
11638
+ {
11639
+ ref: containerRef,
11640
+ className: cn(
11641
+ "pdf-thumbnail-nav",
11642
+ "overflow-auto",
11643
+ "bg-gray-100 dark:bg-gray-800",
11644
+ isHorizontal ? "flex-row" : "flex-col",
11645
+ className
11646
+ ),
11647
+ style: {
11648
+ ...isHorizontal ? { overflowX: "auto", overflowY: "hidden" } : { overflowX: "hidden", overflowY: "auto" }
11649
+ },
11650
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11651
+ "div",
11652
+ {
11653
+ className: cn(
11654
+ "relative",
11655
+ isHorizontal ? "flex flex-row items-center" : "flex flex-col items-center"
11656
+ ),
11657
+ style: {
11658
+ [isHorizontal ? "width" : "height"]: totalSize,
11659
+ [isHorizontal ? "minWidth" : "minHeight"]: totalSize,
11660
+ padding: gap / 2,
11661
+ gap
11662
+ },
11663
+ children: Array.from({ length: numPages }, (_, i) => i + 1).map((pageNum) => {
11664
+ const thumbnail = thumbnails.get(pageNum);
11665
+ const isActive = pageNum === currentPage;
11666
+ const isVisible = pageNum >= visibleRange.start && pageNum <= visibleRange.end;
11667
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
11668
+ "div",
11669
+ {
11670
+ className: cn(
11671
+ "pdf-thumbnail",
11672
+ "flex-shrink-0 cursor-pointer transition-all duration-200",
11673
+ "border-2 rounded shadow-sm hover:shadow-md",
11674
+ isActive ? "border-blue-500 ring-2 ring-blue-200 dark:ring-blue-800" : "border-gray-300 dark:border-gray-600 hover:border-blue-400"
11675
+ ),
11676
+ style: {
11677
+ width: thumbnailWidth,
11678
+ height: thumbnailHeight + (showPageNumbers ? 24 : 0)
11679
+ },
11680
+ onClick: () => handleThumbnailClick(pageNum),
11681
+ role: "button",
11682
+ tabIndex: 0,
11683
+ "aria-label": `Go to page ${pageNum}`,
11684
+ "aria-current": isActive ? "page" : void 0,
11685
+ onKeyDown: (e) => {
11686
+ if (e.key === "Enter" || e.key === " ") {
11687
+ e.preventDefault();
11688
+ handleThumbnailClick(pageNum);
11689
+ }
11690
+ },
11691
+ children: [
11692
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11693
+ "div",
11694
+ {
11695
+ className: "relative bg-white dark:bg-gray-700",
11696
+ style: {
11697
+ width: thumbnailWidth,
11698
+ height: thumbnailHeight
11699
+ },
11700
+ children: isVisible && thumbnail?.canvas ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11701
+ "img",
11702
+ {
11703
+ src: thumbnail.canvas.toDataURL(),
11704
+ alt: `Page ${pageNum}`,
11705
+ className: "w-full h-full object-contain",
11706
+ loading: "lazy"
11707
+ }
11708
+ ) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute inset-0 flex items-center justify-center text-gray-400 dark:text-gray-500 text-xs", children: pageNum })
11709
+ }
11710
+ ),
11711
+ showPageNumbers && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11712
+ "div",
11713
+ {
11714
+ className: cn(
11715
+ "text-center text-xs py-1",
11716
+ "bg-gray-50 dark:bg-gray-700",
11717
+ isActive ? "text-blue-600 dark:text-blue-400 font-medium" : "text-gray-600 dark:text-gray-400"
11718
+ ),
11719
+ children: pageNum
11720
+ }
11721
+ )
11722
+ ]
11723
+ },
11724
+ pageNum
11725
+ );
11726
+ })
11727
+ }
11728
+ )
11729
+ }
11730
+ );
11731
+ });
11732
+
11733
+ // src/components/ErrorBoundary/PDFErrorBoundary.tsx
11734
+ var import_react51 = require("react");
11735
+ init_utils();
11736
+ var import_jsx_runtime37 = require("react/jsx-runtime");
11737
+ var PDFErrorBoundary = class extends import_react51.Component {
10834
11738
  constructor(props) {
10835
11739
  super(props);
10836
11740
  this.handleReset = () => {
@@ -10857,7 +11761,7 @@ var PDFErrorBoundary = class extends import_react50.Component {
10857
11761
  return fallback;
10858
11762
  }
10859
11763
  if (showDefaultUI) {
10860
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11764
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
10861
11765
  DefaultErrorUI,
10862
11766
  {
10863
11767
  error,
@@ -10876,7 +11780,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10876
11780
  const isNetworkError = error.message.includes("fetch") || error.message.includes("network") || error.message.includes("Failed to load");
10877
11781
  let title = "Something went wrong";
10878
11782
  let description = error.message;
10879
- let icon = /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11783
+ let icon = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
10880
11784
  "path",
10881
11785
  {
10882
11786
  strokeLinecap: "round",
@@ -10888,7 +11792,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10888
11792
  if (isPDFError) {
10889
11793
  title = "Unable to load PDF";
10890
11794
  description = "The PDF file could not be loaded. It may be corrupted or in an unsupported format.";
10891
- icon = /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11795
+ icon = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
10892
11796
  "path",
10893
11797
  {
10894
11798
  strokeLinecap: "round",
@@ -10900,7 +11804,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10900
11804
  } else if (isNetworkError) {
10901
11805
  title = "Network error";
10902
11806
  description = "Unable to fetch the PDF file. Please check your internet connection and try again.";
10903
- icon = /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11807
+ icon = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("svg", { className: "w-12 h-12 text-red-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
10904
11808
  "path",
10905
11809
  {
10906
11810
  strokeLinecap: "round",
@@ -10910,7 +11814,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10910
11814
  }
10911
11815
  ) });
10912
11816
  }
10913
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
11817
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
10914
11818
  "div",
10915
11819
  {
10916
11820
  className: cn(
@@ -10923,14 +11827,14 @@ function DefaultErrorUI({ error, onReset, className }) {
10923
11827
  ),
10924
11828
  children: [
10925
11829
  icon,
10926
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h2", { className: "mt-4 text-xl font-semibold text-gray-900 dark:text-gray-100", children: title }),
10927
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 max-w-md", children: description }),
10928
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("details", { className: "mt-4 text-left max-w-md w-full", children: [
10929
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("summary", { className: "cursor-pointer text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", children: "Technical details" }),
10930
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("pre", { className: "mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto", children: error.stack || error.message })
11830
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("h2", { className: "mt-4 text-xl font-semibold text-gray-900 dark:text-gray-100", children: title }),
11831
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400 max-w-md", children: description }),
11832
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("details", { className: "mt-4 text-left max-w-md w-full", children: [
11833
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("summary", { className: "cursor-pointer text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", children: "Technical details" }),
11834
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("pre", { className: "mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs overflow-auto", children: error.stack || error.message })
10931
11835
  ] }),
10932
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "mt-6 flex gap-3", children: [
10933
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11836
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "mt-6 flex gap-3", children: [
11837
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
10934
11838
  "button",
10935
11839
  {
10936
11840
  onClick: onReset,
@@ -10944,7 +11848,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10944
11848
  children: "Try again"
10945
11849
  }
10946
11850
  ),
10947
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
11851
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
10948
11852
  "button",
10949
11853
  {
10950
11854
  onClick: () => window.location.reload(),
@@ -10964,7 +11868,7 @@ function DefaultErrorUI({ error, onReset, className }) {
10964
11868
  );
10965
11869
  }
10966
11870
  function withErrorBoundary({ component, ...props }) {
10967
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(PDFErrorBoundary, { ...props, children: component });
11871
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(PDFErrorBoundary, { ...props, children: component });
10968
11872
  }
10969
11873
 
10970
11874
  // src/index.ts
@@ -10999,6 +11903,7 @@ init_utils();
10999
11903
  OutlinePanel,
11000
11904
  PDFErrorBoundary,
11001
11905
  PDFPage,
11906
+ PDFThumbnailNav,
11002
11907
  PDFViewer,
11003
11908
  PDFViewerClient,
11004
11909
  PDFViewerContext,
@@ -11017,9 +11922,11 @@ init_utils();
11017
11922
  ThumbnailPanel,
11018
11923
  Toolbar,
11019
11924
  VirtualizedDocumentContainer,
11925
+ applyRotation,
11020
11926
  clearHighlights,
11021
11927
  clearStudentData,
11022
11928
  cn,
11929
+ countTextOnPage,
11023
11930
  createAgentAPI,
11024
11931
  createAgentStore,
11025
11932
  createAnnotationStore,
@@ -11028,6 +11935,7 @@ init_utils();
11028
11935
  createSearchStore,
11029
11936
  createStudentStore,
11030
11937
  createViewerStore,
11938
+ doRectsIntersect,
11031
11939
  downloadAnnotationsAsJSON,
11032
11940
  downloadAnnotationsAsMarkdown,
11033
11941
  downloadFile,
@@ -11035,25 +11943,39 @@ init_utils();
11035
11943
  exportAnnotationsAsMarkdown,
11036
11944
  exportHighlightsAsJSON,
11037
11945
  exportHighlightsAsMarkdown,
11946
+ extractPageText,
11947
+ findTextInDocument,
11948
+ findTextOnPage,
11038
11949
  generateDocumentId,
11039
11950
  getAllDocumentIds,
11040
11951
  getAllStudentDataDocumentIds,
11041
11952
  getMetadata,
11042
11953
  getOutline,
11043
11954
  getPage,
11955
+ getPageText,
11044
11956
  getPageTextContent,
11045
11957
  getPluginManager,
11958
+ getRectIntersection,
11959
+ getRotatedDimensions,
11046
11960
  getStorageStats,
11047
11961
  importHighlightsFromJSON,
11048
11962
  initializePDFJS,
11049
11963
  isPDFJSInitialized,
11964
+ isPointInRect,
11050
11965
  loadDocument,
11051
11966
  loadHighlights,
11052
11967
  loadStudentData,
11968
+ mergeAdjacentRects,
11969
+ pdfToPercent,
11970
+ pdfToViewport,
11053
11971
  pdfjsLib,
11972
+ percentToPDF,
11973
+ percentToViewport,
11054
11974
  quickViewer,
11975
+ removeRotation,
11055
11976
  saveHighlights,
11056
11977
  saveStudentData,
11978
+ scaleRect,
11057
11979
  useAgentContext,
11058
11980
  useAgentStore,
11059
11981
  useAnnotationStore,
@@ -11075,6 +11997,8 @@ init_utils();
11075
11997
  useTouchGestures,
11076
11998
  useViewerStore,
11077
11999
  useZoom,
12000
+ viewportToPDF,
12001
+ viewportToPercent,
11078
12002
  withErrorBoundary
11079
12003
  });
11080
12004
  //# sourceMappingURL=index.cjs.map