Typography.kt

/*
 * Copyright 2023 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.wear.compose.material3

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.text.PlatformTextStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

/**
 * Class holding typography definitions as defined by the Wear Material typography specification.
 *
 * The text styles in this typography are scaled according to the user's preferred font size in
 * the system settings. Larger font sizes can be fixed if necessary in order to avoid pressure on
 * screen space, because they are already sufficiently accessible.
 * Here is an example of fixing the font size for Display1:
 * @sample androidx.wear.compose.material.samples.FixedFontSize
 *
 * TODO(b/273526150) Review documentation for typography, add examples for each size.
 * @property displayExtraLarge DisplayExtraLarge is the largest headline. Displays are the
 * largest text on the screen, reserved for short, important text or numerals.
 *
 * @property displayLarge DisplayLarge is the second largest headline. Displays are the largest text
 * on the screen, reserved for short, important text or numerals.
 *
 * @property displayMedium DisplayMedium is the third largest headline. Displays are the
 * largest text on the screen, reserved for short, important text or numerals.
 *
 * @property displaySmall DisplaySmall is the fourth largest headline. Displays are the largest
 * text on the screen, reserved for short, important text or numerals.
 *
 * @property titleLarge TitleLarge is the largest title. Titles are smaller than Displays. They are
 * typically reserved for medium-emphasis text that is shorter in length.
 *
 * @property titleMedium TitleMedium is the medium title. Titles are smaller than Displays. They are
 * typically reserved for medium-emphasis text that is shorter in length.
 *
 * @property titleSmall TitleSmall is the smallest title. Titles are smaller than Displays. They are
 * typically reserved for medium-emphasis text that is shorter in length.
 *
 * @property bodyLarge BodyLarge is the largest body. Body texts are typically used for long-form
 * writing as it works well for small text sizes. For longer sections of text, a serif or
 * sans serif typeface is recommended.
 *
 * @property bodyMedium BodyMedium is the medium body. Body texts are typically used for long-form
 * writing as it works well for small text sizes. For longer sections of text, a serif or sans serif
 * typeface is recommended.
 *
 * @property bodySmall BodySmall is the smallest body. Body texts are typically used for long-form
 * writing as it works well for small text sizes. For longer sections of text, a serif or sans serif
 * typeface is recommended.
 *
 * @property buttonMedium ButtonMedium text is a call to action used in different types of buttons
 * (such as text, outlined and contained buttons) and in tabs, dialogs, and cards. Button text is
 * typically sans serif, using all caps text.
 *
 * @property captionLarge CaptionLarge is the largest caption. Caption texts are the smallest
 * font sizes. They are used on secondary content.
 *
 * @property captionMedium CaptionMedium is the second largest caption. Caption texts are the
 * smallest font sizes. They are used on secondary content.
 *
 * @property captionSmall CaptionSmall is an exceptional small font size which is used for the extra
 * long-form writing like legal texts.
 */
