Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ const ReactNativeStyleAttributes: {[string]: AnyAttributeType, ...} = {
fontSize: true,
fontStyle: true,
fontVariant: fontVariantAttribute,
fontVariationSettings: true,
fontWeight: true,
includeFontPadding: true,
letterSpacing: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,12 @@ export interface TextStyle extends TextStyleIOS, TextStyleAndroid, ViewStyle {
| 'heavy'
| 'black'
| undefined;
/**
* Specifies OpenType font variation axis values using CSS syntax. An empty
* string resets inherited variation settings. On Android, this requires API
* level 26 or later.
*/
fontVariationSettings?: string | undefined;
letterSpacing?: number | undefined;
lineHeight?: number | undefined;
textAlign?:
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native/Libraries/StyleSheet/StyleSheetTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,12 @@ type ____TextStyle_InternalBase = Readonly<{
*/
fontWeight?: ____FontWeight_Internal,
fontVariant?: ____FontVariantArray_Internal | string,
/**
* Specifies OpenType font variation axis values using CSS syntax. An empty
* string resets inherited variation settings. On Android, this requires API
* level 26 or later.
*/
fontVariationSettings?: string,
textShadowOffset?: Readonly<{
width: number,
height: number,
Expand Down
3 changes: 3 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -4543,6 +4543,7 @@ public final class com/facebook/react/uimanager/ViewProps {
public static final field FONT_SIZE Ljava/lang/String;
public static final field FONT_STYLE Ljava/lang/String;
public static final field FONT_VARIANT Ljava/lang/String;
public static final field FONT_VARIATION_SETTINGS Ljava/lang/String;
public static final field FONT_WEIGHT Ljava/lang/String;
public static final field FOREGROUND_COLOR Ljava/lang/String;
public static final field GAP Ljava/lang/String;
Expand Down Expand Up @@ -6140,6 +6141,7 @@ public final class com/facebook/react/views/text/TextAttributeProps {
public static final field TA_KEY_FONT_SIZE_MULTIPLIER I
public static final field TA_KEY_FONT_STYLE I
public static final field TA_KEY_FONT_VARIANT I
public static final field TA_KEY_FONT_VARIATION_SETTINGS I
public static final field TA_KEY_FONT_WEIGHT I
public static final field TA_KEY_FOREGROUND_COLOR I
public static final field TA_KEY_IS_HIGHLIGHTED I
Expand Down Expand Up @@ -6170,6 +6172,7 @@ public final class com/facebook/react/views/text/TextAttributeProps {
public final fun getFontFeatureSettings ()Ljava/lang/String;
public final fun getFontSize ()I
public final fun getFontStyle ()I
public final fun getFontVariationSettings ()Ljava/lang/String;
public final fun getFontWeight ()I
public final fun getLayoutDirection ()I
public final fun getLetterSpacing ()F
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public object ViewProps {
public const val FONT_WEIGHT: String = "fontWeight"
public const val FONT_STYLE: String = "fontStyle"
public const val FONT_VARIANT: String = "fontVariant"
public const val FONT_VARIATION_SETTINGS: String = "fontVariationSettings"
public const val FONT_FAMILY: String = "fontFamily"
public const val LINE_HEIGHT: String = "lineHeight"
public const val LETTER_SPACING: String = "letterSpacing"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ package com.facebook.react.views.text
import android.content.Context
import android.content.res.AssetManager
import android.content.res.Configuration
import android.graphics.Paint
import android.graphics.Typeface
import android.graphics.fonts.FontVariationAxis
import android.os.Build
import com.facebook.common.logging.FLog
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.common.ReactConstants
import com.facebook.react.common.assets.ReactFontManager
Expand Down Expand Up @@ -151,4 +154,44 @@ public object ReactTypefaceUtils {

return Typeface.create(baseTypeface, adjustedWeight, italic)
}

internal fun parseFontVariationSettings(fontVariationSettings: String?): String? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return null
}

if (fontVariationSettings.isNullOrEmpty()) {
return fontVariationSettings
}

if (fontVariationSettings.trim().equals("normal", ignoreCase = true)) {
return ""
}

return try {
FontVariationAxis.fromFontVariationSettings(fontVariationSettings)
fontVariationSettings
} catch (exception: IllegalArgumentException) {
FLog.w(ReactConstants.TAG, "Invalid fontVariationSettings: $fontVariationSettings")
null
}
}

internal fun applyFontVariationSettings(paint: Paint, fontVariationSettings: String?) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return
}

try {
paint.setFontVariationSettings(fontVariationSettings)
} catch (exception: IllegalArgumentException) {
// Paint instances are reused, so explicitly clear axes from a previous layout.
paint.setFontVariationSettings(null)
FLog.w(
ReactConstants.TAG,
"Invalid fontVariationSettings: $fontVariationSettings",
exception,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.ViewProps
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontVariant
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontVariationSettings
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
import kotlin.math.ceil

Expand Down Expand Up @@ -153,6 +154,10 @@ public class TextAttributeProps private constructor() {
public var fontFeatureSettings: String? = null
private set

/** @see android.graphics.Paint.setFontVariationSettings */
public var fontVariationSettings: String? = null
private set

@Deprecated("Use lineHeight instead", ReplaceWith("lineHeight"))
public val effectiveLineHeight: Float
get() = lineHeight
Expand Down Expand Up @@ -396,6 +401,7 @@ public class TextAttributeProps private constructor() {
public const val TA_KEY_TEXT_TRANSFORM: Int = 27
public const val TA_KEY_MAX_FONT_SIZE_MULTIPLIER: Int = 29
public const val TA_KEY_TEXT_EFFECTS: Int = 30
public const val TA_KEY_FONT_VARIATION_SETTINGS: Int = 31
private const val TE_KEY_NAME: Int = 0
private const val TE_KEY_PROPS: Int = 1

Expand Down Expand Up @@ -433,6 +439,8 @@ public class TextAttributeProps private constructor() {
TA_KEY_FONT_WEIGHT -> result.setFontWeight(entry.stringValue)
TA_KEY_FONT_STYLE -> result.setFontStyle(entry.stringValue)
TA_KEY_FONT_VARIANT -> result.setFontVariant(entry.mapBufferValue)
TA_KEY_FONT_VARIATION_SETTINGS ->
result.fontVariationSettings = parseFontVariationSettings(entry.stringValue)
TA_KEY_ALLOW_FONT_SCALING -> result.allowFontScaling = entry.booleanValue
TA_KEY_LETTER_SPACING -> result.letterSpacing = entry.doubleValue.toFloat()
TA_KEY_LINE_HEIGHT -> result.lineHeight = entry.doubleValue.toFloat()
Expand Down Expand Up @@ -500,6 +508,8 @@ public class TextAttributeProps private constructor() {
result.setFontWeight(getStringProp(props, ViewProps.FONT_WEIGHT))
result.setFontStyle(getStringProp(props, ViewProps.FONT_STYLE))
result.setFontVariant(getArrayProp(props, ViewProps.FONT_VARIANT))
result.fontVariationSettings =
parseFontVariationSettings(getStringProp(props, ViewProps.FONT_VARIATION_SETTINGS))
result.includeFontPadding = getBooleanProp(props, ViewProps.INCLUDE_FONT_PADDING, true)
result.setTextDecorationLine(getStringProp(props, ViewProps.TEXT_DECORATION_LINE))
result.textDecorationColor =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ internal object TextLayoutManager {
if (
textAttributes.fontStyle != ReactConstants.UNSET ||
textAttributes.fontWeight != ReactConstants.UNSET ||
textAttributes.fontVariationSettings != null ||
textAttributes.fontFamily != null
) {
ops.add(
Expand All @@ -323,6 +324,7 @@ internal object TextLayoutManager {
textAttributes.fontStyle,
textAttributes.fontWeight,
textAttributes.fontFeatureSettings,
textAttributes.fontVariationSettings,
textAttributes.fontFamily,
assets,
fontWeightAdjustment,
Expand Down Expand Up @@ -547,13 +549,15 @@ internal object TextLayoutManager {
if (
fragment.props.fontStyle != ReactConstants.UNSET ||
fragment.props.fontWeight != ReactConstants.UNSET ||
fragment.props.fontVariationSettings != null ||
fragment.props.fontFamily != null
) {
spannable.setSpan(
CustomStyleSpan(
fragment.props.fontStyle,
fragment.props.fontWeight,
fragment.props.fontFeatureSettings,
fragment.props.fontVariationSettings,
fragment.props.fontFamily,
assets,
fontWeightAdjustment,
Expand Down Expand Up @@ -883,6 +887,7 @@ internal object TextLayoutManager {
if (
baseTextAttributes.fontStyle != ReactConstants.UNSET ||
baseTextAttributes.fontWeight != ReactConstants.UNSET ||
baseTextAttributes.fontVariationSettings != null ||
baseTextAttributes.fontFamily != null
) {
val typeface =
Expand Down Expand Up @@ -912,6 +917,8 @@ internal object TextLayoutManager {
paint.setTypeface(typeface)
}
}

ReactTypefaceUtils.applyFontVariationSettings(paint, baseTextAttributes.fontVariationSettings)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal class CustomStyleSpan(
private val privateStyle: Int,
private val privateWeight: Int,
val fontFeatureSettings: String?,
val fontVariationSettings: String?,
val fontFamily: String?,
private val assetManager: AssetManager,
private val fontWeightAdjustment: Int = 0,
Expand All @@ -41,6 +42,7 @@ internal class CustomStyleSpan(
privateStyle,
privateWeight,
fontFeatureSettings,
fontVariationSettings,
fontFamily,
assetManager,
fontWeightAdjustment,
Expand All @@ -53,6 +55,7 @@ internal class CustomStyleSpan(
privateStyle,
privateWeight,
fontFeatureSettings,
fontVariationSettings,
fontFamily,
assetManager,
fontWeightAdjustment,
Expand Down Expand Up @@ -81,6 +84,7 @@ internal class CustomStyleSpan(
style: Int,
weight: Int,
fontFeatureSettingsParam: String?,
fontVariationSettingsParam: String?,
family: String?,
assetManager: AssetManager,
fontWeightAdjustment: Int,
Expand All @@ -92,6 +96,7 @@ internal class CustomStyleSpan(
paint.apply {
fontFeatureSettings = fontFeatureSettingsParam
setTypeface(adjustedTypeface)
ReactTypefaceUtils.applyFontVariationSettings(this, fontVariationSettingsParam)
isSubpixelText = true
isLinearText = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,8 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
span.style == fontStyle &&
span.fontFamily == fontFamily &&
span.weight == fontWeight &&
span.fontFeatureSettings == fontFeatureSettings
span.fontFeatureSettings == fontFeatureSettings &&
span.fontVariationSettings == null
}
}

Expand Down Expand Up @@ -908,6 +909,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
fontStyle,
fontWeight,
fontFeatureSettings,
null,
fontFamily,
context.assets,
getFontWeightAdjustment(context),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,69 @@ package com.facebook.react.views.text

import android.view.Gravity
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.uimanager.DisplayMetricsHolder
import com.facebook.react.uimanager.ReactStylesDiffMap
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment

@RunWith(RobolectricTestRunner::class)
class TextAttributePropsTest {

@Before
fun setUp() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(RuntimeEnvironment.getApplication())
}

@After
fun tearDown() {
DisplayMetricsHolder.setScreenDisplayMetrics(null)
}

@Test
fun readableMapSetsFontVariationSettings() {
val textAttributes =
TextAttributeProps.fromReadableMap(
ReactStylesDiffMap(JavaOnlyMap.of("fontVariationSettings", "'wght' 550"))
)

assertThat(textAttributes.fontVariationSettings).isEqualTo("'wght' 550")
}

@Test
fun readableMapSetsDoubleQuotedFontVariationSettings() {
val textAttributes =
TextAttributeProps.fromReadableMap(
ReactStylesDiffMap(JavaOnlyMap.of("fontVariationSettings", "\"wght\" 450"))
)

assertThat(textAttributes.fontVariationSettings).isEqualTo("\"wght\" 450")
}

@Test
fun readableMapIgnoresInvalidFontVariationSettings() {
val textAttributes =
TextAttributeProps.fromReadableMap(
ReactStylesDiffMap(JavaOnlyMap.of("fontVariationSettings", "invalid"))
)

assertThat(textAttributes.fontVariationSettings).isNull()
}

@Test
fun readableMapTreatsNormalFontVariationSettingsAsExplicitReset() {
val textAttributes =
TextAttributeProps.fromReadableMap(
ReactStylesDiffMap(JavaOnlyMap.of("fontVariationSettings", "NoRmAl"))
)

assertThat(textAttributes.fontVariationSettings).isEmpty()
}

@Test
fun textAlignStartUsesStartSide() {
assertThat(textAlignment("start", isRTL = false)).isEqualTo(Gravity.LEFT)
Expand Down
Loading
Loading