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 +997 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +420 -6
- package/dist/index.d.ts +420 -6
- package/dist/index.js +976 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
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
|
|
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().
|
|
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().
|
|
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
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
const
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
const
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
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
|
-
|
|
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
|
|
9439
|
+
const textItems = [];
|
|
9034
9440
|
for (const item of textContent.items) {
|
|
9035
9441
|
if ("str" in item && item.str) {
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
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
|
-
|
|
9063
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
10927
|
-
/* @__PURE__ */ (0,
|
|
10928
|
-
/* @__PURE__ */ (0,
|
|
10929
|
-
/* @__PURE__ */ (0,
|
|
10930
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
10933
|
-
/* @__PURE__ */ (0,
|
|
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,
|
|
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,
|
|
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
|