Merge changes from topics "fontscaling-lineheight-after", "fontscaling-lineheight-spfix" into udc-dev
* changes: fix(non linear font scaling): recalculate proportional line height if they setTextSize() after setting line height fix(non linear font scaling): preserve proportionality of lineHeight when non-linear font scaling is in effect feat(non linear font scaling): add FontScaleConverterFactory.isNonLinearFontScalingActive()
This commit is contained in:
committed by
Android (Google) Code Review
commit
48e1f12c18
@@ -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,10 +84,29 @@ 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.
|
||||
*
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user