From a4d59758d4790e7f32d11699a0292199dee9c12b Mon Sep 17 00:00:00 2001 From: Tyler Freeman Date: Thu, 23 Mar 2023 23:31:11 +0000 Subject: [PATCH] fix(non linear font scaling): preserve proportionality of lineHeight when non-linear font scaling is in effect This ensures that the design intent is preserved when using large font scales and non-linear font scaling: the line height proportions should still match even when using SP units for both text size and line height. If the dev does not define both textSize and lineHeight in SP units, we don't do the corrective recalculation. Unfortunately, this could result in cramped-looking text due to the lineHeight not scaling with text size. There are linters to warn the developer of this though, so we hope they do the right thing. Bug: 273326061 Test: atest cts/tests/tests/widget/src/android/widget/cts/TextViewFontScalingTest.kt Change-Id: I17f9b7c8d3e0e63deed4037ebf0e99b690bc694a --- core/java/android/widget/TextView.java | 48 +++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 67c9f8ca00489..65a937bdc4bc7 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -62,6 +62,7 @@ import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.FontScaleConverterFactory; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -1233,7 +1234,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener defStyleAttr, defStyleRes); int firstBaselineToTopHeight = -1; int lastBaselineToBottomHeight = -1; - int lineHeight = -1; + float lineHeight = -1f; + int lineHeightUnit = -1; readTextAppearance(context, a, attributes, true /* styleArray */); @@ -1583,7 +1585,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_lineHeight: - lineHeight = a.getDimensionPixelSize(attr, -1); + TypedValue peekValue = a.peekValue(attr); + if (peekValue != null && peekValue.type == TypedValue.TYPE_DIMENSION) { + lineHeightUnit = peekValue.getComplexUnit(); + lineHeight = TypedValue.complexToFloat(peekValue.data); + } else { + lineHeight = a.getDimensionPixelSize(attr, -1); + } break; } } @@ -1936,7 +1944,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setLastBaselineToBottomHeight(lastBaselineToBottomHeight); } if (lineHeight >= 0) { - setLineHeight(lineHeight); + if (lineHeightUnit == -1) { + setLineHeightPx(lineHeight); + } else { + setLineHeight(lineHeightUnit, lineHeight); + } } } @@ -6236,8 +6248,34 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @TypedValue.ComplexDimensionUnit int unit, @FloatRange(from = 0) float lineHeight ) { - setLineHeightPx( - TypedValue.applyDimension(unit, lineHeight, getDisplayMetricsOrSystem())); + var metrics = getDisplayMetricsOrSystem(); + // We can avoid the recalculation if we know non-linear font scaling isn't being used + // (an optimization for the majority case). + // We also don't try to do the recalculation unless both textSize and lineHeight are in SP. + if (!FontScaleConverterFactory.isNonLinearFontScalingActive( + getResources().getConfiguration().fontScale) + || unit != TypedValue.COMPLEX_UNIT_SP + || mTextSizeUnit != TypedValue.COMPLEX_UNIT_SP + ) { + setLineHeightPx(TypedValue.applyDimension(unit, lineHeight, metrics)); + return; + } + + // Recalculate a proportional line height when non-linear font scaling is in effect. + // Otherwise, a desired 2x line height at font scale 1.0 will not be 2x at font scale 2.0, + // due to non-linear font scaling compressing higher SP sizes. See b/273326061 for details. + // We know they are using SP units for both the text size and the line height + // at this point, so determine the ratio between them. This is the *intended* line spacing + // multiplier if font scale == 1.0. We can then determine what the pixel value for the line + // height would be if we preserved proportions. + var textSizePx = getTextSize(); + var textSizeSp = TypedValue.convertPixelsToDimension( + TypedValue.COMPLEX_UNIT_SP, + textSizePx, + metrics + ); + var ratio = lineHeight / textSizeSp; + setLineHeightPx(textSizePx * ratio); } /**