从零到完美:Mermaid 时序图绘制工作流
写在前面
最近在写一份技术文档,需要画架构图、时序图、流程图,加起来十几张。我比较抗拒用鼠标拖框——改个文字要重新对齐,换个节点要重画连线。
Mermaid 用代码生成图就好多了,改文字就改图。但问题来了:AI 生成的 Mermaid 代码渲染出来的 SVG,浏览器看着挺好,一插进 Word 就炸了。
矩形变黑块。线条消失。箭头方向乱跑。
花了一整天踩坑,最后把 beautiful-mermaid 这个开源库从里到外改了一遍,打包成了一个换台设备也能一键跑的工具包。这篇文章就是踩坑记录。
TIP: 如果你只是想在 Word 里插一张 Mermaid 图,直接跳到最后一节看工具包用法。
先看效果
这是同样的 Mermaid 源码,经过工具包渲染后的输出,直接拖进 Word 完美显示:
IMPORTANT: 问题不在 Mermaid,也不在 beautiful-mermaid。问题在 Word 的 SVG 渲染能力——它不支持 CSS 变量
var(--bg),不支持color-mix(),也不认orient="auto-start-reverse"。
第一关:文字全溢出了
刚开始渲染的第一批图,所有中文标签都溢出边框。
追了一下源码,beautiful-mermaid 估算文字宽度用的公式是 text.length × fontSize × 0.52。这个 0.52 是拉丁字符的平均宽高比。中文字符的宽高比接近 0.92——差了一倍。一个 5 字中文标签,估算宽度 34px,实际需要 60px。
修起来简单:数一下中文字符数,分别乘不同的系数。
// 原来:所有字符按拉丁算
return text.length * fontSize * widthRatio;
// 改后:中文单独算
const cjk = (text.match(/[一-鿿]/g) || []).length;
const latin = text.length - cjk;
return latin * fontSize * widthRatio + cjk * fontSize * 0.92;修完这个,文字不溢出了。但图还是不对。
第二关:菱形又大又丑
流程图里的判断节点(菱形)用的是 side = max(width, height) + diamondExtra。原库的 diamondExtra: 24——额外加 24px。一个"资源负载评估"标签,文字宽 60px,加上 32px 内边距,再加上 24px 额外空间,菱形撑到 116×116。
旁边的矩形节点"接收任务"才 80×38。菱形比矩形大三倍,视觉上很突兀。
两刀修:diamondExtra 砍到 8,长标签加 <br/> 换行。"任务复杂度
评估"拆成两行后,菱形从 118px 缩到 100px。
但 <br/> 在 beautiful-mermaid 里默认不生效——escapeXml 会把 < 转成 <。得让 escapeXml 保留 <br/>,然后把单行 <text> 改成多行 <tspan>。
第三关:时序图缺胳膊少腿
Mermaid 的标准时序图有几个要素:参与者矩形、虚线生命线、激活框(覆盖在生命线上的细条)、箭头、文字。
beautiful-mermaid 只渲染了前两个+后两个。激活框没了。
原因是激活框只在 Mermaid 源码里显式标了 +/- 时才画。标准 Mermaid 不需要——自动从第一条消息到最后一条画激活框。
补了一段自动计算逻辑:每个参与者扫描自己的第一条和最后一条消息,中间全画激活框。白底、黑边框、居中在虚线上。
然后是箭头起止点。原版箭头从参与者中心出发,直接越过激活框到目标中心——箭头扎进框里了。改成从激活框右边缘出发,到目标激活框左边缘止。
自调用消息(自己发给自己的箭头)的箭头是折线,向右绕一圈回来。这个折线把激活框也带歪了——alt 框的宽度只算了参与者的宽度,没算自调用消息向右延伸的部分。修复方式是在框宽度计算里扫描框内每一条消息的标签位置。
第四关:Word 不认你的 CSS
前三关调完之后,浏览器里看着已经很好了。插进 Word——全崩。
F12 看了一下 SVG 源码,beautiful-mermaid 用 CSS 变量实现主题:
<rect fill="var(--_node-fill)" stroke="var(--_node-stroke)" ... />--_node-fill 的定义在 <style> 里:var(--surface, color-mix(in srgb, var(--fg) 3%, var(--bg)))。
浏览器能解这个链条,Word 不能。Word 看到 var(--_node-fill) 不认识,默认给黑色——所以矩形全黑了。color-mix() 不认识,直接跳过——所以线条消失了。
解决方案:写了一个 flatten-svg.js,把 SVG 里所有 CSS 变量替换为具体色值。github-white 主题的配色很简单:
| 变量 | 值 |
|---|---|
--bg / --surface | #FFFFFF |
--line / --border / --accent | #000000 |
--fg | #1f2328 |
--muted | #555555 |
color-mix(in srgb, var(--fg) 20%, var(--bg)) 这种相对值也直接算成 #d2d3d4。
还把 <style> 块整个删了——Word 不需要那些 CSS 定义,只要元素上的颜色属性是具体的。
另外发现 Word 不支持 SVG 的 dy 属性(相对于基线的偏移量)。文本位置原本是 <text y="100" dy="0.35em">,Word 忽略 dy,文字就偏上了。改成 <text y="105">——把偏移直接算进 y 坐标,问题解决。
第五关:箭头的方向——坑最深的
颜色问题修完之后,插进 Word 一看——返回箭头全朝右了。
时序图里,实线箭头(请求)朝右,虚线箭头(返回)朝左。但 Word 里所有箭头都朝右。
问题出在 SVG 的 <marker> 标签。beautiful-mermaid 给 marker 设了 orient="auto-start-reverse"——浏览器会根据线条方向自动把 marker 翻转 180 度。Word 不认这个属性,所有箭头按原始多边形方向渲染,全朝右。
试了三版。
第一版:把 auto-start-reverse 换成 auto。Word 还是不认。
第二版:创建两个 marker——-l(左指)和 -r(右指),根据线条 x1 > x2 判断往哪走。方向对了,但位置漂了——箭头尖没对齐线端点。排查发现我只翻转了多边形的顶点坐标,没改 refX(连接点)。左指箭头的尖在 x=0 但 refX 还指着 x=10。
第三版:翻转多边形的时候同步翻转 refX。左指箭头:多边形镜像 + refX 归零。右指箭头:原样保留。
// 左指 marker: 多边形水平翻转 + refX 归零
const flipped = polygon.replace(...); // x -> w-x
const lMarker = marker.replace('refX="10"', 'refX="0"').replace(polygon, flipped);
// 右指 marker: 原样
const rMarker = marker;WARNING:
auto-start-reverse只影响 sequence 图的seq-arrow和seq-arrow-openmarker。流程图的arrowhead用的是orient="auto"不需要处理。还有个大坑:正则匹配 marker 时如果用[\s\S]*?会跨过前面的<marker>标签,把相邻的arrowhead也吞进去复制一份——结果变成两个id="arrowhead",SVG 非法,Word 直接不渲染箭头。改成<marker\b[^>]*\bid="..."匹配单个标签才修好。
工具包
所有修改已经打包。换台设备:
git clone https://github.com/Raylene-Y/mermaid-toolkit.git && cd mermaid-toolkit
bash install.sh然后一行命令:
node render-custom.mjs -i diagram.mmd -o diagram.svg -t github-white
node flatten-svg.js diagram.svgdiagram-flat.svg 直接拖进 Word——白底黑线,箭头方向正确,菱形大小合理,激活框正常。
项目地址:github.com/Raylene-Y/mermaid-toolkit
总结
一天时间改了 18 处。核心教训就三条:
- 英文开源库对中文的支持几乎为零。文字宽度、换行、标记方向,每个环节都要手动适配。
- Word 的 SVG 支持远比你想象的弱。CSS 变量、
color-mix()、auto-start-reverse全不支持。但它的 SVG 规范支持其实还行——只要把颜色写死、marker 拆好,就能完美渲染。 flatten-svg.js打通了最后一公里。原来是 beautiful-mermaid → 浏览器能看但 Word 不能看,现在加一层压平,输出直接进 Word。
如果有和我一样需要在 Word 文档里大量插 Mermaid 图的,这个工具包应该能省你一天时间。