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

Commit a7d731d5 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topics 'roozbeh-b31537595-thirdtry', 'roozbeh-b64372088'

* changes:
  Make ellipsize retry if text doesn't fit
  Reset StaticLayout.mEllipsized during generate()
parents e2ba325d e88b5df5
Loading
Loading
Loading
Loading
+179 −87
Original line number Original line Diff line number Diff line
@@ -559,7 +559,7 @@ public class StaticLayout extends Layout {
        Builder.recycle(b);
        Builder.recycle(b);
    }
    }


    /* package */ StaticLayout(CharSequence text) {
    /* package */ StaticLayout(@Nullable CharSequence text) {
        super(text, null, 0, null, 0, 0);
        super(text, null, 0, null, 0, 0);


        mColumns = COLUMNS_ELLIPSIZE;
        mColumns = COLUMNS_ELLIPSIZE;
@@ -601,7 +601,7 @@ public class StaticLayout extends Layout {
    }
    }


    /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
    /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
        CharSequence source = b.mText;
        final CharSequence source = b.mText;
        int bufStart = b.mStart;
        int bufStart = b.mStart;
        int bufEnd = b.mEnd;
        int bufEnd = b.mEnd;
        TextPaint paint = b.mPaint;
        TextPaint paint = b.mPaint;
@@ -621,6 +621,8 @@ public class StaticLayout extends Layout {
        b.setLocales(paint.getTextLocales());
        b.setLocales(paint.getTextLocales());


        mLineCount = 0;
        mLineCount = 0;
        mEllipsized = false;
        mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;


        int v = 0;
        int v = 0;
        boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
        boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
@@ -720,7 +722,7 @@ public class StaticLayout extends Layout {
                    // TODO: Support more justification mode, e.g. letter spacing, stretching.
                    // TODO: Support more justification mode, e.g. letter spacing, stretching.
                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE);
                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE);
            if (mLeftIndents != null || mRightIndents != null) {
            if (mLeftIndents != null || mRightIndents != null) {
                // TODO(raph) performance: it would be better to do this once per layout rather
                // TODO(performance): it would be better to do this once per layout rather
                // than once per paragraph, but that would require a change to the native
                // than once per paragraph, but that would require a change to the native
                // interface.
                // interface.
                int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
                int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
@@ -807,7 +809,7 @@ public class StaticLayout extends Layout {
                            width += widths[j];
                            width += widths[j];
                        }
                        }
                    }
                    }
                    flag |= flags[i] & TAB_MASK;
                    flag |= flags[i] & TAB_MASK; // XXX May need to also have starting hyphen edit
                }
                }
                // Treat the last line and overflowed lines as a single line.
                // Treat the last line and overflowed lines as a single line.
                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
