@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.
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/dist/contracts/types.d.ts +32 -0
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/platform/assets.d.ts +21 -5
- package/dist/platform/assets.js +129 -9
- package/dist/registry/formats.d.ts +1 -1
- package/dist/renderers/drawing.d.ts +1 -1
- package/dist/renderers/drawing.js +89 -15
- package/dist/renderers/pdf.d.ts +1 -1
- package/dist/renderers/pdf.js +163 -15
- package/dist/renderers/typst.js +38 -54
- package/dist/renderers/wordDocx.js +132 -201
- package/dist/renderers/wordDocx.worker.js +2 -2
- package/package.json +8 -10
package/dist/renderers/pdf.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { getDocument, GlobalWorkerOptions, PDFWorker as PdfJsWorker, PixelsPerInch,
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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:
|
|
738
|
-
wasmUrl:
|
|
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);
|
package/dist/renderers/typst.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $typst } from '@myriaddreamin/typst.ts';
|
|
2
|
-
import {
|
|
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}
|
|
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 =
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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
|
-
|
|
491
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|