Loading core/java/android/text/Layout.java +12 −20 Original line number Diff line number Diff line Loading @@ -2036,35 +2036,27 @@ public abstract class Layout { } } private char getEllipsisChar(TextUtils.TruncateAt method) { return (method == TextUtils.TruncateAt.END_SMALL) ? TextUtils.ELLIPSIS_TWO_DOTS[0] : TextUtils.ELLIPSIS_NORMAL[0]; } private void ellipsize(int start, int end, int line, char[] dest, int destoff, TextUtils.TruncateAt method) { int ellipsisCount = getEllipsisCount(line); final int ellipsisCount = getEllipsisCount(line); if (ellipsisCount == 0) { return; } final int ellipsisStart = getEllipsisStart(line); final int lineStart = getLineStart(line); int ellipsisStart = getEllipsisStart(line); int linestart = getLineStart(line); for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) { char c; if (i == ellipsisStart) { c = getEllipsisChar(method); // ellipsis final String ellipsisString = TextUtils.getEllipsisString(method); final int ellipsisStringLen = ellipsisString.length(); for (int i = 0; i < ellipsisCount; i++) { final char c; if (i < ellipsisStringLen && ellipsisCount <= ellipsisStringLen) { c = ellipsisString.charAt(i); } else { c = '\uFEFF'; // 0-width space c = TextUtils.ELLIPSIS_FILLER; } int a = i + linestart; if (a >= start && a < end) { final int a = i + ellipsisStart + lineStart; if (start <= a && a < end) { dest[destoff + a - start] = c; } } Loading core/java/android/text/StaticLayout.java +3 −5 Original line number Diff line number Diff line Loading @@ -781,8 +781,8 @@ public class StaticLayout extends Layout { && (ellipsize == TextUtils.TruncateAt.END || (mMaximumVisibleLineCount == 1 && ellipsize != TextUtils.TruncateAt.MARQUEE)); if (remainingLineCount > 0 && remainingLineCount < breakCount && ellipsisMayBeApplied) { if (0 < remainingLineCount && remainingLineCount < breakCount && ellipsisMayBeApplied) { // Calculate width and flag. float width = 0; int flag = 0; Loading Loading @@ -1053,9 +1053,7 @@ public class StaticLayout extends Layout { return; } float ellipsisWidth = paint.measureText( (where == TextUtils.TruncateAt.END_SMALL) ? TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1); float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where)); int ellipsisStart = 0; int ellipsisCount = 0; int len = lineEnd - lineStart; Loading core/java/android/text/TextUtils.java +24 −15 Original line number Diff line number Diff line Loading @@ -78,12 +78,21 @@ import java.util.regex.Pattern; public class TextUtils { private static final String TAG = "TextUtils"; /* package */ static final char[] ELLIPSIS_NORMAL = { '\u2026' }; // this is "..." // Zero-width character used to fill ellipsized strings when codepoint lenght must be preserved. /* package */ static final char ELLIPSIS_FILLER = '\uFEFF'; // ZERO WIDTH NO-BREAK SPACE // TODO: Based on CLDR data, these need to be localized for Dzongkha (dz) and perhaps // Hong Kong Traditional Chinese (zh-Hant-HK), but that may need to depend on the actual word // being ellipsized and not the locale. private static final String ELLIPSIS_NORMAL = "\u2026"; // HORIZONTAL ELLIPSIS (…) private static final String ELLIPSIS_TWO_DOTS = "\u2025"; // TWO DOT LEADER (‥) /** {@hide} */ public static final String ELLIPSIS_STRING = new String(ELLIPSIS_NORMAL); @NonNull public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) { return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL; } /* package */ static final char[] ELLIPSIS_TWO_DOTS = { '\u2025' }; // this is ".." private static final String ELLIPSIS_TWO_DOTS_STRING = new String(ELLIPSIS_TWO_DOTS); private TextUtils() { /* cannot be instantiated */ } Loading Loading @@ -1200,10 +1209,10 @@ public class TextUtils { TextPaint paint, float avail, TruncateAt where, boolean preserveLength, EllipsizeCallback callback) { @Nullable EllipsizeCallback callback) { return ellipsize(text, paint, avail, where, preserveLength, callback, TextDirectionHeuristics.FIRSTSTRONG_LTR, (where == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS_STRING : ELLIPSIS_STRING); getEllipsisString(where)); } /** Loading @@ -1223,7 +1232,7 @@ public class TextUtils { TextPaint paint, float avail, TruncateAt where, boolean preserveLength, EllipsizeCallback callback, @Nullable EllipsizeCallback callback, TextDirectionHeuristic textDir, String ellipsis) { int len = text.length(); Loading Loading @@ -1266,13 +1275,15 @@ public class TextUtils { char[] buf = mt.mChars; Spanned sp = text instanceof Spanned ? (Spanned) text : null; int remaining = len - (right - left); final int removed = right - left; final int remaining = len - removed; if (preserveLength) { if (remaining > 0) { // else eliminate the ellipsis too buf[left++] = ellipsis.charAt(0); } if (remaining > 0 && removed >= ellipsis.length()) { ellipsis.getChars(0, ellipsis.length(), buf, left); left += ellipsis.length(); } // else skip the ellipsis for (int i = left; i < right; i++) { buf[i] = ZWNBS_CHAR; buf[i] = ELLIPSIS_FILLER; } String s = new String(buf, 0, len); if (sp == null) { Loading Loading @@ -1372,7 +1383,7 @@ public class TextUtils { final int remainingElements = totalLen - i - 1; if (remainingElements > 0) { CharSequence morePiece = (res == null) ? ELLIPSIS_STRING : ELLIPSIS_NORMAL : res.getQuantityString(moreId, remainingElements, remainingElements); morePiece = bidiFormatter.unicodeWrap(morePiece); output.append(morePiece); Loading Loading @@ -2078,6 +2089,4 @@ public class TextUtils { private static char[] sTemp = null; private static String[] EMPTY_STRING_ARRAY = new String[]{}; private static final char ZWNBS_CHAR = '\uFEFF'; } core/java/com/android/internal/app/LocaleHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -181,7 +181,7 @@ public class LocaleHelper { // Hong Kong Traditional Chinese (zh_Hant_HK) and Dzongkha (dz). But that has two // problems: it's expensive to extract it, and in case the output string becomes // automatically ellipsized, it can result in weird output. localeNames[maxLocales] = TextUtils.ELLIPSIS_STRING; localeNames[maxLocales] = TextUtils.getEllipsisString(TextUtils.TruncateAt.END); } ListFormatter lfn = ListFormatter.getInstance(dispLocale); Loading core/tests/coretests/src/android/text/TextUtilsTest.java +60 −0 Original line number Diff line number Diff line Loading @@ -340,6 +340,66 @@ public class TextUtilsTest { } } @Test public void testEllipsize_multiCodepoint() { final TextPaint paint = new TextPaint(); final float wordWidth = paint.measureText("MMMM"); // Establish the ground rules first, for single-codepoint cases. final String ellipsis = "."; // one full stop character assertEquals( "MM.\uFEFF", TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, TextUtils.TruncateAt.END, true /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, ellipsis)); assertEquals( "MM.", TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, TextUtils.TruncateAt.END, false /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, ellipsis)); assertEquals( "M.", TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, TextUtils.TruncateAt.END, true /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, ellipsis)); assertEquals( "M.", TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, TextUtils.TruncateAt.END, false /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, ellipsis)); // Now check the differences for multi-codepoint ellipsis. final String longEllipsis = ".."; // two full stop characters assertEquals( "MM..", TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, TextUtils.TruncateAt.END, true /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, longEllipsis)); assertEquals( "MM..", TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, TextUtils.TruncateAt.END, false /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, longEllipsis)); assertEquals( "M\uFEFF", TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, TextUtils.TruncateAt.END, true /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, longEllipsis)); assertEquals( "M..", TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, TextUtils.TruncateAt.END, false /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, longEllipsis)); } @Test public void testDelimitedStringContains() { assertFalse(TextUtils.delimitedStringContains("", ',', null)); Loading Loading
core/java/android/text/Layout.java +12 −20 Original line number Diff line number Diff line Loading @@ -2036,35 +2036,27 @@ public abstract class Layout { } } private char getEllipsisChar(TextUtils.TruncateAt method) { return (method == TextUtils.TruncateAt.END_SMALL) ? TextUtils.ELLIPSIS_TWO_DOTS[0] : TextUtils.ELLIPSIS_NORMAL[0]; } private void ellipsize(int start, int end, int line, char[] dest, int destoff, TextUtils.TruncateAt method) { int ellipsisCount = getEllipsisCount(line); final int ellipsisCount = getEllipsisCount(line); if (ellipsisCount == 0) { return; } final int ellipsisStart = getEllipsisStart(line); final int lineStart = getLineStart(line); int ellipsisStart = getEllipsisStart(line); int linestart = getLineStart(line); for (int i = ellipsisStart; i < ellipsisStart + ellipsisCount; i++) { char c; if (i == ellipsisStart) { c = getEllipsisChar(method); // ellipsis final String ellipsisString = TextUtils.getEllipsisString(method); final int ellipsisStringLen = ellipsisString.length(); for (int i = 0; i < ellipsisCount; i++) { final char c; if (i < ellipsisStringLen && ellipsisCount <= ellipsisStringLen) { c = ellipsisString.charAt(i); } else { c = '\uFEFF'; // 0-width space c = TextUtils.ELLIPSIS_FILLER; } int a = i + linestart; if (a >= start && a < end) { final int a = i + ellipsisStart + lineStart; if (start <= a && a < end) { dest[destoff + a - start] = c; } } Loading
core/java/android/text/StaticLayout.java +3 −5 Original line number Diff line number Diff line Loading @@ -781,8 +781,8 @@ public class StaticLayout extends Layout { && (ellipsize == TextUtils.TruncateAt.END || (mMaximumVisibleLineCount == 1 && ellipsize != TextUtils.TruncateAt.MARQUEE)); if (remainingLineCount > 0 && remainingLineCount < breakCount && ellipsisMayBeApplied) { if (0 < remainingLineCount && remainingLineCount < breakCount && ellipsisMayBeApplied) { // Calculate width and flag. float width = 0; int flag = 0; Loading Loading @@ -1053,9 +1053,7 @@ public class StaticLayout extends Layout { return; } float ellipsisWidth = paint.measureText( (where == TextUtils.TruncateAt.END_SMALL) ? TextUtils.ELLIPSIS_TWO_DOTS : TextUtils.ELLIPSIS_NORMAL, 0, 1); float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where)); int ellipsisStart = 0; int ellipsisCount = 0; int len = lineEnd - lineStart; Loading
core/java/android/text/TextUtils.java +24 −15 Original line number Diff line number Diff line Loading @@ -78,12 +78,21 @@ import java.util.regex.Pattern; public class TextUtils { private static final String TAG = "TextUtils"; /* package */ static final char[] ELLIPSIS_NORMAL = { '\u2026' }; // this is "..." // Zero-width character used to fill ellipsized strings when codepoint lenght must be preserved. /* package */ static final char ELLIPSIS_FILLER = '\uFEFF'; // ZERO WIDTH NO-BREAK SPACE // TODO: Based on CLDR data, these need to be localized for Dzongkha (dz) and perhaps // Hong Kong Traditional Chinese (zh-Hant-HK), but that may need to depend on the actual word // being ellipsized and not the locale. private static final String ELLIPSIS_NORMAL = "\u2026"; // HORIZONTAL ELLIPSIS (…) private static final String ELLIPSIS_TWO_DOTS = "\u2025"; // TWO DOT LEADER (‥) /** {@hide} */ public static final String ELLIPSIS_STRING = new String(ELLIPSIS_NORMAL); @NonNull public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) { return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL; } /* package */ static final char[] ELLIPSIS_TWO_DOTS = { '\u2025' }; // this is ".." private static final String ELLIPSIS_TWO_DOTS_STRING = new String(ELLIPSIS_TWO_DOTS); private TextUtils() { /* cannot be instantiated */ } Loading Loading @@ -1200,10 +1209,10 @@ public class TextUtils { TextPaint paint, float avail, TruncateAt where, boolean preserveLength, EllipsizeCallback callback) { @Nullable EllipsizeCallback callback) { return ellipsize(text, paint, avail, where, preserveLength, callback, TextDirectionHeuristics.FIRSTSTRONG_LTR, (where == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS_STRING : ELLIPSIS_STRING); getEllipsisString(where)); } /** Loading @@ -1223,7 +1232,7 @@ public class TextUtils { TextPaint paint, float avail, TruncateAt where, boolean preserveLength, EllipsizeCallback callback, @Nullable EllipsizeCallback callback, TextDirectionHeuristic textDir, String ellipsis) { int len = text.length(); Loading Loading @@ -1266,13 +1275,15 @@ public class TextUtils { char[] buf = mt.mChars; Spanned sp = text instanceof Spanned ? (Spanned) text : null; int remaining = len - (right - left); final int removed = right - left; final int remaining = len - removed; if (preserveLength) { if (remaining > 0) { // else eliminate the ellipsis too buf[left++] = ellipsis.charAt(0); } if (remaining > 0 && removed >= ellipsis.length()) { ellipsis.getChars(0, ellipsis.length(), buf, left); left += ellipsis.length(); } // else skip the ellipsis for (int i = left; i < right; i++) { buf[i] = ZWNBS_CHAR; buf[i] = ELLIPSIS_FILLER; } String s = new String(buf, 0, len); if (sp == null) { Loading Loading @@ -1372,7 +1383,7 @@ public class TextUtils { final int remainingElements = totalLen - i - 1; if (remainingElements > 0) { CharSequence morePiece = (res == null) ? ELLIPSIS_STRING : ELLIPSIS_NORMAL : res.getQuantityString(moreId, remainingElements, remainingElements); morePiece = bidiFormatter.unicodeWrap(morePiece); output.append(morePiece); Loading Loading @@ -2078,6 +2089,4 @@ public class TextUtils { private static char[] sTemp = null; private static String[] EMPTY_STRING_ARRAY = new String[]{}; private static final char ZWNBS_CHAR = '\uFEFF'; }
core/java/com/android/internal/app/LocaleHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -181,7 +181,7 @@ public class LocaleHelper { // Hong Kong Traditional Chinese (zh_Hant_HK) and Dzongkha (dz). But that has two // problems: it's expensive to extract it, and in case the output string becomes // automatically ellipsized, it can result in weird output. localeNames[maxLocales] = TextUtils.ELLIPSIS_STRING; localeNames[maxLocales] = TextUtils.getEllipsisString(TextUtils.TruncateAt.END); } ListFormatter lfn = ListFormatter.getInstance(dispLocale); Loading
core/tests/coretests/src/android/text/TextUtilsTest.java +60 −0 Original line number Diff line number Diff line Loading @@ -340,6 +340,66 @@ public class TextUtilsTest { } } @Test public void testEllipsize_multiCodepoint() { final TextPaint paint = new TextPaint(); final float wordWidth = paint.measureText("MMMM"); // Establish the ground rules first, for single-codepoint cases. final String ellipsis = "."; // one full stop character assertEquals( "MM.\uFEFF", TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, TextUtils.TruncateAt.END, true /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, ellipsis)); assertEquals( "MM.", TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, TextUtils.TruncateAt.END, false /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, ellipsis)); assertEquals( "M.", TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, TextUtils.TruncateAt.END, true /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, ellipsis)); assertEquals( "M.", TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, TextUtils.TruncateAt.END, false /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, ellipsis)); // Now check the differences for multi-codepoint ellipsis. final String longEllipsis = ".."; // two full stop characters assertEquals( "MM..", TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, TextUtils.TruncateAt.END, true /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, longEllipsis)); assertEquals( "MM..", TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, TextUtils.TruncateAt.END, false /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, longEllipsis)); assertEquals( "M\uFEFF", TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, TextUtils.TruncateAt.END, true /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, longEllipsis)); assertEquals( "M..", TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, TextUtils.TruncateAt.END, false /* preserve length */, null /* no callback */, TextDirectionHeuristics.LTR, longEllipsis)); } @Test public void testDelimitedStringContains() { assertFalse(TextUtils.delimitedStringContains("", ',', null)); Loading