@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 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 = (rotation) => {
90
- const normalized = ((Math.round(rotation / 90) * 90) % 360 + 360) % 360;
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, _b;
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 nextRotation = Number(state.rotation);
1043
- const nextScale = Number((_a = state.scale) !== null && _a !== void 0 ? _a : (_b = state.zoom) === null || _b === void 0 ? void 0 : _b.scale);
1044
- const nextPage = Number(state.page);
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
- syncUi();
1081
+ if (navigationChanged) {
1082
+ syncUi();
1083
+ }
1054
1084
  }
1055
- if (Number.isFinite(nextRotation)) {
1056
- applyRotation(nextRotation, 'rotation-change', source, false);
1085
+ if (update.rotation !== undefined) {
1086
+ applyRotation(update.rotation, 'rotation-change', source, false);
1057
1087
  }
1058
- if (Number.isFinite(nextScale)) {
1088
+ if (update.scale !== undefined) {
1059
1089
  autoFitWidth = false;
1060
- setScale(nextScale, 'zoom-change', source, false);
1090
+ setScale(update.scale, 'zoom-change', source, false);
1061
1091
  }
1062
- if (Number.isFinite(nextPage)) {
1063
- goToPage(nextPage, 'page-change', source, false);
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;
@@ -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.4",
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.4",
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"