diff options
| author | Gilles Debunne <debunne@google.com> | 2011-09-26 14:18:14 -0700 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-09-26 14:18:14 -0700 |
| commit | 52edaa9cfb612bd20b0f718dc95e576f55d9367e (patch) | |
| tree | 51ebaee3d1702fd5d9d342f0d6233a40038f8133 /core/java/android/text/TextLine.java | |
| parent | 71bfec448847d8585c8a789596525dc729350877 (diff) | |
| parent | 945ee9b1661e60e0074d4f16f61fc147c728c6bf (diff) | |
Merge "Bug 5250788: TextView gets slower as the text length grows"
Diffstat (limited to 'core/java/android/text/TextLine.java')
| -rw-r--r-- | core/java/android/text/TextLine.java | 162 |
1 files changed, 117 insertions, 45 deletions
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 1e17632e08e6..68fea1907b87 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -30,6 +30,8 @@ import android.util.Log; import com.android.internal.util.ArrayUtils; +import java.lang.reflect.Array; + /** * Represents a line of styled text, for measuring in visual order and * for rendering. @@ -850,6 +852,73 @@ class TextLine { return runIsRtl ? -ret : ret; } + private static class SpanSet<E> { + final int numberOfSpans; + final E[] spans; + final int[] spanStarts; + final int[] spanEnds; + final int[] spanFlags; + + @SuppressWarnings("unchecked") + SpanSet(Spanned spanned, int start, int limit, Class<? extends E> type) { + final E[] allSpans = spanned.getSpans(start, limit, type); + final int length = allSpans.length; + // These arrays may end up being too large because of empty spans + spans = (E[]) Array.newInstance(type, length); + spanStarts = new int[length]; + spanEnds = new int[length]; + spanFlags = new int[length]; + + int count = 0; + for (int i = 0; i < length; i++) { + final E span = allSpans[i]; + + final int spanStart = spanned.getSpanStart(span); + final int spanEnd = spanned.getSpanEnd(span); + if (spanStart == spanEnd) continue; + + final int spanFlag = spanned.getSpanFlags(span); + final int priority = spanFlag & Spanned.SPAN_PRIORITY; + if (priority != 0 && count != 0) { + int j; + + for (j = 0; j < count; j++) { + final int otherPriority = spanFlags[j] & Spanned.SPAN_PRIORITY; + if (priority > otherPriority) break; + } + + System.arraycopy(spans, j, spans, j + 1, count - j); + System.arraycopy(spanStarts, j, spanStarts, j + 1, count - j); + System.arraycopy(spanEnds, j, spanEnds, j + 1, count - j); + System.arraycopy(spanFlags, j, spanFlags, j + 1, count - j); + + spans[j] = span; + spanStarts[j] = spanStart; + spanEnds[j] = spanEnd; + spanFlags[j] = spanFlag; + } else { + spans[i] = span; + spanStarts[i] = spanStart; + spanEnds[i] = spanEnd; + spanFlags[i] = spanFlag; + } + + count++; + } + numberOfSpans = count; + } + + int getNextTransition(int start, int limit) { + for (int i = 0; i < numberOfSpans; i++) { + final int spanStart = spanStarts[i]; + final int spanEnd = spanEnds[i]; + if (spanStart > start && spanStart < limit) limit = spanStart; + if (spanEnd > start && spanEnd < limit) limit = spanEnd; + } + return limit; + } + } + /** * Utility function for handling a unidirectional run. The run must not * contain tabs or emoji but can contain styles. @@ -883,66 +952,70 @@ class TextLine { return 0f; } + if (mSpanned == null) { + TextPaint wp = mWorkPaint; + wp.set(mPaint); + final int mlimit = measureLimit; + return handleText(wp, start, mlimit, start, limit, runIsRtl, c, x, top, + y, bottom, fmi, needWidth || mlimit < measureLimit); + } + + final SpanSet<MetricAffectingSpan> metricAffectingSpans = new SpanSet<MetricAffectingSpan>( + mSpanned, mStart + start, mStart + limit, MetricAffectingSpan.class); + final SpanSet<CharacterStyle> characterStyleSpans = new SpanSet<CharacterStyle>( + mSpanned, mStart + start, mStart + limit, CharacterStyle.class); + // Shaping needs to take into account context up to metric boundaries, // but rendering needs to take into account character style boundaries. // So we iterate through metric runs to get metric bounds, // then within each metric run iterate through character style runs // for the run bounds. - float ox = x; + final float originalX = x; for (int i = start, inext; i < measureLimit; i = inext) { TextPaint wp = mWorkPaint; wp.set(mPaint); - int mlimit; - if (mSpanned == null) { - inext = limit; - mlimit = measureLimit; - } else { - inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit, - MetricAffectingSpan.class) - mStart; - - mlimit = inext < measureLimit ? inext : measureLimit; - MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i, - mStart + mlimit, MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class); - - if (spans.length > 0) { - ReplacementSpan replacement = null; - for (int j = 0; j < spans.length; j++) { - MetricAffectingSpan span = spans[j]; - if (span instanceof ReplacementSpan) { - replacement = (ReplacementSpan)span; - } else { - // We might have a replacement that uses the draw - // state, otherwise measure state would suffice. - span.updateDrawState(wp); - } - } - - if (replacement != null) { - x += handleReplacement(replacement, wp, i, - mlimit, runIsRtl, c, x, top, y, bottom, fmi, - needWidth || mlimit < measureLimit); - continue; - } + inext = metricAffectingSpans.getNextTransition(mStart + i, mStart + limit) - mStart; + int mlimit = Math.min(inext, measureLimit); + + ReplacementSpan replacement = null; + + for (int j = 0; j < metricAffectingSpans.numberOfSpans; j++) { + // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT + // empty by construction. This special case in getSpans() explains the >= & <= tests + if ((metricAffectingSpans.spanStarts[j] >= mStart + mlimit) || + (metricAffectingSpans.spanEnds[j] <= mStart + i)) continue; + MetricAffectingSpan span = metricAffectingSpans.spans[j]; + if (span instanceof ReplacementSpan) { + replacement = (ReplacementSpan)span; + } else { + // We might have a replacement that uses the draw + // state, otherwise measure state would suffice. + span.updateDrawState(wp); } } - if (mSpanned == null || c == null) { + if (replacement != null) { + x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y, + bottom, fmi, needWidth || mlimit < measureLimit); + continue; + } + + if (c == null) { x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top, y, bottom, fmi, needWidth || mlimit < measureLimit); } else { for (int j = i, jnext; j < mlimit; j = jnext) { - jnext = mSpanned.nextSpanTransition(mStart + j, - mStart + mlimit, CharacterStyle.class) - mStart; - - CharacterStyle[] spans = mSpanned.getSpans(mStart + j, - mStart + jnext, CharacterStyle.class); - spans = TextUtils.removeEmptySpans(spans, mSpanned, CharacterStyle.class); + jnext = characterStyleSpans.getNextTransition(mStart + j, mStart + mlimit) - + mStart; wp.set(mPaint); - for (int k = 0; k < spans.length; k++) { - CharacterStyle span = spans[k]; + for (int k = 0; k < characterStyleSpans.numberOfSpans; k++) { + // Intentionally using >= and <= as explained above + if ((characterStyleSpans.spanStarts[k] >= mStart + jnext) || + (characterStyleSpans.spanEnds[k] <= mStart + j)) continue; + + CharacterStyle span = characterStyleSpans.spans[k]; span.updateDrawState(wp); } @@ -952,7 +1025,7 @@ class TextLine { } } - return x - ox; + return x - originalX; } /** @@ -997,8 +1070,7 @@ class TextLine { } pos += mStart; - MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, - MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, MetricAffectingSpan.class); if (spans.length == 0) { return mPaint.ascent(); } |
