From 720edf95db6c908f87dbbb33c6bde67aedb36899 Mon Sep 17 00:00:00 2001 From: Keisuke Kuroyanagi Date: Thu, 27 Aug 2015 18:15:48 +0900 Subject: [PATCH 1/2] Fix: Cursor can be at an invalid offset in EditText. "getLineEnd(line) - 1" is used as the return value when the "horiz" is beyond the line end for multiple line text. In this case, the returned value can point an invalid offset like the middle point of a surrogate pair. Bug: 23069901 Change-Id: I1afef7205a15079a42bb0018df73f70fe9ada862 (cherry picked from commit 00ad16d1cd24b788262ab4f62935e720a392da6d) --- core/java/android/text/Layout.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index fa347b9dece86..706d98986520d 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1130,13 +1130,18 @@ public abstract class Layout { */ public int getOffsetForHorizontal(int line, float horiz) { // TODO: use Paint.getOffsetForAdvance to avoid binary search - int max = getLineEnd(line) - 1; - int min = getLineStart(line); + final int lineEndOffset = getLineEnd(line); + final int max; + if (line == getLineCount() - 1) { + max = lineEndOffset; + } else { + max = mPaint.getTextRunCursor(mText, 0, mText.length(), + isRtlCharAt(lineEndOffset) ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR, + lineEndOffset, Paint.CURSOR_BEFORE); + } + final int min = getLineStart(line); Directions dirs = getLineDirections(line); - if (line == getLineCount() - 1) - max++; - int best = min; float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz); From 5bff01d72f2a49add74dd36c8d1ed5038611f20b Mon Sep 17 00:00:00 2001 From: Keisuke Kuroyanagi Date: Fri, 8 Jan 2016 19:55:17 +0900 Subject: [PATCH 2/2] Respect grapheme clusters in Layout#getOffsetForHorizontal. TextUtils#getOffsetAfter was used for getting the next offset in Layout#getOffsetForHorizontal. However, this method doesn't treat grapheme clusters properly. Changed to use TextLine#getOffsetToLeftRightOf instead of TextUtils#getOffsetAfter. Bug: 25375561 Change-Id: I96c9e6e5da65adfb5266446ecab852c52628dbb5 --- core/java/android/text/Layout.java | 53 +++++++++++++++++------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 706d98986520d..22ad63405770c 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1131,24 +1131,30 @@ public abstract class Layout { public int getOffsetForHorizontal(int line, float horiz) { // TODO: use Paint.getOffsetForAdvance to avoid binary search final int lineEndOffset = getLineEnd(line); + final int lineStartOffset = getLineStart(line); + + Directions dirs = getLineDirections(line); + + TextLine tl = TextLine.obtain(); + // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here. + tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs, + false, null); + final int max; if (line == getLineCount() - 1) { max = lineEndOffset; } else { - max = mPaint.getTextRunCursor(mText, 0, mText.length(), - isRtlCharAt(lineEndOffset) ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR, - lineEndOffset, Paint.CURSOR_BEFORE); + max = tl.getOffsetToLeftRightOf(lineEndOffset - lineStartOffset, + !isRtlCharAt(lineEndOffset - 1)) + lineStartOffset; } - final int min = getLineStart(line); - Directions dirs = getLineDirections(line); - - int best = min; + int best = lineStartOffset; float bestdist = Math.abs(getPrimaryHorizontal(best) - horiz); for (int i = 0; i < dirs.mDirections.length; i += 2) { - int here = min + dirs.mDirections[i]; + int here = lineStartOffset + dirs.mDirections[i]; int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK); - int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1; + boolean isRtl = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0; + int swap = isRtl ? -1 : 1; if (there > max) there = max; @@ -1168,23 +1174,23 @@ public abstract class Layout { low = here + 1; if (low < there) { - low = getOffsetAtStartOf(low); + int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset; + low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset; + if (low >= here && low < there) { + float dist = Math.abs(getPrimaryHorizontal(low) - horiz); + if (aft < there) { + float other = Math.abs(getPrimaryHorizontal(aft) - horiz); - float dist = Math.abs(getPrimaryHorizontal(low) - horiz); - - int aft = TextUtils.getOffsetAfter(mText, low); - if (aft < there) { - float other = Math.abs(getPrimaryHorizontal(aft) - horiz); - - if (other < dist) { - dist = other; - low = aft; + if (other < dist) { + dist = other; + low = aft; + } } - } - if (dist < bestdist) { - bestdist = dist; - best = low; + if (dist < bestdist) { + bestdist = dist; + best = low; + } } } @@ -1203,6 +1209,7 @@ public abstract class Layout { best = max; } + TextLine.recycle(tl); return best; }