web_ppt / frontend /src /utils /svg2Base64.ts
CatPtain's picture
Upload svg2Base64.ts
77be7be verified
raw
history blame
9.12 kB
// svg转base64图片,参考:https://github.com/scriptex/svg64
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
const PREFIX = 'data:image/svg+xml;base64,'
const utf8Encode = (string: string) => {
string = string.replace(/\r\n/g, '\n')
let utftext = ''
for (let n = 0; n < string.length; n++) {
const c = string.charCodeAt(n)
if (c < 128) {
utftext += String.fromCharCode(c)
}
else if (c > 127 && c < 2048) {
utftext += String.fromCharCode((c >> 6) | 192)
utftext += String.fromCharCode((c & 63) | 128)
}
else {
utftext += String.fromCharCode((c >> 12) | 224)
utftext += String.fromCharCode(((c >> 6) & 63) | 128)
utftext += String.fromCharCode((c & 63) | 128)
}
}
return utftext
}
const encode = (input: string) => {
let output = ''
let chr1, chr2, chr3, enc1, enc2, enc3, enc4
let i = 0
input = utf8Encode(input)
while (i < input.length) {
chr1 = input.charCodeAt(i++)
chr2 = input.charCodeAt(i++)
chr3 = input.charCodeAt(i++)
enc1 = chr1 >> 2
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4)
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6)
enc4 = chr3 & 63
if (isNaN(chr2)) enc3 = enc4 = 64
else if (isNaN(chr3)) enc4 = 64
output = output + characters.charAt(enc1) + characters.charAt(enc2) + characters.charAt(enc3) + characters.charAt(enc4)
}
return output
}
export const svg2Base64 = (element: Element) => {
try {
console.log('svg2Base64: Starting conversion for element:', {
tagName: element.tagName,
className: element.className,
id: element.id,
hasChildren: element.children.length > 0
});
// 克隆元素以避免修改原始DOM
const clonedElement = element.cloneNode(true) as Element;
console.log('svg2Base64: Element cloned successfully');
// 确保SVG有正确的命名空间和属性
if (clonedElement.tagName.toLowerCase() === 'svg') {
const svgElement = clonedElement as SVGElement;
svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgElement.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
// 确保SVG有明确的尺寸
const rect = element.getBoundingClientRect();
if (!svgElement.getAttribute('width') && rect.width > 0) {
svgElement.setAttribute('width', rect.width.toString());
}
if (!svgElement.getAttribute('height') && rect.height > 0) {
svgElement.setAttribute('height', rect.height.toString());
}
// 确保viewBox存在
if (!svgElement.getAttribute('viewBox')) {
const width = parseFloat(svgElement.getAttribute('width') || '0');
const height = parseFloat(svgElement.getAttribute('height') || '0');
if (width > 0 && height > 0) {
svgElement.setAttribute('viewBox', `0 0 ${width} ${height}`);
}
}
console.log('svg2Base64: Added SVG namespaces and dimensions');
}
// 处理内联样式 - 将计算样式应用到元素
const applyComputedStyles = (elem: Element, originalElem: Element) => {
if (elem.nodeType === Node.ELEMENT_NODE) {
const computedStyles = window.getComputedStyle(originalElem as HTMLElement);
const styleProps = ['fill', 'stroke', 'stroke-width', 'opacity', 'font-family', 'font-size', 'font-weight'];
styleProps.forEach(prop => {
const value = computedStyles.getPropertyValue(prop);
if (value && value !== 'none' && !elem.getAttribute(prop)) {
elem.setAttribute(prop, value);
}
});
// 递归处理子元素
for (let i = 0; i < elem.children.length; i++) {
const child = elem.children[i];
const originalChild = (originalElem as HTMLElement).children[i];
if (originalChild) {
applyComputedStyles(child, originalChild);
}
}
}
};
applyComputedStyles(clonedElement, element);
// 检查元素尺寸 - 使用更宽松的检查逻辑
const rect = element.getBoundingClientRect();
const computedStyle = window.getComputedStyle(element as HTMLElement);
const hasValidDimensions = (
rect.width > 0 || rect.height > 0 ||
parseFloat(computedStyle.width) > 0 || parseFloat(computedStyle.height) > 0 ||
(element as HTMLElement).offsetWidth > 0 || (element as HTMLElement).offsetHeight > 0
);
console.log('svg2Base64: Element dimensions:', {
boundingRect: { width: rect.width, height: rect.height },
computedStyle: { width: computedStyle.width, height: computedStyle.height },
offset: { width: (element as HTMLElement).offsetWidth, height: (element as HTMLElement).offsetHeight },
hasValidDimensions
});
if (!hasValidDimensions) {
console.warn('svg2Base64: Element has no valid dimensions, but continuing with serialization');
}
const XMLS = new XMLSerializer();
let svg = XMLS.serializeToString(clonedElement);
// 清理和优化SVG字符串
svg = svg.replace(/vector-effect="[^"]*"/g, ''); // 移除vector-effect属性
svg = svg.replace(/xmlns="[^"]*"/g, ''); // 移除重复的xmlns
// 确保SVG标签包含正确的命名空间
if (svg.includes('<svg') && !svg.includes('xmlns="http://www.w3.org/2000/svg"')) {
svg = svg.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
}
// 添加xmlns:xlink命名空间(如果需要)
if (svg.includes('xlink:') && !svg.includes('xmlns:xlink')) {
svg = svg.replace('<svg', '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}
// 确保SVG有基本的尺寸信息
if (svg.includes('<svg') && !svg.includes('viewBox=') && !svg.includes('width=')) {
const rect = element.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
svg = svg.replace('<svg', `<svg width="${rect.width}" height="${rect.height}" viewBox="0 0 ${rect.width} ${rect.height}"`);
}
}
console.log('svg2Base64: Serialization result:', {
length: svg.length,
preview: svg.substring(0, 200) + '...',
containsSvgTag: svg.includes('<svg'),
containsContent: svg.length > 50,
hasNamespace: svg.includes('xmlns="http://www.w3.org/2000/svg"')
});
if (!svg || svg.length === 0) {
throw new Error('SVG serialization returned empty string');
}
if (svg.length < 20) {
throw new Error('SVG serialization returned suspiciously short string');
}
const encoded = encode(svg);
if (!encoded) {
throw new Error('Base64 encoding failed');
}
const result = PREFIX + encoded;
console.log('svg2Base64: Encoding successful, result length:', result.length);
// 验证结果
if (result.length < 100) {
throw new Error('Base64 result is suspiciously short');
}
return result;
} catch (error) {
console.error('svg2Base64: Conversion failed:', error);
// 尝试多种备选方案
const fallbackStrategies = [
// 策略1: 简化的序列化
() => {
console.log('svg2Base64: Attempting simplified serialization');
const rect = element.getBoundingClientRect();
const width = rect.width || 100;
const height = rect.height || 100;
const simplifiedSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">${element.innerHTML}</svg>`;
const base64 = encode(utf8Encode(simplifiedSvg));
return `data:image/svg+xml;base64,${base64}`;
},
// 策略2: 使用outerHTML
() => {
console.log('svg2Base64: Attempting outerHTML serialization');
let svgString = (element as HTMLElement).outerHTML;
if (!svgString.includes('xmlns')) {
svgString = svgString.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
}
const base64 = encode(utf8Encode(svgString));
return `data:image/svg+xml;base64,${base64}`;
},
// 策略3: 最小化SVG
() => {
console.log('svg2Base64: Attempting minimal SVG creation');
const rect = element.getBoundingClientRect();
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>`;
const base64 = encode(utf8Encode(minimalSvg));
return `data:image/svg+xml;base64,${base64}`;
}
];
for (let i = 0; i < fallbackStrategies.length; i++) {
try {
const result = fallbackStrategies[i]();
console.log(`svg2Base64: Fallback strategy ${i + 1} succeeded`);
return result;
} catch (fallbackError) {
console.warn(`svg2Base64: Fallback strategy ${i + 1} failed:`, fallbackError);
}
}
console.error('svg2Base64: All fallback strategies failed');
throw new Error(`SVG to Base64 conversion failed: ${error}`);
}
}