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