CatPtain commited on
Commit
21eec2e
·
verified ·
1 Parent(s): 744cf12

Upload VectorRenderManager.ts

Browse files
frontend/public/VectorRenderManager.ts ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * 生产环境矢量图形渲染管理器
3
+ * 统一管理所有矢量图形的渲染策略和错误处理
4
+ */
5
+
6
+ import { VECTOR_EXPORT_CONFIG } from '@/config/vectorExportConfig';
7
+ import { svg2Base64 } from './svg2Base64';
8
+ import { renderElementToBase64, getElementDimensions } from './canvasRenderer';
9
+
10
+ // 渲染策略枚举
11
+ export enum RenderStrategy {
12
+ SVG_SERIALIZATION = 'svg_serialization',
13
+ CANVAS_RENDER = 'canvas_render',
14
+ SIMPLIFIED_SVG = 'simplified_svg',
15
+ PLACEHOLDER = 'placeholder'
16
+ }
17
+
18
+ // 渲染结果接口
19
+ export interface RenderResult {
20
+ success: boolean;
21
+ data?: string;
22
+ strategy: RenderStrategy;
23
+ duration: number;
24
+ error?: Error;
25
+ metadata?: {
26
+ width: number;
27
+ height: number;
28
+ format: string;
29
+ size: number;
30
+ };
31
+ }
32
+
33
+ // 性能监控器
34
+ class PerformanceMonitor {
35
+ private static instance: PerformanceMonitor;
36
+ private metrics: Map<string, number[]> = new Map();
37
+
38
+ static getInstance(): PerformanceMonitor {
39
+ if (!this.instance) {
40
+ this.instance = new PerformanceMonitor();
41
+ }
42
+ return this.instance;
43
+ }
44
+
45
+ recordMetric(operation: string, duration: number): void {
46
+ if (!VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.MONITORING.ENABLED) return;
47
+
48
+ if (!this.metrics.has(operation)) {
49
+ this.metrics.set(operation, []);
50
+ }
51
+
52
+ const metrics = this.metrics.get(operation)!;
53
+ metrics.push(duration);
54
+
55
+ // 保持最近的记录数量
56
+ const maxCount = VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.MONITORING.MAX_METRICS_COUNT;
57
+ if (metrics.length > maxCount) {
58
+ metrics.shift();
59
+ }
60
+ }
61
+
62
+ getAverageTime(operation: string): number {
63
+ const metrics = this.metrics.get(operation);
64
+ if (!metrics || metrics.length === 0) return 0;
65
+ return metrics.reduce((sum, time) => sum + time, 0) / metrics.length;
66
+ }
67
+
68
+ getMetrics(): Record<string, { average: number; count: number; latest: number }> {
69
+ const result: Record<string, { average: number; count: number; latest: number }> = {};
70
+
71
+ for (const [operation, times] of this.metrics.entries()) {
72
+ if (times.length > 0) {
73
+ result[operation] = {
74
+ average: this.getAverageTime(operation),
75
+ count: times.length,
76
+ latest: times[times.length - 1]
77
+ };
78
+ }
79
+ }
80
+
81
+ return result;
82
+ }
83
+ }
84
+
85
+ // 错误处理器
86
+ class ErrorHandler {
87
+ static logError(error: Error, context: string, element?: Element): void {
88
+ const config = VECTOR_EXPORT_CONFIG.ERROR_CONFIG.LOGGING;
89
+
90
+ if (config.ERROR_ONLY && VECTOR_EXPORT_CONFIG.ENVIRONMENT.isProduction()) {
91
+ console.error(`VectorRenderManager[${context}]: ${error.message}`);
92
+ } else if (config.VERBOSE) {
93
+ console.error(`VectorRenderManager[${context}]:`, {
94
+ error: error.message,
95
+ stack: config.INCLUDE_STACK_TRACE ? error.stack : undefined,
96
+ element: element ? {
97
+ tagName: element.tagName,
98
+ className: element.className,
99
+ id: element.id
100
+ } : undefined
101
+ });
102
+ }
103
+ }
104
+
105
+ static async retry<T>(
106
+ operation: () => Promise<T>,
107
+ context: string,
108
+ maxAttempts: number = VECTOR_EXPORT_CONFIG.ERROR_CONFIG.RETRY.MAX_ATTEMPTS
109
+ ): Promise<T> {
110
+ let lastError: Error;
111
+
112
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
113
+ try {
114
+ return await operation();
115
+ } catch (error) {
116
+ lastError = error as Error;
117
+
118
+ if (attempt === maxAttempts) {
119
+ this.logError(lastError, `${context} (final attempt ${attempt}/${maxAttempts})`);
120
+ throw lastError;
121
+ }
122
+
123
+ const delay = VECTOR_EXPORT_CONFIG.ERROR_CONFIG.RETRY.DELAY_MS *
124
+ Math.pow(VECTOR_EXPORT_CONFIG.ERROR_CONFIG.RETRY.BACKOFF_MULTIPLIER, attempt - 1);
125
+
126
+ this.logError(lastError, `${context} (attempt ${attempt}/${maxAttempts}, retrying in ${delay}ms)`);
127
+ await new Promise(resolve => setTimeout(resolve, delay));
128
+ }
129
+ }
130
+
131
+ throw lastError!;
132
+ }
133
+ }
134
+
135
+ // 主渲染管理器
136
+ export class VectorRenderManager {
137
+ private static instance: VectorRenderManager;
138
+ private monitor: PerformanceMonitor;
139
+
140
+ private constructor() {
141
+ this.monitor = PerformanceMonitor.getInstance();
142
+ }
143
+
144
+ static getInstance(): VectorRenderManager {
145
+ if (!this.instance) {
146
+ this.instance = new VectorRenderManager();
147
+ }
148
+ return this.instance;
149
+ }
150
+
151
+ /**
152
+ * 渲染元素为Base64图像
153
+ */
154
+ async renderElement(element: Element, preferredStrategy?: RenderStrategy): Promise<RenderResult> {
155
+ const startTime = performance.now();
156
+
157
+ // 验证元素
158
+ const validation = this.validateElement(element);
159
+ if (!validation.valid) {
160
+ return {
161
+ success: false,
162
+ strategy: RenderStrategy.PLACEHOLDER,
163
+ duration: performance.now() - startTime,
164
+ error: new Error(validation.reason || 'Element validation failed')
165
+ };
166
+ }
167
+
168
+ // 确定渲染策略顺序
169
+ const strategies = this.getStrategies(element, preferredStrategy);
170
+
171
+ // 尝试每个策略
172
+ for (const strategy of strategies) {
173
+ try {
174
+ const result = await this.executeStrategy(element, strategy);
175
+
176
+ if (result.success) {
177
+ const duration = performance.now() - startTime;
178
+ this.monitor.recordMetric(`render_success_${strategy}`, duration);
179
+
180
+ return {
181
+ ...result,
182
+ duration
183
+ };
184
+ }
185
+ } catch (error) {
186
+ ErrorHandler.logError(error as Error, `Strategy ${strategy}`, element);
187
+ continue;
188
+ }
189
+ }
190
+
191
+ // 所有策略都失败,返回占位符
192
+ const duration = performance.now() - startTime;
193
+ this.monitor.recordMetric('render_fallback_placeholder', duration);
194
+
195
+ return this.createPlaceholder(element, duration);
196
+ }
197
+
198
+ /**
199
+ * 验证元素是否可以渲染
200
+ */
201
+ private validateElement(element: Element): { valid: boolean; reason?: string } {
202
+ if (!element) {
203
+ return { valid: false, reason: 'Element is null or undefined' };
204
+ }
205
+
206
+ // 检查元素尺寸
207
+ const dimensions = getElementDimensions(element as HTMLElement);
208
+ const config = VECTOR_EXPORT_CONFIG.VALIDATION_CONFIG.DIMENSIONS;
209
+
210
+ if (!dimensions.isValid) {
211
+ return { valid: false, reason: 'Element has invalid dimensions' };
212
+ }
213
+
214
+ if (dimensions.width < config.MIN_WIDTH || dimensions.height < config.MIN_HEIGHT) {
215
+ return { valid: false, reason: 'Element dimensions too small' };
216
+ }
217
+
218
+ if (dimensions.width > config.MAX_WIDTH || dimensions.height > config.MAX_HEIGHT) {
219
+ return { valid: false, reason: 'Element dimensions too large' };
220
+ }
221
+
222
+ return { valid: true };
223
+ }
224
+
225
+ /**
226
+ * 获取渲染策略顺序
227
+ */
228
+ private getStrategies(element: Element, preferred?: RenderStrategy): RenderStrategy[] {
229
+ const isSVG = element.tagName.toLowerCase() === 'svg';
230
+ const isHuggingface = VECTOR_EXPORT_CONFIG.ENVIRONMENT.isHuggingface();
231
+ const canvasSupported = VECTOR_EXPORT_CONFIG.COMPATIBILITY_CONFIG.FEATURES.CANVAS_SUPPORT();
232
+
233
+ let strategies: RenderStrategy[] = [];
234
+
235
+ // 如果指定了首选策略,优先使用
236
+ if (preferred) {
237
+ strategies.push(preferred);
238
+ }
239
+
240
+ // 根据环境和元素类型确定策略顺序
241
+ if (isSVG) {
242
+ if (!strategies.includes(RenderStrategy.SVG_SERIALIZATION)) {
243
+ strategies.push(RenderStrategy.SVG_SERIALIZATION);
244
+ }
245
+ if (!strategies.includes(RenderStrategy.SIMPLIFIED_SVG)) {
246
+ strategies.push(RenderStrategy.SIMPLIFIED_SVG);
247
+ }
248
+ }
249
+
250
+ // Canvas渲染(如果支持且不是Huggingface环境)
251
+ if (canvasSupported && !isHuggingface && !strategies.includes(RenderStrategy.CANVAS_RENDER)) {
252
+ strategies.push(RenderStrategy.CANVAS_RENDER);
253
+ }
254
+
255
+ // 最后的占位符策略
256
+ if (!strategies.includes(RenderStrategy.PLACEHOLDER)) {
257
+ strategies.push(RenderStrategy.PLACEHOLDER);
258
+ }
259
+
260
+ return strategies;
261
+ }
262
+
263
+ /**
264
+ * 执行特定的渲染策略
265
+ */
266
+ private async executeStrategy(element: Element, strategy: RenderStrategy): Promise<RenderResult> {
267
+ const startTime = performance.now();
268
+
269
+ switch (strategy) {
270
+ case RenderStrategy.SVG_SERIALIZATION:
271
+ return this.executeSVGSerialization(element, startTime);
272
+
273
+ case RenderStrategy.CANVAS_RENDER:
274
+ return this.executeCanvasRender(element, startTime);
275
+
276
+ case RenderStrategy.SIMPLIFIED_SVG:
277
+ return this.executeSimplifiedSVG(element, startTime);
278
+
279
+ case RenderStrategy.PLACEHOLDER:
280
+ return this.createPlaceholder(element, performance.now() - startTime);
281
+
282
+ default:
283
+ throw new Error(`Unknown strategy: ${strategy}`);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * SVG序列化策略
289
+ */
290
+ private async executeSVGSerialization(element: Element, startTime: number): Promise<RenderResult> {
291
+ try {
292
+ const base64 = svg2Base64(element);
293
+ const duration = performance.now() - startTime;
294
+
295
+ return {
296
+ success: true,
297
+ data: base64,
298
+ strategy: RenderStrategy.SVG_SERIALIZATION,
299
+ duration,
300
+ metadata: {
301
+ width: element.getBoundingClientRect().width,
302
+ height: element.getBoundingClientRect().height,
303
+ format: 'svg',
304
+ size: base64.length
305
+ }
306
+ };
307
+ } catch (error) {
308
+ return {
309
+ success: false,
310
+ strategy: RenderStrategy.SVG_SERIALIZATION,
311
+ duration: performance.now() - startTime,
312
+ error: error as Error
313
+ };
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Canvas渲染策略
319
+ */
320
+ private async executeCanvasRender(element: Element, startTime: number): Promise<RenderResult> {
321
+ try {
322
+ const base64 = await renderElementToBase64(element as HTMLElement, {
323
+ scale: VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.CANVAS.DEFAULT_SCALE,
324
+ backgroundColor: VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.CANVAS.BACKGROUND_COLOR,
325
+ timeout: VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.RENDER_TIMEOUT
326
+ });
327
+
328
+ const duration = performance.now() - startTime;
329
+
330
+ return {
331
+ success: true,
332
+ data: base64,
333
+ strategy: RenderStrategy.CANVAS_RENDER,
334
+ duration,
335
+ metadata: {
336
+ width: element.getBoundingClientRect().width,
337
+ height: element.getBoundingClientRect().height,
338
+ format: 'png',
339
+ size: base64.length
340
+ }
341
+ };
342
+ } catch (error) {
343
+ return {
344
+ success: false,
345
+ strategy: RenderStrategy.CANVAS_RENDER,
346
+ duration: performance.now() - startTime,
347
+ error: error as Error
348
+ };
349
+ }
350
+ }
351
+
352
+ /**
353
+ * 简化SVG策略
354
+ */
355
+ private async executeSimplifiedSVG(element: Element, startTime: number): Promise<RenderResult> {
356
+ try {
357
+ const rect = element.getBoundingClientRect();
358
+ const width = rect.width || 100;
359
+ const height = rect.height || 100;
360
+
361
+ // 创建简化的SVG
362
+ const simplifiedSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
363
+ <rect width="100%" height="100%" fill="#f0f0f0" stroke="#ccc" stroke-width="1"/>
364
+ <text x="50%" y="50%" text-anchor="middle" dy="0.3em" font-size="14" fill="#666">SVG</text>
365
+ </svg>`;
366
+
367
+ const base64 = `data:image/svg+xml;base64,${btoa(simplifiedSvg)}`;
368
+ const duration = performance.now() - startTime;
369
+
370
+ return {
371
+ success: true,
372
+ data: base64,
373
+ strategy: RenderStrategy.SIMPLIFIED_SVG,
374
+ duration,
375
+ metadata: {
376
+ width,
377
+ height,
378
+ format: 'svg',
379
+ size: base64.length
380
+ }
381
+ };
382
+ } catch (error) {
383
+ return {
384
+ success: false,
385
+ strategy: RenderStrategy.SIMPLIFIED_SVG,
386
+ duration: performance.now() - startTime,
387
+ error: error as Error
388
+ };
389
+ }
390
+ }
391
+
392
+ /**
393
+ * 创建占位符
394
+ */
395
+ private createPlaceholder(element: Element, duration: number): RenderResult {
396
+ const rect = element.getBoundingClientRect();
397
+ const width = rect.width || 100;
398
+ const height = rect.height || 100;
399
+ const config = VECTOR_EXPORT_CONFIG.ERROR_CONFIG.FALLBACK;
400
+
401
+ const placeholderSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
402
+ <rect width="100%" height="100%" fill="${config.PLACEHOLDER_COLOR}" stroke="#ddd" stroke-width="1"/>
403
+ <text x="50%" y="50%" text-anchor="middle" dy="0.3em" font-size="12" fill="#999">${config.PLACEHOLDER_TEXT}</text>
404
+ </svg>`;
405
+
406
+ const base64 = `data:image/svg+xml;base64,${btoa(placeholderSvg)}`;
407
+
408
+ return {
409
+ success: true,
410
+ data: base64,
411
+ strategy: RenderStrategy.PLACEHOLDER,
412
+ duration,
413
+ metadata: {
414
+ width,
415
+ height,
416
+ format: 'svg',
417
+ size: base64.length
418
+ }
419
+ };
420
+ }
421
+
422
+ /**
423
+ * 获取性能指标
424
+ */
425
+ getPerformanceMetrics(): Record<string, { average: number; count: number; latest: number }> {
426
+ return this.monitor.getMetrics();
427
+ }
428
+ }
429
+
430
+ // 导出单例实例
431
+ export const vectorRenderManager = VectorRenderManager.getInstance();
432
+ export default vectorRenderManager;