Loading core/java/android/text/Layout.java +1 −3 Original line number Diff line number Diff line Loading @@ -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; Loading core/java/android/text/StaticLayout.java +48 −110 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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]; Loading Loading @@ -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); Loading @@ -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); } } Loading Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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; Loading @@ -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 || Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading core/java/android/text/TextUtils.java +75 −95 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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); } Loading @@ -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, Loading @@ -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); Loading @@ -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); } } } Loading Loading @@ -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, Loading Loading
core/java/android/text/Layout.java +1 −3 Original line number Diff line number Diff line Loading @@ -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; Loading
core/java/android/text/StaticLayout.java +48 −110 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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]; Loading Loading @@ -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); Loading @@ -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); } } Loading Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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; Loading @@ -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 || Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading
core/java/android/text/TextUtils.java +75 −95 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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); } Loading @@ -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, Loading @@ -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); Loading @@ -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); } } } Loading Loading @@ -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, Loading