Compose Multiplatform的中文乱码问题

问题

最近在用Kotlin Multiplatform(KMP)+ Compose Multiplatform(CMP)开发软件,用Android Studio创建完工程后,工程中其实是自带示例代码的,但是运行之后就发现,如果把它展示的字符串里加上中文,比如"你好",中文就会显示乱码,但是在Android、DestkTop上就不会(没有Mac和iPhone,无法验证ios平台)。乱码如下:

略有点前端功底,所以检查了下生成的index.html,但也没发现问题,Kotlin代码和strings.xml也都检查了,有些地方还特意专门又去明确设置为了"UTF-8",仍旧无效,试过几个AI平台,给出的答案也都不对,最后从CodeBuddy处得到了答案。

字体、CMP和系统之间的关系

CMP其实并不关心字体(比如字体粗细、是否斜体、笔画末端是方头还是圆头),CMP呈现界面的原理就是使用绘画引擎(比如skia)把要画的东西画出来,其中按钮、背景等等都是CMP自己计算出来的,但是文字却是调用系统接口而产生的,也就是CMP读取到界面上有一个按钮,它就按照自己的一套算法算出这个按钮应该长什么样,读取到界面上有一串文字,它就调用系统接口询问本地系统这串文字应该长什么样------我要显示"你好"、宋体、加粗,你告诉我这些字该长什么样,系统收到后就去自己的字体库(如果系统本地库里没有对应的字也会按照一套规则去处理,比如没有宋体,就用楷体替补 )里查询然后返回给CMP于是它就画出来。很显然,Android、ios、DeskTop这些系统已经很成熟了,支持中文,而且还不止一种中文字体,所以当KMP向它们询问字体模样时,它们就能告诉KMP,但是Web却特殊,出于安全,Web是以一种叫沙箱的模式运行程序的,这种机制严格管控程序对系统本地接口的访问,其中就包括询问字体的接口,所以CMP程序在Web端根本就无法询问操作系统、无法使用本地的字体库,那么,它就只能自己去计算字体样貌了,也就是它需要自己带一套字体,但是通常字体包都是比较大,动辄好几MB,所以CMP只带了很少部分的字体------没有中文字体,这就导致CMP在web上无法计算出字体样式,进而无法绘制出来。

具体的执行场景可能还会略有差异,但总体原理就是CMP在Android、ios、desktop等平台上可以使用本地字体库得出字体模样,但在Web上由于缺少对应的字体库,所以中文乱码。

解决方案

下载中文字体包(如 Noto Sans SC)放入 Compose Resources 目录(比如app/shared/src/commonMain/composeResources/font/NotoSansSC-Regular.ttf),然后在Text 组件上显式指定 fontFamily 。这种方案对全平台都生效,如果不想其他平台受影响,可以在web平台这样设置,在其他平台使用默认效果。

复制代码
@Composable
fun App() {
    val notoSansSC = FontFamily(ComposeFont(Res.font.NotoSansSC_Regular))
    MaterialTheme {
        // 每个 Text 显式指定字体
        Text("Click me!", fontFamily = notoSansSC)
        Text("Compose: $greeting", fontFamily = notoSansSC)
    }
}