From 963adfb629d43afc38d12e644b4282d84864ef36 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Sat, 22 Nov 2025 23:17:21 +0700 Subject: [PATCH] feat: (Tried) to deal with variable font --- GITHUB_CHANGELOG.md | 1 + lawnchair/src/app/lawnchair/font/FontAxes.kt | 13 +++ lawnchair/src/app/lawnchair/font/FontCache.kt | 93 +++++++++++++++++-- 3 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 lawnchair/src/app/lawnchair/font/FontAxes.kt diff --git a/GITHUB_CHANGELOG.md b/GITHUB_CHANGELOG.md index 2990594423..4915c33434 100644 --- a/GITHUB_CHANGELOG.md +++ b/GITHUB_CHANGELOG.md @@ -19,6 +19,7 @@ This is a developer-focused change log: * Material 3 Expressive Settings (Phase 2, TODO) * Better At-a-Glance perceptive wallpaper colour luminance detection * Big word that means Lawnchair will take system default hint bright/dark theme and fallback to luminosity detection for bright/dark mode detection in At a Glance. +* Variable font for Launcher3 (????????) ### 🥞 Snapshot 9 (Development 4 Release 1) diff --git a/lawnchair/src/app/lawnchair/font/FontAxes.kt b/lawnchair/src/app/lawnchair/font/FontAxes.kt new file mode 100644 index 0000000000..c29da5b6c2 --- /dev/null +++ b/lawnchair/src/app/lawnchair/font/FontAxes.kt @@ -0,0 +1,13 @@ +package app.lawnchair.font + +object FontAxes { + const val WEIGHT = "wght" + const val WIDTH = "wdth" + const val OPTICAL_SIZE = "opsz" + const val GRADE = "GRAD" + const val ROUNDNESS = "ROND" + + fun mapToString(axes: Map): String { + return axes.entries.joinToString(", ") { "'${it.key}' ${it.value}" } + } +} diff --git a/lawnchair/src/app/lawnchair/font/FontCache.kt b/lawnchair/src/app/lawnchair/font/FontCache.kt index 1ce1b42a2b..821f14d312 100644 --- a/lawnchair/src/app/lawnchair/font/FontCache.kt +++ b/lawnchair/src/app/lawnchair/font/FontCache.kt @@ -22,6 +22,7 @@ import android.content.res.AssetManager import android.graphics.Typeface import android.net.Uri import androidx.annotation.Keep +import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.font.Font as ComposeFont import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle @@ -75,10 +76,49 @@ class FontCache @Inject constructor( .toList() } - val uiRegular = ResourceFont(context, R.font.googlesansflex_variable, "Google Sans Flex " + context.getString(R.string.font_weight_regular)) - val uiMedium = ResourceFont(context, R.font.googlesansflex_variable, "Google Sans Flex " + context.getString(R.string.font_weight_medium)) - val uiText = ResourceFont(context, R.font.googlesansflex_variable, "Google Sans Flex " + context.getString(R.string.font_weight_regular)) - val uiTextMedium = ResourceFont(context, R.font.googlesansflex_variable, "Google Sans Flex " + context.getString(R.string.font_weight_medium)) + val uiRegular = ResourceFont( + context, + R.font.googlesansflex_variable, + "Google Sans Flex " + context.getString(R.string.font_weight_medium), + mapOf( + FontAxes.WEIGHT to FontWeight.Normal.weight.toFloat(), + FontAxes.ROUNDNESS to 100f, + FontAxes.GRADE to 100f + ) + ) + + val uiMedium = ResourceFont( + context, + R.font.googlesansflex_variable, + "Google Sans Flex " + context.getString(R.string.font_weight_medium), + mapOf( + FontAxes.WEIGHT to FontWeight.Medium.weight.toFloat(), + FontAxes.ROUNDNESS to 100f, + FontAxes.GRADE to 0f + ) + ) + + val uiText = ResourceFont( + context, + R.font.googlesansflex_variable, + "Google Sans Flex " + context.getString(R.string.font_weight_medium), + mapOf( + FontAxes.WEIGHT to FontWeight.Normal.weight.toFloat(), + FontAxes.ROUNDNESS to 100f, + FontAxes.GRADE to 0f + ) + ) + + val uiTextMedium = ResourceFont( + context, + R.font.googlesansflex_variable, + "Google Sans Flex " + context.getString(R.string.font_weight_medium), + mapOf( + FontAxes.WEIGHT to FontWeight.Medium.weight.toFloat(), + FontAxes.ROUNDNESS to 100f, + FontAxes.GRADE to 100f + ) + ) suspend fun getTypeface(font: Font): Typeface? { return loadFontAsync(font).await()?.typeface @@ -354,21 +394,36 @@ class FontCache @Inject constructor( context: Context, private val resId: Int, private val name: String, - ) : TypefaceFont(ResourcesCompat.getFont(context, resId)) { + private val axisSettings: Map = emptyMap() + ) : TypefaceFont(createTypeface(context, resId, axisSettings)) { - private val hashCode = "ResourceFont|$name".hashCode() + private val hashCode = "ResourceFont|$name|$axisSettings".hashCode() override val fullDisplayName = name - override val composeFontFamily = FontFamily(ComposeFont(resId)) + @OptIn(ExperimentalTextApi::class) + override val composeFontFamily = FontFamily( + // Don't let it fool you, removing qualifier name makes everything 10x worse + androidx.compose.ui.text.font.Font( + resId = resId, + variationSettings = androidx.compose.ui.text.font.FontVariation.Settings( + *axisSettings.map { + androidx.compose.ui.text.font.FontVariation.Setting(it.key, it.value) + }.toTypedArray() + ) + ) + ) override fun saveToJson(obj: JSONObject) { super.saveToJson(obj) obj.put(KEY_RESOURCE_ID, resId) obj.put(KEY_FAMILY_NAME, name) + val axesObj = JSONObject() + axisSettings.forEach { (k, v) -> axesObj.put(k, v) } + obj.put("axes", axesObj) } override fun equals(other: Any?): Boolean { - return other is ResourceFont && name == other.name + return other is ResourceFont && name == other.name && axisSettings == other.axisSettings } override fun hashCode(): Int { @@ -376,13 +431,33 @@ class FontCache @Inject constructor( } companion object { + private fun createTypeface(context: Context, resId: Int, axes: Map): Typeface? { + if (axes.isEmpty()) { + return ResourcesCompat.getFont(context, resId) + } + return try { + context.resources.openRawResourceFd(resId).use { pfd -> + Typeface.Builder(pfd.fileDescriptor) + .setFontVariationSettings(FontAxes.mapToString(axes)) + .build() + } + } catch (e: Exception) { + ResourcesCompat.getFont(context, resId) + } + } @Keep @JvmStatic fun fromJson(context: Context, obj: JSONObject): Font { val resId = obj.getInt(KEY_RESOURCE_ID) val name = obj.getString(KEY_FAMILY_NAME) - return ResourceFont(context, resId, name) + val axesMap = mutableMapOf() + val axesObj = obj.optJSONObject("axes") + axesObj?.keys()?.forEach { key -> + axesMap[key] = axesObj.getDouble(key).toFloat() + } + + return ResourceFont(context, resId, name, axesMap) } } }