/*
* 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.protolayout.material;
import static androidx.annotation.Dimension.DP;
import static androidx.annotation.Dimension.SP;
import static androidx.wear.protolayout.DimensionBuilders.sp;
import static androidx.wear.protolayout.LayoutElementBuilders.FONT_VARIANT_BODY;
import static androidx.wear.protolayout.LayoutElementBuilders.FONT_VARIANT_TITLE;
import static androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_BOLD;
import static androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_MEDIUM;
import static androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_NORMAL;
import static androidx.wear.protolayout.material.Helper.checkNotNull;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.DisplayMetrics;
import androidx.annotation.Dimension;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.wear.protolayout.DimensionBuilders;
import androidx.wear.protolayout.DimensionBuilders.SpProp;
import androidx.wear.protolayout.LayoutElementBuilders.FontStyle;
import androidx.wear.protolayout.LayoutElementBuilders.FontVariant;
import androidx.wear.protolayout.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;
@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
})
@interface TypographyName {}
/** Mapping for line height for different typography. */
@NonNull
private static final Map<Integer, Float> TYPOGRAPHY_TO_LINE_HEIGHT_SP = new HashMap<>();
static {
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY1, 46f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY2, 40f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_DISPLAY3, 36f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE1, 28f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE2, 24f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_TITLE3, 20f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BODY1, 20f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BODY2, 18f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_BUTTON, 19f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION1, 18f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION2, 16f);
TYPOGRAPHY_TO_LINE_HEIGHT_SP.put(TYPOGRAPHY_CAPTION3, 14f);
}
/**
* Returns the {@link FontStyle.Builder} for the given FontStyle code with the recommended size,
* weight and letter spacing. Font will be scalable.
*/
@NonNull
static FontStyle.Builder getFontStyleBuilder(
@TypographyName int fontStyleCode, @NonNull Context context) {
return getFontStyleBuilder(fontStyleCode, context, true);
}
private Typography() {}
/**
* Returns the {@link FontStyle.Builder} for the given Typography code with the recommended
* size, weight and letter spacing, with the option to make this font not scalable.
*/
@NonNull
static FontStyle.Builder getFontStyleBuilder(
@TypographyName int typographyCode, @NonNull Context context, boolean isScalable) {
switch (typographyCode) {
case TYPOGRAPHY_BODY1:
return body1(isScalable, context);
case TYPOGRAPHY_BODY2:
return body2(isScalable, context);
case TYPOGRAPHY_BUTTON:
return button(isScalable, context);
case TYPOGRAPHY_CAPTION1:
return caption1(isScalable, context);
case TYPOGRAPHY_CAPTION2:
return caption2(isScalable, context);
case TYPOGRAPHY_CAPTION3:
return caption3(isScalable, context);
case TYPOGRAPHY_DISPLAY1:
return display1(isScalable, context);
case TYPOGRAPHY_DISPLAY2:
return display2(isScalable, context);
case TYPOGRAPHY_DISPLAY3:
return display3(isScalable, context);
case TYPOGRAPHY_TITLE1:
return title1(isScalable, context);
case TYPOGRAPHY_TITLE2:
return title2(isScalable, context);
case TYPOGRAPHY_TITLE3:
return title3(isScalable, context);
default:
// Shouldn't happen.
throw new IllegalArgumentException(
"Typography " + 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 (!TYPOGRAPHY_TO_LINE_HEIGHT_SP.containsKey(typography)) {
throw new IllegalArgumentException("Typography " + typography + " doesn't exist.");
}
return sp(checkNotNull(TYPOGRAPHY_TO_LINE_HEIGHT_SP.get(typography)).intValue());
}
@NonNull
@SuppressLint("ResourceType")
@SuppressWarnings("deprecation")
// This is a helper function to make the font not scalable. It should interpret in value as DP
// and convert it to SP which is needed to be passed in as a font size. However, we will pass an
// SP object to it, because the default style is defined in it, but for the case when the font
// size on device in 1, so the DP is equal to SP.
private static SpProp dpToSp(@NonNull Context context, @Dimension(unit = DP) float valueDp) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
float scaledSp = (valueDp / metrics.scaledDensity) * metrics.density;
return sp(scaledSp);
}
// 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,
@FontVariant int variant,
float letterSpacing,
boolean isScalable,
@NonNull Context context) {
return new FontStyle.Builder()
.setSize(isScalable ? DimensionBuilders.sp(size) : dpToSp(context, size))
.setLetterSpacing(DimensionBuilders.em(letterSpacing))
.setVariant(variant)
.setWeight(weight);
}
/** Font style for large display text. */
@NonNull
private static FontStyle.Builder display1(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
40, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.01f, isScalable, context);
}
/** Font style for medium display text. */
@NonNull
private static FontStyle.Builder display2(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
34, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.03f, isScalable, context);
}
/** Font style for small display text. */
@NonNull
private static FontStyle.Builder display3(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
30, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.03f, isScalable, context);
}
/** Font style for large title text. */
@NonNull
private static FontStyle.Builder title1(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
24, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.008f, isScalable, context);
}
/** Font style for medium title text. */
@NonNull
private static FontStyle.Builder title2(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
20, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.01f, isScalable, context);
}
/** Font style for small title text. */
@NonNull
private static FontStyle.Builder title3(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
16, FONT_WEIGHT_MEDIUM, FONT_VARIANT_TITLE, 0.01f, isScalable, context);
}
/** Font style for normal body text. */
@NonNull
private static FontStyle.Builder body1(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
16, FONT_WEIGHT_NORMAL, FONT_VARIANT_BODY, 0.01f, isScalable, context);
}
/** Font style for small body text. */
@NonNull
private static FontStyle.Builder body2(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
14, FONT_WEIGHT_NORMAL, FONT_VARIANT_BODY, 0.014f, isScalable, context);
}
/** Font style for bold button text. */
@NonNull
private static FontStyle.Builder button(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
15, FONT_WEIGHT_BOLD, FONT_VARIANT_BODY, 0.03f, isScalable, context);
}
/** Font style for large caption text. */
@NonNull
private static FontStyle.Builder caption1(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
14, FONT_WEIGHT_MEDIUM, FONT_VARIANT_BODY, 0.01f, isScalable, context);
}
/** Font style for medium caption text. */
@NonNull
private static FontStyle.Builder caption2(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
12, FONT_WEIGHT_MEDIUM, FONT_VARIANT_BODY, 0.01f, isScalable, context);
}
/** Font style for small caption text. */
@NonNull
private static FontStyle.Builder caption3(boolean isScalable, @NonNull Context context) {
return createFontStyleBuilder(
10, FONT_WEIGHT_MEDIUM, FONT_VARIANT_BODY, 0.01f, isScalable, context);
}
}