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

Commit 39b4db73 authored by Raph Levien's avatar Raph Levien
Browse files

Add breakStrategy attribute to TextView

This patch adds plumbing to TextView to control the strategy used
for breaking paragraphs into lines.

The default for TextView is "quality", while the default for EditText
is "simple", largely to avoid too much re-layout when editing.

StaticLayout now has a builder which provides access to more
functionality and is also cleaner than the old mechanism of having
lots of constructors with varying numbers of arguments. This patch
changes TextView to use that builder, and also contains cleanups
of the Builder within StaticLayout.

Change-Id: Iee3cf3a05a3e51ba0834554e4a3ec606e9cabca5
parent 066be66d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -354,6 +354,7 @@ package android {
    field public static final int bottomRightRadius = 16843180; // 0x10101ac
    field public static final int breadCrumbShortTitle = 16843524; // 0x1010304
    field public static final int breadCrumbTitle = 16843523; // 0x1010303
    field public static final int breakStrategy = 16844011; // 0x10104eb
    field public static final int bufferType = 16843086; // 0x101014e
    field public static final int button = 16843015; // 0x1010107
    field public static final int buttonBarButtonStyle = 16843567; // 0x101032f
@@ -30920,6 +30921,9 @@ package android.text {
    method public final void increaseWidthTo(int);
    method public boolean isRtlCharAt(int);
    method protected final boolean isSpanned();
    field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
    field public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; // 0x1
    field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0
    field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
    field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
  }
@@ -40082,6 +40086,7 @@ package android.widget {
    method public void endBatchEdit();
    method public boolean extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText);
    method public final int getAutoLinkMask();
    method public int getBreakStrategy();
    method public int getCompoundDrawablePadding();
    method public android.content.res.ColorStateList getCompoundDrawableTintList();
    method public android.graphics.PorterDuff.Mode getCompoundDrawableTintMode();
@@ -40183,6 +40188,7 @@ package android.widget {
    method public void removeTextChangedListener(android.text.TextWatcher);
    method public void setAllCaps(boolean);
    method public final void setAutoLinkMask(int);
    method public void setBreakStrategy(int);
    method public void setCompoundDrawablePadding(int);
    method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
    method public void setCompoundDrawableTintMode(android.graphics.PorterDuff.Mode);
+6 −0
Original line number Diff line number Diff line
@@ -426,6 +426,7 @@ package android {
    field public static final int bottomRightRadius = 16843180; // 0x10101ac
    field public static final int breadCrumbShortTitle = 16843524; // 0x1010304
    field public static final int breadCrumbTitle = 16843523; // 0x1010303
    field public static final int breakStrategy = 16844011; // 0x10104eb
    field public static final int bufferType = 16843086; // 0x101014e
    field public static final int button = 16843015; // 0x1010107
    field public static final int buttonBarButtonStyle = 16843567; // 0x101032f
@@ -33462,6 +33463,9 @@ package android.text {
    method public final void increaseWidthTo(int);
    method public boolean isRtlCharAt(int);
    method protected final boolean isSpanned();
    field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
    field public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; // 0x1
    field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0
    field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
    field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
  }
@@ -42925,6 +42929,7 @@ package android.widget {
    method public void endBatchEdit();
    method public boolean extractText(android.view.inputmethod.ExtractedTextRequest, android.view.inputmethod.ExtractedText);
    method public final int getAutoLinkMask();
    method public int getBreakStrategy();
    method public int getCompoundDrawablePadding();
    method public android.content.res.ColorStateList getCompoundDrawableTintList();
    method public android.graphics.PorterDuff.Mode getCompoundDrawableTintMode();
@@ -43026,6 +43031,7 @@ package android.widget {
    method public void removeTextChangedListener(android.text.TextWatcher);
    method public void setAllCaps(boolean);
    method public final void setAutoLinkMask(int);
    method public void setBreakStrategy(int);
    method public void setCompoundDrawablePadding(int);
    method public void setCompoundDrawableTintList(android.content.res.ColorStateList);
    method public void setCompoundDrawableTintMode(android.graphics.PorterDuff.Mode);
+8 −5
Original line number Diff line number Diff line
@@ -79,7 +79,8 @@ public class DynamicLayout extends Layout
                         boolean includepad,
                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
        this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
                spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
                spacingmult, spacingadd, includepad, StaticLayout.BREAK_STRATEGY_SIMPLE,
                ellipsize, ellipsizedWidth);
    }

    /**
@@ -95,7 +96,7 @@ public class DynamicLayout extends Layout
                         TextPaint paint,
                         int width, Alignment align, TextDirectionHeuristic textDir,
                         float spacingmult, float spacingadd,
                         boolean includepad,
                         boolean includepad, int breakStrategy,
                         TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
        super((ellipsize == null)
                ? display
@@ -120,6 +121,7 @@ public class DynamicLayout extends Layout
        mObjects = new PackedObjectVector<Directions>(1);

        mIncludePad = includepad;
        mBreakStrategy = breakStrategy;

        /*
         * This is annoying, but we can't refer to the layout until
@@ -279,10 +281,9 @@ public class DynamicLayout extends Layout
            sBuilder = null;
        }

        // TODO: make sure reflowed is properly initialized
        if (reflowed == null) {
            reflowed = new StaticLayout(null);
            b = StaticLayout.Builder.obtain();
            b = StaticLayout.Builder.obtain(text, where, where + after, getWidth());
        }

        b.setText(text, where, where + after)
@@ -292,7 +293,8 @@ public class DynamicLayout extends Layout
                .setSpacingMult(getSpacingMultiplier())
                .setSpacingAdd(getSpacingAdd())
                .setEllipsizedWidth(mEllipsizedWidth)
                .setEllipsize(mEllipsizeAt);
                .setEllipsize(mEllipsizeAt)
                .setBreakStrategy(mBreakStrategy);
        reflowed.generate(b, false, true);
        int n = reflowed.getLineCount();

@@ -717,6 +719,7 @@ public class DynamicLayout extends Layout
    private boolean mEllipsize;
    private int mEllipsizedWidth;
    private TextUtils.TruncateAt mEllipsizeAt;
    private int mBreakStrategy;

    private PackedIntVector mInts;
    private PackedObjectVector<Directions> mObjects;
+28 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.text;

import android.annotation.IntDef;
import android.emoji.EmojiFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -33,6 +34,8 @@ import android.text.style.TabStopSpan;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;

/**
@@ -43,6 +46,31 @@ import java.util.Arrays;
 * For text that will not change, use a {@link StaticLayout}.
 */
public abstract class Layout {
    /** @hide */
    @IntDef({BREAK_STRATEGY_SIMPLE, BREAK_STRATEGY_HIGH_QUALITY, BREAK_STRATEGY_BALANCED})
    @Retention(RetentionPolicy.SOURCE)
    public @interface BreakStrategy {}

    /**
     * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
     * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
     * before it (which yields a more consistent user experience when editing), but layout may not
     * be the highest quality.
     */
    public static final int BREAK_STRATEGY_SIMPLE = 0;

    /**
     * Value for break strategy indicating high quality line breaking, including automatic
     * hyphenation and doing whole-paragraph optimization of line breaks.
     */
    public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;

    /**
     * Value for break strategy indicating balanced line breaking. The breaks are chosen to
     * make all lines as close to the same length as possible, including automatic hyphenation.
     */
    public static final int BREAK_STRATEGY_BALANCED = 2;

    private static final ParagraphStyle[] NO_PARA_SPANS =
        ArrayUtils.emptyArray(ParagraphStyle.class);

+58 −33
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.TabStopSpan;
import android.util.Log;
import android.util.Pools.SynchronizedPool;

import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -56,28 +57,23 @@ public class StaticLayout extends Layout {
            mNativePtr = nNewBuilder();
        }

        static Builder obtain() {
            Builder b = null;
            synchronized (sLock) {
                for (int i = 0; i < sCached.length; i++) {
                    if (sCached[i] != null) {
                        b = sCached[i];
                        sCached[i] = null;
                        break;
                    }
                }
            }
        public static Builder obtain(CharSequence source, int start, int end, int width) {
            Builder b = sPool.acquire();
            if (b == null) {
                b = new Builder();
            }

            // set default initial values
            b.mWidth = 0;
            b.mText = source;
            b.mStart = start;
            b.mEnd = end;
            b.mWidth = width;
            b.mAlignment = Alignment.ALIGN_NORMAL;
            b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
            b.mSpacingMult = 1.0f;
            b.mSpacingAdd = 0.0f;
            b.mIncludePad = true;
            b.mEllipsizedWidth = 0;
            b.mEllipsizedWidth = width;
            b.mEllipsize = null;
            b.mMaxLines = Integer.MAX_VALUE;

@@ -85,18 +81,11 @@ public class StaticLayout extends Layout {
            return b;
        }

        static void recycle(Builder b) {
        private static void recycle(Builder b) {
            b.mPaint = null;
            b.mText = null;
            MeasuredText.recycle(b.mMeasuredText);
            synchronized (sLock) {
                for (int i = 0; i < sCached.length; i++) {
                    if (sCached[i] == null) {
                        sCached[i] = b;
                        break;
                    }
                }
            }
            sPool.release(b);
        }

        // release any expensive state
@@ -129,6 +118,11 @@ public class StaticLayout extends Layout {
            return this;
        }

        public Builder setAlignment(Alignment alignment) {
            mAlignment = alignment;
            return this;
        }

        public Builder setTextDir(TextDirectionHeuristic textDir) {
            mTextDir = textDir;
            return this;
@@ -166,6 +160,11 @@ public class StaticLayout extends Layout {
            return this;
        }

        public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
            mBreakStrategy = breakStrategy;
            return this;
        }

        /**
         * Measurement and break iteration is done in native code. The protocol for using
         * the native code is as follows.
@@ -207,10 +206,8 @@ public class StaticLayout extends Layout {
        }

        public StaticLayout build() {
            // TODO: can optimize based on whether ellipsis is needed
            StaticLayout result = new StaticLayout(mText);
            result.generate(this, this.mIncludePad, this.mIncludePad);
            recycle(this);
            StaticLayout result = new StaticLayout(this);
            Builder.recycle(this);
            return result;
        }

@@ -230,6 +227,7 @@ public class StaticLayout extends Layout {
        int mEnd;
        TextPaint mPaint;
        int mWidth;
        Alignment mAlignment;
        TextDirectionHeuristic mTextDir;
        float mSpacingMult;
        float mSpacingAdd;
@@ -237,6 +235,7 @@ public class StaticLayout extends Layout {
        int mEllipsizedWidth;
        TextUtils.TruncateAt mEllipsize;
        int mMaxLines;
        int mBreakStrategy;

        Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();

@@ -245,8 +244,7 @@ public class StaticLayout extends Layout {

        Locale mLocale;

        private static final Object sLock = new Object();
        private static final Builder[] sCached = new Builder[3];
        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
    }

    public StaticLayout(CharSequence source, TextPaint paint,
@@ -316,10 +314,9 @@ public class StaticLayout extends Layout {
                    : new Ellipsizer(source),
              paint, outerwidth, align, textDir, spacingmult, spacingadd);

        Builder b = Builder.obtain();
        b.setText(source, bufstart, bufend)
        Builder b = Builder.obtain(source, bufstart, bufend, outerwidth)
            .setPaint(paint)
            .setWidth(outerwidth)
            .setAlignment(align)
            .setTextDir(textDir)
            .setSpacingMult(spacingmult)
            .setSpacingAdd(spacingadd)
@@ -366,6 +363,35 @@ public class StaticLayout extends Layout {
        mLines = new int[mLineDirections.length];
    }

    private StaticLayout(Builder b) {
        super((b.mEllipsize == null)
                ? b.mText
                : (b.mText instanceof Spanned)
                    ? new SpannedEllipsizer(b.mText)
                    : new Ellipsizer(b.mText),
                b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);

        if (b.mEllipsize != null) {
            Ellipsizer e = (Ellipsizer) getText();

            e.mLayout = this;
            e.mWidth = b.mEllipsizedWidth;
            e.mMethod = b.mEllipsize;
            mEllipsizedWidth = b.mEllipsizedWidth;

            mColumns = COLUMNS_ELLIPSIZE;
        } else {
            mColumns = COLUMNS_NORMAL;
            mEllipsizedWidth = b.mWidth;
        }

        mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
        mLines = new int[mLineDirections.length];
        mMaximumVisibleLineCount = b.mMaxLines;

        generate(b, b.mIncludePad, b.mIncludePad);
    }

    /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
        CharSequence source = b.mText;
        int bufStart = b.mStart;
@@ -477,10 +503,9 @@ public class StaticLayout extends Layout {
                }
            }

            int breakStrategy = 0;  // 0 = kBreakStrategy_Greedy
            nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
                    firstWidth, firstWidthLineCount, restWidth,
                    variableTabStops, TAB_INCREMENT, breakStrategy);
                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy);

            // measurement has to be done before performing line breaking
            // but we don't want to recompute fontmetrics or span ranges the
Loading