blob: 01f24a8243a4c5c19d692e601c537031fb75b0c0 [file] [log] [blame]
/*
* Copyright 2019 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.ui.text
import androidx.ui.core.Em
import androidx.ui.core.Sp
import androidx.ui.core.em
import androidx.ui.core.sp
import androidx.ui.graphics.Color
import androidx.ui.graphics.Shadow
import androidx.ui.graphics.lerp
import androidx.ui.lerp
import androidx.ui.text.font.FontFamily
import androidx.ui.text.font.FontStyle
import androidx.ui.text.font.FontSynthesis
import androidx.ui.text.font.FontWeight
import androidx.ui.text.font.lerp
import androidx.ui.text.style.BaselineShift
import androidx.ui.text.style.TextDecoration
import androidx.ui.text.style.TextGeometricTransform
import androidx.ui.text.style.lerp
/**
* Styling configuration for a `Text`.
*
* @sample androidx.ui.text.samples.TextStyleSample
*
* @param color The text color.
* @param fontSize The size of glyphs (in logical pixels) to use when painting the text.
* @param fontSizeScale The scale factor of the font size. When [fontSize] is also given in this
* TextStyle, the final fontSize will be the [fontSize] times this value.
* Otherwise, the final fontSize will be the current fontSize times this value.
* @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
* @param fontStyle The typeface variant to use when drawing the letters (e.g., italic).
* @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight or
* style cannot be found in the provided custom font family.
* @param fontFamily The font family to be used when rendering the text.
* @param fontFeatureSettings The advanced typography settings provided by font. The format is the
* same as the CSS font-feature-settings attribute:
* https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
* @param letterSpacing The amount of space (in em) to add between each letter.
* @param baselineShift The amount by which the text is shifted up from the current baseline.
* @param textGeometricTransform The geometric transformation applied the text.
* @param localeList The locale list used to select region-specific glyphs.
* @param background The background color for the text.
* @param decoration The decorations to paint near the text (e.g., an underline).
* @param shadow The shadow effect applied on the text.
*
* @see [AnnotatedString]
*/
data class TextStyle(
val color: Color? = null,
val fontSize: Sp? = null,
val fontSizeScale: Float? = null,
val fontWeight: FontWeight? = null,
val fontStyle: FontStyle? = null,
val fontSynthesis: FontSynthesis? = null,
val fontFamily: FontFamily? = null,
val fontFeatureSettings: String? = null,
val letterSpacing: Em? = null,
val baselineShift: BaselineShift? = null,
val textGeometricTransform: TextGeometricTransform? = null,
val localeList: LocaleList? = null,
val background: Color? = null,
val decoration: TextDecoration? = null,
val shadow: Shadow? = null
) {
/**
* Returns a new text style that is a combination of this style and the given [other] style.
*
* [other] text style's null properties are replaced with the non-null properties of this text
* style. Another way to think of it is that the "missing" properties of the [other] style are
* _filled_ by the properties of this style.
*
* If the given text style is null, returns this text style.
*/
fun merge(other: TextStyle? = null): TextStyle {
if (other == null) return this
return TextStyle(
color = other.color ?: this.color,
fontFamily = other.fontFamily ?: this.fontFamily,
fontSize = other.fontSize ?: this.fontSize,
fontSizeScale = other.fontSizeScale ?: this.fontSizeScale,
fontWeight = other.fontWeight ?: this.fontWeight,
fontStyle = other.fontStyle ?: this.fontStyle,
fontSynthesis = other.fontSynthesis ?: this.fontSynthesis,
fontFeatureSettings = other.fontFeatureSettings ?: this.fontFeatureSettings,
letterSpacing = other.letterSpacing ?: this.letterSpacing,
baselineShift = other.baselineShift ?: this.baselineShift,
textGeometricTransform = other.textGeometricTransform ?: this.textGeometricTransform,
localeList = other.localeList ?: this.localeList,
background = other.background ?: this.background,
decoration = other.decoration ?: this.decoration,
shadow = other.shadow ?: this.shadow
)
}
}
private fun lerpColor(a: Color?, b: Color?, t: Float): Color? {
if (a == null && b == null) {
return null
}
val start = a ?: b!!.copy(alpha = 0f)
val end = b ?: a!!.copy(alpha = 0f)
return lerp(start, end, t)
}
private fun lerpFloat(a: Float?, b: Float?, t: Float, default: Float = 0f): Float? {
if (a == null && b == null) return null
val start = a ?: default
val end = b ?: default
return lerp(start, end, t)
}
private fun lerpSp(a: Sp?, b: Sp?, t: Float, default: Sp = 0f.sp): Sp? {
if (a == null && b == null) return null
val start = a ?: default
val end = b ?: default
return androidx.ui.core.lerp(start, end, t)
}
private fun lerpEm(a: Em?, b: Em?, t: Float, default: Em = 0f.em): Em? {
if (a == null && b == null) return null
val start = a ?: default
val end = b ?: default
return androidx.ui.core.lerp(start, end, t)
}
private fun <T> lerpDiscrete(a: T?, b: T?, t: Float): T? = if (t < 0.5) a else b
/**
* Interpolate between two text styles.
*
* This will not work well if the styles don't set the same fields.
*
* The [fraction] argument represents position on the timeline, with 0.0 meaning
* that the interpolation has not started, returning [start] (or something
* equivalent to [start]), 1.0 meaning that the interpolation has finished,
* returning [stop] (or something equivalent to [stop]), and values in between
* meaning that the interpolation is at the relevant point on the timeline
* between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and
* 1.0, so negative values and values greater than 1.0 are valid.
*/
// TODO(siyamed): This should not accept nullable values
// TODO(siyamed): This should be in the file level, not a companon function
fun lerp(start: TextStyle? = null, stop: TextStyle? = null, fraction: Float): TextStyle? {
val aIsNull = start == null
val bIsNull = stop == null
if (aIsNull && bIsNull) return null
if (start == null) {
val newB = stop?.copy() ?: TextStyle()
return if (fraction < 0.5) {
TextStyle(
color = lerpColor(null, newB.color, fraction),
fontWeight = lerp(null, newB.fontWeight, fraction)
)
} else {
newB.copy(
color = lerpColor(null, newB.color, fraction),
fontWeight = lerp(null, newB.fontWeight, fraction)
)
}
}
if (stop == null) {
return if (fraction < 0.5) {
start.copy(
color = lerpColor(start.color, null, fraction),
fontWeight = lerp(start.fontWeight, null, fraction)
)
} else {
TextStyle(
color = lerpColor(start.color, null, fraction),
fontWeight = lerp(start.fontWeight, null, fraction)
)
}
}
return TextStyle(
color = lerpColor(start.color, stop.color, fraction),
fontFamily = lerpDiscrete(
start.fontFamily,
stop.fontFamily,
fraction
),
fontSize = lerpSp(start.fontSize, stop.fontSize, fraction),
fontSizeScale = lerpFloat(
start.fontSizeScale,
stop.fontSizeScale,
fraction,
1f
),
fontWeight = lerp(start.fontWeight, stop.fontWeight, fraction),
fontStyle = lerpDiscrete(
start.fontStyle,
stop.fontStyle,
fraction
),
fontSynthesis = lerpDiscrete(
start.fontSynthesis,
stop.fontSynthesis,
fraction
),
fontFeatureSettings = lerpDiscrete(
start.fontFeatureSettings,
stop.fontFeatureSettings,
fraction
),
letterSpacing = lerpEm(
start.letterSpacing,
stop.letterSpacing,
fraction
),
baselineShift = lerp(
start.baselineShift,
stop.baselineShift,
fraction
),
textGeometricTransform = lerp(
start.textGeometricTransform ?: TextGeometricTransform.None,
stop.textGeometricTransform ?: TextGeometricTransform.None,
fraction
),
localeList = lerpDiscrete(start.localeList, stop.localeList, fraction),
background = lerpDiscrete(
start.background,
stop.background,
fraction
),
decoration = lerpDiscrete(
start.decoration,
stop.decoration,
fraction
),
shadow = lerp(
start.shadow ?: Shadow(),
stop.shadow ?: Shadow(),
fraction
)
)
}