CatPtain commited on
Commit
4822fa6
·
verified ·
1 Parent(s): f27eeb4

Upload useExport.ts

Browse files
Files changed (1) hide show
  1. frontend/src/hooks/useExport.ts +221 -66
frontend/src/hooks/useExport.ts CHANGED
@@ -12,6 +12,7 @@ import { type AST, toAST } from '@/utils/htmlParser'
12
  import { type SvgPoints, toPoints } from '@/utils/svgPathParser'
13
  import { encrypt } from '@/utils/crypto'
14
  import { svg2Base64 } from '@/utils/svg2Base64'
 
15
  import message from '@/utils/message'
16
 
17
  interface ExportImageConfig {
@@ -996,8 +997,7 @@ export default () => {
996
 
997
  else if (el.type === 'shape') {
998
  if (el.special) {
999
- // 调试:记录元素信息
1000
- console.log(`Processing special shape ${el.id}:`, {
1001
  type: el.type,
1002
  special: el.special,
1003
  width: el.width,
@@ -1006,71 +1006,164 @@ export default () => {
1006
  top: el.top
1007
  });
1008
 
1009
- // 尝试多种选择器策略
1010
- let svgRef = document.querySelector(`.thumbnail-list .base-element-${el.id} svg`) as HTMLElement;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1011
 
1012
- if (!svgRef) {
1013
- // 尝试其他可能的选择器
1014
- svgRef = document.querySelector(`[data-element-id="${el.id}"] svg`) as HTMLElement;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1015
  }
1016
 
1017
- if (!svgRef) {
1018
- svgRef = document.querySelector(`#element-${el.id} svg`) as HTMLElement;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1019
  }
1020
 
1021
- if (!svgRef) {
1022
- // 尝试查找所有SVG元素并匹配
1023
  const allSvgs = document.querySelectorAll('svg');
1024
- console.log(`Found ${allSvgs.length} SVG elements in document`);
1025
 
1026
- for (let i = 0; i < allSvgs.length; i++) {
1027
- const svg = allSvgs[i] as HTMLElement;
1028
  const parent = svg.closest(`[class*="${el.id}"]`);
1029
  if (parent) {
1030
- svgRef = svg;
1031
  console.log(`Found SVG for ${el.id} using parent matching`);
1032
  break;
1033
  }
1034
  }
1035
  }
1036
 
1037
- // 改进的尺寸检查逻辑
1038
- if (!svgRef) {
1039
- console.warn(`SVG element not found for shape ${el.id} after trying multiple selectors`);
1040
- console.log('Available elements with class containing element ID:',
1041
- document.querySelectorAll(`[class*="${el.id}"]`));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1042
  continue;
1043
  }
1044
 
1045
- console.log(`Found SVG element for ${el.id}:`, {
1046
- tagName: svgRef.tagName,
1047
- className: svgRef.className,
1048
- innerHTML: svgRef.innerHTML.substring(0, 200) + '...'
1049
- });
1050
-
1051
- // 获取多种尺寸信息进行判断
1052
- const clientWidth = svgRef.clientWidth;
1053
- const clientHeight = svgRef.clientHeight;
1054
- const boundingRect = svgRef.getBoundingClientRect();
1055
- const computedStyle = window.getComputedStyle(svgRef);
1056
-
1057
- // 更智能的尺寸判断
1058
- const hasValidSize = (
1059
- (clientWidth > 0 && clientHeight > 0) ||
1060
- (boundingRect.width > 0 && boundingRect.height > 0) ||
1061
- (parseFloat(computedStyle.width) > 0 && parseFloat(computedStyle.height) > 0)
1062
- );
1063
 
1064
- if (!hasValidSize) {
1065
- console.warn(`Invalid SVG dimensions for shape ${el.id}:`, {
1066
- clientWidth,
1067
- clientHeight,
1068
- boundingRect: { width: boundingRect.width, height: boundingRect.height },
1069
- computedStyle: { width: computedStyle.width, height: computedStyle.height }
1070
- });
1071
-
1072
- // 降级到普通形状处理
1073
- console.info(`Falling back to path-based export for shape ${el.id}`);
1074
  const scale = {
1075
  x: el.width / el.viewBox[0],
1076
  y: el.height / el.viewBox[1],
@@ -1111,35 +1204,96 @@ export default () => {
1111
  continue;
1112
  }
1113
 
1114
- // SVG序列化with错误处理
1115
- let base64SVG;
1116
  try {
1117
- console.log(`Starting SVG serialization for ${el.id}`);
1118
- console.log('SVG content before serialization:', svgRef.outerHTML.substring(0, 500) + '...');
 
 
 
 
 
 
 
 
 
1119
 
1120
- base64SVG = svg2Base64(svgRef);
1121
 
1122
- console.log(`SVG serialization result for ${el.id}:`, {
1123
- success: !!base64SVG,
1124
- length: base64SVG ? base64SVG.length : 0,
1125
- preview: base64SVG ? base64SVG.substring(0, 100) + '...' : 'null'
1126
  });
1127
 
1128
- if (!base64SVG || base64SVG === 'data:image/svg+xml;base64,') {
1129
- throw new Error('SVG serialization returned empty result');
1130
  }
1131
  } catch (error) {
1132
- console.error(`SVG serialization failed for shape ${el.id}:`, error);
1133
- console.log('SVG element that failed:', svgRef);
1134
- console.log('SVG outerHTML:', svgRef.outerHTML);
1135
 
1136
- // 尝试降级处理
1137
- console.log(`Attempting fallback processing for shape ${el.id}`);
1138
- continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1139
  }
1140
 
 
1141
  const options: pptxgen.ImageProps = {
1142
- data: base64SVG,
1143
  x: el.left / ratioPx2Inch.value,
1144
  y: el.top / ratioPx2Inch.value,
1145
  w: el.width / ratioPx2Inch.value,
@@ -1153,6 +1307,7 @@ export default () => {
1153
  if (linkOption) options.hyperlink = linkOption
1154
  }
1155
 
 
1156
  pptxSlide.addImage(options)
1157
  }
1158
  else {
 
12
  import { type SvgPoints, toPoints } from '@/utils/svgPathParser'
13
  import { encrypt } from '@/utils/crypto'
14
  import { svg2Base64 } from '@/utils/svg2Base64'
15
+ import { renderElementToBase64, isCanvasRenderSupported, getElementDimensions } from '@/utils/canvasRenderer'
16
  import message from '@/utils/message'
17
 
18
  interface ExportImageConfig {
 
997
 
998
  else if (el.type === 'shape') {
999
  if (el.special) {
1000
+ console.log(`Processing special shape ${el.id} with Canvas rendering:`, {
 
1001
  type: el.type,
1002
  special: el.special,
1003
  width: el.width,
 
1006
  top: el.top
1007
  });
1008
 
1009
+ // 检查Canvas渲染支持
1010
+ if (!isCanvasRenderSupported()) {
1011
+ console.warn('Canvas rendering not supported, falling back to path-based export');
1012
+ // 降级到普通形状处理的代码保持不变
1013
+ const scale = {
1014
+ x: el.width / el.viewBox[0],
1015
+ y: el.height / el.viewBox[1],
1016
+ };
1017
+ const points = formatPoints(toPoints(el.path), scale);
1018
+
1019
+ let fillColor = formatColor(el.fill);
1020
+ if (el.gradient) {
1021
+ const colors = el.gradient.colors;
1022
+ const color1 = colors[0].color;
1023
+ const color2 = colors[colors.length - 1].color;
1024
+ const color = tinycolor.mix(color1, color2).toHexString();
1025
+ fillColor = formatColor(color);
1026
+ }
1027
+ if (el.pattern) fillColor = formatColor('#00000000');
1028
+ const opacity = el.opacity === undefined ? 1 : el.opacity;
1029
+
1030
+ const fallbackOptions: pptxgen.ShapeProps = {
1031
+ x: el.left / ratioPx2Inch.value,
1032
+ y: el.top / ratioPx2Inch.value,
1033
+ w: el.width / ratioPx2Inch.value,
1034
+ h: el.height / ratioPx2Inch.value,
1035
+ fill: { color: fillColor.color, transparency: (1 - fillColor.alpha * opacity) * 100 },
1036
+ points,
1037
+ };
1038
+
1039
+ if (el.flipH) fallbackOptions.flipH = el.flipH;
1040
+ if (el.flipV) fallbackOptions.flipV = el.flipV;
1041
+ if (el.shadow) fallbackOptions.shadow = getShadowOption(el.shadow);
1042
+ if (el.outline?.width) fallbackOptions.line = getOutlineOption(el.outline);
1043
+ if (el.rotate) fallbackOptions.rotate = el.rotate;
1044
+ if (el.link) {
1045
+ const linkOption = getLinkOption(el.link);
1046
+ if (linkOption) fallbackOptions.hyperlink = linkOption;
1047
+ }
1048
+
1049
+ pptxSlide.addShape('custGeom' as pptxgen.ShapeType, fallbackOptions);
1050
+ continue;
1051
+ }
1052
+
1053
+ // 尝试多种选择器策略查找元素
1054
+ let targetElement: HTMLElement | null = null;
1055
+
1056
+ // 优先查找包含SVG的容器元素
1057
+ const containerSelectors = [
1058
+ `.thumbnail-list .base-element-${el.id}`,
1059
+ `[data-element-id="${el.id}"]`,
1060
+ `#element-${el.id}`,
1061
+ `[class*="${el.id}"]`
1062
+ ];
1063
 
1064
+ for (const selector of containerSelectors) {
1065
+ const container = document.querySelector(selector) as HTMLElement;
1066
+ if (container) {
1067
+ // 检查容器是否包含SVG
1068
+ const svgInContainer = container.querySelector('svg');
1069
+ if (svgInContainer) {
1070
+ targetElement = container;
1071
+ console.log(`Found container with SVG for ${el.id} using selector: ${selector}`);
1072
+ break;
1073
+ }
1074
+ // 如果容器本身就是我们要的元素
1075
+ if (container.tagName.toLowerCase() === 'svg' || container.children.length > 0) {
1076
+ targetElement = container;
1077
+ console.log(`Found target element for ${el.id} using selector: ${selector}`);
1078
+ break;
1079
+ }
1080
+ }
1081
  }
1082
 
1083
+ // 如果没找到容器,直接查找SVG元素
1084
+ if (!targetElement) {
1085
+ const svgSelectors = [
1086
+ `.thumbnail-list .base-element-${el.id} svg`,
1087
+ `[data-element-id="${el.id}"] svg`,
1088
+ `#element-${el.id} svg`
1089
+ ];
1090
+
1091
+ for (const selector of svgSelectors) {
1092
+ const svgElement = document.querySelector(selector) as HTMLElement;
1093
+ if (svgElement) {
1094
+ targetElement = svgElement;
1095
+ console.log(`Found SVG element for ${el.id} using selector: ${selector}`);
1096
+ break;
1097
+ }
1098
+ }
1099
  }
1100
 
1101
+ // 最后尝试通过父元素匹配查找
1102
+ if (!targetElement) {
1103
  const allSvgs = document.querySelectorAll('svg');
1104
+ console.log(`Searching through ${allSvgs.length} SVG elements for ${el.id}`);
1105
 
1106
+ for (const svg of allSvgs) {
 
1107
  const parent = svg.closest(`[class*="${el.id}"]`);
1108
  if (parent) {
1109
+ targetElement = svg as HTMLElement;
1110
  console.log(`Found SVG for ${el.id} using parent matching`);
1111
  break;
1112
  }
1113
  }
1114
  }
1115
 
1116
+ if (!targetElement) {
1117
+ console.warn(`No target element found for shape ${el.id}, falling back to path-based export`);
1118
+ // 降级处理代码...
1119
+ const scale = {
1120
+ x: el.width / el.viewBox[0],
1121
+ y: el.height / el.viewBox[1],
1122
+ };
1123
+ const points = formatPoints(toPoints(el.path), scale);
1124
+
1125
+ let fillColor = formatColor(el.fill);
1126
+ if (el.gradient) {
1127
+ const colors = el.gradient.colors;
1128
+ const color1 = colors[0].color;
1129
+ const color2 = colors[colors.length - 1].color;
1130
+ const color = tinycolor.mix(color1, color2).toHexString();
1131
+ fillColor = formatColor(color);
1132
+ }
1133
+ if (el.pattern) fillColor = formatColor('#00000000');
1134
+ const opacity = el.opacity === undefined ? 1 : el.opacity;
1135
+
1136
+ const fallbackOptions: pptxgen.ShapeProps = {
1137
+ x: el.left / ratioPx2Inch.value,
1138
+ y: el.top / ratioPx2Inch.value,
1139
+ w: el.width / ratioPx2Inch.value,
1140
+ h: el.height / ratioPx2Inch.value,
1141
+ fill: { color: fillColor.color, transparency: (1 - fillColor.alpha * opacity) * 100 },
1142
+ points,
1143
+ };
1144
+
1145
+ if (el.flipH) fallbackOptions.flipH = el.flipH;
1146
+ if (el.flipV) fallbackOptions.flipV = el.flipV;
1147
+ if (el.shadow) fallbackOptions.shadow = getShadowOption(el.shadow);
1148
+ if (el.outline?.width) fallbackOptions.line = getOutlineOption(el.outline);
1149
+ if (el.rotate) fallbackOptions.rotate = el.rotate;
1150
+ if (el.link) {
1151
+ const linkOption = getLinkOption(el.link);
1152
+ if (linkOption) fallbackOptions.hyperlink = linkOption;
1153
+ }
1154
+
1155
+ pptxSlide.addShape('custGeom' as pptxgen.ShapeType, fallbackOptions);
1156
  continue;
1157
  }
1158
 
1159
+ // 检查元素尺寸
1160
+ const dimensions = getElementDimensions(targetElement);
1161
+ console.log(`Target element dimensions for ${el.id}:`, dimensions);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1162
 
1163
+ if (dimensions.width <= 0 || dimensions.height <= 0) {
1164
+ console.warn(`Invalid element dimensions for shape ${el.id}:`, dimensions);
1165
+ console.log('Falling back to path-based export');
1166
+ // 降级处理代码...
 
 
 
 
 
 
1167
  const scale = {
1168
  x: el.width / el.viewBox[0],
1169
  y: el.height / el.viewBox[1],
 
1204
  continue;
1205
  }
1206
 
1207
+ // 使用Canvas渲染
1208
+ let base64Image;
1209
  try {
1210
+ console.log(`Starting Canvas rendering for ${el.id}`);
1211
+
1212
+ // Canvas渲染选项
1213
+ const renderOptions = {
1214
+ scale: 2, // 高分辨率渲染
1215
+ backgroundColor: null, // 透明背景
1216
+ useCORS: true,
1217
+ timeout: 10000, // 10秒超时
1218
+ format: 'png' as const,
1219
+ quality: 0.95
1220
+ };
1221
 
1222
+ base64Image = await renderElementToBase64(targetElement, renderOptions);
1223
 
1224
+ console.log(`Canvas rendering result for ${el.id}:`, {
1225
+ success: !!base64Image,
1226
+ length: base64Image ? base64Image.length : 0,
1227
+ preview: base64Image ? base64Image.substring(0, 100) + '...' : 'null'
1228
  });
1229
 
1230
+ if (!base64Image || base64Image.length < 100) {
1231
+ throw new Error('Canvas rendering returned empty or invalid result');
1232
  }
1233
  } catch (error) {
1234
+ console.error(`Canvas rendering failed for shape ${el.id}:`, error);
 
 
1235
 
1236
+ // 尝试SVG序列化作为备选方案
1237
+ console.log(`Attempting SVG serialization fallback for ${el.id}`);
1238
+ try {
1239
+ const svgElement = targetElement.tagName.toLowerCase() === 'svg'
1240
+ ? targetElement
1241
+ : targetElement.querySelector('svg');
1242
+
1243
+ if (svgElement) {
1244
+ base64Image = svg2Base64(svgElement);
1245
+ console.log(`SVG fallback successful for ${el.id}`);
1246
+ } else {
1247
+ throw new Error('No SVG element found for fallback');
1248
+ }
1249
+ } catch (svgError) {
1250
+ console.error(`SVG fallback also failed for ${el.id}:`, svgError);
1251
+ // 最终降级到路径导出
1252
+ console.log(`Final fallback to path-based export for ${el.id}`);
1253
+ const scale = {
1254
+ x: el.width / el.viewBox[0],
1255
+ y: el.height / el.viewBox[1],
1256
+ };
1257
+ const points = formatPoints(toPoints(el.path), scale);
1258
+
1259
+ let fillColor = formatColor(el.fill);
1260
+ if (el.gradient) {
1261
+ const colors = el.gradient.colors;
1262
+ const color1 = colors[0].color;
1263
+ const color2 = colors[colors.length - 1].color;
1264
+ const color = tinycolor.mix(color1, color2).toHexString();
1265
+ fillColor = formatColor(color);
1266
+ }
1267
+ if (el.pattern) fillColor = formatColor('#00000000');
1268
+ const opacity = el.opacity === undefined ? 1 : el.opacity;
1269
+
1270
+ const fallbackOptions: pptxgen.ShapeProps = {
1271
+ x: el.left / ratioPx2Inch.value,
1272
+ y: el.top / ratioPx2Inch.value,
1273
+ w: el.width / ratioPx2Inch.value,
1274
+ h: el.height / ratioPx2Inch.value,
1275
+ fill: { color: fillColor.color, transparency: (1 - fillColor.alpha * opacity) * 100 },
1276
+ points,
1277
+ };
1278
+
1279
+ if (el.flipH) fallbackOptions.flipH = el.flipH;
1280
+ if (el.flipV) fallbackOptions.flipV = el.flipV;
1281
+ if (el.shadow) fallbackOptions.shadow = getShadowOption(el.shadow);
1282
+ if (el.outline?.width) fallbackOptions.line = getOutlineOption(el.outline);
1283
+ if (el.rotate) fallbackOptions.rotate = el.rotate;
1284
+ if (el.link) {
1285
+ const linkOption = getLinkOption(el.link);
1286
+ if (linkOption) fallbackOptions.hyperlink = linkOption;
1287
+ }
1288
+
1289
+ pptxSlide.addShape('custGeom' as pptxgen.ShapeType, fallbackOptions);
1290
+ continue;
1291
+ }
1292
  }
1293
 
1294
+ // 添加渲染后的图像到幻灯片
1295
  const options: pptxgen.ImageProps = {
1296
+ data: base64Image,
1297
  x: el.left / ratioPx2Inch.value,
1298
  y: el.top / ratioPx2Inch.value,
1299
  w: el.width / ratioPx2Inch.value,
 
1307
  if (linkOption) options.hyperlink = linkOption
1308
  }
1309
 
1310
+ console.log(`Successfully added Canvas-rendered image for shape ${el.id}`);
1311
  pptxSlide.addImage(options)
1312
  }
1313
  else {