Typography.java

/*
 * Copyright 2022 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.tiles.material;

import static androidx.annotation.Dimension.SP;
import static androidx.wear.tiles.DimensionBuilders.sp;
import static androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_BOLD;
import static androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM;
import static androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_NORMAL;
import static androidx.wear.tiles.material.Helper.checkNotNull;

import android.annotation.SuppressLint;

import androidx.annotation.Dimension;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.wear.tiles.DimensionBuilders;
import androidx.wear.tiles.DimensionBuilders.SpProp;
import androidx.wear.tiles.LayoutElementBuilders.FontStyle;
import androidx.wear.tiles.LayoutElementBuilders.FontWeight;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.Map;

/** Typography styles, currently set up to match Wear's styling. */
public class Typography {
    /** Typography for large display text. */
    public static final int TYPOGRAPHY_DISPLAY1 = 1;

    /** Typography for medium display text. */
    public static final int TYPOGRAPHY_DISPLAY2 = 2;

    /** Typography for small display text. */
    public static final int TYPOGRAPHY_DISPLAY3 = 3;

    /** Typography for large title text. */
    public static final int TYPOGRAPHY_TITLE1 = 4;

    /** Typography for medium title text. */
    public static final int TYPOGRAPHY_TITLE2 = 5;

    /** Typography for small title text. */
    public static final int TYPOGRAPHY_TITLE3 = 6;

    /** Typography for large body text. */
    public static final int TYPOGRAPHY_BODY1 = 7;

    /** Typography for medium body text. */
    public static final int TYPOGRAPHY_BODY2 = 8;

    /** Typography for bold button text. */
    public static final int TYPOGRAPHY_BUTTON = 9;

    /** Typography for large caption text. */
    public static final int TYPOGRAPHY_CAPTION1 = 10;

    /** Typography for medium caption text. */
    public static final int TYPOGRAPHY_CAPTION2 = 11;

    /** Typography for small caption text. */
    public static final int TYPOGRAPHY_CAPTION3 = 12;

