Loading core/java/android/text/Layout.java +3 −1 Original line number Original line Diff line number Diff line Loading @@ -2067,9 +2067,11 @@ public abstract class Layout { final String ellipsisString = TextUtils.getEllipsisString(method); final String ellipsisString = TextUtils.getEllipsisString(method); final int ellipsisStringLen = ellipsisString.length(); 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++) { for (int i = 0; i < ellipsisCount; i++) { final char c; final char c; if (i < ellipsisStringLen && ellipsisCount <= ellipsisStringLen) { if (useEllipsisString && i < ellipsisStringLen) { c = ellipsisString.charAt(i); c = ellipsisString.charAt(i); } else { } else { c = TextUtils.ELLIPSIS_FILLER; c = TextUtils.ELLIPSIS_FILLER; Loading core/java/android/text/StaticLayout.java +166 −86 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -720,7 +720,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; Loading Loading @@ -807,7 +807,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]; Loading Loading @@ -909,17 +909,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; Loading @@ -939,30 +938,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 Loading @@ -975,9 +974,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); } } } } Loading @@ -996,6 +995,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; Loading @@ -1006,8 +1027,6 @@ public class StaticLayout extends Layout { } } } } int extra; if (lastLine) { if (lastLine) { if (trackPad) { if (trackPad) { mBottomPadding = bottom - below; mBottomPadding = bottom - below; Loading @@ -1018,8 +1037,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 { Loading @@ -1029,8 +1049,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; Loading @@ -1038,7 +1056,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); } } Loading @@ -1047,33 +1065,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; Loading @@ -1081,11 +1079,47 @@ 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; do { final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths, widthStart, tempAvail, where, line, textWidth, paint, 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. 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 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) { Loading @@ -1093,9 +1127,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; Loading @@ -1106,9 +1140,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 || Loading @@ -1117,7 +1155,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; Loading @@ -1126,24 +1164,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; Loading @@ -1151,9 +1192,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; Loading @@ -1165,14 +1206,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; } final TextLine textline = TextLine.obtain(); paint.setHyphenEdit(hyphen); 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; paint.setHyphenEdit(0); TextLine.recycle(textline); return ellipsizedWidth; } } private float getTotalInsets(int line) { private float getTotalInsets(int line) { Loading core/java/android/text/TextUtils.java +114 −75 File changed.Preview size limit exceeded, changes collapsed. Show changes graphics/java/android/graphics/Paint.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -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"); Loading Loading
core/java/android/text/Layout.java +3 −1 Original line number Original line Diff line number Diff line Loading @@ -2067,9 +2067,11 @@ public abstract class Layout { final String ellipsisString = TextUtils.getEllipsisString(method); final String ellipsisString = TextUtils.getEllipsisString(method); final int ellipsisStringLen = ellipsisString.length(); 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++) { for (int i = 0; i < ellipsisCount; i++) { final char c; final char c; if (i < ellipsisStringLen && ellipsisCount <= ellipsisStringLen) { if (useEllipsisString && i < ellipsisStringLen) { c = ellipsisString.charAt(i); c = ellipsisString.charAt(i); } else { } else { c = TextUtils.ELLIPSIS_FILLER; c = TextUtils.ELLIPSIS_FILLER; Loading
core/java/android/text/StaticLayout.java +166 −86 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -720,7 +720,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; Loading Loading @@ -807,7 +807,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]; Loading Loading @@ -909,17 +909,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; Loading @@ -939,30 +938,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 Loading @@ -975,9 +974,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); } } } } Loading @@ -996,6 +995,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; Loading @@ -1006,8 +1027,6 @@ public class StaticLayout extends Layout { } } } } int extra; if (lastLine) { if (lastLine) { if (trackPad) { if (trackPad) { mBottomPadding = bottom - below; mBottomPadding = bottom - below; Loading @@ -1018,8 +1037,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 { Loading @@ -1029,8 +1049,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; Loading @@ -1038,7 +1056,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); } } Loading @@ -1047,33 +1065,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; Loading @@ -1081,11 +1079,47 @@ 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; do { final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths, widthStart, tempAvail, where, line, textWidth, paint, 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. 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 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) { Loading @@ -1093,9 +1127,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; Loading @@ -1106,9 +1140,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 || Loading @@ -1117,7 +1155,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; Loading @@ -1126,24 +1164,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; Loading @@ -1151,9 +1192,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; Loading @@ -1165,14 +1206,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; } final TextLine textline = TextLine.obtain(); paint.setHyphenEdit(hyphen); 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; paint.setHyphenEdit(0); TextLine.recycle(textline); return ellipsizedWidth; } } private float getTotalInsets(int line) { private float getTotalInsets(int line) { Loading
core/java/android/text/TextUtils.java +114 −75 File changed.Preview size limit exceeded, changes collapsed. Show changes
graphics/java/android/graphics/Paint.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -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"); Loading