diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt index 830d987439cbb9..eb4238d6e25ba1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt @@ -10,8 +10,8 @@ package com.facebook.react.views.text.internal.span import android.graphics.Paint.FontMetricsInt import android.text.style.LineHeightSpan import kotlin.math.ceil +import kotlin.math.abs import kotlin.math.floor -import kotlin.math.min /** * We use a custom [LineHeightSpan], because `lineSpacingExtra` is broken. Details here: @@ -26,31 +26,48 @@ public class CustomLineHeightSpan(height: Float) : LineHeightSpan, ReactSpan { end: Int, spanstartv: Int, v: Int, - fm: FontMetricsInt + fm: FontMetricsInt, ) { // This is more complicated that I wanted it to be. You can find a good explanation of what the // FontMetrics mean here: http://stackoverflow.com/questions/27631736. // The general solution is that if there's not enough height to show the full line height, we - // will prioritize in this order: descent, ascent, bottom, top - + // will generally try to distribute the deficit proportionally between the ascent/top and descent/bottom. + if (fm.descent > lineHeight) { // Show as much descent as possible - fm.descent = min(lineHeight.toDouble(), fm.descent.toDouble()).toInt() - fm.bottom = fm.descent + fm.descent = lineHeight + fm.bottom = fm.ascent + fm.top = 0 fm.ascent = 0 - fm.top = fm.ascent } else if (-fm.ascent + fm.descent > lineHeight) { - // Show all descent, and as much ascent as possible - fm.bottom = fm.descent - fm.ascent = -lineHeight + fm.descent - fm.top = fm.ascent - } else if (-fm.ascent + fm.bottom > lineHeight) { - // Show all ascent, descent, as much bottom as possible + // Calculate the amount we are over, and split the adjustment between descent and ascent + val difference = -(lineHeight + fm.ascent - fm.descent)/ 2 + val remainder = difference % 2 + fm.ascent = fm.ascent + difference + fm.descent = fm.descent - difference - remainder fm.top = fm.ascent - fm.bottom = fm.ascent + lineHeight + fm.bottom = fm.descent } else if (-fm.top + fm.bottom > lineHeight) { - // Show all ascent, descent, bottom, as much top as possible - fm.top = fm.bottom - lineHeight + val excess = (-fm.top + fm.bottom) - lineHeight + + // Calculate the differences from top to ascent and bottom to descent + val topToAscent = abs(fm.top - fm.ascent) + val bottomToDescent = abs(fm.bottom - fm.descent) + + // Calculate the total delta + val totalDelta = topToAscent + bottomToDescent + + // Calculate proportional reductions + val topReduction = (excess * topToAscent / totalDelta).toInt() + val bottomReduction = (excess * bottomToDescent / totalDelta).toInt() + + // Adjust fm.top and fm.bottom + fm.top += topReduction + fm.bottom += bottomReduction + + // If there's a remainder, put it on the top + val remainder = excess - (topReduction + bottomReduction) + fm.top += remainder } else { // Show proportionally additional ascent / top & descent / bottom val additional = lineHeight - (-fm.top + fm.bottom)