@file-viewer/renderer-pdf 2.1.28 → 2.1.30

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
@@ -4,12 +4,11 @@ import { registerFileViewerSearchProvider, registerFileViewerZoomProvider, unreg
4
4
  import { DEFAULT_FILE_VIEWER_PDF_WORKER_PATH, resolveFileViewerPdfAssetUrls, resolveFileViewerRuntimeAssetBaseUrl, } from '@file-viewer/core/assets';
5
5
  import { pdfViewerStyle } from './pdfStyles.js';
6
6
  import { collectMalformedIdentityFontNames, createPdfCjkFontFallbackManager, detectMalformedIdentityCjkFontFamilies, } from './pdfFontFallback.js';
7
+ import { PDF_FIT_HORIZONTAL_PADDING, PDF_PAGE_BORDER_WIDTH, resolvePdfFitViewportSize, } from './pdfFit.js';
7
8
  export const DEFAULT_FILE_VIEWER_PDF_WORKER_URL = DEFAULT_FILE_VIEWER_PDF_WORKER_PATH;
8
9
  const MIN_SCALE = 0.2;
9
10
  const MAX_SCALE = 3;
10
11
  const SCALE_STEP = 0.1;
11
- const FIT_HORIZONTAL_PADDING = 28;
12
- const PAGE_BORDER_WIDTH = 18;
13
12
  const PDF_EXPORT_MAX_PAGE_PIXELS = 8000000;
14
13
  const PDF_WORKER_PROBE_TIMEOUT_MS = 1200;
15
14
  const PDF_JS_DESTROY_CONSOLE_SUPPRESSION_MS = 1500;
@@ -315,7 +314,9 @@ export default async function renderPdf(buffer, target, context) {
315
314
  let activeViewStateApplyVersion = 0;
316
315
  let pendingInitialViewState = initialViewState;
317
316
  let pendingFitRequest = null;
317
+ let activeFitRequest = null;
318
318
  let suppressScrollEventUntil = 0;
319
+ let userScrollIntentUntil = 0;
319
320
  let scrollStateFrame = 0;
320
321
  let pdfSearchState = createPdfSearchState();
321
322
  let pdfMatchesCount = { current: 0, total: 0 };
@@ -893,7 +894,7 @@ export default async function renderPdf(buffer, target, context) {
893
894
  label: scaleText(),
894
895
  canZoomIn: loadStatus === 'ready' && !!pdfContext.viewer && canZoomIn(),
895
896
  canZoomOut: loadStatus === 'ready' && !!pdfContext.viewer && canZoomOut(),
896
- canReset: loadStatus === 'ready' && !!pdfContext.viewer && !autoFitWidth,
897
+ canReset: loadStatus === 'ready' && !!pdfContext.viewer && Math.abs(currentScale - 1) > 0.001,
897
898
  minScale: MIN_SCALE,
898
899
  maxScale: MAX_SCALE,
899
900
  });
@@ -949,6 +950,21 @@ export default async function renderPdf(buffer, target, context) {
949
950
  const suppressProgrammaticScrollEvents = () => {
950
951
  suppressScrollEventUntil = Math.max(suppressScrollEventUntil, Date.now() + 180);
951
952
  };
953
+ const markFitInteraction = (source) => {
954
+ if (source !== 'user' && source !== 'api') {
955
+ return;
956
+ }
957
+ if ((activeFitRequest === null || activeFitRequest === void 0 ? void 0 : activeFitRequest.resize) === 'always') {
958
+ return;
959
+ }
960
+ autoFitWidth = false;
961
+ activeFitRequest = null;
962
+ };
963
+ const recordUserScrollIntent = () => {
964
+ userScrollIntentUntil = Date.now() + 750;
965
+ suppressScrollEventUntil = 0;
966
+ markFitInteraction('user');
967
+ };
952
968
  const restoreScrollState = (scroll, notify = true) => {
953
969
  if (!scroll) {
954
970
  return;
@@ -976,6 +992,7 @@ export default async function renderPdf(buffer, target, context) {
976
992
  const source = applyOptions.source || 'api';
977
993
  const action = applyOptions.action || 'restore';
978
994
  const notify = applyOptions.notify !== false;
995
+ markFitInteraction(source);
979
996
  const applyVersion = ++viewStateApplyVersion;
980
997
  activeViewStateApplyVersion = applyVersion;
981
998
  suppressProgrammaticScrollEvents();
@@ -1034,7 +1051,11 @@ export default async function renderPdf(buffer, target, context) {
1034
1051
  }
1035
1052
  scrollStateFrame = targetWindow.requestAnimationFrame(() => {
1036
1053
  scrollStateFrame = 0;
1037
- emitViewStateChange('scroll', 'user');
1054
+ const source = Date.now() <= userScrollIntentUntil
1055
+ ? 'user'
1056
+ : 'viewer';
1057
+ markFitInteraction(source);
1058
+ emitViewStateChange('scroll', source);
1038
1059
  });
1039
1060
  };
1040
1061
  const setScale = (scale, action = 'zoom-change', source = 'viewer', notifyViewState = true) => {
@@ -1082,13 +1103,15 @@ export default async function renderPdf(buffer, target, context) {
1082
1103
  const getFitWidthScale = (pdfViewer) => {
1083
1104
  const pageWidth = getPageWidthAtScaleOne(pdfViewer);
1084
1105
  const containerWidth = container.clientWidth || targetWindow.innerWidth;
1085
- const availableWidth = Math.max(containerWidth - FIT_HORIZONTAL_PADDING - PAGE_BORDER_WIDTH, 96);
1106
+ const availableWidth = Math.max(containerWidth - PDF_FIT_HORIZONTAL_PADDING - PDF_PAGE_BORDER_WIDTH, 96);
1086
1107
  return pageWidth ? clampScale(availableWidth / pageWidth) : 1;
1087
1108
  };
1088
1109
  const fitToWidth = (source = 'user', notifyViewState = true) => {
1089
1110
  if (!pdfContext.viewer) {
1090
1111
  return;
1091
1112
  }
1113
+ userScrollIntentUntil = 0;
1114
+ suppressProgrammaticScrollEvents();
1092
1115
  autoFitWidth = true;
1093
1116
  setScale(getFitWidthScale(pdfContext.viewer), 'zoom-reset', source, notifyViewState);
1094
1117
  void waitForPaint(targetWindow).then(() => {
@@ -1098,6 +1121,8 @@ export default async function renderPdf(buffer, target, context) {
1098
1121
  };
1099
1122
  const applyPdfFit = async (request) => {
1100
1123
  var _a, _b, _c;
1124
+ userScrollIntentUntil = 0;
1125
+ activeFitRequest = { ...request };
1101
1126
  if (!pdfContext.viewer || loadStatus !== 'ready') {
1102
1127
  pendingFitRequest = request;
1103
1128
  return {
@@ -1111,13 +1136,20 @@ export default async function renderPdf(buffer, target, context) {
1111
1136
  }
1112
1137
  const pageSize = getPageSizeAtScaleOne(pdfContext.viewer);
1113
1138
  const mode = request.mode === 'auto' ? 'width' : request.mode;
1139
+ const fitViewport = resolvePdfFitViewportSize({
1140
+ containerWidth: container.clientWidth,
1141
+ containerHeight: container.clientHeight,
1142
+ fallbackWidth: targetWindow.innerWidth,
1143
+ fallbackHeight: targetWindow.innerHeight,
1144
+ request,
1145
+ });
1114
1146
  const scale = resolveFileViewerFitScale({
1115
1147
  mode,
1116
- viewportWidth: Math.max(96, (request.viewportWidth || container.clientWidth || targetWindow.innerWidth) -
1117
- FIT_HORIZONTAL_PADDING -
1118
- PAGE_BORDER_WIDTH),
1119
- viewportHeight: Math.max(96, (container.clientHeight || request.viewportHeight || targetWindow.innerHeight) -
1120
- PAGE_BORDER_WIDTH),
1148
+ // The core request measures the whole viewer root. PDF's scroll
1149
+ // container is the actual visible document area after the navigation
1150
+ // pane, so prefer it whenever it has been laid out.
1151
+ viewportWidth: fitViewport.width,
1152
+ viewportHeight: fitViewport.height,
1121
1153
  contentWidth: pageSize.width,
1122
1154
  contentHeight: pageSize.height,
1123
1155
  currentScale,
@@ -1134,9 +1166,17 @@ export default async function renderPdf(buffer, target, context) {
1134
1166
  provider: 'view-state',
1135
1167
  };
1136
1168
  }
1169
+ suppressProgrammaticScrollEvents();
1137
1170
  autoFitWidth = request.mode === 'auto' || request.mode === 'width';
1138
- setScale(scale, 'fit', request.source);
1171
+ if (Math.abs(scale - currentScale) > 0.001) {
1172
+ setScale(scale, 'fit', request.source);
1173
+ }
1174
+ else {
1175
+ pdfContext.viewer.update();
1176
+ syncUi();
1177
+ }
1139
1178
  await waitForPaint(targetWindow);
1179
+ suppressProgrammaticScrollEvents();
1140
1180
  (_c = pdfContext.viewer) === null || _c === void 0 ? void 0 : _c.update();
1141
1181
  const state = getPdfViewState();
1142
1182
  return {
@@ -1149,22 +1189,49 @@ export default async function renderPdf(buffer, target, context) {
1149
1189
  state,
1150
1190
  };
1151
1191
  };
1152
- const scheduleFitToWidth = () => {
1153
- if (!autoFitWidth || !pdfContext.viewer) {
1192
+ const scheduleFitAfterResize = () => {
1193
+ if (!pdfContext.viewer) {
1194
+ return;
1195
+ }
1196
+ if ((activeFitRequest === null || activeFitRequest === void 0 ? void 0 : activeFitRequest.resize) === 'initial') {
1197
+ return;
1198
+ }
1199
+ if (!activeFitRequest && !autoFitWidth) {
1154
1200
  return;
1155
1201
  }
1156
1202
  targetWindow.cancelAnimationFrame(fitFrame);
1157
- fitFrame = targetWindow.requestAnimationFrame(() => fitToWidth('viewer'));
1203
+ fitFrame = targetWindow.requestAnimationFrame(() => {
1204
+ if (activeFitRequest) {
1205
+ void applyPdfFit({ ...activeFitRequest, source: 'viewer' });
1206
+ return;
1207
+ }
1208
+ fitToWidth('viewer');
1209
+ });
1158
1210
  };
1159
1211
  const zoomIn = (source = 'user') => {
1160
- autoFitWidth = false;
1212
+ markFitInteraction(source);
1161
1213
  setScale(currentScale + SCALE_STEP, 'zoom-in', source);
1162
1214
  };
1163
1215
  const zoomOut = (source = 'user') => {
1164
- autoFitWidth = false;
1216
+ markFitInteraction(source);
1165
1217
  setScale(currentScale - SCALE_STEP, 'zoom-out', source);
1166
1218
  };
1219
+ const reapplyFitAfterLayout = (source, notifyViewState = true) => {
1220
+ if (activeFitRequest) {
1221
+ if (activeFitRequest.resize === 'initial') {
1222
+ return false;
1223
+ }
1224
+ void applyPdfFit({ ...activeFitRequest, source });
1225
+ return true;
1226
+ }
1227
+ if (autoFitWidth) {
1228
+ fitToWidth(source, notifyViewState);
1229
+ return true;
1230
+ }
1231
+ return false;
1232
+ };
1167
1233
  const applyRotation = (rotation, action = 'rotation-change', source = 'viewer', notifyViewState = true) => {
1234
+ markFitInteraction(source);
1168
1235
  const normalized = normalizeRotation(rotation);
1169
1236
  currentRotation = normalized;
1170
1237
  pdfThumbnails.clear();
@@ -1176,8 +1243,7 @@ export default async function renderPdf(buffer, target, context) {
1176
1243
  pdfContext.viewer.pagesRotation = normalized;
1177
1244
  void waitForPaint(targetWindow).then(() => {
1178
1245
  var _a;
1179
- if (autoFitWidth) {
1180
- fitToWidth(source, notifyViewState);
1246
+ if (reapplyFitAfterLayout(source, notifyViewState)) {
1181
1247
  if (notifyViewState) {
1182
1248
  emitViewStateChange(action, source);
1183
1249
  }
@@ -1203,6 +1269,7 @@ export default async function renderPdf(buffer, target, context) {
1203
1269
  if (!pdfContext.viewer || !pageCount) {
1204
1270
  return;
1205
1271
  }
1272
+ markFitInteraction(source);
1206
1273
  const nextPage = Math.min(pageCount, Math.max(1, pageNumber));
1207
1274
  runWithStableHorizontalScroll(() => {
1208
1275
  pdfContext.viewer.currentPageNumber = nextPage;
@@ -1217,12 +1284,12 @@ export default async function renderPdf(buffer, target, context) {
1217
1284
  if (!navigationEnabled) {
1218
1285
  return;
1219
1286
  }
1287
+ markFitInteraction(source);
1220
1288
  navVisible = !navVisible;
1221
1289
  syncUi();
1222
1290
  void waitForPaint(targetWindow).then(() => {
1223
1291
  var _a;
1224
- if (autoFitWidth) {
1225
- fitToWidth(source, notifyViewState);
1292
+ if (reapplyFitAfterLayout(source, notifyViewState)) {
1226
1293
  return;
1227
1294
  }
1228
1295
  (_a = pdfContext.viewer) === null || _a === void 0 ? void 0 : _a.update();
@@ -1232,6 +1299,7 @@ export default async function renderPdf(buffer, target, context) {
1232
1299
  }
1233
1300
  };
1234
1301
  const setNavMode = (mode, source = 'user', notifyViewState = true) => {
1302
+ markFitInteraction(source);
1235
1303
  navMode = mode;
1236
1304
  syncUi();
1237
1305
  if (notifyViewState) {
@@ -1359,7 +1427,7 @@ export default async function renderPdf(buffer, target, context) {
1359
1427
  `width:${formatCssPixels(pageWidth)}`,
1360
1428
  `height:${formatCssPixels(pageHeight)}`,
1361
1429
  ].join(';');
1362
- pagesHtml.push(`<section class="pdf-export-page viewer-print-page" style="${pageStyle}" aria-label="${escapeAttribute(pageTitle)}"><img src="${canvas.toDataURL('image/png')}" alt="${escapeAttribute(pageTitle)}" /></section>`);
1430
+ pagesHtml.push(`<section class="pdf-export-page viewer-print-page" data-viewer-print-page-index="${pageNumber - 1}" style="${pageStyle}" aria-label="${escapeAttribute(pageTitle)}"><img src="${canvas.toDataURL('image/png')}" alt="${escapeAttribute(pageTitle)}" /></section>`);
1363
1431
  canvas.width = 0;
1364
1432
  canvas.height = 0;
1365
1433
  (_b = (_a = page).cleanup) === null || _b === void 0 ? void 0 : _b.call(_a);
@@ -1414,7 +1482,6 @@ export default async function renderPdf(buffer, target, context) {
1414
1482
  var _a;
1415
1483
  applyRotation(currentRotation, 'rotation-change', 'viewer', false);
1416
1484
  loadStatus = 'ready';
1417
- fitToWidth('viewer', false);
1418
1485
  scheduleLegacyPageDimensionPatch();
1419
1486
  syncUi();
1420
1487
  (_a = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _a === void 0 ? void 0 : _a.call(context);
@@ -1433,6 +1500,7 @@ export default async function renderPdf(buffer, target, context) {
1433
1500
  void applyPdfFit(fitRequest);
1434
1501
  }
1435
1502
  else {
1503
+ fitToWidth('viewer', false);
1436
1504
  emitViewStateChange('init', 'viewer');
1437
1505
  }
1438
1506
  }
@@ -1613,6 +1681,7 @@ export default async function renderPdf(buffer, target, context) {
1613
1681
  }
1614
1682
  (_c = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _c === void 0 ? void 0 : _c.call(context, {
1615
1683
  includeDocumentStyles: false,
1684
+ getPrintMaskPages: () => Array.from(root.querySelectorAll('.pdfViewer .page')),
1616
1685
  printStyle: buildPdfPrintStyle,
1617
1686
  toHtml: renderPdfPagesForExport,
1618
1687
  });
@@ -1655,11 +1724,12 @@ export default async function renderPdf(buffer, target, context) {
1655
1724
  return getPdfZoomState();
1656
1725
  },
1657
1726
  resetZoom: () => {
1658
- fitToWidth('api');
1727
+ markFitInteraction('api');
1728
+ setScale(1, 'zoom-reset', 'api');
1659
1729
  return getPdfZoomState();
1660
1730
  },
1661
1731
  setZoom: scale => {
1662
- autoFitWidth = false;
1732
+ markFitInteraction('api');
1663
1733
  setScale(scale, 'zoom-change', 'api');
1664
1734
  return getPdfZoomState();
1665
1735
  },
@@ -1678,14 +1748,21 @@ export default async function renderPdf(buffer, target, context) {
1678
1748
  nextPageButton.addEventListener('click', () => goToPage(currentPage + 1, 'page-step', 'user'));
1679
1749
  zoomOutButton.addEventListener('click', () => zoomOut('user'));
1680
1750
  zoomInButton.addEventListener('click', () => zoomIn('user'));
1681
- scaleButton.addEventListener('click', () => fitToWidth('user'));
1751
+ scaleButton.addEventListener('click', () => {
1752
+ activeFitRequest = null;
1753
+ fitToWidth('user');
1754
+ });
1682
1755
  rotateLeftButton.addEventListener('click', () => applyRotation(currentRotation - 90, 'rotate-left', 'user'));
1683
1756
  rotateRightButton.addEventListener('click', () => applyRotation(currentRotation + 90, 'rotate-right', 'user'));
1684
1757
  pagesTab.addEventListener('click', () => setNavMode('pages', 'user'));
1685
1758
  outlineTab.addEventListener('click', () => setNavMode('outline', 'user'));
1759
+ container.addEventListener('wheel', recordUserScrollIntent, { passive: true });
1760
+ container.addEventListener('touchstart', recordUserScrollIntent, { passive: true });
1761
+ container.addEventListener('pointerdown', recordUserScrollIntent, { passive: true });
1762
+ container.addEventListener('keydown', recordUserScrollIntent);
1686
1763
  container.addEventListener('scroll', scheduleScrollViewStateChange, { passive: true });
1687
1764
  if (targetWindow.ResizeObserver) {
1688
- resizeObserver = new targetWindow.ResizeObserver(() => scheduleFitToWidth());
1765
+ resizeObserver = new targetWindow.ResizeObserver(() => scheduleFitAfterResize());
1689
1766
  resizeObserver.observe(container);
1690
1767
  }
1691
1768
  syncUi();
@@ -0,0 +1,20 @@
1
+ import type { FileViewerFitRequest } from '@file-viewer/core';
2
+ export declare const PDF_FIT_MIN_VIEWPORT_SIZE = 96;
3
+ export declare const PDF_FIT_HORIZONTAL_PADDING = 28;
4
+ export declare const PDF_PAGE_BORDER_WIDTH = 18;
5
+ export interface ResolvePdfFitViewportSizeInput {
6
+ containerWidth: number;
7
+ containerHeight: number;
8
+ fallbackWidth: number;
9
+ fallbackHeight: number;
10
+ request: Pick<FileViewerFitRequest, 'viewportWidth' | 'viewportHeight' | 'padding'>;
11
+ }
12
+ /**
13
+ * Resolves the PDF page viewport after the navigation pane has been laid out.
14
+ * Core request dimensions already exclude fit.padding; live container and
15
+ * window fallback dimensions do not, so only those branches subtract it.
16
+ */
17
+ export declare const resolvePdfFitViewportSize: ({ containerWidth, containerHeight, fallbackWidth, fallbackHeight, request, }: ResolvePdfFitViewportSizeInput) => {
18
+ width: number;
19
+ height: number;
20
+ };
package/dist/pdfFit.js ADDED
@@ -0,0 +1,26 @@
1
+ export const PDF_FIT_MIN_VIEWPORT_SIZE = 96;
2
+ export const PDF_FIT_HORIZONTAL_PADDING = 28;
3
+ export const PDF_PAGE_BORDER_WIDTH = 18;
4
+ const normalizePadding = (padding) => (Number.isFinite(padding) && Number(padding) > 0 ? Number(padding) : 0);
5
+ /**
6
+ * Resolves the PDF page viewport after the navigation pane has been laid out.
7
+ * Core request dimensions already exclude fit.padding; live container and
8
+ * window fallback dimensions do not, so only those branches subtract it.
9
+ */
10
+ export const resolvePdfFitViewportSize = ({ containerWidth, containerHeight, fallbackWidth, fallbackHeight, request, }) => {
11
+ const padding = normalizePadding(request.padding);
12
+ const requestWidth = Number(request.viewportWidth) || 0;
13
+ const requestHeight = Number(request.viewportHeight) || 0;
14
+ const hasContainerWidth = containerWidth > 0;
15
+ const hasContainerHeight = containerHeight > 0;
16
+ const width = hasContainerWidth
17
+ ? containerWidth - padding * 2
18
+ : requestWidth || fallbackWidth - padding * 2;
19
+ const height = hasContainerHeight
20
+ ? containerHeight - padding * 2
21
+ : requestHeight || fallbackHeight - padding * 2;
22
+ return {
23
+ width: Math.max(PDF_FIT_MIN_VIEWPORT_SIZE, width - PDF_FIT_HORIZONTAL_PADDING - PDF_PAGE_BORDER_WIDTH),
24
+ height: Math.max(PDF_FIT_MIN_VIEWPORT_SIZE, height - PDF_PAGE_BORDER_WIDTH),
25
+ };
26
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-pdf",
3
- "version": "2.1.28",
3
+ "version": "2.1.30",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone PDF renderer plugin for Flyfish File Viewer powered by PDF.js.",
@@ -54,7 +54,7 @@
54
54
  "LICENSE"
55
55
  ],
56
56
  "dependencies": {
57
- "@file-viewer/core": "2.1.28",
57
+ "@file-viewer/core": "2.1.30",
58
58
  "@fontsource-variable/noto-sans-sc": "5.2.10",
59
59
  "pdf-lib": "1.17.1",
60
60
  "pdfjs-dist": "5.4.624"