blob: d928ccff62d8feffd3e4b5274d34810b9964c9a0 [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.ui.text.font
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
/**
* The primary typography interface for Compose applications.
*
* @see FontListFontFamily
* @see GenericFontFamily
* @see FontFamily.Resolver
*/
// TODO(b/214587299): Add large ktdoc comment here about how it all works, including fallback and
// optional
@Immutable
sealed class FontFamily(canLoadSynchronously: Boolean) {
/**
* Main interface for resolving [FontFamily] into a platform-specific typeface for use in
* Compose-based applications.
*
* Fonts are loaded via [Resolver.resolve] from a FontFamily and a type request, and return a
* platform-specific typeface.
*
* Fonts may be preloaded by calling [Resolver.preload] to avoid text reflow when async fonts
* load.
*/
sealed interface Resolver {
/**
* Preloading resolves and caches all fonts reachable in a [FontFamily].
*
* It checks the cache first, and if there is a miss, it will fetch from the network.
*
* Fonts are consider reachable if they are the first entry in the fallback chain for any
* call to [resolve].
*
* This method will suspend until:
*
* 1. All [FontLoadingStrategy.Async] fonts that are reachable have completed loading, or
* failed to load
* 2. All reachable fonts in the fallback chain have been loaded and inserted into the
* cache
*
* After returning, all fonts with [FontLoadingStrategy.Async] and
* [FontLoadingStrategy.OptionalLocal] will be permanently cached. In contrast to [resolve]
* this method will throw when a reachable [FontLoadingStrategy.Async] font fails to
* resolve.
*
* All fonts with [FontLoadingStrategy.Blocking] will be cached with normal eviction rules.
*
* @throws IllegalStateException if any reachable font fails to load
* @param fontFamily the family to resolve all fonts from
*/
suspend fun preload(
fontFamily: FontFamily
)
/**
* Resolves a typeface using any appropriate logic for the [FontFamily].
*
* [FontListFontFamily] will always resolve using fallback chains and load using
* [Font.ResourceLoader].
*
* Platform specific [FontFamily] will resolve according to platform behavior, as documented
* for each [FontFamily].
*
* @param fontFamily family to resolve. If `null` will use [FontFamily.Default]
* @param fontWeight desired font weight
* @param fontStyle desired font style
* @param fontSynthesis configuration for font synthesis
* @throws IllegalStateException if the FontFamily cannot resolve a to a typeface
* @return platform-specific Typeface such as [android.graphics.Typeface]
*/
fun resolve(
fontFamily: FontFamily? = null,
fontWeight: FontWeight = FontWeight.Normal,
fontStyle: FontStyle = FontStyle.Normal,
fontSynthesis: FontSynthesis = FontSynthesis.All
): State<Any>
}
companion object {
/**
* The platform default font.
*/
val Default: SystemFontFamily = DefaultFontFamily()
/**
* Font family with low contrast and plain stroke endings.
*
* @sample androidx.compose.ui.text.samples.FontFamilySansSerifSample
*
* See [CSS sans-serif](https://www.w3.org/TR/css-fonts-3/#sans-serif)
*/
val SansSerif = GenericFontFamily("sans-serif", "FontFamily.SansSerif")
/**
* The formal text style for scripts.
*
* @sample androidx.compose.ui.text.samples.FontFamilySerifSample
*
* See [CSS serif](https://www.w3.org/TR/css-fonts-3/#serif)
*/
val Serif = GenericFontFamily("serif", "FontFamily.Serif")
/**
* Font family where glyphs have the same fixed width.
*
* @sample androidx.compose.ui.text.samples.FontFamilyMonospaceSample
*
* See [CSS monospace](https://www.w3.org/TR/css-fonts-3/#monospace)
*/
val Monospace = GenericFontFamily("monospace", "FontFamily.Monospace")
/**
* Cursive, hand-written like font family.
*
* If the device doesn't support this font family, the system will fallback to the
* default font.
*
* @sample androidx.compose.ui.text.samples.FontFamilyCursiveSample
*
* See [CSS cursive](https://www.w3.org/TR/css-fonts-3/#cursive)
*/
val Cursive = GenericFontFamily("cursive", "FontFamily.Cursive")
}
@Suppress("CanBePrimaryConstructorProperty") // for deprecation
@get:Deprecated(
message = "Unused property that has no meaning. Do not use.",
level = DeprecationLevel.ERROR
)
val canLoadSynchronously = canLoadSynchronously
}
/**
* A base class of [FontFamily]s that is created from file sources.
*/
sealed class FileBasedFontFamily : FontFamily(false)
/**
* A base class of [FontFamily]s installed on the system.
*/
sealed class SystemFontFamily : FontFamily(true)
/**
* Defines a font family with list of [Font].
*
* @sample androidx.compose.ui.text.samples.FontFamilySansSerifSample
* @sample androidx.compose.ui.text.samples.CustomFontFamilySample
*/
@Immutable
class FontListFontFamily internal constructor(
fonts: List<Font>
) : FileBasedFontFamily(), List<Font> by fonts {
init {
check(fonts.isNotEmpty()) { "At least one font should be passed to FontFamily" }
}
/**
* The fallback list of fonts used for resolving typefaces for this FontFamily.
*/
val fonts: List<Font> = ArrayList(fonts)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is FontListFontFamily) return false
if (fonts != other.fonts) return false
return true
}
override fun hashCode(): Int {
return fonts.hashCode()
}
override fun toString(): String {
return "FontListFontFamily(fonts=$fonts)"
}
}
/**
* Defines a font family with a generic font family name.
*
* If the platform cannot find the passed generic font family, use the platform default one.
*
* @param name a generic font family name, e.g. "serif", "sans-serif"
* @see FontFamily.SansSerif
* @see FontFamily.Serif
* @see FontFamily.Monospace
* @see FontFamily.Cursive
*/
@Immutable
class GenericFontFamily internal constructor(
val name: String,
private val fontFamilyName: String
) : SystemFontFamily() {
override fun toString(): String = fontFamilyName
}
/**
* Defines a default font family.
*/
@Immutable
internal class DefaultFontFamily internal constructor() : SystemFontFamily() {
override fun toString(): String = "FontFamily.Default"
}
/**
* Defines a font family that is already loaded Typeface.
*
* @param typeface A typeface instance.
*/
class LoadedFontFamily internal constructor(val typeface: Typeface) : FontFamily(true) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is LoadedFontFamily) return false
if (typeface != other.typeface) return false
return true
}
override fun hashCode(): Int {
return typeface.hashCode()
}
override fun toString(): String {
return "LoadedFontFamily(typeface=$typeface)"
}
}
/**
* Construct a font family that contains list of custom font files.
*
* @param fonts list of font files
*/
@Stable
fun FontFamily(fonts: List<Font>): FontFamily = FontListFontFamily(fonts)
/**
* Construct a font family that contains list of custom font files.
*
* @param fonts list of font files
*/
@Stable
fun FontFamily(vararg fonts: Font): FontFamily = FontListFontFamily(fonts.asList())
/**
* Construct a font family that contains loaded font family: Typeface.
*
* @param typeface A typeface instance.
*/
@Stable
fun FontFamily(typeface: Typeface): FontFamily = LoadedFontFamily(typeface)