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

Commit 200ae379 authored by Roozbeh Pournader's avatar Roozbeh Pournader Committed by Android (Google) Code Review
Browse files

Merge "Revert "Make ellipsize retry if text doesn't fit""

parents b0789926 7f8dcaaa
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -2059,11 +2059,9 @@ public abstract class Layout {

        final String ellipsisString = TextUtils.getEllipsisString(method);
        final int ellipsisStringLen = ellipsisString.length();
        // Use the ellipsis string only if there are that at least as many characters to replace.
        final boolean useEllipsisString = ellipsisCount >= ellipsisStringLen;
        for (int i = 0; i < ellipsisCount; i++) {
            final char c;
            if (useEllipsisString && i < ellipsisStringLen) {
            if (i < ellipsisStringLen && ellipsisCount <= ellipsisStringLen) {
                c = ellipsisString.charAt(i);
            } else {
                c = TextUtils.ELLIPSIS_FILLER;
+48 −110
Original line number Diff line number Diff line
@@ -720,7 +720,7 @@ public class StaticLayout extends Layout {
                    // TODO: Support more justification mode, e.g. letter spacing, stretching.
                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE);
            if (mLeftIndents != null || mRightIndents != null) {
                // TODO(performance): it would be better to do this once per layout rather
                // TODO(raph) 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
                // interface.
                int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
@@ -807,7 +807,7 @@ public class StaticLayout extends Layout {
                            width += widths[j];
                        }
                    }
                    flag |= flags[i] & TAB_MASK; // XXX May need to also have starting hyphen edit
                    flag |= flags[i] & TAB_MASK;
                }
                // Treat the last line and overflowed lines as a single line.
                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
@@ -961,25 +961,6 @@ public class StaticLayout extends Layout {
            bottom = fm.bottom;
        }

        // Information about hyphenation, tabs, and directions are needed for determining
        // ellipsization, so the values should be assigned before ellipsization.

        // 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;
        // 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] = DIRS_ALL_LEFT_TO_RIGHT;
        } else {
            mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
                    start - widthStart, end - start);
        }

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

@@ -995,8 +976,8 @@ public class StaticLayout extends Layout {
                            ellipsize == TextUtils.TruncateAt.END);
            if (doEllipsis) {
                calculateEllipsis(start, end, widths, widthStart,
                        ellipsisWidth - getTotalInsets(j), ellipsize, j,
                        textWidth, paint, forceEllipsis, dir);
                        ellipsisWidth, ellipsize, j,
                        textWidth, paint, forceEllipsis);
            }
        }

@@ -1048,7 +1029,7 @@ public class StaticLayout extends Layout {
            extra = 0;
        }

        lines[off + START] |= start;
        lines[off + START] = start;
        lines[off + TOP] = v;
        lines[off + DESCENT] = below + extra;
        lines[off + EXTRA] = extra;
