@file-viewer/core 2.0.11 → 2.1.1

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 (116) 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 +76 -1
  6. package/dist/headless.d.ts +3 -3
  7. package/dist/headless.js +2 -2
  8. package/dist/index.d.ts +10 -7
  9. package/dist/index.js +106 -49
  10. package/dist/lifecycle/operations.d.ts +1 -0
  11. package/dist/lifecycle/operations.js +65 -6
  12. package/dist/platform/assets.d.ts +3 -1
  13. package/dist/platform/assets.js +43 -6
  14. package/dist/registry/capabilities.d.ts +2 -2
  15. package/dist/registry/capabilities.js +2 -1
  16. package/dist/registry/formats.d.ts +20 -7
  17. package/dist/registry/formats.js +14 -5
  18. package/dist/registry/registry.d.ts +8 -1
  19. package/dist/registry/registry.js +29 -0
  20. package/dist/renderers/image.js +1 -10
  21. package/dist/renderers/index.d.ts +320 -2
  22. package/dist/renderers/index.js +27 -157
  23. package/dist/viewer/createViewer.js +86 -3
  24. package/package.json +17 -44
  25. package/dist/renderers/archive.d.ts +0 -2
  26. package/dist/renderers/archive.js +0 -547
  27. package/dist/renderers/archiveCache.d.ts +0 -10
  28. package/dist/renderers/archiveCache.js +0 -96
  29. package/dist/renderers/archiveFallback.d.ts +0 -7
  30. package/dist/renderers/archiveFallback.js +0 -166
  31. package/dist/renderers/archiveShared.d.ts +0 -23
  32. package/dist/renderers/archiveShared.js +0 -71
  33. package/dist/renderers/audio.d.ts +0 -8
  34. package/dist/renderers/audio.js +0 -219
  35. package/dist/renderers/cad.d.ts +0 -2
  36. package/dist/renderers/cad.js +0 -446
  37. package/dist/renderers/code.d.ts +0 -11
  38. package/dist/renderers/code.js +0 -233
  39. package/dist/renderers/data.d.ts +0 -7
  40. package/dist/renderers/data.js +0 -370
  41. package/dist/renderers/drawing.d.ts +0 -10
  42. package/dist/renderers/drawing.js +0 -882
  43. package/dist/renderers/eda.d.ts +0 -2
  44. package/dist/renderers/eda.js +0 -434
  45. package/dist/renderers/edaParser.d.ts +0 -77
  46. package/dist/renderers/edaParser.js +0 -569
  47. package/dist/renderers/email.d.ts +0 -2
  48. package/dist/renderers/email.js +0 -463
  49. package/dist/renderers/epub.d.ts +0 -2
  50. package/dist/renderers/epub.js +0 -331
  51. package/dist/renderers/geo.d.ts +0 -2
  52. package/dist/renderers/geo.js +0 -284
  53. package/dist/renderers/markdown.d.ts +0 -2
  54. package/dist/renderers/markdown.js +0 -83
  55. package/dist/renderers/model.d.ts +0 -2
  56. package/dist/renderers/model.js +0 -567
  57. package/dist/renderers/ofd.d.ts +0 -2
  58. package/dist/renderers/ofd.js +0 -256
  59. package/dist/renderers/openDocument.d.ts +0 -2
  60. package/dist/renderers/openDocument.js +0 -122
  61. package/dist/renderers/pdf.d.ts +0 -3
  62. package/dist/renderers/pdf.js +0 -1001
  63. package/dist/renderers/pdfStyles.d.ts +0 -1
  64. package/dist/renderers/pdfStyles.js +0 -1
  65. package/dist/renderers/pptx.d.ts +0 -2
  66. package/dist/renderers/pptx.js +0 -217
  67. package/dist/renderers/spreadsheet/state.d.ts +0 -80
  68. package/dist/renderers/spreadsheet/state.js +0 -96
  69. package/dist/renderers/spreadsheet/view.d.ts +0 -25
  70. package/dist/renderers/spreadsheet/view.js +0 -833
  71. package/dist/renderers/spreadsheet/worker/index.d.ts +0 -2
  72. package/dist/renderers/spreadsheet/worker/index.js +0 -1
  73. package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +0 -73
  74. package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +0 -623
  75. package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +0 -2
  76. package/dist/renderers/spreadsheet/worker/sheetjs/color.js +0 -73
  77. package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +0 -1
  78. package/dist/renderers/spreadsheet/worker/sheetjs/index.js +0 -1
  79. package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +0 -18
  80. package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +0 -106
  81. package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +0 -1
  82. package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +0 -11
  83. package/dist/renderers/spreadsheet/worker/type.d.ts +0 -57
  84. package/dist/renderers/spreadsheet/worker/type.js +0 -1
  85. package/dist/renderers/spreadsheet.d.ts +0 -3
  86. package/dist/renderers/spreadsheet.js +0 -929
  87. package/dist/renderers/typst.d.ts +0 -8
  88. package/dist/renderers/typst.js +0 -547
  89. package/dist/renderers/umd/parser.d.ts +0 -30
  90. package/dist/renderers/umd/parser.js +0 -408
  91. package/dist/renderers/umd.d.ts +0 -2
  92. package/dist/renderers/umd.js +0 -297
  93. package/dist/renderers/video.d.ts +0 -8
  94. package/dist/renderers/video.js +0 -108
  95. package/dist/renderers/wordDoc.d.ts +0 -5
  96. package/dist/renderers/wordDoc.js +0 -284
  97. package/dist/renderers/wordDocx.d.ts +0 -5
  98. package/dist/renderers/wordDocx.js +0 -985
  99. package/dist/renderers/wordDocx.worker.d.ts +0 -1
  100. package/dist/renderers/wordDocx.worker.js +0 -96
  101. package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +0 -183
  102. package/vendor/ofd/dltech/jbig2/ccitt.js +0 -1070
  103. package/vendor/ofd/dltech/jbig2/compatibility.js +0 -12
  104. package/vendor/ofd/dltech/jbig2/core_utils.js +0 -180
  105. package/vendor/ofd/dltech/jbig2/is_node.js +0 -27
  106. package/vendor/ofd/dltech/jbig2/jbig2.js +0 -2589
  107. package/vendor/ofd/dltech/jbig2/jbig2_stream.js +0 -81
  108. package/vendor/ofd/dltech/jbig2/primitives.js +0 -387
  109. package/vendor/ofd/dltech/jbig2/stream.js +0 -1348
  110. package/vendor/ofd/dltech/jbig2/util.js +0 -972
  111. package/vendor/ofd/dltech/ofd/ofd.d.ts +0 -11
  112. package/vendor/ofd/dltech/ofd/ofd.js +0 -100
  113. package/vendor/ofd/dltech/ofd/ofd_parser.js +0 -395
  114. package/vendor/ofd/dltech/ofd/ofd_render.js +0 -473
  115. package/vendor/ofd/dltech/ofd/ofd_util.js +0 -350
  116. 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>;