🚀 先看结论
- 🌍 跨平台:支持 Android、iOS、Desktop 和 Web(Kotlin/Wasm)。
- 📊 图表范围:目前只支持 8 类图表:Flowchart、Sequence Diagram、State Diagram、Class Diagram、ER Diagram、Gantt、Pie Chart 和 XY Chart;其他图表类型仍在持续翻译和验证中。
- ✅ 严谨测试:8 类图各 256 个唯一 Mermaid 源码,共完成 2,048 组 Native/Official 对拍、4,096 张原始截图和 128 页 contact sheet。查看完整测试报告
- 🧪 立即体验:打开 Web Playground
Mermaid 很适合把流程图、时序图、状态图直接写进 Markdown。
但在 Android、iOS 或 Compose Multiplatform 应用里,想展示 Mermaid,常见做法几乎都是:
- 启动一个 WebView;
- 加载 Mermaid.js;
- 把源码交给 JavaScript;
- 最后显示一段 SVG。
只展示一张图时,这个方案很省事;一个页面要展示十几张甚至几十张图时,WebView 的内存、启动开销、生命周期和滚动体验就会逐渐变成问题。
所以我做了一个开源项目:CMP Mermaid。
它把 Mermaid 12.0.0 的解析、图表状态和渲染语义翻译到 Kotlin Multiplatform,最终交给 Compose Canvas 绘制。生产渲染链路不依赖 WebView,也不加载 Mermaid.js。
- GitHub:github.com/swithun-liu…
- 在线 Demo:swithun-liu.github.io/cmp-mermaid…
- 2,048 案例视觉报告:github.com/swithun-liu…
这不是把 Mermaid 放进 WebView
CMP Mermaid 的正式渲染流程是:
Mermaid source
-> Kotlin preprocessor and translated parser
-> translated diagram database and layout preparation
-> platform-independent MermaidScene
-> Compose Canvas
其中:
mermaid-core负责解析、Diagram DB、布局准备、主题和平台无关 SceneGraph;mermaid-compose负责 Compose Canvas 绘制、文本测量、交互与缩放;mermaid-debug-ui才包含文档、Playground 和 Mermaid.js Official 对比能力,并与生产模块隔离。
因此业务模块只需要依赖渲染能力,不需要把完整 Demo 或 Official 对比工具打进正式包。
Android 的 debug/release APK 都经过权限检查,不声明:
android.permission.INTERNET
需要特别说明的是:ELK 布局路径使用 Mermaid 对 elkjs@0.9.3 的适配方式,在 Native 端通过隔离运行时执行 ELK worker;这只是布局实现细节,正式渲染仍不加载 Mermaid.js,也不创建 WebView。无法忠实跨平台表达的 Mermaid 能力,会明确返回 MermaidError.UnsupportedFeature,而不是偷偷画一个“看起来差不多”的结果。
下面先看两组同源码对比。
Flowchart:CMP Native / Compose Canvas
Flowchart:Official / Mermaid.js 12.0.0
XY Chart:CMP Native / Compose Canvas
XY Chart:Official / Mermaid.js 12.0.0
为什么选择“翻译源码”,而不是重新发明一套
如果目标只是画几个 Demo,自己写一个简化版 parser 和自动布局并不难。
真正困难的是长期维护:
- Mermaid 新版本增加了语法怎么办?
- 某个 shape、marker 或 edge routing 改了怎么办?
- parser 能识别,但 Diagram DB 的状态语义不同怎么办?
- 浏览器 SVG 和 Compose Canvas 的文本测量不同怎么办?
如果实现与上游没有映射关系,每次升级都只能靠截图找差异,再一点点猜问题在哪里。短期看起来快,长期维护成本会非常高。
CMP Mermaid 采用的是源码映射的忠实翻译路线:
- 锁定 Mermaid、Jison、D3、Marked、Dagre、ELK 等版本;
- Kotlin parser、DB、layout、shape、theme、renderer 都记录对应的上游文件和函数;
- 可生成的 parser table、rule、entity、fixture 和 worker 从锁定输入生成;
- Mermaid 升级时,对照上游 diff,只翻译发生变化的部分;
- 每次变更重新跑 Native/Official 对拍和跨平台门禁。
这不意味着 Kotlin 代码逐字符照搬 JavaScript,而是要求每段核心行为都能回答两个问题:
它对应 Mermaid 的哪段源码?
为什么 Compose 端需要这个适配?
举两个实际修复过的问题。
XY Chart 的长标题为什么会被裁切
Mermaid.js 的 SVG 使用固定 viewBox,但 SVG 默认允许文本 overflow: visible。Compose Canvas 默认会裁剪到自身边界。
如果只照搬坐标,不处理渲染载体差异,同一段长标题在浏览器里完整,在 Compose 中就会被截断。
最终的处理不是重写标题布局,而是:
- 保留 Mermaid 的标题、坐标轴、图例和 plot 坐标;
- 根据翻译后的文本边界扩展 Scene viewport;
- 只补齐 SVG 可见溢出与 Canvas 裁剪之间的语义差异。
ER 图的长别名为什么不能简单按字符换行
浏览器 break-spaces 会在有换行机会的文本上换行,但不会把 BILLING_PROFILE 这样的连续标识符强行拆成两行。
Compose 的默认软换行行为并不完全相同。如果对所有 Entity title 统一打开换行,长别名正常了,普通数据库标识符却可能变成:
BILLING_PROFIL
E
最终实现将 Mermaid 的 wrappingWidth 同时用于测量和 SceneText.softWrap,但只对存在空白断点的 alias 开启软换行;无空格标识符继续保持单行。
这类问题也是“功能能跑”和“效果可用于生产”之间的差距。
当前支持什么
当前版本支持 8 类图:
| 图表 | 状态 | 主要覆盖能力 |
|---|---|---|
| Flowchart | Stable | Jison/FlowDB、Dagre、ELK、形状、连线、Markdown/HTML 标签 |
| XY Chart | Stable | D3 比例尺和刻度、柱状/折线混合、标签 |
| Sequence | Stable | 参与者、26 种消息形式、Note、Activation、控制区域 |
| Class | Stable | 分区、泛型、命名空间、关系、ELK/Dagre |
| State | Stable | 复合状态、并发、Note、Fork/Join、ELK/Dagre |
| Entity Relationship | Stable | 属性、基数、关系、嵌套子图 |
| Gantt | Stable | 日期、依赖、排除日期、里程碑、D3 风格刻度 |
| Pie | Stable | Langium grammar、D3 angle、donut、legend、palette |
同时内置 Mermaid 12.0.0 的 11 个主题:
default, dark, forest, neutral, base,
neo, neo-dark, redux, redux-color,
redux-dark, redux-dark-color
业务侧也可以从预设主题通过 Kotlin copy 派生品牌主题,或者传入 Mermaid 兼容的 themeVariables。
“Stable”不是贴一个标签,而是拿证据说话
最初项目只有 Demo 和少量截图时,我并不认为它足以叫 Stable。
现在的验证分为三层,而且三层不会互相冒充:
第一层:106 个独立生产场景
这些是手写的复杂生产结构,不来自 Demo Gallery,用于:
- 能力覆盖;
- 确定性 SceneGraph 重放;
- 人工视觉审查;
- 性能压力测试;
- 常规 Quality Gate。
第二层:2,048 个 Native/Official 视觉案例
8 类图每类 256 个唯一 Mermaid 源码,总计:
- 2,048 个 CMP Native 截图;
- 2,048 个 Mermaid.js
12.0.0Official 截图; - 4,096 张原始截图;
- 128 页分页 contact sheet;
- 全部记录源码、seed、profile、feature 与 SHA-256。
每类图由 13~14 个复杂结构种子和 20 个可见文本/布局压力 profile 确定性组合而成。我不会把它描述成“每类 256 个完全不同的拓扑”,但每个源码都唯一,而且文本长度和布局压力会真实变化。
全部 2,048 对都通过自动几何门禁:
width ratio: 1.003 - 1.261
height ratio: 0.878 - 1.140
ink ratio: 0.585 - 1.467
failures: 0
这里检查的是空白图、严重裁切、内容边界和前景密度,不会把它包装成像素级或完整语义证明,所以人工审查仍然保留。
第三层:2,048 个 Native-only 随机压力输入
它们负责验证 parser 与 layout 的鲁棒性,但因为没有 Mermaid.js Official 截图,所以不会被算进 Official 对拍证据。
此外还有:
JVM tests: 283 passed, 0 failed
Theme matrix: 88 / 88
Deterministic replay: 106 passed, 0 mismatch
Core production soak: 530 renders, 66ms P95
Platform builds: Android / iOS / Desktop / Web passed
完整报告和所有对比图都放在仓库里,不需要只相信 README 上的一句话:
- Stable 报告:github.com/swithun-liu…
- 2,048 案例报告:github.com/swithun-liu…
如何接入
模块已经拆成生产能力和调试能力:
dependencies {
implementation("com.swithun:mermaid-compose:0.1.0")
debugImplementation("com.swithun:mermaid-debug-ui:0.1.0")
}
基本用法:
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.swithun.cmpmermaid.compose.MermaidDiagram
import com.swithun.cmpmermaid.core.MermaidTheme
import com.swithun.cmpmermaid.core.MermaidThemePreset
@Composable
fun Diagram(source: String) {
MermaidDiagram(
source = source,
modifier = Modifier.fillMaxWidth(),
theme = MermaidTheme.preset(MermaidThemePreset.Default),
contentDescription = "Mermaid diagram",
)
}
目前 Maven 坐标和 POM 已准备好,但首个公开制品仓库版本还没有上传。现阶段可以直接依赖仓库模块,或发布到本地 Maven 仓库:
./gradlew \
:mermaid-core:publishAllPublicationsToBuildRepository \
:mermaid-compose:publishAllPublicationsToBuildRepository \
:mermaid-debug-ui:publishAllPublicationsToBuildRepository
跨平台情况
Parser、Diagram DB、布局准备和 SceneGraph 主要位于 commonMain,目前构建和运行验证覆盖:
- Android;
- iOS;
- Desktop;
- Web(Kotlin/Wasm)。
在线 Demo 本身也是 Compose Multiplatform Web 版本,可以直接体验全部图表、主题和 Native/Official 对比:
swithun-liu.github.io/cmp-mermaid…
仍然有哪些边界
Stable 不代表“Mermaid 的所有合法语法已经 100% 实现”。
更准确的描述是:
在项目明确声明的 Mermaid
12.0.0支持范围内,8 类图通过了生产场景、视觉对拍、随机压力、性能和跨平台门禁。
当前边界包括:
- 只覆盖文档中列出的 8 类图;
- 任意浏览器
themeCSS依赖 DOM/CSS 语义,Native 端不做静默近似; - 不支持的合法能力会返回
MermaidError.UnsupportedFeature; - Compose 与浏览器字体度量不同,不追求像素级完全一致;
- 公共 Maven 仓库制品还没有正式上传。
这些边界全部公开记录,比“什么都说支持,但错误时画一张近似图”更适合生产接入。
最后
这个项目最初只是想解决一个很具体的问题:
一个页面需要展示很多 Mermaid 图时,能不能不要创建很多 WebView?
继续做下去后,真正困难的部分逐渐变成了:如何让 Kotlin 实现保持可维护,如何跟随 Mermaid 上游版本,以及如何证明它不只对 Demo 有效。
现在 CMP Mermaid 已经有一条可持续的源码翻译路线,也有公开、可复现的视觉和跨平台证据。
如果你正在做 Kotlin Multiplatform、Compose Multiplatform、Markdown/文档工具,或者也遇到 Native Mermaid 渲染问题,可以到 GitHub 看看实现和测试报告:
GitHub:github.com/swithun-liu…
觉得项目有价值,欢迎 Star、提交 Issue,或者带着真实 Mermaid case 来验证。