@file-viewer/renderer-pdf 2.2.4 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pdf.js +72 -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,
|
|
@@ -957,6 +958,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
957
958
|
};
|
|
958
959
|
const getPdfViewState = () => {
|
|
959
960
|
const zoom = getPdfZoomState();
|
|
961
|
+
const bbox = pdfBoundingBoxController.getStateValue();
|
|
960
962
|
return {
|
|
961
963
|
renderer: 'pdf',
|
|
962
964
|
page: currentPage,
|
|
@@ -969,6 +971,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
969
971
|
visible: navigationEnabled ? navVisible : false,
|
|
970
972
|
mode: navMode,
|
|
971
973
|
},
|
|
974
|
+
extra: bbox ? { bbox } : undefined,
|
|
972
975
|
};
|
|
973
976
|
};
|
|
974
977
|
const emitViewStateChange = (action, source = 'viewer') => {
|
|
@@ -993,6 +996,20 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
993
996
|
const suppressProgrammaticScrollEvents = () => {
|
|
994
997
|
suppressScrollEventUntil = Math.max(suppressScrollEventUntil, Date.now() + 180);
|
|
995
998
|
};
|
|
999
|
+
const pdfBoundingBoxController = createPdfBoundingBoxController({
|
|
1000
|
+
documentRef,
|
|
1001
|
+
targetWindow,
|
|
1002
|
+
viewerRoot: pdfViewerRoot,
|
|
1003
|
+
scrollContainer: container,
|
|
1004
|
+
initial: options === null || options === void 0 ? void 0 : options.bbox,
|
|
1005
|
+
getDocument: () => pdfContext.document,
|
|
1006
|
+
getPageCount: () => pageCount,
|
|
1007
|
+
getCurrentPage: () => currentPage,
|
|
1008
|
+
getRotation: () => currentRotation,
|
|
1009
|
+
goToPage: (page, source) => goToPage(page, 'bbox-focus', source, false),
|
|
1010
|
+
suppressProgrammaticScrollEvents,
|
|
1011
|
+
waitForPaint,
|
|
1012
|
+
});
|
|
996
1013
|
const markFitInteraction = (source) => {
|
|
997
1014
|
if (source !== 'user' && source !== 'api') {
|
|
998
1015
|
return;
|
|
@@ -1027,7 +1044,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1027
1044
|
}
|
|
1028
1045
|
};
|
|
1029
1046
|
const applyPdfViewState = async (state, applyOptions = {}) => {
|
|
1030
|
-
var _a
|
|
1047
|
+
var _a;
|
|
1031
1048
|
if (!pdfContext.viewer || loadStatus !== 'ready') {
|
|
1032
1049
|
pendingInitialViewState = state;
|
|
1033
1050
|
return getPdfViewState();
|
|
@@ -1039,29 +1056,46 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1039
1056
|
const applyVersion = ++viewStateApplyVersion;
|
|
1040
1057
|
activeViewStateApplyVersion = applyVersion;
|
|
1041
1058
|
suppressProgrammaticScrollEvents();
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1059
|
+
const update = resolvePdfViewStateUpdate(state, {
|
|
1060
|
+
rotation: currentRotation,
|
|
1061
|
+
scale: currentScale,
|
|
1062
|
+
page: currentPage,
|
|
1063
|
+
pageCount,
|
|
1064
|
+
}, {
|
|
1065
|
+
minScale: MIN_SCALE,
|
|
1066
|
+
maxScale: MAX_SCALE,
|
|
1067
|
+
});
|
|
1068
|
+
const hasBboxUpdate = !!state.extra && Object.prototype.hasOwnProperty.call(state.extra, 'bbox');
|
|
1069
|
+
const bboxUpdate = hasBboxUpdate ? (_a = state.extra) === null || _a === void 0 ? void 0 : _a.bbox : undefined;
|
|
1045
1070
|
try {
|
|
1046
1071
|
if (state.navigation) {
|
|
1072
|
+
let navigationChanged = false;
|
|
1047
1073
|
if (navigationEnabled && typeof state.navigation.visible === 'boolean') {
|
|
1074
|
+
navigationChanged = navVisible !== state.navigation.visible;
|
|
1048
1075
|
navVisible = state.navigation.visible;
|
|
1049
1076
|
}
|
|
1050
1077
|
if (state.navigation.mode === 'pages' || state.navigation.mode === 'outline') {
|
|
1078
|
+
navigationChanged = navigationChanged || navMode !== state.navigation.mode;
|
|
1051
1079
|
navMode = state.navigation.mode;
|
|
1052
1080
|
}
|
|
1053
|
-
|
|
1081
|
+
if (navigationChanged) {
|
|
1082
|
+
syncUi();
|
|
1083
|
+
}
|
|
1054
1084
|
}
|
|
1055
|
-
if (
|
|
1056
|
-
applyRotation(
|
|
1085
|
+
if (update.rotation !== undefined) {
|
|
1086
|
+
applyRotation(update.rotation, 'rotation-change', source, false);
|
|
1057
1087
|
}
|
|
1058
|
-
if (
|
|
1088
|
+
if (update.scale !== undefined) {
|
|
1059
1089
|
autoFitWidth = false;
|
|
1060
|
-
setScale(
|
|
1090
|
+
setScale(update.scale, 'zoom-change', source, false);
|
|
1061
1091
|
}
|
|
1062
|
-
if (
|
|
1063
|
-
goToPage(
|
|
1092
|
+
if (update.page !== undefined) {
|
|
1093
|
+
goToPage(update.page, 'page-change', source, false);
|
|
1064
1094
|
}
|
|
1095
|
+
// A remote presenter can send scroll snapshots faster than one animation
|
|
1096
|
+
// frame. Apply the latest offset before yielding so superseded promises
|
|
1097
|
+
// cannot starve the projected screen until the presenter stops scrolling.
|
|
1098
|
+
restoreScrollState(state.scroll, false);
|
|
1065
1099
|
await waitForPaint(targetWindow);
|
|
1066
1100
|
if (applyVersion !== viewStateApplyVersion) {
|
|
1067
1101
|
return getPdfViewState();
|
|
@@ -1072,6 +1106,9 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1072
1106
|
return getPdfViewState();
|
|
1073
1107
|
}
|
|
1074
1108
|
restoreScrollState(state.scroll, false);
|
|
1109
|
+
if (hasBboxUpdate) {
|
|
1110
|
+
await pdfBoundingBoxController.set(bboxUpdate, { focus: true, source });
|
|
1111
|
+
}
|
|
1075
1112
|
syncUi();
|
|
1076
1113
|
if (notify && applyVersion === viewStateApplyVersion) {
|
|
1077
1114
|
emitViewStateChange(action, source);
|
|
@@ -1286,10 +1323,18 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1286
1323
|
pdfContext.viewer.pagesRotation = normalized;
|
|
1287
1324
|
void waitForPaint(targetWindow).then(() => {
|
|
1288
1325
|
var _a;
|
|
1326
|
+
const refocusBoundingBoxes = () => {
|
|
1327
|
+
if (pdfBoundingBoxController.hasBoxes()) {
|
|
1328
|
+
void waitForPaint(targetWindow)
|
|
1329
|
+
.then(() => waitForPaint(targetWindow))
|
|
1330
|
+
.then(() => pdfBoundingBoxController.render({ focus: true, source }));
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1289
1333
|
if (reapplyFitAfterLayout(source, notifyViewState)) {
|
|
1290
1334
|
if (notifyViewState) {
|
|
1291
1335
|
emitViewStateChange(action, source);
|
|
1292
1336
|
}
|
|
1337
|
+
refocusBoundingBoxes();
|
|
1293
1338
|
return;
|
|
1294
1339
|
}
|
|
1295
1340
|
(_a = pdfContext.viewer) === null || _a === void 0 ? void 0 : _a.update();
|
|
@@ -1298,6 +1343,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1298
1343
|
if (notifyViewState) {
|
|
1299
1344
|
emitViewStateChange(action, source);
|
|
1300
1345
|
}
|
|
1346
|
+
refocusBoundingBoxes();
|
|
1301
1347
|
});
|
|
1302
1348
|
};
|
|
1303
1349
|
const runWithStableHorizontalScroll = (action) => {
|
|
@@ -1550,6 +1596,12 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1550
1596
|
if (pdfContext.search) {
|
|
1551
1597
|
eventBus.dispatch('find', { type: '', query: pdfContext.search });
|
|
1552
1598
|
}
|
|
1599
|
+
if (pdfBoundingBoxController.hasBoxes()) {
|
|
1600
|
+
void waitForPaint(targetWindow).then(() => pdfBoundingBoxController.render({
|
|
1601
|
+
focus: true,
|
|
1602
|
+
source: 'initial',
|
|
1603
|
+
}));
|
|
1604
|
+
}
|
|
1553
1605
|
});
|
|
1554
1606
|
eventBus.on('pagechanging', ({ pageNumber }) => {
|
|
1555
1607
|
const previousPage = currentPage;
|
|
@@ -1571,6 +1623,9 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1571
1623
|
});
|
|
1572
1624
|
eventBus.on('pagerendered', ({ pageNumber }) => {
|
|
1573
1625
|
scheduleLegacyPageDimensionPatch();
|
|
1626
|
+
if (pdfBoundingBoxController.hasBoxes()) {
|
|
1627
|
+
void pdfBoundingBoxController.render({ pageNumber, source: 'viewer' });
|
|
1628
|
+
}
|
|
1574
1629
|
if (!pdfCjkFontFallbackManager ||
|
|
1575
1630
|
pdfCjkFontFallbackRenderHandledPages.has(pageNumber)) {
|
|
1576
1631
|
return;
|
|
@@ -1839,6 +1894,7 @@ export default async function renderPdf(buffer, target, context) {
|
|
|
1839
1894
|
unregisterFileViewerSearchProvider(root);
|
|
1840
1895
|
unregisterFileViewerZoomProvider(root);
|
|
1841
1896
|
unregisterFileViewerViewStateProvider(root);
|
|
1897
|
+
pdfBoundingBoxController.destroy();
|
|
1842
1898
|
outlineItems = [];
|
|
1843
1899
|
(_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, null);
|
|
1844
1900
|
(_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.5",
|
|
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.5",
|
|
58
58
|
"@fontsource-variable/noto-sans-sc": "5.2.10",
|
|
59
59
|
"pdf-lib": "1.17.1",
|
|
60
60
|
"pdfjs-dist": "5.4.624"
|