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

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

Merge "Empty spans are not considered in text layout/rendering process."

parents 9646145d 1e3ac18e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -235,6 +235,8 @@ public class StaticLayout extends Layout {
                    } else {
                        MetricAffectingSpan[] spans =
                            spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
                        spans = TextUtils.removeEmptySpans(spans, spanned,
                                MetricAffectingSpan.class);
                        measured.addStyleRun(paint, spans, spanLen, fm);
                    }
                }
+12 −9
Original line number Diff line number Diff line
@@ -127,12 +127,12 @@ class TextLine {
        boolean hasReplacement = false;
        if (text instanceof Spanned) {
            mSpanned = (Spanned) text;
            hasReplacement = mSpanned.getSpans(start, limit,
                    ReplacementSpan.class).length > 0;
            ReplacementSpan[] spans = mSpanned.getSpans(start, limit, ReplacementSpan.class);
            spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
            hasReplacement = spans.length > 0;
        }

        mCharsValid = hasReplacement || hasTabs ||
            directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
        mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;

        if (mCharsValid) {
            if (mChars == null || mChars.length < mLen) {
@@ -147,10 +147,11 @@ class TextLine {
                // zero-width characters.
                char[] chars = mChars;
                for (int i = start, inext; i < limit; i = inext) {
                    inext = mSpanned.nextSpanTransition(i, limit,
                            ReplacementSpan.class);
                    if (mSpanned.getSpans(i, inext, ReplacementSpan.class)
                            .length > 0) { // transition into a span
                    inext = mSpanned.nextSpanTransition(i, limit, ReplacementSpan.class);
                    ReplacementSpan[] spans = mSpanned.getSpans(i, inext, ReplacementSpan.class);
                    spans = TextUtils.removeEmptySpans(spans, mSpanned, ReplacementSpan.class);
                    if (spans.length > 0) {
                        // transition into a span
                        chars[i - start] = '\ufffc';
                        for (int j = i - start + 1, e = inext - start; j < e; ++j) {
                            chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip
@@ -197,7 +198,6 @@ class TextLine {
            boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;

            int segstart = runStart;
            char[] chars = mChars;
            for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                int codept = 0;
                Bitmap bm = null;
@@ -629,6 +629,7 @@ class TextLine {

            MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart,
                    mStart + spanLimit, MetricAffectingSpan.class);
            spans = TextUtils.removeEmptySpans(spans, mSpanned, MetricAffectingSpan.class);

            if (spans.length > 0) {
                ReplacementSpan replacement = null;
@@ -835,6 +836,7 @@ class TextLine {
                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;
@@ -868,6 +870,7 @@ class TextLine {

                    CharacterStyle[] spans = mSpanned.getSpans(mStart + j,
                            mStart + jnext, CharacterStyle.class);
                    spans = TextUtils.removeEmptySpans(spans, mSpanned, CharacterStyle.class);

                    wp.set(mPaint);
                    for (int k = 0; k < spans.length; k++) {
+60 −9
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Printer;

import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.regex.Pattern;

@@ -54,7 +55,7 @@ public class TextUtils {

    public static void getChars(CharSequence s, int start, int end,
                                char[] dest, int destoff) {
        Class c = s.getClass();
        Class<? extends CharSequence> c = s.getClass();

        if (c == String.class)
            ((String) s).getChars(start, end, dest, destoff);
@@ -75,7 +76,7 @@ public class TextUtils {
    }

    public static int indexOf(CharSequence s, char ch, int start) {
        Class c = s.getClass();
        Class<? extends CharSequence> c = s.getClass();

        if (c == String.class)
            return ((String) s).indexOf(ch, start);
@@ -84,7 +85,7 @@ public class TextUtils {
    }

    public static int indexOf(CharSequence s, char ch, int start, int end) {
        Class c = s.getClass();
        Class<? extends CharSequence> c = s.getClass();

        if (s instanceof GetChars || c == StringBuffer.class ||
            c == StringBuilder.class || c == String.class) {
@@ -125,7 +126,7 @@ public class TextUtils {
    }

    public static int lastIndexOf(CharSequence s, char ch, int last) {
        Class c = s.getClass();
        Class<? extends CharSequence> c = s.getClass();

        if (c == String.class)
            return ((String) s).lastIndexOf(ch, last);
@@ -142,7 +143,7 @@ public class TextUtils {

        int end = last + 1;

        Class c = s.getClass();
        Class<? extends CharSequence> c = s.getClass();

        if (s instanceof GetChars || c == StringBuffer.class ||
            c == StringBuilder.class || c == String.class) {
@@ -499,6 +500,7 @@ public class TextUtils {
            return new String(buf);
        }

        @Override
        public String toString() {
            return subSequence(0, length()).toString();
        }
@@ -760,7 +762,7 @@ public class TextUtils {

            if (where >= 0)
                tb.setSpan(sources[i], where, where + sources[i].length(),
                           Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                           Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        for (int i = 0; i < sources.length; i++) {
@@ -1114,7 +1116,6 @@ public class TextUtils {
            int remaining = commaCount + 1;

            int ok = 0;
            int okRemaining = remaining;
            String okFormat = "";

            int w = 0;
@@ -1146,7 +1147,6 @@ public class TextUtils {

                    if (w + moreWid <= avail) {
                        ok = i + 1;
                        okRemaining = remaining;
                        okFormat = format;
                    }
                }
@@ -1179,6 +1179,7 @@ public class TextUtils {
                        MetricAffectingSpan.class);
                MetricAffectingSpan[] spans = sp.getSpans(
                        spanStart, spanEnd, MetricAffectingSpan.class);
                spans = TextUtils.removeEmptySpans(spans, sp, MetricAffectingSpan.class);
                width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
            }
        }
@@ -1537,6 +1538,56 @@ public class TextUtils {
        return false;
    }

    /**
     * Removes empty spans from the <code>spans</code> array.
     *
     * When parsing a Spanned using {@link Spanned#nextSpanTransition(int, int, Class)}, empty spans
     * will (correctly) create span transitions, and calling getSpans on a slice of text bounded by
     * one of these transitions will (correctly) include the empty overlapping span.
     *
     * However, these empty spans should not be taken into account when layouting or rendering the
     * string and this method provides a way to filter getSpans' results accordingly.
     *
     * @param spans A list of spans retrieved using {@link Spanned#getSpans(int, int, Class)} from
     * the <code>spanned</code>
     * @param spanned The Spanned from which spans were extracted
     * @return A subset of spans where empty spans ({@link Spanned#getSpanStart(Object)}  ==
     * {@link Spanned#getSpanEnd(Object)} have been removed. The initial order is preserved
     * @hide
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] removeEmptySpans(T[] spans, Spanned spanned, Class<T> klass) {
        T[] copy = null;
        int count = 0;

        for (int i = 0; i < spans.length; i++) {
            final T span = spans[i];
            final int start = spanned.getSpanStart(span);
            final int end = spanned.getSpanEnd(span);

            if (start == end) {
                if (copy == null) {
                    copy = (T[]) Array.newInstance(klass, spans.length - 1);
                    System.arraycopy(spans, 0, copy, 0, i);
                    count = i;
                }
            } else {
                if (copy != null) {
                    copy[count] = span;
                    count++;
                }
            }
        }

        if (copy != null) {
            T[] result = (T[]) Array.newInstance(klass, count);
            System.arraycopy(copy, 0, result, 0, count);
            return result;
        } else {
            return spans;
        }
    }

    private static Object sLock = new Object();
    private static char[] sTemp = null;
}
+106 −13
Original line number Diff line number Diff line
@@ -16,28 +16,20 @@

package android.text;

import android.graphics.Paint;
import com.google.android.collect.Lists;

import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.test.MoreAsserts;

import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import java.util.ArrayList;
import java.util.List;

import junit.framework.TestCase;

import java.util.List;
import java.util.Map;

/**
 * TextUtilsTest tests {@link TextUtils}.
 */
@@ -354,6 +346,7 @@ public class TextUtilsTest extends TestCase {
            return mString.charAt(off);
        }

        @Override
        public String toString() {
            return mString.toString();
        }
@@ -362,4 +355,104 @@ public class TextUtilsTest extends TestCase {
            return new Wrapper(mString.subSequence(start, end));
        }
    }

    @LargeTest
    public void testRemoveEmptySpans() {
        MockSpanned spanned = new MockSpanned();

        spanned.test();
        spanned.addSpan().test();
        spanned.addSpan().test();
        spanned.addSpan().test();
        spanned.addEmptySpan().test();
        spanned.addSpan().test();
        spanned.addEmptySpan().test();
        spanned.addEmptySpan().test();
        spanned.addSpan().test();

        spanned.clear();
        spanned.addEmptySpan().test();
        spanned.addEmptySpan().test();
        spanned.addEmptySpan().test();
        spanned.addSpan().test();
        spanned.addEmptySpan().test();
        spanned.addSpan().test();

        spanned.clear();
        spanned.addSpan().test();
        spanned.addEmptySpan().test();
        spanned.addSpan().test();
        spanned.addEmptySpan().test();
        spanned.addSpan().test();
        spanned.addSpan().test();
    }

    protected static class MockSpanned implements Spanned {

        private List<Object> allSpans = new ArrayList<Object>();
        private List<Object> nonEmptySpans = new ArrayList<Object>();

        public void clear() {
            allSpans.clear();
            nonEmptySpans.clear();
        }

        public MockSpanned addSpan() {
            Object o = new Object();
            allSpans.add(o);
            nonEmptySpans.add(o);
            return this;
        }

        public MockSpanned addEmptySpan() {
            Object o = new Object();
            allSpans.add(o);
            return this;
        }

        public void test() {
            Object[] nonEmpty = TextUtils.removeEmptySpans(allSpans.toArray(), this, Object.class);
            assertEquals("Mismatched array size", nonEmptySpans.size(), nonEmpty.length);
            for (int i=0; i<nonEmpty.length; i++) {
                assertEquals("Span differ", nonEmptySpans.get(i), nonEmpty[i]);
            }
        }

        public char charAt(int arg0) {
            return 0;
        }

        public int length() {
            return 0;
        }

        public CharSequence subSequence(int arg0, int arg1) {
            return null;
        }

        @Override
        public <T> T[] getSpans(int start, int end, Class<T> type) {
            return null;
        }

        @Override
        public int getSpanStart(Object tag) {
            return 0;
        }

        @Override
        public int getSpanEnd(Object tag) {
            return nonEmptySpans.contains(tag) ? 1 : 0;
        }

        @Override
        public int getSpanFlags(Object tag) {
            return 0;
        }

        @Override
        public int nextSpanTransition(int start, int limit, Class type) {
            return 0;
        }
    }
}