Merge "Add a11y support for system-wide bold text setting" into androidx-main
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index 1ba93dd..6b9c9b4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -557,7 +557,8 @@
current.overflow != overflow ||
current.maxLines != maxLines ||
current.density != density ||
- current.placeholders != placeholders
+ current.placeholders != placeholders ||
+ current.fontFamilyResolver !== fontFamilyResolver
) {
TextDelegate(
text = text,
@@ -592,7 +593,8 @@
current.softWrap != softWrap ||
current.overflow != overflow ||
current.maxLines != maxLines ||
- current.density != density
+ current.density != density ||
+ current.fontFamilyResolver !== fontFamilyResolver
) {
TextDelegate(
text = AnnotatedString(text),
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index c8aa120..71901ff 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -550,6 +550,9 @@
public final class AndroidFontLoader_androidKt {
}
+ public final class AndroidFontResolveInterceptor_androidKt {
+ }
+
public final class AndroidFontUtils_androidKt {
}
diff --git a/compose/ui/ui-text/api/public_plus_experimental_current.txt b/compose/ui/ui-text/api/public_plus_experimental_current.txt
index 604b381..32259b5 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -565,6 +565,9 @@
public final class AndroidFontLoader_androidKt {
}
+ public final class AndroidFontResolveInterceptor_androidKt {
+ }
+
public final class AndroidFontUtils_androidKt {
}
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index c8aa120..71901ff 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -550,6 +550,9 @@
public final class AndroidFontLoader_androidKt {
}
+ public final class AndroidFontResolveInterceptor_androidKt {
+ }
+
public final class AndroidFontUtils_androidKt {
}
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
index 3458f16..de9bfc3 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
@@ -27,6 +27,8 @@
import androidx.compose.ui.text.font.PlatformFontFamilyTypefaceAdapter
import androidx.compose.ui.text.font.TypefaceRequestCache
import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.text.font.AndroidFontResolveInterceptor
+import androidx.compose.ui.text.font.PlatformResolveInterceptor
import kotlin.math.ceil
import kotlin.math.roundToInt
@@ -43,13 +45,18 @@
@OptIn(ExperimentalTextApi::class)
internal fun UncachedFontFamilyResolver(
context: Context
-): FontFamily.Resolver = UncachedFontFamilyResolver(AndroidFontLoader(context))
+): FontFamily.Resolver = UncachedFontFamilyResolver(
+ AndroidFontLoader(context),
+ AndroidFontResolveInterceptor(context)
+)
@OptIn(ExperimentalTextApi::class)
internal fun UncachedFontFamilyResolver(
- platformFontLoader: PlatformFontLoader
+ platformFontLoader: PlatformFontLoader,
+ platformResolveInterceptor: PlatformResolveInterceptor
): FontFamily.Resolver = FontFamilyResolverImpl(
platformFontLoader,
+ platformResolveInterceptor,
TypefaceRequestCache(),
FontListFontFamilyTypefaceAdapter(AsyncTypefaceCache()),
PlatformFontFamilyTypefaceAdapter()
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplCancellationTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplCancellationTest.kt
index 1d1e891..ef79faf8 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplCancellationTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplCancellationTest.kt
@@ -50,6 +50,7 @@
private val context = InstrumentationRegistry.getInstrumentation().context
private val fontLoader = AndroidFontLoader(context)
+ private val fontResolveInterceptor = AndroidFontResolveInterceptor(context)
@Before
fun setup() {
@@ -62,6 +63,7 @@
val injectedContext = scope.coroutineContext.minusKey(CoroutineExceptionHandler)
subject = FontFamilyResolverImpl(
fontLoader,
+ fontResolveInterceptor,
typefaceRequestCache,
FontListFontFamilyTypefaceAdapter(asyncTypefaceCache, injectedContext))
typefaceLoader = AsyncTestTypefaceLoader()
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
index 68ea243..4d0fad8 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplTest.kt
@@ -22,8 +22,10 @@
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.FontTestData
import androidx.compose.ui.text.UncachedFontFamilyResolver
+import androidx.compose.ui.text.font.testutils.AsyncFauxFont
import androidx.compose.ui.text.font.testutils.AsyncTestTypefaceLoader
import androidx.compose.ui.text.font.testutils.BlockingFauxFont
+import androidx.compose.ui.text.font.testutils.OptionalFauxFont
import androidx.compose.ui.text.matchers.assertThat
import androidx.compose.ui.text.platform.bitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -54,6 +56,8 @@
private val context = InstrumentationRegistry.getInstrumentation().context
private val fontLoader = AndroidFontLoader(context)
+ // This is the default value that Android uses
+ private val accessibilityFontWeightAdjustment = 300
private lateinit var subject: FontFamilyResolverImpl
@Before
@@ -62,16 +66,25 @@
typefaceCache = TypefaceRequestCache()
dispatcher = UnconfinedTestDispatcher()
scope = TestScope(dispatcher)
+ initializeSubject()
+ typefaceLoader = AsyncTestTypefaceLoader()
+ }
+
+ private fun initializeSubject(
+ platformResolveInterceptor: PlatformResolveInterceptor =
+ AndroidFontResolveInterceptor(context)
+ ) {
val injectedContext = scope.coroutineContext.minusKey(CoroutineExceptionHandler)
+
subject = FontFamilyResolverImpl(
fontLoader,
+ platformResolveInterceptor = platformResolveInterceptor,
typefaceRequestCache = typefaceCache,
fontListFontFamilyTypefaceAdapter = FontListFontFamilyTypefaceAdapter(
asyncTypefaceCache,
injectedContext
)
)
- typefaceLoader = AsyncTestTypefaceLoader()
}
private fun resolveAsTypeface(
@@ -428,7 +441,10 @@
override suspend fun awaitLoad(font: Font): Any = Typeface.DEFAULT
override val cacheKey: String = "Not the default resource loader"
}
- val otherTypeface = UncachedFontFamilyResolver(newFontLoader)
+ val otherTypeface = UncachedFontFamilyResolver(
+ newFontLoader,
+ PlatformResolveInterceptor.Default
+ )
.resolve(fontFamily).value as Typeface
assertThat(typeface).isNotSameInstanceAs(otherTypeface)
@@ -459,14 +475,17 @@
}
)
val firstAndroidResourceLoader = AndroidFontLoader(context)
+ val androidResolveInterceptor = AndroidFontResolveInterceptor(context)
val typeface = FontFamilyResolverImpl(
fontLoader,
+ androidResolveInterceptor,
typefaceCache,
FontListFontFamilyTypefaceAdapter(asyncTypefaceCache)
).resolve(fontFamily).value as Typeface
val secondAndroidResourceLoader = AndroidFontLoader(context)
val otherTypeface = FontFamilyResolverImpl(
fontLoader,
+ androidResolveInterceptor,
typefaceCache,
FontListFontFamilyTypefaceAdapter(asyncTypefaceCache)
).resolve(fontFamily).value as Typeface
@@ -599,4 +618,159 @@
assertThat(typeface500).hasWeightAndStyle(FontWeight.W100, FontStyle.Normal)
assertThat(typeface600).hasWeightAndStyle(FontWeight.W600, FontStyle.Normal)
}
+
+ @Test
+ fun androidFontResolveInterceptor_affectsTheFontWeight() {
+ initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+ val fontFamily = FontFamily(
+ FontTestData.FONT_400_REGULAR,
+ FontTestData.FONT_500_REGULAR,
+ FontTestData.FONT_600_REGULAR,
+ FontTestData.FONT_700_REGULAR,
+ FontTestData.FONT_800_REGULAR
+ )
+ val typeface = resolveAsTypeface(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.W400
+ )
+
+ assertThat(typeface).hasWeightAndStyle(FontWeight.W700, FontStyle.Normal)
+ }
+
+ @Test
+ fun androidFontResolveInterceptor_doesNotAffectTheFontStyle() {
+ initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+
+ val typeface = resolveAsTypeface(
+ fontWeight = FontWeight.W400,
+ fontStyle = FontStyle.Italic
+ )
+
+ assertThat(typeface).hasWeightAndStyle(FontWeight.W700, FontStyle.Italic)
+ }
+
+ @Test
+ fun platformResolveInterceptor_affectsTheResolvedFontStyle() {
+ initializeSubject(
+ platformResolveInterceptor = object : PlatformResolveInterceptor {
+ override fun interceptFontStyle(fontStyle: FontStyle) = FontStyle.Italic
+ }
+ )
+
+ val typeface = resolveAsTypeface(
+ fontWeight = FontWeight.Normal,
+ fontStyle = FontStyle.Normal
+ )
+
+ assertThat(typeface).hasWeightAndStyle(FontWeight.Normal, FontStyle.Italic)
+ }
+
+ @Test
+ fun platformResolveInterceptor_affectsTheResolvedFontSynthesis() {
+ initializeSubject(
+ platformResolveInterceptor = object : PlatformResolveInterceptor {
+ override fun interceptFontSynthesis(fontSynthesis: FontSynthesis) =
+ FontSynthesis.All
+ }
+ )
+
+ val fontFamily = FontTestData.FONT_100_REGULAR.toFontFamily()
+
+ val typeface = resolveAsTypeface(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Bold,
+ fontStyle = FontStyle.Italic,
+ fontSynthesis = FontSynthesis.None
+ )
+
+ assertThat(typeface).hasWeightAndStyle(FontWeight.Bold, FontStyle.Italic)
+ }
+
+ @Test
+ fun platformResolveInterceptor_affectsTheResolvedFontFamily() {
+ initializeSubject(
+ platformResolveInterceptor = object : PlatformResolveInterceptor {
+ override fun interceptFontFamily(fontFamily: FontFamily?) =
+ FontTestData.FONT_100_REGULAR.toFontFamily()
+ }
+ )
+
+ val typeface = resolveAsTypeface(fontFamily = FontFamily.Cursive)
+
+ assertThat(typeface).hasWeightAndStyle(FontWeight.W100, FontStyle.Normal)
+ }
+
+ @Test
+ fun androidResolveInterceptor_affectsAsyncFontResolution_withFallback() {
+ initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+
+ val loader = AsyncTestTypefaceLoader()
+ val asyncFauxFontW400 = AsyncFauxFont(loader, FontWeight.W400)
+ val asyncFauxFontW700 = AsyncFauxFont(loader, FontWeight.W700)
+ val blockingFauxFontW400 = BlockingFauxFont(loader, Typeface.DEFAULT, FontWeight.W400)
+
+ val fontFamily = FontFamily(
+ asyncFauxFontW400,
+ blockingFauxFontW400,
+ asyncFauxFontW700
+ )
+
+ val fallbackTypeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+ assertThat(fallbackTypeface).hasWeightAndStyle(FontWeight.W700, FontStyle.Normal)
+
+ // loads the W700 async font which should be the matched font
+ loader.completeOne(asyncFauxFontW700, Typeface.MONOSPACE)
+
+ val typeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+ assertThat(typeface).isSameInstanceAs(Typeface.MONOSPACE)
+ }
+
+ @Test
+ fun androidResolveInterceptor_affectsAsyncFontResolution_withBlockingFont() {
+ initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+
+ val loader = AsyncTestTypefaceLoader()
+ val asyncFauxFontW400 = AsyncFauxFont(loader, FontWeight.W400)
+ val asyncFauxFontW700 = AsyncFauxFont(loader, FontWeight.W700)
+ val blockingFauxFontW700 = BlockingFauxFont(loader, Typeface.SANS_SERIF, FontWeight.W700)
+
+ val fontFamily = FontFamily(
+ asyncFauxFontW400,
+ asyncFauxFontW700,
+ blockingFauxFontW700
+ )
+
+ val blockingTypeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+ assertThat(blockingTypeface).isSameInstanceAs(Typeface.SANS_SERIF)
+
+ // loads the W700 async font which should be the matched font
+ loader.completeOne(asyncFauxFontW700, Typeface.MONOSPACE)
+
+ val typeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+ assertThat(typeface).isSameInstanceAs(Typeface.MONOSPACE)
+ }
+
+ @Test
+ fun androidResolveInterceptor_choosesOptionalFont_whenWeightMatches() {
+ val loader = AsyncTestTypefaceLoader()
+ val optionalFauxFontW400 = OptionalFauxFont(loader, Typeface.MONOSPACE, FontWeight.W400)
+ val optionalFauxFontW700 = OptionalFauxFont(loader, Typeface.SERIF, FontWeight.W700)
+ val blockingFauxFontW700 = BlockingFauxFont(loader, Typeface.SANS_SERIF, FontWeight.W700)
+
+ initializeSubject()
+
+ val fontFamily = FontFamily(
+ optionalFauxFontW400,
+ optionalFauxFontW700,
+ blockingFauxFontW700
+ )
+
+ val typefaceNoAdjustment = resolveAsTypeface(fontFamily, FontWeight.W400)
+ assertThat(typefaceNoAdjustment).isSameInstanceAs(Typeface.MONOSPACE)
+
+ initializeSubject(AndroidFontResolveInterceptor(accessibilityFontWeightAdjustment))
+
+ val typeface = resolveAsTypeface(fontFamily, FontWeight.W400)
+ assertThat(typeface).isSameInstanceAs(Typeface.SERIF)
+ }
}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFontResolveInterceptor.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFontResolveInterceptor.android.kt
new file mode 100644
index 0000000..0c91c61
--- /dev/null
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFontResolveInterceptor.android.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.compose.ui.text.font
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.fonts.FontStyle
+import android.os.Build
+import androidx.compose.ui.unit.Density
+
+/**
+ * Android implementation for [PlatformResolveInterceptor].
+ *
+ * Android system accessibility should be able to override any font metric that
+ * affects how FontFamily is resolved. Font Size preference is handled by [Density.fontScale].
+ * This interceptor currently adjusts the Font Weight for Bold Text feature.
+ */
+internal data class AndroidFontResolveInterceptor(
+ private val fontWeightAdjustment: Int
+) : PlatformResolveInterceptor {
+
+ override fun interceptFontWeight(fontWeight: FontWeight): FontWeight {
+ // do not intercept if fontWeightAdjustment is not set or undefined
+ if (fontWeightAdjustment == 0 ||
+ fontWeightAdjustment == Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
+ return fontWeight
+ }
+
+ val finalFontWeight = (fontWeight.weight + fontWeightAdjustment).coerceIn(
+ FontStyle.FONT_WEIGHT_MIN,
+ FontStyle.FONT_WEIGHT_MAX
+ )
+ return FontWeight(finalFontWeight)
+ }
+}
+
+/**
+ * A helper function to create an interceptor using a context.
+ */
+internal fun AndroidFontResolveInterceptor(context: Context): AndroidFontResolveInterceptor {
+ val fontWeightAdjustment = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ context.resources.configuration.fontWeightAdjustment
+ } else {
+ 0
+ }
+ return AndroidFontResolveInterceptor(fontWeightAdjustment)
+}
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.android.kt
index b565fd5..bb37ba3 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.android.kt
@@ -36,7 +36,10 @@
fun createFontFamilyResolver(
context: Context
): FontFamily.Resolver {
- return FontFamilyResolverImpl(AndroidFontLoader(context))
+ return FontFamilyResolverImpl(
+ AndroidFontLoader(context),
+ AndroidFontResolveInterceptor(context)
+ )
}
/**
@@ -69,6 +72,7 @@
): FontFamily.Resolver {
return FontFamilyResolverImpl(
AndroidFontLoader(context),
+ AndroidFontResolveInterceptor(context),
GlobalTypefaceRequestCache,
FontListFontFamilyTypefaceAdapter(
GlobalAsyncTypefaceCache,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt
index fdc1d67..3fd77b3 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt
@@ -26,6 +26,8 @@
@ExperimentalTextApi
internal class FontFamilyResolverImpl(
internal val platformFontLoader: PlatformFontLoader /* exposed for desktop ParagraphBuilder */,
+ private val platformResolveInterceptor: PlatformResolveInterceptor =
+ PlatformResolveInterceptor.Default,
private val typefaceRequestCache: TypefaceRequestCache = GlobalTypefaceRequestCache,
private val fontListFontFamilyTypefaceAdapter: FontListFontFamilyTypefaceAdapter =
FontListFontFamilyTypefaceAdapter(GlobalAsyncTypefaceCache),
@@ -33,7 +35,7 @@
PlatformFontFamilyTypefaceAdapter()
) : FontFamily.Resolver {
private val createDefaultTypeface: (TypefaceRequest) -> Any = {
- resolve(null, it.fontWeight, it.fontStyle, it.fontSynthesis).value
+ resolve(it.copy(fontFamily = null)).value
}
override suspend fun preload(
@@ -46,9 +48,9 @@
val typeRequests = fontFamily.fonts.fastMap {
TypefaceRequest(
- fontFamily,
- it.weight,
- it.style,
+ platformResolveInterceptor.interceptFontFamily(fontFamily),
+ platformResolveInterceptor.interceptFontWeight(it.weight),
+ platformResolveInterceptor.interceptFontStyle(it.style),
FontSynthesis.All,
platformFontLoader.cacheKey
)
@@ -76,21 +78,27 @@
fontStyle: FontStyle,
fontSynthesis: FontSynthesis,
): State<Any> {
- val typeRequest = TypefaceRequest(
- fontFamily,
- fontWeight,
- fontStyle,
- fontSynthesis,
+ return resolve(TypefaceRequest(
+ platformResolveInterceptor.interceptFontFamily(fontFamily),
+ platformResolveInterceptor.interceptFontWeight(fontWeight),
+ platformResolveInterceptor.interceptFontStyle(fontStyle),
+ platformResolveInterceptor.interceptFontSynthesis(fontSynthesis),
platformFontLoader.cacheKey
- )
- val result = typefaceRequestCache.runCached(typeRequest) { onAsyncCompletion ->
+ ))
+ }
+
+ /**
+ * Resolves the final [typefaceRequest] without interceptors.
+ */
+ private fun resolve(typefaceRequest: TypefaceRequest): State<Any> {
+ val result = typefaceRequestCache.runCached(typefaceRequest) { onAsyncCompletion ->
fontListFontFamilyTypefaceAdapter.resolve(
- typeRequest,
+ typefaceRequest,
platformFontLoader,
onAsyncCompletion,
createDefaultTypeface
) ?: platformFamilyTypefaceAdapter.resolve(
- typeRequest,
+ typefaceRequest,
platformFontLoader,
onAsyncCompletion,
createDefaultTypeface
@@ -100,6 +108,27 @@
}
}
+/**
+ * Platform level [FontFamily.Resolver] argument interceptor. This interface is
+ * intended to bridge accessibility constraints on any platform with
+ * Compose through the use of [FontFamilyResolverImpl.resolve].
+ */
+internal interface PlatformResolveInterceptor {
+
+ fun interceptFontFamily(fontFamily: FontFamily?): FontFamily? = fontFamily
+
+ fun interceptFontWeight(fontWeight: FontWeight): FontWeight = fontWeight
+
+ fun interceptFontStyle(fontStyle: FontStyle): FontStyle = fontStyle
+
+ fun interceptFontSynthesis(fontSynthesis: FontSynthesis): FontSynthesis = fontSynthesis
+
+ companion object {
+ // NO-OP default interceptor
+ internal val Default: PlatformResolveInterceptor = object : PlatformResolveInterceptor {}
+ }
+}
+
internal val GlobalTypefaceRequestCache = TypefaceRequestCache()
@OptIn(ExperimentalTextApi::class)
internal val GlobalAsyncTypefaceCache = AsyncTypefaceCache()
diff --git a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.sikio.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.sikio.kt
index a0964d6..5be43e1f 100644
--- a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.sikio.kt
+++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.sikio.kt
@@ -59,6 +59,7 @@
): FontFamily.Resolver {
return FontFamilyResolverImpl(
SkiaFontLoader(),
+ PlatformResolveInterceptor.Default,
GlobalTypefaceRequestCache,
FontListFontFamilyTypefaceAdapter(
GlobalAsyncTypefaceCache,
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/font/AndroidFontResolverInterceptorTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/font/AndroidFontResolverInterceptorTest.kt
new file mode 100644
index 0000000..3de00a3a
--- /dev/null
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/font/AndroidFontResolverInterceptorTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.compose.ui.text.font
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class AndroidFontResolverInterceptorTest {
+ private lateinit var subject: AndroidFontResolveInterceptor
+
+ private fun createSubject(adjustment: Int) {
+ subject = AndroidFontResolveInterceptor(adjustment)
+ }
+
+ @Test
+ fun fontWeightDoesNotChange_whenBoldTextAccessibilityIsNotEnabled() {
+ val testedWeight = 400
+ val adjustment = 0
+ val expected = 400
+ createSubject(adjustment)
+ assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+ .isEqualTo(expected)
+ }
+
+ @Test
+ fun fontWeightIncreases_whenBoldTextAccessibilityIsEnabled() {
+ val testedWeight = 400
+ val adjustment = 300
+ val expected = 700
+ createSubject(adjustment)
+ assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+ .isEqualTo(expected)
+ }
+
+ @Test
+ fun fontWeightNeverExceeds1000_whenBoldTextAccessibilityIsEnabled() {
+ val testedWeight = 500
+ val adjustment = 600
+ createSubject(adjustment)
+ assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+ .isEqualTo(android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX)
+ }
+
+ @Test
+ fun fontWeightWontBeZero_whenBoldTextAccessibilityIsEnabled() {
+ val testedWeight = 500
+ val adjustment = -600
+ createSubject(adjustment)
+ assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+ .isEqualTo(android.graphics.fonts.FontStyle.FONT_WEIGHT_MIN)
+ }
+
+ @Test
+ fun fontWeightWontBeNegative_whenBoldTextAccessibilityIsEnabled() {
+ val testedWeight = 500
+ val adjustment = -500
+ createSubject(adjustment)
+ assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+ .isEqualTo(android.graphics.fonts.FontStyle.FONT_WEIGHT_MIN)
+ }
+
+ @Test
+ fun fontWeightWontOverflow_whenBoldTextAccessibilityIsEnabled() {
+ val testedWeight = 500
+ val adjustment = Int.MAX_VALUE
+ createSubject(adjustment)
+ assertThat(subject.interceptFontWeight(FontWeight(testedWeight)).weight)
+ .isEqualTo(testedWeight)
+ }
+
+ @Test
+ fun otherFontArgumentsWontChange_whenBoldTextAccessibilityIsEnabled() {
+ val adjustment = 300
+ createSubject(adjustment)
+ assertThat(subject.interceptFontFamily(FontFamily.SansSerif))
+ .isEqualTo(FontFamily.SansSerif)
+
+ assertThat(subject.interceptFontStyle(FontStyle.Normal))
+ .isEqualTo(FontStyle.Normal)
+
+ assertThat(subject.interceptFontSynthesis(FontSynthesis.Weight))
+ .isEqualTo(FontSynthesis.Weight)
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 3efe4c2..1b9ac43 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -54,6 +54,7 @@
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.referentialEqualityPolicy
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.InternalComposeUiApi
@@ -378,7 +379,20 @@
@Suppress("DEPRECATION", "OverridingDeprecatedMember")
override val fontLoader: Font.ResourceLoader = AndroidFontResourceLoader(context)
- override val fontFamilyResolver: FontFamily.Resolver = createFontFamilyResolver(context)
+ // Backed by mutableStateOf so that the local provider recomposes when it changes
+ // FontFamily.Resolver is not guaranteed to be stable or immutable, hence referential check
+ override var fontFamilyResolver: FontFamily.Resolver by mutableStateOf(
+ createFontFamilyResolver(context),
+ referentialEqualityPolicy()
+ )
+ private set
+
+ // keeps track of changes in font weight adjustment to update fontFamilyResolver
+ private var currentFontWeightAdjustment: Int =
+ context.resources.configuration.fontWeightAdjustmentCompat
+
+ private val Configuration.fontWeightAdjustmentCompat: Int
+ get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) fontWeightAdjustment else 0
// Backed by mutableStateOf so that the ambient provider recomposes when it changes
override var layoutDirection by mutableStateOf(
@@ -1409,6 +1423,10 @@
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
density = Density(context)
+ if (newConfig.fontWeightAdjustmentCompat != currentFontWeightAdjustment) {
+ currentFontWeightAdjustment = newConfig.fontWeightAdjustmentCompat
+ fontFamilyResolver = createFontFamilyResolver(context)
+ }
configurationChangeObserver(newConfig)
}