@file-viewer/renderer-ofd 2.2.5 → 2.2.7

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.
Files changed (2) hide show
  1. package/dist/ofd.js +150 -5
  2. package/package.json +4 -3
package/dist/ofd.js CHANGED
@@ -3,9 +3,17 @@ const OFD_MIN_SCALE = 0.35;
3
3
  const OFD_MAX_SCALE = 3;
4
4
  const OFD_ZOOM_STEP = 0.1;
5
5
  const ofdStyle = `
6
- .ofd-viewer{position:relative;box-sizing:border-box;min-height:100%;background:var(--file-viewer-render-surface-background,#e9edf2);color:#172033;font-family:Aptos,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif}
6
+ .ofd-viewer{position:relative;display:flex;box-sizing:border-box;min-height:100%;min-width:0;flex-direction:column;background:var(--file-viewer-render-surface-background,#e9edf2);color:#172033;font-family:Aptos,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif}
7
7
  .ofd-viewer *{box-sizing:border-box}
8
- .ofd-stage{min-height:100%;padding:18px 0 28px;overflow:auto;scrollbar-gutter:stable}
8
+ .ofd-toolbar{position:sticky;top:0;z-index:4;display:flex;min-height:44px;align-items:center;justify-content:center;border-bottom:1px solid rgba(148,163,184,.32);background:rgba(255,255,255,.92);box-shadow:0 4px 14px rgba(15,23,42,.06);backdrop-filter:blur(10px)}
9
+ .ofd-toolbar-group{display:inline-flex;align-items:center;gap:8px;padding:5px 8px;border:1px solid #d7dee8;border-radius:9px;background:#fff;box-shadow:0 2px 8px rgba(15,23,42,.06)}
10
+ .ofd-page-button{display:inline-flex;width:30px;height:30px;align-items:center;justify-content:center;border:1px solid transparent;border-radius:7px;color:#334155;background:transparent;font:700 22px/1 Aptos,'Segoe UI',sans-serif;cursor:pointer}
11
+ .ofd-page-button:hover:not(:disabled){border-color:#b9d4f6;color:#1769d8;background:#edf5ff}
12
+ .ofd-page-button:focus-visible{outline:2px solid #408fff;outline-offset:1px}
13
+ .ofd-page-button:disabled{color:#a7b0bd;cursor:not-allowed}
14
+ .ofd-page-meter{display:inline-flex;min-width:92px;align-items:baseline;justify-content:center;gap:5px;color:#64748b;font-size:12px;white-space:nowrap}
15
+ .ofd-page-meter strong{color:#172033;font-size:13px}
16
+ .ofd-stage{position:relative;flex:1;min-width:0;min-height:0;padding:18px 0 28px;overflow:auto;scrollbar-gutter:stable}
9
17
  .ofd-state{position:absolute;inset:0;z-index:1;display:flex;align-items:center;justify-content:center;background:rgba(246,248,250,.92);color:#64748b;font-size:14px}
10
18
  .ofd-state[hidden]{display:none!important}
11
19
  .ofd-state.error{color:#b42318}
@@ -15,8 +23,15 @@ const ofdStyle = `
15
23
  .ofd-page svg,.ofd-page canvas,.ofd-page img{filter:none!important;mix-blend-mode:normal!important}
