File size: 7,307 Bytes
ad7128b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# PPT矢量元素导出问题修复方案

## 问题分析

通过代码分析,发现PPT内置矢量元素无法正常导出的主要原因:

### 1. 核心问题`useExport.ts` 第977行存在一个临时处理逻辑:
```typescript
if (svgRef.clientWidth < 1 || svgRef.clientHeight < 1) continue // 临时处理(导入PPTX文件带来的异常数据)
```

这个检查会跳过所有尺寸小于1像素的SVG元素,但某些矢量元素在特定情况下可能确实会出现这种情况。

### 2. 相关问题
- SVG元素在DOM中可能未完全渲染
- `vector-effect="non-scaling-stroke"` 属性可能影响尺寸计算
- 特殊形状元素(special=true)的DOM查询可能失败
- SVG序列化过程缺乏错误处理

## 修复方案

### 方案1: 改进尺寸检查逻辑

```typescript
// 替换原有的简单尺寸检查
if (el.special) {
  const svgRef = document.querySelector(`.thumbnail-list .base-element-${el.id} svg`) as HTMLElement
  
  // 改进的尺寸检查
  if (!svgRef) {
    console.warn(`SVG element not found for shape ${el.id}`);
    continue;
  }
  
  // 获取多种尺寸信息进行判断
  const clientWidth = svgRef.clientWidth;
  const clientHeight = svgRef.clientHeight;
  const boundingRect = svgRef.getBoundingClientRect();
  const computedStyle = window.getComputedStyle(svgRef);
  
  // 更智能的尺寸判断
  const hasValidSize = (
    (clientWidth > 0 && clientHeight > 0) ||
    (boundingRect.width > 0 && boundingRect.height > 0) ||
    (parseFloat(computedStyle.width) > 0 && parseFloat(computedStyle.height) > 0)
  );
  
  if (!hasValidSize) {
    console.warn(`Invalid SVG dimensions for shape ${el.id}:`, {
      clientWidth,
      clientHeight,
      boundingRect: { width: boundingRect.width, height: boundingRect.height },
      computedStyle: { width: computedStyle.width, height: computedStyle.height }
    });
    continue;
  }
  
  // SVG序列化with错误处理
  let base64SVG;
  try {
    base64SVG = svg2Base64(svgRef);
    if (!base64SVG || base64SVG === 'data:image/svg+xml;base64,') {
      throw new Error('SVG serialization returned empty result');
    }
  } catch (error) {
    console.error(`SVG serialization failed for shape ${el.id}:`, error);
    continue;
  }
  
  // 其余导出逻辑...
}
```

### 方案2: 添加渲染等待机制

```typescript
// 在导出开始前添加渲染等待
const ensureElementsRendered = async () => {
  return new Promise<void>((resolve) => {
    // 强制重绘
    document.body.offsetHeight;
    
    // 等待下一个动画帧
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        resolve();
      });
    });
  });
};

// 在exportPPTX函数开始处调用
export const exportPPTX = async (slides: Slide[], title: string, ignoreMedia = false) => {
  exporting.value = true;
  
  // 确保所有元素已渲染
  await ensureElementsRendered();
  
  // 其余导出逻辑...
}
```

### 方案3: 改进SVG处理逻辑

```typescript
// 改进svg2Base64函数
export const svg2Base64 = (element: Element) => {
  try {
    // 克隆元素以避免修改原始DOM
    const clonedElement = element.cloneNode(true) as Element;
    
    // 确保SVG有正确的命名空间
    if (clonedElement.tagName.toLowerCase() === 'svg') {
      clonedElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
      clonedElement.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
    }
    
    const XMLS = new XMLSerializer();
    const svg = XMLS.serializeToString(clonedElement);
    
    if (!svg || svg.length === 0) {
      throw new Error('SVG serialization returned empty string');
    }
    
    const encoded = encode(svg);
    if (!encoded) {
      throw new Error('Base64 encoding failed');
    }
    
    return PREFIX + encoded;
  } catch (error) {
    console.error('svg2Base64 failed:', error);
    throw error;
  }
};
```

### 方案4: 添加降级处理

```typescript
// 为特殊形状添加降级处理
else if (el.type === 'shape') {
  if (el.special) {
    let base64SVG;
    
    try {
      // 尝试从DOM获取SVG
      const svgRef = document.querySelector(`.thumbnail-list .base-element-${el.id} svg`) as HTMLElement;
      if (svgRef && svgRef.clientWidth > 0 && svgRef.clientHeight > 0) {
        base64SVG = svg2Base64(svgRef);
      } else {
        throw new Error('SVG element not found or has invalid dimensions');
      }
    } catch (error) {
      console.warn(`Failed to export special shape ${el.id} as SVG, falling back to path-based export:`, error);
      
      // 降级到普通形状处理
      const scale = {
        x: el.width / el.viewBox[0],
        y: el.height / el.viewBox[1],
      };
      const points = formatPoints(toPoints(el.path), scale);
      
      let fillColor = formatColor(el.fill);
      if (el.gradient) {
        const colors = el.gradient.colors;
        const color1 = colors[0].color;
        const color2 = colors[colors.length - 1].color;
        const color = tinycolor.mix(color1, color2).toHexString();
        fillColor = formatColor(color);
      }
      
      const opacity = el.opacity === undefined ? 1 : el.opacity;
      
      const options: pptxgen.ShapeProps = {
        x: el.left / ratioPx2Inch.value,
        y: el.top / ratioPx2Inch.value,
        w: el.width / ratioPx2Inch.value,
        h: el.height / ratioPx2Inch.value,
        fill: { color: fillColor.color, transparency: (1 - fillColor.alpha * opacity) * 100 },
        points,
      };
      
      if (el.flipH) options.flipH = el.flipH;
      if (el.flipV) options.flipV = el.flipV;
      if (el.shadow) options.shadow = getShadowOption(el.shadow);
      if (el.outline?.width) options.line = getOutlineOption(el.outline);
      if (el.rotate) options.rotate = el.rotate;
      
      pptxSlide.addShape('custGeom' as pptxgen.ShapeType, options);
      continue;
    }
    
    // 成功获取SVG的情况
    if (base64SVG) {
      const options: pptxgen.ImageProps = {
        data: base64SVG,
        x: el.left / ratioPx2Inch.value,
        y: el.top / ratioPx2Inch.value,
        w: el.width / ratioPx2Inch.value,
        h: el.height / ratioPx2Inch.value,
      };
      
      if (el.rotate) options.rotate = el.rotate;
      if (el.flipH) options.flipH = el.flipH;
      if (el.flipV) options.flipV = el.flipV;
      if (el.link) {
        const linkOption = getLinkOption(el.link);
        if (linkOption) options.hyperlink = linkOption;
      }
      
      pptxSlide.addImage(options);
    }
  }
  // 普通形状处理逻辑保持不变
  else {
    // 现有的普通形状处理代码...
  }
}
```

## 实施步骤

1. **立即修复**: 实施方案1,改进尺寸检查逻辑
2. **增强稳定性**: 实施方案2,添加渲染等待机制
3. **提升兼容性**: 实施方案3,改进SVG处理
4. **添加容错**: 实施方案4,添加降级处理

## 测试验证

使用提供的 `debug_vector_export.html` 文件进行测试:
1. 打开调试页面
2. 运行所有测试
3. 检查SVG序列化和Base64转换是否正常
4. 验证元素尺寸检测逻辑

## 预期效果

修复后应该能够:
- 正确导出所有类型的矢量元素
- 提供详细的错误日志用于问题排查
- 在特殊情况下提供降级处理方案
- 提高导出成功率和稳定性