Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 52edaa9c authored by Gilles Debunne's avatar Gilles Debunne Committed by Android (Google) Code Review
Browse files

Merge "Bug 5250788: TextView gets slower as the text length grows"

parents 71bfec44 945ee9b1
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -709,8 +709,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
        T ret1 = null;

        for (int i = 0; i < spanCount; i++) {
            if (!kind.isInstance(spans[i])) continue;

            int spanStart = starts[i];
            int spanEnd = ends[i];

@@ -735,6 +733,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
                    continue;
            }

            // Expensive test, should be performed after the previous tests
            if (!kind.isInstance(spans[i])) continue;

            if (count == 0) {
                // Safe conversion thanks to the isInstance test above
                ret1 = (T) spans[i];
+117 −45
Original line number Diff line number Diff line
@@ -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,33 +952,40 @@ 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);
            inext = metricAffectingSpans.getNextTransition(mStart + i, mStart + limit) - mStart;
            int mlimit = Math.min(inext, measureLimit);

                if (spans.length > 0) {
            ReplacementSpan replacement = null;
                    for (int j = 0; j < spans.length; j++) {
                        MetricAffectingSpan span = spans[j];

            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 {
@@ -920,29 +996,26 @@ class TextLine {
            }

            if (replacement != null) {
                        x += handleReplacement(replacement, wp, i,
                                mlimit, runIsRtl, c, x, top, y, bottom, fmi,
                                needWidth || mlimit < measureLimit);
                x += handleReplacement(replacement, wp, i, mlimit, runIsRtl, c, x, top, y,
                        bottom, fmi, needWidth || mlimit < measureLimit);
                continue;
            }
                }
            }

            if (mSpanned == null || c == null) {
            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();
        }