Loading core/java/android/text/Layout.java +120 −117 Original line number Diff line number Diff line Loading @@ -76,7 +76,6 @@ public abstract class Layout { int start, int end, TextPaint paint) { float need = 0; TextPaint workPaint = new TextPaint(); int next; for (int i = start; i <= end; i = next) { Loading @@ -86,7 +85,7 @@ public abstract class Layout { next = end; // note, omits trailing paragraph char float w = measurePara(paint, workPaint, source, i, next); float w = measurePara(paint, source, i, next); if (w > need) need = w; Loading Loading @@ -189,106 +188,34 @@ public abstract class Layout { * Draw this Layout on the specified canvas, with the highlight path drawn * between the background and the text. * * @param c the canvas * @param canvas the canvas * @param highlight the path of the highlight or cursor; can be null * @param highlightPaint the paint for the highlight * @param cursorOffsetVertical the amount to temporarily translate the * canvas while rendering the highlight */ public void draw(Canvas c, Path highlight, Paint highlightPaint, public void draw(Canvas canvas, Path highlight, Paint highlightPaint, int cursorOffsetVertical) { int dtop, dbottom; synchronized (sTempRect) { if (!c.getClipBounds(sTempRect)) { return; } final long lineRange = getLineRangeForDraw(canvas); int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); if (lastLine < 0) return; dtop = sTempRect.top; dbottom = sTempRect.bottom; drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical, firstLine, lastLine); drawText(canvas, firstLine, lastLine); } int top = 0; int bottom = getLineTop(getLineCount()); if (dtop > top) { top = dtop; } if (dbottom < bottom) { bottom = dbottom; } int first = getLineForVertical(top); int last = getLineForVertical(bottom); int previousLineBottom = getLineTop(first); int previousLineEnd = getLineStart(first); TextPaint paint = mPaint; CharSequence buf = mText; int width = mWidth; boolean spannedText = mSpannedText; /** * @hide */ public void drawText(Canvas canvas, int firstLine, int lastLine) { int previousLineBottom = getLineTop(firstLine); int previousLineEnd = getLineStart(firstLine); ParagraphStyle[] spans = NO_PARA_SPANS; int spanEnd = 0; int textLength = 0; // First, draw LineBackgroundSpans. // LineBackgroundSpans know nothing about the alignment, margins, or // direction of the layout or line. XXX: Should they? // They are evaluated at each line. if (spannedText) { Spanned sp = (Spanned) buf; textLength = buf.length(); for (int i = first; i <= last; i++) { int start = previousLineEnd; int end = getLineStart(i+1); previousLineEnd = end; int ltop = previousLineBottom; int lbottom = getLineTop(i+1); previousLineBottom = lbottom; int lbaseline = lbottom - getLineDescent(i); if (start >= spanEnd) { // These should be infrequent, so we'll use this so that // we don't have to check as often. spanEnd = sp.nextSpanTransition(start, textLength, LineBackgroundSpan.class); // All LineBackgroundSpans on a line contribute to its // background. spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class); } for (int n = 0; n < spans.length; n++) { LineBackgroundSpan back = (LineBackgroundSpan) spans[n]; back.drawBackground(c, paint, 0, width, ltop, lbaseline, lbottom, buf, start, end, i); } } // reset to their original values spanEnd = 0; previousLineBottom = getLineTop(first); previousLineEnd = getLineStart(first); spans = NO_PARA_SPANS; } // There can be a highlight even without spans if we are drawing // a non-spanned transformation of a spanned editing buffer. if (highlight != null) { if (cursorOffsetVertical != 0) { c.translate(0, cursorOffsetVertical); } c.drawPath(highlight, highlightPaint); if (cursorOffsetVertical != 0) { c.translate(0, -cursorOffsetVertical); } } TextPaint paint = mPaint; CharSequence buf = mText; Alignment paraAlign = mAlignment; TabStops tabStops = null; Loading @@ -296,12 +223,10 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); // Next draw the lines, one at a time. // the baseline is the top of the following line minus the current // line's descent. for (int i = first; i <= last; i++) { // Draw the lines, one at a time. // The baseline is the top of the following line minus the current line's descent. for (int i = firstLine; i <= lastLine; i++) { int start = previousLineEnd; previousLineEnd = getLineStart(i + 1); int end = getLineVisibleEnd(i, start, previousLineEnd); Loading @@ -314,10 +239,10 @@ public abstract class Layout { int left = 0; int right = mWidth; if (spannedText) { if (mSpannedText) { Spanned sp = (Spanned) buf; boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n'); int textLength = buf.length(); boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n'); // New batch of paragraph styles, collect into spans array. // Compute the alignment, last alignment style wins. Loading @@ -329,7 +254,7 @@ public abstract class Layout { // just collect the ones present at the start of the paragraph. // If spanEnd is before the end of the paragraph, that's not // our problem. if (start >= spanEnd && (i == first || isFirstParaLine)) { if (start >= spanEnd && (i == firstLine || isFirstParaLine)) { spanEnd = sp.nextSpanTransition(start, textLength, ParagraphStyle.class); spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class); Loading Loading @@ -359,12 +284,12 @@ public abstract class Layout { } if (dir == DIR_RIGHT_TO_LEFT) { margin.drawLeadingMargin(c, paint, right, dir, ltop, margin.drawLeadingMargin(canvas, paint, right, dir, ltop, lbaseline, lbottom, buf, start, end, isFirstParaLine, this); right -= margin.getLeadingMargin(useFirstLineMargin); } else { margin.drawLeadingMargin(c, paint, left, dir, ltop, margin.drawLeadingMargin(canvas, paint, left, dir, ltop, lbaseline, lbottom, buf, start, end, isFirstParaLine, this); left += margin.getLeadingMargin(useFirstLineMargin); Loading Loading @@ -416,19 +341,97 @@ public abstract class Layout { } Directions directions = getLineDirections(i); if (directions == DIRS_ALL_LEFT_TO_RIGHT && !spannedText && !hasTabOrEmoji) { if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) { // XXX: assumes there's nothing additional to be done c.drawText(buf, start, end, x, lbaseline, paint); canvas.drawText(buf, start, end, x, lbaseline, paint); } else { tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops); tl.draw(c, x, ltop, lbaseline, lbottom); tl.draw(canvas, x, ltop, lbaseline, lbottom); } } TextLine.recycle(tl); } /** * @hide */ public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint, int cursorOffsetVertical, int firstLine, int lastLine) { // First, draw LineBackgroundSpans. // LineBackgroundSpans know nothing about the alignment, margins, or // direction of the layout or line. XXX: Should they? // They are evaluated at each line. if (mSpannedText) { int previousLineBottom = getLineTop(firstLine); int previousLineEnd = getLineStart(firstLine); ParagraphStyle[] spans = NO_PARA_SPANS; TextPaint paint = mPaint; CharSequence buf = mText; int spanEnd = 0; final int width = mWidth; Spanned sp = (Spanned) buf; int textLength = buf.length(); for (int i = firstLine; i <= lastLine; i++) { int start = previousLineEnd; int end = getLineStart(i + 1); previousLineEnd = end; int ltop = previousLineBottom; int lbottom = getLineTop(i + 1); previousLineBottom = lbottom; int lbaseline = lbottom - getLineDescent(i); if (start >= spanEnd) { // These should be infrequent, so we'll use this so that // we don't have to check as often. spanEnd = sp.nextSpanTransition(start, textLength, LineBackgroundSpan.class); // All LineBackgroundSpans on a line contribute to its background. spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class); } for (int n = 0; n < spans.length; n++) { LineBackgroundSpan back = (LineBackgroundSpan) spans[n]; back.drawBackground(canvas, paint, 0, width, ltop, lbaseline, lbottom, buf, start, end, i); } } } // There can be a highlight even without spans if we are drawing // a non-spanned transformation of a spanned editing buffer. if (highlight != null) { if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical); canvas.drawPath(highlight, highlightPaint); if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical); } } /** * @param canvas * @return The range of lines that need to be drawn, possibly empty. * @hide */ public long getLineRangeForDraw(Canvas canvas) { int dtop, dbottom; synchronized (sTempRect) { if (!canvas.getClipBounds(sTempRect)) { // Negative range end used as a special flag return TextUtils.packRangeInLong(0, -1); } dtop = sTempRect.top; dbottom = sTempRect.bottom; } final int top = Math.max(dtop, 0); final int bottom = Math.min(getLineTop(getLineCount()), dbottom); return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom)); } /** * Return the start position of the line, given the left and right bounds * of the margins. Loading Loading @@ -460,7 +463,8 @@ public abstract class Layout { int start = getLineStart(line); int spanEnd = spanned.nextSpanTransition(start, spanned.length(), TabStopSpan.class); TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class); TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class); if (tabSpans.length > 0) { tabStops = new TabStops(TAB_INCREMENT, tabSpans); } Loading Loading @@ -1481,8 +1485,7 @@ public abstract class Layout { } /* package */ static float measurePara(TextPaint paint, TextPaint workPaint, CharSequence text, int start, int end) { static float measurePara(TextPaint paint, CharSequence text, int start, int end) { MeasuredText mt = MeasuredText.obtain(); TextLine tl = TextLine.obtain(); Loading Loading @@ -1659,7 +1662,7 @@ public abstract class Layout { */ /* package */ static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) { if (start == end && start > 0) { return (T[]) ArrayUtils.emptyArray(type); return ArrayUtils.emptyArray(type); } return text.getSpans(start, end, type); Loading Loading @@ -1777,8 +1780,7 @@ public abstract class Layout { } /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned { /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned { private Spanned mSpanned; public SpannedEllipsizer(CharSequence display) { Loading @@ -1802,6 +1804,7 @@ public abstract class Layout { return mSpanned.getSpanFlags(tag); } @SuppressWarnings("rawtypes") public int nextSpanTransition(int start, int limit, Class type) { return mSpanned.nextSpanTransition(start, limit, type); } Loading core/java/android/text/TextUtils.java +30 −0 Original line number Diff line number Diff line Loading @@ -1664,6 +1664,36 @@ public class TextUtils { } } /** * Pack 2 int values into a long, useful as a return value for a range * @see #unpackRangeStartFromLong(long) * @see #unpackRangeEndFromLong(long) * @hide */ public static long packRangeInLong(int start, int end) { return (((long) start) << 32) | end; } /** * Get the start value from a range packed in a long by {@link #packRangeInLong(int, int)} * @see #unpackRangeEndFromLong(long) * @see #packRangeInLong(int, int) * @hide */ public static int unpackRangeStartFromLong(long range) { return (int) (range >>> 32); } /** * Get the end value from a range packed in a long by {@link #packRangeInLong(int, int)} * @see #unpackRangeStartFromLong(long) * @see #packRangeInLong(int, int) * @hide */ public static int unpackRangeEndFromLong(long range) { return (int) (range & 0x00000000FFFFFFFFL); } private static Object sLock = new Object(); private static char[] sTemp = null; Loading core/java/android/widget/TextView.java +56 −55 Original line number Diff line number Diff line Loading @@ -6946,7 +6946,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ protected void onSelectionChanged(int selStart, int selEnd) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); if (mEditor != null) getEditor().mTextDisplayListIsValid = false; } /** Loading Loading @@ -7772,18 +7771,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hasPrimaryClip()); } private static long packRangeInLong(int start, int end) { return (((long) start) << 32) | end; } private static int extractRangeStartFromLong(long range) { return (int) (range >>> 32); } private static int extractRangeEndFromLong(long range) { return (int) (range & 0x00000000FFFFFFFFL); } private boolean selectAll() { final int length = mText.length(); Selection.setSelection((Spannable) mText, 0, length); Loading Loading @@ -7822,8 +7809,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } long lastTouchOffsets = getLastTouchOffsets(); final int minOffset = extractRangeStartFromLong(lastTouchOffsets); final int maxOffset = extractRangeEndFromLong(lastTouchOffsets); final int minOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets); final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets); // Safety check in case standard touch event handling has been bypassed if (minOffset < 0 || minOffset >= mText.length()) return false; Loading @@ -7848,8 +7835,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener selectionStart == selectionEnd) { // Possible when the word iterator does not properly handle the text's language long range = getCharRange(minOffset); selectionStart = extractRangeStartFromLong(range); selectionEnd = extractRangeEndFromLong(range); selectionStart = TextUtils.unpackRangeStartFromLong(range); selectionEnd = TextUtils.unpackRangeEndFromLong(range); } } Loading Loading @@ -7897,30 +7884,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final char currentChar = mText.charAt(offset); final char nextChar = mText.charAt(offset + 1); if (Character.isSurrogatePair(currentChar, nextChar)) { return packRangeInLong(offset, offset + 2); return TextUtils.packRangeInLong(offset, offset + 2); } } if (offset < textLength) { return packRangeInLong(offset, offset + 1); return TextUtils.packRangeInLong(offset, offset + 1); } if (offset - 2 >= 0) { final char previousChar = mText.charAt(offset - 1); final char previousPreviousChar = mText.charAt(offset - 2); if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { return packRangeInLong(offset - 2, offset); return TextUtils.packRangeInLong(offset - 2, offset); } } if (offset - 1 >= 0) { return packRangeInLong(offset - 1, offset); return TextUtils.packRangeInLong(offset - 1, offset); } return packRangeInLong(offset, offset); return TextUtils.packRangeInLong(offset, offset); } private long getLastTouchOffsets() { SelectionModifierCursorController selectionController = getSelectionController(); final int minOffset = selectionController.getMinTouchOffset(); final int maxOffset = selectionController.getMaxTouchOffset(); return packRangeInLong(minOffset, maxOffset); return TextUtils.packRangeInLong(minOffset, maxOffset); } @Override Loading Loading @@ -8111,7 +8098,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } return packRangeInLong(min, max); return TextUtils.packRangeInLong(min, max); } private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) { Loading Loading @@ -8470,8 +8457,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (paste != null) { if (!didFirst) { long minMax = prepareSpacesAroundPaste(min, max, paste); min = extractRangeStartFromLong(minMax); max = extractRangeEndFromLong(minMax); min = TextUtils.unpackRangeStartFromLong(minMax); max = TextUtils.unpackRangeEndFromLong(minMax); Selection.setSelection((Spannable) mText, max); ((Editable) mText).replace(min, max, paste); didFirst = true; Loading Loading @@ -8630,8 +8617,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int originalLength = mText.length(); long minMax = prepareSpacesAroundPaste(offset, offset, content); int min = extractRangeStartFromLong(minMax); int max = extractRangeEndFromLong(minMax); int min = TextUtils.unpackRangeStartFromLong(minMax); int max = TextUtils.unpackRangeEndFromLong(minMax); Selection.setSelection((Spannable) mText, max); replaceText_internal(min, max, content); Loading Loading @@ -11667,9 +11654,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (canHaveDisplayList() && canvas.isHardwareAccelerated()) { drawHardwareAccelerated(canvas, layout, highlight, cursorOffsetVertical); } else { layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } if (mMarquee != null && mMarquee.shouldDrawGhost()) { canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } } private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight, int cursorOffsetVertical) { final int width = mRight - mLeft; final int height = mBottom - mTop; final long lineRange = layout.getLineRangeForDraw(canvas); int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); if (lastLine < 0) return; layout.drawBackground(canvas, highlight, mHighlightPaint, cursorOffsetVertical, firstLine, lastLine); if (mTextDisplayList == null || !mTextDisplayList.isValid() || !mTextDisplayListIsValid) { if (mTextDisplayList == null) { Loading @@ -11682,7 +11690,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // The dirty rect should always be null for a display list hardwareCanvas.onPreDraw(null); hardwareCanvas.translate(-mScrollX, -mScrollY); layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical); layout.drawText(hardwareCanvas, firstLine, lastLine); //layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical); hardwareCanvas.translate(mScrollX, mScrollY); } finally { hardwareCanvas.onPostDraw(); Loading @@ -11694,14 +11703,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null, DisplayList.FLAG_CLIP_CHILDREN); canvas.translate(-mScrollX, -mScrollY); } else { layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } if (mMarquee != null && mMarquee.shouldDrawGhost()) { canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } } private void drawCursor(Canvas canvas, int cursorOffsetVertical) { Loading Loading
core/java/android/text/Layout.java +120 −117 Original line number Diff line number Diff line Loading @@ -76,7 +76,6 @@ public abstract class Layout { int start, int end, TextPaint paint) { float need = 0; TextPaint workPaint = new TextPaint(); int next; for (int i = start; i <= end; i = next) { Loading @@ -86,7 +85,7 @@ public abstract class Layout { next = end; // note, omits trailing paragraph char float w = measurePara(paint, workPaint, source, i, next); float w = measurePara(paint, source, i, next); if (w > need) need = w; Loading Loading @@ -189,106 +188,34 @@ public abstract class Layout { * Draw this Layout on the specified canvas, with the highlight path drawn * between the background and the text. * * @param c the canvas * @param canvas the canvas * @param highlight the path of the highlight or cursor; can be null * @param highlightPaint the paint for the highlight * @param cursorOffsetVertical the amount to temporarily translate the * canvas while rendering the highlight */ public void draw(Canvas c, Path highlight, Paint highlightPaint, public void draw(Canvas canvas, Path highlight, Paint highlightPaint, int cursorOffsetVertical) { int dtop, dbottom; synchronized (sTempRect) { if (!c.getClipBounds(sTempRect)) { return; } final long lineRange = getLineRangeForDraw(canvas); int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); if (lastLine < 0) return; dtop = sTempRect.top; dbottom = sTempRect.bottom; drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical, firstLine, lastLine); drawText(canvas, firstLine, lastLine); } int top = 0; int bottom = getLineTop(getLineCount()); if (dtop > top) { top = dtop; } if (dbottom < bottom) { bottom = dbottom; } int first = getLineForVertical(top); int last = getLineForVertical(bottom); int previousLineBottom = getLineTop(first); int previousLineEnd = getLineStart(first); TextPaint paint = mPaint; CharSequence buf = mText; int width = mWidth; boolean spannedText = mSpannedText; /** * @hide */ public void drawText(Canvas canvas, int firstLine, int lastLine) { int previousLineBottom = getLineTop(firstLine); int previousLineEnd = getLineStart(firstLine); ParagraphStyle[] spans = NO_PARA_SPANS; int spanEnd = 0; int textLength = 0; // First, draw LineBackgroundSpans. // LineBackgroundSpans know nothing about the alignment, margins, or // direction of the layout or line. XXX: Should they? // They are evaluated at each line. if (spannedText) { Spanned sp = (Spanned) buf; textLength = buf.length(); for (int i = first; i <= last; i++) { int start = previousLineEnd; int end = getLineStart(i+1); previousLineEnd = end; int ltop = previousLineBottom; int lbottom = getLineTop(i+1); previousLineBottom = lbottom; int lbaseline = lbottom - getLineDescent(i); if (start >= spanEnd) { // These should be infrequent, so we'll use this so that // we don't have to check as often. spanEnd = sp.nextSpanTransition(start, textLength, LineBackgroundSpan.class); // All LineBackgroundSpans on a line contribute to its // background. spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class); } for (int n = 0; n < spans.length; n++) { LineBackgroundSpan back = (LineBackgroundSpan) spans[n]; back.drawBackground(c, paint, 0, width, ltop, lbaseline, lbottom, buf, start, end, i); } } // reset to their original values spanEnd = 0; previousLineBottom = getLineTop(first); previousLineEnd = getLineStart(first); spans = NO_PARA_SPANS; } // There can be a highlight even without spans if we are drawing // a non-spanned transformation of a spanned editing buffer. if (highlight != null) { if (cursorOffsetVertical != 0) { c.translate(0, cursorOffsetVertical); } c.drawPath(highlight, highlightPaint); if (cursorOffsetVertical != 0) { c.translate(0, -cursorOffsetVertical); } } TextPaint paint = mPaint; CharSequence buf = mText; Alignment paraAlign = mAlignment; TabStops tabStops = null; Loading @@ -296,12 +223,10 @@ public abstract class Layout { TextLine tl = TextLine.obtain(); // Next draw the lines, one at a time. // the baseline is the top of the following line minus the current // line's descent. for (int i = first; i <= last; i++) { // Draw the lines, one at a time. // The baseline is the top of the following line minus the current line's descent. for (int i = firstLine; i <= lastLine; i++) { int start = previousLineEnd; previousLineEnd = getLineStart(i + 1); int end = getLineVisibleEnd(i, start, previousLineEnd); Loading @@ -314,10 +239,10 @@ public abstract class Layout { int left = 0; int right = mWidth; if (spannedText) { if (mSpannedText) { Spanned sp = (Spanned) buf; boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n'); int textLength = buf.length(); boolean isFirstParaLine = (start == 0 || buf.charAt(start - 1) == '\n'); // New batch of paragraph styles, collect into spans array. // Compute the alignment, last alignment style wins. Loading @@ -329,7 +254,7 @@ public abstract class Layout { // just collect the ones present at the start of the paragraph. // If spanEnd is before the end of the paragraph, that's not // our problem. if (start >= spanEnd && (i == first || isFirstParaLine)) { if (start >= spanEnd && (i == firstLine || isFirstParaLine)) { spanEnd = sp.nextSpanTransition(start, textLength, ParagraphStyle.class); spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class); Loading Loading @@ -359,12 +284,12 @@ public abstract class Layout { } if (dir == DIR_RIGHT_TO_LEFT) { margin.drawLeadingMargin(c, paint, right, dir, ltop, margin.drawLeadingMargin(canvas, paint, right, dir, ltop, lbaseline, lbottom, buf, start, end, isFirstParaLine, this); right -= margin.getLeadingMargin(useFirstLineMargin); } else { margin.drawLeadingMargin(c, paint, left, dir, ltop, margin.drawLeadingMargin(canvas, paint, left, dir, ltop, lbaseline, lbottom, buf, start, end, isFirstParaLine, this); left += margin.getLeadingMargin(useFirstLineMargin); Loading Loading @@ -416,19 +341,97 @@ public abstract class Layout { } Directions directions = getLineDirections(i); if (directions == DIRS_ALL_LEFT_TO_RIGHT && !spannedText && !hasTabOrEmoji) { if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) { // XXX: assumes there's nothing additional to be done c.drawText(buf, start, end, x, lbaseline, paint); canvas.drawText(buf, start, end, x, lbaseline, paint); } else { tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops); tl.draw(c, x, ltop, lbaseline, lbottom); tl.draw(canvas, x, ltop, lbaseline, lbottom); } } TextLine.recycle(tl); } /** * @hide */ public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint, int cursorOffsetVertical, int firstLine, int lastLine) { // First, draw LineBackgroundSpans. // LineBackgroundSpans know nothing about the alignment, margins, or // direction of the layout or line. XXX: Should they? // They are evaluated at each line. if (mSpannedText) { int previousLineBottom = getLineTop(firstLine); int previousLineEnd = getLineStart(firstLine); ParagraphStyle[] spans = NO_PARA_SPANS; TextPaint paint = mPaint; CharSequence buf = mText; int spanEnd = 0; final int width = mWidth; Spanned sp = (Spanned) buf; int textLength = buf.length(); for (int i = firstLine; i <= lastLine; i++) { int start = previousLineEnd; int end = getLineStart(i + 1); previousLineEnd = end; int ltop = previousLineBottom; int lbottom = getLineTop(i + 1); previousLineBottom = lbottom; int lbaseline = lbottom - getLineDescent(i); if (start >= spanEnd) { // These should be infrequent, so we'll use this so that // we don't have to check as often. spanEnd = sp.nextSpanTransition(start, textLength, LineBackgroundSpan.class); // All LineBackgroundSpans on a line contribute to its background. spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class); } for (int n = 0; n < spans.length; n++) { LineBackgroundSpan back = (LineBackgroundSpan) spans[n]; back.drawBackground(canvas, paint, 0, width, ltop, lbaseline, lbottom, buf, start, end, i); } } } // There can be a highlight even without spans if we are drawing // a non-spanned transformation of a spanned editing buffer. if (highlight != null) { if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical); canvas.drawPath(highlight, highlightPaint); if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical); } } /** * @param canvas * @return The range of lines that need to be drawn, possibly empty. * @hide */ public long getLineRangeForDraw(Canvas canvas) { int dtop, dbottom; synchronized (sTempRect) { if (!canvas.getClipBounds(sTempRect)) { // Negative range end used as a special flag return TextUtils.packRangeInLong(0, -1); } dtop = sTempRect.top; dbottom = sTempRect.bottom; } final int top = Math.max(dtop, 0); final int bottom = Math.min(getLineTop(getLineCount()), dbottom); return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom)); } /** * Return the start position of the line, given the left and right bounds * of the margins. Loading Loading @@ -460,7 +463,8 @@ public abstract class Layout { int start = getLineStart(line); int spanEnd = spanned.nextSpanTransition(start, spanned.length(), TabStopSpan.class); TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class); TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class); if (tabSpans.length > 0) { tabStops = new TabStops(TAB_INCREMENT, tabSpans); } Loading Loading @@ -1481,8 +1485,7 @@ public abstract class Layout { } /* package */ static float measurePara(TextPaint paint, TextPaint workPaint, CharSequence text, int start, int end) { static float measurePara(TextPaint paint, CharSequence text, int start, int end) { MeasuredText mt = MeasuredText.obtain(); TextLine tl = TextLine.obtain(); Loading Loading @@ -1659,7 +1662,7 @@ public abstract class Layout { */ /* package */ static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) { if (start == end && start > 0) { return (T[]) ArrayUtils.emptyArray(type); return ArrayUtils.emptyArray(type); } return text.getSpans(start, end, type); Loading Loading @@ -1777,8 +1780,7 @@ public abstract class Layout { } /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned { /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned { private Spanned mSpanned; public SpannedEllipsizer(CharSequence display) { Loading @@ -1802,6 +1804,7 @@ public abstract class Layout { return mSpanned.getSpanFlags(tag); } @SuppressWarnings("rawtypes") public int nextSpanTransition(int start, int limit, Class type) { return mSpanned.nextSpanTransition(start, limit, type); } Loading
core/java/android/text/TextUtils.java +30 −0 Original line number Diff line number Diff line Loading @@ -1664,6 +1664,36 @@ public class TextUtils { } } /** * Pack 2 int values into a long, useful as a return value for a range * @see #unpackRangeStartFromLong(long) * @see #unpackRangeEndFromLong(long) * @hide */ public static long packRangeInLong(int start, int end) { return (((long) start) << 32) | end; } /** * Get the start value from a range packed in a long by {@link #packRangeInLong(int, int)} * @see #unpackRangeEndFromLong(long) * @see #packRangeInLong(int, int) * @hide */ public static int unpackRangeStartFromLong(long range) { return (int) (range >>> 32); } /** * Get the end value from a range packed in a long by {@link #packRangeInLong(int, int)} * @see #unpackRangeStartFromLong(long) * @see #packRangeInLong(int, int) * @hide */ public static int unpackRangeEndFromLong(long range) { return (int) (range & 0x00000000FFFFFFFFL); } private static Object sLock = new Object(); private static char[] sTemp = null; Loading
core/java/android/widget/TextView.java +56 −55 Original line number Diff line number Diff line Loading @@ -6946,7 +6946,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ protected void onSelectionChanged(int selStart, int selEnd) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); if (mEditor != null) getEditor().mTextDisplayListIsValid = false; } /** Loading Loading @@ -7772,18 +7771,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hasPrimaryClip()); } private static long packRangeInLong(int start, int end) { return (((long) start) << 32) | end; } private static int extractRangeStartFromLong(long range) { return (int) (range >>> 32); } private static int extractRangeEndFromLong(long range) { return (int) (range & 0x00000000FFFFFFFFL); } private boolean selectAll() { final int length = mText.length(); Selection.setSelection((Spannable) mText, 0, length); Loading Loading @@ -7822,8 +7809,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } long lastTouchOffsets = getLastTouchOffsets(); final int minOffset = extractRangeStartFromLong(lastTouchOffsets); final int maxOffset = extractRangeEndFromLong(lastTouchOffsets); final int minOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets); final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets); // Safety check in case standard touch event handling has been bypassed if (minOffset < 0 || minOffset >= mText.length()) return false; Loading @@ -7848,8 +7835,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener selectionStart == selectionEnd) { // Possible when the word iterator does not properly handle the text's language long range = getCharRange(minOffset); selectionStart = extractRangeStartFromLong(range); selectionEnd = extractRangeEndFromLong(range); selectionStart = TextUtils.unpackRangeStartFromLong(range); selectionEnd = TextUtils.unpackRangeEndFromLong(range); } } Loading Loading @@ -7897,30 +7884,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final char currentChar = mText.charAt(offset); final char nextChar = mText.charAt(offset + 1); if (Character.isSurrogatePair(currentChar, nextChar)) { return packRangeInLong(offset, offset + 2); return TextUtils.packRangeInLong(offset, offset + 2); } } if (offset < textLength) { return packRangeInLong(offset, offset + 1); return TextUtils.packRangeInLong(offset, offset + 1); } if (offset - 2 >= 0) { final char previousChar = mText.charAt(offset - 1); final char previousPreviousChar = mText.charAt(offset - 2); if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { return packRangeInLong(offset - 2, offset); return TextUtils.packRangeInLong(offset - 2, offset); } } if (offset - 1 >= 0) { return packRangeInLong(offset - 1, offset); return TextUtils.packRangeInLong(offset - 1, offset); } return packRangeInLong(offset, offset); return TextUtils.packRangeInLong(offset, offset); } private long getLastTouchOffsets() { SelectionModifierCursorController selectionController = getSelectionController(); final int minOffset = selectionController.getMinTouchOffset(); final int maxOffset = selectionController.getMaxTouchOffset(); return packRangeInLong(minOffset, maxOffset); return TextUtils.packRangeInLong(minOffset, maxOffset); } @Override Loading Loading @@ -8111,7 +8098,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } return packRangeInLong(min, max); return TextUtils.packRangeInLong(min, max); } private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) { Loading Loading @@ -8470,8 +8457,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (paste != null) { if (!didFirst) { long minMax = prepareSpacesAroundPaste(min, max, paste); min = extractRangeStartFromLong(minMax); max = extractRangeEndFromLong(minMax); min = TextUtils.unpackRangeStartFromLong(minMax); max = TextUtils.unpackRangeEndFromLong(minMax); Selection.setSelection((Spannable) mText, max); ((Editable) mText).replace(min, max, paste); didFirst = true; Loading Loading @@ -8630,8 +8617,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int originalLength = mText.length(); long minMax = prepareSpacesAroundPaste(offset, offset, content); int min = extractRangeStartFromLong(minMax); int max = extractRangeEndFromLong(minMax); int min = TextUtils.unpackRangeStartFromLong(minMax); int max = TextUtils.unpackRangeEndFromLong(minMax); Selection.setSelection((Spannable) mText, max); replaceText_internal(min, max, content); Loading Loading @@ -11667,9 +11654,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (canHaveDisplayList() && canvas.isHardwareAccelerated()) { drawHardwareAccelerated(canvas, layout, highlight, cursorOffsetVertical); } else { layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } if (mMarquee != null && mMarquee.shouldDrawGhost()) { canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } } private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight, int cursorOffsetVertical) { final int width = mRight - mLeft; final int height = mBottom - mTop; final long lineRange = layout.getLineRangeForDraw(canvas); int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); if (lastLine < 0) return; layout.drawBackground(canvas, highlight, mHighlightPaint, cursorOffsetVertical, firstLine, lastLine); if (mTextDisplayList == null || !mTextDisplayList.isValid() || !mTextDisplayListIsValid) { if (mTextDisplayList == null) { Loading @@ -11682,7 +11690,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // The dirty rect should always be null for a display list hardwareCanvas.onPreDraw(null); hardwareCanvas.translate(-mScrollX, -mScrollY); layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical); layout.drawText(hardwareCanvas, firstLine, lastLine); //layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical); hardwareCanvas.translate(mScrollX, mScrollY); } finally { hardwareCanvas.onPostDraw(); Loading @@ -11694,14 +11703,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null, DisplayList.FLAG_CLIP_CHILDREN); canvas.translate(-mScrollX, -mScrollY); } else { layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } if (mMarquee != null && mMarquee.shouldDrawGhost()) { canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } } private void drawCursor(Canvas canvas, int cursorOffsetVertical) { Loading