CJK words can not correctly show in the browser
Describe the bug Currently CJK words can not correctly show in the browser I don't know how does compose get latin font. it might be getting from local system or just package it in the web package, but according to sources i read, it seems compose obtain font from skia, or in other word, render in skia. If the former one, we just need to add correct font fallback logic and configurations. To load a a custom full weight font family such as noto sans in just simplified chinese can take up to more than 90 mega bytes, even in local test, it takes me several seconds to load the page with full font familly loaded, that's an unbearable cost.
And this also restricted user input. All the texts are rendered in skiko, so that all the text will have to ensure there's available font to show, If i designed a textfield that allows user to input any characters in unicode, then cjk words can't show up, emojis can't show up and any character not included in packaged font will not show up.(Then we can't simply do compress on font by only include chars we used in webpage like font-spider)
I think we must pay attention to this problem.
What I tried to avoid this problem is to load font by font name such as "Microsoft Yahei", "Noto Sans CJK", "Noto Sans SC" or all of them with space removed from SkTypeface and convert SkTypeface object into Typeface and then to FontFamily, but that doesn't work...
Or is there any other way available to load local fonts?
Thank you all for your great effort work on this great project. Affected platforms Select one of the platforms below:
- Web (K/Wasm) - Canvas based API
- Web (K/JS) - Canvas based API
Versions
- Kotlin version*: 1.9.20
- Compose Multiplatform version*: 1.5.10
To Reproduce
- Create a Text with cjk word(such as "你好") in canvas
- Build it and open it in browser
- And you can see that the word shows as two square
Expected behavior Text should shows up correctly
Screenshots
The chinese words doesn' show correctly
i try to load resource by using LaunchedEffect, it works but the experience is a bit poor.
@OptIn(ExperimentalResourceApi::class)
suspend fun loadCjkFont(): FontFamily {
val regular = resource("font/NotoSansCJKsc-Regular.ttf").readBytes()
val bold = resource("font/NotoSansCJKsc-Bold.ttf").readBytes()
val italic = resource("font/NotoSansCJKsc-Italic.ttf").readBytes()
return FontFamily(
Font(identity = "CJKRegular", data = regular, weight = FontWeight.Normal),
Font(identity = "CJKBold", data = bold, weight = FontWeight.Bold),
Font(identity = "CJKItalic", data = italic, style = FontStyle.Italic),
)
}
@Composable
fun App() {
var typography by remember { mutableStateOf<Typography?>(null) }
LaunchedEffect(Unit) {
val font = loadCjkFont()
typography = Typography(defaultFontFamily = font)
}
MaterialTheme(typography = typography ?: MaterialTheme.typography) {...
}
}
i try to load resource by using
LaunchedEffect, it works but the experience is a bit poor.@OptIn(ExperimentalResourceApi::class) suspend fun loadCjkFont(): FontFamily { val regular = resource("font/NotoSansCJKsc-Regular.ttf").readBytes() val bold = resource("font/NotoSansCJKsc-Bold.ttf").readBytes() val italic = resource("font/NotoSansCJKsc-Italic.ttf").readBytes() return FontFamily( Font(identity = "CJKRegular", data = regular, weight = FontWeight.Normal), Font(identity = "CJKBold", data = bold, weight = FontWeight.Bold), Font(identity = "CJKItalic", data = italic, style = FontStyle.Italic), ) } @Composable fun App() { var typography by remember { mutableStateOf<Typography?>(null) } LaunchedEffect(Unit) { val font = loadCjkFont() typography = Typography(defaultFontFamily = font) } MaterialTheme(typography = typography ?: MaterialTheme.typography) {... } }
Thank you for your help, here's my implementation with lower invasivieness: https://github.com/ISNing/XWareManage/commit/7c3d63ce93efa6bc169f2c33c5c8be3511f59bb4
But it still can't help with the huge transportation for fetching font files(even if only to load three weights)
Looking forward to official solution.
@eymar Is there a plan to address this issue?
Yes, we plan to address it after we address the issues which have no workarounds.
i try to load resource by using LaunchedEffect, it works but the experience is a bit poor.
原来直接用就可以了,不需要这么麻烦 Text("首页", fontFamily = MiSansFont()
但是上面的代码在 JVM 是正常的,在 Web 却显示方块,奇怪
i try to load resource by using LaunchedEffect, it works but the experience is a bit poor.
@OptIn(ExperimentalResourceApi::class) suspend fun loadCjkFont(): FontFamily { val regular = resource("font/NotoSansCJKsc-Regular.ttf").readBytes() val bold = resource("font/NotoSansCJKsc-Bold.ttf").readBytes() val italic = resource("font/NotoSansCJKsc-Italic.ttf").readBytes() return FontFamily( Font(identity = "CJKRegular", data = regular, weight = FontWeight.Normal), Font(identity = "CJKBold", data = bold, weight = FontWeight.Bold), Font(identity = "CJKItalic", data = italic, style = FontStyle.Italic), ) } @Composable fun App() { var typography by remember { mutableStateOf<Typography?>(null) } LaunchedEffect(Unit) { val font = loadCjkFont() typography = Typography(defaultFontFamily = font) } MaterialTheme(typography = typography ?: MaterialTheme.typography) {... } }你好,请问能给一些详细的代码吗?试了一下好像不起作用 QAQHi, could you please give me some detailed code? I tried it but it doesn't work.
@Composable @Preview fun MainApp() { var fontFamily by remember { mutableStateOf<FontFamily?>(null) } val customFont = MiSansVfFont() LaunchedEffect(Unit) { fontFamilyby = customFont } MaterialTheme { Column(horizontalAlignment = Alignment.CenterHorizontally) { Row(horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.Top) { Text("首页", fontFamily = fontFamily ?: MaterialTheme.typography.displayMedium.fontFamily) Text("EnglishText", fontFamily = fontFamily ?: MaterialTheme.typography.displayMedium.fontFamily) } } } } @Composable fun MiSansVfFont() = FontFamily( Font( resource = Res.font.misans_vf_normal, weight = FontWeight.Normal, style = FontStyle.Normal ), Font( resource = Res.font.misans_vf_bold, weight = FontWeight.Bold, style = FontStyle.Normal ), Font( resource = Res.font.misans_vf_thin, weight = FontWeight.Thin, style = FontStyle.Normal ) )原来直接用就可以了,不需要这么麻烦
Text("首页", fontFamily = MiSansFont()但是上面的代码在 JVM 是正常的,在 Web 却显示方块,很奇怪
I found that it is OK to use it directly, no need to be so complicated.
Text("首页", fontFamily = MiSansFont()But the above code works in JVM, but it is displayed as a square on the Web, which is very strange
Don't know what resource library you're using, but I guess it's because for the web target, the font files are fetched asyncnormously (calling the getter of the resource will return null at the beginning when the page is loaded), and when it comes to the first composing, the fonts aren't loaded. Then you will have to handle the expected application recomposing when the fonts loaded manually.
(虽然我英文很烂,但是应该能看懂,懒得中英再写两遍了[捂脸])
Is there any method to avoid loading large fonts?
https://github.com/OmicoDev/wwm
https://wwm.omico.me
@Omico you might try to use Local Font Access API https://developer.mozilla.org/en-US/docs/Web/API/Local_Font_Access_API, it's experimental and available only in Chrome and Edge.
Compose Multiplatform 1.7.0-dev1721 is out and it contains the Fonts fallback provider change: https://github.com/JetBrains/compose-multiplatform-core/pull/1400 (please take a look to see a usage example).
That version of Compose for Web can be used either with kotlin 1.9.24 or with 2.0.10-RC-515 (it won't work with kotlin 2.0).
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
flutter can use browser fonts,compose can not