@file-viewer/core 2.0.11 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/config/options.d.ts +1 -1
  4. package/dist/config/options.js +1 -1
  5. package/dist/contracts/types.d.ts +71 -1
  6. package/dist/headless.d.ts +2 -2
  7. package/dist/headless.js +1 -1
  8. package/dist/index.d.ts +9 -6
  9. package/dist/index.js +105 -48
  10. package/dist/platform/assets.d.ts +3 -1
  11. package/dist/platform/assets.js +18 -2
  12. package/dist/registry/capabilities.d.ts +2 -2
  13. package/dist/registry/capabilities.js +2 -1
  14. package/dist/registry/formats.d.ts +20 -7
  15. package/dist/registry/formats.js +14 -5
  16. package/dist/registry/registry.d.ts +8 -1
  17. package/dist/registry/registry.js +29 -0
  18. package/dist/renderers/image.js +1 -10
  19. package/dist/renderers/index.d.ts +320 -2
  20. package/dist/renderers/index.js +27 -157
  21. package/dist/viewer/createViewer.js +86 -3
  22. package/package.json +17 -44
  23. package/dist/renderers/archive.d.ts +0 -2
  24. package/dist/renderers/archive.js +0 -547
  25. package/dist/renderers/archiveCache.d.ts +0 -10
  26. package/dist/renderers/archiveCache.js +0 -96
  27. package/dist/renderers/archiveFallback.d.ts +0 -7
  28. package/dist/renderers/archiveFallback.js +0 -166
  29. package/dist/renderers/archiveShared.d.ts +0 -23
  30. package/dist/renderers/archiveShared.js +0 -71
  31. package/dist/renderers/audio.d.ts +0 -8
  32. package/dist/renderers/audio.js +0 -219
  33. package/dist/renderers/cad.d.ts +0 -2
  34. package/dist/renderers/cad.js +0 -446
  35. package/dist/renderers/code.d.ts +0 -11
  36. package/dist/renderers/code.js +0 -233
  37. package/dist/renderers/data.d.ts +0 -7
  38. package/dist/renderers/data.js +0 -370
  39. package/dist/renderers/drawing.d.ts +0 -10
  40. package/dist/renderers/drawing.js +0 -882
  41. package/dist/renderers/eda.d.ts +0 -2
  42. package/dist/renderers/eda.js +0 -434
  43. package/dist/renderers/edaParser.d.ts +0 -77
  44. package/dist/renderers/edaParser.js +0 -569
  45. package/dist/renderers/email.d.ts +0 -2
  46. package/dist/renderers/email.js +0 -463
  47. package/dist/renderers/epub.d.ts +0 -2
  48. package/dist/renderers/epub.js +0 -331
  49. package/dist/renderers/geo.d.ts +0 -2
  50. package/dist/renderers/geo.js +0 -284
  51. package/dist/renderers/markdown.d.ts +0 -2
  52. package/dist/renderers/markdown.js +0 -83
  53. package/dist/renderers/model.d.ts +0 -2
  54. package/dist/renderers/model.js +0 -567
  55. package/dist/renderers/ofd.d.ts +0 -2
  56. package/dist/renderers/ofd.js +0 -256
  57. package/dist/renderers/openDocument.d.ts +0 -2
  58. package/dist/renderers/openDocument.js +0 -122
  59. package/dist/renderers/pdf.d.ts +0 -3
  60. package/dist/renderers/pdf.js +0 -1001
  61. package/dist/renderers/pdfStyles.d.ts +0 -1
  62. package/dist/renderers/pdfStyles.js +0 -1
  63. package/dist/renderers/pptx.d.ts +0 -2
  64. package/dist/renderers/pptx.js +0 -217
  65. package/dist/renderers/spreadsheet/state.d.ts +0 -80
  66. package/dist/renderers/spreadsheet/state.js +0 -96
  67. package/dist/renderers/spreadsheet/view.d.ts +0 -25
  68. package/dist/renderers/spreadsheet/view.js +0 -833
  69. package/dist/renderers/spreadsheet/worker/index.d.ts +0 -2
  70. package/dist/renderers/spreadsheet/worker/index.js +0 -1
  71. package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +0 -73
  72. package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +0 -623
  73. package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +0 -2
  74. package/dist/renderers/spreadsheet/worker/sheetjs/color.js +0 -73
  75. package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +0 -1
  76. package/dist/renderers/spreadsheet/worker/sheetjs/index.js +0 -1
  77. package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +0 -18
  78. package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +0 -106
  79. package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +0 -1
  80. package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +0 -11
  81. package/dist/renderers/spreadsheet/worker/type.d.ts +0 -57
  82. package/dist/renderers/spreadsheet/worker/type.js +0 -1
  83. package/dist/renderers/spreadsheet.d.ts +0 -3
  84. package/dist/renderers/spreadsheet.js +0 -929
  85. package/dist/renderers/typst.d.ts +0 -8
  86. package/dist/renderers/typst.js +0 -547
  87. package/dist/renderers/umd/parser.d.ts +0 -30
  88. package/dist/renderers/umd/parser.js +0 -408
  89. package/dist/renderers/umd.d.ts +0 -2
  90. package/dist/renderers/umd.js +0 -297
  91. package/dist/renderers/video.d.ts +0 -8
  92. package/dist/renderers/video.js +0 -108
  93. package/dist/renderers/wordDoc.d.ts +0 -5
  94. package/dist/renderers/wordDoc.js +0 -284
  95. package/dist/renderers/wordDocx.d.ts +0 -5
  96. package/dist/renderers/wordDocx.js +0 -985
  97. package/dist/renderers/wordDocx.worker.d.ts +0 -1
  98. package/dist/renderers/wordDocx.worker.js +0 -96
  99. package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +0 -183
  100. package/vendor/ofd/dltech/jbig2/ccitt.js +0 -1070
  101. package/vendor/ofd/dltech/jbig2/compatibility.js +0 -12
  102. package/vendor/ofd/dltech/jbig2/core_utils.js +0 -180
  103. package/vendor/ofd/dltech/jbig2/is_node.js +0 -27
  104. package/vendor/ofd/dltech/jbig2/jbig2.js +0 -2589
  105. package/vendor/ofd/dltech/jbig2/jbig2_stream.js +0 -81
  106. package/vendor/ofd/dltech/jbig2/primitives.js +0 -387
  107. package/vendor/ofd/dltech/jbig2/stream.js +0 -1348
  108. package/vendor/ofd/dltech/jbig2/util.js +0 -972
  109. package/vendor/ofd/dltech/ofd/ofd.d.ts +0 -11
  110. package/vendor/ofd/dltech/ofd/ofd.js +0 -100
  111. package/vendor/ofd/dltech/ofd/ofd_parser.js +0 -395
  112. package/vendor/ofd/dltech/ofd/ofd_render.js +0 -473
  113. package/vendor/ofd/dltech/ofd/ofd_util.js +0 -350
  114. package/vendor/ofd/dltech/ofd/pipeline.js +0 -26
