🎉 优化SVG导出功能

 新功能:
- 实现表格转SVG原生元素转换器
- 支持rowspan和colspan的复杂表格布局
- 智能列宽计算,针对中文内容优化
- 动态行高计算,支持多行文本

🔧 修复:
- 解决SVG导出时图片压线问题
- 优化图片尺寸,增大显示但不压线
- 减少图片和文字距离,布局更紧凑
- 解决表格内容拥挤问题

📊 技术改进:
- 中文字符宽度:字体大小 × 1.0
- 英文字符宽度:字体大小 × 0.6
- 最小列宽:80px,最小行高:35px
- 支持多行文本的精确布局计算
This commit is contained in:
lixinran 2025-10-10 15:48:51 +08:00
parent cef6b60db7
commit beee48eb0c
1 changed files with 460 additions and 26 deletions

View File

@ -52,6 +52,296 @@ function generateSvgText(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: num
return g return g
} }
// 表格转SVG转换器类
class TableToSVGConverter {
private table: HTMLTableElement
private cellWidth: number
private cellHeight: number
private fontSize: number
private fontFamily: string
constructor(table: HTMLTableElement, fontSize: number, fontFamily: string) {
this.table = table
this.fontSize = fontSize
this.fontFamily = fontFamily
this.cellWidth = 80 // 默认单元格宽度
this.cellHeight = 30 // 默认单元格高度
}
// 分析表格结构处理rowspan和colspan
analyzeStructure() {
const structure: Array<{
row: number
col: number
rowspan: number
colspan: number
content: string
isHeader: boolean
}> = []
const rows = this.table.querySelectorAll('tr')
rows.forEach((row, rowIndex) => {
const cells = row.querySelectorAll('td, th')
let colIndex = 0
cells.forEach(cell => {
const htmlCell = cell as HTMLTableCellElement
const rowspan = parseInt(htmlCell.getAttribute('rowspan') || '1')
const colspan = parseInt(htmlCell.getAttribute('colspan') || '1')
const content = htmlCell.textContent?.trim() || ''
const isHeader = htmlCell.tagName.toLowerCase() === 'th'
structure.push({
row: rowIndex,
col: colIndex,
rowspan,
colspan,
content,
isHeader
})
colIndex += colspan
})
})
return structure
}
// 计算布局尺寸
calculateLayout(structure: any[]) {
const maxCols = Math.max(...structure.map(cell => cell.col + cell.colspan))
const maxRows = Math.max(...structure.map(cell => cell.row + cell.rowspan))
// 为每列计算最合适的宽度
const columnWidths: number[] = new Array(maxCols).fill(0)
structure.forEach(cell => {
// 计算这个单元格内容需要的宽度
// 中文字符宽度大约是字体大小的1倍英文是0.6倍
let contentWidth = 0
for (const char of cell.content) {
if (/[\u4e00-\u9fa5]/.test(char)) {
// 中文字符
contentWidth += this.fontSize * 1.0
} else {
// 英文字符
contentWidth += this.fontSize * 0.6
}
}
// 加上内边距
contentWidth += 16 // 左右各8px的padding
// 考虑colspan平均分配宽度
const avgWidthPerCol = contentWidth / cell.colspan
// 更新这一行涉及的列的最大宽度
for (let col = cell.col; col < cell.col + cell.colspan; col++) {
columnWidths[col] = Math.max(columnWidths[col], avgWidthPerCol)
}
})
// 设置最小列宽,确保不会太窄
columnWidths.forEach((width, index) => {
columnWidths[index] = Math.max(width, 80) // 最小80px
})
// 计算总宽度
const totalWidth = columnWidths.reduce((sum, width) => sum + width, 0)
// 计算行高,考虑多行文本
this.cellHeight = Math.max(35, this.fontSize * 2) // 增加行高
// 为每行计算实际高度(考虑多行文本)
const rowHeights: number[] = new Array(maxRows).fill(this.cellHeight)
structure.forEach(cell => {
const lines = cell.content.split('\n').length
const cellHeight = Math.max(this.cellHeight, lines * this.fontSize * 1.4 + 10)
// 更新这一行涉及的行的高度
for (let row = cell.row; row < cell.row + cell.rowspan; row++) {
rowHeights[row] = Math.max(rowHeights[row], cellHeight)
}
})
const totalHeight = rowHeights.reduce((sum, height) => sum + height, 0)
return {
totalWidth,
totalHeight,
cols: maxCols,
rows: maxRows,
columnWidths,
rowHeights
}
}
// 生成SVG元素
generateSVG(structure: any[], layout: any, x: number, y: number) {
const svgGroup = document.createElementNS(ns, 'g')
// 绘制表格边框
const tableRect = document.createElementNS(ns, 'rect')
setAttributes(tableRect, {
x: x + '',
y: y + '',
width: layout.totalWidth + '',
height: layout.totalHeight + '',
fill: 'white',
stroke: '#ccc',
'stroke-width': '1'
})
svgGroup.appendChild(tableRect)
// 绘制垂直网格线(使用动态列宽)
let currentX = x
for (let i = 0; i < layout.columnWidths.length - 1; i++) {
currentX += layout.columnWidths[i]
const line = document.createElementNS(ns, 'line')
setAttributes(line, {
x1: currentX + '',
y1: y + '',
x2: currentX + '',
y2: y + layout.totalHeight + '',
stroke: '#ccc',
'stroke-width': '1'
})
svgGroup.appendChild(line)
}
// 绘制水平网格线(使用动态行高)
let currentY = y
for (let i = 0; i < layout.rowHeights.length - 1; i++) {
currentY += layout.rowHeights[i]
const line = document.createElementNS(ns, 'line')
setAttributes(line, {
x1: x + '',
y1: currentY + '',
x2: x + layout.totalWidth + '',
y2: currentY + '',
stroke: '#ccc',
'stroke-width': '1'
})
svgGroup.appendChild(line)
}
// 绘制单元格内容和背景
structure.forEach(cell => {
// 计算单元格的实际位置和尺寸
let cellX = x
for (let i = 0; i < cell.col; i++) {
cellX += layout.columnWidths[i]
}
let cellY = y
for (let i = 0; i < cell.row; i++) {
cellY += layout.rowHeights[i]
}
// 计算单元格宽度考虑colspan
let cellWidth = 0
for (let i = cell.col; i < cell.col + cell.colspan; i++) {
cellWidth += layout.columnWidths[i]
}
// 计算单元格高度考虑rowspan
let cellHeight = 0
for (let i = cell.row; i < cell.row + cell.rowspan; i++) {
cellHeight += layout.rowHeights[i]
}
// 绘制单元格背景
if (cell.isHeader) {
const bgRect = document.createElementNS(ns, 'rect')
setAttributes(bgRect, {
x: cellX + '',
y: cellY + '',
width: cellWidth + '',
height: cellHeight + '',
fill: '#f5f5f5',
stroke: 'none'
})
svgGroup.appendChild(bgRect)
}
// 绘制文本内容
if (cell.content) {
const text = document.createElementNS(ns, 'text')
setAttributes(text, {
x: cellX + cellWidth / 2 + '',
y: cellY + cellHeight / 2 + this.fontSize / 3 + '',
'text-anchor': 'middle',
'dominant-baseline': 'central',
'font-family': this.fontFamily,
'font-size': this.fontSize + '',
'font-weight': cell.isHeader ? 'bold' : 'normal',
fill: '#333'
})
// 处理多行文本
const lines = cell.content.split('\n')
if (lines.length === 1) {
text.textContent = cell.content
} else {
lines.forEach((line: string, index: number) => {
const tspan = document.createElementNS(ns, 'tspan')
setAttributes(tspan, {
x: cellX + cellWidth / 2 + '',
dy: index === 0 ? '0' : '1.2em'
})
tspan.textContent = line
text.appendChild(tspan)
})
}
svgGroup.appendChild(text)
}
})
return svgGroup
}
// 转换表格为SVG
convert(x: number, y: number) {
const structure = this.analyzeStructure()
const layout = this.calculateLayout(structure)
return this.generateSVG(structure, layout, x, y)
}
}
// 清理HTML内容修复SVG解析错误
function cleanHtmlForSvg(html: string): string {
if (!html) return html
// 修复表格中的<br>标签问题 - 将<td>内的<br>替换为空格
let cleanedHtml = html.replace(/<td[^>]*>([^<]*)<br\s*\/?>([^<]*)<\/td>/gi, (match, before, after) => {
return `<td>${before} ${after}</td>`
})
// 修复<th>中的<br>标签问题
cleanedHtml = cleanedHtml.replace(/<th[^>]*>([^<]*)<br\s*\/?>([^<]*)<\/th>/gi, (match, before, after) => {
return `<th>${before} ${after}</th>`
})
// 移除其他可能导致SVG解析错误的标签
cleanedHtml = cleanedHtml.replace(/<br\s*\/?>/gi, ' ')
// 确保所有标签都正确闭合
cleanedHtml = cleanedHtml.replace(/<([^>]+)>/g, (match, tagContent) => {
// 检查是否是自闭合标签
const selfClosingTags = ['br', 'hr', 'img', 'input', 'meta', 'link']
const tagName = tagContent.split(' ')[0].toLowerCase()
if (selfClosingTags.includes(tagName)) {
return `<${tagContent} />`
}
return match
})
return cleanedHtml
}
function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: number, y: number) { function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: number, y: number) {
const g = document.createElementNS(ns, 'g') const g = document.createElementNS(ns, 'g')
@ -77,33 +367,76 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
contentLength: htmlContent.length contentLength: htmlContent.length
}) })
// 如果包含表格使用新的SVG原生转换器
if (hasTableContent) {
console.log('🔄 检测到表格内容使用SVG原生转换器')
// 创建一个临时DOM元素来解析表格
const tempDiv = document.createElement('div')
tempDiv.innerHTML = cleanHtmlForSvg(htmlContent)
const table = tempDiv.querySelector('table') as HTMLTableElement
if (table) {
const fontSize = parseFloat(tpcStyle.fontSize) || 14
const fontFamily = tpcStyle.fontFamily || 'Arial, sans-serif'
const converter = new TableToSVGConverter(table, fontSize, fontFamily)
const tableSVG = converter.convert(x, y)
g.appendChild(tableSVG)
console.log('✅ 表格已转换为SVG原生元素')
return g
}
}
if (hasHTMLContent || hasTableContent) { if (hasHTMLContent || hasTableContent) {
// 清理HTML内容修复SVG解析错误
const cleanedHtml = cleanHtmlForSvg(htmlContent)
console.log('🔍 清理后的HTML内容:', cleanedHtml.substring(0, 200))
// 对于HTML内容使用foreignObject来正确渲染 // 对于HTML内容使用foreignObject来正确渲染
const foreignObject = document.createElementNS(ns, 'foreignObject') const foreignObject = document.createElementNS(ns, 'foreignObject')
// 获取节点的实际尺寸,并大幅增加高度以防止图片压线
const nodeWidth = parseInt(tpcStyle.width) || 200
const originalHeight = parseInt(tpcStyle.height) || 100
const nodeHeight = originalHeight + 50 // 大幅增加50px高度给图片留出空间
setAttributes(foreignObject, { setAttributes(foreignObject, {
x: x + '', x: x + '',
y: y + '', y: y + '',
width: tpcStyle.width, width: nodeWidth + 'px',
height: tpcStyle.height, height: nodeHeight + 'px',
}) })
// 创建div容器来包含HTML内容 // 创建div容器来包含HTML内容
const div = document.createElement('div') const div = document.createElement('div')
div.innerHTML = htmlContent div.innerHTML = cleanedHtml
// 应用样式确保与思维导图显示一致大幅增加底部padding防止图片压线
const paddingTop = parseInt(tpcStyle.paddingTop) || 8
const paddingBottom = Math.max(parseInt(tpcStyle.paddingBottom) || 8, 35) // 大幅增加底部padding到35px
const paddingLeft = parseInt(tpcStyle.paddingLeft) || 8
const paddingRight = parseInt(tpcStyle.paddingRight) || 8
// 应用样式,确保与思维导图显示一致
div.style.cssText = ` div.style.cssText = `
width: 100%; width: 100%;
height: 100%; height: auto;
min-height: 100%;
font-family: ${tpcStyle.fontFamily}; font-family: ${tpcStyle.fontFamily};
font-size: ${tpcStyle.fontSize}; font-size: ${tpcStyle.fontSize};
color: ${tpcStyle.color}; color: ${tpcStyle.color};
background: transparent; background: transparent;
padding: ${tpcStyle.padding}; padding: ${paddingTop}px ${paddingRight}px ${paddingBottom}px ${paddingLeft}px;
box-sizing: border-box; box-sizing: border-box;
overflow: visible; overflow: visible;
text-align: center; text-align: center;
line-height: 1.4; line-height: 1.4;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
position: relative;
` `
// 为表格添加样式 // 为表格添加样式
@ -134,12 +467,12 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
}) })
}) })
// 为其他元素添加样式 // 优化列表样式,减少底部空白
const lists = div.querySelectorAll('ul, ol') const lists = div.querySelectorAll('ul, ol')
lists.forEach(list => { lists.forEach(list => {
const htmlList = list as HTMLElement const htmlList = list as HTMLElement
htmlList.style.cssText = ` htmlList.style.cssText = `
margin: 4px 0; margin: 2px 0 4px 0;
padding-left: 20px; padding-left: 20px;
text-align: left; text-align: left;
` `
@ -149,11 +482,50 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
listItems.forEach(item => { listItems.forEach(item => {
const htmlItem = item as HTMLElement const htmlItem = item as HTMLElement
htmlItem.style.cssText = ` htmlItem.style.cssText = `
margin: 2px 0; margin: 1px 0;
line-height: 1.3; line-height: 1.2;
padding: 0;
` `
}) })
// 为图片元素添加特殊样式处理,彻底解决图片压线问题
const images = div.querySelectorAll('img')
images.forEach((img, index) => {
const htmlImg = img as HTMLImageElement
// 更大的图片尺寸,减少图片和文字距离
const availableWidth = nodeWidth - paddingLeft - paddingRight - 5 // 进一步减少边距
const availableHeight = Math.floor(nodeHeight * 0.7) // 增加到70%高度
htmlImg.style.cssText = `
max-width: ${availableWidth}px !important;
max-height: ${availableHeight}px !important;
width: auto !important;
height: auto !important;
display: block !important;
margin: 8px auto 20px auto !important;
border-radius: 4px;
object-fit: contain !important;
flex-shrink: 0 !important;
box-sizing: border-box !important;
position: relative !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
`
console.log(`🖼️ 图片 ${index + 1} 强制尺寸控制:`, {
nodeWidth,
nodeHeight,
availableWidth,
availableHeight,
paddingLeft,
paddingRight,
bottomMargin: '20px'
})
})
foreignObject.appendChild(div) foreignObject.appendChild(div)
g.appendChild(foreignObject) g.appendChild(foreignObject)
console.log('✅ 使用foreignObject渲染HTML内容') console.log('✅ 使用foreignObject渲染HTML内容')
@ -169,6 +541,15 @@ function createElBox(mei: MindElixirInstance, tpc: Topic) {
const tpcStyle = getComputedStyle(tpc) const tpcStyle = getComputedStyle(tpc)
const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, tpc) const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, tpc)
// 检查是否有HTML内容或节点图片如果有则增加高度
const tpcWithNodeObj = tpc as Topic
const hasHTMLContent = tpcWithNodeObj.nodeObj && tpcWithNodeObj.nodeObj.dangerouslySetInnerHTML
const hasImages = hasHTMLContent && tpcWithNodeObj.nodeObj.dangerouslySetInnerHTML?.includes('<img')
const hasNodeImage = tpcWithNodeObj.nodeObj && tpcWithNodeObj.nodeObj.image
const originalHeight = parseInt(tpcStyle.height) || 100
const adjustedHeight = (hasImages || hasNodeImage) ? originalHeight + 50 : originalHeight
const bg = document.createElementNS(ns, 'rect') const bg = document.createElementNS(ns, 'rect')
setAttributes(bg, { setAttributes(bg, {
x: x + '', x: x + '',
@ -176,7 +557,7 @@ function createElBox(mei: MindElixirInstance, tpc: Topic) {
rx: tpcStyle.borderRadius, rx: tpcStyle.borderRadius,
ry: tpcStyle.borderRadius, ry: tpcStyle.borderRadius,
width: tpcStyle.width, width: tpcStyle.width,
height: tpcStyle.height, height: adjustedHeight + 'px',
fill: tpcStyle.backgroundColor, fill: tpcStyle.backgroundColor,
stroke: tpcStyle.borderColor, stroke: tpcStyle.borderColor,
'stroke-width': tpcStyle.borderWidth, 'stroke-width': tpcStyle.borderWidth,
@ -187,6 +568,15 @@ function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignOb
const tpcStyle = getComputedStyle(tpc) const tpcStyle = getComputedStyle(tpc)
const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, tpc) const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, tpc)
// 检查是否有HTML内容或节点图片如果有则增加高度
const tpcWithNodeObj2 = tpc as Topic
const hasHTMLContent2 = tpcWithNodeObj2.nodeObj && tpcWithNodeObj2.nodeObj.dangerouslySetInnerHTML
const hasImages2 = hasHTMLContent2 && tpcWithNodeObj2.nodeObj.dangerouslySetInnerHTML?.includes('<img')
const hasNodeImage2 = tpcWithNodeObj2.nodeObj && tpcWithNodeObj2.nodeObj.image
const originalHeight2 = parseInt(tpcStyle.height) || 100
const adjustedHeight2 = (hasImages2 || hasNodeImage2) ? originalHeight2 + 50 : originalHeight2
const bg = document.createElementNS(ns, 'rect') const bg = document.createElementNS(ns, 'rect')
setAttributes(bg, { setAttributes(bg, {
x: x + '', x: x + '',
@ -194,7 +584,7 @@ function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignOb
rx: tpcStyle.borderRadius, rx: tpcStyle.borderRadius,
ry: tpcStyle.borderRadius, ry: tpcStyle.borderRadius,
width: tpcStyle.width, width: tpcStyle.width,
height: tpcStyle.height, height: adjustedHeight2 + 'px',
fill: tpcStyle.backgroundColor, fill: tpcStyle.backgroundColor,
stroke: tpcStyle.borderColor, stroke: tpcStyle.borderColor,
'stroke-width': tpcStyle.borderWidth, 'stroke-width': tpcStyle.borderWidth,
@ -205,9 +595,9 @@ function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignOb
let text: SVGGElement | null = null let text: SVGGElement | null = null
// 检查是否有dangerouslySetInnerHTML内容 // 检查是否有dangerouslySetInnerHTML内容
const tpcWithNodeObj = tpc as Topic const tpcWithNodeObj3 = tpc as Topic
if (tpcWithNodeObj.nodeObj && tpcWithNodeObj.nodeObj.dangerouslySetInnerHTML) { if (tpcWithNodeObj3.nodeObj && tpcWithNodeObj3.nodeObj.dangerouslySetInnerHTML) {
console.log('🔍 处理dangerouslySetInnerHTML内容:', tpcWithNodeObj.nodeObj.dangerouslySetInnerHTML.substring(0, 200)) console.log('🔍 处理dangerouslySetInnerHTML内容:', tpcWithNodeObj3.nodeObj.dangerouslySetInnerHTML.substring(0, 200))
// 对于dangerouslySetInnerHTML使用ForeignObject // 对于dangerouslySetInnerHTML使用ForeignObject
text = generateSvgTextUsingForeignObject(tpc, tpcStyle, x, y) text = generateSvgTextUsingForeignObject(tpc, tpcStyle, x, y)
} else if (useForeignObject) { } else if (useForeignObject) {
@ -258,14 +648,34 @@ async function convertNodeImageToSvg(mei: MindElixirInstance, tpc: Topic): Promi
console.log('🖼️ 处理节点图片:', imageData) console.log('🖼️ 处理节点图片:', imageData)
// 计算图片位置(在节点中央) // 计算图片位置,为文字预留空间,限制图片尺寸防止压线
const nodeWidth = parseInt(tpcStyle.width) const nodeWidth = parseInt(tpcStyle.width)
const nodeHeight = parseInt(tpcStyle.height) const nodeHeight = parseInt(tpcStyle.height)
const imgWidth = imageData.width || 200 const maxImgWidth = Math.min(nodeWidth - 10, 300) // 进一步减少边距,增加最大宽度
const imgHeight = imageData.height || 150 const maxImgHeight = Math.min(nodeHeight * 0.6, 150) // 增加最大高度为节点的60%
const imgWidth = Math.min(imageData.width || 200, maxImgWidth)
const imgHeight = Math.min(imageData.height || 150, maxImgHeight)
// 计算文字内容的高度
let textHeight = 0
if (tpc.text && tpc.text.textContent) {
const textContent = tpc.text.textContent.trim()
if (textContent) {
// 估算文字高度:行数 * 行高
const lines = textContent.split('\n').length
const lineHeight = parseFloat(tpcStyle.lineHeight) || parseFloat(tpcStyle.fontSize) * 1.2
const fontSize = parseFloat(tpcStyle.fontSize)
const paddingTop = parseInt(tpcStyle.paddingTop) || 8
const paddingBottom = parseInt(tpcStyle.paddingBottom) || 8
textHeight = lines * lineHeight + paddingTop + paddingBottom
console.log('📝 文字高度计算:', { lines, lineHeight, fontSize, textHeight })
}
}
// 图片位置:水平居中,垂直位置考虑文字高度,大幅增加底部间距
const imgX = x + (nodeWidth - imgWidth) / 2 const imgX = x + (nodeWidth - imgWidth) / 2
const imgY = y + (nodeHeight - imgHeight) / 2 const imgY = y + textHeight + 20 // 减少底部间距到20px让图片和文字更接近
try { try {
// 尝试将图片转换为base64 // 尝试将图片转换为base64
@ -427,8 +837,12 @@ const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) =>
// 对于HTML内容表格等使用ForeignObject // 对于HTML内容表格等使用ForeignObject
console.log('✅ 使用ForeignObject渲染HTML内容') console.log('✅ 使用ForeignObject渲染HTML内容')
g.appendChild(convertDivToSvg(mei, tpc, noForeignObject ? false : true)) g.appendChild(convertDivToSvg(mei, tpc, noForeignObject ? false : true))
} else if (!hasImage) {
// 对于没有图片的普通文本内容
g.appendChild(convertDivToSvg(mei, tpc, false))
} else { } else {
// 对于普通文本内容 // 对于有图片的节点,只渲染文字部分(不包含图片)
console.log('📝 渲染有图片节点的文字内容')
g.appendChild(convertDivToSvg(mei, tpc, false)) g.appendChild(convertDivToSvg(mei, tpc, false))
} }
@ -458,16 +872,36 @@ const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) =>
g.appendChild(convertAToSvg(mei, hl as HTMLAnchorElement)) g.appendChild(convertAToSvg(mei, hl as HTMLAnchorElement))
}) })
// 处理图片元素 - 只处理不在节点内的独立图片 // 处理图片元素 - 只处理不在节点内的独立图片
const imgPromises = Array.from(mapDiv.querySelectorAll('img')).map(async (img) => { const allImages = Array.from(mapDiv.querySelectorAll('img'))
// 检查图片是否在节点内,如果在节点内则跳过(因为节点已经包含了图片和文字) console.log('🔍 发现的所有图片元素:', allImages.length)
const isInNode = img.closest('me-tpc')
if (isInNode) { const imgPromises = allImages.map(async (img, index) => {
return null // 跳过节点内的图片,因为节点已经包含了完整内容 // 检查图片是否在节点内
const parentNode = img.closest('me-tpc')
const isInForeignObject = img.closest('foreignObject')
console.log(`🖼️ 处理图片 ${index + 1}:`, {
isInNode: !!parentNode,
isInForeignObject: !!isInForeignObject,
src: img.src.substring(0, 50)
})
// 如果图片在节点内,跳过独立处理(节点已经包含了完整内容)
if (parentNode || isInForeignObject) {
console.log('⏭️ 跳过节点内图片')
return null
} }
// 只处理独立的图片元素
console.log('✅ 处理独立图片')
return await convertImgToSvg(mei, img) return await convertImgToSvg(mei, img)
}) })
const imgElements = await Promise.all(imgPromises) const imgElements = await Promise.all(imgPromises)
imgElements.forEach(imgEl => { const validImgElements = imgElements.filter(imgEl => imgEl !== null)
console.log('📊 有效图片元素数量:', validImgElements.length)
validImgElements.forEach(imgEl => {
if (imgEl) { if (imgEl) {
g.appendChild(imgEl) g.appendChild(imgEl)
} }