@file-viewer/core 2.0.8 → 2.0.10

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.
@@ -1,11 +1,12 @@
1
- import { getDocument, GlobalWorkerOptions, PDFWorker as PdfJsWorker, PixelsPerInch, version, } from 'pdfjs-dist/legacy/build/pdf.mjs';
1
+ import { getDocument, GlobalWorkerOptions, PDFWorker as PdfJsWorker, PixelsPerInch, } from 'pdfjs-dist/legacy/build/pdf.mjs';
2
2
  import { EventBus, GenericL10n, PDFFindController, PDFLinkService, PDFViewer, } from 'pdfjs-dist/legacy/web/pdf_viewer.mjs';
3
3
  import { registerFileViewerSearchProvider, registerFileViewerZoomProvider, unregisterFileViewerSearchProvider, unregisterFileViewerZoomProvider, } from '../features/document/dom/index.js';
4
4
  import { createFileViewerZoomChangeEmitter } from '../features/document/zoom.js';
5
5
  import { buildPrintPageStyle, formatCssPixels, } from '../output/printLayout.js';
6
+ import { DEFAULT_FILE_VIEWER_PDF_WORKER_PATH, resolveFileViewerPdfAssetUrls, } from '../platform/assets.js';
6
7
  import { DEFAULT_PDF_RANGE_CHUNK_SIZE } from '../source/loading.js';
7
8
  import { pdfViewerStyle } from './pdfStyles.js';
8
- export const DEFAULT_FILE_VIEWER_PDF_WORKER_URL = `https://npm.onmicrosoft.cn/pdfjs-dist@${version}/legacy/build/pdf.worker.mjs`;
9
+ export const DEFAULT_FILE_VIEWER_PDF_WORKER_URL = DEFAULT_FILE_VIEWER_PDF_WORKER_PATH;
9
10
  const MIN_SCALE = 0.2;
10
11
  const MAX_SCALE = 3;
11
12
  const SCALE_STEP = 0.1;
@@ -21,6 +22,10 @@ const createStyle = (documentRef) => {
21
22
  const style = documentRef.createElement('style');
22
23
  style.textContent = `${normalizedPdfViewerStyle}
23
24
  .pdf-state[hidden],.pdf-nav-pane[hidden]{display:none!important}
25
+ .pdf-page-button--with-thumbnail{grid-template-columns:52px minmax(0,1fr);min-height:74px}
26
+ .pdf-page-thumb--thumbnail{width:46px;height:60px;overflow:hidden;background:#fff}
27
+ .pdf-page-thumb--thumbnail img{display:block;width:100%;height:100%;object-fit:contain}
28
+ .pdf-page-thumb--thumbnail span{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%}
24
29
  `;
25
30
  return style;
26
31
  };
@@ -71,16 +76,7 @@ const waitForPaint = (view) => new Promise(resolve => {
71
76
  globalThis.setTimeout(resolve, 0);
72
77
  });
73
78
  const resolvePdfWorkerUrl = (options, documentUrl) => {
74
- const configured = options === null || options === void 0 ? void 0 : options.workerUrl;
75
- if (!configured) {
76
- return DEFAULT_FILE_VIEWER_PDF_WORKER_URL;
77
- }
78
- try {
79
- return documentUrl ? new URL(configured, documentUrl).href : configured;
80
- }
81
- catch {
82
- return configured;
83
- }
79
+ return resolveFileViewerPdfAssetUrls(options, documentUrl).workerUrl;
84
80
  };
