@file-viewer/core 2.0.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.
- package/LICENSE +160 -0
- package/README.en.md +78 -0
- package/README.md +83 -0
- package/dist/assets.d.ts +62 -0
- package/dist/assets.js +260 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +1 -0
- package/dist/capabilities.d.ts +24 -0
- package/dist/capabilities.js +95 -0
- package/dist/document.d.ts +9 -0
- package/dist/document.js +86 -0
- package/dist/documentDom/anchors.d.ts +7 -0
- package/dist/documentDom/anchors.js +196 -0
- package/dist/documentDom/index.d.ts +5 -0
- package/dist/documentDom/index.js +3 -0
- package/dist/documentDom/providers.d.ts +13 -0
- package/dist/documentDom/providers.js +52 -0
- package/dist/documentDom/scroll.d.ts +12 -0
- package/dist/documentDom/scroll.js +42 -0
- package/dist/documentDom.d.ts +5 -0
- package/dist/documentDom.js +3 -0
- package/dist/documentEvents.d.ts +73 -0
- package/dist/documentEvents.js +150 -0
- package/dist/documentSearch.d.ts +50 -0
- package/dist/documentSearch.js +459 -0
- package/dist/documentZoom.d.ts +47 -0
- package/dist/documentZoom.js +184 -0
- package/dist/export.d.ts +26 -0
- package/dist/export.js +466 -0
- package/dist/formats.d.ts +305 -0
- package/dist/formats.js +207 -0
- package/dist/headless.d.ts +25 -0
- package/dist/headless.js +14 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +118 -0
- package/dist/lifecycleFacade.d.ts +46 -0
- package/dist/lifecycleFacade.js +68 -0
- package/dist/loading.d.ts +59 -0
- package/dist/loading.js +489 -0
- package/dist/operations.d.ts +287 -0
- package/dist/operations.js +485 -0
- package/dist/options.d.ts +16 -0
- package/dist/options.js +92 -0
- package/dist/presentation.d.ts +14 -0
- package/dist/presentation.js +16 -0
- package/dist/printLayout.d.ts +19 -0
- package/dist/printLayout.js +83 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +63 -0
- package/dist/rendererDispatcher.d.ts +21 -0
- package/dist/rendererDispatcher.js +42 -0
- package/dist/rendererHandler.d.ts +165 -0
- package/dist/rendererHandler.js +354 -0
- package/dist/renderers/archive.d.ts +2 -0
- package/dist/renderers/archive.js +547 -0
- package/dist/renderers/archiveCache.d.ts +10 -0
- package/dist/renderers/archiveCache.js +96 -0
- package/dist/renderers/archiveFallback.d.ts +7 -0
- package/dist/renderers/archiveFallback.js +166 -0
- package/dist/renderers/archiveShared.d.ts +23 -0
- package/dist/renderers/archiveShared.js +71 -0
- package/dist/renderers/audio.d.ts +8 -0
- package/dist/renderers/audio.js +219 -0
- package/dist/renderers/cad.d.ts +2 -0
- package/dist/renderers/cad.js +445 -0
- package/dist/renderers/code.d.ts +11 -0
- package/dist/renderers/code.js +233 -0
- package/dist/renderers/data.d.ts +7 -0
- package/dist/renderers/data.js +370 -0
- package/dist/renderers/drawing.d.ts +10 -0
- package/dist/renderers/drawing.js +537 -0
- package/dist/renderers/eda.d.ts +2 -0
- package/dist/renderers/eda.js +434 -0
- package/dist/renderers/edaParser.d.ts +77 -0
- package/dist/renderers/edaParser.js +569 -0
- package/dist/renderers/email.d.ts +2 -0
- package/dist/renderers/email.js +463 -0
- package/dist/renderers/epub.d.ts +2 -0
- package/dist/renderers/epub.js +330 -0
- package/dist/renderers/geo.d.ts +2 -0
- package/dist/renderers/geo.js +284 -0
- package/dist/renderers/image.d.ts +2 -0
- package/dist/renderers/image.js +179 -0
- package/dist/renderers/index.d.ts +21 -0
- package/dist/renderers/index.js +207 -0
- package/dist/renderers/markdown.d.ts +2 -0
- package/dist/renderers/markdown.js +83 -0
- package/dist/renderers/model.d.ts +2 -0
- package/dist/renderers/model.js +566 -0
- package/dist/renderers/ofd.d.ts +2 -0
- package/dist/renderers/ofd.js +255 -0
- package/dist/renderers/openDocument.d.ts +2 -0
- package/dist/renderers/openDocument.js +122 -0
- package/dist/renderers/pdf.d.ts +3 -0
- package/dist/renderers/pdf.js +846 -0
- package/dist/renderers/pdfStyles.d.ts +1 -0
- package/dist/renderers/pdfStyles.js +1 -0
- package/dist/renderers/pptx.d.ts +2 -0
- package/dist/renderers/pptx.js +202 -0
- package/dist/renderers/spreadsheet/state.d.ts +80 -0
- package/dist/renderers/spreadsheet/state.js +96 -0
- package/dist/renderers/spreadsheet/view.d.ts +25 -0
- package/dist/renderers/spreadsheet/view.js +833 -0
- package/dist/renderers/spreadsheet/worker/index.d.ts +2 -0
- package/dist/renderers/spreadsheet/worker/index.js +1 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +73 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +623 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +2 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/color.js +73 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +1 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/index.js +1 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +18 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +106 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +1 -0
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +11 -0
- package/dist/renderers/spreadsheet/worker/type.d.ts +57 -0
- package/dist/renderers/spreadsheet/worker/type.js +1 -0
- package/dist/renderers/spreadsheet.d.ts +3 -0
- package/dist/renderers/spreadsheet.js +929 -0
- package/dist/renderers/typst.d.ts +8 -0
- package/dist/renderers/typst.js +415 -0
- package/dist/renderers/umd/parser.d.ts +30 -0
- package/dist/renderers/umd/parser.js +408 -0
- package/dist/renderers/umd.d.ts +2 -0
- package/dist/renderers/umd.js +297 -0
- package/dist/renderers/video.d.ts +8 -0
- package/dist/renderers/video.js +108 -0
- package/dist/renderers/wordDoc.d.ts +5 -0
- package/dist/renderers/wordDoc.js +284 -0
- package/dist/renderers/wordDocx.d.ts +5 -0
- package/dist/renderers/wordDocx.js +501 -0
- package/dist/renderers/wordDocx.worker.d.ts +1 -0
- package/dist/renderers/wordDocx.worker.js +96 -0
- package/dist/source.d.ts +18 -0
- package/dist/source.js +152 -0
- package/dist/sourceLoading.d.ts +566 -0
- package/dist/sourceLoading.js +918 -0
- package/dist/state.d.ts +16 -0
- package/dist/state.js +81 -0
- package/dist/types.d.ts +446 -0
- package/dist/types.js +1 -0
- package/dist/viewer.d.ts +8 -0
- package/dist/viewer.js +285 -0
- package/dist/viewerOperations.d.ts +88 -0
- package/dist/viewerOperations.js +242 -0
- package/dist/watermark.d.ts +15 -0
- package/dist/watermark.js +81 -0
- package/dist/worker.d.ts +34 -0
- package/dist/worker.js +101 -0
- package/package.json +109 -0
- package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +183 -0
- package/vendor/ofd/dltech/jbig2/ccitt.js +1070 -0
- package/vendor/ofd/dltech/jbig2/compatibility.js +12 -0
- package/vendor/ofd/dltech/jbig2/core_utils.js +180 -0
- package/vendor/ofd/dltech/jbig2/is_node.js +27 -0
- package/vendor/ofd/dltech/jbig2/jbig2.js +2589 -0
- package/vendor/ofd/dltech/jbig2/jbig2_stream.js +81 -0
- package/vendor/ofd/dltech/jbig2/primitives.js +387 -0
- package/vendor/ofd/dltech/jbig2/stream.js +1348 -0
- package/vendor/ofd/dltech/jbig2/util.js +972 -0
- package/vendor/ofd/dltech/ofd/ofd.d.ts +11 -0
- package/vendor/ofd/dltech/ofd/ofd.js +100 -0
- package/vendor/ofd/dltech/ofd/ofd_parser.js +395 -0
- package/vendor/ofd/dltech/ofd/ofd_render.js +473 -0
- package/vendor/ofd/dltech/ofd/ofd_util.js +350 -0
- package/vendor/ofd/dltech/ofd/pipeline.js +26 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { registerFileViewerZoomProvider, unregisterFileViewerZoomProvider, } from '../documentDom.js';
|
|
2
|
+
import { createFileViewerZoomChangeEmitter } from '../documentZoom.js';
|
|
3
|
+
import { waitForFileViewerNextPaint } from '../export.js';
|
|
4
|
+
import { readFileViewerText } from '../source.js';
|
|
5
|
+
const DIAGRAMS_VIEWER_URL = 'https://viewer.diagrams.net/js/viewer-static.min.js';
|
|
6
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
7
|
+
const EXCALIDRAW_OFFICIAL_TIMEOUT = 6000;
|
|
8
|
+
const diagramsViewerPromises = new WeakMap();
|
|
9
|
+
const drawingStyle = `
|
|
10
|
+
.drawing-viewer{display:flex;height:100%;min-height:360px;flex-direction:column;background:#edf2f7;color:#172033}
|
|
11
|
+
.drawing-toolbar{position:sticky;top:0;z-index:2;display:flex;min-height:46px;align-items:center;justify-content:space-between;gap:16px;padding:8px 14px;border-bottom:1px solid rgba(148,163,184,.35);background:rgba(248,250,252,.92);backdrop-filter:blur(12px)}
|
|
12
|
+
.drawing-title{display:flex;min-width:0;align-items:center;gap:10px}
|
|
13
|
+
.drawing-title span{display:inline-flex;height:24px;align-items:center;justify-content:center;border-radius:6px;padding:0 8px;background:#0f766e;color:#fff;font-size:11px;font-weight:800;letter-spacing:0}
|
|
14
|
+
.drawing-title strong{overflow:hidden;color:#172033;font-size:13px;font-weight:800;text-overflow:ellipsis;white-space:nowrap}
|
|
15
|
+
.drawing-actions{display:flex;flex-shrink:0;align-items:center;gap:6px}
|
|
16
|
+
.drawing-actions button{min-width:32px;height:28px;border:1px solid rgba(100,116,139,.28);border-radius:6px;background:#fff;color:#0f172a;cursor:pointer;font-size:12px;font-weight:800}
|
|
17
|
+
.drawing-actions button:hover{border-color:rgba(15,118,110,.5);color:#0f766e}
|
|
18
|
+
.drawing-actions span{min-width:48px;color:#64748b;font-size:12px;font-weight:800;text-align:center}
|
|
19
|
+
.drawing-stage{position:relative;min-height:0;flex:1;overflow:hidden}
|
|
20
|
+
.drawing-scroll{height:100%;overflow:auto;padding:22px}
|
|
21
|
+
.drawing-canvas{width:100%;min-height:420px;transition:transform .18s ease,zoom .18s ease}
|
|
22
|
+
.drawing-canvas .drawing-svg,.drawing-canvas svg{display:block;max-width:100%;height:auto;margin:0 auto;border-radius:10px;background:#fff;box-shadow:0 18px 42px rgba(15,23,42,.12)}
|
|
23
|
+
.drawing-canvas .drawing-mxgraph{min-height:420px;overflow:hidden;border-radius:10px;background:#fff;box-shadow:0 18px 42px rgba(15,23,42,.12)}
|
|
24
|
+
.drawing-state{position:absolute;inset:0;z-index:1;display:flex;align-items:center;justify-content:center;padding:24px;color:#64748b;font-size:14px;font-weight:700;text-align:center}
|
|
25
|
+
.drawing-state.error{color:#b42318}
|
|
26
|
+
@media (max-width:720px){.drawing-toolbar{align-items:flex-start;flex-direction:column}.drawing-actions{width:100%;justify-content:space-between}.drawing-scroll{padding:12px}}
|
|
27
|
+
`;
|
|
28
|
+
const createStyle = (documentRef) => {
|
|
29
|
+
const style = documentRef.createElement('style');
|
|
30
|
+
style.textContent = drawingStyle;
|
|
31
|
+
return style;
|
|
32
|
+
};
|
|
33
|
+
const createElement = (documentRef, tagName, className, text) => {
|
|
34
|
+
const element = documentRef.createElement(tagName);
|
|
35
|
+
if (className) {
|
|
36
|
+
element.className = className;
|
|
37
|
+
}
|
|
38
|
+
if (text !== undefined) {
|
|
39
|
+
element.textContent = text;
|
|
40
|
+
}
|
|
41
|
+
return element;
|
|
42
|
+
};
|
|
43
|
+
const createSvgElement = (documentRef, tagName) => {
|
|
44
|
+
return documentRef.createElementNS(SVG_NS, tagName);
|
|
45
|
+
};
|
|
46
|
+
const normalizeDrawingType = (type) => {
|
|
47
|
+
return (type === null || type === void 0 ? void 0 : type.toLowerCase()) === 'excalidraw' ? 'excalidraw' : 'drawio';
|
|
48
|
+
};
|
|
49
|
+
const formatDrawingLabel = (type) => {
|
|
50
|
+
const normalized = (type || 'drawio').toLowerCase();
|
|
51
|
+
return normalized === 'dio' ? 'DRAWIO' : normalized.toUpperCase();
|
|
52
|
+
};
|
|
53
|
+
const clampZoom = (value) => {
|
|
54
|
+
return Math.min(3, Math.max(0.5, Number(value.toFixed(2))));
|
|
55
|
+
};
|
|
56
|
+
const toNumber = (value, fallback = 0) => {
|
|
57
|
+
const parsed = Number(value);
|
|
58
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
59
|
+
};
|
|
60
|
+
const isTransparent = (color) => {
|
|
61
|
+
return !color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)';
|
|
62
|
+
};
|
|
63
|
+
const loadDiagramsViewer = (documentRef) => {
|
|
64
|
+
const ownerWindow = documentRef.defaultView || (typeof window !== 'undefined' ? window : undefined);
|
|
65
|
+
if (ownerWindow === null || ownerWindow === void 0 ? void 0 : ownerWindow.GraphViewer) {
|
|
66
|
+
return Promise.resolve();
|
|
67
|
+
}
|
|
68
|
+
const existingPromise = diagramsViewerPromises.get(documentRef);
|
|
69
|
+
if (existingPromise) {
|
|
70
|
+
return existingPromise;
|
|
71
|
+
}
|
|
72
|
+
const nextPromise = new Promise((resolve, reject) => {
|
|
73
|
+
const existed = documentRef.querySelector(`script[src="${DIAGRAMS_VIEWER_URL}"]`);
|
|
74
|
+
if (existed) {
|
|
75
|
+
existed.addEventListener('load', () => resolve(), { once: true });
|
|
76
|
+
existed.addEventListener('error', () => reject(new Error('diagrams.net viewer 加载失败')), { once: true });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const script = documentRef.createElement('script');
|
|
80
|
+
script.src = DIAGRAMS_VIEWER_URL;
|
|
81
|
+
script.async = true;
|
|
82
|
+
script.onload = () => resolve();
|
|
83
|
+
script.onerror = () => reject(new Error('diagrams.net viewer 加载失败'));
|
|
84
|
+
documentRef.head.appendChild(script);
|
|
85
|
+
});
|
|
86
|
+
diagramsViewerPromises.set(documentRef, nextPromise);
|
|
87
|
+
return nextPromise;
|
|
88
|
+
};
|
|
89
|
+
const runWithTimeout = async (task, timeout, message) => {
|
|
90
|
+
let timer;
|
|
91
|
+
try {
|
|
92
|
+
return await Promise.race([
|
|
93
|
+
task,
|
|
94
|
+
new Promise((_, reject) => {
|
|
95
|
+
timer = setTimeout(() => reject(new Error(message)), timeout);
|
|
96
|
+
}),
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
if (timer) {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const markRendered = (target, mode) => {
|
|
106
|
+
if (target.dataset.drawingRendered) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
target.dataset.drawingRendered = mode;
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
const appendRenderedSvg = (target, svg, mode) => {
|
|
113
|
+
if (!markRendered(target, mode)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
svg.classList.add('drawing-svg');
|
|
117
|
+
target.appendChild(svg);
|
|
118
|
+
};
|
|
119
|
+
const suppressExcalidrawWorkerWarning = () => {
|
|
120
|
+
const originalError = console.error;
|
|
121
|
+
const patchedError = (...args) => {
|
|
122
|
+
const message = args.map(arg => String(arg)).join(' ');
|
|
123
|
+
if (message.includes('Failed to use workers for subsetting')) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
originalError(...args);
|
|
127
|
+
};
|
|
128
|
+
console.error = patchedError;
|
|
129
|
+
return () => {
|
|
130
|
+
if (console.error === patchedError) {
|
|
131
|
+
console.error = originalError;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
const getElementPoints = (element) => {
|
|
136
|
+
if (Array.isArray(element.points) && element.points.length) {
|
|
137
|
+
return element.points.map((point) => [
|
|
138
|
+
toNumber(element.x) + toNumber(point[0]),
|
|
139
|
+
toNumber(element.y) + toNumber(point[1]),
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
return [
|
|
143
|
+
[toNumber(element.x), toNumber(element.y)],
|
|
144
|
+
[toNumber(element.x) + toNumber(element.width), toNumber(element.y) + toNumber(element.height)],
|
|
145
|
+
];
|
|
146
|
+
};
|
|
147
|
+
const getElementBounds = (element) => {
|
|
148
|
+
const points = getElementPoints(element);
|
|
149
|
+
const xs = points.map(point => point[0]);
|
|
150
|
+
const ys = points.map(point => point[1]);
|
|
151
|
+
if (!Array.isArray(element.points)) {
|
|
152
|
+
xs.push(toNumber(element.x) + toNumber(element.width));
|
|
153
|
+
ys.push(toNumber(element.y) + toNumber(element.height));
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
minX: Math.min(...xs),
|
|
157
|
+
minY: Math.min(...ys),
|
|
158
|
+
maxX: Math.max(...xs),
|
|
159
|
+
maxY: Math.max(...ys),
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
const getSceneBounds = (elements) => {
|
|
163
|
+
const initial = {
|
|
164
|
+
minX: Number.POSITIVE_INFINITY,
|
|
165
|
+
minY: Number.POSITIVE_INFINITY,
|
|
166
|
+
maxX: Number.NEGATIVE_INFINITY,
|
|
167
|
+
maxY: Number.NEGATIVE_INFINITY,
|
|
168
|
+
};
|
|
169
|
+
const bounds = elements.reduce((scene, element) => {
|
|
170
|
+
const elementBounds = getElementBounds(element);
|
|
171
|
+
return {
|
|
172
|
+
minX: Math.min(scene.minX, elementBounds.minX),
|
|
173
|
+
minY: Math.min(scene.minY, elementBounds.minY),
|
|
174
|
+
maxX: Math.max(scene.maxX, elementBounds.maxX),
|
|
175
|
+
maxY: Math.max(scene.maxY, elementBounds.maxY),
|
|
176
|
+
};
|
|
177
|
+
}, initial);
|
|
178
|
+
if (!Number.isFinite(bounds.minX)) {
|
|
179
|
+
return { minX: 0, minY: 0, maxX: 800, maxY: 480 };
|
|
180
|
+
}
|
|
181
|
+
return bounds;
|
|
182
|
+
};
|
|
183
|
+
const getRoughOptions = (element) => {
|
|
184
|
+
const fill = isTransparent(element.backgroundColor) ? undefined : element.backgroundColor;
|
|
185
|
+
return {
|
|
186
|
+
stroke: element.strokeColor || '#1e1e1e',
|
|
187
|
+
strokeWidth: Math.max(1, toNumber(element.strokeWidth, 1)),
|
|
188
|
+
roughness: Math.max(0, toNumber(element.roughness, 1)),
|
|
189
|
+
fill,
|
|
190
|
+
fillStyle: element.fillStyle || 'hachure',
|
|
191
|
+
seed: toNumber(element.seed, 1),
|
|
192
|
+
strokeLineDash: element.strokeStyle === 'dashed'
|
|
193
|
+
? [10, 8]
|
|
194
|
+
: element.strokeStyle === 'dotted'
|
|
195
|
+
? [2, 6]
|
|
196
|
+
: undefined,
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
const appendWithOpacity = (group, element, node) => {
|
|
200
|
+
const opacity = toNumber(element.opacity, 100) / 100;
|
|
201
|
+
if (opacity < 1) {
|
|
202
|
+
node.setAttribute('opacity', String(opacity));
|
|
203
|
+
}
|
|
204
|
+
group.appendChild(node);
|
|
205
|
+
};
|
|
206
|
+
const createElementGroup = (documentRef, element) => {
|
|
207
|
+
const group = createSvgElement(documentRef, 'g');
|
|
208
|
+
const angle = toNumber(element.angle);
|
|
209
|
+
if (angle) {
|
|
210
|
+
const cx = toNumber(element.x) + toNumber(element.width) / 2;
|
|
211
|
+
const cy = toNumber(element.y) + toNumber(element.height) / 2;
|
|
212
|
+
group.setAttribute('transform', `rotate(${angle * 180 / Math.PI} ${cx} ${cy})`);
|
|
213
|
+
}
|
|
214
|
+
return group;
|
|
215
|
+
};
|
|
216
|
+
const renderTextFallback = (documentRef, group, element) => {
|
|
217
|
+
const text = String(element.text || '');
|
|
218
|
+
if (!text.trim()) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const textNode = createSvgElement(documentRef, 'text');
|
|
222
|
+
const fontSize = Math.max(8, toNumber(element.fontSize, 20));
|
|
223
|
+
const lineHeight = fontSize * 1.25;
|
|
224
|
+
const lines = text.split(/\r?\n/);
|
|
225
|
+
const familyMap = {
|
|
226
|
+
1: 'Virgil, Segoe Print, Comic Sans MS, sans-serif',
|
|
227
|
+
2: 'Helvetica, Arial, sans-serif',
|
|
228
|
+
3: 'Cascadia Mono, Menlo, Consolas, monospace',
|
|
229
|
+
};
|
|
230
|
+
textNode.setAttribute('x', String(toNumber(element.x)));
|
|
231
|
+
textNode.setAttribute('y', String(toNumber(element.y) + fontSize));
|
|
232
|
+
textNode.setAttribute('fill', element.strokeColor || '#1e1e1e');
|
|
233
|
+
textNode.setAttribute('font-size', String(fontSize));
|
|
234
|
+
textNode.setAttribute('font-family', familyMap[toNumber(element.fontFamily, 1)] || familyMap[1]);
|
|
235
|
+
textNode.setAttribute('font-weight', String(element.fontWeight || 400));
|
|
236
|
+
textNode.setAttribute('text-anchor', element.textAlign === 'center' ? 'middle' : element.textAlign === 'right' ? 'end' : 'start');
|
|
237
|
+
if (element.textAlign === 'center') {
|
|
238
|
+
textNode.setAttribute('x', String(toNumber(element.x) + toNumber(element.width) / 2));
|
|
239
|
+
}
|
|
240
|
+
else if (element.textAlign === 'right') {
|
|
241
|
+
textNode.setAttribute('x', String(toNumber(element.x) + toNumber(element.width)));
|
|
242
|
+
}
|
|
243
|
+
lines.forEach((line, index) => {
|
|
244
|
+
const lineNode = createSvgElement(documentRef, 'tspan');
|
|
245
|
+
lineNode.setAttribute('x', textNode.getAttribute('x') || String(toNumber(element.x)));
|
|
246
|
+
lineNode.setAttribute('dy', index === 0 ? '0' : String(lineHeight));
|
|
247
|
+
lineNode.textContent = line;
|
|
248
|
+
textNode.appendChild(lineNode);
|
|
249
|
+
});
|
|
250
|
+
appendWithOpacity(group, element, textNode);
|
|
251
|
+
};
|
|
252
|
+
const appendArrowHead = (documentRef, group, element, points) => {
|
|
253
|
+
const endArrow = element.endArrowhead || element.startArrowhead;
|
|
254
|
+
if (!endArrow || points.length < 2) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const end = points[points.length - 1];
|
|
258
|
+
const before = points[points.length - 2];
|
|
259
|
+
const angle = Math.atan2(end[1] - before[1], end[0] - before[0]);
|
|
260
|
+
const size = Math.max(10, toNumber(element.strokeWidth, 1) * 7);
|
|
261
|
+
const left = [
|
|
262
|
+
end[0] - size * Math.cos(angle - Math.PI / 7),
|
|
263
|
+
end[1] - size * Math.sin(angle - Math.PI / 7),
|
|
264
|
+
];
|
|
265
|
+
const right = [
|
|
266
|
+
end[0] - size * Math.cos(angle + Math.PI / 7),
|
|
267
|
+
end[1] - size * Math.sin(angle + Math.PI / 7),
|
|
268
|
+
];
|
|
269
|
+
const arrow = createSvgElement(documentRef, 'polygon');
|
|
270
|
+
arrow.setAttribute('points', `${end.join(',')} ${left.join(',')} ${right.join(',')}`);
|
|
271
|
+
arrow.setAttribute('fill', element.strokeColor || '#1e1e1e');
|
|
272
|
+
arrow.setAttribute('stroke', element.strokeColor || '#1e1e1e');
|
|
273
|
+
appendWithOpacity(group, element, arrow);
|
|
274
|
+
};
|
|
275
|
+
const renderRoughFallback = async (documentRef, payload, elements, target) => {
|
|
276
|
+
var _a;
|
|
277
|
+
const { default: rough } = await import('roughjs');
|
|
278
|
+
const bounds = getSceneBounds(elements);
|
|
279
|
+
const padding = 80;
|
|
280
|
+
const width = Math.max(320, bounds.maxX - bounds.minX + padding * 2);
|
|
281
|
+
const height = Math.max(220, bounds.maxY - bounds.minY + padding * 2);
|
|
282
|
+
const svg = createSvgElement(documentRef, 'svg');
|
|
283
|
+
const root = createSvgElement(documentRef, 'g');
|
|
284
|
+
const rc = rough.svg(svg);
|
|
285
|
+
svg.setAttribute('viewBox', `${bounds.minX - padding} ${bounds.minY - padding} ${width} ${height}`);
|
|
286
|
+
svg.setAttribute('width', String(width));
|
|
287
|
+
svg.setAttribute('height', String(height));
|
|
288
|
+
svg.setAttribute('role', 'img');
|
|
289
|
+
svg.setAttribute('aria-label', 'Excalidraw rough.js preview');
|
|
290
|
+
const background = createSvgElement(documentRef, 'rect');
|
|
291
|
+
background.setAttribute('x', String(bounds.minX - padding));
|
|
292
|
+
background.setAttribute('y', String(bounds.minY - padding));
|
|
293
|
+
background.setAttribute('width', String(width));
|
|
294
|
+
background.setAttribute('height', String(height));
|
|
295
|
+
background.setAttribute('fill', ((_a = payload.appState) === null || _a === void 0 ? void 0 : _a.viewBackgroundColor) || '#ffffff');
|
|
296
|
+
svg.appendChild(background);
|
|
297
|
+
svg.appendChild(root);
|
|
298
|
+
elements.forEach(element => {
|
|
299
|
+
const group = createElementGroup(documentRef, element);
|
|
300
|
+
const options = getRoughOptions(element);
|
|
301
|
+
const x = toNumber(element.x);
|
|
302
|
+
const y = toNumber(element.y);
|
|
303
|
+
const width = toNumber(element.width);
|
|
304
|
+
const height = toNumber(element.height);
|
|
305
|
+
if (element.type === 'text') {
|
|
306
|
+
renderTextFallback(documentRef, group, element);
|
|
307
|
+
}
|
|
308
|
+
else if (element.type === 'rectangle') {
|
|
309
|
+
appendWithOpacity(group, element, rc.rectangle(x, y, width, height, options));
|
|
310
|
+
}
|
|
311
|
+
else if (element.type === 'diamond') {
|
|
312
|
+
appendWithOpacity(group, element, rc.polygon([
|
|
313
|
+
[x + width / 2, y],
|
|
314
|
+
[x + width, y + height / 2],
|
|
315
|
+
[x + width / 2, y + height],
|
|
316
|
+
[x, y + height / 2],
|
|
317
|
+
], options));
|
|
318
|
+
}
|
|
319
|
+
else if (element.type === 'ellipse') {
|
|
320
|
+
appendWithOpacity(group, element, rc.ellipse(x + width / 2, y + height / 2, Math.abs(width), Math.abs(height), options));
|
|
321
|
+
}
|
|
322
|
+
else if (element.type === 'line' || element.type === 'arrow' || element.type === 'freedraw') {
|
|
323
|
+
const points = getElementPoints(element);
|
|
324
|
+
appendWithOpacity(group, element, rc.linearPath(points, options));
|
|
325
|
+
if (element.type === 'arrow') {
|
|
326
|
+
appendArrowHead(documentRef, group, element, points);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (group.childNodes.length) {
|
|
330
|
+
root.appendChild(group);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
appendRenderedSvg(target, svg, 'rough');
|
|
334
|
+
};
|
|
335
|
+
const renderOfficialExcalidraw = async (payload, elements, target) => {
|
|
336
|
+
// Excalidraw 在部分浏览器会把字体子集 worker 降级记为 console.error;
|
|
337
|
+
// 实际会继续使用主线程导出,这里只屏蔽这条已知噪声。
|
|
338
|
+
const restoreConsole = suppressExcalidrawWorkerWarning();
|
|
339
|
+
const restoreTimer = setTimeout(restoreConsole, EXCALIDRAW_OFFICIAL_TIMEOUT + 1000);
|
|
340
|
+
const { exportToSvg, restore } = await import('@excalidraw/excalidraw');
|
|
341
|
+
try {
|
|
342
|
+
const restored = restore({
|
|
343
|
+
elements: elements,
|
|
344
|
+
appState: payload.appState || {},
|
|
345
|
+
files: payload.files || {},
|
|
346
|
+
}, null, null, {
|
|
347
|
+
repairBindings: true,
|
|
348
|
+
refreshDimensions: true,
|
|
349
|
+
});
|
|
350
|
+
const restoredElements = restored.elements.filter((element) => !element.isDeleted);
|
|
351
|
+
const svg = await exportToSvg({
|
|
352
|
+
elements: restoredElements,
|
|
353
|
+
appState: {
|
|
354
|
+
...restored.appState,
|
|
355
|
+
exportBackground: true,
|
|
356
|
+
viewBackgroundColor: restored.appState.viewBackgroundColor || '#ffffff',
|
|
357
|
+
},
|
|
358
|
+
files: restored.files || {},
|
|
359
|
+
});
|
|
360
|
+
appendRenderedSvg(target, svg, 'official');
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
clearTimeout(restoreTimer);
|
|
364
|
+
restoreConsole();
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const renderExcalidraw = async (documentRef, text, target) => {
|
|
368
|
+
const payload = JSON.parse(text);
|
|
369
|
+
const elements = Array.isArray(payload.elements)
|
|
370
|
+
? payload.elements.filter((element) => !element.isDeleted)
|
|
371
|
+
: [];
|
|
372
|
+
if (!elements.length) {
|
|
373
|
+
throw new Error('Excalidraw 文件中没有可预览图元');
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
await runWithTimeout(renderOfficialExcalidraw(payload, elements, target), EXCALIDRAW_OFFICIAL_TIMEOUT, 'Excalidraw 官方导出超时,自动切换 rough.js 兼容渲染');
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
console.warn(error);
|
|
380
|
+
await renderRoughFallback(documentRef, payload, elements, target);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
const renderDrawio = async (documentRef, text, target) => {
|
|
384
|
+
const ownerWindow = documentRef.defaultView || (typeof window !== 'undefined' ? window : undefined);
|
|
385
|
+
await loadDiagramsViewer(documentRef);
|
|
386
|
+
await waitForFileViewerNextPaint(ownerWindow);
|
|
387
|
+
const host = createElement(documentRef, 'div', 'mxgraph drawing-mxgraph');
|
|
388
|
+
host.setAttribute('data-mxgraph', JSON.stringify({
|
|
389
|
+
xml: text,
|
|
390
|
+
toolbar: 'zoom layers lightbox',
|
|
391
|
+
nav: true,
|
|
392
|
+
resize: true,
|
|
393
|
+
'auto-fit': true,
|
|
394
|
+
'auto-crop': true,
|
|
395
|
+
'auto-origin': true,
|
|
396
|
+
'allow-zoom-in': true,
|
|
397
|
+
'allow-zoom-out': true,
|
|
398
|
+
border: 16,
|
|
399
|
+
highlight: '#0f766e',
|
|
400
|
+
}));
|
|
401
|
+
target.appendChild(host);
|
|
402
|
+
if (!(ownerWindow === null || ownerWindow === void 0 ? void 0 : ownerWindow.GraphViewer)) {
|
|
403
|
+
throw new Error('diagrams.net viewer 未正确初始化');
|
|
404
|
+
}
|
|
405
|
+
ownerWindow.GraphViewer.createViewerForElement(host);
|
|
406
|
+
};
|
|
407
|
+
export default async function renderDrawing(buffer, target, type = 'drawio', _context) {
|
|
408
|
+
const documentRef = target.ownerDocument || document;
|
|
409
|
+
const kind = normalizeDrawingType(type);
|
|
410
|
+
const zoomEmitter = createFileViewerZoomChangeEmitter();
|
|
411
|
+
let status = 'loading';
|
|
412
|
+
let errorMessage = '';
|
|
413
|
+
let zoom = 1;
|
|
414
|
+
let disposed = false;
|
|
415
|
+
const root = createElement(documentRef, 'div', 'drawing-viewer');
|
|
416
|
+
root.dataset.viewerZoomProvider = 'drawing';
|
|
417
|
+
const toolbar = createElement(documentRef, 'div', 'drawing-toolbar');
|
|
418
|
+
const title = createElement(documentRef, 'div', 'drawing-title');
|
|
419
|
+
title.append(createElement(documentRef, 'span', undefined, formatDrawingLabel(type)), createElement(documentRef, 'strong', undefined, kind === 'excalidraw'
|
|
420
|
+
? 'Excalidraw 官方 SVG 预览'
|
|
421
|
+
: 'diagrams.net 官方 Viewer 预览'));
|
|
422
|
+
const actions = createElement(documentRef, 'div', 'drawing-actions');
|
|
423
|
+
const zoomOutButton = createElement(documentRef, 'button', undefined, '-');
|
|
424
|
+
const zoomLabel = createElement(documentRef, 'span');
|
|
425
|
+
const zoomInButton = createElement(documentRef, 'button', undefined, '+');
|
|
426
|
+
const resetButton = createElement(documentRef, 'button', undefined, '适合');
|
|
427
|
+
[zoomOutButton, zoomInButton, resetButton].forEach(button => {
|
|
428
|
+
button.type = 'button';
|
|
429
|
+
});
|
|
430
|
+
zoomOutButton.title = '缩小';
|
|
431
|
+
zoomInButton.title = '放大';
|
|
432
|
+
resetButton.title = '适合宽度';
|
|
433
|
+
actions.append(zoomOutButton, zoomLabel, zoomInButton, resetButton);
|
|
434
|
+
toolbar.append(title, actions);
|
|
435
|
+
const stageWrapper = createElement(documentRef, 'div', 'drawing-stage');
|
|
436
|
+
const state = createElement(documentRef, 'div', 'drawing-state');
|
|
437
|
+
const scroll = createElement(documentRef, 'div', 'drawing-scroll');
|
|
438
|
+
const canvas = createElement(documentRef, 'div', 'drawing-canvas');
|
|
439
|
+
scroll.append(canvas);
|
|
440
|
+
stageWrapper.append(state, scroll);
|
|
441
|
+
root.append(toolbar, stageWrapper);
|
|
442
|
+
target.replaceChildren(createStyle(documentRef), root);
|
|
443
|
+
const clearStage = () => {
|
|
444
|
+
delete canvas.dataset.drawingRendered;
|
|
445
|
+
canvas.replaceChildren();
|
|
446
|
+
};
|
|
447
|
+
const getDrawingZoomState = () => ({
|
|
448
|
+
scale: zoom,
|
|
449
|
+
label: `${Math.round(zoom * 100)}%`,
|
|
450
|
+
canZoomIn: zoom < 3,
|
|
451
|
+
canZoomOut: zoom > 0.5,
|
|
452
|
+
canReset: zoom !== 1,
|
|
453
|
+
minScale: 0.5,
|
|
454
|
+
maxScale: 3,
|
|
455
|
+
});
|
|
456
|
+
const applyZoom = () => {
|
|
457
|
+
if (kind === 'excalidraw') {
|
|
458
|
+
canvas.style.transform = `scale(${zoom})`;
|
|
459
|
+
canvas.style.transformOrigin = 'top center';
|
|
460
|
+
canvas.style.zoom = '';
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
canvas.style.transform = '';
|
|
464
|
+
canvas.style.transformOrigin = '';
|
|
465
|
+
canvas.style.zoom = String(zoom);
|
|
466
|
+
}
|
|
467
|
+
zoomLabel.textContent = `${Math.round(zoom * 100)}%`;
|
|
468
|
+
};
|
|
469
|
+
const setZoom = (scale) => {
|
|
470
|
+
zoom = clampZoom(scale);
|
|
471
|
+
applyZoom();
|
|
472
|
+
zoomEmitter.emit();
|
|
473
|
+
return getDrawingZoomState();
|
|
474
|
+
};
|
|
475
|
+
const syncUi = () => {
|
|
476
|
+
state.hidden = status === 'ready';
|
|
477
|
+
state.classList.toggle('error', status === 'error');
|
|
478
|
+
state.textContent = status === 'error'
|
|
479
|
+
? errorMessage
|
|
480
|
+
: '正在加载官方绘图预览器...';
|
|
481
|
+
applyZoom();
|
|
482
|
+
};
|
|
483
|
+
const loadDrawing = async () => {
|
|
484
|
+
status = 'loading';
|
|
485
|
+
errorMessage = '';
|
|
486
|
+
zoom = 1;
|
|
487
|
+
clearStage();
|
|
488
|
+
syncUi();
|
|
489
|
+
try {
|
|
490
|
+
const text = await readFileViewerText(buffer);
|
|
491
|
+
if (disposed) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (kind === 'excalidraw') {
|
|
495
|
+
await renderExcalidraw(documentRef, text, canvas);
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
await renderDrawio(documentRef, text, canvas);
|
|
499
|
+
}
|
|
500
|
+
if (disposed) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
status = 'ready';
|
|
504
|
+
syncUi();
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
if (disposed) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
console.error(error);
|
|
511
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
512
|
+
status = 'error';
|
|
513
|
+
syncUi();
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
registerFileViewerZoomProvider(root, {
|
|
517
|
+
zoomIn: () => setZoom(zoom + 0.15),
|
|
518
|
+
zoomOut: () => setZoom(zoom - 0.15),
|
|
519
|
+
resetZoom: () => setZoom(1),
|
|
520
|
+
setZoom,
|
|
521
|
+
getState: getDrawingZoomState,
|
|
522
|
+
subscribe: zoomEmitter.subscribe,
|
|
523
|
+
});
|
|
524
|
+
zoomOutButton.addEventListener('click', () => setZoom(zoom - 0.15));
|
|
525
|
+
zoomInButton.addEventListener('click', () => setZoom(zoom + 0.15));
|
|
526
|
+
resetButton.addEventListener('click', () => setZoom(1));
|
|
527
|
+
syncUi();
|
|
528
|
+
void loadDrawing();
|
|
529
|
+
return {
|
|
530
|
+
$el: root,
|
|
531
|
+
unmount() {
|
|
532
|
+
disposed = true;
|
|
533
|
+
unregisterFileViewerZoomProvider(root);
|
|
534
|
+
target.replaceChildren();
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
}
|