summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/res/FontScaleConverterFactory.java26
-rw-r--r--core/java/android/util/TypedValue.java20
-rw-r--r--core/java/android/widget/TextView.java80
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt16
4 files changed, 129 insertions, 13 deletions
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 6b09c303e3cd..5eb65262918d 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -34,6 +34,8 @@ public class FontScaleConverterFactory {
@VisibleForTesting
static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>();
+ private static float sMinScaleBeforeCurvesApplied = 1.05f;
+
static {
// These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and
// manually tweaked for optimum readability.
@@ -82,11 +84,30 @@ public class FontScaleConverterFactory {
new float[] { 16f, 20f, 24f, 26f, 30f, 34f, 36f, 38f, 100})
);
+ sMinScaleBeforeCurvesApplied = getScaleFromKey(LOOKUP_TABLES.keyAt(0)) - 0.02f;
+ if (sMinScaleBeforeCurvesApplied <= 1.0f) {
+ throw new IllegalStateException(
+ "You should only apply non-linear scaling to font scales > 1"
+ );
+ }
}
private FontScaleConverterFactory() {}
/**
+ * Returns true if non-linear font scaling curves would be in effect for the given scale, false
+ * if the scaling would follow a linear curve or for no scaling.
+ *
+ * <p>Example usage:
+ * <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
+ *
+ * @hide
+ */
+ public static boolean isNonLinearFontScalingActive(float fontScale) {
+ return fontScale >= sMinScaleBeforeCurvesApplied;
+ }
+
+ /**
* Finds a matching FontScaleConverter for the given fontScale factor.
*
* @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
@@ -97,10 +118,7 @@ public class FontScaleConverterFactory {
*/
@Nullable
public static FontScaleConverter forScale(float fontScale) {
- if (fontScale <= 1) {
- // We don't need non-linear curves for shrinking text or for 100%.
- // Also, fontScale==0 should not have a curve either.
- // And ignore negative font scales; that's just silly.
+ if (!isNonLinearFontScalingActive(fontScale)) {
return null;
}
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index b93e3386b7cc..330a9fce1b83 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -385,10 +385,22 @@ public class TypedValue {
*
* @return The complex unit type.
*/
- public int getComplexUnit()
- {
- return COMPLEX_UNIT_MASK & (data>>TypedValue.COMPLEX_UNIT_SHIFT);
- }
+ public int getComplexUnit() {
+ return getUnitFromComplexDimension(data);
+ }
+
+ /**
+ * Return the complex unit type for the given complex dimension. For example, a dimen type
+ * with value 12sp will return {@link #COMPLEX_UNIT_SP}. Use with values created with {@link
+ * #createComplexDimension(int, int)} etc.
+ *
+ * @return The complex unit type.
+ *
+ * @hide
+ */
+ public static int getUnitFromComplexDimension(int complexDimension) {
+ return COMPLEX_UNIT_MASK & (complexDimension >> TypedValue.COMPLEX_UNIT_SHIFT);
+ }
/**
* Converts an unpacked complex data value holding a dimension to its final floating point pixel
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 67c9f8ca0048..3fbb50581b25 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;
@@ -867,6 +868,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@UnsupportedAppUsage
private float mSpacingAdd = 0.0f;
+ /**
+ * Remembers what line height was set to originally, before we broke it down into raw pixels.
+ *
+ * <p>This is stored as a complex dimension with both value and unit packed into one field!
+ * {@see TypedValue}
+ */
+ private int mLineHeightComplexDimen;
+
private int mBreakStrategy;
private int mHyphenationFrequency;
private int mJustificationMode;
@@ -1233,7 +1242,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 +1593,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 +1952,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);
+ }
}
}
@@ -4629,6 +4649,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (size != mTextPaint.getTextSize()) {
mTextPaint.setTextSize(size);
+ maybeRecalculateLineHeight();
if (shouldRequestLayout && mLayout != null) {
// Do not auto-size right after setting the text size.
mNeedsAutoSizeText = false;
@@ -6214,6 +6235,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (lineHeight != fontHeight) {
// Set lineSpacingExtra by the difference of lineSpacing with lineHeight
setLineSpacing(lineHeight - fontHeight, 1f);
+
+ mLineHeightComplexDimen =
+ TypedValue.createComplexDimension(lineHeight, TypedValue.COMPLEX_UNIT_PX);
}
}
@@ -6236,8 +6260,54 @@ 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));
+
+ // Do this last so it overwrites what setLineHeightPx() sets it to.
+ mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
+ 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);
+
+ // Do this last so it overwrites what setLineHeightPx() sets it to.
+ mLineHeightComplexDimen = TypedValue.createComplexDimension(lineHeight, unit);
+ }
+
+ private void maybeRecalculateLineHeight() {
+ if (mLineHeightComplexDimen == 0) {
+ return;
+ }
+ int unit = TypedValue.getUnitFromComplexDimension(mLineHeightComplexDimen);
+ if (unit != TypedValue.COMPLEX_UNIT_SP) {
+ // The lineHeight was never supplied in SP, so we didn't do any fancy recalculations
+ // in setLineHeight(). We don't need to recalculate.
+ return;
+ }
+
+ setLineHeight(unit, TypedValue.complexToFloat(mLineHeightComplexDimen));
}
/**
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index a0d8dcf830e8..ba6c8fab48d4 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -122,6 +122,22 @@ class FontScaleConverterFactoryTest {
}
}
+ @SmallTest
+ fun testIsNonLinearFontScalingActive() {
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f))
+ .isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.5f)).isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(2f)).isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(3f)).isTrue()
+ }
+
@LargeTest
@Test
fun allFeasibleScalesAndConversionsDoNotCrash() {