web_ppt / frontend /public /VectorRenderManager.ts
CatPtain's picture
Upload VectorRenderManager.ts
21eec2e verified
/**
* 生产环境矢量图形渲染管理器
* 统一管理所有矢量图形的渲染策略和错误处理
*/
import { VECTOR_EXPORT_CONFIG } from '@/config/vectorExportConfig';
import { svg2Base64 } from './svg2Base64';
import { renderElementToBase64, getElementDimensions } from './canvasRenderer';
// 渲染策略枚举
export enum RenderStrategy {
SVG_SERIALIZATION = 'svg_serialization',
CANVAS_RENDER = 'canvas_render',
SIMPLIFIED_SVG = 'simplified_svg',
PLACEHOLDER = 'placeholder'
}
// 渲染结果接口
export interface RenderResult {
success: boolean;
data?: string;
strategy: RenderStrategy;
duration: number;
error?: Error;
metadata?: {
width: number;
height: number;
format: string;
size: number;
};
}
// 性能监控器
class PerformanceMonitor {
private static instance: PerformanceMonitor;
private metrics: Map<string, number[]> = new Map();
static getInstance(): PerformanceMonitor {
if (!this.instance) {
this.instance = new PerformanceMonitor();
}
return this.instance;
}
recordMetric(operation: string, duration: number): void {
if (!VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.MONITORING.ENABLED) return;
if (!this.metrics.has(operation)) {
this.metrics.set(operation, []);
}
const metrics = this.metrics.get(operation)!;
metrics.push(duration);
// 保持最近的记录数量
const maxCount = VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.MONITORING.MAX_METRICS_COUNT;
if (metrics.length > maxCount) {
metrics.shift();
}
}
getAverageTime(operation: string): number {
const metrics = this.metrics.get(operation);
if (!metrics || metrics.length === 0) return 0;
return metrics.reduce((sum, time) => sum + time, 0) / metrics.length;
}
getMetrics(): Record<string, { average: number; count: number; latest: number }> {
const result: Record<string, { average: number; count: number; latest: number }> = {};
for (const [operation, times] of this.metrics.entries()) {
if (times.length > 0) {
result[operation] = {
average: this.getAverageTime(operation),
count: times.length,
latest: times[times.length - 1]
};
}
}
return result;
}
}
// 错误处理器
class ErrorHandler {
static logError(error: Error, context: string, element?: Element): void {
const config = VECTOR_EXPORT_CONFIG.ERROR_CONFIG.LOGGING;
if (config.ERROR_ONLY && VECTOR_EXPORT_CONFIG.ENVIRONMENT.isProduction()) {
console.error(`VectorRenderManager[${context}]: ${error.message}`);
} else if (config.VERBOSE) {
console.error(`VectorRenderManager[${context}]:`, {
error: error.message,
stack: config.INCLUDE_STACK_TRACE ? error.stack : undefined,
element: element ? {
tagName: element.tagName,
className: element.className,
id: element.id
} : undefined
});
}
}
static async retry<T>(
operation: () => Promise<T>,
context: string,
maxAttempts: number = VECTOR_EXPORT_CONFIG.ERROR_CONFIG.RETRY.MAX_ATTEMPTS
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (attempt === maxAttempts) {
this.logError(lastError, `${context} (final attempt ${attempt}/${maxAttempts})`);
throw lastError;
}
const delay = VECTOR_EXPORT_CONFIG.ERROR_CONFIG.RETRY.DELAY_MS *
Math.pow(VECTOR_EXPORT_CONFIG.ERROR_CONFIG.RETRY.BACKOFF_MULTIPLIER, attempt - 1);
this.logError(lastError, `${context} (attempt ${attempt}/${maxAttempts}, retrying in ${delay}ms)`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError!;
}
}
// 主渲染管理器
export class VectorRenderManager {
private static instance: VectorRenderManager;
private monitor: PerformanceMonitor;
private constructor() {
this.monitor = PerformanceMonitor.getInstance();
}
static getInstance(): VectorRenderManager {
if (!this.instance) {
this.instance = new VectorRenderManager();
}
return this.instance;
}
/**
* 渲染元素为Base64图像
*/
async renderElement(element: Element, preferredStrategy?: RenderStrategy): Promise<RenderResult> {
const startTime = performance.now();
// 验证元素
const validation = this.validateElement(element);
if (!validation.valid) {
return {
success: false,
strategy: RenderStrategy.PLACEHOLDER,
duration: performance.now() - startTime,
error: new Error(validation.reason || 'Element validation failed')
};
}
// 确定渲染策略顺序
const strategies = this.getStrategies(element, preferredStrategy);
// 尝试每个策略
for (const strategy of strategies) {
try {
const result = await this.executeStrategy(element, strategy);
if (result.success) {
const duration = performance.now() - startTime;
this.monitor.recordMetric(`render_success_${strategy}`, duration);
return {
...result,
duration
};
}
} catch (error) {
ErrorHandler.logError(error as Error, `Strategy ${strategy}`, element);
continue;
}
}
// 所有策略都失败,返回占位符
const duration = performance.now() - startTime;
this.monitor.recordMetric('render_fallback_placeholder', duration);
return this.createPlaceholder(element, duration);
}
/**
* 验证元素是否可以渲染
*/
private validateElement(element: Element): { valid: boolean; reason?: string } {
if (!element) {
return { valid: false, reason: 'Element is null or undefined' };
}
// 检查元素尺寸
const dimensions = getElementDimensions(element as HTMLElement);
const config = VECTOR_EXPORT_CONFIG.VALIDATION_CONFIG.DIMENSIONS;
if (!dimensions.isValid) {
return { valid: false, reason: 'Element has invalid dimensions' };
}
if (dimensions.width < config.MIN_WIDTH || dimensions.height < config.MIN_HEIGHT) {
return { valid: false, reason: 'Element dimensions too small' };
}
if (dimensions.width > config.MAX_WIDTH || dimensions.height > config.MAX_HEIGHT) {
return { valid: false, reason: 'Element dimensions too large' };
}
return { valid: true };
}
/**
* 获取渲染策略顺序
*/
private getStrategies(element: Element, preferred?: RenderStrategy): RenderStrategy[] {
const isSVG = element.tagName.toLowerCase() === 'svg';
const isHuggingface = VECTOR_EXPORT_CONFIG.ENVIRONMENT.isHuggingface();
const canvasSupported = VECTOR_EXPORT_CONFIG.COMPATIBILITY_CONFIG.FEATURES.CANVAS_SUPPORT();
let strategies: RenderStrategy[] = [];
// 如果指定了首选策略,优先使用
if (preferred) {
strategies.push(preferred);
}
// 根据环境和元素类型确定策略顺序
if (isSVG) {
if (!strategies.includes(RenderStrategy.SVG_SERIALIZATION)) {
strategies.push(RenderStrategy.SVG_SERIALIZATION);
}
if (!strategies.includes(RenderStrategy.SIMPLIFIED_SVG)) {
strategies.push(RenderStrategy.SIMPLIFIED_SVG);
}
}
// Canvas渲染(如果支持且不是Huggingface环境)
if (canvasSupported && !isHuggingface && !strategies.includes(RenderStrategy.CANVAS_RENDER)) {
strategies.push(RenderStrategy.CANVAS_RENDER);
}
// 最后的占位符策略
if (!strategies.includes(RenderStrategy.PLACEHOLDER)) {
strategies.push(RenderStrategy.PLACEHOLDER);
}
return strategies;
}
/**
* 执行特定的渲染策略
*/
private async executeStrategy(element: Element, strategy: RenderStrategy): Promise<RenderResult> {
const startTime = performance.now();
switch (strategy) {
case RenderStrategy.SVG_SERIALIZATION:
return this.executeSVGSerialization(element, startTime);
case RenderStrategy.CANVAS_RENDER:
return this.executeCanvasRender(element, startTime);
case RenderStrategy.SIMPLIFIED_SVG:
return this.executeSimplifiedSVG(element, startTime);
case RenderStrategy.PLACEHOLDER:
return this.createPlaceholder(element, performance.now() - startTime);
default:
throw new Error(`Unknown strategy: ${strategy}`);
}
}
/**
* SVG序列化策略
*/
private async executeSVGSerialization(element: Element, startTime: number): Promise<RenderResult> {
try {
const base64 = svg2Base64(element);
const duration = performance.now() - startTime;
return {
success: true,
data: base64,
strategy: RenderStrategy.SVG_SERIALIZATION,
duration,
metadata: {
width: element.getBoundingClientRect().width,
height: element.getBoundingClientRect().height,
format: 'svg',
size: base64.length
}
};
} catch (error) {
return {
success: false,
strategy: RenderStrategy.SVG_SERIALIZATION,
duration: performance.now() - startTime,
error: error as Error
};
}
}
/**
* Canvas渲染策略
*/
private async executeCanvasRender(element: Element, startTime: number): Promise<RenderResult> {
try {
const base64 = await renderElementToBase64(element as HTMLElement, {
scale: VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.CANVAS.DEFAULT_SCALE,
backgroundColor: VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.CANVAS.BACKGROUND_COLOR,
timeout: VECTOR_EXPORT_CONFIG.PERFORMANCE_CONFIG.RENDER_TIMEOUT
});
const duration = performance.now() - startTime;
return {
success: true,
data: base64,
strategy: RenderStrategy.CANVAS_RENDER,
duration,
metadata: {
width: element.getBoundingClientRect().width,
height: element.getBoundingClientRect().height,
format: 'png',
size: base64.length
}
};
} catch (error) {
return {
success: false,
strategy: RenderStrategy.CANVAS_RENDER,
duration: performance.now() - startTime,
error: error as Error
};
}
}
/**
* 简化SVG策略
*/
private async executeSimplifiedSVG(element: Element, startTime: number): Promise<RenderResult> {
try {
const rect = element.getBoundingClientRect();
const width = rect.width || 100;
const height = rect.height || 100;
// 创建简化的SVG
const simplifiedSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<rect width="100%" height="100%" fill="#f0f0f0" stroke="#ccc" stroke-width="1"/>
<text x="50%" y="50%" text-anchor="middle" dy="0.3em" font-size="14" fill="#666">SVG</text>
</svg>`;
const base64 = `data:image/svg+xml;base64,${btoa(simplifiedSvg)}`;
const duration = performance.now() - startTime;
return {
success: true,
data: base64,
strategy: RenderStrategy.SIMPLIFIED_SVG,
duration,
metadata: {
width,
height,
format: 'svg',
size: base64.length
}
};
} catch (error) {
return {
success: false,
strategy: RenderStrategy.SIMPLIFIED_SVG,
duration: performance.now() - startTime,
error: error as Error
};
}
}
/**
* 创建占位符
*/
private createPlaceholder(element: Element, duration: number): RenderResult {
const rect = element.getBoundingClientRect();
const width = rect.width || 100;
const height = rect.height || 100;
const config = VECTOR_EXPORT_CONFIG.ERROR_CONFIG.FALLBACK;
const placeholderSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<rect width="100%" height="100%" fill="${config.PLACEHOLDER_COLOR}" stroke="#ddd" stroke-width="1"/>
<text x="50%" y="50%" text-anchor="middle" dy="0.3em" font-size="12" fill="#999">${config.PLACEHOLDER_TEXT}</text>
</svg>`;
const base64 = `data:image/svg+xml;base64,${btoa(placeholderSvg)}`;
return {
success: true,
data: base64,
strategy: RenderStrategy.PLACEHOLDER,
duration,
metadata: {
width,
height,
format: 'svg',
size: base64.length
}
};
}
/**
* 获取性能指标
*/
getPerformanceMetrics(): Record<string, { average: number; count: number; latest: number }> {
return this.monitor.getMetrics();
}
}
// 导出单例实例
export const vectorRenderManager = VectorRenderManager.getInstance();
export default vectorRenderManager;