blob: 970c573a0c0fbf95408e08ea66ab770678de7592 [file]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.foundation.demos.text
import android.content.Context
import android.graphics.Typeface
import android.os.Build
import android.os.ParcelFileDescriptor
import android.text.TextPaint
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.demos.text.FontVariationSettingsCompot.compatSetFontVariationSettings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Checkbox
import androidx.compose.material.Slider
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.font.DeviceFontFamilyName
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.testutils.fonts.R
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
fun VariableFontsDemo() {
if (Build.VERSION.SDK_INT < 26) {
Text("Variable fonts are only supported on API 26+")
}
val (weight, setWeight) = remember { mutableFloatStateOf(1000f) }
val (italic, setItalic) = remember { mutableStateOf(false) }
LazyColumn {
this.stickyHeader {
Column(Modifier.background(Color.White)) {
Slider(
value = weight,
onValueChange = setWeight,
modifier = Modifier.fillMaxWidth(),
valueRange = 1f..1000f
)
Row {
Text("Italic: ")
Checkbox(checked = italic, onCheckedChange = setItalic)
}
}
}
item {
Text("These demos show setting fontVariationSettings on a demo font that " +
"exaggerates 'wght'. Font only supports the codepoint 'A' code=\"0x41\"")
}
item {
TagLine(tag = "ResourceFont")
ResourceFont(weight.toInt(), italic)
}
item {
TagLine(tag = "AssetFont")
AssetFont(weight.toInt(), italic)
}
item {
TagLine(tag = "FileFont")
FileFont(weight.toInt(), italic)
}
if (Build.VERSION.SDK_INT >= 26) {
// PDF is 26+
item {
TagLine(tag = "ParcelFileDescriptorFont")
ParcelFileDescriptorFont(weight.toInt(), italic)
}
}
item {
TagLine(tag = "DeviceNamedFontFamily")
DeviceNamedFontFamilyFont(weight.toInt(), italic)
}
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
fun AssetFont(weight: Int, italic: Boolean) {
Column(Modifier.fillMaxWidth()) {
val context = LocalContext.current
val assetFonts = remember(weight, italic) {
FontFamily(
Font(
"subdirectory/asset_variable_font.ttf",
context.assets,
variationSettings = FontVariation.Settings(
FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
/* italic not supported by font, ignored */
FontVariation.italic(if (italic) 1f else 0f)
)
)
)
}
Text(
"A",
fontSize = 48.sp,
fontFamily = assetFonts,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
fun FileFont(weight: Int, italic: Boolean) {
val context = LocalContext.current
val filePath = remember { mutableStateOf<String?> (null) }
LaunchedEffect(Unit) {
filePath.value = mkTempFont(context).path
}
val actualPath = filePath.value ?: return
Column(Modifier.fillMaxWidth()) {
val fileFonts = remember(weight, italic) {
FontFamily(
Font(
File(actualPath),
variationSettings = FontVariation.Settings(
FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
/* italic not supported by font, ignored */
FontVariation.italic(if (italic) 1f else 0f)
)
)
)
}
Text(
"A",
fontSize = 48.sp,
fontFamily = fileFonts,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
@RequiresApi(26)
fun ParcelFileDescriptorFont(weight: Int, italic: Boolean) {
val context = LocalContext.current
val filePath = remember { mutableStateOf<String?> (null) }
LaunchedEffect(Unit) {
filePath.value = mkTempFont(context).path
}
val actualPath = filePath.value ?: return
Column(Modifier.fillMaxWidth()) {
val parcelFonts = remember(weight, italic) {
FontFamily(
Font(
File(actualPath).toParcelFileDescriptor(context),
variationSettings = FontVariation.Settings(
FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
/* italic not supported by font, ignored */
FontVariation.italic(if (italic) 1f else 0f)
)
)
)
}
Text(
"A",
fontSize = 48.sp,
fontFamily = parcelFonts,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
fun DeviceNamedFontFamilyFont(weight: Int, italic: Boolean) {
Column(Modifier.fillMaxWidth()) {
val deviceFonts = remember(weight, italic) {
FontFamily(
Font(
DeviceFontFamilyName("sans-serif"),
variationSettings = FontVariation.Settings(
FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
/* italic not supported by font, ignored */
FontVariation.italic(if (italic) 1f else 0f)
)
)
)
}
Text(
"Setting variation on system fonts has no effect on (most) Android builds",
fontSize = 12.sp,
fontFamily = deviceFonts,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
val textPaint = remember { TextPaint() }
Canvas(modifier = Modifier
.fillMaxWidth()
.height(40.dp)) {
this.drawIntoCanvas {
val nativeCanvas = drawContext.canvas.nativeCanvas
textPaint.typeface = Typeface.create("sans-serif", Typeface.NORMAL)
textPaint.textSize = 24f
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
textPaint.compatSetFontVariationSettings(
"'wght' $weight, 'ital' ${if (italic) 1f else 0f}"
)
}
nativeCanvas.drawText(
"Platform 'sans-serif' behavior on this device' (nativeCanvas)" /* text */,
0f /* x */,
40f /* y */,
textPaint)
}
}
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
fun ResourceFont(weight: Int, italic: Boolean) {
Column(Modifier.fillMaxWidth()) {
val resourceFonts = remember(weight, italic) {
FontFamily(
Font(
R.font.variable_font,
variationSettings = FontVariation.Settings(
FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
/* italic not supported by font, ignored */
FontVariation.italic(if (italic) 1f else 0f)
)
)
)
}
Text(
"A",
fontSize = 48.sp,
fontFamily = resourceFonts,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}
private suspend fun mkTempFont(context: Context): File = withContext(Dispatchers.IO) {
val temp = File.createTempFile("tmp", ".ttf", context.filesDir)
context.assets.open("subdirectory/asset_variable_font.ttf").use { input ->
val bytes = input.readBytes()
context.openFileOutput(temp.name, Context.MODE_PRIVATE).use { output ->
output.write(bytes)
}
}
temp
}
private fun File.toParcelFileDescriptor(context: Context): ParcelFileDescriptor {
context.openFileInput(name).use { input ->
return ParcelFileDescriptor.dup(input.fd)
}
}
@RequiresApi(26)
object FontVariationSettingsCompot {
fun TextPaint.compatSetFontVariationSettings(variationSettings: String) {
fontVariationSettings = variationSettings
}
}