@@ -1,331 +0,0 @@
1
- const epubStyle = `
2
- .epub-viewer{width:100%;height:100%;display:flex;flex-direction:column;overflow:hidden;background:#eef1f4;color:#172033;box-sizing:border-box}
3
- .epub-viewer *{box-sizing:border-box}
4
- .epub-toolbar{flex-shrink:0;display:grid;grid-template-columns:40px minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;border-bottom:1px solid rgba(15,23,42,.08);background:rgba(255,255,255,.92)}
5
- .epub-title{min-width:0;display:flex;flex-direction:column;gap:3px}
6
- .epub-title strong,.epub-title span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
7
- .epub-title strong{font-size:14px}
8
- .epub-title span{color:#64748b;font-size:12px}
9
- .epub-icon-button,.epub-button{height:36px;border:1px solid rgba(15,23,42,.08);background:#fff;color:#172033;font:inherit;cursor:pointer}
10
- .epub-icon-button{width:40px;display:inline-flex;align-items:center;justify-content:center;border-radius:8px}
11
- .epub-icon-button span,.epub-icon-button span::before,.epub-icon-button span::after{width:16px;height:2px;display:block;border-radius:999px;background:currentColor}
12
- .epub-icon-button span{position:relative}
13
- .epub-icon-button span::before,.epub-icon-button span::after{content:'';position:absolute;left:0}
14
- .epub-icon-button span::before{top:-5px}
15
- .epub-icon-button span::after{top:5px}
16
- .epub-icon-button.active{border-color:rgba(37,99,235,.24);background:rgba(37,99,235,.08);color:#1d4ed8}
17
- .epub-actions{display:flex;align-items:center;gap:8px}
18
- .epub-button{min-width:68px;padding:0 12px;border-radius:8px;font-size:13px;font-weight:700}
19
- .epub-button:disabled{color:#94a3b8;cursor:not-allowed}
20
- .epub-progress{min-width:58px;color:#64748b;font-size:12px;text-align:center}
21
- .epub-body{flex:1;min-height:0;display:grid;grid-template-columns:minmax(180px,240px) minmax(0,1fr)}
22
- .epub-viewer--toc-hidden .epub-body{grid-template-columns:minmax(0,1fr)}
23
- .epub-toc{min-width:0;min-height:0;display:flex;flex-direction:column;border-right:1px solid rgba(15,23,42,.08);background:rgba(255,255,255,.8)}
24
- .epub-toc[hidden]{display:none}
25
- .epub-toc-head{flex-shrink:0;display:flex;justify-content:space-between;gap:8px;padding:12px;color:#172033;font-size:13px}
26
- .epub-toc-head span{color:#64748b}
27
- .epub-toc-list{flex:1;min-height:0;overflow:auto;padding:0 8px 10px}
28
- .epub-toc-item{width:100%;min-height:34px;border:0;border-radius:8px;background:transparent;color:#475569;font:inherit;font-size:12px;text-align:left;cursor:pointer}
29
- .epub-toc-item:hover,.epub-toc-item.active{background:rgba(37,99,235,.08);color:#1d4ed8}
30
- .epub-stage-wrap{position:relative;min-width:0;min-height:0;padding:18px;overflow:hidden}
31
- .epub-stage{width:100%;height:100%;overflow-x:hidden;overflow-y:auto;border-radius:8px;background:#fff;box-shadow:0 18px 45px rgba(15,23,42,.12),inset 0 0 0 1px rgba(15,23,42,.06)}
32
- .epub-stage .epub-container{width:100%!important;max-width:100%;overflow-x:hidden!important;overflow-y:auto!important}
33
- .epub-stage iframe{max-width:100%}
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}
36
- .epub-state.error{color:#b42318}
37
- .file-viewer[data-viewer-theme='dark'] .epub-viewer{background:#172033;color:#e5eef8}
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}
39
- @media (prefers-color-scheme:dark){.file-viewer[data-viewer-theme='system'] .epub-viewer{background:#172033;color:#e5eef8}.file-viewer[data-viewer-theme='system'] .epub-toolbar,.file-viewer[data-viewer-theme='system'] .epub-toc,.file-viewer[data-viewer-theme='system'] .epub-stage{background:#fff;color:#172033}}
40
- @media (max-width:720px){.epub-toolbar{grid-template-columns:40px minmax(0,1fr)}.epub-actions{grid-column:1/-1;justify-content:space-between}.epub-body{position:relative;grid-template-columns:minmax(0,1fr)}.epub-toc{position:absolute;z-index:5;top:0;bottom:0;left:0;width:min(82vw,280px);box-shadow:18px 0 40px rgba(15,23,42,.16)}.epub-stage-wrap{padding:12px}}
41
- `;
42
- const createStyle = () => {
43
- const style = document.createElement('style');
44
- style.textContent = epubStyle;
45
- return style;
46
- };
47
- const createElement = (tagName, className, text) => {
48
- const element = document.createElement(tagName);
49
- if (className) {
50
- element.className = className;
51
- }
52
- if (text !== undefined) {
53
- element.textContent = text;
54
- }
55
- return element;
56
- };
57
- const normalizeLabel = (value, fallback) => {
58
- if (typeof value === 'string' && value.trim()) {
59
- return value.trim();
60
- }
61
- return fallback;
62
- };
63
- const flattenToc = (items, depth = 0) => {
64
- if (!Array.isArray(items)) {
65
- return [];
66
- }
67
- return items.flatMap((item, index) => {
68
- const node = item;
69
- const href = typeof node.href === 'string' ? node.href : '';
70
- const label = normalizeLabel(node.label || node.title, `章节 ${index + 1}`);
71
- const subitems = flattenToc(node.subitems || node.children, depth + 1);
72
- if (!href) {
73
- return subitems;
74
- }
75
- return [{
76
- depth,
77
- href,
78
- id: `${depth}-${index}-${href}`,
79
- label,
80
- }, ...subitems];
81
- });
82
- };
83
- const pickInitialHref = (items) => {
84
- var _a;
85
- const chapterLike = items.find(item => {
86
- const label = item.label.toLowerCase();
87
- return /(^|\s)(chapter|part|book|prologue|preface|introduction)\b/.test(label)
88
- || /第[一二三四五六七八九十百千0-9]+[章节回部卷篇]/.test(item.label);
89
- });
90
- if (chapterLike) {
91
- return chapterLike.href;
92
- }
93
- const readable = items.find(item => {
94
- const text = `${item.label} ${item.href}`.toLowerCase();
95
- return !/(cover|titlepage|title-page|copyright|license|toc|contents|nav|table-of-contents|wrap0000)/.test(text);
96
- });
97
- return (readable === null || readable === void 0 ? void 0 : readable.href) || ((_a = items[0]) === null || _a === void 0 ? void 0 : _a.href);
98
- };
99
- export default async function renderEpub(buffer, target) {
100
- let book;
101
- let rendition;
102
- let disposed = false;
103
- let tocOpen = true;
104
- let status = 'loading';
105
- let title = 'EPUB 电子书';
106
- let author = '';
107
- let tocItems = [];
108
- let currentHref = '';
109
- let progress = null;
110
- let atStart = true;
111
- let atEnd = false;
112
- const timers = new Set();
113
- const cleanups = [];
114
- const root = createElement('div', 'epub-viewer');
115
- const toolbar = createElement('div', 'epub-toolbar');
116
- const tocButton = createElement('button', 'epub-icon-button');
117
- tocButton.type = 'button';
118
- tocButton.title = '目录';
119
- tocButton.append(createElement('span'));
120
- const titleRoot = createElement('div', 'epub-title');
121
- const titleText = createElement('strong', undefined, title);
122
- const subtitleText = createElement('span', undefined, '阅读中');
123
- titleRoot.append(titleText, subtitleText);
124
- const actions = createElement('div', 'epub-actions');
125
- const prevButton = createElement('button', 'epub-button', '上一页');
126
- const progressText = createElement('span', 'epub-progress', '阅读中');
127
- const nextButton = createElement('button', 'epub-button', '下一页');
128
- prevButton.type = 'button';
129
- nextButton.type = 'button';
130
- actions.append(prevButton, progressText, nextButton);
131
- toolbar.append(tocButton, titleRoot, actions);
132
- const body = createElement('div', 'epub-body');
133
- const toc = createElement('aside', 'epub-toc');
134
- const tocHead = createElement('div', 'epub-toc-head');
135
- const tocCount = createElement('span', undefined, '0 项');
136
- tocHead.append(createElement('strong', undefined, '目录'), tocCount);
137
- const tocList = createElement('div', 'epub-toc-list');
138
- toc.append(tocHead, tocList);
139
- const stageWrap = createElement('main', 'epub-stage-wrap');
140
- const stage = createElement('div', 'epub-stage');
141
- const state = createElement('div', 'epub-state', '正在解析 EPUB...');
142
- stageWrap.append(stage, state);
143
- body.append(toc, stageWrap);
144
- root.append(toolbar, body);
145
- target.replaceChildren(createStyle(), root);
146
- const listen = (element, event, listener) => {
147
- element.addEventListener(event, listener);
148
- cleanups.push(() => element.removeEventListener(event, listener));
149
- };
150
- const currentChapter = () => {
151
- var _a;
152
- if (!currentHref) {
153
- return '';
154
- }
155
- const exact = tocItems.find(item => item.href === currentHref);
156
- if (exact) {
157
- return exact.label;
158
- }
159
- return ((_a = tocItems.find(item => currentHref.includes(item.href.split('#')[0]))) === null || _a === void 0 ? void 0 : _a.label) || '';
160
- };
161
- const progressLabel = () => {
162
- if (typeof progress === 'number') {
163
- return `${progress}%`;
164
- }
165
- return currentChapter() || '阅读中';
166
- };
167
- const syncUi = () => {
168
- root.classList.toggle('epub-viewer--toc-hidden', !tocOpen);
169
- toc.hidden = !tocOpen;
170
- tocButton.classList.toggle('active', tocOpen);
171
- titleText.textContent = title;
172
- subtitleText.textContent = author || progressLabel();
173
- progressText.textContent = progressLabel();
174
- prevButton.disabled = status !== 'ready' || atStart;
175
- nextButton.disabled = status !== 'ready' || atEnd;
176
- state.hidden = status === 'ready';
177
- state.classList.toggle('error', status === 'error');
178
- tocCount.textContent = `${tocItems.length} 项`;
179
- Array.from(tocList.querySelectorAll('.epub-toc-item')).forEach(button => {
180
- button.classList.toggle('active', button.dataset.href === currentHref);
181
- });
182
- };
183
- const renderToc = () => {
184
- tocList.replaceChildren();
185
- tocItems.forEach(item => {
186
- const button = createElement('button', 'epub-toc-item', item.label);
187
- button.type = 'button';
188
- button.dataset.href = item.href;
189
- button.style.paddingLeft = `${12 + item.depth * 14}px`;
190
- listen(button, 'click', () => {
191
- void (rendition === null || rendition === void 0 ? void 0 : rendition.display(item.href));
192
- tocOpen = false;
193
- syncUi();
194
- });
195
- tocList.append(button);
196
- });
197
- syncUi();
198
- };
199
- const updateLocation = (epubLocation) => {
200
- var _a, _b;
201
- atStart = Boolean(epubLocation === null || epubLocation === void 0 ? void 0 : epubLocation.atStart);
202
- atEnd = Boolean(epubLocation === null || epubLocation === void 0 ? void 0 : epubLocation.atEnd);
203
- currentHref = ((_a = epubLocation === null || epubLocation === void 0 ? void 0 : epubLocation.start) === null || _a === void 0 ? void 0 : _a.href) || '';
204
- if (typeof ((_b = epubLocation === null || epubLocation === void 0 ? void 0 : epubLocation.start) === null || _b === void 0 ? void 0 : _b.percentage) === 'number') {
205
- progress = Math.round(epubLocation.start.percentage * 100);
206
- }
207
- syncUi();
208
- };
209
- const wait = (ms) => new Promise(resolve => {
210
- const timer = window.setTimeout(() => {
211
- timers.delete(timer);
212
- resolve(undefined);
213
- }, ms);
214
- timers.add(timer);
215
- });
216
- const hasReadableFrame = () => {
217
- var _a;
218
- try {
219
- const iframe = stage.querySelector('iframe');
220
- const frameBody = (_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body;
221
- if (!frameBody) {
222
- return false;
223
- }
224
- return Boolean(frameBody.innerText.trim() || frameBody.querySelector('img, svg, canvas'));
225
- }
226
- catch {
227
- return false;
228
- }
229
- };
230
- const waitForReadableFrame = async () => {
231
- for (let index = 0; index < 20; index += 1) {
232
- if (disposed || hasReadableFrame()) {
233
- return hasReadableFrame();
234
- }
235
- await wait(100);
236
- }
237
- return hasReadableFrame();
238
- };
239
- const openBook = async () => {
240
- status = 'loading';
241
- state.textContent = '正在解析 EPUB...';
242
- syncUi();
243
- try {
244
- const { default: ePub } = await import('epubjs');
245
- if (disposed) {
246
- return;
247
- }
248
- book = ePub(buffer.slice(0), {
249
- openAs: 'binary',
250
- replacements: 'blobUrl',
251
- });
252
- rendition = book.renderTo(stage, {
253
- allowScriptedContent: false,
254
- flow: 'scrolled',
255
- height: '100%',
256
- manager: 'continuous',
257
- resizeOnOrientationChange: true,
258
- spread: 'none',
259
- width: '100%',
260
- });
261
- rendition.themes.default({
262
- body: {
263
- color: '#172033',
264
- fontFamily: 'Georgia, "Times New Roman", serif',
265
- lineHeight: '1.72',
266
- padding: '0 8px',
267
- },
268
- img: {
269
- maxWidth: '100%',
270
- },
271
- html: {
272
- height: 'auto',
273
- overflow: 'auto',
274
- },
275
- });
276
- rendition.on('relocated', updateLocation);
277
- await book.ready;
278
- const metadata = await book.loaded.metadata.catch(() => undefined);
279
- title = normalizeLabel(metadata === null || metadata === void 0 ? void 0 : metadata.title, title);
280
- author = normalizeLabel(metadata === null || metadata === void 0 ? void 0 : metadata.creator, '');
281
- const navigation = await book.loaded.navigation.catch(() => undefined);
282
- tocItems = flattenToc(navigation === null || navigation === void 0 ? void 0 : navigation.toc);
283
- renderToc();
284
- await rendition.display(pickInitialHref(tocItems));
285
- if (!await waitForReadableFrame()) {
286
- throw new Error('EPUB 正文渲染未完成,请刷新后重试');
287
- }
288
- if (disposed) {
289
- return;
290
- }
291
- status = 'ready';
292
- syncUi();
293
- void book.locations.generate(1200).catch(() => undefined);
294
- }
295
- catch (error) {
296
- console.error(error);
297
- status = 'error';
298
- state.textContent = error instanceof Error ? error.message : String(error);
299
- syncUi();
300
- }
301
- };
302
- listen(tocButton, 'click', () => {
303
- tocOpen = !tocOpen;
304
- syncUi();
305
- });
306
- listen(prevButton, 'click', () => {
307
- void (rendition === null || rendition === void 0 ? void 0 : rendition.prev());
308
- });
309
- listen(nextButton, 'click', () => {
310
- void (rendition === null || rendition === void 0 ? void 0 : rendition.next());
311
- });
312
- syncUi();
313
- void openBook();
314
- return {
315
- $el: root,
316
- unmount() {
317
- disposed = true;
318
- timers.forEach(timer => window.clearTimeout(timer));
319
- timers.clear();
320
- if (rendition) {
321
- rendition.off('relocated', updateLocation);
322
- rendition.destroy();
323
- rendition = undefined;
324
- }
325
- book === null || book === void 0 ? void 0 : book.destroy();
326
- book = undefined;
327
- cleanups.splice(0).forEach(cleanup => cleanup());
328
- target.replaceChildren();
329
- },
330
- };
331
- }
@@ -1,2 +0,0 @@
1
- import type { FileViewerRenderedInstance } from '../contracts/types';
2
- export default function renderGeo(buffer: ArrayBuffer, target: HTMLDivElement, type?: string): Promise<FileViewerRenderedInstance>;
@@ -1,284 +0,0 @@
1
- import { readFileViewerText } from '../source/index.js';
2
- const SVG_NS = 'http://www.w3.org/2000/svg';
3
- const GEO_WIDTH = 960;
4
- const GEO_HEIGHT = 620;
5
- const GEO_PADDING = 36;
6
- const geoStyle = `
7
- .geo-viewer{min-height:100%;display:grid;grid-template-columns:minmax(240px,320px) minmax(0,1fr);background:#eef1f4;color:#132235}
8
- .geo-panel{padding:24px;border-right:1px solid rgba(15,23,42,.08);background:#fff;box-sizing:border-box}
9
- .geo-panel>span{color:#0f766e;font-size:12px;font-weight:800}.geo-panel h2{margin:8px 0 22px;font-size:24px}
10
- .geo-panel dl{display:grid;gap:12px;margin:0}.geo-panel dt,.geo-counts strong{color:#64748b;font-size:12px;font-weight:700}.geo-panel dd{margin:4px 0 0;word-break:break-all;font-size:14px}
11
- .geo-counts{margin-top:24px}.geo-counts p{margin:8px 0 0;font-size:14px}
12
- .geo-map{padding:28px;overflow:auto}.geo-map svg{display:block;width:min(100%,1200px);min-width:640px;height:auto;margin:0 auto;overflow:visible}
13
- .geo-map rect{fill:#f8fafc;stroke:rgba(15,23,42,.08)}
14
- .geo-map path{fill:none;stroke:#0f766e;stroke-width:2.2;vector-effect:non-scaling-stroke}.geo-map .geo-polygon{fill:rgba(45,212,191,.18)}
15
- .geo-map circle{fill:#2563eb;stroke:#fff;stroke-width:1.5;vector-effect:non-scaling-stroke}
16
- .geo-state{display:flex;align-items:center;justify-content:center;min-height:280px;border-radius:8px;background:rgba(255,255,255,.82);color:#64748b}
17
- .geo-state.error{color:#b42318}
18
- .file-viewer[data-viewer-theme='dark'] .geo-viewer{background:#101820;color:#e5edf6}.file-viewer[data-viewer-theme='dark'] .geo-panel{background:#111827;border-color:rgba(148,163,184,.18)}.file-viewer[data-viewer-theme='dark'] .geo-panel dt,.file-viewer[data-viewer-theme='dark'] .geo-counts strong{color:#94a3b8}.file-viewer[data-viewer-theme='dark'] .geo-map rect{fill:#111827;stroke:rgba(148,163,184,.18)}
19
- @media (prefers-color-scheme:dark){.file-viewer[data-viewer-theme='system'] .geo-viewer{background:#101820;color:#e5edf6}.file-viewer[data-viewer-theme='system'] .geo-panel{background:#111827;border-color:rgba(148,163,184,.18)}.file-viewer[data-viewer-theme='system'] .geo-panel dt,.file-viewer[data-viewer-theme='system'] .geo-counts strong{color:#94a3b8}.file-viewer[data-viewer-theme='system'] .geo-map rect{fill:#111827;stroke:rgba(148,163,184,.18)}}
20
- @media (max-width:860px){.geo-viewer{grid-template-columns:1fr}.geo-panel{border-right:0;border-bottom:1px solid rgba(15,23,42,.08)}}
21
- `;
22
- const createStyle = () => {
23
- const style = document.createElement('style');
24
- style.textContent = geoStyle;
25
- return style;
26
- };
27
- const createSvgElement = (tag) => {
28
- return document.createElementNS(SVG_NS, tag);
29
- };
30
- const normalizeGeoJson = (value) => {
31
- const candidate = value;
32
- if ((candidate === null || candidate === void 0 ? void 0 : candidate.type) === 'FeatureCollection' && Array.isArray(candidate.features)) {
33
- return candidate;
34
- }
35
- if ((candidate === null || candidate === void 0 ? void 0 : candidate.type) === 'Feature') {
36
- return { type: 'FeatureCollection', features: [candidate] };
37
- }
38
- if ((candidate === null || candidate === void 0 ? void 0 : candidate.type) && candidate.coordinates) {
39
- return {
40
- type: 'FeatureCollection',
41
- features: [{ type: 'Feature', geometry: candidate, properties: {} }],
42
- };
43
- }
44
- throw new Error('无法识别的 GeoJSON 数据');
45
- };
46
- const parseXml = (text) => {
47
- const doc = new DOMParser().parseFromString(text, 'application/xml');
48
- const error = doc.querySelector('parsererror');
49
- if (error) {
50
- throw new Error(error.textContent || 'XML 解析失败');
51
- }
52
- return doc;
53
- };
54
- const normalizeGeoType = (type) => {
55
- const normalized = (type || 'geojson').trim().toLowerCase();
56
- if (normalized === 'json') {
57
- return 'geojson';
58
- }
59
- if (normalized === 'shapefile') {
60
- return 'shp';
61
- }
62
- return normalized;
63
- };
64
- const parseGeo = async (buffer, type) => {
65
- if (type === 'geojson') {
66
- return normalizeGeoJson(JSON.parse(await readFileViewerText(buffer)));
67
- }
68
- if (type === 'kml' || type === 'gpx') {
69
- const toGeoJSON = await import('@tmcw/togeojson');
70
- const doc = parseXml(await readFileViewerText(buffer));
71
- return normalizeGeoJson(type === 'kml' ? toGeoJSON.kml(doc) : toGeoJSON.gpx(doc));
72
- }
73
- if (type === 'shp') {
74
- const { default: shp } = await import('shpjs');
75
- return normalizeGeoJson(await shp(buffer));
76
- }
77
- throw new Error(`不支持 .${type} 地理格式`);
78
- };
79
- const walkPositions = (geometry, visit) => {
80
- if (!geometry) {
81
- return;
82
- }
83
- switch (geometry.type) {
84
- case 'Point':
85
- visit(geometry.coordinates);
86
- break;
87
- case 'MultiPoint':
88
- case 'LineString':
89
- geometry.coordinates.forEach(visit);
90
- break;
91
- case 'MultiLineString':
92
- case 'Polygon':
93
- geometry.coordinates.forEach(line => line.forEach(visit));
94
- break;
95
- case 'MultiPolygon':
96
- geometry.coordinates.forEach(polygon => polygon.forEach(line => line.forEach(visit)));
97
- break;
98
- case 'GeometryCollection':
99
- geometry.geometries.forEach(item => walkPositions(item, visit));
100
- break;
101
- }
102
- };
103
- const collectBounds = (features) => {
104
- let minX = Infinity;
105
- let minY = Infinity;
106
- let maxX = -Infinity;
107
- let maxY = -Infinity;
108
- features.forEach(feature => {
109
- walkPositions(feature.geometry, position => {
110
- const [x, y] = position;
111
- if (!Number.isFinite(x) || !Number.isFinite(y)) {
112
- return;
113
- }
114
- minX = Math.min(minX, x);
115
- minY = Math.min(minY, y);
116
- maxX = Math.max(maxX, x);
117
- maxY = Math.max(maxY, y);
118
- });
119
- });
120
- if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
121
- return null;
122
- }
123
- return { minX, minY, maxX, maxY };
124
- };
125
- const projectPosition = (position, bounds) => {
126
- if (!bounds) {
127
- return [GEO_WIDTH / 2, GEO_HEIGHT / 2];
128
- }
129
- const xRange = Math.max(1e-9, bounds.maxX - bounds.minX);
130
- const yRange = Math.max(1e-9, bounds.maxY - bounds.minY);
131
- const scale = Math.min((GEO_WIDTH - GEO_PADDING * 2) / xRange, (GEO_HEIGHT - GEO_PADDING * 2) / yRange);
132
- const xOffset = (GEO_WIDTH - xRange * scale) / 2;
133
- const yOffset = (GEO_HEIGHT - yRange * scale) / 2;
134
- const x = xOffset + (position[0] - bounds.minX) * scale;
135
- const y = GEO_HEIGHT - (yOffset + (position[1] - bounds.minY) * scale);
136
- return [Number(x.toFixed(2)), Number(y.toFixed(2))];
137
- };
138
- const pathLine = (positions, bounds, close = false) => {
139
- const points = positions
140
- .filter(position => Number.isFinite(position[0]) && Number.isFinite(position[1]))
141
- .map(position => projectPosition(position, bounds));
142
- if (!points.length) {
143
- return '';
144
- }
145
- const [first, ...rest] = points;
146
- const body = [`M${first[0]} ${first[1]}`, ...rest.map(point => `L${point[0]} ${point[1]}`)];
147
- if (close) {
148
- body.push('Z');
149
- }
150
- return body.join(' ');
151
- };
152
- const appendGeometry = (parent, geometry, bounds) => {
153
- if (!geometry) {
154
- return;
155
- }
156
- switch (geometry.type) {
157
- case 'Point': {
158
- const [x, y] = projectPosition(geometry.coordinates, bounds);
159
- const circle = createSvgElement('circle');
160
- circle.setAttribute('cx', String(x));
161
- circle.setAttribute('cy', String(y));
162
- circle.setAttribute('r', '4');
163
- parent.appendChild(circle);
164
- break;
165
- }
166
- case 'MultiPoint':
167
- geometry.coordinates.forEach(position => appendGeometry(parent, { type: 'Point', coordinates: position }, bounds));
168
- break;
169
- case 'LineString': {
170
- const path = createSvgElement('path');
171
- path.setAttribute('d', pathLine(geometry.coordinates, bounds));
172
- parent.appendChild(path);
173
- break;
174
- }
175
- case 'MultiLineString':
176
- geometry.coordinates.forEach(line => appendGeometry(parent, { type: 'LineString', coordinates: line }, bounds));
177
- break;
178
- case 'Polygon':
179
- geometry.coordinates.forEach(line => {
180
- const path = createSvgElement('path');
181
- path.classList.add('geo-polygon');
182
- path.setAttribute('d', pathLine(line, bounds, true));
183
- parent.appendChild(path);
184
- });
185
- break;
186
- case 'MultiPolygon':
187
- geometry.coordinates.forEach(polygon => {
188
- appendGeometry(parent, { type: 'Polygon', coordinates: polygon }, bounds);
189
- });
190
- break;
191
- case 'GeometryCollection':
192
- geometry.geometries.forEach(item => appendGeometry(parent, item, bounds));
193
- break;
194
- }
195
- };
196
- const buildGeometryCounts = (features) => {
197
- const counts = new Map();
198
- features.forEach(feature => {
199
- var _a;
200
- const key = ((_a = feature.geometry) === null || _a === void 0 ? void 0 : _a.type) || 'Null';
201
- counts.set(key, (counts.get(key) || 0) + 1);
202
- });
203
- return [...counts.entries()];
204
- };
205
- const formatBounds = (bounds) => {
206
- if (!bounds) {
207
- return '-';
208
- }
209
- return `${bounds.minX.toFixed(5)}, ${bounds.minY.toFixed(5)} -> ${bounds.maxX.toFixed(5)}, ${bounds.maxY.toFixed(5)}`;
210
- };
211
- const appendDescriptionItem = (list, label, value) => {
212
- const row = document.createElement('div');
213
- const term = document.createElement('dt');
214
- term.textContent = label;
215
- const detail = document.createElement('dd');
216
- detail.textContent = String(value);
217
- row.append(term, detail);
218
- list.appendChild(row);
219
- };
220
- const renderCollection = (collection, type) => {
221
- const root = document.createElement('div');
222
- root.className = 'geo-viewer';
223
- const features = collection.features || [];
224
- const bounds = collectBounds(features);
225
- const panel = document.createElement('aside');
226
- panel.className = 'geo-panel';
227
- const badge = document.createElement('span');
228
- badge.textContent = type.toUpperCase();
229
- const heading = document.createElement('h2');
230
- heading.textContent = '地理数据预览';
231
- const description = document.createElement('dl');
232
- appendDescriptionItem(description, '要素数', features.length);
233
- appendDescriptionItem(description, '范围', formatBounds(bounds));
234
- const counts = document.createElement('div');
235
- counts.className = 'geo-counts';
236
- const countsHeading = document.createElement('strong');
237
- countsHeading.textContent = '几何类型';
238
- counts.appendChild(countsHeading);
239
- buildGeometryCounts(features).forEach(([name, count]) => {
240
- const row = document.createElement('p');
241
- row.textContent = `${name}: ${count}`;
242
- counts.appendChild(row);
243
- });
244
- panel.append(badge, heading, description, counts);
245
- const map = document.createElement('main');
246
- map.className = 'geo-map';
247
- const svg = createSvgElement('svg');
248
- svg.setAttribute('viewBox', `0 0 ${GEO_WIDTH} ${GEO_HEIGHT}`);
249
- svg.setAttribute('role', 'img');
250
- svg.setAttribute('aria-label', '地理数据 SVG 预览');
251
- const rect = createSvgElement('rect');
252
- rect.setAttribute('width', String(GEO_WIDTH));
253
- rect.setAttribute('height', String(GEO_HEIGHT));
254
- rect.setAttribute('rx', '8');
255
- const group = createSvgElement('g');
256
- features.forEach(feature => appendGeometry(group, feature.geometry, bounds));
257
- svg.append(rect, group);
258
- map.appendChild(svg);
259
- root.append(panel, map);
260
- return root;
261
- };
262
- export default async function renderGeo(buffer, target, type) {
263
- const normalizedType = normalizeGeoType(type);
264
- const style = createStyle();
265
- const state = document.createElement('div');
266
- state.className = 'geo-state';
267
- state.textContent = '正在解析地理数据...';
268
- target.replaceChildren(style, state);
269
- try {
270
- const collection = await parseGeo(buffer, normalizedType);
271
- target.replaceChildren(style, renderCollection(collection, normalizedType));
272
- }
273
- catch (error) {
274
- console.error(error);
275
- state.classList.add('error');
276
- state.textContent = error instanceof Error ? error.message : String(error);
277
- }
278
- return {
279
- $el: target,
280
- unmount() {
281
- target.replaceChildren();
282
- },
283
- };
284
- }
@@ -1,2 +0,0 @@
1
- import type { FileViewerRenderedInstance } from '../contracts/types';
2
- export default function renderMarkdown(buffer: ArrayBuffer, target: HTMLDivElement): Promise<FileViewerRenderedInstance>;