diff --git a/common/src/commonMain/kotlin/tk/zwander/commonCompose/util/FontMapper.kt b/common/src/commonMain/kotlin/tk/zwander/commonCompose/util/FontMapper.kt new file mode 100644 index 000000000..bdfe1ab82 --- /dev/null +++ b/common/src/commonMain/kotlin/tk/zwander/commonCompose/util/FontMapper.kt @@ -0,0 +1,78 @@ +package tk.zwander.commonCompose.util + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalFontFamilyResolver +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.GenericFontFamily +import korlibs.memory.Platform +import tk.zwander.common.util.CrossPlatformBugsnag + +object FontMapper { + private val GenericFontFamiliesMapping: Map> by lazy { + when { + Platform.isLinux -> + mapOf( + FontFamily.SansSerif.name to listOf("Noto Sans", "DejaVu Sans", "Arial"), + FontFamily.Serif.name to listOf("Noto Serif", "DejaVu Serif", "Times New Roman"), + FontFamily.Monospace.name to listOf("Noto Sans Mono", "DejaVu Sans Mono", "Consolas"), + // better alternative? + FontFamily.Cursive.name to listOf("Comic Sans MS"), + ) + Platform.isWindows -> + mapOf( + // Segoe UI is the Windows system font, so try it first. + // See https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts + FontFamily.SansSerif.name to listOf("Segoe UI", "Arial"), + FontFamily.Serif.name to listOf("Times New Roman"), + FontFamily.Monospace.name to listOf("Consolas"), + FontFamily.Cursive.name to listOf("Comic Sans MS"), + ) + Platform.isMac || Platform.isIos || Platform.isTvos || Platform.isWatchos -> + mapOf( + // .AppleSystem* aliases is the only legal way to get default SF and NY fonts. + FontFamily.SansSerif.name to listOf(".AppleSystemUIFont", "Helvetica Neue", "Helvetica"), + FontFamily.Serif.name to listOf(".AppleSystemUIFontSerif", "Times", "Times New Roman"), + FontFamily.Monospace.name to listOf(".AppleSystemUIFontMonospaced", "Menlo", "Courier"), + // Safari "font-family: cursive" real font names from macOS and iOS. + FontFamily.Cursive.name to listOf("Apple Chancery", "Snell Roundhand"), + ) + Platform.isAndroid -> // https://m3.material.io/styles/typography/fonts + mapOf( + FontFamily.SansSerif.name to listOf("Roboto", "Noto Sans"), + FontFamily.Serif.name to listOf("Roboto Serif", "Noto Serif"), + FontFamily.Monospace.name to listOf("Roboto Mono", "Noto Sans Mono"), + FontFamily.Cursive.name to listOf("Comic Sans MS"), + ) + else -> + mapOf( + FontFamily.SansSerif.name to listOf("Arial"), + FontFamily.Serif.name to listOf("Times New Roman"), + FontFamily.Monospace.name to listOf("Consolas"), + FontFamily.Cursive.name to listOf("Comic Sans MS"), + ) + } + } + + @OptIn(ExperimentalTextApi::class) + @Composable + fun mapGenericFontFamilyToSpecificFontFamily(family: FontFamily): FontFamily { + if (family !is GenericFontFamily) { + return family + } + + val mapped = GenericFontFamiliesMapping[family.name] ?: return family + val resolver = LocalFontFamilyResolver.current + + return mapped.firstNotNullOfOrNull { fontName -> + try { + FontFamily(fontName).also { + resolver.resolve() + } + } catch (e: Throwable) { + CrossPlatformBugsnag.notify(Exception("Unable to load font $fontName.", e)) + null + } + } ?: family + } +} diff --git a/common/src/commonMain/kotlin/tk/zwander/commonCompose/view/components/CustomMaterialTheme.kt b/common/src/commonMain/kotlin/tk/zwander/commonCompose/view/components/CustomMaterialTheme.kt index fb03b7377..8f34661f2 100644 --- a/common/src/commonMain/kotlin/tk/zwander/commonCompose/view/components/CustomMaterialTheme.kt +++ b/common/src/commonMain/kotlin/tk/zwander/commonCompose/view/components/CustomMaterialTheme.kt @@ -1,12 +1,14 @@ package tk.zwander.commonCompose.view.components import androidx.compose.material3.ColorScheme +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SwitchColors import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.graphics.Color +import tk.zwander.commonCompose.util.FontMapper import tk.zwander.commonCompose.util.ThemeInfo import tk.zwander.commonCompose.util.getThemeInfo @@ -14,7 +16,7 @@ import tk.zwander.commonCompose.util.getThemeInfo * A Material theme with custom colors and such. */ @Composable -fun CustomMaterialTheme(block: @Composable()() -> Unit) { +fun CustomMaterialTheme(block: @Composable () -> Unit) { val themeInfo = getThemeInfo() val colorScheme = if (themeInfo.isDarkMode) { @@ -26,7 +28,15 @@ fun CustomMaterialTheme(block: @Composable()() -> Unit) { MaterialTheme( colorScheme = colorScheme, ) { - block() + CompositionLocalProvider( + LocalTextStyle provides LocalTextStyle.current.copy( + fontFamily = LocalTextStyle.current.fontFamily?.let { + FontMapper.mapGenericFontFamilyToSpecificFontFamily(it) + }, + ), + ) { + block() + } } }