如何在XSLT文件中使用GetBbox()实现SVG绘图文本框居中
问题描述
我有以下XSLT代码(感谢Martin Honnen提供)和SVG文件,想要让生成的文本块居中显示在对应的多边形图形上方。需要为<text x="" y="" id="{$id}-text">和<tspan id="{$id}-tspan" x="" y="">动态计算x、y坐标——因为每个图形的位置都不固定。
我知道需要借助JavaScript,且整体流程要通过调用getBBox()方法的VBA宏触发,但不清楚怎么在XSLT中结合这个方法,也不确定合适的JS代码怎么写。逻辑上x坐标应该是父组的bbox中心x值,y坐标同理,但不知道怎么通过@id关联对应的图形和文本。
现有XSLT代码
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" exclude-result-prefixes="svg" version="1.0"> <xsl:strip-space elements="*"/> <xsl:template match="svg:g[@id[starts-with(., 'Item')]]"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> <xsl:variable name="id" select="substring-before(@id, '-')"/> <text x="" y="" id="{$id}-text"> <tspan id="{$id}-tspan" x="" y=""> <xsl:value-of select="$id"/> </tspan> </text> </xsl:copy> </xsl:template> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
待转换的SVG文件
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="exportSvg" width="400" height="400"> <defs/> <rect width="400" height="400" transform="translate(0, 0)" fill="rgb(255, 255, 255)" style="fill:rgb(255, 255, 255);"/> <g> <g id="Drawing-svg" clip-path="url(#rect-mask-Drawing)"> <clipPath id="rect-mask-Drawing"> <rect x="0" y="0" width="400" height="400"/> </clipPath> <g id="chart-svg"> <g id="svg-main" clip-path="url(#rect-mask-Main)"> <clipPath id="rect-mask-Main"> <rect x="0" y="0" width="400" height="400"/> </clipPath> <g id="Drawing-svg"> <g id="Parts-svg"> <g id="Section-svg"> <g id="Item1-svg"> <path d="M 155.09357,45.542471 104.77897,86.931934 75,200 152.79121,141.87343 200,84.246354 Z" stroke="#e6e6e6" style="fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id="Item1"/> </g> <g id="Item2-svg"> <path d="M 198.06872,89.614437 -9.21291,31.643703 -23.42303,34.67823 51.52002,20.68699 47.20879,-57.62707 z" stroke="#e6e6e6" style="fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id="Item2"/> </g> <g id="Item3-svg"> <path d="M 161.0455,182.56778 -41.68122,-5.64443 15.98375,27.05111 67.62172,3.73783 32.80201,-13.55927 z" stroke="#e6e6e6" style="fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id="Item3"/> </g> </g> </g> </g> </g> </g> </g> </g> </svg>
解决方案
1. 调整XSLT
XSLT仅能完成静态元素生成,无法获取SVG渲染时的边界框属性,所以先修改XSLT,给文本元素添加标识属性并设置初始坐标,同时加入文本居中的样式属性,简化后续JS计算:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" exclude-result-prefixes="svg" version="1.0"> <xsl:strip-space elements="*"/> <xsl:template match="svg:g[@id[starts-with(., 'Item')]]"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> <xsl:variable name="id" select="substring-before(@id, '-')"/> <!-- 添加class便于JS选择,设置文本居中属性 --> <text x="0" y="0" id="{$id}-text" class="item-label" text-anchor="middle" dominant-baseline="middle"> <tspan id="{$id}-tspan"> <xsl:value-of select="$id"/> </tspan> </text> </xsl:copy> </xsl:template> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
2. 编写JavaScript代码
通过JS遍历所有Item组,借助getBBox()获取图形边界框,计算中心坐标后绑定到对应文本元素:
function centerItemLabels() { // 筛选所有Item开头的分组 const itemGroups = document.querySelectorAll('g[id^="Item"]'); itemGroups.forEach(group => { const itemId = group.id.split('-')[0]; // 获取当前组对应的图形路径 const path = group.querySelector(`#${itemId}`); if (!path) return; // 获取路径的边界框 const bbox = path.getBBox(); // 计算边界框中心坐标 const centerX = bbox.x + bbox.width / 2; const centerY = bbox.y + bbox.height / 2; // 绑定坐标到文本元素 const text = document.getElementById(`${itemId}-text`); if (text) { text.setAttribute('x', centerX); text.setAttribute('y', centerY); // tspan继承text坐标,若需换行可单独调整 const tspan = document.getElementById(`${itemId}-tspan`); if (tspan) { tspan.setAttribute('x', centerX); tspan.setAttribute('y', centerY); } } }); } // SVG加载完成后自动执行 window.addEventListener('load', centerItemLabels);
3. VBA宏执行逻辑
在VBA中通过WebBrowser控件加载转换后的SVG,执行JS脚本并保存修改后的文件:
Sub CenterSVGLabels() Dim svgPath As String Dim objBrowser As Object svgPath = "C:\你的SVG文件路径\transformed.svg" ' 替换为实际路径 Set objBrowser = CreateObject("InternetExplorer.Application") objBrowser.Visible = False ' 加载SVG文件 objBrowser.Navigate "file:///" & Replace(svgPath, "\", "/") ' 等待加载完成 Do While objBrowser.Busy Or objBrowser.ReadyState <> 4 DoEvents Loop ' 执行居中脚本 objBrowser.Document.parentWindow.execScript "centerItemLabels();" ' 保存修改后的SVG Dim modifiedSVG As String modifiedSVG = objBrowser.Document.DocumentElement.outerHTML Open "C:\输出路径\centered.svg" For Output As #1 Print #1, modifiedSVG Close #1 objBrowser.Quit Set objBrowser = Nothing End Sub
关键说明
- XSLT负责静态生成文本元素,无法获取渲染时的边界框,必须依赖JS在SVG加载后计算坐标。
getBBox()返回元素局部坐标系下的边界框,无需额外处理父元素变换,直接计算中心即可。text-anchor和dominant-baseline属性确保文本以设置的x/y为中心对齐,无需额外偏移调整。
内容的提问来源于stack exchange,提问作者Oran G. Utan
相关产品推荐
相关产品推荐

