Ask the font only for the axis it has

Reported from a Xiaomi on Android 16: the naskh weight control does nothing.

Both bundled faces carry exactly one variable axis, wght 400-700, and nothing
else. The code asked for two. FontVariation.Settings(weight, style) sends ital
alongside wght, and a font with no italic axis is entitled to refuse the whole
request rather than the part it cannot honour. AOSP ignores the stray axis,
which is why this renders correctly on an emulator and not on the device.

When a platform does refuse it, nothing visibly fails. Each entry in the family
declares the weight it was asked for, so the text matches an entry claiming 700,
draws with 400 glyphs, and is never a candidate for synthetic bolding either —
the one fallback that would have covered it is ruled out by the declaration.
Every weight then looks identical, with no error anywhere.

So: request wght only.

The same declaration broke API 24 and 25 outright, where variable axes do not
exist at all. The comment claimed Android would synthesise the heavier weights
there; it would not, for the reason above. Below 26 the family is now a single
entry at its real weight, so asking for bold synthesises bold.

Measured on an API 30 emulator, same poem, only the preference changed: naskh
+42% ink at Bold, nastaliq +11%, and the two faces differ from each other, so
both files load and both respond. Whether it settles HyperOS is for the device
to say — this removes the cause I can see from here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
anas 2026-10-08 00:29:03 +02:00
parent c801d88ef3
commit f49d9f080f

View File

@ -1,12 +1,12 @@
package com.ganjoor.android.ui.theme
import android.os.Build
import androidx.compose.material3.Typography
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.TextUnit
@ -15,24 +15,40 @@ import com.ganjoor.android.R
import com.ganjoor.android.ui.ReadingFont
/**
* Both files are variable fonts with a `wght` axis, registered at four weights so a reader can
* thicken the text — thin naskh strokes wash out on a lit screen, especially in the dark themes.
* Both files are variable fonts, registered at four weights so a reader can thicken the text —
* thin naskh strokes wash out on a lit screen, especially in the dark themes.
*
* Real axis interpolation needs API 26+; on 24 and 25 the variation settings are ignored and
* Android falls back to synthesising the heavier weights, which still darkens the text.
* Only `wght` is asked for, and deliberately: both files carry that one axis and nothing else.
* `FontVariation.Settings(weight, style)` would also send `ital`, and a font with no italic axis
* is entitled to refuse the whole request rather than the part it cannot honour. A platform that
* does so leaves every weight drawing at 400 — and silently, because each entry here declares the
* weight it was asked for, so the text is never a candidate for synthetic bolding either.
*/
@OptIn(ExperimentalTextApi::class)
private fun variable(resId: Int, weight: FontWeight) = Font(
resId = resId,
weight = weight,
variationSettings = FontVariation.Settings(weight, FontStyle.Normal),
variationSettings = FontVariation.Settings(FontVariation.weight(weight.weight)),
)
private val weights =
listOf(FontWeight.Normal, FontWeight.Medium, FontWeight.SemiBold, FontWeight.Bold)
val Naskh = FontFamily(weights.map { variable(R.font.noto_naskh_arabic, it) })
val Nastaliq = FontFamily(weights.map { variable(R.font.noto_nastaliq_urdu, it) })
/**
* Variable axes need API 26. Below that the settings above are dropped, and declaring four
* weights of the same file would make the heavier ones unreachable: Android would match the
* entry claiming 700, draw it at 400, and skip synthetic bolding because the entry said it was
* already bold. One entry at its real weight instead, so asking for bold actually synthesises it.
*/
private fun scriptFamily(resId: Int): FontFamily =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
FontFamily(weights.map { variable(resId, it) })
} else {
FontFamily(Font(resId, FontWeight.Normal))
}
val Naskh = scriptFamily(R.font.noto_naskh_arabic)
val Nastaliq = scriptFamily(R.font.noto_nastaliq_urdu)
/**
* The English UI only. Libron is a reading serif (OFL, github.com/nicoverbruggen/libron) and