不用 WebView,我用 Kotlin + Compose Multiplatform 重写 Mermaid,并做了 2048 组对拍

Mermaid 很适合把流程图、时序图、状态图直接写进 Markdown。

但在 Android、iOS 或 Compose Multiplatform 应用里,想展示 Mermaid,常见做法几乎都是:

  1. 启动一个 WebView;
  2. 加载 Mermaid.js;
  3. 把源码交给 JavaScript;
  4. 最后显示一段 SVG。

只展示一张图时,这个方案很省事;一个页面要展示十几张甚至几十张图时,WebView 的内存、启动开销、生命周期和滚动体验就会逐渐变成问题。

所以我做了一个开源项目:CMP Mermaid

它把 Mermaid 12.0.0 的解析、图表状态和渲染语义翻译到 Kotlin Multiplatform,最终交给 Compose Canvas 绘制。生产渲染链路不依赖 WebView,也不加载 Mermaid.js。

这不是把 Mermaid 放进 WebView

CMP Mermaid 的正式渲染流程是:

text 复制代码
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 都经过权限检查,不声明:

text 复制代码
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 采用的是源码映射的忠实翻译路线

  1. 锁定 Mermaid、Jison、D3、Marked、Dagre、ELK 等版本;
  2. Kotlin parser、DB、layout、shape、theme、renderer 都记录对应的上游文件和函数;
  3. 可生成的 parser table、rule、entity、fixture 和 worker 从锁定输入生成;
  4. Mermaid 升级时,对照上游 diff,只翻译发生变化的部分;
  5. 每次变更重新跑 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 统一打开换行,长别名正常了,普通数据库标识符却可能变成:

text 复制代码
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 个主题:

text 复制代码
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.0 Official 截图;
  • 4,096 张原始截图;
  • 128 页分页 contact sheet;
  • 全部记录源码、seed、profile、feature 与 SHA-256。

每类图由 13~14 个复杂结构种子和 20 个可见文本/布局压力 profile 确定性组合而成。我不会把它描述成"每类 256 个完全不同的拓扑",但每个源码都唯一,而且文本长度和布局压力会真实变化。

全部 2,048 对都通过自动几何门禁:

text 复制代码
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 对拍证据。

此外还有:

text 复制代码
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 上的一句话:

如何接入

模块已经拆成生产能力和调试能力:

kotlin 复制代码
dependencies {
    implementation("com.swithun:mermaid-compose:0.1.0")
    debugImplementation("com.swithun:mermaid-debug-ui:0.1.0")
}

基本用法:

kotlin 复制代码
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 仓库:

bash 复制代码
./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 来验证。

相关推荐
邪修king5 小时前
Re:Linux系统篇(二十六):文件系统(二):Ext 文件系统底层详解:从 inode、块组到软硬链接,结合 Windows 讲透文件管理本质
android·java·linux
parksben5 小时前
OpenSider:让浏览器驱动 Agent
开源·github·agent
又见情义6 小时前
RK3568 Android 13 版本号管理实战:基于 ROCKCHIP_BUILD_NUMBER 的定制化方案
android
程序员老赵6 小时前
Docker 部署填鸭表单完整教程:搭建私有化问卷与表单收集平台
前端·docker·开源
JMchen6 小时前
第 9 篇|网络请求基础 —— 从 HttpURLConnection 到 OkHttp
kotlin·android studio
终端安全笔记6 小时前
iOS 27 强制 TLS 1.2:租赁设备的注册链路会在哪一环断
android·网络·安全·ios·智能手机
Rudon滨海渔村6 小时前
免费开源的Web版windows画图软件 - 简单画画 - js Paint
开源·画图·ms·画画
mmsx6 小时前
MapLibre 实战 11|用户说"我的地块丢了":一个 sealed class 图层模型,和四个让我重构三版的坑
android·前端·开源
追涨杀跌老能手7 小时前
GD32H759 + RT-Thread 工控实战--第4篇 SDRAM,SDIO,以及触摸屏
开源·嵌入式
DolphinScheduler社区7 小时前
Apache DolphinScheduler 3.4.3 发布!权限安全与稳定性全面增强,调度补火即将上线
开源·agent·海豚调度·大数据工作流调度