@@ -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