@file-viewer/core 2.0.6 → 2.0.8

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.
@@ -107,6 +107,7 @@ export type FileViewerRenderedInstance = {
107
107
  export interface FileViewerTypstOptions {
108
108
  compilerWasmUrl?: string;
109
109
  rendererWasmUrl?: string;
110
+ renderTimeoutMs?: number;
110
111
  }
111
112
  export interface FileViewerDataOptions {
112
113
  sqlWasmUrl?: string;
@@ -6,10 +6,14 @@ export declare const DEFAULT_FILE_VIEWER_SPREADSHEET_WORKER_PATH = "vendor/xlsx/
6
6
  export declare const DEFAULT_FILE_VIEWER_CAD_WASM_PATH = "wasm/cad/";
7
7
  export declare const DEFAULT_FILE_VIEWER_CAD_WORKER_PATH = "wasm/cad/dwg-worker.js";
8
8
  export declare const DEFAULT_FILE_VIEWER_CAD_DWF_WASM_PATH = "wasm/cad/dwfv-render.wasm";
9
- export declare const DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_URL = "https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-web-compiler@0.7.0/pkg/typst_ts_web_compiler_bg.wasm";
10
- export declare const DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_URL = "https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-renderer@0.7.0/pkg/typst_ts_renderer_bg.wasm";
9
+ export declare const DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_URL = "wasm/typst/typst_ts_web_compiler_bg.wasm";
10
+ export declare const DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_URL = "wasm/typst/typst_ts_renderer_bg.wasm";
11
+ export declare const FALLBACK_FILE_VIEWER_TYPST_COMPILER_WASM_URL = "https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-web-compiler@0.7.0/pkg/typst_ts_web_compiler_bg.wasm";
12
+ export declare const FALLBACK_FILE_VIEWER_TYPST_RENDERER_WASM_URL = "https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-renderer@0.7.0/pkg/typst_ts_renderer_bg.wasm";
13
+ export declare const DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_PACKAGE_PATH = "@myriaddreamin/typst-ts-web-compiler/pkg/typst_ts_web_compiler_bg.wasm";
11
14
  export declare const DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_PACKAGE_PATH = "@myriaddreamin/typst-ts-renderer/pkg/typst_ts_renderer_bg.wasm";
12
- export declare const DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL = "https://cdn.jsdelivr.net/npm/sql.js@1.14.1/dist/sql-wasm.wasm";
15
+ export declare const DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL = "wasm/data/sql-wasm.wasm";
16
+ export declare const DEFAULT_FILE_VIEWER_DATA_SQL_WASM_PACKAGE_PATH = "sql.js/dist/sql-wasm.wasm";
13
17
  export interface ResolveFileViewerAssetUrlOptions {
14
18
  baseUrl?: string;
15
19
  documentBaseUrl?: string;
@@ -54,9 +58,9 @@ export declare const resolveFileViewerArchiveWasmUrl: (options?: Pick<FileViewer
54
58
  export declare const resolveFileViewerCadAssetUrls: (options?: Pick<FileViewerCadOptions, "wasmPath" | "workerUrl" | "dwfWasmUrl"> | null, documentBaseUrl?: string) => ResolvedFileViewerCadAssetUrls;
55
59
  export declare const resolveFileViewerDocxWorkerUrl: (options?: Pick<FileViewerDocxOptions, "workerUrl"> | null, documentBaseUrl?: string) => string;
56
60
  export declare const resolveFileViewerSpreadsheetWorkerUrl: (options?: Pick<FileViewerSpreadsheetOptions, "workerUrl"> | null, documentBaseUrl?: string) => string;
57
- export declare const resolveFileViewerTypstCompilerWasmUrl: (options?: Pick<FileViewerTypstOptions, "compilerWasmUrl"> | null, overrides?: Array<string | undefined>) => string;
58
- export declare const resolveFileViewerTypstRendererWasmUrl: (options?: Pick<FileViewerTypstOptions, "rendererWasmUrl"> | null, overrides?: Array<string | undefined>) => string;
59
- export declare const resolveFileViewerDataSqlWasmUrl: (options?: Pick<FileViewerDataOptions, "sqlWasmUrl"> | null, overrides?: Array<string | undefined>) => string;
61
+ export declare const resolveFileViewerTypstCompilerWasmUrl: (options?: Pick<FileViewerTypstOptions, "compilerWasmUrl"> | null, overrides?: Array<string | undefined>, documentBaseUrl?: string) => string;
62
+ export declare const resolveFileViewerTypstRendererWasmUrl: (options?: Pick<FileViewerTypstOptions, "rendererWasmUrl"> | null, overrides?: Array<string | undefined>, documentBaseUrl?: string) => string;
63
+ export declare const resolveFileViewerDataSqlWasmUrl: (options?: Pick<FileViewerDataOptions, "sqlWasmUrl"> | null, overrides?: Array<string | undefined>, documentBaseUrl?: string) => string;
60
64
  export declare const listFileViewerRendererAssetManifests: () => FileViewerRendererAssetManifest[];
61
65
  export declare const getFileViewerRendererAssetManifest: (rendererId: string) => FileViewerRendererAssetManifest | null;
62
66
  export declare const resolveFileViewerRendererAssets: (rendererId: string, resolveOptions?: ResolveFileViewerRendererAssetsOptions) => ResolvedFileViewerRendererAsset[];
@@ -5,10 +5,14 @@ export const DEFAULT_FILE_VIEWER_SPREADSHEET_WORKER_PATH = 'vendor/xlsx/sheet.wo
5
5
  export const DEFAULT_FILE_VIEWER_CAD_WASM_PATH = 'wasm/cad/';
6
6
  export const DEFAULT_FILE_VIEWER_CAD_WORKER_PATH = 'wasm/cad/dwg-worker.js';
7
7
  export const DEFAULT_FILE_VIEWER_CAD_DWF_WASM_PATH = 'wasm/cad/dwfv-render.wasm';
8
- export const DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_URL = 'https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-web-compiler@0.7.0/pkg/typst_ts_web_compiler_bg.wasm';
9
- export const DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_URL = 'https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-renderer@0.7.0/pkg/typst_ts_renderer_bg.wasm';
8
+ export const DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_URL = 'wasm/typst/typst_ts_web_compiler_bg.wasm';
9
+ export const DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_URL = 'wasm/typst/typst_ts_renderer_bg.wasm';
10
+ export const FALLBACK_FILE_VIEWER_TYPST_COMPILER_WASM_URL = 'https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-web-compiler@0.7.0/pkg/typst_ts_web_compiler_bg.wasm';
11
+ export const FALLBACK_FILE_VIEWER_TYPST_RENDERER_WASM_URL = 'https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-renderer@0.7.0/pkg/typst_ts_renderer_bg.wasm';
12
+ export const DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_PACKAGE_PATH = '@myriaddreamin/typst-ts-web-compiler/pkg/typst_ts_web_compiler_bg.wasm';
10
13
  export const DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_PACKAGE_PATH = '@myriaddreamin/typst-ts-renderer/pkg/typst_ts_renderer_bg.wasm';
11
- export const DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL = 'https://cdn.jsdelivr.net/npm/sql.js@1.14.1/dist/sql-wasm.wasm';
14
+ export const DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL = 'wasm/data/sql-wasm.wasm';
15
+ export const DEFAULT_FILE_VIEWER_DATA_SQL_WASM_PACKAGE_PATH = 'sql.js/dist/sql-wasm.wasm';
12
16
  export const DEFAULT_FILE_VIEWER_RENDERER_ASSET_MANIFESTS = [
13
17
  {
14
18
  rendererId: 'archive',
@@ -107,22 +111,23 @@ export const DEFAULT_FILE_VIEWER_RENDERER_ASSET_MANIFESTS = [
107
111
  id: 'typst-compiler-wasm',
108
112
  rendererId: 'typst',
109
113
  kind: 'wasm',
110
- target: 'external',
114
+ target: 'public',
111
115
  required: true,
112
- defaultUrl: DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_URL,
116
+ defaultPath: DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_URL,
117
+ packagePath: DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_PACKAGE_PATH,
113
118
  optionPath: 'typst.compilerWasmUrl',
114
- description: 'Typst compiler WebAssembly module; configurable for private CDN deployment.',
119
+ description: 'Typst compiler WebAssembly module copied to the public assets directory.',
115
120
  },
116
121
  {
117
122
  id: 'typst-renderer-wasm',
118
123
  rendererId: 'typst',
119
- kind: 'bundled-wasm',
120
- target: 'bundled',
124
+ kind: 'wasm',
125
+ target: 'public',
121
126
  required: true,
122
- defaultUrl: DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_URL,
127
+ defaultPath: DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_URL,
123
128
  packagePath: DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_PACKAGE_PATH,
124
129
  optionPath: 'typst.rendererWasmUrl',
125
- description: 'Typst SVG renderer WebAssembly module bundled by the active frontend build tool.',
130
+ description: 'Typst SVG renderer WebAssembly module copied to the public assets directory.',
126
131
  },
127
132
  ],
128
133
  },
@@ -133,11 +138,12 @@ export const DEFAULT_FILE_VIEWER_RENDERER_ASSET_MANIFESTS = [
133
138
  id: 'data-sql-wasm',
134
139
  rendererId: 'data-asset',
135
140
  kind: 'wasm',
136
- target: 'external',
141
+ target: 'public',
137
142
  required: false,
138
- defaultUrl: DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL,
143
+ defaultPath: DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL,
144
+ packagePath: DEFAULT_FILE_VIEWER_DATA_SQL_WASM_PACKAGE_PATH,
139
145
  optionPath: 'data.sqlWasmUrl',
140
- description: 'sql.js WebAssembly module used when previewing SQLite files.',
146
+ description: 'sql.js WebAssembly module copied to the public assets directory for SQLite previews.',
141
147
  },
142
148
  ],
143
149
  },
@@ -188,20 +194,14 @@ export const resolveFileViewerSpreadsheetWorkerUrl = (options, documentBaseUrl)
188
194
  documentBaseUrl,
189
195
  });
