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