1 /*
2  * Copyright 2018 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 androidx.compose.runtime.Immutable
20 import androidx.compose.runtime.Stable
21 import androidx.compose.runtime.State
22 import androidx.compose.ui.text.internal.checkPrecondition
23 
24 /**
25  * The primary typography interface for Compose applications.
26  *
27  * @see FontListFontFamily
28  * @see GenericFontFamily
29  * @see FontFamily.Resolver
30  */
31 // TODO(b/214587299): Add large ktdoc comment here about how it all works, including fallback and
32 //  optional
33 @Immutable
34 sealed class FontFamily(canLoadSynchronously: Boolean) {
35 
36     /**
37      * Main interface for resolving [FontFamily] into a platform-specific typeface for use in
38      * Compose-based applications.
39      *
40      * Fonts are loaded via [Resolver.resolve] from a FontFamily and a type request, and return a
41      * platform-specific typeface.
42      *
43      * Fonts may be preloaded by calling [Resolver.preload] to avoid text reflow when async fonts
44      * load.
45      */
46     sealed interface Resolver {
47 
48         /**
49          * Preloading resolves and caches all fonts reachable in a [FontFamily].
50          *
51          * It checks the cache first, and if there is a miss, it will fetch from the network.
52          *
53          * Fonts are consider reachable if they are the first entry in the fallback chain for any
54          * call to [resolve].
55          *
56          * This method will suspend until:
57          * 1. All [FontLoadingStrategy.Async] fonts that are reachable have completed loading, or
58          *    failed to load
59          * 2. All reachable fonts in the fallback chain have been loaded and inserted into the cache
60          *
61          * After returning, all fonts with [FontLoadingStrategy.Async] and
62          * [FontLoadingStrategy.OptionalLocal] will be permanently cached. In contrast to [resolve]
63          * this method will throw when a reachable [FontLoadingStrategy.Async] font fails to
64          * resolve.
65          *
66          * All fonts with [FontLoadingStrategy.Blocking] will be cached with normal eviction rules.
67          *
68          * @param fontFamily the family to resolve all fonts from
69          * @throws IllegalStateException if any reachable font fails to load
70          */
preloadnull71         suspend fun preload(fontFamily: FontFamily)
72 
73         /**
74          * Resolves a typeface using any appropriate logic for the [FontFamily].
75          *
76          * [FontListFontFamily] will always resolve using fallback chains and load using
77          * [Font.ResourceLoader].
78          *
79          * Platform specific [FontFamily] will resolve according to platform behavior, as documented
80          * for each [FontFamily].
81          *
82          * @param fontFamily family to resolve. If `null` will use [FontFamily.Default]
83          * @param fontWeight desired font weight
84          * @param fontStyle desired font style
85          * @param fontSynthesis configuration for font synthesis
86          * @return platform-specific Typeface such as [android.graphics.Typeface]
87          * @throws IllegalStateException if the FontFamily cannot resolve a to a typeface
88          */
89         fun resolve(
90             fontFamily: FontFamily? = null,
91             fontWeight: FontWeight = FontWeight.Normal,
92             fontStyle: FontStyle = FontStyle.Normal,
93             fontSynthesis: FontSynthesis = FontSynthesis.All
94         ): State<Any>
95     }
96 
97     companion object {
98         /** The platform default font. */
99         val Default: SystemFontFamily = DefaultFontFamily()
100 
101         /**
102          * Font family with low contrast and plain stroke endings.
103          *
104          * @sample androidx.compose.ui.text.samples.FontFamilySansSerifSample
105          *
106          * See [CSS sans-serif](https://www.w3.org/TR/css-fonts-3/#sans-serif)
107          */
108         val SansSerif = GenericFontFamily("sans-serif", "FontFamily.SansSerif")
109 
110         /**
111          * The formal text style for scripts.
112          *
113          * @sample androidx.compose.ui.text.samples.FontFamilySerifSample
114          *
115          * See [CSS serif](https://www.w3.org/TR/css-fonts-3/#serif)
116          */
117         val Serif = GenericFontFamily("serif", "FontFamily.Serif")
118 
119         /**
120          * Font family where glyphs have the same fixed width.
121          *
122          * @sample androidx.compose.ui.text.samples.FontFamilyMonospaceSample
123          *
124          * See [CSS monospace](https://www.w3.org/TR/css-fonts-3/#monospace)
125          */
126         val Monospace = GenericFontFamily("monospace", "FontFamily.Monospace")
127 
128         /**
129          * Cursive, hand-written like font family.
130          *
131          * If the device doesn't support this font family, the system will fallback to the default
132          * font.
133          *
134          * @sample androidx.compose.ui.text.samples.FontFamilyCursiveSample
135          *
136          * See [CSS cursive](https://www.w3.org/TR/css-fonts-3/#cursive)
137          */
138         val Cursive = GenericFontFamily("cursive", "FontFamily.Cursive")
139     }
140 
141     @Suppress("CanBePrimaryConstructorProperty") // for deprecation
142     @get:Deprecated(
143         message = "Unused property that has no meaning. Do not use.",
144         level = DeprecationLevel.ERROR
145     )
146     val canLoadSynchronously = canLoadSynchronously
147 }
148 
149 /** A base class of [FontFamily]s that is created from file sources. */
150 sealed class FileBasedFontFamily : FontFamily(false)
151 
152 /** A base class of [FontFamily]s installed on the system. */
153 sealed class SystemFontFamily : FontFamily(true)
154 
155 /**
156  * Defines a font family with list of [Font].
157  *
158  * @sample androidx.compose.ui.text.samples.FontFamilySansSerifSample
159  * @sample androidx.compose.ui.text.samples.CustomFontFamilySample
160  */
161 @Immutable
162 class FontListFontFamily
163 internal constructor(
164     /** The fallback list of fonts used for resolving typefaces for this FontFamily. */
165     val fonts: List<Font>
<lambda>null166 ) : FileBasedFontFamily(), List<Font> by fonts {
167     init {
168         checkPrecondition(fonts.isNotEmpty()) { "At least one font should be passed to FontFamily" }
169     }
170 
171     override fun equals(other: Any?): Boolean {
172         if (this === other) return true
173         if (other !is FontListFontFamily) return false
174         if (fonts != other.fonts) return false
175         return true
176     }
177 
178     override fun hashCode(): Int {
179         return fonts.hashCode()
180     }
181 
182     override fun toString(): String {
183         return "FontListFontFamily(fonts=$fonts)"
184     }
185 }
186 
187 /**
188  * Defines a font family with a generic font family name.
189  *
190  * If the platform cannot find the passed generic font family, use the platform default one.
191  *
192  * @param name a generic font family name, e.g. "serif", "sans-serif"
193  * @see FontFamily.SansSerif
194  * @see FontFamily.Serif
195  * @see FontFamily.Monospace
196  * @see FontFamily.Cursive
197  */
198 @Immutable
199 class GenericFontFamily internal constructor(val name: String, private val fontFamilyName: String) :
200     SystemFontFamily() {
toStringnull201     override fun toString(): String = fontFamilyName
202 }
203 
204 /** Defines a default font family. */
205 @Immutable
206 internal class DefaultFontFamily internal constructor() : SystemFontFamily() {
207     override fun toString(): String = "FontFamily.Default"
208 }
209 
210 /**
211  * Defines a font family that is already loaded Typeface.
212  *
213  * @param typeface A typeface instance.
214  */
215 class LoadedFontFamily internal constructor(val typeface: Typeface) : FontFamily(true) {
equalsnull216     override fun equals(other: Any?): Boolean {
217         if (this === other) return true
218         if (other !is LoadedFontFamily) return false
219         if (typeface != other.typeface) return false
220         return true
221     }
222 
hashCodenull223     override fun hashCode(): Int {
224         return typeface.hashCode()
225     }
226 
toStringnull227     override fun toString(): String {
228         return "LoadedFontFamily(typeface=$typeface)"
229     }
230 }
231 
232 /**
233  * Construct a font family that contains list of custom font files.
234  *
235  * @param fonts list of font files
236  */
FontFamilynull237 @Stable fun FontFamily(fonts: List<Font>): FontFamily = FontListFontFamily(fonts)
238 
239 /**
240  * Construct a font family that contains list of custom font files.
241  *
242  * @param fonts list of font files
243  */
244 @Stable fun FontFamily(vararg fonts: Font): FontFamily = FontListFontFamily(fonts.asList())
245 
246 /**
247  * Construct a font family that contains loaded font family: Typeface.
248  *
249  * @param typeface A typeface instance.
250  */
251 @Stable fun FontFamily(typeface: Typeface): FontFamily = LoadedFontFamily(typeface)
252