summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/text/StaticLayout.java2
-rw-r--r--core/java/android/text/TextLine.java21
-rw-r--r--core/java/android/text/TextUtils.java69
-rw-r--r--core/tests/coretests/src/android/text/TextUtilsTest.java119
4 files changed, 180 insertions, 31 deletions
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index a826a97..9e48eff 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -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);
}
}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 90279d1..1b7f2f3 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -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++) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index d5010c6..30a1f48 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -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();
}
@@ -621,7 +623,7 @@ public class TextUtils {
* Read and return a new CharSequence, possibly with styles,
* from the parcel.
*/
- public CharSequence createFromParcel(Parcel p) {
+ public CharSequence createFromParcel(Parcel p) {
int kind = p.readInt();
if (kind == 1)
@@ -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;
}
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index e111662..e8e56de 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -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;
+ }
+ }
}