@file-viewer/core 2.0.7 → 2.0.9
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 -3
- package/dist/platform/assets.js +129 -7
- 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 +123 -62
- package/dist/renderers/wordDocx.js +118 -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
|
@@ -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
|
}
|
|
@@ -70,15 +61,7 @@ const getWindowOverride = (key) => {
|
|
|
70
61
|
}
|
|
71
62
|
return window[key];
|
|
72
63
|
};
|
|
73
|
-
const configureTypstEngine = (
|
|
74
|
-
var _a;
|
|
75
|
-
const typstOptions = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.typst;
|
|
76
|
-
const compilerWasmUrl = resolveFileViewerTypstCompilerWasmUrl(typstOptions, [
|
|
77
|
-
getWindowOverride('__FLYFISH_TYPST_COMPILER_WASM_URL__'),
|
|
78
|
-
], documentBaseUrl);
|
|
79
|
-
const rendererWasmUrl = resolveFileViewerTypstRendererWasmUrl(typstOptions, [
|
|
80
|
-
getWindowOverride('__FLYFISH_TYPST_RENDERER_WASM_URL__'),
|
|
81
|
-
], documentBaseUrl);
|
|
64
|
+
const configureTypstEngine = (compilerWasmUrl, rendererWasmUrl) => {
|
|
82
65
|
const configKey = `${compilerWasmUrl}\n${rendererWasmUrl}`;
|
|
83
66
|
if (typstEngineConfigKey === configKey) {
|
|
84
67
|
return;
|
|
@@ -91,6 +74,69 @@ const configureTypstEngine = (context, documentBaseUrl) => {
|
|
|
91
74
|
});
|
|
92
75
|
typstEngineConfigKey = configKey;
|
|
93
76
|
};
|
|
77
|
+
const pushUniqueTypstCandidate = (candidates, candidate) => {
|
|
78
|
+
if (candidates.some(item => item.compilerWasmUrl === candidate.compilerWasmUrl &&
|
|
79
|
+
item.rendererWasmUrl === candidate.rendererWasmUrl)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
candidates.push(candidate);
|
|
83
|
+
};
|
|
84
|
+
const resolveTypstEngineCandidates = (context, documentBaseUrl) => {
|
|
85
|
+
var _a;
|
|
86
|
+
const typstOptions = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.typst;
|
|
87
|
+
const compilerOverride = getWindowOverride('__FLYFISH_TYPST_COMPILER_WASM_URL__');
|
|
88
|
+
const rendererOverride = getWindowOverride('__FLYFISH_TYPST_RENDERER_WASM_URL__');
|
|
89
|
+
const compilerWasmUrl = resolveFileViewerTypstCompilerWasmUrl(typstOptions, [
|
|
90
|
+
compilerOverride,
|
|
91
|
+
], documentBaseUrl);
|
|
92
|
+
const rendererWasmUrl = resolveFileViewerTypstRendererWasmUrl(typstOptions, [
|
|
93
|
+
rendererOverride,
|
|
94
|
+
], documentBaseUrl);
|
|
95
|
+
const hasConfiguredAsset = Boolean((typstOptions === null || typstOptions === void 0 ? void 0 : typstOptions.compilerWasmUrl) ||
|
|
96
|
+
(typstOptions === null || typstOptions === void 0 ? void 0 : typstOptions.rendererWasmUrl) ||
|
|
97
|
+
compilerOverride ||
|
|
98
|
+
rendererOverride);
|
|
99
|
+
const candidates = [];
|
|
100
|
+
pushUniqueTypstCandidate(candidates, {
|
|
101
|
+
compilerWasmUrl,
|
|
102
|
+
rendererWasmUrl,
|
|
103
|
+
source: hasConfiguredAsset ? 'configured' : 'local',
|
|
104
|
+
preflight: !hasConfiguredAsset,
|
|
105
|
+
});
|
|
106
|
+
return candidates;
|
|
107
|
+
};
|
|
108
|
+
const isHttpUrl = (url) => /^https?:\/\//i.test(url);
|
|
109
|
+
const isKnownMissingWasmUrl = async (url) => {
|
|
110
|
+
if (typeof fetch !== 'function' || !isHttpUrl(url)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(url, {
|
|
115
|
+
cache: 'force-cache',
|
|
116
|
+
method: 'HEAD',
|
|
117
|
+
});
|
|
118
|
+
return response.status === 404 || response.status === 410;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
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
|
+
};
|
|
133
|
+
const isTypstAssetLoadError = (error) => {
|
|
134
|
+
if (Array.isArray(error)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
138
|
+
return /wasm|webassembly|fetch|module|instantiate|compile|network|404|410/i.test(message);
|
|
139
|
+
};
|
|
94
140
|
const escapeAttribute = (value) => {
|
|
95
141
|
return value.replace(/[&<>"']/g, char => {
|
|
96
142
|
const entities = {
|
|
@@ -184,6 +230,22 @@ const formatTypstError = (error) => {
|
|
|
184
230
|
}
|
|
185
231
|
return String(error);
|
|
186
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
|
+
};
|
|
187
249
|
const clampZoom = (value) => {
|
|
188
250
|
return Math.min(3, Math.max(0.3, Number(value.toFixed(2))));
|
|
189
251
|
};
|
|
@@ -308,7 +370,6 @@ export default async function renderTypst(buffer, target, _type, context) {
|
|
|
308
370
|
let state = 'loading';
|
|
309
371
|
let pages = [];
|
|
310
372
|
let errorMessage = '';
|
|
311
|
-
let sourceFallbackMessage = '';
|
|
312
373
|
let zoom = 1;
|
|
313
374
|
let renderToken = 0;
|
|
314
375
|
let disposed = false;
|
|
@@ -374,16 +435,6 @@ export default async function renderTypst(buffer, target, _type, context) {
|
|
|
374
435
|
error.append(createElement(documentRef, 'strong', undefined, 'Typst 渲染失败'), createElement(documentRef, 'pre', undefined, errorMessage));
|
|
375
436
|
body.replaceChildren(error);
|
|
376
437
|
};
|
|
377
|
-
const renderSourceFallback = () => {
|
|
378
|
-
const fallback = createElement(documentRef, 'main', 'typst-source-fallback');
|
|
379
|
-
fallback.setAttribute('aria-label', 'Typst source preview');
|
|
380
|
-
const header = createElement(documentRef, 'header');
|
|
381
|
-
header.append(createElement(documentRef, 'strong', undefined, '已切换为 Typst 源码预览'), createElement(documentRef, 'p', undefined, sourceFallbackMessage || '当前浏览器没有在预期时间内完成 Typst 编译,源码仍可完整查看。'));
|
|
382
|
-
const pre = createElement(documentRef, 'pre');
|
|
383
|
-
pre.textContent = source;
|
|
384
|
-
fallback.append(header, pre);
|
|
385
|
-
body.replaceChildren(fallback);
|
|
386
|
-
};
|
|
387
438
|
const renderPages = () => {
|
|
388
439
|
pageShells.clear();
|
|
389
440
|
const pagesRoot = createElement(documentRef, 'main', 'typst-pages');
|
|
@@ -403,49 +454,66 @@ export default async function renderTypst(buffer, target, _type, context) {
|
|
|
403
454
|
const syncUi = () => {
|
|
404
455
|
summary.textContent = state === 'ready'
|
|
405
456
|
? getPageSummary(pages)
|
|
406
|
-
:
|
|
407
|
-
? 'Typst source preview'
|
|
408
|
-
: 'Typst WASM renderer';
|
|
457
|
+
: 'Typst WASM renderer';
|
|
409
458
|
status.textContent = state === 'loading'
|
|
410
459
|
? '正在编译'
|
|
411
460
|
: state === 'error'
|
|
412
461
|
? '编译失败'
|
|
413
|
-
:
|
|
414
|
-
? '源码预览'
|
|
415
|
-
: '已渲染';
|
|
462
|
+
: '已渲染';
|
|
416
463
|
if (state === 'loading') {
|
|
417
464
|
renderLoading();
|
|
418
465
|
}
|
|
419
466
|
else if (state === 'error') {
|
|
420
467
|
renderError();
|
|
421
468
|
}
|
|
422
|
-
else if (state === 'source') {
|
|
423
|
-
renderSourceFallback();
|
|
424
|
-
}
|
|
425
469
|
else {
|
|
426
470
|
renderPages();
|
|
427
471
|
}
|
|
428
472
|
};
|
|
473
|
+
const renderTypstSvg = async () => {
|
|
474
|
+
var _a, _b;
|
|
475
|
+
const candidates = resolveTypstEngineCandidates(context, documentRef.baseURI);
|
|
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);
|
|
477
|
+
let lastError;
|
|
478
|
+
for (const candidate of candidates) {
|
|
479
|
+
const missingAsset = candidate.preflight
|
|
480
|
+
? await resolveKnownMissingTypstAsset(candidate)
|
|
481
|
+
: '';
|
|
482
|
+
if (missingAsset) {
|
|
483
|
+
lastError = new Error(missingAsset);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
configureTypstEngine(candidate.compilerWasmUrl, candidate.rendererWasmUrl);
|
|
488
|
+
return await withRenderTimeout($typst.svg({
|
|
489
|
+
mainContent: source,
|
|
490
|
+
data_selection: {
|
|
491
|
+
body: true,
|
|
492
|
+
defs: true,
|
|
493
|
+
css: true,
|
|
494
|
+
js: false,
|
|
495
|
+
},
|
|
496
|
+
}), timeoutMs);
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
lastError = error;
|
|
500
|
+
if (error instanceof TypstRenderTimeoutError || !isTypstAssetLoadError(error)) {
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
throw lastError instanceof Error ? lastError : new Error('Typst WASM 加载失败');
|
|
506
|
+
};
|
|
429
507
|
const render = async () => {
|
|
430
|
-
var _a, _b
|
|
508
|
+
var _a, _b;
|
|
431
509
|
const token = ++renderToken;
|
|
432
510
|
state = 'loading';
|
|
433
511
|
errorMessage = '';
|
|
434
|
-
sourceFallbackMessage = '';
|
|
435
512
|
pages = [];
|
|
436
513
|
(_a = context === null || context === void 0 ? void 0 : context.registerExportAdapter) === null || _a === void 0 ? void 0 : _a.call(context, null);
|
|
437
514
|
syncUi();
|
|
438
515
|
try {
|
|
439
|
-
|
|
440
|
-
const svg = await withRenderTimeout($typst.svg({
|
|
441
|
-
mainContent: source,
|
|
442
|
-
data_selection: {
|
|
443
|
-
body: true,
|
|
444
|
-
defs: true,
|
|
445
|
-
css: true,
|
|
446
|
-
js: false,
|
|
447
|
-
},
|
|
448
|
-
}), normalizeRenderTimeoutMs((_c = (_b = context === null || context === void 0 ? void 0 : context.options) === null || _b === void 0 ? void 0 : _b.typst) === null || _c === void 0 ? void 0 : _c.renderTimeoutMs));
|
|
516
|
+
const svg = await renderTypstSvg();
|
|
449
517
|
if (disposed || token !== renderToken) {
|
|
450
518
|
return;
|
|
451
519
|
}
|
|
@@ -453,20 +521,13 @@ export default async function renderTypst(buffer, target, _type, context) {
|
|
|
453
521
|
state = 'ready';
|
|
454
522
|
syncUi();
|
|
455
523
|
registerExportAdapter();
|
|
456
|
-
(
|
|
524
|
+
(_b = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _b === void 0 ? void 0 : _b.call(context);
|
|
457
525
|
}
|
|
458
526
|
catch (error) {
|
|
459
527
|
if (disposed || token !== renderToken) {
|
|
460
528
|
return;
|
|
461
529
|
}
|
|
462
|
-
|
|
463
|
-
sourceFallbackMessage = error.message;
|
|
464
|
-
state = 'source';
|
|
465
|
-
syncUi();
|
|
466
|
-
(_e = context === null || context === void 0 ? void 0 : context.onProgressiveRender) === null || _e === void 0 ? void 0 : _e.call(context);
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
errorMessage = formatTypstError(error);
|
|
530
|
+
errorMessage = formatTypstRuntimeError(error);
|
|
470
531
|
state = 'error';
|
|
471
532
|
syncUi();
|
|
472
533
|
}
|