1 /*
<lambda>null2  * Copyright 2022 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.ui.text.font
18 
19 import android.content.Context
20 import android.content.res.AssetManager
21 import android.graphics.Typeface
22 import android.graphics.fonts.FontVariationAxis
23 import android.os.Build
24 import android.os.ParcelFileDescriptor
25 import androidx.annotation.RequiresApi
26 import androidx.compose.ui.text.ExperimentalTextApi
27 import androidx.compose.ui.unit.Density
28 import androidx.compose.ui.util.fastMap
29 import java.io.File
30 
31 internal sealed class AndroidPreloadedFont
32 constructor(
33     final override val weight: FontWeight,
34     final override val style: FontStyle,
35     variationSettings: FontVariation.Settings
36 ) :
37     AndroidFont(
38         FontLoadingStrategy.Blocking,
39         AndroidPreloadedFontTypefaceLoader,
40         variationSettings
41     ) {
42     abstract val cacheKey: String?
43 
44     internal abstract fun doLoad(context: Context?): Typeface?
45 
46     private var didInitWithContext: Boolean = false
47     // subclasses MUST initialize this by calling doLoad(null) - after overriding doLoad as final
48     internal var typeface: Typeface? = null
49 
50     internal fun loadCached(context: Context): Typeface? {
51         if (!didInitWithContext && typeface == null) {
52             typeface = doLoad(context)
53         }
54         didInitWithContext = true
55         return typeface
56     }
57 }
58 
59 private object AndroidPreloadedFontTypefaceLoader : AndroidFont.TypefaceLoader {
loadBlockingnull60     override fun loadBlocking(context: Context, font: AndroidFont): Typeface? =
61         (font as? AndroidPreloadedFont)?.loadCached(context)
62 
63     override suspend fun awaitLoad(context: Context, font: AndroidFont): Nothing {
64         throw UnsupportedOperationException("All preloaded fonts are blocking.")
65     }
66 }
67 
68 @OptIn(ExperimentalTextApi::class) /* FontVariation.Settings */
69 internal class AndroidAssetFont
70 constructor(
71     val assetManager: AssetManager,
72     val path: String,
73     weight: FontWeight = FontWeight.Normal,
74     style: FontStyle = FontStyle.Normal,
75     variationSettings: FontVariation.Settings
76 ) : AndroidPreloadedFont(weight, style, variationSettings) {
77 
doLoadnull78     override fun doLoad(context: Context?): Typeface? {
79         return if (Build.VERSION.SDK_INT >= 26) {
80             TypefaceBuilderCompat.createFromAssets(assetManager, path, context, variationSettings)
81         } else {
82             Typeface.createFromAsset(assetManager, path)
83         }
84     }
85 
86     init {
87         typeface = doLoad(null)
88     }
89 
90     override val cacheKey: String = "asset:$path"
91 
toStringnull92     override fun toString(): String {
93         return "Font(assetManager, path=$path, weight=$weight, style=$style)"
94     }
95 
equalsnull96     override fun equals(other: Any?): Boolean {
97         if (this === other) return true
98         if (other !is AndroidAssetFont) return false
99 
100         if (path != other.path) return false
101         if (variationSettings != other.variationSettings) return false
102 
103         return true
104     }
105 
hashCodenull106     override fun hashCode(): Int {
107         var result = path.hashCode()
108         result = 31 * result + variationSettings.hashCode()
109         return result
110     }
111 }
112 
113 @OptIn(ExperimentalTextApi::class)
114 internal class AndroidFileFont
115 constructor(
116     val file: File,
117     weight: FontWeight = FontWeight.Normal,
118     style: FontStyle = FontStyle.Normal,
119     variationSettings: FontVariation.Settings
120 ) : AndroidPreloadedFont(weight, style, variationSettings) {
121 
doLoadnull122     override fun doLoad(context: Context?): Typeface? {
123         return if (Build.VERSION.SDK_INT >= 26) {
124             TypefaceBuilderCompat.createFromFile(file, context, variationSettings)
125         } else {
126             Typeface.createFromFile(file)
127         }
128     }
129 
130     init {
131         typeface = doLoad(null)
132     }
133 
134     override val cacheKey: String? = null
135 
toStringnull136     override fun toString(): String {
137         return "Font(file=$file, weight=$weight, style=$style)"
138     }
139 }
140 
141 @RequiresApi(26)
142 @OptIn(ExperimentalTextApi::class)
143 internal class AndroidFileDescriptorFont
144 constructor(
145     val fileDescriptor: ParcelFileDescriptor,
146     weight: FontWeight = FontWeight.Normal,
147     style: FontStyle = FontStyle.Normal,
148     variationSettings: FontVariation.Settings
149 ) : AndroidPreloadedFont(weight, style, variationSettings) {
150 
doLoadnull151     override fun doLoad(context: Context?): Typeface? {
152         return if (Build.VERSION.SDK_INT >= 26) {
153             TypefaceBuilderCompat.createFromFileDescriptor(
154                 fileDescriptor,
155                 context,
156                 variationSettings
157             )
158         } else {
159             throw IllegalArgumentException("Cannot create font from file descriptor for SDK < 26")
160         }
161     }
162 
163     init {
164         typeface = doLoad(null)
165     }
166 
167     override val cacheKey: String? = null
168 
toStringnull169     override fun toString(): String {
170         return "Font(fileDescriptor=$fileDescriptor, weight=$weight, style=$style)"
171     }
172 }
173 
174 @RequiresApi(api = 26)
175 private object TypefaceBuilderCompat {
176     @ExperimentalTextApi
createFromAssetsnull177     fun createFromAssets(
178         assetManager: AssetManager,
179         path: String,
180         context: Context?,
181         variationSettings: FontVariation.Settings
182     ): Typeface? {
183         if (context == null) {
184             return null
185         }
186         return Typeface.Builder(assetManager, path)
187             .setFontVariationSettings(variationSettings.toVariationSettings(context))
188             .build()
189     }
190 
191     @ExperimentalTextApi
createFromFilenull192     fun createFromFile(
193         file: File,
194         context: Context?,
195         variationSettings: FontVariation.Settings
196     ): Typeface? {
197         if (context == null) {
198             return null
199         }
200         return Typeface.Builder(file)
201             .setFontVariationSettings(variationSettings.toVariationSettings(context))
202             .build()
203     }
204 
205     @ExperimentalTextApi
createFromFileDescriptornull206     fun createFromFileDescriptor(
207         fileDescriptor: ParcelFileDescriptor,
208         context: Context?,
209         variationSettings: FontVariation.Settings,
210     ): Typeface? {
211         if (context == null) {
212             return null
213         }
214         return Typeface.Builder(fileDescriptor.fileDescriptor)
215             .setFontVariationSettings(variationSettings.toVariationSettings(context))
216             .build()
217     }
218 
219     @RequiresApi(Build.VERSION_CODES.O)
220     @ExperimentalTextApi
FontVariationnull221     private fun FontVariation.Settings.toVariationSettings(
222         context: Context?
223     ): Array<FontVariationAxis> {
224         val density =
225             if (context != null) {
226                 Density(context)
227             } else if (!needsDensity) {
228                 // we don't need density, so make a fake one and be on with it
229                 Density(1f, 1f)
230             } else {
231                 // cannot reach
232                 throw IllegalStateException("Required density, but not provided")
233             }
234         return settings
235             .fastMap { setting ->
236                 FontVariationAxis(setting.axisName, setting.toVariationValue(density))
237             }
238             .toTypedArray()
239     }
240 }
241