@file-viewer/renderer-pdf 2.2.4 → 2.2.6
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/pdf.js +194 -16
- package/dist/pdfBbox.d.ts +21 -0
- package/dist/pdfBbox.js +93 -0
- package/dist/pdfBboxController.d.ts +42 -0
- package/dist/pdfBboxController.js +159 -0
- package/dist/pdfViewState.d.ts +17 -0
- package/dist/pdfViewState.js +27 -0
- package/package.json +2 -2
package/dist/pdf.js
CHANGED
|
@@ -5,6 +5,8 @@ import { DEFAULT_FILE_VIEWER_PDF_WORKER_PATH, resolveFileViewerPdfAssetUrls, res
|
|
|
5
5
|
import { pdfViewerStyle } from './pdfStyles.js';
|
|
6
6
|
import { collectMalformedIdentityFontNames, createPdfCjkFontFallbackManager, detectMalformedIdentityCjkFontFamilies, } from './pdfFontFallback.js';
|
|
7
7
|
import { PDF_FIT_HORIZONTAL_PADDING, PDF_PAGE_BORDER_WIDTH, resolvePdfFitViewportSize, } from './pdfFit.js';
|
|
8
|
+
import { createPdfBoundingBoxController, } from './pdfBboxController.js';
|
|
9
|
+
import { clampPdfScale, normalizePdfRotation, resolvePdfViewStateUpdate, } from './pdfViewState.js';
|
|
8
10
|
import { capturePdfJsWorkerGlobal, scopePdfJsWorkerMessageHandler, } from './pdfWorkerGlobal.js';
|
|
9
11
|
import { readPdfJsWorkerVersion } from './pdfWorkerVersion.js';
|
|
10
12
|
export const DEFAULT_FILE_VIEWER_PDF_WORKER_URL = DEFAULT_FILE_VIEWER_PDF_WORKER_PATH;
|
|
@@ -38,6 +40,8 @@ const createStyle = (documentRef) => {
|
|
|
38
40
|
.pdf-page-thumb--thumbnail{width:46px;height:60px;overflow:hidden;background:#fff}
|
|
39
41
|
.pdf-page-thumb--thumbnail img{display:block;width:100%;height:100%;object-fit:contain}
|
|
40
42
|
.pdf-page-thumb--thumbnail span{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%}
|
|
43
|
+
.pdf-bbox-layer{position:absolute;inset:0;z-index:20;pointer-events:none;overflow:hidden}
|
|
44
|
+
.pdf-bbox-highlight{position:absolute;box-sizing:border-box;border:2px solid var(--pdf-bbox-color,#f97316);border-radius:3px;background:rgba(249,115,22,.16);background:color-mix(in srgb,var(--pdf-bbox-color,#f97316) 18%,transparent);box-shadow:0 0 0 1px rgba(255,255,255,.8),0 2px 8px rgba(15,23,42,.16)}
|
|
41
45
|
[data-viewer-theme='dark'] .pdf-shell{background:#101820;color:#e5eef8}
|
|
42
46
|
[data-viewer-theme='dark'] .pdf-toolbar,[data-viewer-theme='dark'] .pdf-nav-pane,[data-viewer-theme='dark'] .pdf-nav-head,[data-viewer-theme='dark'] .pdf-nav-tabs{border-color:rgba(148,163,184,.18);background:#111827;box-shadow:none}
|
|
43
47
|
[data-viewer-theme='dark'] .pdf-toolbar-group,[data-viewer-theme='dark'] .pdf-page-button,[data-viewer-theme='dark'] .pdf-outline-empty,[data-viewer-theme='dark'] .pdf-state{border-color:rgba(148,163,184,.18);background:#151f2b;color:#cbd5e1}
|
|
@@ -86,11 +90,8 @@ const createButton = (documentRef, className, title, label) => {
|
|
|
86
90
|
}
|
|
87
91
|
return button;
|
|
88
92
|
};
|
|
89
|
-
const normalizeRotation =
|
|
90
|
-
|
|
91
|
-
return (normalized === 90 || normalized === 180 || normalized === 270 ? normalized : 0);
|
|
92
|
-
};
|
|
93
|
-
const clampScale = (scale) => Number(Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale)).toFixed(2));
|
|
93
|
+
const normalizeRotation = normalizePdfRotation;
|
|
94
|
+
const clampScale = (scale) => clampPdfScale(scale, MIN_SCALE, MAX_SCALE);
|
|
94
95
|
const createPdfSearchState = (query = '') => ({
|
|
95
96
|
query,
|
|
96
97
|
total: 0,
|
|
@@ -340,6 +341,8 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
340
341
|
let suppressScrollEventUntil = 0;
|
|
341
342
|
let userScrollIntentUntil = 0;
|
|
342
343
|
let scrollStateFrame = 0;
|
|
344
|
+
let rotationOperationVersion = 0;
|
|
345
|
+
let pendingUserRotationAnchor = null;
|
|
343
346
|
let pdfSearchState = createPdfSearchState();
|
|
344
347
|
let pdfMatchesCount = { current: 0, total: 0 };
|
|
345
348
|
let pdfSearchOptions;
|
|
@@ -456,9 +459,22 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
456
459
|
visit(outlineItems, 0);
|
|
457
460
|
return result;
|
|
458
461
|
};
|
|
462
|
+
const navScrollTopByMode = {
|
|
463
|
+
pages: 0,
|
|
464
|
+
outline: 0,
|
|
465
|
+
};
|
|
459
466
|
const renderNavList = () => {
|
|
467
|
+
if (navList.classList.contains('pdf-page-list')) {
|
|
468
|
+
navScrollTopByMode.pages = navList.scrollTop;
|
|
469
|
+
}
|
|
470
|
+
else if (navList.classList.contains('pdf-outline-list')) {
|
|
471
|
+
navScrollTopByMode.outline = navList.scrollTop;
|
|
472
|
+
}
|
|
460
473
|
navList.replaceChildren();
|
|
461
474
|
navList.className = navMode === 'pages' ? 'pdf-page-list' : 'pdf-outline-list';
|
|
475
|
+
const restoreNavScrollTop = () => {
|
|
476
|
+
navList.scrollTop = navScrollTopByMode[navMode];
|
|
477
|
+
};
|
|
462
478
|
if (navMode === 'pages') {
|
|
463
479
|
thumbnailObserver === null || thumbnailObserver === void 0 ? void 0 : thumbnailObserver.disconnect();
|
|
464
480
|
for (let page = 1; page <= pageCount; page += 1) {
|
|
@@ -478,6 +494,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
478
494
|
button.addEventListener('click', () => goToPage(page, 'page-click', 'user'));
|
|
479
495
|
navList.append(button);
|
|
480
496
|
}
|
|
497
|
+
restoreNavScrollTop();
|
|
481
498
|
return;
|
|
482
499
|
}
|
|
483
500
|
const entries = flattenedOutlineItems();
|
|
@@ -500,6 +517,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
500
517
|
if (!entries.length) {
|
|
501
518
|
navList.append(createElement(documentRef, 'div', 'pdf-outline-empty', t('pdf.nav.outlineEmpty')));
|
|
502
519
|
}
|
|
520
|
+
restoreNavScrollTop();
|
|
503
521
|
};
|
|
504
522
|
const paintPdfThumbnail = (pageNumber, thumb) => {
|
|
505
523
|
const imageUrl = pdfThumbnails.get(pageNumber);
|
|
@@ -957,6 +975,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
957
975
|
};
|
|
958
976
|
const getPdfViewState = () => {
|
|
959
977
|
const zoom = getPdfZoomState();
|
|
978
|
+
const bbox = pdfBoundingBoxController.getStateValue();
|
|
960
979
|
return {
|
|
961
980
|
renderer: 'pdf',
|
|
962
981
|
page: currentPage,
|
|
@@ -969,6 +988,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
969
988
|
visible: navigationEnabled ? navVisible : false,
|
|
970
989
|
mode: navMode,
|
|
971
990
|
},
|
|
991
|
+
extra: bbox ? { bbox } : undefined,
|
|
972
992
|
};
|
|
973
993
|
};
|
|
974
994
|
const emitViewStateChange = (action, source = 'viewer') => {
|
|
@@ -993,6 +1013,20 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
993
1013
|
const suppressProgrammaticScrollEvents = () => {
|
|
994
1014
|
suppressScrollEventUntil = Math.max(suppressScrollEventUntil, Date.now() + 180);
|
|
995
1015
|
};
|
|
1016
|
+
const pdfBoundingBoxController = createPdfBoundingBoxController({
|
|
1017
|
+
documentRef,
|
|
1018
|
+
targetWindow,
|
|
1019
|
+
viewerRoot: pdfViewerRoot,
|
|
1020
|
+
scrollContainer: container,
|
|
1021
|
+
initial: options === null || options === void 0 ? void 0 : options.bbox,
|
|
1022
|
+
getDocument: () => pdfContext.document,
|
|
1023
|
+
getPageCount: () => pageCount,
|
|
1024
|
+
getCurrentPage: () => currentPage,
|
|
1025
|
+
getRotation: () => currentRotation,
|
|
1026
|
+
goToPage: (page, source) => goToPage(page, 'bbox-focus', source, false),
|
|
1027
|
+
suppressProgrammaticScrollEvents,
|
|
1028
|
+
waitForPaint,
|
|
1029
|
+
});
|
|
996
1030
|
const markFitInteraction = (source) => {
|
|
997
1031
|
if (source !== 'user' && source !== 'api') {
|
|
998
1032
|
return;
|
|
@@ -1003,7 +1037,83 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1003
1037
|
autoFitWidth = false;
|
|
1004
1038
|
activeFitRequest = null;
|
|
1005
1039
|
};
|
|
1040
|
+
const getPdfPageElement = (pageNumber) => {
|
|
1041
|
+
var _a;
|
|
1042
|
+
const pageView = (_a = pdfContext.viewer) === null || _a === void 0 ? void 0 : _a.getPageView(pageNumber - 1);
|
|
1043
|
+
return (pageView === null || pageView === void 0 ? void 0 : pageView.div) ||
|
|
1044
|
+
pdfViewerRoot.querySelector(`.page[data-page-number="${pageNumber}"]`);
|
|
1045
|
+
};
|
|
1046
|
+
const captureCurrentPdfPageAnchor = () => {
|
|
1047
|
+
const pageElement = getPdfPageElement(currentPage);
|
|
1048
|
+
if (!pageElement) {
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
const containerRect = container.getBoundingClientRect();
|
|
1052
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
1053
|
+
const pageHeight = pageElement.offsetHeight || pageRect.height;
|
|
1054
|
+
if (pageHeight <= 0) {
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
const pageTop = pageRect.top - containerRect.top + container.scrollTop;
|
|
1058
|
+
const inPageRatio = (container.scrollTop - pageTop) / pageHeight;
|
|
1059
|
+
return {
|
|
1060
|
+
page: currentPage,
|
|
1061
|
+
inPageRatio: Math.max(0, Math.min(1, inPageRatio)),
|
|
1062
|
+
};
|
|
1063
|
+
};
|
|
1064
|
+
const cancelPendingUserRotationRestore = () => {
|
|
1065
|
+
if (!pendingUserRotationAnchor) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
pendingUserRotationAnchor = null;
|
|
1069
|
+
rotationOperationVersion += 1;
|
|
1070
|
+
};
|
|
1071
|
+
const restoreUserRotationAnchor = (anchor, operationVersion) => {
|
|
1072
|
+
const apply = () => {
|
|
1073
|
+
if (destroyed ||
|
|
1074
|
+
operationVersion !== rotationOperationVersion ||
|
|
1075
|
+
pendingUserRotationAnchor !== anchor) {
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
const pageElement = getPdfPageElement(anchor.page);
|
|
1079
|
+
if (!pageElement) {
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
const containerRect = container.getBoundingClientRect();
|
|
1083
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
1084
|
+
const pageHeight = pageElement.offsetHeight || pageRect.height;
|
|
1085
|
+
if (pageHeight <= 0) {
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
const pageTop = pageRect.top - containerRect.top + container.scrollTop;
|
|
1089
|
+
const maxTop = Math.max(0, container.scrollHeight - container.clientHeight);
|
|
1090
|
+
suppressProgrammaticScrollEvents();
|
|
1091
|
+
container.scrollTop = Math.max(0, Math.min(maxTop, pageTop + anchor.inPageRatio * pageHeight));
|
|
1092
|
+
currentPage = anchor.page;
|
|
1093
|
+
syncUi();
|
|
1094
|
+
return true;
|
|
1095
|
+
};
|
|
1096
|
+
apply();
|
|
1097
|
+
void waitForPaint(targetWindow)
|
|
1098
|
+
.then(() => {
|
|
1099
|
+
apply();
|
|
1100
|
+
return waitForPaint(targetWindow);
|
|
1101
|
+
})
|
|
1102
|
+
.then(apply);
|
|
1103
|
+
targetWindow.requestAnimationFrame(() => {
|
|
1104
|
+
apply();
|
|
1105
|
+
targetWindow.requestAnimationFrame(apply);
|
|
1106
|
+
});
|
|
1107
|
+
targetWindow.setTimeout(() => {
|
|
1108
|
+
apply();
|
|
1109
|
+
if (operationVersion === rotationOperationVersion &&
|
|
1110
|
+
pendingUserRotationAnchor === anchor) {
|
|
1111
|
+
pendingUserRotationAnchor = null;
|
|
1112
|
+
}
|
|
1113
|
+
}, 180);
|
|
1114
|
+
};
|
|
1006
1115
|
const recordUserScrollIntent = () => {
|
|
1116
|
+
cancelPendingUserRotationRestore();
|
|
1007
1117
|
userScrollIntentUntil = Date.now() + 750;
|
|
1008
1118
|
suppressScrollEventUntil = 0;
|
|
1009
1119
|
markFitInteraction('user');
|
|
@@ -1027,7 +1137,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1027
1137
|
}
|
|
1028
1138
|
};
|
|
1029
1139
|
const applyPdfViewState = async (state, applyOptions = {}) => {
|
|
1030
|
-
var _a
|
|
1140
|
+
var _a;
|
|
1031
1141
|
if (!pdfContext.viewer || loadStatus !== 'ready') {
|
|
1032
1142
|
pendingInitialViewState = state;
|
|
1033
1143
|
return getPdfViewState();
|
|
@@ -1039,29 +1149,46 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1039
1149
|
const applyVersion = ++viewStateApplyVersion;
|
|
1040
1150
|
activeViewStateApplyVersion = applyVersion;
|
|
1041
1151
|
suppressProgrammaticScrollEvents();
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1152
|
+
const update = resolvePdfViewStateUpdate(state, {
|
|
1153
|
+
rotation: currentRotation,
|
|
1154
|
+
scale: currentScale,
|
|
1155
|
+
page: currentPage,
|
|
1156
|
+
pageCount,
|
|
1157
|
+
}, {
|
|
1158
|
+
minScale: MIN_SCALE,
|
|
1159
|
+
maxScale: MAX_SCALE,
|
|
1160
|
+
});
|
|
1161
|
+
const hasBboxUpdate = !!state.extra && Object.prototype.hasOwnProperty.call(state.extra, 'bbox');
|
|
1162
|
+
const bboxUpdate = hasBboxUpdate ? (_a = state.extra) === null || _a === void 0 ? void 0 : _a.bbox : undefined;
|
|
1045
1163
|
try {
|
|
1046
1164
|
if (state.navigation) {
|
|
1165
|
+
let navigationChanged = false;
|
|
1047
1166
|
if (navigationEnabled && typeof state.navigation.visible === 'boolean') {
|
|
1167
|
+
navigationChanged = navVisible !== state.navigation.visible;
|
|
1048
1168
|
navVisible = state.navigation.visible;
|
|
1049
1169
|
}
|
|
1050
1170
|
if (state.navigation.mode === 'pages' || state.navigation.mode === 'outline') {
|
|
1171
|
+
navigationChanged = navigationChanged || navMode !== state.navigation.mode;
|
|
1051
1172
|
navMode = state.navigation.mode;
|
|
1052
1173
|
}
|
|
1053
|
-
|
|
1174
|
+
if (navigationChanged) {
|
|
1175
|
+
syncUi();
|
|
1176
|
+
}
|
|
1054
1177
|
}
|
|
1055
|
-
if (
|
|
1056
|
-
applyRotation(
|
|
1178
|
+
if (update.rotation !== undefined) {
|
|
1179
|
+
applyRotation(update.rotation, 'rotation-change', source, false);
|
|
1057
1180
|
}
|
|
1058
|
-
if (
|
|
1181
|
+
if (update.scale !== undefined) {
|
|
1059
1182
|
autoFitWidth = false;
|
|
1060
|
-
setScale(
|
|
1183
|
+
setScale(update.scale, 'zoom-change', source, false);
|
|
1061
1184
|
}
|
|
1062
|
-
if (
|
|
1063
|
-
goToPage(
|
|
1185
|
+
if (update.page !== undefined) {
|
|
1186
|
+
goToPage(update.page, 'page-change', source, false);
|
|
1064
1187
|
}
|
|
1188
|
+
// A remote presenter can send scroll snapshots faster than one animation
|
|
1189
|
+
// frame. Apply the latest offset before yielding so superseded promises
|
|
1190
|
+
// cannot starve the projected screen until the presenter stops scrolling.
|
|
1191
|
+
restoreScrollState(state.scroll, false);
|
|
1065
1192
|
await waitForPaint(targetWindow);
|
|
1066
1193
|
if (applyVersion !== viewStateApplyVersion) {
|
|
1067
1194
|
return getPdfViewState();
|
|
@@ -1072,6 +1199,9 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1072
1199
|
return getPdfViewState();
|
|
1073
1200
|
}
|
|
1074
1201
|
restoreScrollState(state.scroll, false);
|
|
1202
|
+
if (hasBboxUpdate) {
|
|
1203
|
+
await pdfBoundingBoxController.set(bboxUpdate, { focus: true, source });
|
|
1204
|
+
}
|
|
1075
1205
|
syncUi();
|
|
1076
1206
|
if (notify && applyVersion === viewStateApplyVersion) {
|
|
1077
1207
|
emitViewStateChange(action, source);
|
|
@@ -1276,6 +1406,18 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1276
1406
|
const applyRotation = (rotation, action = 'rotation-change', source = 'viewer', notifyViewState = true) => {
|
|
1277
1407
|
markFitInteraction(source);
|
|
1278
1408
|
const normalized = normalizeRotation(rotation);
|
|
1409
|
+
if (source === 'user' && pdfContext.viewer) {
|
|
1410
|
+
pendingUserRotationAnchor || (pendingUserRotationAnchor = captureCurrentPdfPageAnchor());
|
|
1411
|
+
}
|
|
1412
|
+
else {
|
|
1413
|
+
cancelPendingUserRotationRestore();
|
|
1414
|
+
}
|
|
1415
|
+
const pageAnchor = source === 'user' ? pendingUserRotationAnchor : null;
|
|
1416
|
+
const operationVersion = ++rotationOperationVersion;
|
|
1417
|
+
if (pageAnchor) {
|
|
1418
|
+
suppressProgrammaticScrollEvents();
|
|
1419
|
+
currentPage = pageAnchor.page;
|
|
1420
|
+
}
|
|
1279
1421
|
currentRotation = normalized;
|
|
1280
1422
|
pdfThumbnails.clear();
|
|
1281
1423
|
pendingPdfThumbnails.clear();
|
|
@@ -1286,18 +1428,36 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1286
1428
|
pdfContext.viewer.pagesRotation = normalized;
|
|
1287
1429
|
void waitForPaint(targetWindow).then(() => {
|
|
1288
1430
|
var _a;
|
|
1431
|
+
if (operationVersion !== rotationOperationVersion) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const refocusBoundingBoxes = () => {
|
|
1435
|
+
if (pdfBoundingBoxController.hasBoxes()) {
|
|
1436
|
+
void waitForPaint(targetWindow)
|
|
1437
|
+
.then(() => waitForPaint(targetWindow))
|
|
1438
|
+
.then(() => pdfBoundingBoxController.render({ focus: true, source }));
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1289
1441
|
if (reapplyFitAfterLayout(source, notifyViewState)) {
|
|
1442
|
+
if (pageAnchor) {
|
|
1443
|
+
restoreUserRotationAnchor(pageAnchor, operationVersion);
|
|
1444
|
+
}
|
|
1290
1445
|
if (notifyViewState) {
|
|
1291
1446
|
emitViewStateChange(action, source);
|
|
1292
1447
|
}
|
|
1448
|
+
refocusBoundingBoxes();
|
|
1293
1449
|
return;
|
|
1294
1450
|
}
|
|
1295
1451
|
(_a = pdfContext.viewer) === null || _a === void 0 ? void 0 : _a.update();
|
|
1296
1452
|
scheduleLegacyPageDimensionPatch();
|
|
1297
1453
|
syncUi();
|
|
1454
|
+
if (pageAnchor) {
|
|
1455
|
+
restoreUserRotationAnchor(pageAnchor, operationVersion);
|
|
1456
|
+
}
|
|
1298
1457
|
if (notifyViewState) {
|
|
1299
1458
|
emitViewStateChange(action, source);
|
|
1300
1459
|
}
|
|
1460
|
+
refocusBoundingBoxes();
|
|
1301
1461
|
});
|
|
1302
1462
|
};
|
|
1303
1463
|
const runWithStableHorizontalScroll = (action) => {
|
|
@@ -1312,6 +1472,9 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1312
1472
|
if (!pdfContext.viewer || !pageCount) {
|
|
1313
1473
|
return;
|
|
1314
1474
|
}
|
|
1475
|
+
if (source === 'user') {
|
|
1476
|
+
cancelPendingUserRotationRestore();
|
|
1477
|
+
}
|
|
1315
1478
|
markFitInteraction(source);
|
|
1316
1479
|
const nextPage = Math.min(pageCount, Math.max(1, pageNumber));
|
|
1317
1480
|
runWithStableHorizontalScroll(() => {
|
|
@@ -1550,8 +1713,19 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1550
1713
|
if (pdfContext.search) {
|
|
1551
1714
|
eventBus.dispatch('find', { type: '', query: pdfContext.search });
|
|
1552
1715
|
}
|
|
1716
|
+
if (pdfBoundingBoxController.hasBoxes()) {
|
|
1717
|
+
void waitForPaint(targetWindow).then(() => pdfBoundingBoxController.render({
|
|
1718
|
+
focus: true,
|
|
1719
|
+
source: 'initial',
|
|
1720
|
+
}));
|
|
1721
|
+
}
|
|
1553
1722
|
});
|
|
1554
1723
|
eventBus.on('pagechanging', ({ pageNumber }) => {
|
|
1724
|
+
if (pendingUserRotationAnchor && pageNumber !== pendingUserRotationAnchor.page) {
|
|
1725
|
+
currentPage = pendingUserRotationAnchor.page;
|
|
1726
|
+
syncUi();
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1555
1729
|
const previousPage = currentPage;
|
|
1556
1730
|
currentPage = pageNumber;
|
|
1557
1731
|
syncUi();
|
|
@@ -1571,6 +1745,9 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1571
1745
|
});
|
|
1572
1746
|
eventBus.on('pagerendered', ({ pageNumber }) => {
|
|
1573
1747
|
scheduleLegacyPageDimensionPatch();
|
|
1748
|
+
if (pdfBoundingBoxController.hasBoxes()) {
|
|
1749
|
+
void pdfBoundingBoxController.render({ pageNumber, source: 'viewer' });
|
|
1750
|
+
}
|
|
1574
1751
|
if (!pdfCjkFontFallbackManager ||
|
|
1575
1752
|
pdfCjkFontFallbackRenderHandledPages.has(pageNumber)) {
|
|
1576
1753
|
return;
|
|
@@ -1839,6 +2016,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1839
2016
|
unregisterFileViewerSearchProvider(root);
|
|
1840
2017
|
unregisterFileViewerZoomProvider(root);
|
|
1841
2018
|
unregisterFileViewerViewStateProvider(root);
|
|
2019
|
+
pdfBoundingBoxController.destroy();
|
|
1842
2020
|
outlineItems = [];
|
|
1843
2021
|
(_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, null);
|
|
1844
2022
|
(_b = context === null || context === void 0 ? void 0 : context.registerThumbnailAdapter) === null || _b === void 0 ? void 0 : _b.call(context, null);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FileViewerPdfBoundingBox } from '@file-viewer/core';
|
|
2
|
+
export interface NormalizedPdfBoundingBox {
|
|
3
|
+
id?: string;
|
|
4
|
+
page: number;
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
color?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PdfPageBox {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const normalizePdfBoundingBoxInput: (input: unknown) => FileViewerPdfBoundingBox[];
|
|
19
|
+
export declare const normalizePdfBoundingBox: (input: FileViewerPdfBoundingBox, pageBox: PdfPageBox, fallbackPage?: number) => NormalizedPdfBoundingBox | null;
|
|
20
|
+
export declare const rotateNormalizedPdfBoundingBox: (box: NormalizedPdfBoundingBox, rotation: number) => NormalizedPdfBoundingBox;
|
|
21
|
+
export declare const serializePdfBoundingBoxes: (input: unknown) => string;
|
package/dist/pdfBbox.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const clamp = (value, min = 0, max = 1) => Math.min(max, Math.max(min, value));
|
|
2
|
+
const finitePositive = (value) => Number.isFinite(value) && Number(value) > 0;
|
|
3
|
+
export const normalizePdfBoundingBoxInput = (input) => {
|
|
4
|
+
const values = input ? (Array.isArray(input) ? input : [input]) : [];
|
|
5
|
+
return values.filter(value => !!value && typeof value === 'object');
|
|
6
|
+
};
|
|
7
|
+
export const normalizePdfBoundingBox = (input, pageBox, fallbackPage = 1) => {
|
|
8
|
+
if (!Number.isFinite(input.x) ||
|
|
9
|
+
!Number.isFinite(input.y) ||
|
|
10
|
+
!finitePositive(input.width) ||
|
|
11
|
+
!finitePositive(input.height) ||
|
|
12
|
+
!finitePositive(pageBox.width) ||
|
|
13
|
+
!finitePositive(pageBox.height)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const unit = input.unit || 'pdf-point';
|
|
17
|
+
let x = input.x;
|
|
18
|
+
let y = input.y;
|
|
19
|
+
let width = input.width;
|
|
20
|
+
let height = input.height;
|
|
21
|
+
if (unit === 'percent') {
|
|
22
|
+
x /= 100;
|
|
23
|
+
y /= 100;
|
|
24
|
+
width /= 100;
|
|
25
|
+
height /= 100;
|
|
26
|
+
}
|
|
27
|
+
else if (unit === 'pixel') {
|
|
28
|
+
if (!finitePositive(input.sourceWidth) || !finitePositive(input.sourceHeight)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
x /= Number(input.sourceWidth);
|
|
32
|
+
y /= Number(input.sourceHeight);
|
|
33
|
+
width /= Number(input.sourceWidth);
|
|
34
|
+
height /= Number(input.sourceHeight);
|
|
35
|
+
}
|
|
36
|
+
else if (unit === 'pdf-point') {
|
|
37
|
+
x = (x - pageBox.x) / pageBox.width;
|
|
38
|
+
y = (y - pageBox.y) / pageBox.height;
|
|
39
|
+
width /= pageBox.width;
|
|
40
|
+
height /= pageBox.height;
|
|
41
|
+
}
|
|
42
|
+
const origin = input.origin || (unit === 'pdf-point' ? 'bottom-left' : 'top-left');
|
|
43
|
+
if (origin === 'bottom-left') {
|
|
44
|
+
y = 1 - y - height;
|
|
45
|
+
}
|
|
46
|
+
const left = clamp(x);
|
|
47
|
+
const top = clamp(y);
|
|
48
|
+
const right = clamp(x + width);
|
|
49
|
+
const bottom = clamp(y + height);
|
|
50
|
+
if (right <= left || bottom <= top) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
id: input.id,
|
|
55
|
+
page: Math.max(1, Math.round(Number(input.page) || fallbackPage || 1)),
|
|
56
|
+
x: left,
|
|
57
|
+
y: top,
|
|
58
|
+
width: right - left,
|
|
59
|
+
height: bottom - top,
|
|
60
|
+
color: input.color,
|
|
61
|
+
label: input.label,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export const rotateNormalizedPdfBoundingBox = (box, rotation) => {
|
|
65
|
+
const normalizedRotation = ((Math.round(rotation / 90) * 90) % 360 + 360) % 360;
|
|
66
|
+
if (normalizedRotation === 90) {
|
|
67
|
+
return {
|
|
68
|
+
...box,
|
|
69
|
+
x: 1 - box.y - box.height,
|
|
70
|
+
y: box.x,
|
|
71
|
+
width: box.height,
|
|
72
|
+
height: box.width,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (normalizedRotation === 180) {
|
|
76
|
+
return {
|
|
77
|
+
...box,
|
|
78
|
+
x: 1 - box.x - box.width,
|
|
79
|
+
y: 1 - box.y - box.height,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (normalizedRotation === 270) {
|
|
83
|
+
return {
|
|
84
|
+
...box,
|
|
85
|
+
x: box.y,
|
|
86
|
+
y: 1 - box.x - box.width,
|
|
87
|
+
width: box.height,
|
|
88
|
+
height: box.width,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return { ...box };
|
|
92
|
+
};
|
|
93
|
+
export const serializePdfBoundingBoxes = (input) => JSON.stringify(normalizePdfBoundingBoxInput(input));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FileViewerPdfBoundingBox, FileViewerViewStateChangeSource } from '@file-viewer/core';
|
|
2
|
+
interface PdfBoundingBoxPage {
|
|
3
|
+
view?: number[];
|
|
4
|
+
getViewport: (options: {
|
|
5
|
+
scale: number;
|
|
6
|
+
rotation: number;
|
|
7
|
+
}) => {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
interface PdfBoundingBoxDocument {
|
|
13
|
+
getPage: (pageNumber: number) => Promise<PdfBoundingBoxPage>;
|
|
14
|
+
}
|
|
15
|
+
export interface CreatePdfBoundingBoxControllerOptions {
|
|
16
|
+
documentRef: Document;
|
|
17
|
+
targetWindow: Window;
|
|
18
|
+
viewerRoot: HTMLElement;
|
|
19
|
+
scrollContainer: HTMLElement;
|
|
20
|
+
initial?: FileViewerPdfBoundingBox | readonly FileViewerPdfBoundingBox[];
|
|
21
|
+
getDocument: () => PdfBoundingBoxDocument | null;
|
|
22
|
+
getPageCount: () => number;
|
|
23
|
+
getCurrentPage: () => number;
|
|
24
|
+
getRotation: () => number;
|
|
25
|
+
goToPage: (page: number, source: FileViewerViewStateChangeSource) => void;
|
|
26
|
+
suppressProgrammaticScrollEvents: () => void;
|
|
27
|
+
waitForPaint: (view?: Window | null) => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface PdfBoundingBoxRenderOptions {
|
|
30
|
+
focus?: boolean;
|
|
31
|
+
pageNumber?: number;
|
|
32
|
+
source?: FileViewerViewStateChangeSource;
|
|
33
|
+
}
|
|
34
|
+
export interface PdfBoundingBoxController {
|
|
35
|
+
hasBoxes(): boolean;
|
|
36
|
+
getStateValue(): FileViewerPdfBoundingBox | FileViewerPdfBoundingBox[] | null;
|
|
37
|
+
set(input: unknown, options?: Pick<PdfBoundingBoxRenderOptions, 'focus' | 'source'>): Promise<boolean>;
|
|
38
|
+
render(options?: PdfBoundingBoxRenderOptions): Promise<void>;
|
|
39
|
+
destroy(): void;
|
|
40
|
+
}
|
|
41
|
+
export declare const createPdfBoundingBoxController: ({ documentRef, targetWindow, viewerRoot, scrollContainer, initial, getDocument, getPageCount, getCurrentPage, getRotation, goToPage, suppressProgrammaticScrollEvents, waitForPaint, }: CreatePdfBoundingBoxControllerOptions) => PdfBoundingBoxController;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { normalizePdfBoundingBox, normalizePdfBoundingBoxInput, rotateNormalizedPdfBoundingBox, serializePdfBoundingBoxes, } from './pdfBbox.js';
|
|
2
|
+
export const createPdfBoundingBoxController = ({ documentRef, targetWindow, viewerRoot, scrollContainer, initial, getDocument, getPageCount, getCurrentPage, getRotation, goToPage, suppressProgrammaticScrollEvents, waitForPaint, }) => {
|
|
3
|
+
let renderVersion = 0;
|
|
4
|
+
let destroyed = false;
|
|
5
|
+
let active = normalizePdfBoundingBoxInput(initial);
|
|
6
|
+
let fingerprint = serializePdfBoundingBoxes(active);
|
|
7
|
+
const removeLayers = (pageNumber) => {
|
|
8
|
+
const selector = pageNumber
|
|
9
|
+
? `.page[data-page-number="${pageNumber}"] > .pdf-bbox-layer`
|
|
10
|
+
: '.pdf-bbox-layer';
|
|
11
|
+
viewerRoot.querySelectorAll(selector).forEach(layer => layer.remove());
|
|
12
|
+
};
|
|
13
|
+
const getPageBox = async (pageNumber) => {
|
|
14
|
+
const document = getDocument();
|
|
15
|
+
if (!document) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const page = await document.getPage(pageNumber);
|
|
19
|
+
const view = page.view;
|
|
20
|
+
if (Array.isArray(view) && view.length >= 4) {
|
|
21
|
+
return {
|
|
22
|
+
x: Number(view[0]) || 0,
|
|
23
|
+
y: Number(view[1]) || 0,
|
|
24
|
+
width: Math.abs(Number(view[2]) - Number(view[0])),
|
|
25
|
+
height: Math.abs(Number(view[3]) - Number(view[1])),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const viewport = page.getViewport({ scale: 1, rotation: 0 });
|
|
29
|
+
return { x: 0, y: 0, width: viewport.width, height: viewport.height };
|
|
30
|
+
};
|
|
31
|
+
const focusNodes = (pageNode, nodes) => {
|
|
32
|
+
if (!nodes.length) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const pageRect = pageNode.getBoundingClientRect();
|
|
36
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
37
|
+
const nodeRects = nodes.map(node => node.getBoundingClientRect());
|
|
38
|
+
const left = Math.min(...nodeRects.map(rect => rect.left));
|
|
39
|
+
const top = Math.min(...nodeRects.map(rect => rect.top));
|
|
40
|
+
const right = Math.max(...nodeRects.map(rect => rect.right));
|
|
41
|
+
const bottom = Math.max(...nodeRects.map(rect => rect.bottom));
|
|
42
|
+
suppressProgrammaticScrollEvents();
|
|
43
|
+
scrollContainer.scrollTop = Math.max(0, scrollContainer.scrollTop + (top + bottom) / 2 - (containerRect.top + containerRect.bottom) / 2);
|
|
44
|
+
if (pageRect.width > containerRect.width) {
|
|
45
|
+
scrollContainer.scrollLeft = Math.max(0, scrollContainer.scrollLeft + (left + right) / 2 - (containerRect.left + containerRect.right) / 2);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const render = async ({ focus = false, pageNumber, source = 'api', } = {}) => {
|
|
49
|
+
var _a;
|
|
50
|
+
const currentVersion = pageNumber ? renderVersion : ++renderVersion;
|
|
51
|
+
removeLayers(pageNumber);
|
|
52
|
+
const document = getDocument();
|
|
53
|
+
if (!active.length || !document || destroyed) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const fallbackPage = getCurrentPage() || 1;
|
|
57
|
+
const pageCount = getPageCount();
|
|
58
|
+
const grouped = new Map();
|
|
59
|
+
active.forEach(box => {
|
|
60
|
+
const page = Math.min(pageCount || Number.MAX_SAFE_INTEGER, Math.max(1, Math.round(Number(box.page) || fallbackPage)));
|
|
61
|
+
if (pageNumber && page !== pageNumber) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const boxes = grouped.get(page) || [];
|
|
65
|
+
boxes.push(box);
|
|
66
|
+
grouped.set(page, boxes);
|
|
67
|
+
});
|
|
68
|
+
const focusPage = Math.min(...grouped.keys());
|
|
69
|
+
if (focus && Number.isFinite(focusPage)) {
|
|
70
|
+
goToPage(focusPage, source);
|
|
71
|
+
await waitForPaint(targetWindow);
|
|
72
|
+
}
|
|
73
|
+
for (const [page, boxes] of grouped) {
|
|
74
|
+
if (currentVersion !== renderVersion || destroyed) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const pageNode = viewerRoot.querySelector(`.page[data-page-number="${page}"]`);
|
|
78
|
+
const pageBox = await getPageBox(page);
|
|
79
|
+
if (!pageNode || !pageBox || currentVersion !== renderVersion || destroyed) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
(_a = pageNode.querySelector(':scope > .pdf-bbox-layer')) === null || _a === void 0 ? void 0 : _a.remove();
|
|
83
|
+
const layer = documentRef.createElement('div');
|
|
84
|
+
layer.className = 'pdf-bbox-layer';
|
|
85
|
+
layer.dataset.pdfBboxPage = String(page);
|
|
86
|
+
const nodes = [];
|
|
87
|
+
boxes.forEach((box, index) => {
|
|
88
|
+
const normalized = normalizePdfBoundingBox(box, pageBox, fallbackPage);
|
|
89
|
+
if (!normalized) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const rotated = rotateNormalizedPdfBoundingBox(normalized, getRotation());
|
|
93
|
+
const node = documentRef.createElement('div');
|
|
94
|
+
node.className = 'pdf-bbox-highlight';
|
|
95
|
+
node.dataset.pdfBboxId = box.id || `${page}-${index}`;
|
|
96
|
+
node.style.left = `${rotated.x * 100}%`;
|
|
97
|
+
node.style.top = `${rotated.y * 100}%`;
|
|
98
|
+
node.style.width = `${rotated.width * 100}%`;
|
|
99
|
+
node.style.height = `${rotated.height * 100}%`;
|
|
100
|
+
if (box.color) {
|
|
101
|
+
node.style.setProperty('--pdf-bbox-color', box.color);
|
|
102
|
+
}
|
|
103
|
+
if (box.label) {
|
|
104
|
+
node.setAttribute('role', 'note');
|
|
105
|
+
node.setAttribute('aria-label', box.label);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
node.setAttribute('aria-hidden', 'true');
|
|
109
|
+
}
|
|
110
|
+
layer.append(node);
|
|
111
|
+
nodes.push(node);
|
|
112
|
+
});
|
|
113
|
+
if (!nodes.length) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
pageNode.append(layer);
|
|
117
|
+
if (focus && page === focusPage) {
|
|
118
|
+
await waitForPaint(targetWindow);
|
|
119
|
+
focusNodes(pageNode, nodes);
|
|
120
|
+
// PDF.js can settle dimensions one frame after fit or rotation.
|
|
121
|
+
await waitForPaint(targetWindow);
|
|
122
|
+
focusNodes(pageNode, nodes);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
hasBoxes: () => active.length > 0,
|
|
128
|
+
getStateValue: () => {
|
|
129
|
+
if (!active.length) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return active.length === 1
|
|
133
|
+
? { ...active[0] }
|
|
134
|
+
: active.map(box => ({ ...box }));
|
|
135
|
+
},
|
|
136
|
+
async set(input, options = {}) {
|
|
137
|
+
var _a;
|
|
138
|
+
const next = normalizePdfBoundingBoxInput(input);
|
|
139
|
+
const nextFingerprint = serializePdfBoundingBoxes(next);
|
|
140
|
+
if (nextFingerprint === fingerprint) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
active = next;
|
|
144
|
+
fingerprint = nextFingerprint;
|
|
145
|
+
await render({
|
|
146
|
+
focus: (_a = options.focus) !== null && _a !== void 0 ? _a : true,
|
|
147
|
+
source: options.source,
|
|
148
|
+
});
|
|
149
|
+
return true;
|
|
150
|
+
},
|
|
151
|
+
render,
|
|
152
|
+
destroy() {
|
|
153
|
+
destroyed = true;
|
|
154
|
+
renderVersion += 1;
|
|
155
|
+
removeLayers();
|
|
156
|
+
active = [];
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FileViewerViewState } from '@file-viewer/core';
|
|
2
|
+
export type PdfRotation = 0 | 90 | 180 | 270;
|
|
3
|
+
export declare const normalizePdfRotation: (rotation: number) => PdfRotation;
|
|
4
|
+
export declare const clampPdfScale: (scale: number, minScale: number, maxScale: number) => number;
|
|
5
|
+
export declare const resolvePdfViewStateUpdate: (state: FileViewerViewState, current: {
|
|
6
|
+
rotation: number;
|
|
7
|
+
scale: number;
|
|
8
|
+
page: number;
|
|
9
|
+
pageCount: number;
|
|
10
|
+
}, limits: {
|
|
11
|
+
minScale: number;
|
|
12
|
+
maxScale: number;
|
|
13
|
+
}) => {
|
|
14
|
+
rotation: number | undefined;
|
|
15
|
+
scale: number | undefined;
|
|
16
|
+
page: number | undefined;
|
|
17
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const normalizePdfRotation = (rotation) => {
|
|
2
|
+
const normalized = (((Math.round(rotation / 90) * 90) % 360) + 360) % 360;
|
|
3
|
+
return (normalized === 90 || normalized === 180 || normalized === 270 ? normalized : 0);
|
|
4
|
+
};
|
|
5
|
+
export const clampPdfScale = (scale, minScale, maxScale) => {
|
|
6
|
+
return Number(Math.min(maxScale, Math.max(minScale, scale)).toFixed(2));
|
|
7
|
+
};
|
|
8
|
+
export const resolvePdfViewStateUpdate = (state, current, limits) => {
|
|
9
|
+
var _a, _b;
|
|
10
|
+
const requestedRotation = Number(state.rotation);
|
|
11
|
+
const requestedScale = Number((_a = state.scale) !== null && _a !== void 0 ? _a : (_b = state.zoom) === null || _b === void 0 ? void 0 : _b.scale);
|
|
12
|
+
const requestedPage = Number(state.page);
|
|
13
|
+
const rotation = Number.isFinite(requestedRotation)
|
|
14
|
+
? normalizePdfRotation(requestedRotation)
|
|
15
|
+
: current.rotation;
|
|
16
|
+
const scale = Number.isFinite(requestedScale)
|
|
17
|
+
? clampPdfScale(requestedScale, limits.minScale, limits.maxScale)
|
|
18
|
+
: current.scale;
|
|
19
|
+
const page = Number.isFinite(requestedPage)
|
|
20
|
+
? Math.min(current.pageCount, Math.max(1, Math.round(requestedPage)))
|
|
21
|
+
: current.page;
|
|
22
|
+
return {
|
|
23
|
+
rotation: rotation !== current.rotation ? rotation : undefined,
|
|
24
|
+
scale: Math.abs(scale - current.scale) > 0.001 ? scale : undefined,
|
|
25
|
+
page: page !== current.page ? page : undefined
|
|
26
|
+
};
|
|
27
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/renderer-pdf",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone PDF renderer plugin for File Viewer powered by PDF.js.",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"LICENSE"
|
|
55
55
|
],
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@file-viewer/core": "2.2.
|
|
57
|
+
"@file-viewer/core": "2.2.6",
|
|
58
58
|
"@fontsource-variable/noto-sans-sc": "5.2.10",
|
|
59
59
|
"pdf-lib": "1.17.1",
|
|
60
60
|
"pdfjs-dist": "5.4.624"
|