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. 验证元素尺寸检测逻辑
## 预期效果
修复后应该能够:
- 正确导出所有类型的矢量元素
- 提供详细的错误日志用于问题排查
- 在特殊情况下提供降级处理方案
- 提高导出成功率和稳定性 |