16
24
  [data-viewer-theme='dark'] .ofd-viewer{background:var(--file-viewer-render-surface-background,#172033);color:#e5eef8}
17
25
  [data-viewer-theme='dark'] .ofd-state{background:rgba(15,23,42,.9);color:#cbd5e1}
18
- @media (prefers-color-scheme:dark){[data-viewer-theme='system'] .ofd-viewer{background:var(--file-viewer-render-surface-background,#172033);color:#e5eef8}[data-viewer-theme='system'] .ofd-state{background:rgba(15,23,42,.9);color:#cbd5e1}}
19
- @media print{.ofd-viewer{background:#fff!important}.ofd-stage{padding:0!important;overflow:visible!important}.ofd-page-frame{break-after:page;page-break-after:always;margin:0 auto!important}.ofd-page-frame:last-child{break-after:auto;page-break-after:auto}.ofd-page{box-shadow:none!important;transition:none!important}}
26
+ [data-viewer-theme='dark'] .ofd-toolbar{border-color:rgba(100,116,139,.5);background:rgba(15,23,42,.9)}
27
+ [data-viewer-theme='dark'] .ofd-toolbar-group{border-color:#475569;background:#1e293b}
28
+ [data-viewer-theme='dark'] .ofd-page-button{color:#e2e8f0}
29
+ [data-viewer-theme='dark'] .ofd-page-button:hover:not(:disabled){border-color:#4d79ad;color:#93c5fd;background:#263b55}
30
+ [data-viewer-theme='dark'] .ofd-page-button:disabled{color:#64748b}
31
+ [data-viewer-theme='dark'] .ofd-page-meter{color:#94a3b8}
32
+ [data-viewer-theme='dark'] .ofd-page-meter strong{color:#f1f5f9}
33
+ @media (prefers-color-scheme:dark){[data-viewer-theme='system'] .ofd-viewer{background:var(--file-viewer-render-surface-background,#172033);color:#e5eef8}[data-viewer-theme='system'] .ofd-state{background:rgba(15,23,42,.9);color:#cbd5e1}[data-viewer-theme='system'] .ofd-toolbar{border-color:rgba(100,116,139,.5);background:rgba(15,23,42,.9)}[data-viewer-theme='system'] .ofd-toolbar-group{border-color:#475569;background:#1e293b}[data-viewer-theme='system'] .ofd-page-button{color:#e2e8f0}[data-viewer-theme='system'] .ofd-page-button:hover:not(:disabled){border-color:#4d79ad;color:#93c5fd;background:#263b55}[data-viewer-theme='system'] .ofd-page-button:disabled{color:#64748b}[data-viewer-theme='system'] .ofd-page-meter{color:#94a3b8}[data-viewer-theme='system'] .ofd-page-meter strong{color:#f1f5f9}}
34
+ @media print{.ofd-viewer{display:block;background:#fff!important}.ofd-toolbar{display:none!important}.ofd-stage{padding:0!important;overflow:visible!important}.ofd-page-frame{break-after:page;page-break-after:always;margin:0 auto!important}.ofd-page-frame:last-child{break-after:auto;page-break-after:auto}.ofd-page{box-shadow:none!important;transition:none!important}}
20
35
  `;
21
36
  const loadOfdModule = (() => {
22
37
  let modulePromise = null;
@@ -131,14 +146,38 @@ export default async function renderOfd(buffer, target, context) {
131
146
  let state = 'loading';
132
147
  let errorMessage = '';
133
148
  let zoom = 1;
149
+ let currentPage = 1;
150
+ let pageCount = 0;
151
+ let scrollFrame = 0;
134
152
  let ofdDocumentPromise = null;
135
153
  const style = createStyle(documentRef);
136
154
  const viewer = createElement(documentRef, 'div', 'ofd-viewer');
137
155
  viewer.dataset.viewerZoomProvider = 'ofd';
138
156
  const stateNode = createElement(documentRef, 'div', 'ofd-state', t('ofd.state.loading'));
139
157
  stateNode.setAttribute('aria-live', 'polite');
158
+ const toolbar = createElement(documentRef, 'div', 'ofd-toolbar');
159
+ toolbar.setAttribute('role', 'toolbar');
160
+ toolbar.setAttribute('aria-label', t('ofd.toolbar.pageNavigation'));
161
+ const pageGroup = createElement(documentRef, 'div', 'ofd-toolbar-group');
162
+ const previousPageButton = createElement(documentRef, 'button', 'ofd-page-button', '‹');
163
+ previousPageButton.type = 'button';
164
+ previousPageButton.title = t('ofd.toolbar.previousPage');
165
+ previousPageButton.setAttribute('aria-label', t('ofd.toolbar.previousPage'));
166
+ const pageMeter = createElement(documentRef, 'span', 'ofd-page-meter');
167
+ pageMeter.setAttribute('aria-live', 'polite');
168
+ const pageMeterCurrent = createElement(documentRef, 'strong', undefined, '1');
169
+ const pageMeterTotal = createElement(documentRef, 'span', undefined, '/ -');
170
+ pageMeter.append(pageMeterCurrent, pageMeterTotal);
171
+ const nextPageButton = createElement(documentRef, 'button', 'ofd-page-button', '›');
172
+ nextPageButton.type = 'button';
173
+ nextPageButton.title = t('ofd.toolbar.nextPage');
174
+ nextPageButton.setAttribute('aria-label', t('ofd.toolbar.nextPage'));
175
+ pageGroup.append(previousPageButton, pageMeter, nextPageButton);
176
+ toolbar.append(pageGroup);
140
177
  const stage = createElement(documentRef, 'div', 'ofd-stage');
141
- viewer.append(stateNode, stage);
178
+ stage.tabIndex = 0;
179
+ stage.setAttribute('aria-label', t('ofd.toolbar.documentPages'));
180
+ viewer.append(toolbar, stateNode, stage);
142
181
  target.replaceChildren(style, viewer);
143
182
  const clearStage = () => {
144
183
  stage.replaceChildren();
@@ -148,6 +187,77 @@ export default async function renderOfd(buffer, target, context) {
148
187
  stateNode.classList.toggle('error', state === 'error');
149
188
  stateNode.textContent = state === 'error' ? errorMessage : t('ofd.state.loading');
150
189
  };
190
+ const getPageFrames = () => Array.from(stage.querySelectorAll('.ofd-page-frame'));
191
+ const syncPageNavigation = () => {
192
+ pageCount = getPageFrames().length;
193
+ currentPage = pageCount ? Math.min(pageCount, Math.max(1, currentPage)) : 0;
194
+ pageMeterCurrent.textContent = pageCount ? String(currentPage) : '-';
195
+ pageMeterTotal.textContent = `/ ${pageCount || '-'}`;
196
+ pageMeter.setAttribute('aria-label', pageCount
197
+ ? t('ofd.toolbar.pageStatus', { current: currentPage, total: pageCount })
198
+ : t('ofd.toolbar.noPages'));
199
+ previousPageButton.disabled = currentPage <= 1;
200
+ nextPageButton.disabled = currentPage <= 0 || currentPage >= pageCount;
201
+ };
202
+ const scrollToPage = (requestedPage, behavior = 'smooth') => {
203
+ const frames = getPageFrames();
204
+ if (!frames.length) {
205
+ currentPage = 0;
206
+ syncPageNavigation();
207
+ return false;
208
+ }
209
+ const nextPage = Math.min(frames.length, Math.max(1, Math.round(requestedPage)));
210
+ currentPage = nextPage;
211
+ syncPageNavigation();
212
+ const frame = frames[nextPage - 1];
213
+ if (typeof (frame === null || frame === void 0 ? void 0 : frame.scrollIntoView) === 'function') {
214
+ frame.scrollIntoView({ block: 'start', inline: 'nearest', behavior });
215
+ }
216
+ return true;
217
+ };
218
+ const detectCurrentPage = () => {
219
+ const frames = getPageFrames();
220
+ if (!frames.length) {
221
+ currentPage = 0;
222
+ syncPageNavigation();
223
+ return;
224
+ }
225
+ const stageRect = stage.getBoundingClientRect();
226
+ const viewportCenter = stageRect.top + stage.clientHeight / 2;
227
+ let closestIndex = 0;
228
+ let closestDistance = Number.POSITIVE_INFINITY;
229
+ frames.forEach((frame, index) => {
230
+ const rect = frame.getBoundingClientRect();
231
+ const center = rect.top + rect.height / 2;
232
+ const distance = Math.abs(center - viewportCenter);
233
+ if (distance < closestDistance) {
234
+ closestDistance = distance;
235
+ closestIndex = index;
236
+ }
237
+ });
238
+ const detectedPage = closestIndex + 1;
239
+ if (detectedPage !== currentPage) {
240
+ currentPage = detectedPage;
241
+ syncPageNavigation();
242
+ }
243
+ };
244
+ const scheduleCurrentPageDetection = () => {
245
+ var _a;
246
+ if (scrollFrame)
247
+ return;
248
+ const requestFrame = (_a = targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.requestAnimationFrame) === null || _a === void 0 ? void 0 : _a.bind(targetWindow);
249
+ if (requestFrame) {
250
+ scrollFrame = requestFrame(() => {
251
+ scrollFrame = 0;
252
+ detectCurrentPage();
253
+ });
254
+ return;
255
+ }
256
+ scrollFrame = (targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.setTimeout(() => {
257
+ scrollFrame = 0;
258
+ detectCurrentPage();
259
+ }, 0)) || 0;
260
+ };
151
261
  const getOfdDocument = async (ofd) => {
152
262
  // OFD 解压和 XML 解析成本较高,尺寸变化只重排页面,不重复解析压缩包。
153
263
  ofdDocumentPromise || (ofdDocumentPromise = parseWithOfdJs(ofd, buffer).then(documents => {
@@ -251,6 +361,9 @@ export default async function renderOfd(buffer, target, context) {
251
361
  lastRenderedWidth = width;
252
362
  await waitForPaint(targetWindow);
253
363
  syncPageZoom();
364
+ syncPageNavigation();
365
+ if (currentPage > 1)
366
+ scrollToPage(currentPage, 'auto');
254
367
  state = 'ready';
255
368
  syncState();
256
369
  zoomEmitter.emit();
@@ -281,6 +394,31 @@ export default async function renderOfd(buffer, target, context) {
281
394
  });
282
395
  resizeObserver.observe(viewer);
283
396
  };
397
+ previousPageButton.addEventListener('click', () => {
398
+ scrollToPage(currentPage - 1);
399
+ });
400
+ nextPageButton.addEventListener('click', () => {
401
+ scrollToPage(currentPage + 1);
402
+ });
403
+ stage.addEventListener('scroll', scheduleCurrentPageDetection, { passive: true });
404
+ stage.addEventListener('keydown', event => {
405
+ if (event.key === 'PageUp') {
406
+ event.preventDefault();
407
+ scrollToPage(currentPage - 1);
408
+ }
409
+ else if (event.key === 'PageDown') {
410
+ event.preventDefault();
411
+ scrollToPage(currentPage + 1);
412
+ }
413
+ else if (event.key === 'Home' && (event.ctrlKey || event.metaKey)) {
414
+ event.preventDefault();
415
+ scrollToPage(1);
416
+ }
417
+ else if (event.key === 'End' && (event.ctrlKey || event.metaKey)) {
418
+ event.preventDefault();
419
+ scrollToPage(pageCount);
420
+ }
421
+ });
284
422
  try {
285
423
  // Do not report the renderer as loaded until page surfaces exist. This
286
424
  // keeps immediate print/mask operations page-aware and propagates initial
@@ -333,6 +471,13 @@ export default async function renderOfd(buffer, target, context) {
333
471
  disposed = true;
334
472
  renderVersion += 1;
335
473
  targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.clearTimeout(resizeTimer);
474
+ if (scrollFrame) {
475
+ if (targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.cancelAnimationFrame)
476
+ targetWindow.cancelAnimationFrame(scrollFrame);
477
+ else
478
+ targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.clearTimeout(scrollFrame);
479
+ }
480
+ scrollFrame = 0;
336
481
  resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
337
482
  resizeObserver = null;
338
483
  unregisterFileViewerZoomProvider(viewer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-ofd",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone OFD renderer plugin for File Viewer powered by DLTech21/ofd.js.",
@@ -53,7 +53,7 @@
53
53
  "LICENSE"
54
54
  ],
55
55
  "dependencies": {
56
- "@file-viewer/core": "2.2.5",
56
+ "@file-viewer/core": "2.2.7",
57
57
  "jszip": "^3.10.1"
58
58
  },
59
59
  "devDependencies": {
@@ -67,6 +67,7 @@
67
67
  "verify:github-94": "node scripts/verify-github-94.mjs",
68
68
  "verify:image-placement": "node scripts/verify-image-placement.mjs",
69
69
  "verify:pageblock-render": "node scripts/verify-pageblock-render.mjs",
70
- "verify:render-fidelity": "node scripts/verify-render-fidelity.mjs"
70
+ "verify:render-fidelity": "node scripts/verify-render-fidelity.mjs",
71
+ "verify:github-177": "pnpm --filter @file-viewer/core build && pnpm exec tsc -b tsconfig.json --force && node scripts/verify-github-177.mjs"
71
72
  }
72
73
  }