compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

CJK words can not correctly show in the browser

Open ISNing opened this issue 2 years ago • 8 comments

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

  1. Create a Text with cjk word(such as "你好") in canvas
  2. Build it and open it in browser
  3. And you can see that the word shows as two square

Expected behavior Text should shows up correctly

Screenshots 334086eb55d2b8f9a0cc50e16e009046 The chinese words doesn' show correctly

ISNing avatar Nov 24 '23 06:11 ISNing

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) {...
    }
}

record

yazinnnn avatar Dec 08 '23 09:12 yazinnnn

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) {...
    }
}

record record

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.

ISNing avatar Dec 11 '23 05:12 ISNing

@eymar Is there a plan to address this issue?

KevinnZou avatar Feb 22 '24 08:02 KevinnZou

Yes, we plan to address it after we address the issues which have no workarounds.

eymar avatar Feb 22 '24 08:02 eymar

i try to load resource by using LaunchedEffect, it works but the experience is a bit poor.

你好,请问能给一些详细的代码吗?试了一下好像不起作用

原来直接用就可以了,不需要这么麻烦 Text("首页", fontFamily = MiSansFont()

但是上面的代码在 JVM 是正常的,在 Web 却显示方块,奇怪

zhangz1han avatar May 30 '24 13:05 zhangz1han

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) {...
    }
}
你好,请问能给一些详细的代码吗?试了一下好像不起作用 QAQ

Hi, 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.

(虽然我英文很烂,但是应该能看懂,懒得中英再写两遍了[捂脸])

ISNing avatar Jun 03 '24 05:06 ISNing

Is there any method to avoid loading large fonts? https://github.com/OmicoDev/wwm https://wwm.omico.me image

Omico avatar Jun 16 '24 16:06 Omico

@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.

eymar avatar Jun 17 '24 09:06 eymar

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).

eymar avatar Jul 10 '24 13:07 eymar

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.

okushnikov avatar Jul 14 '24 14:07 okushnikov

flutter can use browser fonts,compose can not

djj20115502 avatar Sep 24 '24 01:09 djj20115502