1 /*
<lambda>null2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.foundation.demos.text
18
19 import android.content.Context
20 import android.graphics.Typeface
21 import android.os.Build
22 import android.os.ParcelFileDescriptor
23 import android.text.TextPaint
24 import androidx.annotation.RequiresApi
25 import androidx.compose.foundation.Canvas
26 import androidx.compose.foundation.ExperimentalFoundationApi
27 import androidx.compose.foundation.background
28 import androidx.compose.foundation.demos.text.FontVariationSettingsCompot.compatSetFontVariationSettings
29 import androidx.compose.foundation.layout.Column
30 import androidx.compose.foundation.layout.Row
31 import androidx.compose.foundation.layout.fillMaxWidth
32 import androidx.compose.foundation.layout.height
33 import androidx.compose.foundation.lazy.LazyColumn
34 import androidx.compose.material.Checkbox
35 import androidx.compose.material.Slider
36 import androidx.compose.material.Text
37 import androidx.compose.runtime.Composable
38 import androidx.compose.runtime.LaunchedEffect
39 import androidx.compose.runtime.mutableFloatStateOf
40 import androidx.compose.runtime.mutableStateOf
41 import androidx.compose.runtime.remember
42 import androidx.compose.ui.Alignment
43 import androidx.compose.ui.Modifier
44 import androidx.compose.ui.graphics.Color
45 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
46 import androidx.compose.ui.graphics.nativeCanvas
47 import androidx.compose.ui.platform.LocalContext
48 import androidx.compose.ui.text.ExperimentalTextApi
49 import androidx.compose.ui.text.font.DeviceFontFamilyName
50 import androidx.compose.ui.text.font.Font
51 import androidx.compose.ui.text.font.FontFamily
52 import androidx.compose.ui.text.font.FontVariation
53 import androidx.compose.ui.tooling.preview.Preview
54 import androidx.compose.ui.unit.dp
55 import androidx.compose.ui.unit.sp
56 import androidx.testutils.fonts.R
57 import java.io.File
58 import kotlinx.coroutines.Dispatchers
59 import kotlinx.coroutines.withContext
60
61 @OptIn(ExperimentalFoundationApi::class)
62 @Preview
63 @Composable
64 fun VariableFontsDemo() {
65 if (Build.VERSION.SDK_INT < 26) {
66 Text("Variable fonts are only supported on API 26+")
67 }
68
69 val (weight, setWeight) = remember { mutableFloatStateOf(1000f) }
70 val (italic, setItalic) = remember { mutableStateOf(false) }
71 LazyColumn {
72 this.stickyHeader {
73 Column(Modifier.background(Color.White)) {
74 Slider(
75 value = weight,
76 onValueChange = setWeight,
77 modifier = Modifier.fillMaxWidth(),
78 valueRange = 1f..1000f
79 )
80 Row {
81 Text("Italic: ")
82 Checkbox(checked = italic, onCheckedChange = setItalic)
83 }
84 }
85 }
86 item {
87 Text(
88 "These demos show setting fontVariationSettings on a demo font that " +
89 "exaggerates 'wght'. Font only supports the codepoint 'A' code=\"0x41\""
90 )
91 }
92 item {
93 TagLine(tag = "ResourceFont")
94 ResourceFont(weight.toInt(), italic)
95 }
96 item {
97 TagLine(tag = "AssetFont")
98 AssetFont(weight.toInt(), italic)
99 }
100 item {
101 TagLine(tag = "FileFont")
102 FileFont(weight.toInt(), italic)
103 }
104 if (Build.VERSION.SDK_INT >= 26) {
105 // PDF is 26+
106 item {
107 TagLine(tag = "ParcelFileDescriptorFont")
108 ParcelFileDescriptorFont(weight.toInt(), italic)
109 }
110 }
111 item {
112 TagLine(tag = "DeviceNamedFontFamily")
113 DeviceNamedFontFamilyFont(weight.toInt(), italic)
114 }
115 }
116 }
117
118 @OptIn(ExperimentalTextApi::class)
119 @Composable
AssetFontnull120 fun AssetFont(weight: Int, italic: Boolean) {
121 Column(Modifier.fillMaxWidth()) {
122 val context = LocalContext.current
123 val assetFonts =
124 remember(weight, italic) {
125 FontFamily(
126 Font(
127 "subdirectory/asset_variable_font.ttf",
128 context.assets,
129 variationSettings =
130 FontVariation.Settings(
131 FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
132 /* italic not supported by font, ignored */
133 FontVariation.italic(if (italic) 1f else 0f)
134 )
135 )
136 )
137 }
138 Text(
139 "A",
140 fontSize = 48.sp,
141 fontFamily = assetFonts,
142 modifier = Modifier.align(Alignment.CenterHorizontally)
143 )
144 }
145 }
146
147 @OptIn(ExperimentalTextApi::class)
148 @Composable
FileFontnull149 fun FileFont(weight: Int, italic: Boolean) {
150 val context = LocalContext.current
151 val filePath = remember { mutableStateOf<String?>(null) }
152 LaunchedEffect(Unit) { filePath.value = mkTempFont(context).path }
153 val actualPath = filePath.value ?: return
154
155 Column(Modifier.fillMaxWidth()) {
156 val fileFonts =
157 remember(weight, italic) {
158 FontFamily(
159 Font(
160 File(actualPath),
161 variationSettings =
162 FontVariation.Settings(
163 FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
164 /* italic not supported by font, ignored */
165 FontVariation.italic(if (italic) 1f else 0f)
166 )
167 )
168 )
169 }
170 Text(
171 "A",
172 fontSize = 48.sp,
173 fontFamily = fileFonts,
174 modifier = Modifier.align(Alignment.CenterHorizontally)
175 )
176 }
177 }
178
179 @OptIn(ExperimentalTextApi::class)
180 @Composable
181 @RequiresApi(26)
ParcelFileDescriptorFontnull182 fun ParcelFileDescriptorFont(weight: Int, italic: Boolean) {
183 val context = LocalContext.current
184 val filePath = remember { mutableStateOf<String?>(null) }
185 LaunchedEffect(Unit) { filePath.value = mkTempFont(context).path }
186 val actualPath = filePath.value ?: return
187
188 Column(Modifier.fillMaxWidth()) {
189 val parcelFonts =
190 remember(weight, italic) {
191 FontFamily(
192 Font(
193 File(actualPath).toParcelFileDescriptor(context),
194 variationSettings =
195 FontVariation.Settings(
196 FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
197 /* italic not supported by font, ignored */
198 FontVariation.italic(if (italic) 1f else 0f)
199 )
200 )
201 )
202 }
203 Text(
204 "A",
205 fontSize = 48.sp,
206 fontFamily = parcelFonts,
207 modifier = Modifier.align(Alignment.CenterHorizontally)
208 )
209 }
210 }
211
212 @OptIn(ExperimentalTextApi::class)
213 @Composable
DeviceNamedFontFamilyFontnull214 fun DeviceNamedFontFamilyFont(weight: Int, italic: Boolean) {
215 Column(Modifier.fillMaxWidth()) {
216 val deviceFonts =
217 remember(weight, italic) {
218 FontFamily(
219 Font(
220 DeviceFontFamilyName("sans-serif"),
221 variationSettings =
222 FontVariation.Settings(
223 FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
224 /* italic not supported by font, ignored */
225 FontVariation.italic(if (italic) 1f else 0f)
226 )
227 )
228 )
229 }
230 Text(
231 "Setting variation on system fonts has no effect on (most) Android builds",
232 fontSize = 12.sp,
233 fontFamily = deviceFonts,
234 modifier = Modifier.align(Alignment.CenterHorizontally)
235 )
236 val textPaint = remember { TextPaint() }
237 Canvas(modifier = Modifier.fillMaxWidth().height(40.dp)) {
238 this.drawIntoCanvas {
239 val nativeCanvas = drawContext.canvas.nativeCanvas
240 textPaint.typeface = Typeface.create("sans-serif", Typeface.NORMAL)
241 textPaint.textSize = 24f
242 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
243 textPaint.compatSetFontVariationSettings(
244 "'wght' $weight, 'ital' ${if (italic) 1f else 0f}"
245 )
246 }
247 nativeCanvas.drawText(
248 "Platform 'sans-serif' behavior on this device' (nativeCanvas)" /* text */,
249 0f /* x */,
250 40f /* y */,
251 textPaint
252 )
253 }
254 }
255 }
256 }
257
258 @OptIn(ExperimentalTextApi::class)
259 @Composable
ResourceFontnull260 fun ResourceFont(weight: Int, italic: Boolean) {
261 Column(Modifier.fillMaxWidth()) {
262 val resourceFonts =
263 remember(weight, italic) {
264 FontFamily(
265 Font(
266 R.font.variable_font,
267 variationSettings =
268 FontVariation.Settings(
269 FontVariation.weight(weight.toInt()), /* Changes "A" glyph */
270 /* italic not supported by font, ignored */
271 FontVariation.italic(if (italic) 1f else 0f)
272 )
273 )
274 )
275 }
276 Text(
277 "A",
278 fontSize = 48.sp,
279 fontFamily = resourceFonts,
280 modifier = Modifier.align(Alignment.CenterHorizontally)
281 )
282 }
283 }
284
mkTempFontnull285 private suspend fun mkTempFont(context: Context): File =
286 withContext(Dispatchers.IO) {
287 val temp = File.createTempFile("tmp", ".ttf", context.filesDir)
288 context.assets.open("subdirectory/asset_variable_font.ttf").use { input ->
289 val bytes = input.readBytes()
290 context.openFileOutput(temp.name, Context.MODE_PRIVATE).use { output ->
291 output.write(bytes)
292 }
293 }
294 temp
295 }
296
toParcelFileDescriptornull297 private fun File.toParcelFileDescriptor(context: Context): ParcelFileDescriptor {
298 context.openFileInput(name).use { input ->
299 return ParcelFileDescriptor.dup(input.fd)
300 }
301 }
302
303 @RequiresApi(26)
304 object FontVariationSettingsCompot {
compatSetFontVariationSettingsnull305 fun TextPaint.compatSetFontVariationSettings(variationSettings: String) {
306 fontVariationSettings = variationSettings
307 }
308 }
309