Jetpack ComposeでfontSize設定はどうするのが良いのか

Jetpack Composeでフォントサイズを設定するときに、どう書くのがベストなのか分からなかったので調べてみました。
[こちらのStack Overflow](https://stackoverflow.com/questions/67522145/text-composable-dimensionresource-not-working-as-fontsize-parameter)で議論されていましたが、書き方によってスケーリング、つまりOSのフォントサイズを変更した際の挙動に違いがあるみたいです。
ただいまいち結論がわからなかったので自分でも調べてみました。

# 先に（多分）ベストプラクティス
上記StackOverflowでも言われていますが、テーマを設定できる場合はそちらを使った方が良いです。
Material Design3は不勉強なのですが、以下のようにTypographyクラスを使っておくとアプリのデザインの統一感に繋がるのでこれが一番良いと思います。
```Type.kt
val typography = Typography(
   headlineSmall = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 24.sp,
       lineHeight = 32.sp,
       letterSpacing = 0.sp
   ),
    ...
)
```
参考：[Googleの公式チュートリアル](https://developer.android.com/codelabs/jetpack-compose-theming?hl=ja#0)
ただ今回は上記以外の方法を見てみます。

# XMLレイアウトの場合
フォントサイズをリソースファイルdimen.xmlに設定していました。
```dimen.xml
<?xml version="1.0" encoding="utf-8"?> 
<resources>
    <dimen name="text_size">14sp</dimen>
</resources>
```
これを代替していきます。

# 試した方法

## 単純に数値にspをつける
```Text.kt
Text(
    text = "Hello, Jetpack Compose!",
    fontSize = 14.sp
)
```
一番単純な方法です。ただしアプリのフォントサイズが変更となる場合にメンテナンスコストがかかるなどのリスクがあります。
またOSのフォントサイズが最小の場合のスケーリング結果は以下のようになります。
![Screenshot_20240321_023406.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724617/a6927dc0-bc3d-1da9-2b7d-460ffa7ad50f.png)
一方最大の場合は以下。
![Screenshot_20240315_184543.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724617/9f2d163b-f2e2-810e-29a5-f191ef38ced8.png)
いずれもLayout Inspectorで確認したサイズは14spです。

## dimenstionResourceを使う
リソースファイルdimen.xmlに指定した値をdimensionResourceを使って使用します。この方法のメリットは、XMLレイアウトからJetpack Composeへ移行する際にそのままの値が使えることかと思います。
またアプリの統一性も上がり、メンテナンスが楽です。
ただし、以下の書き方ではエラーが出ます。
```Text.kt
Text(
    text = "Hello, Jetpack Compose!",
    fontSize = dimensionResource(id = R.dimen.text_size)
)
```
なぜなら、dimensionResourceはDpを返すためです。dimensionResourceの中身は以下のようになっています。
```PrimitiveResources.android.kt
@Composable
@ReadOnlyComposable
fun dimensionResource(@DimenRes id: Int): Dp {
    val context = LocalContext.current
    val density = LocalDensity.current
    val pxValue = context.resources.getDimension(id)
    return Dp(pxValue / density.density)
}
```
つまりspに変換してあげる必要があります。
以下の書き方で書けばエラーは出ません。
```Text.kt
Text(
    text = "Hello, Jetpack Compose!",
    fontSize = dimensionResource(id = R.dimen.text_size).value.sp
)
```
ただし、これが上記のStack Overflowで議論されている問題の記述です。
確かにこう書けばエラーは出ず、指定された文字サイズになりますが、スケーリング時にややおかしな挙動をします。
フォントサイズ最大時の結果は以下のようになります。サイズは26spになっていました。
確かにスケーリングで大幅にフォントサイズが変わってしまうようです。
![Screenshot_20240315_184231.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724617/499471d7-841b-08e1-d02d-6e1b015a1cfe.png)

## toSp()を使う
dimensionResourceがdpで返ってくるならspに変換すれば良いのでは？ということでちょっと雑ですが以下みたいな感じ。
```ToSp.kt
Text(
    text = "Hello Jetpack Compose!",
    fontSize = with(LocalDensity.current) { dimensionResource(id = R.dimen.text_size).toSp() },
)
```
こちらを使った場合、最大サイズで以下のようになります。フォントサイズはパッと見て違いは分かりませんが14sp→13spになっていました。何で？
![Screenshot_20240321_022429.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724617/ba4f799d-29c2-772f-1211-6b3dee5f0577.png)

## 拡張関数を作る
上記Stack Overflowの中でBetter Solutionとして書かれていた方法です。ちなみにscaledDensityはDeprecatedになっていました。
```ScaledSize.kt
val scaledSize = with(LocalContext.current.resources) {
    (getDimension(R.dimen.text_size) / displayMetrics.scaledDensity).sp
}

Text(
    text = "Hello Jetpack Compose!",
    fontSize = scaledSize,
)
```
こちらを使って適用した場合、最大サイズで以下のようになります。こちらも13spになっていました。書き方違うだけでほとんど処理的には同じなのかもしれない。
![Screenshot_20240321_021143.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2724617/a5bdbb3f-3731-cdc3-0e05-a1913386e210.png)

# まとめ
結論よくわからない感じになりました。
正直変にdimensionResourceを使おうとするよりは、ちゃんとテーマ設定した方が圧倒的に楽で正確な気がしました。