Upload svg2Base64.ts
Browse files- frontend/src/utils/svg2Base64.ts +154 -19
frontend/src/utils/svg2Base64.ts
CHANGED
@@ -60,47 +60,182 @@ export const svg2Base64 = (element: Element) => {
|
|
60 |
const clonedElement = element.cloneNode(true) as Element;
|
61 |
console.log('svg2Base64: Element cloned successfully');
|
62 |
|
63 |
-
// 确保SVG
|
64 |
if (clonedElement.tagName.toLowerCase() === 'svg') {
|
65 |
-
clonedElement
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
}
|
69 |
|
70 |
-
//
|
71 |
-
const
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
});
|
78 |
|
|
|
|
|
|
|
|
|
79 |
const XMLS = new XMLSerializer();
|
80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
|
82 |
console.log('svg2Base64: Serialization result:', {
|
83 |
length: svg.length,
|
84 |
preview: svg.substring(0, 200) + '...',
|
85 |
containsSvgTag: svg.includes('<svg'),
|
86 |
-
containsContent: svg.length > 50
|
|
|
87 |
});
|
88 |
|
89 |
if (!svg || svg.length === 0) {
|
90 |
throw new Error('SVG serialization returned empty string');
|
91 |
}
|
92 |
|
|
|
|
|
|
|
|
|
93 |
const encoded = encode(svg);
|
94 |
if (!encoded) {
|
95 |
throw new Error('Base64 encoding failed');
|
96 |
}
|
97 |
|
98 |
-
|
99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
} catch (error) {
|
101 |
-
console.error('svg2Base64 failed:', error);
|
102 |
-
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
}
|
106 |
}
|
|
|
60 |
const clonedElement = element.cloneNode(true) as Element;
|
61 |
console.log('svg2Base64: Element cloned successfully');
|
62 |
|
63 |
+
// 确保SVG有正确的命名空间和属性
|
64 |
if (clonedElement.tagName.toLowerCase() === 'svg') {
|
65 |
+
const svgElement = clonedElement as SVGElement;
|
66 |
+
svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
67 |
+
svgElement.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
|
68 |
+
|
69 |
+
// 确保SVG有明确的尺寸
|
70 |
+
const rect = element.getBoundingClientRect();
|
71 |
+
if (!svgElement.getAttribute('width') && rect.width > 0) {
|
72 |
+
svgElement.setAttribute('width', rect.width.toString());
|
73 |
+
}
|
74 |
+
if (!svgElement.getAttribute('height') && rect.height > 0) {
|
75 |
+
svgElement.setAttribute('height', rect.height.toString());
|
76 |
+
}
|
77 |
+
|
78 |
+
// 确保viewBox存在
|
79 |
+
if (!svgElement.getAttribute('viewBox')) {
|
80 |
+
const width = parseFloat(svgElement.getAttribute('width') || '0');
|
81 |
+
const height = parseFloat(svgElement.getAttribute('height') || '0');
|
82 |
+
if (width > 0 && height > 0) {
|
83 |
+
svgElement.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
84 |
+
}
|
85 |
+
}
|
86 |
+
|
87 |
+
console.log('svg2Base64: Added SVG namespaces and dimensions');
|
88 |
}
|
89 |
|
90 |
+
// 处理内联样式 - 将计算样式应用到元素
|
91 |
+
const applyComputedStyles = (elem: Element, originalElem: Element) => {
|
92 |
+
if (elem.nodeType === Node.ELEMENT_NODE) {
|
93 |
+
const computedStyles = window.getComputedStyle(originalElem as HTMLElement);
|
94 |
+
const styleProps = ['fill', 'stroke', 'stroke-width', 'opacity', 'font-family', 'font-size', 'font-weight'];
|
95 |
+
|
96 |
+
styleProps.forEach(prop => {
|
97 |
+
const value = computedStyles.getPropertyValue(prop);
|
98 |
+
if (value && value !== 'none' && !elem.getAttribute(prop)) {
|
99 |
+
elem.setAttribute(prop, value);
|
100 |
+
}
|
101 |
+
});
|
102 |
+
|
103 |
+
// 递归处理子元素
|
104 |
+
for (let i = 0; i < elem.children.length; i++) {
|
105 |
+
const child = elem.children[i];
|
106 |
+
const originalChild = (originalElem as HTMLElement).children[i];
|
107 |
+
if (originalChild) {
|
108 |
+
applyComputedStyles(child, originalChild);
|
109 |
+
}
|
110 |
+
}
|
111 |
+
}
|
112 |
+
};
|
113 |
+
|
114 |
+
applyComputedStyles(clonedElement, element);
|
115 |
+
|
116 |
+
// 检查元素尺寸 - 使用更宽松的检查逻辑
|
117 |
+
const rect = element.getBoundingClientRect();
|
118 |
+
const computedStyle = window.getComputedStyle(element as HTMLElement);
|
119 |
+
const hasValidDimensions = (
|
120 |
+
rect.width > 0 || rect.height > 0 ||
|
121 |
+
parseFloat(computedStyle.width) > 0 || parseFloat(computedStyle.height) > 0 ||
|
122 |
+
(element as HTMLElement).offsetWidth > 0 || (element as HTMLElement).offsetHeight > 0
|
123 |
+
);
|
124 |
+
|
125 |
+
console.log('svg2Base64: Element dimensions:', {
|
126 |
+
boundingRect: { width: rect.width, height: rect.height },
|
127 |
+
computedStyle: { width: computedStyle.width, height: computedStyle.height },
|
128 |
+
offset: { width: (element as HTMLElement).offsetWidth, height: (element as HTMLElement).offsetHeight },
|
129 |
+
hasValidDimensions
|
130 |
});
|
131 |
|
132 |
+
if (!hasValidDimensions) {
|
133 |
+
console.warn('svg2Base64: Element has no valid dimensions, but continuing with serialization');
|
134 |
+
}
|
135 |
+
|
136 |
const XMLS = new XMLSerializer();
|
137 |
+
let svg = XMLS.serializeToString(clonedElement);
|
138 |
+
|
139 |
+
// 清理和优化SVG字符串
|
140 |
+
svg = svg.replace(/vector-effect="[^"]*"/g, ''); // 移除vector-effect属性
|
141 |
+
svg = svg.replace(/xmlns="[^"]*"/g, ''); // 移除重复的xmlns
|
142 |
+
|
143 |
+
// 确保SVG标签包含正确的命名空间
|
144 |
+
if (svg.includes('<svg') && !svg.includes('xmlns="http://www.w3.org/2000/svg"')) {
|
145 |
+
svg = svg.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
|
146 |
+
}
|
147 |
+
|
148 |
+
// 添加xmlns:xlink命名空间(如果需要)
|
149 |
+
if (svg.includes('xlink:') && !svg.includes('xmlns:xlink')) {
|
150 |
+
svg = svg.replace('<svg', '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
|
151 |
+
}
|
152 |
+
|
153 |
+
// 确保SVG有基本的尺寸信息
|
154 |
+
if (svg.includes('<svg') && !svg.includes('viewBox=') && !svg.includes('width=')) {
|
155 |
+
const rect = element.getBoundingClientRect();
|
156 |
+
if (rect.width > 0 && rect.height > 0) {
|
157 |
+
svg = svg.replace('<svg', `<svg width="${rect.width}" height="${rect.height}" viewBox="0 0 ${rect.width} ${rect.height}"`);
|
158 |
+
}
|
159 |
+
}
|
160 |
|
161 |
console.log('svg2Base64: Serialization result:', {
|
162 |
length: svg.length,
|
163 |
preview: svg.substring(0, 200) + '...',
|
164 |
containsSvgTag: svg.includes('<svg'),
|
165 |
+
containsContent: svg.length > 50,
|
166 |
+
hasNamespace: svg.includes('xmlns="http://www.w3.org/2000/svg"')
|
167 |
});
|
168 |
|
169 |
if (!svg || svg.length === 0) {
|
170 |
throw new Error('SVG serialization returned empty string');
|
171 |
}
|
172 |
|
173 |
+
if (svg.length < 20) {
|
174 |
+
throw new Error('SVG serialization returned suspiciously short string');
|
175 |
+
}
|
176 |
+
|
177 |
const encoded = encode(svg);
|
178 |
if (!encoded) {
|
179 |
throw new Error('Base64 encoding failed');
|
180 |
}
|
181 |
|
182 |
+
const result = PREFIX + encoded;
|
183 |
+
console.log('svg2Base64: Encoding successful, result length:', result.length);
|
184 |
+
|
185 |
+
// 验证结果
|
186 |
+
if (result.length < 100) {
|
187 |
+
throw new Error('Base64 result is suspiciously short');
|
188 |
+
}
|
189 |
+
|
190 |
+
return result;
|
191 |
} catch (error) {
|
192 |
+
console.error('svg2Base64: Conversion failed:', error);
|
193 |
+
|
194 |
+
// 尝试多种备选方案
|
195 |
+
const fallbackStrategies = [
|
196 |
+
// 策略1: 简化的序列化
|
197 |
+
() => {
|
198 |
+
console.log('svg2Base64: Attempting simplified serialization');
|
199 |
+
const rect = element.getBoundingClientRect();
|
200 |
+
const width = rect.width || 100;
|
201 |
+
const height = rect.height || 100;
|
202 |
+
const simplifiedSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${element.innerHTML}</svg>`;
|
203 |
+
const base64 = encode(utf8Encode(simplifiedSvg));
|
204 |
+
return `data:image/svg+xml;base64,${base64}`;
|
205 |
+
},
|
206 |
+
|
207 |
+
// 策略2: 使用outerHTML
|
208 |
+
() => {
|
209 |
+
console.log('svg2Base64: Attempting outerHTML serialization');
|
210 |
+
let svgString = (element as HTMLElement).outerHTML;
|
211 |
+
if (!svgString.includes('xmlns')) {
|
212 |
+
svgString = svgString.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
|
213 |
+
}
|
214 |
+
const base64 = encode(utf8Encode(svgString));
|
215 |
+
return `data:image/svg+xml;base64,${base64}`;
|
216 |
+
},
|
217 |
+
|
218 |
+
// 策略3: 最小化SVG
|
219 |
+
() => {
|
220 |
+
console.log('svg2Base64: Attempting minimal SVG creation');
|
221 |
+
const rect = element.getBoundingClientRect();
|
222 |
+
const minimalSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${rect.width || 100}" height="${rect.height || 100}"><rect width="100%" height="100%" fill="#cccccc"/></svg>`;
|
223 |
+
const base64 = encode(utf8Encode(minimalSvg));
|
224 |
+
return `data:image/svg+xml;base64,${base64}`;
|
225 |
+
}
|
226 |
+
];
|
227 |
+
|
228 |
+
for (let i = 0; i < fallbackStrategies.length; i++) {
|
229 |
+
try {
|
230 |
+
const result = fallbackStrategies[i]();
|
231 |
+
console.log(`svg2Base64: Fallback strategy ${i + 1} succeeded`);
|
232 |
+
return result;
|
233 |
+
} catch (fallbackError) {
|
234 |
+
console.warn(`svg2Base64: Fallback strategy ${i + 1} failed:`, fallbackError);
|
235 |
+
}
|
236 |
+
}
|
237 |
+
|
238 |
+
console.error('svg2Base64: All fallback strategies failed');
|
239 |
+
throw new Error(`SVG to Base64 conversion failed: ${error}`);
|
240 |
}
|
241 |
}
|