190
196
  };
191
- export const resolveFileViewerTypstCompilerWasmUrl = (options, overrides = []) => {
192
- return (options === null || options === void 0 ? void 0 : options.compilerWasmUrl) ||
193
- overrides.find(Boolean) ||
194
- DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_URL;
197
+ export const resolveFileViewerTypstCompilerWasmUrl = (options, overrides = [], documentBaseUrl) => {
198
+ return resolveFileViewerAssetUrl((options === null || options === void 0 ? void 0 : options.compilerWasmUrl) || overrides.find(Boolean), DEFAULT_FILE_VIEWER_TYPST_COMPILER_WASM_URL, { documentBaseUrl });
195
199
  };
196
- export const resolveFileViewerTypstRendererWasmUrl = (options, overrides = []) => {
197
- return (options === null || options === void 0 ? void 0 : options.rendererWasmUrl) ||
198
- overrides.find(Boolean) ||
199
- DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_URL;
200
+ export const resolveFileViewerTypstRendererWasmUrl = (options, overrides = [], documentBaseUrl) => {
201
+ return resolveFileViewerAssetUrl((options === null || options === void 0 ? void 0 : options.rendererWasmUrl) || overrides.find(Boolean), DEFAULT_FILE_VIEWER_TYPST_RENDERER_WASM_URL, { documentBaseUrl });
200
202
  };
201
- export const resolveFileViewerDataSqlWasmUrl = (options, overrides = []) => {
202
- return (options === null || options === void 0 ? void 0 : options.sqlWasmUrl) ||
203
- overrides.find(Boolean) ||
204
- DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL;
203
+ export const resolveFileViewerDataSqlWasmUrl = (options, overrides = [], documentBaseUrl) => {
204
+ return resolveFileViewerAssetUrl((options === null || options === void 0 ? void 0 : options.sqlWasmUrl) || overrides.find(Boolean), DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL, { documentBaseUrl });
205
205
  };
206
206
  export const listFileViewerRendererAssetManifests = () => {
207
207
  return [...DEFAULT_FILE_VIEWER_RENDERER_ASSET_MANIFESTS];
@@ -68,6 +68,7 @@ const cadStyle = `
68
68
  .cad-native-stage .dwfv-root,.cad-native-stage .dwfv-workspace,.cad-native-stage .dwfv-stage{width:100%;min-width:0;min-height:0}
69
69
  .cad-native-stage .dwfv-root{height:100%}
70
70
  .cad-state{position:absolute;inset:50% auto auto 50%;max-width:min(520px,calc(100% - 48px));transform:translate(-50%,-50%);border-radius:12px;padding:14px 18px;background:rgba(255,255,255,.92);box-shadow:0 14px 38px rgba(15,23,42,.12);color:#53637a;font-size:13px;font-weight:800;text-align:center}
71
+ .cad-state[hidden]{display:none!important}
71
72
  .cad-state.error{color:#b42318}
72
73
  .cad-inspector strong{display:block;margin-bottom:12px;color:#1f2a3d;font-size:13px}
73
74
  .cad-inspector dl{display:grid;gap:8px;margin:0}
@@ -144,7 +144,7 @@ const renderSqlite = async (documentRef, buffer, context) => {
144
144
  const { default: initSqlJs } = await import('sql.js');
145
145
  const sqlWasmUrl = resolveFileViewerDataSqlWasmUrl((_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.data, [
146
146
  getWindowSqlWasmOverride(documentRef),
147
- ]);
147
+ ], documentRef.baseURI);
148
148
  const SQL = await initSqlJs({ locateFile: () => sqlWasmUrl });
149
149
  const db = new SQL.Database(new Uint8Array(buffer));
150
150
  try {
@@ -5,6 +5,7 @@ import { readFileViewerText } from '../source/index.js';
5
5
  const DIAGRAMS_VIEWER_URL = 'https://viewer.diagrams.net/js/viewer-static.min.js';
6
6
  const SVG_NS = 'http://www.w3.org/2000/svg';
7
7
  const EXCALIDRAW_OFFICIAL_TIMEOUT = 6000;
8
+ const DRAWIO_OFFICIAL_TIMEOUT = 6000;
8
9
  const diagramsViewerPromises = new WeakMap();
9
10
  const drawingStyle = `
10
11
  .drawing-viewer{display:flex;height:100%;min-height:360px;flex-direction:column;background:#edf2f7;color:#172033}
@@ -22,6 +23,7 @@ const drawingStyle = `
22
23
  .drawing-canvas .drawing-svg,.drawing-canvas svg{display:block;max-width:100%;height:auto;margin:0 auto;border-radius:10px;background:#fff;box-shadow:0 18px 42px rgba(15,23,42,.12)}
23
24
  .drawing-canvas .drawing-mxgraph{min-height:420px;overflow:hidden;border-radius:10px;background:#fff;box-shadow:0 18px 42px rgba(15,23,42,.12)}
24
25
  .drawing-state{position:absolute;inset:0;z-index:1;display:flex;align-items:center;justify-content:center;padding:24px;color:#64748b;font-size:14px;font-weight:700;text-align:center}
26
+ .drawing-state[hidden]{display:none!important}
25
27
  .drawing-state.error{color:#b42318}
26
28
  @media (max-width:720px){.drawing-toolbar{align-items:flex-start;flex-direction:column}.drawing-actions{width:100%;justify-content:space-between}.drawing-scroll{padding:12px}}
27
29
  `;
@@ -120,7 +122,8 @@ const suppressExcalidrawWorkerWarning = () => {
120
122
  const originalError = console.error;
121
123
  const patchedError = (...args) => {
122
124
  const message = args.map(arg => String(arg)).join(' ');
123
- if (message.includes('Failed to use workers for subsetting')) {
125
+ if (message.includes('Failed to use workers for subsetting') ||
126
+ message.includes('Failed to fetch font family')) {
124
127
  return;
125
128
  }
126
129
  originalError(...args);
@@ -361,7 +364,7 @@ const renderOfficialExcalidraw = async (payload, elements, target) => {
361
364
  }
362
365
  finally {
363
366
  clearTimeout(restoreTimer);
364
- restoreConsole();
367
+ setTimeout(restoreConsole, 3000);
365
368
  }
366
369
  };
367
370
  const renderExcalidraw = async (documentRef, text, target) => {
@@ -380,7 +383,262 @@ const renderExcalidraw = async (documentRef, text, target) => {
380
383
  await renderRoughFallback(documentRef, payload, elements, target);
381
384
  }
382
385
  };
383
- const renderDrawio = async (documentRef, text, target) => {
386
+ const getDirectChild = (parent, tagName) => {
387
+ return Array.from(parent.children).find(child => child.localName === tagName) || null;
388
+ };
389
+ const parseDrawioStyle = (style) => {
390
+ const entries = new Map();
391
+ for (const item of (style || '').split(';')) {
392
+ if (!item) {
393
+ continue;
394
+ }
395
+ const [key, ...rest] = item.split('=');
396
+ entries.set(key, rest.join('=') || '1');
397
+ }
398
+ return entries;
399
+ };
400
+ const getDrawioStyleValue = (style, key, fallback) => {
401
+ const value = style.get(key);
402
+ return value && value !== 'none' ? value : fallback;
403
+ };
404
+ const parseDrawioGeometry = (cell) => {
405
+ const geometry = getDirectChild(cell, 'mxGeometry');
406
+ if (!geometry) {
407
+ return null;
408
+ }
409
+ return {
410
+ x: toNumber(geometry.getAttribute('x')),
411
+ y: toNumber(geometry.getAttribute('y')),
412
+ width: Math.max(1, toNumber(geometry.getAttribute('width'), 80)),
413
+ height: Math.max(1, toNumber(geometry.getAttribute('height'), 40)),
414
+ points: Array.from(geometry.querySelectorAll('mxPoint')).map(point => ({
415
+ x: toNumber(point.getAttribute('x')),
416
+ y: toNumber(point.getAttribute('y')),
417
+ })),
418
+ };
419
+ };
420
+ const normalizeDrawioText = (documentRef, value) => {
421
+ if (!value) {
422
+ return '';
423
+ }
424
+ const helper = documentRef.createElement('textarea');
425
+ helper.innerHTML = value;
426
+ return helper.value
427
+ .replace(/<br\s*\/?>/gi, '\n')
428
+ .replace(/<[^>]+>/g, '')
429
+ .replace(/\u00a0/g, ' ')
430
+ .replace(/[ \t]+/g, ' ')
431
+ .trim();
432
+ };
433
+ const appendDrawioWrappedText = (documentRef, svg, text, x, y, width, height, fontSize, fill) => {
434
+ if (!text) {
435
+ return;
436
+ }
437
+ const textNode = createSvgElement(documentRef, 'text');
438
+ const maxChars = Math.max(4, Math.floor(width / Math.max(7, fontSize * 0.55)));
439
+ const words = text.includes(' ') ? text.split(/\s+/) : text.match(new RegExp(`.{1,${maxChars}}`, 'g')) || [text];
440
+ const lines = [];
441
+ let current = '';
442
+ for (const word of words) {
443
+ const next = current ? `${current} ${word}` : word;
444
+ if (next.length > maxChars && current) {
445
+ lines.push(current);
446
+ current = word;
447
+ }
448
+ else {
449
+ current = next;
450
+ }
451
+ }
452
+ if (current) {
453
+ lines.push(current);
454
+ }
455
+ const lineHeight = fontSize * 1.24;
456
+ const totalHeight = lineHeight * lines.length;
457
+ textNode.setAttribute('x', String(x + width / 2));
458
+ textNode.setAttribute('y', String(y + height / 2 - totalHeight / 2 + fontSize));
459
+ textNode.setAttribute('fill', fill);
460
+ textNode.setAttribute('font-size', String(fontSize));
461
+ textNode.setAttribute('font-family', 'Inter, Segoe UI, Arial, sans-serif');
462
+ textNode.setAttribute('font-weight', '600');
463
+ textNode.setAttribute('text-anchor', 'middle');
464
+ textNode.setAttribute('pointer-events', 'none');
465
+ lines.slice(0, 5).forEach((line, index) => {
466
+ const lineNode = createSvgElement(documentRef, 'tspan');
467
+ lineNode.setAttribute('x', String(x + width / 2));
468
+ lineNode.setAttribute('dy', index === 0 ? '0' : String(lineHeight));
469
+ lineNode.textContent = line;
470
+ textNode.appendChild(lineNode);
471
+ });
472
+ svg.appendChild(textNode);
473
+ };
474
+ const renderDrawioFallback = (documentRef, text, target) => {
475
+ var _a;
476
+ const parser = new DOMParser();
477
+ const parsed = parser.parseFromString(text, 'text/xml');
478
+ const parseError = parsed.querySelector('parsererror');
479
+ if (parseError) {
480
+ throw new Error(`Draw.io XML 解析失败:${parseError.textContent || 'invalid xml'}`);
481
+ }
482
+ const firstDiagram = parsed.querySelector('diagram');
483
+ const graphModel = (firstDiagram === null || firstDiagram === void 0 ? void 0 : firstDiagram.querySelector('mxGraphModel')) || parsed.querySelector('mxGraphModel');
484
+ if (!graphModel) {
485
+ throw new Error('当前 Draw.io 文件没有可直接渲染的 mxGraphModel。');
486
+ }
487
+ const cells = Array.from(graphModel.querySelectorAll('mxCell'));
488
+ const vertices = cells
489
+ .filter(cell => cell.getAttribute('vertex') === '1' && cell.getAttribute('connectable') !== '0')
490
+ .map(cell => ({
491
+ cell,
492
+ id: cell.getAttribute('id') || '',
493
+ geometry: parseDrawioGeometry(cell),
494
+ style: parseDrawioStyle(cell.getAttribute('style')),
495
+ text: normalizeDrawioText(documentRef, cell.getAttribute('value')),
496
+ }))
497
+ .filter(item => item.id && item.geometry);
498
+ if (!vertices.length) {
499
+ throw new Error('当前 Draw.io 文件没有可预览图元。');
500
+ }
501
+ const vertexById = new Map(vertices.map(vertex => [vertex.id, vertex]));
502
+ const allX = vertices.flatMap(vertex => [vertex.geometry.x, vertex.geometry.x + vertex.geometry.width]);
503
+ const allY = vertices.flatMap(vertex => [vertex.geometry.y, vertex.geometry.y + vertex.geometry.height]);
504
+ const edgePoints = [];
505
+ const edges = cells.filter(cell => cell.getAttribute('edge') === '1');
506
+ for (const edge of edges) {
507
+ const source = vertexById.get(edge.getAttribute('source') || '');
508
+ const targetVertex = vertexById.get(edge.getAttribute('target') || '');
509
+ const geometry = parseDrawioGeometry(edge);
510
+ if (source === null || source === void 0 ? void 0 : source.geometry) {
511
+ edgePoints.push({
512
+ x: source.geometry.x + source.geometry.width / 2,
513
+ y: source.geometry.y + source.geometry.height / 2,
514
+ });
515
+ }
516
+ geometry === null || geometry === void 0 ? void 0 : geometry.points.forEach(point => edgePoints.push(point));
517
+ if (targetVertex === null || targetVertex === void 0 ? void 0 : targetVertex.geometry) {
518
+ edgePoints.push({
519
+ x: targetVertex.geometry.x + targetVertex.geometry.width / 2,
520
+ y: targetVertex.geometry.y + targetVertex.geometry.height / 2,
521
+ });
522
+ }
523
+ }
524
+ allX.push(...edgePoints.map(point => point.x));
525
+ allY.push(...edgePoints.map(point => point.y));
526
+ const padding = 96;
527
+ const minX = Math.min(...allX) - padding;
528
+ const minY = Math.min(...allY) - padding;
529
+ const width = Math.max(480, Math.max(...allX) - Math.min(...allX) + padding * 2);
530
+ const height = Math.max(320, Math.max(...allY) - Math.min(...allY) + padding * 2);
531
+ const svg = createSvgElement(documentRef, 'svg');
532
+ svg.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);
533
+ svg.setAttribute('width', String(Math.ceil(width)));
534
+ svg.setAttribute('height', String(Math.ceil(height)));
535
+ svg.setAttribute('role', 'img');
536
+ svg.setAttribute('aria-label', (firstDiagram === null || firstDiagram === void 0 ? void 0 : firstDiagram.getAttribute('name')) || 'Draw.io local SVG preview');
537
+ const defs = createSvgElement(documentRef, 'defs');
538
+ const marker = createSvgElement(documentRef, 'marker');
539
+ marker.setAttribute('id', 'drawio-arrow');
540
+ marker.setAttribute('viewBox', '0 0 10 10');
541
+ marker.setAttribute('refX', '9');
542
+ marker.setAttribute('refY', '5');
543
+ marker.setAttribute('markerWidth', '7');
544
+ marker.setAttribute('markerHeight', '7');
545
+ marker.setAttribute('orient', 'auto-start-reverse');
546
+ const arrow = createSvgElement(documentRef, 'path');
547
+ arrow.setAttribute('d', 'M 0 0 L 10 5 L 0 10 z');
548
+ arrow.setAttribute('fill', '#64748b');
549
+ marker.appendChild(arrow);
550
+ defs.appendChild(marker);
551
+ svg.appendChild(defs);
552
+ const background = createSvgElement(documentRef, 'rect');
553
+ background.setAttribute('x', String(minX));
554
+ background.setAttribute('y', String(minY));
555
+ background.setAttribute('width', String(width));
556
+ background.setAttribute('height', String(height));
557
+ background.setAttribute('fill', '#ffffff');
558
+ svg.appendChild(background);
559
+ for (const edge of edges) {
560
+ const source = vertexById.get(edge.getAttribute('source') || '');
561
+ const targetVertex = vertexById.get(edge.getAttribute('target') || '');
562
+ if (!(source === null || source === void 0 ? void 0 : source.geometry) || !(targetVertex === null || targetVertex === void 0 ? void 0 : targetVertex.geometry)) {
563
+ continue;
564
+ }
565
+ const style = parseDrawioStyle(edge.getAttribute('style'));
566
+ const stroke = getDrawioStyleValue(style, 'strokeColor', '#64748b');
567
+ const points = [
568
+ {
569
+ x: source.geometry.x + source.geometry.width / 2,
570
+ y: source.geometry.y + source.geometry.height / 2,
571
+ },
572
+ ...(((_a = parseDrawioGeometry(edge)) === null || _a === void 0 ? void 0 : _a.points) || []),
573
+ {
574
+ x: targetVertex.geometry.x + targetVertex.geometry.width / 2,
575
+ y: targetVertex.geometry.y + targetVertex.geometry.height / 2,
576
+ },
577
+ ];
578
+ const polyline = createSvgElement(documentRef, 'polyline');
579
+ polyline.setAttribute('points', points.map(point => `${point.x},${point.y}`).join(' '));
580
+ polyline.setAttribute('fill', 'none');
581
+ polyline.setAttribute('stroke', stroke);
582
+ polyline.setAttribute('stroke-width', '2');
583
+ if (style.get('dashed') === '1') {
584
+ polyline.setAttribute('stroke-dasharray', '8 7');
585
+ }
586
+ if (style.get('endArrow') !== 'none') {
587
+ polyline.setAttribute('marker-end', 'url(#drawio-arrow)');
588
+ }
589
+ svg.appendChild(polyline);
590
+ }
591
+ for (const vertex of vertices) {
592
+ const geometry = vertex.geometry;
593
+ const fill = getDrawioStyleValue(vertex.style, 'fillColor', '#f8fafc');
594
+ const stroke = getDrawioStyleValue(vertex.style, 'strokeColor', '#64748b');
595
+ const fontSize = Math.max(10, toNumber(vertex.style.get('fontSize'), 14));
596
+ const shape = vertex.style.has('ellipse')
597
+ ? 'ellipse'
598
+ : vertex.style.get('shape') === 'rhombus'
599
+ ? 'diamond'
600
+ : 'rect';
601
+ if (shape === 'ellipse') {
602
+ const ellipse = createSvgElement(documentRef, 'ellipse');
603
+ ellipse.setAttribute('cx', String(geometry.x + geometry.width / 2));
604
+ ellipse.setAttribute('cy', String(geometry.y + geometry.height / 2));
605
+ ellipse.setAttribute('rx', String(geometry.width / 2));
606
+ ellipse.setAttribute('ry', String(geometry.height / 2));
607
+ ellipse.setAttribute('fill', fill);
608
+ ellipse.setAttribute('stroke', stroke);
609
+ ellipse.setAttribute('stroke-width', '2');
610
+ svg.appendChild(ellipse);
611
+ }
612
+ else if (shape === 'diamond') {
613
+ const diamond = createSvgElement(documentRef, 'polygon');
614
+ diamond.setAttribute('points', [
615
+ `${geometry.x + geometry.width / 2},${geometry.y}`,
616
+ `${geometry.x + geometry.width},${geometry.y + geometry.height / 2}`,
617
+ `${geometry.x + geometry.width / 2},${geometry.y + geometry.height}`,
618
+ `${geometry.x},${geometry.y + geometry.height / 2}`,
619
+ ].join(' '));
620
+ diamond.setAttribute('fill', fill);
621
+ diamond.setAttribute('stroke', stroke);
622
+ diamond.setAttribute('stroke-width', '2');
623
+ svg.appendChild(diamond);
624
+ }
625
+ else {
626
+ const rect = createSvgElement(documentRef, 'rect');
627
+ rect.setAttribute('x', String(geometry.x));
628
+ rect.setAttribute('y', String(geometry.y));
629
+ rect.setAttribute('width', String(geometry.width));
630
+ rect.setAttribute('height', String(geometry.height));
631
+ rect.setAttribute('rx', vertex.style.get('rounded') === '1' ? '10' : '2');
632
+ rect.setAttribute('fill', fill);
633
+ rect.setAttribute('stroke', stroke);
634
+ rect.setAttribute('stroke-width', '2');
635
+ svg.appendChild(rect);
636
+ }
637
+ appendDrawioWrappedText(documentRef, svg, vertex.text, geometry.x, geometry.y, geometry.width, geometry.height, fontSize, getDrawioStyleValue(vertex.style, 'fontColor', '#172033'));
638
+ }
639
+ appendRenderedSvg(target, svg, 'rough');
640
+ };
641
+ const renderOfficialDrawio = async (documentRef, text, target) => {
384
642
  const ownerWindow = documentRef.defaultView || (typeof window !== 'undefined' ? window : undefined);
385
643
  await loadDiagramsViewer(documentRef);
386
644
  await waitForFileViewerNextPaint(ownerWindow);
@@ -403,6 +661,19 @@ const renderDrawio = async (documentRef, text, target) => {
403
661
  throw new Error('diagrams.net viewer 未正确初始化');
404
662
  }
405
663
  ownerWindow.GraphViewer.createViewerForElement(host);
664
+ markRendered(target, 'official');
665
+ };
666
+ const renderDrawio = async (documentRef, text, target) => {
667
+ try {
668
+ await runWithTimeout(renderOfficialDrawio(documentRef, text, target), DRAWIO_OFFICIAL_TIMEOUT, 'diagrams.net 官方 Viewer 加载超时,自动切换本地 SVG 预览');
669
+ }
670
+ catch (error) {
671
+ console.warn(error);
672
+ diagramsViewerPromises.delete(documentRef);
673
+ delete target.dataset.drawingRendered;
674
+ target.replaceChildren();
675
+ renderDrawioFallback(documentRef, text, target);
676
+ }
406
677
  };
407
678
  export default async function renderDrawing(buffer, target, type = 'drawio', _context) {
408
679
  const documentRef = target.ownerDocument || document;
@@ -32,6 +32,7 @@ const epubStyle = `
32
32
  .epub-stage .epub-container{width:100%!important;max-width:100%;overflow-x:hidden!important;overflow-y:auto!important}
33
33
  .epub-stage iframe{max-width:100%}
34
34
  .epub-state{position:absolute;inset:18px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:rgba(255,255,255,.92);color:#64748b;font-size:14px}
35
+ .epub-state[hidden]{display:none!important}
35
36
  .epub-state.error{color:#b42318}
36
37
  .file-viewer[data-viewer-theme='dark'] .epub-viewer{background:#172033;color:#e5eef8}
37
38
  .file-viewer[data-viewer-theme='dark'] .epub-toolbar,.file-viewer[data-viewer-theme='dark'] .epub-toc,.file-viewer[data-viewer-theme='dark'] .epub-stage{background:#fff;color:#172033}
@@ -13,6 +13,7 @@ const modelStyle = `
13
13
  .model-stage{position:relative;flex:1;min-height:0;overflow:hidden}
14
14
  .model-stage canvas{display:block;width:100%;height:100%;outline:none}
15
15
  .model-state{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:24px;background:rgba(248,250,252,.88);color:#64748b;text-align:center;line-height:1.7}
16
+ .model-state[hidden]{display:none!important}
16
17
  .model-state.error{color:#b42318}
17
18
  .model-state strong{color:#b42318;font-size:18px}
18
19
  @media (max-width:720px){.model-toolbar{min-height:64px;align-items:flex-start;flex-direction:column;gap:8px;padding:8px 10px}.model-meta{width:100%;justify-content:flex-start}}
@@ -8,6 +8,7 @@ const ofdStyle = `
8
8
  .ofd-viewer *{box-sizing:border-box}
9
9
  .ofd-stage{min-height:100%;padding:18px 0 28px;overflow:auto;scrollbar-gutter:stable}
10
10
  .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}
11
+ .ofd-state[hidden]{display:none!important}
11
12
  .ofd-state.error{color:#b42318}
12
13
  .ofd-page-frame{position:relative;display:block;margin:0 auto 20px;overflow:visible}
13
14
  .ofd-page{display:block;margin-left:auto!important;margin-right:auto!important;box-shadow:0 10px 26px rgba(15,23,42,.12);transition:transform .16s ease}
@@ -12,9 +12,14 @@ const SCALE_STEP = 0.1;
12
12
  const FIT_HORIZONTAL_PADDING = 28;
13
13
  const PAGE_BORDER_WIDTH = 18;
14
14
  const PDF_EXPORT_MAX_PAGE_PIXELS = 8000000;
15
+ // PDF.js viewer CSS references image assets that are not shipped with the
16
+ // on-demand renderer chunk, so keep the preview self-contained and 404-free.
17
+ const normalizedPdfViewerStyle = pdfViewerStyle
18
+ .replace(/--page-border-image:\s*url\(images\/shadow\.png\)\s*9 9 repeat;/g, '--page-border-image:none;')
19
+ .replace(/background:\s*url\("\.\/images\/loading-icon\.gif"\)\s*center no-repeat;/g, 'background:none;');
15
20
  const createStyle = (documentRef) => {
16
21
  const style = documentRef.createElement('style');
17
- style.textContent = `${pdfViewerStyle}
22
+ style.textContent = `${normalizedPdfViewerStyle}
18
23
  .pdf-state[hidden],.pdf-nav-pane[hidden]{display:none!important}
19
24
  `;
20
25
  return style;
@@ -1,5 +1,5 @@
1
1
  import { $typst } from '@myriaddreamin/typst.ts';
2
- import { resolveFileViewerTypstCompilerWasmUrl, resolveFileViewerTypstRendererWasmUrl, } from '../platform/assets.js';
2
+ import { FALLBACK_FILE_VIEWER_TYPST_COMPILER_WASM_URL, FALLBACK_FILE_VIEWER_TYPST_RENDERER_WASM_URL, 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,6 +21,11 @@ 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}
24
29
  .file-viewer[data-viewer-theme='dark'] .typst-viewer{background:#101820;color:#e6edf3}
25
30
  .file-viewer[data-viewer-theme='dark'] .typst-toolbar{border-bottom-color:rgba(139,148,158,.22);background:rgba(15,23,42,.9)}
26
31
  .file-viewer[data-viewer-theme='dark'] .typst-toolbar strong{color:#f8fafc}
@@ -28,11 +33,22 @@ const typstStyle = `
28
33
  .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)}
29
34
  .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)}
30
35
  .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}
31
40
  @keyframes typst-spin{to{transform:rotate(360deg)}}
32
41
  @media (max-width:767px){.typst-toolbar{align-items:flex-start;flex-direction:column;gap:4px}.typst-pages{gap:16px;padding:16px 10px 28px}}
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}}
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}}
34
43
  `;
35
44
  let typstEngineConfigKey = '';
45
+ const DEFAULT_TYPST_RENDER_TIMEOUT_MS = 20000;
46
+ class TypstRenderTimeoutError extends Error {
47
+ constructor(timeoutMs) {
48
+ super(`Typst 编译超过 ${Math.round(timeoutMs / 1000)} 秒,已切换为源码预览`);
49
+ this.name = 'TypstRenderTimeoutError';
50
+ }
51
+ }
36
52
  const createStyle = (documentRef) => {
37
53
  const style = documentRef.createElement('style');
38
54
  style.textContent = typstStyle;
@@ -54,15 +70,7 @@ const getWindowOverride = (key) => {
54
70
  }
55
71
  return window[key];
56
72
  };
57
- const configureTypstEngine = (context) => {
58
- var _a;
59
- const typstOptions = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.typst;
60
- const compilerWasmUrl = resolveFileViewerTypstCompilerWasmUrl(typstOptions, [
61
- getWindowOverride('__FLYFISH_TYPST_COMPILER_WASM_URL__'),
62
- ]);
63
- const rendererWasmUrl = resolveFileViewerTypstRendererWasmUrl(typstOptions, [
64
- getWindowOverride('__FLYFISH_TYPST_RENDERER_WASM_URL__'),
65
- ]);
73
+ const configureTypstEngine = (compilerWasmUrl, rendererWasmUrl) => {
66
74
  const configKey = `${compilerWasmUrl}\n${rendererWasmUrl}`;
67
75
  if (typstEngineConfigKey === configKey) {
68
76
  return;
@@ -75,6 +83,69 @@ const configureTypstEngine = (context) => {
75
83
  });
76
84
  typstEngineConfigKey = configKey;
77
85
  };
86
+ const pushUniqueTypstCandidate = (candidates, candidate) => {
87
+ if (candidates.some(item => item.compilerWasmUrl === candidate.compilerWasmUrl &&
88
+ item.rendererWasmUrl === candidate.rendererWasmUrl)) {
89
+ return;
90
+ }
91
+ candidates.push(candidate);
92
+ };
93
+ const resolveTypstEngineCandidates = (context, documentBaseUrl) => {
94
+ var _a;
95
+ const typstOptions = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.typst;
96
+ const compilerOverride = getWindowOverride('__FLYFISH_TYPST_COMPILER_WASM_URL__');
97
+ const rendererOverride = getWindowOverride('__FLYFISH_TYPST_RENDERER_WASM_URL__');
98
+ const compilerWasmUrl = resolveFileViewerTypstCompilerWasmUrl(typstOptions, [
99
+ compilerOverride,
100
+ ], documentBaseUrl);
101
+ const rendererWasmUrl = resolveFileViewerTypstRendererWasmUrl(typstOptions, [
102
+ rendererOverride,
103
+ ], documentBaseUrl);
104
+ const hasConfiguredAsset = Boolean((typstOptions === null || typstOptions === void 0 ? void 0 : typstOptions.compilerWasmUrl) ||
105
+ (typstOptions === null || typstOptions === void 0 ? void 0 : typstOptions.rendererWasmUrl) ||
106
+ compilerOverride ||
107
+ rendererOverride);
108
+ const candidates = [];
109
+ pushUniqueTypstCandidate(candidates, {
110
+ compilerWasmUrl,
111
+ rendererWasmUrl,
112
+ source: hasConfiguredAsset ? 'configured' : 'local',
113
+ preflight: !hasConfiguredAsset,
114
+ });
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
+ return candidates;
125
+ };
126
+ const isHttpUrl = (url) => /^https?:\/\//i.test(url);
127
+ const isKnownMissingWasmUrl = async (url) => {
128
+ if (typeof fetch !== 'function' || !isHttpUrl(url)) {
129
+ return false;
130
+ }
131
+ try {
132
+ const response = await fetch(url, {
133
+ cache: 'force-cache',
134
+ method: 'HEAD',
135
+ });
136
+ return response.status === 404 || response.status === 410;
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ };
142
+ const isTypstAssetLoadError = (error) => {
143
+ if (Array.isArray(error)) {
144
+ return false;
145
+ }
146
+ const message = error instanceof Error ? error.message : String(error);
147
+ return /wasm|webassembly|fetch|module|instantiate|compile|network|404|410/i.test(message);
148
+ };
78
149
  const escapeAttribute = (value) => {
79
150
  return value.replace(/[&<>"']/g, char => {
80
151
  const entities = {
@@ -171,6 +242,29 @@ const formatTypstError = (error) => {
171
242
  const clampZoom = (value) => {
172
243
  return Math.min(3, Math.max(0.3, Number(value.toFixed(2))));
173
244
  };
245
+ const normalizeRenderTimeoutMs = (timeoutMs) => {
246
+ if (!Number.isFinite(timeoutMs) || timeoutMs === undefined) {
247
+ return DEFAULT_TYPST_RENDER_TIMEOUT_MS;
248
+ }
249
+ return Math.max(0, timeoutMs);
250
+ };
251
+ const withRenderTimeout = async (promise, timeoutMs) => {
252
+ if (timeoutMs <= 0) {
253
+ return promise;
254
+ }
255
+ let timeoutId;
256
+ const timeoutPromise = new Promise((_, reject) => {
257
+ timeoutId = setTimeout(() => reject(new TypstRenderTimeoutError(timeoutMs)), timeoutMs);
258
+ });
259
+ try {
260
+ return await Promise.race([promise, timeoutPromise]);
261
+ }
262
+ finally {
263
+ if (timeoutId) {
264
+ clearTimeout(timeoutId);
265
+ }
266
+ }
267
+ };
174
268
  const buildExportStyles = () => `
175
269
  <style>
176
270
  .typst-export-document {
@@ -269,6 +363,7 @@ export default async function renderTypst(buffer, target, _type, context) {
269
363
  let state = 'loading';
270
364
  let pages = [];
271
365
  let errorMessage = '';
366
+ let sourceFallbackMessage = '';
272
367
  let zoom = 1;
273
368
  let renderToken = 0;
274
369
  let disposed = false;
@@ -334,6 +429,16 @@ export default async function renderTypst(buffer, target, _type, context) {
334
429
  error.append(createElement(documentRef, 'strong', undefined, 'Typst 渲染失败'), createElement(documentRef, 'pre', undefined, errorMessage));
335
430
  body.replaceChildren(error);
336
431
  };
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
+ };
337
442
  const renderPages = () => {
338
443
  pageShells.clear();
339
444
  const pagesRoot = createElement(documentRef, 'main', 'typst-pages');
@@ -351,37 +456,73 @@ export default async function renderTypst(buffer, target, _type, context) {
351
456
  applyPageZoom();
352
457
  };
353
458
  const syncUi = () => {
354
- summary.textContent = state === 'ready' ? getPageSummary(pages) : 'Typst WASM renderer';
355
- status.textContent = state === 'loading' ? '正在编译' : state === 'error' ? '编译失败' : '已渲染';
459
+ summary.textContent = state === 'ready'
460
+ ? getPageSummary(pages)
461
+ : state === 'source'
462
+ ? 'Typst source preview'
463
+ : 'Typst WASM renderer';
464
+ status.textContent = state === 'loading'
465
+ ? '正在编译'
466
+ : state === 'error'
467
+ ? '编译失败'
468
+ : state === 'source'
469
+ ? '源码预览'
470
+ : '已渲染';
356
471
  if (state === 'loading') {
357
472
  renderLoading();
358
473
  }
359
474
  else if (state === 'error') {
360
475
  renderError();
361
476
  }
477
+ else if (state === 'source') {
478
+ renderSourceFallback();
479
+ }
362
480
  else {
363
481
  renderPages();
364
482
  }
365
483
  };
366
- const render = async () => {
484
+ const renderTypstSvg = async () => {
367
485
  var _a, _b;
486
+ const candidates = resolveTypstEngineCandidates(context, documentRef.baseURI);
487
+ 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
+ let lastError;
489
+ for (const candidate of candidates) {
490
+ if (candidate.preflight && await isKnownMissingWasmUrl(candidate.compilerWasmUrl)) {
491
+ lastError = new Error(`Typst compiler WASM missing: ${candidate.compilerWasmUrl}`);
492
+ continue;
493
+ }
494
+ try {
495
+ configureTypstEngine(candidate.compilerWasmUrl, candidate.rendererWasmUrl);
496
+ return await withRenderTimeout($typst.svg({
497
+ mainContent: source,
498
+ data_selection: {
499
+ body: true,
500
+ defs: true,
501
+ css: true,
502
+ js: false,
503
+ },
504
+ }), timeoutMs);
505
+ }
506
+ catch (error) {
507
+ lastError = error;
508
+ if (error instanceof TypstRenderTimeoutError || !isTypstAssetLoadError(error)) {
509
+ throw error;
510
+ }
511
+ }
512
+ }
513
+ throw lastError instanceof Error ? lastError : new Error('Typst WASM 加载失败');
514
+ };
515
+ const render = async () => {
516
+ var _a, _b, _c;
368
517
  const token = ++renderToken;
369
518
  state = 'loading';
370
519
  errorMessage = '';
520
+ sourceFallbackMessage = '';
371
521
  pages = [];
372
522
  (_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, null);
373
523
  syncUi();
374
524
  try {
375
- configureTypstEngine(context);
376
- const svg = await $typst.svg({
377
- mainContent: source,
378
- data_selection: {
379
- body: true,
380
- defs: true,
381
- css: true,
382
- js: false,
383
- },
384
- });
525
+ const svg = await renderTypstSvg();
385
526
  if (disposed || token !== renderToken) {
386
527
  return;
387
528
  }
@@ -395,6 +536,13 @@ export default async function renderTypst(buffer, target, _type, context) {
395
536
  if (disposed || token !== renderToken) {
396
537
  return;
397
538
  }
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
+ }
398
546
  errorMessage = formatTypstError(error);
399
547
  state = 'error';
400
548
  syncUi();
@@ -28,7 +28,7 @@ const umdStyle = `
28
28
  .umd-text{color:#1f2937;font-family:Georgia,'Times New Roman','Songti SC',SimSun,serif;font-size:17px;line-height:1.86;white-space:pre-wrap;word-break:break-word}
29
29
  .umd-image-list{display:grid;gap:18px}.umd-image-list figure{margin:0;text-align:center}.umd-image-list img{max-width:100%;height:auto;border-radius:6px;box-shadow:0 10px 24px rgba(15,23,42,.12)}
30
30
  .umd-empty,.umd-warning{color:#64748b;font-size:14px;line-height:1.7}.umd-warning{max-width:820px;margin:-28px auto 36px;padding:0 34px;color:#b45309;box-sizing:border-box}
31
- .umd-state{position:absolute;inset:18px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:rgba(255,255,255,.92);color:#64748b;font-size:14px}.umd-state.error{color:#b42318}
31
+ .umd-state{position:absolute;inset:18px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:rgba(255,255,255,.92);color:#64748b;font-size:14px}.umd-state[hidden]{display:none!important}.umd-state.error{color:#b42318}
32
32
  .file-viewer[data-viewer-theme='dark'] .umd-viewer{background:#101820;color:#e5edf6}.file-viewer[data-viewer-theme='dark'] .umd-toolbar,.file-viewer[data-viewer-theme='dark'] .umd-toc{background:rgba(15,23,42,.92);border-color:rgba(148,163,184,.18)}
33
33
  .file-viewer[data-viewer-theme='dark'] .umd-stage{background:#111827;box-shadow:0 18px 45px rgba(0,0,0,.35),inset 0 0 0 1px rgba(148,163,184,.16)}
34
34
  .file-viewer[data-viewer-theme='dark'] .umd-book-head h1,.file-viewer[data-viewer-theme='dark'] .umd-chapter h2,.file-viewer[data-viewer-theme='dark'] .umd-text,.file-viewer[data-viewer-theme='dark'] .umd-toc-head{color:#e5edf6}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/core",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Framework-neutral TypeScript core for Flyfish File Viewer",
@@ -58,7 +58,7 @@
58
58
  "LICENSE"
59
59
  ],
60
60
  "dependencies": {
61
- "@file-viewer/pptx": "^2.0.6",
61
+ "@file-viewer/pptx": "^2.0.8",
62
62
  "@flyfish-dev/cad-viewer": "^0.6.4",
63
63
  "@excalidraw/excalidraw": "^0.18.1",
64
64
  "@kenjiuno/msgreader": "^1.28.0",