diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 7fa80666d053c..f9bdb0fe040c2 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -755,14 +755,18 @@ public class Editor { } } - private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) { - int wid = tv.getPaddingLeft() + tv.getPaddingRight(); - int ht = tv.getPaddingTop() + tv.getPaddingBottom(); + private void chooseSize(@NonNull PopupWindow pop, @NonNull CharSequence text, + @NonNull TextView tv) { + final int wid = tv.getPaddingLeft() + tv.getPaddingRight(); + final int ht = tv.getPaddingTop() + tv.getPaddingBottom(); - int defaultWidthInPixels = mTextView.getResources().getDimensionPixelSize( + final int defaultWidthInPixels = mTextView.getResources().getDimensionPixelSize( com.android.internal.R.dimen.textview_error_popup_default_width); - Layout l = new StaticLayout(text, tv.getPaint(), defaultWidthInPixels, - Layout.Alignment.ALIGN_NORMAL, 1, 0, true); + final StaticLayout l = StaticLayout.Builder.obtain(text, 0, text.length(), tv.getPaint(), + defaultWidthInPixels) + .setUseLineSpacingFromFallbacks(tv.mUseFallbackLineSpacing) + .build(); + float max = 0; for (int i = 0; i < l.getLineCount(); i++) { max = Math.max(max, l.getLineWidth(i)); diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java index 40253a1871771..2e1e96365f2a7 100644 --- a/core/java/android/widget/Switch.java +++ b/core/java/android/widget/Switch.java @@ -33,6 +33,7 @@ import android.graphics.Rect; import android.graphics.Region.Op; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.os.Build.VERSION_CODES; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; @@ -111,6 +112,7 @@ public class Switch extends CompoundButton { private CharSequence mTextOn; private CharSequence mTextOff; private boolean mShowText; + private boolean mUseFallbackLineSpacing; private int mTouchMode; private int mTouchSlop; @@ -246,6 +248,11 @@ public class Switch extends CompoundButton { com.android.internal.R.styleable.Switch_switchPadding, 0); mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false); + // TODO: replace CUR_DEVELOPMENT with P once P is added to android.os.Build.VERSION_CODES. + // STOPSHIP if the above TODO is not done. + mUseFallbackLineSpacing = + context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.CUR_DEVELOPMENT; + ColorStateList thumbTintList = a.getColorStateList( com.android.internal.R.styleable.Switch_thumbTint); if (thumbTintList != null) { @@ -894,8 +901,9 @@ public class Switch extends CompoundButton { int width = (int) Math.ceil(Layout.getDesiredWidth(transformed, 0, transformed.length(), mTextPaint, getTextDirectionHeuristic())); - return new StaticLayout(transformed, mTextPaint, width, - Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true); + return StaticLayout.Builder.obtain(transformed, 0, transformed.length(), mTextPaint, width) + .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) + .build(); } /** diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 2417b0fd81fd4..140ecc1610571 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -650,6 +650,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mListenerChanged = false; // True if internationalized input should be used for numbers and date and time. private final boolean mUseInternationalizedInput; + // True if fallback fonts that end up getting used should be allowed to affect line spacing. + /* package */ final boolean mUseFallbackLineSpacing; @ViewDebug.ExportedProperty(category = "text") private int mGravity = Gravity.TOP | Gravity.START; @@ -1252,8 +1254,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean numberPasswordInputType = variation == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); - mUseInternationalizedInput = - context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.O; + final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; + mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; + // TODO: replace CUR_DEVELOPMENT with P once P is added to android.os.Build.VERSION_CODES. + // STOPSHIP if the above TODO is not done. + mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.CUR_DEVELOPMENT; if (inputMethod != null) { Class c; @@ -7914,6 +7919,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) + .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -7963,6 +7969,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) + .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -8015,6 +8022,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) + .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) @@ -8374,6 +8382,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) + .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .setBreakStrategy(getBreakStrategy()) .setHyphenationFrequency(getHyphenationFrequency()) .setJustificationMode(getJustificationMode()) diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java index 31b167d43f669..7870333cd82d1 100644 --- a/core/java/com/android/internal/widget/ImageFloatingTextView.java +++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java @@ -81,6 +81,7 @@ public class ImageFloatingTextView extends TextView { .setTextDirection(getTextDirectionHeuristic()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) + .setUseLineSpacingFromFallbacks(true) .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); int maxLines; diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java index 3230185dbda72..110782896d5aa 100644 --- a/core/java/com/android/internal/widget/SubtitleView.java +++ b/core/java/com/android/internal/widget/SubtitleView.java @@ -256,8 +256,11 @@ public class SubtitleView extends View { // StaticLayout.getWidth(), so this is non-trivial. mHasMeasurements = true; mLastMeasuredWidth = maxWidth; - mLayout = new StaticLayout( - mText, mTextPaint, maxWidth, mAlignment, mSpacingMult, mSpacingAdd, true); + mLayout = StaticLayout.Builder.obtain(mText, 0, mText.length(), mTextPaint, maxWidth) + .setAlignment(mAlignment) + .setLineSpacing(mSpacingAdd, mSpacingMult) + .setUseLineSpacingFromFallbacks(true) + .build(); return true; } diff --git a/core/tests/coretests/src/android/text/FontFallbackSetup.java b/core/tests/coretests/src/android/text/FontFallbackSetup.java index bcf2514dc73fd..ced74eebe47b2 100644 --- a/core/tests/coretests/src/android/text/FontFallbackSetup.java +++ b/core/tests/coretests/src/android/text/FontFallbackSetup.java @@ -76,11 +76,15 @@ public class FontFallbackSetup implements AutoCloseable { Typeface.buildSystemFallback(testFontsXml, mTestFontsDir, mFontMap, fallbackMap); } + @NonNull + public Typeface getTypefaceFor(@NonNull String fontName) { + return mFontMap.get(fontName); + } + @NonNull public TextPaint getPaintFor(@NonNull String fontName) { - final Typeface testTypeface = mFontMap.get(fontName); final TextPaint paint = new TextPaint(); - paint.setTypeface(testTypeface); + paint.setTypeface(getTypefaceFor(fontName)); return paint; } diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java index 5806bf102bebb..d31da71581ef5 100644 --- a/core/tests/coretests/src/android/widget/TextViewTest.java +++ b/core/tests/coretests/src/android/widget/TextViewTest.java @@ -30,6 +30,7 @@ import android.support.test.annotation.UiThreadTest; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; +import android.text.FontFallbackSetup; import android.text.GetChars; import android.text.Layout; import android.text.Selection; @@ -249,4 +250,56 @@ public class TextViewTest { } return builder.toString(); } + + @Test + public void testFallbackLineSpacing() { + // All glyphs in the fonts are 1em wide. + final String[] testFontFiles = { + // ascent == 1em, descent == 2em, only supports 'a' and space + "ascent1em-descent2em.ttf", + // ascent == 3em, descent == 4em, only supports 'b' + "ascent3em-descent4em.ttf" + }; + final String xml = "" + + "" + + " " + + " ascent1em-descent2em.ttf" + + " " + + " " + + " ascent3em-descent4em.ttf" + + " " + + ""; + + try (FontFallbackSetup setup = + new FontFallbackSetup("DynamicLayout", testFontFiles, xml)) { + mTextView = new TextView(mActivity); + mTextView.setTypeface(setup.getTypefaceFor("sans-serif")); + mTextView.setTextSize(100); + mTextView.setText("aaaaa aabaa aaaaa"); // This should result in three lines. + mTextView.setPadding(0, 0, 0, 0); + mTextView.setIncludeFontPadding(false); + + final int em = (int) Math.ceil(mTextView.getPaint().measureText("a")); + final int width = 5 * em; + final int height = 30 * em; // tall enough to not affect our other measurements + mTextView.measure( + View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + mTextView.layout(0, 0, width, height); + + final Layout layout = mTextView.getLayout(); + assertNotNull(layout); + assertEquals(3, layout.getLineCount()); + + assertEquals(-em, layout.getLineAscent(0)); + assertEquals(2 * em, layout.getLineDescent(0)); + + // The second line has a 'b', so it needs more ascent and descent. + assertEquals(-3 * em, layout.getLineAscent(1)); + assertEquals(4 * em, layout.getLineDescent(1)); + + assertEquals(-em, layout.getLineAscent(2)); + assertEquals(2 * em, layout.getLineDescent(2)); + } + } }