85
81
  const buildOutlineItems = (items, prefix = 'outline') => items.map((item, index) => {
86
82
  const id = `${prefix}-${index}`;
@@ -105,6 +101,7 @@ export default async function renderPdf(buffer, target, context) {
105
101
  const options = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.pdf;
106
102
  const navigationEnabled = (options === null || options === void 0 ? void 0 : options.navigation) !== false;
107
103
  const toolbarVisible = (options === null || options === void 0 ? void 0 : options.toolbar) !== false;
104
+ const thumbnailsEnabled = (options === null || options === void 0 ? void 0 : options.thumbnails) === true;
108
105
  const zoomEmitter = createFileViewerZoomChangeEmitter();
109
106
  let navVisible = (options === null || options === void 0 ? void 0 : options.navigation) === false ? false : (options === null || options === void 0 ? void 0 : options.defaultNavigationVisible) !== false;
110
107
  let navMode = 'pages';
@@ -117,13 +114,17 @@ export default async function renderPdf(buffer, target, context) {
117
114
  let currentRotation = normalizeRotation((_b = options === null || options === void 0 ? void 0 : options.rotation) !== null && _b !== void 0 ? _b : 0);
118
115
  let outlineItems = [];
119
116
  let resizeObserver = null;
117
+ let thumbnailObserver = null;
120
118
  let fitFrame = 0;
119
+ let pageDimensionFrame = 0;
121
120
  let destroyed = false;
122
121
  let loadVersion = 0;
123
122
  let pdfSearchState = createPdfSearchState();
124
123
  let pdfMatchesCount = { current: 0, total: 0 };
125
124
  let pdfSearchOptions;
126
125
  let pdfSearchWaiters = [];
126
+ const pdfThumbnails = new Map();
127
+ const pendingPdfThumbnails = new Set();
127
128
  const pdfContext = {
128
129
  viewer: null,
129
130
  linkService: null,
@@ -222,11 +223,21 @@ export default async function renderPdf(buffer, target, context) {
222
223
  navList.replaceChildren();
223
224
  navList.className = navMode === 'pages' ? 'pdf-page-list' : 'pdf-outline-list';
224
225
  if (navMode === 'pages') {
226
+ thumbnailObserver === null || thumbnailObserver === void 0 ? void 0 : thumbnailObserver.disconnect();
225
227
  for (let page = 1; page <= pageCount; page += 1) {
226
228
  const button = createElement(documentRef, 'button', 'pdf-page-button');
227
229
  button.type = 'button';
228
230
  button.classList.toggle('pdf-page-button--active', page === currentPage);
229
- button.append(createElement(documentRef, 'span', 'pdf-page-thumb', String(page)), createElement(documentRef, 'span', 'pdf-page-label', `第 ${page} 页`));
231
+ button.classList.toggle('pdf-page-button--with-thumbnail', thumbnailsEnabled);
232
+ const thumb = createElement(documentRef, 'span', 'pdf-page-thumb');
233
+ if (thumbnailsEnabled) {
234
+ thumb.classList.add('pdf-page-thumb--thumbnail');
235
+ queuePdfThumbnail(page, thumb);
236
+ }
237
+ else {
238
+ thumb.textContent = String(page);
239
+ }
240
+ button.append(thumb, createElement(documentRef, 'span', 'pdf-page-label', `第 ${page} 页`));
230
241
  button.addEventListener('click', () => goToPage(page));
231
242
  navList.append(button);
232
243
  }
@@ -253,6 +264,101 @@ export default async function renderPdf(buffer, target, context) {
253
264
  navList.append(createElement(documentRef, 'div', 'pdf-outline-empty', '当前 PDF 没有可用目录'));
254
265
  }
255
266
  };
267
+ const paintPdfThumbnail = (pageNumber, thumb) => {
268
+ const imageUrl = pdfThumbnails.get(pageNumber);
269
+ thumb.dataset.pdfThumbnailPage = String(pageNumber);
270
+ if (!imageUrl) {
271
+ thumb.replaceChildren(createElement(documentRef, 'span', undefined, String(pageNumber)));
272
+ return false;
273
+ }
274
+ const image = documentRef.createElement('img');
275
+ image.src = imageUrl;
276
+ image.alt = `第 ${pageNumber} 页缩略图`;
277
+ image.loading = 'lazy';
278
+ thumb.replaceChildren(image);
279
+ return true;
280
+ };
281
+ const renderPdfThumbnail = async (pageNumber) => {
282
+ var _a, _b;
283
+ const pdfDocument = pdfContext.document;
284
+ if (!pdfDocument || pdfThumbnails.has(pageNumber) || pendingPdfThumbnails.has(pageNumber)) {
285
+ return;
286
+ }
287
+ pendingPdfThumbnails.add(pageNumber);
288
+ try {
289
+ const page = await pdfDocument.getPage(pageNumber);
290
+ if (destroyed || pdfContext.document !== pdfDocument) {
291
+ return;
292
+ }
293
+ const baseViewport = page.getViewport({
294
+ scale: PixelsPerInch.PDF_TO_CSS_UNITS,
295
+ rotation: currentRotation,
296
+ });
297
+ const deviceScale = Math.min(2, Math.max(1, targetWindow.devicePixelRatio || 1));
298
+ const thumbnailWidth = 46;
299
+ const ratio = Math.min(1, thumbnailWidth / Math.max(baseViewport.width, 1));
300
+ const renderViewport = page.getViewport({
301
+ scale: PixelsPerInch.PDF_TO_CSS_UNITS * ratio * deviceScale,
302
+ rotation: currentRotation,
303
+ });
304
+ const canvas = documentRef.createElement('canvas');
305
+ const canvasContext = canvas.getContext('2d');
306
+ if (!canvasContext) {
307
+ return;
308
+ }
309
+ canvas.width = Math.max(1, Math.ceil(renderViewport.width));
310
+ canvas.height = Math.max(1, Math.ceil(renderViewport.height));
311
+ await page.render({ canvas, canvasContext, viewport: renderViewport }).promise;
312
+ if (destroyed || pdfContext.document !== pdfDocument) {
313
+ return;
314
+ }
315
+ pdfThumbnails.set(pageNumber, canvas.toDataURL('image/png'));
316
+ canvas.width = 0;
317
+ canvas.height = 0;
318
+ (_b = (_a = page).cleanup) === null || _b === void 0 ? void 0 : _b.call(_a);
319
+ navList
320
+ .querySelectorAll(`.pdf-page-thumb--thumbnail[data-pdf-thumbnail-page="${pageNumber}"]`)
321
+ .forEach(thumb => paintPdfThumbnail(pageNumber, thumb));
322
+ }
323
+ catch (error) {
324
+ console.warn('[file-viewer] PDF 缩略图渲染失败。', error);
325
+ }
326
+ finally {
327
+ pendingPdfThumbnails.delete(pageNumber);
328
+ }
329
+ };
330
+ const ensureThumbnailObserver = () => {
331
+ if (!thumbnailsEnabled || thumbnailObserver || typeof targetWindow.IntersectionObserver !== 'function') {
332
+ return;
333
+ }
334
+ thumbnailObserver = new targetWindow.IntersectionObserver(entries => {
335
+ entries.forEach(entry => {
336
+ if (!entry.isIntersecting) {
337
+ return;
338
+ }
339
+ const targetElement = entry.target;
340
+ const pageNumber = Number(targetElement.dataset.pdfThumbnailPage || '0');
341
+ thumbnailObserver === null || thumbnailObserver === void 0 ? void 0 : thumbnailObserver.unobserve(targetElement);
342
+ if (pageNumber > 0) {
343
+ void renderPdfThumbnail(pageNumber);
344
+ }
345
+ });
346
+ }, {
347
+ root: navList,
348
+ rootMargin: '96px 0px',
349
+ });
350
+ };
351
+ const queuePdfThumbnail = (pageNumber, thumb) => {
352
+ if (paintPdfThumbnail(pageNumber, thumb)) {
353
+ return;
354
+ }
355
+ ensureThumbnailObserver();
356
+ if (thumbnailObserver) {
357
+ thumbnailObserver.observe(thumb);
358
+ return;
359
+ }
360
+ void renderPdfThumbnail(pageNumber);
361
+ };
256
362
  const syncUi = () => {
257
363
  root.classList.toggle('pdf-shell--nav-hidden', !navigationEnabled || !navVisible);
258
364
  root.classList.toggle('pdf-shell--toolbar-hidden', !toolbarVisible);
@@ -278,6 +384,33 @@ export default async function renderPdf(buffer, target, context) {
278
384
  stateNode.textContent = loadStatus === 'error' ? errorMessage : '正在加载 PDF...';
279
385
  renderNavList();
280
386
  };
387
+ const writeLegacyCompatiblePageDimensions = () => {
388
+ var _a, _b;
389
+ const pdfViewer = pdfContext.viewer;
390
+ if (!pdfViewer) {
391
+ return;
392
+ }
393
+ const totalPages = pageCount || pdfViewer.pagesCount || 0;
394
+ for (let index = 0; index < totalPages; index += 1) {
395
+ const pageView = pdfViewer.getPageView(index);
396
+ const pageElement = (pageView === null || pageView === void 0 ? void 0 : pageView.div) ||
397
+ pdfViewerRoot.querySelector(`.page[data-page-number="${index + 1}"]`);
398
+ const width = (_a = pageView === null || pageView === void 0 ? void 0 : pageView.viewport) === null || _a === void 0 ? void 0 : _a.width;
399
+ const height = (_b = pageView === null || pageView === void 0 ? void 0 : pageView.viewport) === null || _b === void 0 ? void 0 : _b.height;
400
+ if (!pageElement || !Number.isFinite(width) || !Number.isFinite(height)) {
401
+ continue;
402
+ }
403
+ pageElement.style.setProperty('width', `${Math.max(1, Math.round(width || 0))}px`, 'important');
404
+ pageElement.style.setProperty('height', `${Math.max(1, Math.round(height || 0))}px`, 'important');
405
+ }
406
+ };
407
+ const scheduleLegacyPageDimensionPatch = () => {
408
+ targetWindow.cancelAnimationFrame(pageDimensionFrame);
409
+ pageDimensionFrame = targetWindow.requestAnimationFrame(() => {
410
+ writeLegacyCompatiblePageDimensions();
411
+ targetWindow.requestAnimationFrame(writeLegacyCompatiblePageDimensions);
412
+ });
413
+ };
281
414
  const createPdfWorker = () => {
282
415
  if (!(targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.Worker)) {
283
416
  return null;
@@ -435,6 +568,7 @@ export default async function renderPdf(buffer, target, context) {
435
568
  const normalizedScale = clampScale(scale);
436
569
  pdfContext.viewer.currentScale = normalizedScale;
437
570
  currentScale = normalizedScale;
571
+ scheduleLegacyPageDimensionPatch();
438
572
  zoomEmitter.emit();
439
573
  syncUi();
440
574
  };
@@ -489,6 +623,8 @@ export default async function renderPdf(buffer, target, context) {
489
623
  const applyRotation = (rotation) => {
490
624
  const normalized = normalizeRotation(rotation);
491
625
  currentRotation = normalized;
626
+ pdfThumbnails.clear();
627
+ pendingPdfThumbnails.clear();
492
628
  if (!pdfContext.viewer) {
493
629
  syncUi();
494
630
  return;
@@ -501,6 +637,7 @@ export default async function renderPdf(buffer, target, context) {
501
637
  return;
502
638
  }
503
639
  (_a = pdfContext.viewer) === null || _a === void 0 ? void 0 : _a.update();
640
+ scheduleLegacyPageDimensionPatch();
504
641
  syncUi();
505
642
  });
506
643
  };
@@ -669,6 +806,9 @@ export default async function renderPdf(buffer, target, context) {
669
806
  errorMessage = '';
670
807
  pdfContext.document = null;
671
808
  outlineItems = [];
809
+ pdfThumbnails.clear();
810
+ pendingPdfThumbnails.clear();
811
+ thumbnailObserver === null || thumbnailObserver === void 0 ? void 0 : thumbnailObserver.disconnect();
672
812
  (_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, null);
673
813
  syncUi();
674
814
  let resource = null;
@@ -702,6 +842,7 @@ export default async function renderPdf(buffer, target, context) {
702
842
  var _a;
703
843
  applyRotation(currentRotation);
704
844
  fitToWidth();
845
+ scheduleLegacyPageDimensionPatch();
705
846
  loadStatus = 'ready';
706
847
  syncUi();
707
848
  (_a = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _a === void 0 ? void 0 : _a.call(context);
@@ -715,13 +856,16 @@ export default async function renderPdf(buffer, target, context) {
715
856
  });
716
857
  eventBus.on('scalechanging', ({ scale }) => {
717
858
  currentScale = clampScale(scale);
859
+ scheduleLegacyPageDimensionPatch();
718
860
  zoomEmitter.emit();
719
861
  syncUi();
720
862
  });
863
+ eventBus.on('pagerendered', scheduleLegacyPageDimensionPatch);
721
864
  if (!(context === null || context === void 0 ? void 0 : context.streamUrl) && !buffer.byteLength) {
722
865
  throw new Error('PDF 缺少可读取的数据源');
723
866
  }
724
867
  const worker = createPdfWorker();
868
+ const pdfAssets = resolveFileViewerPdfAssetUrls(options, documentRef.URL || documentRef.baseURI);
725
869
  const source = (context === null || context === void 0 ? void 0 : context.streamUrl)
726
870
  ? {
727
871
  url: context.streamUrl,
@@ -734,8 +878,9 @@ export default async function renderPdf(buffer, target, context) {
734
878
  const loadingTask = getDocument({
735
879
  ...source,
736
880
  worker: worker || undefined,
737
- cMapUrl: `https://npm.onmicrosoft.cn/pdfjs-dist@${version}/cmaps/`,
738
- wasmUrl: `https://npm.onmicrosoft.cn/pdfjs-dist@${version}/wasm/`,
881
+ cMapUrl: pdfAssets.cMapUrl,
882
+ wasmUrl: pdfAssets.wasmUrl,
883
+ standardFontDataUrl: pdfAssets.standardFontDataUrl,
739
884
  useWorkerFetch: true,
740
885
  cMapPacked: true,
741
886
  enableXfa: true,
@@ -831,6 +976,9 @@ export default async function renderPdf(buffer, target, context) {
831
976
  destroyed = true;
832
977
  loadVersion += 1;
833
978
  targetWindow.cancelAnimationFrame(fitFrame);
979
+ targetWindow.cancelAnimationFrame(pageDimensionFrame);
980
+ thumbnailObserver === null || thumbnailObserver === void 0 ? void 0 : thumbnailObserver.disconnect();
981
+ thumbnailObserver = null;
834
982
  resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
835
983
  resizeObserver = null;
836
984
  unregisterFileViewerSearchProvider(root);
@@ -1,5 +1,5 @@
1
1
  import { $typst } from '@myriaddreamin/typst.ts';
2
- import { FALLBACK_FILE_VIEWER_TYPST_COMPILER_WASM_URL, FALLBACK_FILE_VIEWER_TYPST_RENDERER_WASM_URL, resolveFileViewerTypstCompilerWasmUrl, resolveFileViewerTypstRendererWasmUrl, } from '../platform/assets.js';
2
+ import { resolveFileViewerTypstCompilerWasmUrl, resolveFileViewerTypstRendererWasmUrl, } from '../platform/assets.js';
3
3
  import { registerFileViewerZoomProvider, unregisterFileViewerZoomProvider, } from '../features/document/dom/index.js';
4
4
  import { createFileViewerZoomChangeEmitter } from '../features/document/zoom.js';
5
5
  import { formatCssPixels } from '../output/printLayout.js';
@@ -21,11 +21,6 @@ const typstStyle = `
21
21
  .typst-loading p{margin:0;color:#6a778b;font-size:13px}
22
22
  .typst-error{color:#9f1d1d}
23
23
  .typst-error pre{max-height:360px;margin:14px 0 0;overflow:auto;border-radius:10px;background:#fff1f2;color:#9f1d1d;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono',monospace;font-size:12px;line-height:1.7;padding:14px;white-space:pre-wrap}
24
- .typst-source-fallback{box-sizing:border-box;width:min(1040px,calc(100% - 32px));margin:28px auto 44px;border:1px solid rgba(20,35,53,.1);border-radius:16px;background:#fff;box-shadow:0 18px 44px rgba(15,23,42,.14);overflow:hidden}
25
- .typst-source-fallback header{padding:18px 20px;border-bottom:1px solid rgba(120,134,155,.18);background:linear-gradient(135deg,#f0fdf4,#eff6ff)}
26
- .typst-source-fallback strong{display:block;color:#172033;font-size:15px;font-weight:850}
27
- .typst-source-fallback p{margin:6px 0 0;color:#5f6e84;font-size:13px;line-height:1.7}
28
- .typst-source-fallback pre{box-sizing:border-box;max-height:none;margin:0;overflow:auto;background:#0f172a;color:#e2e8f0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono',monospace;font-size:13px;line-height:1.75;padding:20px;tab-size:2;white-space:pre}
29
24
  .file-viewer[data-viewer-theme='dark'] .typst-viewer{background:#101820;color:#e6edf3}
30
25
  .file-viewer[data-viewer-theme='dark'] .typst-toolbar{border-bottom-color:rgba(139,148,158,.22);background:rgba(15,23,42,.9)}
31
26
  .file-viewer[data-viewer-theme='dark'] .typst-toolbar strong{color:#f8fafc}
@@ -33,19 +28,15 @@ const typstStyle = `
33
28
  .file-viewer[data-viewer-theme='dark'] .typst-page-shell{border-color:rgba(139,148,158,.26);box-shadow:0 24px 56px rgba(0,0,0,.38)}
34
29
  .file-viewer[data-viewer-theme='dark'] .typst-loading,.file-viewer[data-viewer-theme='dark'] .typst-error{border-color:rgba(139,148,158,.22);background:#151b23;box-shadow:0 24px 56px rgba(0,0,0,.32)}
35
30
  .file-viewer[data-viewer-theme='dark'] .typst-loading strong,.file-viewer[data-viewer-theme='dark'] .typst-error strong{color:#f8fafc}
36
- .file-viewer[data-viewer-theme='dark'] .typst-source-fallback{border-color:rgba(139,148,158,.26);background:#151b23;box-shadow:0 24px 56px rgba(0,0,0,.32)}
37
- .file-viewer[data-viewer-theme='dark'] .typst-source-fallback header{border-bottom-color:rgba(139,148,158,.22);background:linear-gradient(135deg,rgba(16,185,129,.18),rgba(59,130,246,.16))}
38
- .file-viewer[data-viewer-theme='dark'] .typst-source-fallback strong{color:#f8fafc}
39
- .file-viewer[data-viewer-theme='dark'] .typst-source-fallback p{color:#9aa7b8}
40
31
  @keyframes typst-spin{to{transform:rotate(360deg)}}
41
32
  @media (max-width:767px){.typst-toolbar{align-items:flex-start;flex-direction:column;gap:4px}.typst-pages{gap:16px;padding:16px 10px 28px}}
42
- @media (prefers-color-scheme:dark){.file-viewer[data-viewer-theme='system'] .typst-viewer{background:#101820;color:#e6edf3}.file-viewer[data-viewer-theme='system'] .typst-toolbar{border-bottom-color:rgba(139,148,158,.22);background:rgba(15,23,42,.9)}.file-viewer[data-viewer-theme='system'] .typst-toolbar strong{color:#f8fafc}.file-viewer[data-viewer-theme='system'] .typst-toolbar span,.file-viewer[data-viewer-theme='system'] .typst-toolbar em{color:#9aa7b8}.file-viewer[data-viewer-theme='system'] .typst-page-shell{border-color:rgba(139,148,158,.26);box-shadow:0 24px 56px rgba(0,0,0,.38)}.file-viewer[data-viewer-theme='system'] .typst-loading,.file-viewer[data-viewer-theme='system'] .typst-error{border-color:rgba(139,148,158,.22);background:#151b23;box-shadow:0 24px 56px rgba(0,0,0,.32)}.file-viewer[data-viewer-theme='system'] .typst-loading strong,.file-viewer[data-viewer-theme='system'] .typst-error strong{color:#f8fafc}.file-viewer[data-viewer-theme='system'] .typst-source-fallback{border-color:rgba(139,148,158,.26);background:#151b23;box-shadow:0 24px 56px rgba(0,0,0,.32)}.file-viewer[data-viewer-theme='system'] .typst-source-fallback header{border-bottom-color:rgba(139,148,158,.22);background:linear-gradient(135deg,rgba(16,185,129,.18),rgba(59,130,246,.16))}.file-viewer[data-viewer-theme='system'] .typst-source-fallback strong{color:#f8fafc}.file-viewer[data-viewer-theme='system'] .typst-source-fallback p{color:#9aa7b8}}
33
+ @media (prefers-color-scheme:dark){.file-viewer[data-viewer-theme='system'] .typst-viewer{background:#101820;color:#e6edf3}.file-viewer[data-viewer-theme='system'] .typst-toolbar{border-bottom-color:rgba(139,148,158,.22);background:rgba(15,23,42,.9)}.file-viewer[data-viewer-theme='system'] .typst-toolbar strong{color:#f8fafc}.file-viewer[data-viewer-theme='system'] .typst-toolbar span,.file-viewer[data-viewer-theme='system'] .typst-toolbar em{color:#9aa7b8}.file-viewer[data-viewer-theme='system'] .typst-page-shell{border-color:rgba(139,148,158,.26);box-shadow:0 24px 56px rgba(0,0,0,.38)}.file-viewer[data-viewer-theme='system'] .typst-loading,.file-viewer[data-viewer-theme='system'] .typst-error{border-color:rgba(139,148,158,.22);background:#151b23;box-shadow:0 24px 56px rgba(0,0,0,.32)}.file-viewer[data-viewer-theme='system'] .typst-loading strong,.file-viewer[data-viewer-theme='system'] .typst-error strong{color:#f8fafc}}
43
34
  `;
44
35
  let typstEngineConfigKey = '';
45
- const DEFAULT_TYPST_RENDER_TIMEOUT_MS = 20000;
36
+ const DEFAULT_TYPST_RENDER_TIMEOUT_MS = 60000;
46
37
  class TypstRenderTimeoutError extends Error {
47
38
  constructor(timeoutMs) {
48
- super(`Typst 编译超过 ${Math.round(timeoutMs / 1000)} 秒,已切换为源码预览`);
39
+ super(`Typst 编译超过 ${Math.round(timeoutMs / 1000)} 秒`);
49
40
  this.name = 'TypstRenderTimeoutError';
50
41
  }
51
42
  }
@@ -112,15 +103,6 @@ const resolveTypstEngineCandidates = (context, documentBaseUrl) => {
112
103
  source: hasConfiguredAsset ? 'configured' : 'local',
113
104
  preflight: !hasConfiguredAsset,
114
105
  });
115
- // Static hosts such as Cloudflare Pages reject files larger than 25 MiB.
116
- // Keep the package self-contained, but let hosted demos fall back gracefully
117
- // when the local compiler WASM was intentionally omitted from deployment.
118
- pushUniqueTypstCandidate(candidates, {
119
- compilerWasmUrl: FALLBACK_FILE_VIEWER_TYPST_COMPILER_WASM_URL,
120
- rendererWasmUrl: FALLBACK_FILE_VIEWER_TYPST_RENDERER_WASM_URL,
121
- source: 'cdn',
122
- preflight: false,
123
- });
124
106
  return candidates;
125
107
  };
126
108
  const isHttpUrl = (url) => /^https?:\/\//i.test(url);
@@ -139,6 +121,15 @@ const isKnownMissingWasmUrl = async (url) => {
139
121
  return false;
140
122
  }
141
123
  };
124
+ const resolveKnownMissingTypstAsset = async (candidate) => {
125
+ if (await isKnownMissingWasmUrl(candidate.compilerWasmUrl)) {
126
+ return `Typst compiler WASM missing: ${candidate.compilerWasmUrl}`;
127
+ }
128
+ if (await isKnownMissingWasmUrl(candidate.rendererWasmUrl)) {
129
+ return `Typst renderer WASM missing: ${candidate.rendererWasmUrl}`;
130
+ }
131
+ return '';
132
+ };
142
133
  const isTypstAssetLoadError = (error) => {
143
134
  if (Array.isArray(error)) {
144
135
  return false;
@@ -239,6 +230,22 @@ const formatTypstError = (error) => {
239
230
  }
240
231
  return String(error);
241
232
  };
233
+ const formatTypstRuntimeError = (error) => {
234
+ const message = formatTypstError(error);
235
+ if (error instanceof TypstRenderTimeoutError) {
236
+ return [
237
+ message,
238
+ '请检查 Typst 源文件复杂度,或通过 options.typst.renderTimeoutMs 调大浏览器端编译超时。'
239
+ ].join('\n\n');
240
+ }
241
+ if (isTypstAssetLoadError(error)) {
242
+ return [
243
+ message,
244
+ 'Typst 需要本地 compiler / renderer WASM。请运行 file-viewer-copy-assets,或配置 options.typst.compilerWasmUrl / options.typst.rendererWasmUrl,并确认服务器以 application/wasm 返回资源。'
245
+ ].join('\n\n');
246
+ }
247
+ return message;
248
+ };
242
249
  const clampZoom = (value) => {
243
250
  return Math.min(3, Math.max(0.3, Number(value.toFixed(2))));
244
251
  };
@@ -363,7 +370,6 @@ export default async function renderTypst(buffer, target, _type, context) {
363
370
  let state = 'loading';
364
371
  let pages = [];
365
372
  let errorMessage = '';
366
- let sourceFallbackMessage = '';
367
373
  let zoom = 1;
368
374
  let renderToken = 0;
369
375
  let disposed = false;
@@ -429,16 +435,6 @@ export default async function renderTypst(buffer, target, _type, context) {
429
435
  error.append(createElement(documentRef, 'strong', undefined, 'Typst 渲染失败'), createElement(documentRef, 'pre', undefined, errorMessage));
430
436
  body.replaceChildren(error);
431
437
  };
432
- const renderSourceFallback = () => {
433
- const fallback = createElement(documentRef, 'main', 'typst-source-fallback');
434
- fallback.setAttribute('aria-label', 'Typst source preview');
435
- const header = createElement(documentRef, 'header');
436
- header.append(createElement(documentRef, 'strong', undefined, '已切换为 Typst 源码预览'), createElement(documentRef, 'p', undefined, sourceFallbackMessage || '当前浏览器没有在预期时间内完成 Typst 编译,源码仍可完整查看。'));
437
- const pre = createElement(documentRef, 'pre');
438
- pre.textContent = source;
439
- fallback.append(header, pre);
440
- body.replaceChildren(fallback);
441
- };
442
438
  const renderPages = () => {
443
439
  pageShells.clear();
444
440
  const pagesRoot = createElement(documentRef, 'main', 'typst-pages');
@@ -458,25 +454,18 @@ export default async function renderTypst(buffer, target, _type, context) {
458
454
  const syncUi = () => {
459
455
  summary.textContent = state === 'ready'
460
456
  ? getPageSummary(pages)
461
- : state === 'source'
462
- ? 'Typst source preview'
463
- : 'Typst WASM renderer';
457
+ : 'Typst WASM renderer';
464
458
  status.textContent = state === 'loading'
465
459
  ? '正在编译'
466
460
  : state === 'error'
467
461
  ? '编译失败'
468
- : state === 'source'
469
- ? '源码预览'
470
- : '已渲染';
462
+ : '已渲染';
471
463
  if (state === 'loading') {
472
464
  renderLoading();
473
465
  }
474
466
  else if (state === 'error') {
475
467
  renderError();
476
468
  }
477
- else if (state === 'source') {
478
- renderSourceFallback();
479
- }
480
469
  else {
481
470
  renderPages();
482
471
  }
@@ -487,8 +476,11 @@ export default async function renderTypst(buffer, target, _type, context) {
487
476
  const timeoutMs = normalizeRenderTimeoutMs((_b = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.typst) === null || _b === void 0 ? void 0 : _b.renderTimeoutMs);
488
477
  let lastError;
489
478
  for (const candidate of candidates) {
490
- if (candidate.preflight && await isKnownMissingWasmUrl(candidate.compilerWasmUrl)) {
491
- lastError = new Error(`Typst compiler WASM missing: ${candidate.compilerWasmUrl}`);
479
+ const missingAsset = candidate.preflight
480
+ ? await resolveKnownMissingTypstAsset(candidate)
481
+ : '';
482
+ if (missingAsset) {
483
+ lastError = new Error(missingAsset);
492
484
  continue;
493
485
  }
494
486
  try {
@@ -513,11 +505,10 @@ export default async function renderTypst(buffer, target, _type, context) {
513
505
  throw lastError instanceof Error ? lastError : new Error('Typst WASM 加载失败');
514
506
  };
515
507
  const render = async () => {
516
- var _a, _b, _c;
508
+ var _a, _b;
517
509
  const token = ++renderToken;
518
510
  state = 'loading';
519
511
  errorMessage = '';
520
- sourceFallbackMessage = '';
521
512
  pages = [];
522
513
  (_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, null);
523
514
  syncUi();
@@ -536,14 +527,7 @@ export default async function renderTypst(buffer, target, _type, context) {
536
527
  if (disposed || token !== renderToken) {
537
528
  return;
538
529
  }
539
- if (error instanceof TypstRenderTimeoutError) {
540
- sourceFallbackMessage = error.message;
541
- state = 'source';
542
- syncUi();
543
- (_c = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _c === void 0 ? void 0 : _c.call(context);
544
- return;
545
- }
546
- errorMessage = formatTypstError(error);
530
+ errorMessage = formatTypstRuntimeError(error);
547
531
  state = 'error';
548
532
  syncUi();
549
533
  }