Skip to content

Commit 5cefe0b

Browse files
j-piaseckifacebook-github-bot
authored andcommitted
Update Android's adjustsFontSizeToFit algorithm (#54715)
Summary: Changelog: [Android][Fixed] Fixed `adjustsFontSizeToFit` scaling the font size too much down Fixes #54356 Previous implementation was always using the starting font size as base for calculating ratio and was applying it to the already scaled down font sizes in each iteration. This caused the text size to be scaled down too aggresively. In this diff I changed the implementation to use the font size from the previous iteration for calculating the scale ratio to fix that. I also updated it to use binary seach instead of linear search for finding the largest fitting font size. Differential Revision: D87973778
1 parent 57c29fc commit 5cefe0b

File tree

1 file changed

+42
-28
lines changed

1 file changed

+42
-28
lines changed

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -902,21 +902,7 @@ internal object TextLayoutManager {
902902
paint: TextPaint,
903903
): Unit {
904904
var boring = isBoring(text, paint)
905-
var layout =
906-
createLayout(
907-
text,
908-
boring,
909-
width,
910-
widthYogaMeasureMode,
911-
includeFontPadding,
912-
textBreakStrategy,
913-
hyphenationFrequency,
914-
alignment,
915-
justificationMode,
916-
null,
917-
ReactConstants.UNSET,
918-
paint,
919-
)
905+
var layout: Layout
920906

921907
// Minimum font size is 4pts to match the iOS implementation.
922908
val minimumFontSize =
@@ -929,20 +915,20 @@ internal object TextLayoutManager {
929915
currentFontSize = max(currentFontSize, span.size).toInt()
930916
}
931917

932-
val initialFontSize = currentFontSize
933-
while (
934-
currentFontSize > minimumFontSize &&
935-
((maximumNumberOfLines != ReactConstants.UNSET &&
936-
maximumNumberOfLines != 0 &&
937-
layout.lineCount > maximumNumberOfLines) ||
938-
(heightYogaMeasureMode != YogaMeasureMode.UNDEFINED && layout.height > height) ||
939-
(text.length == 1 && layout.getLineWidth(0) > width))
940-
) {
941-
// TODO: We could probably use a smarter algorithm here. This will require 0(n)
942-
// measurements based on the number of points the font size needs to be reduced by.
943-
currentFontSize -= max(1, 1.dpToPx().toInt())
918+
var intervalStart = minimumFontSize
919+
var intervalEnd = currentFontSize
920+
var previousFontSize = currentFontSize
944921

945-
val ratio = currentFontSize.toFloat() / initialFontSize.toFloat()
922+
// `true` instead of `intervalStart != intervalEnd` so that the last iteration where both are at
923+
// the same size goes through and updates all relevant objects with the final font size
924+
while (true) {
925+
// Always use the point closer to the end of the interval, this way at the end when
926+
// end - start == 1, we land at current = end instead of current = start. In the first case
927+
// one measurement may be enough if intervalEnd is small enough to fit. In the second case
928+
// we always end up doing two measurements to check whether intervalEnd would fit.
929+
val currentFontSize = (intervalStart + intervalEnd + 1) / 2
930+
931+
val ratio = currentFontSize.toFloat() / previousFontSize.toFloat()
946932
paint.textSize = max((paint.textSize * ratio).toInt(), minimumFontSize).toFloat()
947933

948934
val sizeSpans = text.getSpans(0, text.length, ReactAbsoluteSizeSpan::class.java)
@@ -973,6 +959,34 @@ internal object TextLayoutManager {
973959
ReactConstants.UNSET,
974960
paint,
975961
)
962+
963+
if (intervalStart == intervalEnd) {
964+
// everything is updated at this point
965+
break
966+
}
967+
968+
val singleLineTextExceedsWidth = text.length == 1 && layout.getLineWidth(0) > width
969+
val exceedsHeight =
970+
heightYogaMeasureMode != YogaMeasureMode.UNDEFINED && layout.height > height
971+
val exceedsMaximumNumberOfLines =
972+
maximumNumberOfLines != ReactConstants.UNSET &&
973+
maximumNumberOfLines != 0 &&
974+
layout.lineCount > maximumNumberOfLines
975+
976+
if (
977+
currentFontSize > minimumFontSize &&
978+
(exceedsMaximumNumberOfLines || exceedsHeight || singleLineTextExceedsWidth)
979+
) {
980+
// Text doesn't fit the constraints. If intervalEnd - intervalStart == 1, it's known that
981+
// the correct font size is intervalStart. Set intervalEnd to match intervalStart and do one
982+
// more iteration to update layout correctly.
983+
intervalEnd = if (intervalEnd - intervalStart == 1) intervalStart else currentFontSize
984+
} else {
985+
// Text fits the constraints
986+
intervalStart = currentFontSize
987+
}
988+
989+
previousFontSize = currentFontSize
976990
}
977991
}
978992

0 commit comments

Comments
 (0)