@Immutable
public class Typography internal constructor(
    public val displayExtraLarge: TextStyle,
    public val displayLarge: TextStyle,
    public val displayMedium: TextStyle,
    public val displaySmall: TextStyle,
    public val titleLarge: TextStyle,
    public val titleMedium: TextStyle,
    public val titleSmall: TextStyle,
    public val bodyLarge: TextStyle,
    public val bodyMedium: TextStyle,
    public val bodySmall: TextStyle,
    public val buttonMedium: TextStyle,
    public val captionLarge: TextStyle,
    public val captionMedium: TextStyle,
    public val captionSmall: TextStyle,
) {
    public constructor (
        defaultFontFamily: FontFamily = FontFamily.Default,
        displayExtraLarge: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 50.sp,
            lineHeight = 56.sp,
            letterSpacing = 0.5.sp
        ),
        displayLarge: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 40.sp,
            lineHeight = 46.sp,
            letterSpacing = 0.5.sp
        ),
        displayMedium: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 34.sp,
            lineHeight = 40.sp,
            letterSpacing = 1.sp
        ),
        displaySmall: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 30.sp,
            lineHeight = 36.sp,
            letterSpacing = 0.8.sp,
        ),
        titleLarge: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 24.sp,
            lineHeight = 28.sp,
            letterSpacing = 0.2.sp
        ),
        titleMedium: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 20.sp,
            lineHeight = 24.sp,
            letterSpacing = 0.2.sp
        ),
        titleSmall: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 16.sp,
            lineHeight = 20.sp,
            letterSpacing = 0.2.sp
        ),
        bodyLarge: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Normal,
            fontSize = 16.sp,
            lineHeight = 20.sp,
            letterSpacing = 0.18.sp
        ),
        bodyMedium: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 16.sp,
            lineHeight = 20.sp,
            letterSpacing = 0.2.sp
        ),
        bodySmall: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Normal,
            fontSize = 14.sp,
            lineHeight = 18.sp,
            letterSpacing = 0.2.sp
        ),
        buttonMedium: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 15.sp,
            lineHeight = 19.sp,
            letterSpacing = 0.2.sp
        ),
        captionLarge: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 14.sp,
            lineHeight = 18.sp,
            letterSpacing = 0.3.sp
        ),
        captionMedium: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 12.sp,
            lineHeight = 16.sp,
            letterSpacing = 0.4.sp
        ),
        captionSmall: TextStyle = DefaultTextStyle.copy(
            fontWeight = FontWeight.Medium,
            fontSize = 10.sp,
            lineHeight = 14.sp,
            letterSpacing = 0.4.sp
        )

    ) : this(
        displayExtraLarge = displayExtraLarge.withDefaultFontFamily(defaultFontFamily),
        displayLarge = displayLarge.withDefaultFontFamily(defaultFontFamily),
        displayMedium = displayMedium.withDefaultFontFamily(defaultFontFamily),
        displaySmall = displaySmall.withDefaultFontFamily(defaultFontFamily),
        titleLarge = titleLarge.withDefaultFontFamily(defaultFontFamily),
        titleMedium = titleMedium.withDefaultFontFamily(defaultFontFamily),
        titleSmall = titleSmall.withDefaultFontFamily(defaultFontFamily),
        bodyLarge = bodyLarge.withDefaultFontFamily(defaultFontFamily),
        bodyMedium = bodyMedium.withDefaultFontFamily(defaultFontFamily),
        bodySmall = bodySmall.withDefaultFontFamily(defaultFontFamily),
        buttonMedium = buttonMedium.withDefaultFontFamily(defaultFontFamily),
        captionLarge = captionLarge.withDefaultFontFamily(defaultFontFamily),
        captionMedium = captionMedium.withDefaultFontFamily(defaultFontFamily),
        captionSmall = captionSmall.withDefaultFontFamily(defaultFontFamily),
    )

    /**
     * Returns a copy of this Typography, optionally overriding some of the values.
     */
    public fun copy(
        displayExtraLarge: TextStyle = this.displayExtraLarge,
        displayLarge: TextStyle = this.displayLarge,
        displayMedium: TextStyle = this.displayMedium,
        displaySmall: TextStyle = this.displaySmall,
        titleLarge: TextStyle = this.titleLarge,
        titleMedium: TextStyle = this.titleMedium,
        titleSmall: TextStyle = this.titleSmall,
        bodyLarge: TextStyle = this.bodyLarge,
        bodyMedium: TextStyle = this.bodyMedium,
        bodySmall: TextStyle = this.bodySmall,
        buttonMedium: TextStyle = this.buttonMedium,
        captionLarge: TextStyle = this.captionLarge,
        captionMedium: TextStyle = this.captionMedium,
        captionSmall: TextStyle = this.captionSmall,
    ): Typography = Typography(
        displayExtraLarge,
        displayLarge,
        displayMedium,
        displaySmall,
        titleLarge,
        titleMedium,
        titleSmall,
        bodyLarge,
        bodyMedium,
        bodySmall,
        buttonMedium,
        captionLarge,
        captionMedium,
        captionSmall,
    )

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Typography) return false

        if (displayExtraLarge != other.displayExtraLarge) return false
        if (displayLarge != other.displayLarge) return false
        if (displayMedium != other.displayMedium) return false
        if (displaySmall != other.displaySmall) return false
        if (titleLarge != other.titleLarge) return false
        if (titleMedium != other.titleMedium) return false
        if (titleSmall != other.titleSmall) return false
        if (bodyLarge != other.bodyLarge) return false
        if (bodyMedium != other.bodyMedium) return false
        if (bodySmall != other.bodySmall) return false
        if (buttonMedium != other.buttonMedium) return false
        if (captionLarge != other.captionLarge) return false
        if (captionMedium != other.captionMedium) return false
        if (captionSmall != other.captionSmall) return false

        return true
    }

    override fun hashCode(): Int {
        var result = displayExtraLarge.hashCode()
        result = 31 * result + displayLarge.hashCode()
        result = 31 * result + displayMedium.hashCode()
        result = 31 * result + displaySmall.hashCode()
        result = 31 * result + titleLarge.hashCode()
        result = 31 * result + titleMedium.hashCode()
        result = 31 * result + titleSmall.hashCode()
        result = 31 * result + bodyLarge.hashCode()
        result = 31 * result + bodyMedium.hashCode()
        result = 31 * result + bodySmall.hashCode()
        result = 31 * result + buttonMedium.hashCode()
        result = 31 * result + captionLarge.hashCode()
        result = 31 * result + captionMedium.hashCode()
        result = 31 * result + captionSmall.hashCode()
        return result
    }

    override fun toString(): String {
        return "Typography(displayExtraLarge=$displayExtraLarge, displayLarge=$displayLarge, " +
            "displayMedium=$displayMedium, displaySmall=$displaySmall, " +
            "titleLarge=$titleLarge, titleMedium=$titleMedium, titleSmall=$titleSmall, " +
            "bodyLarge=$bodyLarge, bodyMedium=$bodyMedium, bodySmall=$bodySmall, " +
            "buttonMedium=$buttonMedium, captionLarge=$captionLarge, " +
            "captionMedium=$captionMedium, captionSmall=$captionSmall)"
    }
}

/**
 * @return [this] if there is a [FontFamily] defined, otherwise copies [this] with [default] as
 * the [FontFamily].
 */
private fun TextStyle.withDefaultFontFamily(default: FontFamily): TextStyle {
    return if (fontFamily != null) this else copy(fontFamily = default)
}

/**
 * Returns theme default [TextStyle] with default [PlatformTextStyle].
 */
internal val DefaultTextStyle = TextStyle.Default.copy(
    platformStyle = defaultPlatformTextStyle()
)

/**
 * This Ambient holds on to the current definition of typography for this application as described
 * by the Wear Material spec. You can read the values in it when creating custom components that
 * want to use Wear Material types, as well as override the values when you want to re-style a part
 * of your hierarchy. Material components related to text such as Button will use this Ambient
 * to set values with which to style children text components.
 *
 * To access values within this ambient, use [MaterialTheme.typography].
 */
internal val LocalTypography = staticCompositionLocalOf { Typography() }