@@ -909,17 +911,16 @@ public class StaticLayout extends Layout {
        }
        }
    }
    }


    private int out(CharSequence text, int start, int end,
    // The parameters that are not changed in the method are marked as final to make the code
                      int above, int below, int top, int bottom, int v,
    // easier to understand.
                      float spacingmult, float spacingadd,
    private int out(final CharSequence text, final int start, final int end, int above, int below,
                      LineHeightSpan[] chooseHt, int[] chooseHtv,
            int top, int bottom, int v, final float spacingmult, final float spacingadd,
                      Paint.FontMetricsInt fm, int flags,
            final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
                      boolean needMultiply, byte[] chdirs, int dir,
            final int flags, final boolean needMultiply, final byte[] chdirs, final int dir,
                      boolean easy, int bufEnd, boolean includePad,
            final boolean easy, final int bufEnd, final boolean includePad, final boolean trackPad,
                      boolean trackPad, boolean addLastLineLineSpacing, char[] chs,
            final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
                      float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
            final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
                      float ellipsisWidth, float textWidth,
            final float textWidth, final TextPaint paint, final boolean moreChars) {
                      TextPaint paint, boolean moreChars) {
        final int j = mLineCount;
        final int j = mLineCount;
        final int off = j * mColumns;
        final int off = j * mColumns;
        final int want = off + mColumns + TOP;
        final int want = off + mColumns + TOP;
@@ -939,30 +940,30 @@ public class StaticLayout extends Layout {
            mLineDirections = grow;
            mLineDirections = grow;
        }
        }


        if (chooseHt != null) {
        lines[off + START] = start;
            fm.ascent = above;
        lines[off + TOP] = v;
            fm.descent = below;
            fm.top = top;
            fm.bottom = bottom;


            for (int i = 0; i < chooseHt.length; i++) {
        // Information about hyphenation, tabs, and directions are needed for determining
                if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
        // ellipsization, so the values should be assigned before ellipsization.
                    ((LineHeightSpan.WithDensity) chooseHt[i]).
                        chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);


                } else {
        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
                    chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
        // one bit for start field
                }
        lines[off + TAB] |= flags & TAB_MASK;
            }
        lines[off + HYPHEN] = flags;


            above = fm.ascent;
        lines[off + DIR] |= dir << DIR_SHIFT;
            below = fm.descent;
        // easy means all chars < the first RTL, so no emoji, no nothing
            top = fm.top;
        // XXX a run with no text or all spaces is easy but might be an empty
            bottom = fm.bottom;
        // RTL paragraph.  Make sure easy is false if this is the case.
        if (easy) {
            mLineDirections[j] = DIRS_ALL_LEFT_TO_RIGHT;
        } else {
            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
                    start - widthStart, end - start);
        }
        }


        boolean firstLine = (j == 0);
        final boolean firstLine = (j == 0);
        boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
        final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);


        if (ellipsize != null) {
        if (ellipsize != null) {
            // If there is only one line, then do any type of ellipsis except when it is MARQUEE
            // If there is only one line, then do any type of ellipsis except when it is MARQUEE
@@ -975,9 +976,9 @@ public class StaticLayout extends Layout {
                    (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                    (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
                            ellipsize == TextUtils.TruncateAt.END);
                            ellipsize == TextUtils.TruncateAt.END);
            if (doEllipsis) {
            if (doEllipsis) {
                calculateEllipsis(start, end, widths, widthStart,
                calculateEllipsis(text, start, end, widths, widthStart,
                        ellipsisWidth, ellipsize, j,
                        ellipsisWidth - getTotalInsets(j), ellipsize, j,
                        textWidth, paint, forceEllipsis);
                        textWidth, paint, forceEllipsis, dir);
            }
            }
        }
        }


@@ -996,6 +997,28 @@ public class StaticLayout extends Layout {
            }
            }
        }
        }


        if (chooseHt != null) {
            fm.ascent = above;
            fm.descent = below;
            fm.top = top;
            fm.bottom = bottom;

            for (int i = 0; i < chooseHt.length; i++) {
                if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
                    ((LineHeightSpan.WithDensity) chooseHt[i])
                        .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);

                } else {
                    chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
                }
            }

            above = fm.ascent;
            below = fm.descent;
            top = fm.top;
            bottom = fm.bottom;
        }

        if (firstLine) {
        if (firstLine) {
            if (trackPad) {
            if (trackPad) {
                mTopPadding = top - above;
                mTopPadding = top - above;
@@ -1006,8 +1029,6 @@ public class StaticLayout extends Layout {
            }
            }
        }
        }


        int extra;

        if (lastLine) {
        if (lastLine) {
            if (trackPad) {
            if (trackPad) {
                mBottomPadding = bottom - below;
                mBottomPadding = bottom - below;
@@ -1018,8 +1039,9 @@ public class StaticLayout extends Layout {
            }
            }
        }
        }


        final int extra;
        if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
        if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
            double ex = (below - above) * (spacingmult - 1) + spacingadd;
            final double ex = (below - above) * (spacingmult - 1) + spacingadd;
            if (ex >= 0) {
            if (ex >= 0) {
                extra = (int)(ex + EXTRA_ROUNDING);
                extra = (int)(ex + EXTRA_ROUNDING);
            } else {
            } else {
@@ -1029,8 +1051,6 @@ public class StaticLayout extends Layout {
            extra = 0;
            extra = 0;
        }
        }


        lines[off + START] = start;
        lines[off + TOP] = v;
        lines[off + DESCENT] = below + extra;
        lines[off + DESCENT] = below + extra;
        lines[off + EXTRA] = extra;
        lines[off + EXTRA] = extra;


@@ -1038,7 +1058,7 @@ public class StaticLayout extends Layout {
        // store the height as if it was ellipsized
        // store the height as if it was ellipsized
        if (!mEllipsized && currentLineIsTheLastVisibleOne) {
        if (!mEllipsized && currentLineIsTheLastVisibleOne) {
            // below calculation as if it was the last line
            // below calculation as if it was the last line
            int maxLineBelow = includePad ? bottom : below;
            final int maxLineBelow = includePad ? bottom : below;
            // similar to the calculation of v below, without the extra.
            // similar to the calculation of v below, without the extra.
            mMaxLineHeight = v + (maxLineBelow - above);
            mMaxLineHeight = v + (maxLineBelow - above);
        }
        }
@@ -1047,33 +1067,13 @@ public class StaticLayout extends Layout {
        lines[off + mColumns + START] = end;
        lines[off + mColumns + START] = end;
        lines[off + mColumns + TOP] = v;
        lines[off + mColumns + TOP] = v;


        // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
        // one bit for start field
        lines[off + TAB] |= flags & TAB_MASK;
        lines[off + HYPHEN] = flags;

        lines[off + DIR] |= dir << DIR_SHIFT;
        Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
        // easy means all chars < the first RTL, so no emoji, no nothing
        // XXX a run with no text or all spaces is easy but might be an empty
        // RTL paragraph.  Make sure easy is false if this is the case.
        if (easy) {
            mLineDirections[j] = linedirs;
        } else {
            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
                    start - widthStart, end - start);
        }

        mLineCount++;
        mLineCount++;
        return v;
        return v;
    }
    }


    private void calculateEllipsis(int lineStart, int lineEnd,
    private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
                                   float[] widths, int widthStart,
            int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
                                   float avail, TextUtils.TruncateAt where,
            TextPaint paint, boolean forceEllipsis, int dir) {
                                   int line, float textWidth, TextPaint paint,
                                   boolean forceEllipsis) {
        avail -= getTotalInsets(line);
        if (textWidth <= avail && !forceEllipsis) {
        if (textWidth <= avail && !forceEllipsis) {
            // Everything fits!
            // Everything fits!
            mLines[mColumns * line + ELLIPSIS_START] = 0;
            mLines[mColumns * line + ELLIPSIS_START] = 0;
@@ -1081,11 +1081,53 @@ public class StaticLayout extends Layout {
            return;
            return;
        }
        }


        float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
        float tempAvail = avail;
        int ellipsisStart = 0;
        int numberOfTries = 0;
        int ellipsisCount = 0;
        boolean lineFits = false;
        int len = lineEnd - lineStart;
        mWorkPaint.set(paint);
        do {
            final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
                    widthStart, tempAvail, where, line, textWidth, mWorkPaint, forceEllipsis, dir);
            if (ellipsizedWidth <= avail) {
                lineFits = true;
            } else {
                numberOfTries++;
                if (numberOfTries > 10) {
                    // If the text still doesn't fit after ten tries, assume it will never fit and
                    // ellipsize it all.
                    mLines[mColumns * line + ELLIPSIS_START] = 0;
                    mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
                    lineFits = true;
                } else {
                    // Some side effect of ellipsization has caused the text to go over the
                    // available width. Let's make the available width shorter by exactly that
                    // amount and retry.
                    tempAvail -= ellipsizedWidth - avail;
                }
            }
        } while (!lineFits);
        mEllipsized = true;
    }


    // Returns the width of the ellipsized line which in some rare cases can actually be larger
    // than 'avail' (due to kerning or other context-based effect of replacement of text by
    // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
    // returns 0 so the caller can stop iterating.
    //
    // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
    // should not be accessed while the method is running.
    private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
            int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
            TextPaint paint, boolean forceEllipsis, int dir) {
        final int savedHyphenEdit = paint.getHyphenEdit();
        paint.setHyphenEdit(0);
        final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
        final int ellipsisStart;
        final int ellipsisCount;
        final int len = lineEnd - lineStart;
        final int offset = lineStart - widthStart;

        int hyphen = getHyphen(line);
        // We only support start ellipsis on a single line
        // We only support start ellipsis on a single line
        if (where == TextUtils.TruncateAt.START) {
        if (where == TextUtils.TruncateAt.START) {
            if (mMaximumVisibleLineCount == 1) {
            if (mMaximumVisibleLineCount == 1) {
@@ -1093,9 +1135,9 @@ public class StaticLayout extends Layout {
                int i;
                int i;


                for (i = len; i > 0; i--) {
                for (i = len; i > 0; i--) {
                    float w = widths[i - 1 + lineStart - widthStart];
                    final float w = widths[i - 1 + offset];
                    if (w + sum + ellipsisWidth > avail) {
                    if (w + sum + ellipsisWidth > avail) {
                        while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
                        while (i < len && widths[i + offset] == 0.0f) {
                            i++;
                            i++;
                        }
                        }
                        break;
                        break;
@@ -1106,9 +1148,13 @@ public class StaticLayout extends Layout {


                ellipsisStart = 0;
                ellipsisStart = 0;
                ellipsisCount = i;
                ellipsisCount = i;
                // Strip the potential hyphenation at beginning of line.
                hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
            } else {
            } else {
                ellipsisStart = 0;
                ellipsisCount = 0;
                if (Log.isLoggable(TAG, Log.WARN)) {
                if (Log.isLoggable(TAG, Log.WARN)) {
                    Log.w(TAG, "Start Ellipsis only supported with one line");
                    Log.w(TAG, "Start ellipsis only supported with one line");
                }
                }
            }
            }
        } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
        } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
@@ -1117,7 +1163,7 @@ public class StaticLayout extends Layout {
            int i;
            int i;


            for (i = 0; i < len; i++) {
            for (i = 0; i < len; i++) {
                float w = widths[i + lineStart - widthStart];
                final float w = widths[i + offset];


                if (w + sum + ellipsisWidth > avail) {
                if (w + sum + ellipsisWidth > avail) {
                    break;
                    break;
@@ -1126,24 +1172,27 @@ public class StaticLayout extends Layout {
                sum += w;
                sum += w;
            }
            }


            ellipsisStart = i;
            if (forceEllipsis && i == len && len > 0) {
            ellipsisCount = len - i;
            if (forceEllipsis && ellipsisCount == 0 && len > 0) {
                ellipsisStart = len - 1;
                ellipsisStart = len - 1;
                ellipsisCount = 1;
                ellipsisCount = 1;
            }
            } else {
            } else {
            // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
                ellipsisStart = i;
                ellipsisCount = len - i;
            }
            // Strip the potential hyphenation at end of line.
            hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
        } else { // where = TextUtils.TruncateAt.MIDDLE
            // We only support middle ellipsis on a single line.
            if (mMaximumVisibleLineCount == 1) {
            if (mMaximumVisibleLineCount == 1) {
                float lsum = 0, rsum = 0;
                float lsum = 0, rsum = 0;
                int left = 0, right = len;
                int left = 0, right = len;


                float ravail = (avail - ellipsisWidth) / 2;
                final float ravail = (avail - ellipsisWidth) / 2;
                for (right = len; right > 0; right--) {
                for (right = len; right > 0; right--) {
                    float w = widths[right - 1 + lineStart - widthStart];
                    final float w = widths[right - 1 + offset];


                    if (w + rsum > ravail) {
                    if (w + rsum > ravail) {
                        while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
                        while (right < len && widths[right + offset] == 0.0f) {
                            right++;
                            right++;
                        }
                        }
                        break;
                        break;
@@ -1151,9 +1200,9 @@ public class StaticLayout extends Layout {
                    rsum += w;
                    rsum += w;
                }
                }


                float lavail = avail - ellipsisWidth - rsum;
                final float lavail = avail - ellipsisWidth - rsum;
                for (left = 0; left < right; left++) {
                for (left = 0; left < right; left++) {
                    float w = widths[left + lineStart - widthStart];
                    final float w = widths[left + offset];


                    if (w + lsum > lavail) {
                    if (w + lsum > lavail) {
                        break;
                        break;
@@ -1165,14 +1214,53 @@ public class StaticLayout extends Layout {
                ellipsisStart = left;
                ellipsisStart = left;
                ellipsisCount = right - left;
                ellipsisCount = right - left;
            } else {
            } else {
                ellipsisStart = 0;
                ellipsisCount = 0;
                if (Log.isLoggable(TAG, Log.WARN)) {
                if (Log.isLoggable(TAG, Log.WARN)) {
                    Log.w(TAG, "Middle Ellipsis only supported with one line");
                    Log.w(TAG, "Middle ellipsis only supported with one line");
                }
                }
            }
            }
        }
        }
        mEllipsized = true;
        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
        mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
        mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;

        if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
            // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
            return 0.0f;
        }

        final boolean isSpanned = text instanceof Spanned;
        final Ellipsizer ellipsizedText = isSpanned
                        ? new SpannedEllipsizer(text)
                        : new Ellipsizer(text);
        ellipsizedText.mLayout = this;
        ellipsizedText.mMethod = where;

        final boolean hasTabs = getLineContainsTab(line);
        final TabStops tabStops;
        if (hasTabs && isSpanned) {
            final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
                    lineEnd, TabStopSpan.class);
            if (tabs.length == 0) {
                tabStops = null;
            } else {
                tabStops = new TabStops(TAB_INCREMENT, tabs);
            }
        } else {
            tabStops = null;
        }
        paint.setHyphenEdit(hyphen);
        final TextLine textline = TextLine.obtain();
        textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
                hasTabs, tabStops);
        // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
        // converts it to an actual width. Note that we don't want to use the absolute value,
        // since we may actually have glyphs with negative advances, which by definition always
        // fit.
        final float ellipsizedWidth = textline.metrics(null) * dir;
        TextLine.recycle(textline);
        paint.setHyphenEdit(savedHyphenEdit);
        return ellipsizedWidth;
    }
    }


    private float getTotalInsets(int line) {
    private float getTotalInsets(int line) {
@@ -1403,7 +1491,9 @@ public class StaticLayout extends Layout {
     * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
     * The value is the same as getLineTop(maxLines) for ellipsized version where structurally no
     * more than maxLines is contained.
     * more than maxLines is contained.
     */
     */
    private int mMaxLineHeight = -1;
    private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;

    private TextPaint mWorkPaint = new TextPaint();


    private static final int COLUMNS_NORMAL = 5;
    private static final int COLUMNS_NORMAL = 5;
    private static final int COLUMNS_ELLIPSIZE = 7;
    private static final int COLUMNS_ELLIPSIZE = 7;
@@ -1432,6 +1522,8 @@ public class StaticLayout extends Layout {


    private static final double EXTRA_ROUNDING = 0.5;
    private static final double EXTRA_ROUNDING = 0.5;


    private static final int DEFAULT_MAX_LINE_HEIGHT = -1;

    // This is used to return three arrays from a single JNI call when
    // This is used to return three arrays from a single JNI call when
    // performing line breaking
    // performing line breaking
    /*package*/ static class LineBreaks {
    /*package*/ static class LineBreaks {
+114 −75

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Original line Diff line number Diff line
@@ -2747,7 +2747,7 @@ public class Paint {
     * @param offset index of caret position
     * @param offset index of caret position
     * @return width measurement between start and offset
     * @return width measurement between start and offset
     */
     */
    public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
    public float getRunAdvance(@NonNull CharSequence text, int start, int end, int contextStart,
            int contextEnd, boolean isRtl, int offset) {
            int contextEnd, boolean isRtl, int offset) {
        if (text == null) {
        if (text == null) {
            throw new IllegalArgumentException("text cannot be null");
            throw new IllegalArgumentException("text cannot be null");