Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 15b213d5 authored by Roozbeh Pournader's avatar Roozbeh Pournader
Browse files

Support fallback linespacing in DynamicLayout

Bug: 28963299
Test: bit FrameworksCoreTests:android.text.
Change-Id: I132499d5927b26fb45522ffee99bac12aca3721f
parent 22a167ca
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -41417,6 +41417,7 @@ package android.text {
    method public android.text.DynamicLayout.Builder setJustificationMode(int);
    method public android.text.DynamicLayout.Builder setLineSpacing(float, float);
    method public android.text.DynamicLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
    method public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
  }
  public abstract interface Editable implements java.lang.Appendable java.lang.CharSequence android.text.GetChars android.text.Spannable {
+1 −0
Original line number Diff line number Diff line
@@ -45019,6 +45019,7 @@ package android.text {
    method public android.text.DynamicLayout.Builder setJustificationMode(int);
    method public android.text.DynamicLayout.Builder setLineSpacing(float, float);
    method public android.text.DynamicLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
    method public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
  }
  public abstract interface Editable implements java.lang.Appendable java.lang.CharSequence android.text.GetChars android.text.Spannable {
+1 −0
Original line number Diff line number Diff line
@@ -41681,6 +41681,7 @@ package android.text {
    method public android.text.DynamicLayout.Builder setJustificationMode(int);
    method public android.text.DynamicLayout.Builder setLineSpacing(float, float);
    method public android.text.DynamicLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
    method public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
  }
  public abstract interface Editable implements java.lang.Appendable java.lang.CharSequence android.text.GetChars android.text.Spannable {
+28 −3
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ public class DynamicLayout extends Layout
            b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
            b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
            b.mIncludePad = true;
            b.mFallbackLineSpacing = false;
            b.mEllipsizedWidth = width;
            b.mEllipsize = null;
            b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
@@ -171,6 +172,25 @@ public class DynamicLayout extends Layout
            return this;
        }

        /**
         * Set whether to respect the ascent and descent of the fallback fonts that are used in
         * displaying the text (which is needed to avoid text from consecutive lines running into
         * each other). If set, fallback fonts that end up getting used can increase the ascent
         * and descent of the lines that they are used on.
         *
         * <p>For backward compatibility reasons, the default is {@code false}, but setting this to
         * true is strongly recommended. It is required to be true if text could be in languages
         * like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
         *
         * @param useLineSpacingFromFallbacks whether to expand linespacing based on fallback fonts
         * @return this builder, useful for chaining
         */
        @NonNull
        public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
            mFallbackLineSpacing = useLineSpacingFromFallbacks;
            return this;
        }

        /**
         * Set the width as used for ellipsizing purposes, if it differs from the normal layout
         * width. The default is the {@code width} passed to {@link #obtain}.
@@ -270,6 +290,7 @@ public class DynamicLayout extends Layout
        private float mSpacingMult;
        private float mSpacingAdd;
        private boolean mIncludePad;
        private boolean mFallbackLineSpacing;
        private int mBreakStrategy;
        private int mHyphenationFrequency;
        private int mJustificationMode;
@@ -320,7 +341,7 @@ public class DynamicLayout extends Layout
                         @IntRange(from = 0) int ellipsizedWidth) {
        this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
                spacingmult, spacingadd, includepad,
                StaticLayout.BREAK_STRATEGY_SIMPLE, StaticLayout.HYPHENATION_FREQUENCY_NONE,
                Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE,
                Layout.JUSTIFICATION_MODE_NONE, ellipsize, ellipsizedWidth);
    }

@@ -388,6 +409,7 @@ public class DynamicLayout extends Layout

    private void generate(@NonNull Builder b) {
        mBase = b.mBase;
        mFallbackLineSpacing = b.mFallbackLineSpacing;
        if (b.mEllipsize != null) {
            mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
            mEllipsizedWidth = b.mEllipsizedWidth;
@@ -573,6 +595,7 @@ public class DynamicLayout extends Layout
                .setWidth(getWidth())
                .setTextDirection(getTextDirectionHeuristic())
                .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
                .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
                .setEllipsizedWidth(mEllipsizedWidth)
                .setEllipsize(mEllipsizeAt)
                .setBreakStrategy(mBreakStrategy)
@@ -1033,11 +1056,12 @@ public class DynamicLayout extends Layout
        private void reflow(CharSequence s, int where, int before, int after) {
            DynamicLayout ml = mLayout.get();

            if (ml != null)
            if (ml != null) {
                ml.reflow(s, where, before, after);
            else if (s instanceof Spannable)
            } else if (s instanceof Spannable) {
                ((Spannable) s).removeSpan(this);
            }
        }

        public void beforeTextChanged(CharSequence s, int where, int before, int after) {
            // Intentionally empty
@@ -1093,6 +1117,7 @@ public class DynamicLayout extends Layout
    private CharSequence mDisplay;
    private ChangeWatcher mWatcher;
    private boolean mIncludePad;
    private boolean mFallbackLineSpacing;
    private boolean mEllipsize;
    private int mEllipsizedWidth;
    private TextUtils.TruncateAt mEllipsizeAt;
+73 −0
Original line number Diff line number Diff line
@@ -186,4 +186,77 @@ public class DynamicLayoutTest {
                ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/);
        layout.getLineExtra(100);
    }

    @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 = "<?xml version='1.0' encoding='UTF-8'?>"
                + "<familyset>"
                + "  <family name='sans-serif'>"
                + "    <font weight='400' style='normal'>ascent1em-descent2em.ttf</font>"
                + "  </family>"
                + "  <family>"
                + "    <font weight='400' style='normal'>ascent3em-descent4em.ttf</font>"
                + "  </family>"
                + "</familyset>";

        try (FontFallbackSetup setup =
                new FontFallbackSetup("DynamicLayout", testFontFiles, xml)) {
            final TextPaint paint = setup.getPaintFor("sans-serif");
            final int textSize = 100;
            paint.setTextSize(textSize);
            assertEquals(-textSize, paint.ascent(), 0.0f);
            assertEquals(2 * textSize, paint.descent(), 0.0f);

            final int paraWidth = 5 * textSize;
            final String text = "aaaaa aabaa aaaaa"; // This should result in three lines.

            // Old line spacing. All lines should get their ascent and descents from the first font.
            DynamicLayout layout = DynamicLayout.Builder
                    .obtain(text, paint, paraWidth)
                    .setIncludePad(false)
                    .setUseLineSpacingFromFallbacks(false)
                    .build();
            assertEquals(3, layout.getLineCount());
            assertEquals(-textSize, layout.getLineAscent(0));
            assertEquals(2 * textSize, layout.getLineDescent(0));
            assertEquals(-textSize, layout.getLineAscent(1));
            assertEquals(2 * textSize, layout.getLineDescent(1));
            assertEquals(-textSize, layout.getLineAscent(2));
            assertEquals(2 * textSize, layout.getLineDescent(2));

            // New line spacing. The second line has a 'b', so it needs more ascent and descent.
            layout = DynamicLayout.Builder
                    .obtain(text, paint, paraWidth)
                    .setIncludePad(false)
                    .setUseLineSpacingFromFallbacks(true)
                    .build();
            assertEquals(3, layout.getLineCount());
            assertEquals(-textSize, layout.getLineAscent(0));
            assertEquals(2 * textSize, layout.getLineDescent(0));
            assertEquals(-3 * textSize, layout.getLineAscent(1));
            assertEquals(4 * textSize, layout.getLineDescent(1));
            assertEquals(-textSize, layout.getLineAscent(2));
            assertEquals(2 * textSize, layout.getLineDescent(2));

            // The default is the old line spacing, for backward compatibility.
            layout = DynamicLayout.Builder
                    .obtain(text, paint, paraWidth)
                    .setIncludePad(false)
                    .build();
            assertEquals(3, layout.getLineCount());
            assertEquals(-textSize, layout.getLineAscent(0));
            assertEquals(2 * textSize, layout.getLineDescent(0));
            assertEquals(-textSize, layout.getLineAscent(1));
            assertEquals(2 * textSize, layout.getLineDescent(1));
            assertEquals(-textSize, layout.getLineAscent(2));
            assertEquals(2 * textSize, layout.getLineDescent(2));
        }
    }
}
Loading