この記事は、フラー株式会社のカレンダー | Advent Calendar 2023 - Qiita の 9 日目の記事です1。
8 日目は いのりこ (id:inoriko711) さんで 弊社エンジニアリンググループ初、産休取得してみた でした。
はじめに
デザインの観点から、複数の Light や Medium といった色々なウエイトのフォントを使いたいということがあります。しかし、Android の標準では日本語フォントは Regular と Bold の 2 種類しか表示できないです2。
画像のように他のウエイトを設定しても、日本語フォントでは Regular か Bold のどちらかで表示されることになります。
コード
val textStyle = TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = false),
lineHeight = 1.2.em,
)
@Preview
@Composable
fun MulchWeights1() {
Column(modifier = Modifier.padding(8.dp)) {
for (weight in 100..900 step 100) {
Text(
text = "サンプル「『』」 weight=$weight",
style = textStyle,
fontWeight = FontWeight(weight),
)
}
}
}
このような場合は、そのウエイトに対応したフォントを Google Fonts などからダウンロードして対応します。
[完]
これだけでは面白くないので、こだわりを持って複数ウエイトに対応する場合を考えます。
Android に搭載される日本語フォントについて
Android 12 以降に搭載されているフォントには chws (Contextual Half-width Spacing) により約物が連続した時のアキを調整する機能があります3。
Google Fonts の Noto Sans JP では chws の機能のない ttf しかダウンロードできません。安直にこのフォントを設定した場合複数ウエイトに対応することはできても、約物が残念になってしまいます。
コード
@OptIn(ExperimentalTextApi::class)
val notoSansJpGoogleFonts = FontFamily(
(100..900 step 100).map { weight ->
Font(
resId = R.font.noto_sans_jp_google_fonts,
weight = FontWeight(weight),
variationSettings = FontVariation.Settings(FontWeight(weight), FontStyle.Normal)
)
}
)
val textStyle = TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = false),
lineHeight = 1.2.em,
)
@Preview
@Composable
fun MulchWeights2() {
Column(modifier = Modifier.padding(8.dp)) {
for (weight in 100..900 step 100) {
Text(
text = "サンプル「『』」 weight=$weight",
style = textStyle,
fontWeight = FontWeight(weight),
fontFamily = notoSansJpGoogleFonts,
)
}
}
}
chws 機能付きの Noto Sans JP を求めて
chws 機能がついた Noto Sans JP を探したところ simonsmh/notocjk というリポジトリを見つけました。このリポジトリでは Android デバイスに含まれるパッチを加えた全てのウエイトの Noto Sans CJK / Noto Serif CJK を公開しています。
日本語の Sans だけが必要なため、NotoSansCJK-VF.otf.ttc
をダウンロードし、日本語の部分の otf を取得します4。
これにより複数ウエイトかつ約物の間隔が綺麗な表示に対応できます。
コード
@OptIn(ExperimentalTextApi::class)
val notoSansJpWithPatch = FontFamily(
(100..900 step 100).map { weight ->
Font(
resId = R.font.noto_sans_jp_with_patch,
weight = FontWeight(weight),
variationSettings = FontVariation.Settings(FontWeight(weight), FontStyle.Normal)
)
}
)
val textStyle = TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = false),
lineHeight = 1.2.em,
)
@Preview
@Composable
fun MulchWeights3() {
Column(modifier = Modifier.padding(8.dp)) {
for (weight in 100..900 step 100) {
Text(
text = "サンプル「『』」 weight=$weight",
style = textStyle,
fontWeight = FontWeight(weight),
fontFamily = notoSansJpWithPatch,
)
}
}
}
バリアブルフォントを使用しているので、100 より細かい単位でウエイトを変えることもできます。
コード
@OptIn(ExperimentalTextApi::class)
val notoSansJpWithPatch = FontFamily(
(400..500 step 10).map { weight ->
Font(
resId = R.font.noto_sans_jp_with_patch,
weight = FontWeight(weight),
variationSettings = FontVariation.Settings(FontWeight(weight), FontStyle.Normal)
)
}
)
val textStyle = TextStyle(
platformStyle = PlatformTextStyle(includeFontPadding = false),
lineHeight = 1.2.em,
)
@Preview
@Composable
fun MulchWeights4() {
Column(modifier = Modifier.padding(8.dp)) {
for (weight in 400..500 step 10) {
Text(
text = "サンプル「『』」 weight=$weight",
style = textStyle,
fontWeight = FontWeight(weight),
fontFamily = notoSansJpWithPatch,
)
}
}
}
最後に
今回の方法では、標準の Roboto + Noto Sans JP にならず、すべて Noto Sans JP になります。その話はまたいつの日か…