    /** @hide */
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            TYPOGRAPHY_DISPLAY1,
            TYPOGRAPHY_DISPLAY2,
            TYPOGRAPHY_DISPLAY3,
            TYPOGRAPHY_TITLE1,
            TYPOGRAPHY_TITLE2,
            TYPOGRAPHY_TITLE3,
            TYPOGRAPHY_BODY1,
            TYPOGRAPHY_BODY2,
            TYPOGRAPHY_BUTTON,
            TYPOGRAPHY_CAPTION1,
            TYPOGRAPHY_CAPTION2,
            TYPOGRAPHY_CAPTION3
    })
    public @interface TypographyName {}

    /** Mapping for line height for different typography. */
    @NonNull
    private static final Map<Integer, Float> FONT_STYLE_TO_LINE_HEIGHT_SP = new HashMap<>();

    static {
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY1, 46f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY2, 40f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY3, 36f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE1, 28f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE2, 24f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE3, 20f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BODY1, 20f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BODY2, 18f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BUTTON, 19f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION1, 18f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION2, 16f);
        FONT_STYLE_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION3, 14f);
    }

    private Typography() {}

    /**
     * Returns the {@link FontStyle.Builder} for the given Typography name with the recommended
     * size, weight and letter spacing.
     */
    @NonNull
    static FontStyle.Builder getFontStyleBuilder(@TypographyName int typographyCode) {
        switch (typographyCode) {
            case TYPOGRAPHY_BODY1:
                return body1();
            case TYPOGRAPHY_BODY2:
                return body2();
            case TYPOGRAPHY_BUTTON:
                return button();
            case TYPOGRAPHY_CAPTION1:
                return caption1();
            case TYPOGRAPHY_CAPTION2:
                return caption2();
            case TYPOGRAPHY_CAPTION3:
                return caption3();
            case TYPOGRAPHY_DISPLAY1:
                return display1();
            case TYPOGRAPHY_DISPLAY2:
                return display2();
            case TYPOGRAPHY_DISPLAY3:
                return display3();
            case TYPOGRAPHY_TITLE1:
                return title1();
            case TYPOGRAPHY_TITLE2:
                return title2();
            case TYPOGRAPHY_TITLE3:
                return title3();
            default:
                // Shouldn't happen.
                throw new IllegalArgumentException(
                        "Typography name " + typographyCode + " doesn't exist.");
        }
    }

    /**
     * Returns the recommended line height for the given Typography to be added to the Text
     * component.
     */
    @NonNull
    static SpProp getLineHeightForTypography(@TypographyName int typography) {
        if (!FONT_STYLE_TO_LINE_HEIGHT_SP.containsKey(typography)) {
            throw new IllegalArgumentException("Typography " + typography + " doesn't exist.");
        }
        return sp(checkNotNull(FONT_STYLE_TO_LINE_HEIGHT_SP.get(typography)).intValue());
    }

    // The @Dimension(unit = SP) on sp() is seemingly being ignored, so lint complains that we're
    // passing SP to something expecting PX. Just suppress the warning for now.
    @SuppressLint("ResourceType")
    private static FontStyle.Builder createFontStyleBuilder(
            @Dimension(unit = SP) int size, @FontWeight int weight, float letterSpacing) {
        return new FontStyle.Builder()
                .setSize(DimensionBuilders.sp(size))
                .setLetterSpacing(DimensionBuilders.em(letterSpacing))
                .setWeight(weight);
    }

    /** Font style for large display text. */
    @NonNull
    private static FontStyle.Builder display1() {
        return createFontStyleBuilder(40, FONT_WEIGHT_MEDIUM, 0.01f);
    }

    /** Font style for medium display text. */
    @NonNull
    private static FontStyle.Builder display2() {
        return createFontStyleBuilder(34, FONT_WEIGHT_MEDIUM, 0.03f);
    }

    /** Font style for small display text. */
    @NonNull
    private static FontStyle.Builder display3() {
        return createFontStyleBuilder(30, FONT_WEIGHT_MEDIUM, 0.03f);
    }

    /** Font style for large title text. */
    @NonNull
    private static FontStyle.Builder title1() {
        return createFontStyleBuilder(24, FONT_WEIGHT_MEDIUM, 0.008f);
    }

    /** Font style for medium title text. */
    @NonNull
    private static FontStyle.Builder title2() {
        return createFontStyleBuilder(20, FONT_WEIGHT_MEDIUM, 0.01f);
    }

    /** Font style for small title text. */
    @NonNull
    private static FontStyle.Builder title3() {
        return createFontStyleBuilder(16, FONT_WEIGHT_MEDIUM, 0.01f);
    }

    /** Font style for normal body text. */
    @NonNull
    private static FontStyle.Builder body1() {
        return createFontStyleBuilder(16, FONT_WEIGHT_NORMAL, 0.01f);
    }

    /** Font style for small body text. */
    @NonNull
    private static FontStyle.Builder body2() {
        return createFontStyleBuilder(14, FONT_WEIGHT_NORMAL, 0.014f);
    }

    /** Font style for bold button text. */
    @NonNull
    private static FontStyle.Builder button() {
        return createFontStyleBuilder(15, FONT_WEIGHT_BOLD, 0.03f);
    }

    /** Font style for large caption text. */
    @NonNull
    private static FontStyle.Builder caption1() {
        return createFontStyleBuilder(14, FONT_WEIGHT_MEDIUM, 0.01f);
    }

    /** Font style for medium caption text. */
    @NonNull
    private static FontStyle.Builder caption2() {
        return createFontStyleBuilder(12, FONT_WEIGHT_MEDIUM, 0.01f);
    }

    /** Font style for small caption text. */
    @NonNull
    private static FontStyle.Builder caption3() {
        return createFontStyleBuilder(10, FONT_WEIGHT_MEDIUM, 0.01f);
    }
}