CatPtain commited on
Commit
af7cc7f
·
verified ·
1 Parent(s): 9ecdcee

Upload canvasRenderer.ts

Browse files
Files changed (1) hide show
  1. frontend/src/utils/canvasRenderer.ts +192 -45
frontend/src/utils/canvasRenderer.ts CHANGED
@@ -29,46 +29,103 @@ export async function renderElementToCanvas(
29
  element: HTMLElement,
30
  options: RenderOptions = {}
31
  ): Promise<HTMLCanvasElement> {
32
- const {
33
- scale = 1,
34
- backgroundColor = null,
35
- useCORS = true,
36
- timeout = 5000
37
  } = options;
38
-
39
- return new Promise(async (resolve, reject) => {
40
- const timeoutId = setTimeout(() => {
41
- reject(new Error('Canvas rendering timeout'));
42
- }, timeout);
43
-
44
- try {
45
- // 动态导入html2canvas
46
- const html2canvas = await getHtml2Canvas();
47
-
48
- // 使用html2canvas进行渲染
49
- html2canvas(element, {
50
- scale,
51
- backgroundColor,
52
- useCORS,
53
- allowTaint: !useCORS,
54
- foreignObjectRendering: true,
55
- logging: false,
56
- width: element.offsetWidth,
57
- height: element.offsetHeight,
58
- windowWidth: element.offsetWidth,
59
- windowHeight: element.offsetHeight
60
- }).then((canvas) => {
61
- clearTimeout(timeoutId);
62
- resolve(canvas);
63
- }).catch((error) => {
64
- clearTimeout(timeoutId);
65
- reject(new Error(`html2canvas rendering failed: ${error.message}`));
66
- });
67
- } catch (error) {
68
- clearTimeout(timeoutId);
69
- reject(error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
71
- });
 
 
 
 
 
72
  }
73
 
74
  /**
@@ -81,12 +138,10 @@ export async function renderSVGToCanvas(
81
  svgElement: SVGElement,
82
  options: RenderOptions = {}
83
  ): Promise<HTMLCanvasElement> {
84
- // 对于SVG元素,我们可以直接使用html2canvas
85
- // 或者使用传统的SVG序列化方��
86
  const {
87
  scale = 1,
88
  backgroundColor = null,
89
- timeout = 5000
90
  } = options;
91
 
92
  return new Promise((resolve, reject) => {
@@ -95,10 +150,31 @@ export async function renderSVGToCanvas(
95
  }, timeout);
96
 
97
  try {
 
 
98
  // 获取SVG尺寸
99
  const rect = svgElement.getBoundingClientRect();
100
- const width = rect.width || svgElement.clientWidth || 300;
101
- const height = rect.height || svgElement.clientHeight || 200;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  // 创建Canvas
104
  const canvas = document.createElement('canvas');
@@ -120,17 +196,84 @@ export async function renderSVGToCanvas(
120
  ctx.fillRect(0, 0, width, height);
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  // 获取SVG的XML字符串
124
- const svgData = new XMLSerializer().serializeToString(svgElement);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
126
  const url = URL.createObjectURL(svgBlob);
127
 
128
  const img = new Image();
 
129
  img.onload = () => {
130
  try {
 
131
  ctx.drawImage(img, 0, 0, width, height);
132
  URL.revokeObjectURL(url);
133
  clearTimeout(timeoutId);
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  resolve(canvas);
135
  } catch (error) {
136
  URL.revokeObjectURL(url);
@@ -139,14 +282,18 @@ export async function renderSVGToCanvas(
139
  }
140
  };
141
 
142
- img.onerror = () => {
 
143
  URL.revokeObjectURL(url);
144
  clearTimeout(timeoutId);
145
  reject(new Error('SVG image loading failed'));
146
  };
147
 
 
148
  img.src = url;
 
149
  } catch (error) {
 
150
  clearTimeout(timeoutId);
151
  reject(new Error(`SVG serialization failed: ${error}`));
152
  }
 
29
  element: HTMLElement,
30
  options: RenderOptions = {}
31
  ): Promise<HTMLCanvasElement> {
32
+ const {
33
+ scale = 2, // 默认使用更高的缩放比例
34
+ backgroundColor = null, // 默认透明背景
35
+ useCORS = true,
36
+ timeout = 15000 // 增加超时时间
37
  } = options;
38
+
39
+ try {
40
+ // 动态导入html2canvas以减少初始包大小
41
+ const html2canvas = (await import('html2canvas')).default;
42
+
43
+ // 获取元素的实际尺寸
44
+ const rect = element.getBoundingClientRect();
45
+ const computedStyle = window.getComputedStyle(element);
46
+
47
+ const renderWidth = Math.max(
48
+ rect.width,
49
+ element.offsetWidth,
50
+ parseFloat(computedStyle.width) || 0
51
+ );
52
+ const renderHeight = Math.max(
53
+ rect.height,
54
+ element.offsetHeight,
55
+ parseFloat(computedStyle.height) || 0
56
+ );
57
+
58
+ console.log('Canvas rendering dimensions:', {
59
+ boundingRect: { width: rect.width, height: rect.height },
60
+ offset: { width: element.offsetWidth, height: element.offsetHeight },
61
+ computed: { width: computedStyle.width, height: computedStyle.height },
62
+ final: { width: renderWidth, height: renderHeight }
63
+ });
64
+
65
+ const canvas = await html2canvas(element, {
66
+ scale,
67
+ backgroundColor,
68
+ useCORS,
69
+ allowTaint: false,
70
+ foreignObjectRendering: true,
71
+ logging: false,
72
+ width: renderWidth || 100,
73
+ height: renderHeight || 100,
74
+ windowWidth: window.innerWidth,
75
+ windowHeight: window.innerHeight,
76
+ scrollX: 0,
77
+ scrollY: 0,
78
+ onclone: (clonedDoc, clonedElement) => {
79
+ // 在克隆的文档中应用样式修复
80
+ const targetElement = clonedElement || clonedDoc.querySelector(`[data-element-id="${element.getAttribute('data-element-id')}"]`);
81
+ if (targetElement) {
82
+ // 确保SVG元素正确渲染
83
+ const svgElements = targetElement.querySelectorAll('svg');
84
+ svgElements.forEach(svg => {
85
+ // 设置SVG尺寸
86
+ if (!svg.getAttribute('width') || !svg.getAttribute('height')) {
87
+ const svgRect = svg.getBoundingClientRect();
88
+ if (svgRect.width > 0 && svgRect.height > 0) {
89
+ svg.setAttribute('width', svgRect.width.toString());
90
+ svg.setAttribute('height', svgRect.height.toString());
91
+ } else {
92
+ svg.setAttribute('width', renderWidth.toString());
93
+ svg.setAttribute('height', renderHeight.toString());
94
+ }
95
+ }
96
+
97
+ // 确保viewBox存在
98
+ if (!svg.getAttribute('viewBox')) {
99
+ const width = svg.getAttribute('width') || renderWidth;
100
+ const height = svg.getAttribute('height') || renderHeight;
101
+ svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
102
+ }
103
+
104
+ // 添加必要的命名空间
105
+ if (!svg.getAttribute('xmlns')) {
106
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
107
+ }
108
+ });
109
+
110
+ // 确保元素可见
111
+ const style = targetElement.style;
112
+ style.visibility = 'visible';
113
+ style.opacity = '1';
114
+ style.display = style.display === 'none' ? 'block' : style.display;
115
+ }
116
+ }
117
+ });
118
+
119
+ // 验证生成的Canvas
120
+ if (canvas.width === 0 || canvas.height === 0) {
121
+ throw new Error('Generated canvas has zero dimensions');
122
  }
123
+
124
+ return canvas;
125
+ } catch (error) {
126
+ console.error('Canvas rendering failed:', error);
127
+ throw new Error(`Canvas rendering failed: ${error}`);
128
+ }
129
  }
130
 
131
  /**
 
138
  svgElement: SVGElement,
139
  options: RenderOptions = {}
140
  ): Promise<HTMLCanvasElement> {
 
 
141
  const {
142
  scale = 1,
143
  backgroundColor = null,
144
+ timeout = 10000 // 增加超时时间
145
  } = options;
146
 
147
  return new Promise((resolve, reject) => {
 
150
  }, timeout);
151
 
152
  try {
153
+ console.log('renderSVGToCanvas: Starting SVG to Canvas conversion');
154
+
155
  // 获取SVG尺寸
156
  const rect = svgElement.getBoundingClientRect();
157
+ let width = rect.width || svgElement.clientWidth;
158
+ let height = rect.height || svgElement.clientHeight;
159
+
160
+ // 从SVG属性获取尺寸作为备选
161
+ if (width <= 0 || height <= 0) {
162
+ const svgWidth = svgElement.getAttribute('width');
163
+ const svgHeight = svgElement.getAttribute('height');
164
+ if (svgWidth && svgHeight) {
165
+ width = parseFloat(svgWidth);
166
+ height = parseFloat(svgHeight);
167
+ }
168
+ }
169
+
170
+ // 使用默认尺寸作为最后备选
171
+ if (width <= 0 || height <= 0) {
172
+ width = 300;
173
+ height = 200;
174
+ console.warn('renderSVGToCanvas: Using default dimensions due to zero size');
175
+ }
176
+
177
+ console.log('renderSVGToCanvas: SVG dimensions:', { width, height });
178
 
179
  // 创建Canvas
180
  const canvas = document.createElement('canvas');
 
196
  ctx.fillRect(0, 0, width, height);
197
  }
198
 
199
+ // 克隆并优化SVG元素
200
+ const clonedSVG = svgElement.cloneNode(true) as SVGElement;
201
+
202
+ // 确保SVG有正确的命名空间和属性
203
+ clonedSVG.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
204
+ clonedSVG.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
205
+
206
+ // 设置明确的尺寸
207
+ if (!clonedSVG.getAttribute('width')) {
208
+ clonedSVG.setAttribute('width', width.toString());
209
+ }
210
+ if (!clonedSVG.getAttribute('height')) {
211
+ clonedSVG.setAttribute('height', height.toString());
212
+ }
213
+
214
+ // 设置viewBox
215
+ if (!clonedSVG.getAttribute('viewBox')) {
216
+ clonedSVG.setAttribute('viewBox', `0 0 ${width} ${height}`);
217
+ }
218
+
219
+ // 移除可能导致问题的属性
220
+ const problematicAttrs = ['vector-effect'];
221
+ const removeProblematicAttrs = (element: Element) => {
222
+ problematicAttrs.forEach(attr => {
223
+ if (element.hasAttribute(attr)) {
224
+ element.removeAttribute(attr);
225
+ }
226
+ });
227
+
228
+ // 递归处理子元素
229
+ Array.from(element.children).forEach(child => {
230
+ removeProblematicAttrs(child);
231
+ });
232
+ };
233
+
234
+ removeProblematicAttrs(clonedSVG);
235
+
236
  // 获取SVG的XML字符串
237
+ let svgData = new XMLSerializer().serializeToString(clonedSVG);
238
+
239
+ // 清理和优化SVG字符串
240
+ svgData = svgData.replace(/vector-effect="[^"]*"/g, '');
241
+
242
+ // 确保SVG字符串格式正确
243
+ if (!svgData.includes('xmlns="http://www.w3.org/2000/svg"')) {
244
+ svgData = svgData.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
245
+ }
246
+
247
+ console.log('renderSVGToCanvas: SVG data prepared:', {
248
+ length: svgData.length,
249
+ hasNamespace: svgData.includes('xmlns="http://www.w3.org/2000/svg"'),
250
+ preview: svgData.substring(0, 200) + '...'
251
+ });
252
+
253
  const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
254
  const url = URL.createObjectURL(svgBlob);
255
 
256
  const img = new Image();
257
+
258
  img.onload = () => {
259
  try {
260
+ console.log('renderSVGToCanvas: Image loaded successfully, drawing to canvas');
261
  ctx.drawImage(img, 0, 0, width, height);
262
  URL.revokeObjectURL(url);
263
  clearTimeout(timeoutId);
264
+
265
+ // 验证Canvas内容
266
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
267
+ const hasContent = imageData.data.some((value, index) => {
268
+ // 检查非透明像素 (alpha通道)
269
+ return index % 4 === 3 && value > 0;
270
+ });
271
+
272
+ if (!hasContent) {
273
+ console.warn('renderSVGToCanvas: Canvas appears to be empty after drawing');
274
+ }
275
+
276
+ console.log('renderSVGToCanvas: Canvas rendering completed successfully');
277
  resolve(canvas);
278
  } catch (error) {
279
  URL.revokeObjectURL(url);
 
282
  }
283
  };
284
 
285
+ img.onerror = (error) => {
286
+ console.error('renderSVGToCanvas: Image loading failed:', error);
287
  URL.revokeObjectURL(url);
288
  clearTimeout(timeoutId);
289
  reject(new Error('SVG image loading failed'));
290
  };
291
 
292
+ // 设置图片源
293
  img.src = url;
294
+
295
  } catch (error) {
296
+ console.error('renderSVGToCanvas: SVG serialization failed:', error);
297
  clearTimeout(timeoutId);
298
  reject(new Error(`SVG serialization failed: ${error}`));
299
  }