@@ -1066,13 +1047,33 @@ public class StaticLayout extends Layout {
        lines[off + mColumns + START] = end;
        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++;
        return v;
    }

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

        float tempAvail = avail;
        while (true) {
            float ellipsizedWidth = guessEllipsis(lineStart, lineEnd, widths, widthStart, tempAvail,
                    where, line, textWidth, paint, forceEllipsis, dir);
            if (ellipsizedWidth <= avail) {
                mEllipsized = true;
                return; // The ellipsized line fits.
            } 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;
            }
        }
    }

    // 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.
    private float guessEllipsis(int lineStart, int lineEnd, float[] widths, int widthStart,
            float avail, TextUtils.TruncateAt where, int line, float textWidth, TextPaint paint,
            boolean forceEllipsis, int dir) {
        final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
        final int ellipsisStart;
        final int ellipsisCount;
        final int len = lineEnd - lineStart;
        final int offset = lineStart - widthStart;
        float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
        int ellipsisStart = 0;
        int ellipsisCount = 0;
        int len = lineEnd - lineStart;

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

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

                ellipsisStart = 0;
                ellipsisCount = i;
                // Strip the potential hyphenation at beginning of line.
                hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
            } else {
                ellipsisStart = 0;
                ellipsisCount = 0;
                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 ||
@@ -1144,7 +1117,7 @@ public class StaticLayout extends Layout {
            int i;

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

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

            if (forceEllipsis && i == len && len > 0) {
                ellipsisStart = len - 1;
                ellipsisCount = 1;
            } else {
            ellipsisStart = i;
            ellipsisCount = len - i;
            if (forceEllipsis && ellipsisCount == 0 && len > 0) {
                ellipsisStart = len - 1;
                ellipsisCount = 1;
            }
            // 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.
        } else {
            // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
            if (mMaximumVisibleLineCount == 1) {
                float lsum = 0, rsum = 0;
                int left = 0, right = len;

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

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

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

                    if (w + lsum > lavail) {
                        break;
@@ -1195,46 +1165,14 @@ public class StaticLayout extends Layout {
                ellipsisStart = left;
                ellipsisCount = right - left;
            } else {
                ellipsisStart = 0;
                ellipsisCount = 0;
                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_COUNT] = ellipsisCount;

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

        final CharSequence text = getText();
        final boolean hasTabs = getLineContainsTab(line);
        final TabStops tabStops;
        if (hasTabs && text instanceof Spanned) {
            final TabStopSpan[] tabs = getParagraphSpans((Spanned) text, lineStart, lineEnd,
                    TabStopSpan.class);
            if (tabs.length == 0) {
                tabStops = null;
            } else {
                tabStops = new TabStops(TAB_INCREMENT, tabs);
            }
        } else {
            tabStops = null;
        }
        final TextLine textline = TextLine.obtain();
        paint.setHyphenEdit(hyphen);
        textline.set(paint,
                text, /* this is an instance of Ellipsizer or SpannedEllipsizer */
                lineStart, lineEnd,
                dir, getLineDirections(line),
                hasTabs, tabStops);
        final float hyphenatedWidth = textline.metrics(null);
        paint.setHyphenEdit(0);
        TextLine.recycle(textline);
        return hyphenatedWidth;
    }

    private float getTotalInsets(int line) {
+75 −95
Original line number Diff line number Diff line
@@ -89,8 +89,8 @@ public class TextUtils {

    /** {@hide} */
    @NonNull
    public static String getEllipsisString(@NonNull TruncateAt method) {
        return (method == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
    public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) {
        return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
    }


@@ -1187,11 +1187,9 @@ public class TextUtils {
     * or, if it does not fit, a truncated
     * copy with ellipsis character added at the specified edge or center.
     */
    @NonNull
    public static CharSequence ellipsize(@NonNull CharSequence text,
                                         @NonNull TextPaint p,
                                         @FloatRange(from = 0.0) float avail,
                                         @NonNull TruncateAt where) {
    public static CharSequence ellipsize(CharSequence text,
                                         TextPaint p,
                                         float avail, TruncateAt where) {
        return ellipsize(text, p, avail, where, false, null);
    }

@@ -1207,11 +1205,9 @@ public class TextUtils {
     * report the start and end of the ellipsized range.  TextDirection
     * is determined by the first strong directional character.
     */
    @NonNull
    public static CharSequence ellipsize(@NonNull CharSequence text,
                                         @NonNull TextPaint paint,
                                         @FloatRange(from = 0.0) float avail,
                                         @NonNull TruncateAt where,
    public static CharSequence ellipsize(CharSequence text,
                                         TextPaint paint,
                                         float avail, TruncateAt where,
                                         boolean preserveLength,
                                         @Nullable EllipsizeCallback callback) {
        return ellipsize(text, paint, avail, where, preserveLength, callback,
@@ -1232,19 +1228,16 @@ public class TextUtils {
     *
     * @hide
     */
    @NonNull
    public static CharSequence ellipsize(@NonNull CharSequence text,
            @NonNull TextPaint paint,
            @FloatRange(from = 0.0) float avail,
            @NonNull TruncateAt where,
    public static CharSequence ellipsize(CharSequence text,
            TextPaint paint,
            float avail, TruncateAt where,
            boolean preserveLength,
            @Nullable EllipsizeCallback callback,
            @NonNull TextDirectionHeuristic textDir,
            @NonNull String ellipsis) {
            TextDirectionHeuristic textDir, String ellipsis) {

        int len = text.length();

        final int len = text.length();
        final MeasuredText mt = MeasuredText.obtain();
        MeasuredText resultMt = null;
        MeasuredText mt = MeasuredText.obtain();
        try {
            float width = setPara(mt, paint, text, 0, text.length(), textDir);

@@ -1252,88 +1245,74 @@ public class TextUtils {
                if (callback != null) {
                    callback.ellipsized(0, 0);
                }

                return text;
            }

            resultMt = MeasuredText.obtain();
            // First estimate of effective width of ellipsis.
            float ellipsisWidth = paint.measureText(ellipsis);
            while (true) { // Repeat until the ellipsized text fits.
                final float remainingWidth = avail - ellipsisWidth;
                final int start, end;
                if (remainingWidth < 0) {
                    // Even the ellipsis can't fit. So it all goes.
                    start = 0;
                    end = len;
            // XXX assumes ellipsis string does not require shaping and
            // is unaffected by style
            float ellipsiswid = paint.measureText(ellipsis);
            avail -= ellipsiswid;

            int left = 0;
            int right = len;
            if (avail < 0) {
                // it all goes
            } else if (where == TruncateAt.START) {
                    start = 0;
                    end = len - mt.breakText(len, false /* backwards */, remainingWidth);
                right = len - mt.breakText(len, false, avail);
            } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
                    start = mt.breakText(len, true /* forwards */, remainingWidth);
                    end = len;
                left = mt.breakText(len, true, avail);
            } else {
                    end = len - mt.breakText(len, false /* backwards */, remainingWidth / 2);
                    start = mt.breakText(end, true /* forwards */,
                            remainingWidth - mt.measure(end, len));
                right = len - mt.breakText(len, false, avail / 2);
                avail -= mt.measure(right, len);
                left = mt.breakText(right, true, avail);
            }

            if (callback != null) {
                callback.ellipsized(left, right);
            }

                final char[] buf = mt.mChars;
                final Spanned sp = text instanceof Spanned ? (Spanned) text : null;
            char[] buf = mt.mChars;
            Spanned sp = text instanceof Spanned ? (Spanned) text : null;

                final CharSequence result;
                final int removed = end - start;
            final int removed = right - left;
            final int remaining = len - removed;
            if (preserveLength) {
                    int pos = start;
                if (remaining > 0 && removed >= ellipsis.length()) {
                        ellipsis.getChars(0, ellipsis.length(), buf, start);
                        pos += ellipsis.length();
                    } // else eliminate the ellipsis
                    while (pos < end) {
                        buf[pos++] = ELLIPSIS_FILLER;
                    ellipsis.getChars(0, ellipsis.length(), buf, left);
                    left += ellipsis.length();
                } // else skip the ellipsis
                for (int i = left; i < right; i++) {
                    buf[i] = ELLIPSIS_FILLER;
                }
                    final String s = new String(buf, 0, len);
                String s = new String(buf, 0, len);
                if (sp == null) {
                        result = s;
                    } else {
                        final SpannableString ss = new SpannableString(s);
                    return s;
                }
                SpannableString ss = new SpannableString(s);
                copySpansFrom(sp, 0, len, Object.class, ss, 0);
                        result = ss;
                return ss;
            }
                } else {

            if (remaining == 0) {
                        result = "";
                    } else if (sp == null) {
                        final StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
                        sb.append(buf, 0, start);
                        sb.append(ellipsis);
                        sb.append(buf, end, len - end);
                        result = sb.toString();
                    } else {
                        final SpannableStringBuilder ssb = new SpannableStringBuilder();
                        ssb.append(text, 0, start);
                        ssb.append(ellipsis);
                        ssb.append(text, end, len);
                        result = ssb;
                    }
                return "";
            }

                width = setPara(resultMt, paint, result, 0, result.length(), textDir);
                if (width <= avail || remaining == 0) { // Text fits or all text is gone.
                    if (callback != null) {
                        callback.ellipsized(start, end);
                    }
                    return result;
                } else {
                    // Adjust the width of the ellipsis by adding the amount 'width' is still over.
                    ellipsisWidth += width - avail;
                }
            if (sp == null) {
                StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
                sb.append(buf, 0, left);
                sb.append(ellipsis);
                sb.append(buf, right, len - right);
                return sb.toString();
            }

            SpannableStringBuilder ssb = new SpannableStringBuilder();
            ssb.append(text, 0, left);
            ssb.append(ellipsis);
            ssb.append(text, right, len);
            return ssb;
        } finally {
            MeasuredText.recycle(mt);
            if (resultMt != null) {
                MeasuredText.recycle(resultMt);
            }
        }
    }

@@ -1364,6 +1343,7 @@ public class TextUtils {
     * @return the formatted CharSequence. If even the shortest sequence (e.g. {@code "A, 11 more"})
     *     doesn't fit, it will return an empty string.
     */

    public static CharSequence listEllipsize(@Nullable Context context,
            @Nullable List<CharSequence> elements, @NonNull String separator,
            @NonNull TextPaint paint, @FloatRange(from=0.0,fromInclusive=false) float avail,