3030
3131import com .android .internal .util .ArrayUtils ;
3232
33+ import java .lang .reflect .Array ;
34+
3335/**
3436 * Represents a line of styled text, for measuring in visual order and
3537 * for rendering.
@@ -823,6 +825,73 @@ private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
823825 return runIsRtl ? -ret : ret ;
824826 }
825827
828+ private static class SpanSet <E > {
829+ final int numberOfSpans ;
830+ final E [] spans ;
831+ final int [] spanStarts ;
832+ final int [] spanEnds ;
833+ final int [] spanFlags ;
834+
835+ @ SuppressWarnings ("unchecked" )
836+ SpanSet (Spanned spanned , int start , int limit , Class <? extends E > type ) {
837+ final E [] allSpans = spanned .getSpans (start , limit , type );
838+ final int length = allSpans .length ;
839+ // These arrays may end up being too large because of empty spans
840+ spans = (E []) Array .newInstance (type , length );
841+ spanStarts = new int [length ];
842+ spanEnds = new int [length ];
843+ spanFlags = new int [length ];
844+
845+ int count = 0 ;
846+ for (int i = 0 ; i < length ; i ++) {
847+ final E span = allSpans [i ];
848+
849+ final int spanStart = spanned .getSpanStart (span );
850+ final int spanEnd = spanned .getSpanEnd (span );
851+ if (spanStart == spanEnd ) continue ;
852+
853+ final int spanFlag = spanned .getSpanFlags (span );
854+ final int priority = spanFlag & Spanned .SPAN_PRIORITY ;
855+ if (priority != 0 && count != 0 ) {
856+ int j ;
857+
858+ for (j = 0 ; j < count ; j ++) {
859+ final int otherPriority = spanFlags [j ] & Spanned .SPAN_PRIORITY ;
860+ if (priority > otherPriority ) break ;
861+ }
862+
863+ System .arraycopy (spans , j , spans , j + 1 , count - j );
864+ System .arraycopy (spanStarts , j , spanStarts , j + 1 , count - j );
865+ System .arraycopy (spanEnds , j , spanEnds , j + 1 , count - j );
866+ System .arraycopy (spanFlags , j , spanFlags , j + 1 , count - j );
867+
868+ spans [j ] = span ;
869+ spanStarts [j ] = spanStart ;
870+ spanEnds [j ] = spanEnd ;
871+ spanFlags [j ] = spanFlag ;
872+ } else {
873+ spans [i ] = span ;
874+ spanStarts [i ] = spanStart ;
875+ spanEnds [i ] = spanEnd ;
876+ spanFlags [i ] = spanFlag ;
877+ }
878+
879+ count ++;
880+ }
881+ numberOfSpans = count ;
882+ }
883+
884+ int getNextTransition (int start , int limit ) {
885+ for (int i = 0 ; i < numberOfSpans ; i ++) {
886+ final int spanStart = spanStarts [i ];
887+ final int spanEnd = spanEnds [i ];
888+ if (spanStart > start && spanStart < limit ) limit = spanStart ;
889+ if (spanEnd > start && spanEnd < limit ) limit = spanEnd ;
890+ }
891+ return limit ;
892+ }
893+ }
894+
826895 /**
827896 * Utility function for handling a unidirectional run. The run must not
828897 * contain tabs or emoji but can contain styles.
@@ -856,66 +925,70 @@ private float handleRun(int start, int measureLimit,
856925 return 0f ;
857926 }
858927
928+ if (mSpanned == null ) {
929+ TextPaint wp = mWorkPaint ;
930+ wp .set (mPaint );
931+ final int mlimit = measureLimit ;
932+ return handleText (wp , start , mlimit , start , limit , runIsRtl , c , x , top ,
933+ y , bottom , fmi , needWidth || mlimit < measureLimit );
934+ }
935+
936+ final SpanSet <MetricAffectingSpan > metricAffectingSpans = new SpanSet <MetricAffectingSpan >(
937+ mSpanned , mStart + start , mStart + limit , MetricAffectingSpan .class );
938+ final SpanSet <CharacterStyle > characterStyleSpans = new SpanSet <CharacterStyle >(
939+ mSpanned , mStart + start , mStart + limit , CharacterStyle .class );
940+
859941 // Shaping needs to take into account context up to metric boundaries,
860942 // but rendering needs to take into account character style boundaries.
861943 // So we iterate through metric runs to get metric bounds,
862944 // then within each metric run iterate through character style runs
863945 // for the run bounds.
864- float ox = x ;
946+ final float originalX = x ;
865947 for (int i = start , inext ; i < measureLimit ; i = inext ) {
866948 TextPaint wp = mWorkPaint ;
867949 wp .set (mPaint );
868950
869- int mlimit ;
870- if (mSpanned == null ) {
871- inext = limit ;
872- mlimit = measureLimit ;
873- } else {
874- inext = mSpanned .nextSpanTransition (mStart + i , mStart + limit ,
875- MetricAffectingSpan .class ) - mStart ;
876-
877- mlimit = inext < measureLimit ? inext : measureLimit ;
878- MetricAffectingSpan [] spans = mSpanned .getSpans (mStart + i ,
879- mStart + mlimit , MetricAffectingSpan .class );
880- spans = TextUtils .removeEmptySpans (spans , mSpanned , MetricAffectingSpan .class );
881-
882- if (spans .length > 0 ) {
883- ReplacementSpan replacement = null ;
884- for (int j = 0 ; j < spans .length ; j ++) {
885- MetricAffectingSpan span = spans [j ];
886- if (span instanceof ReplacementSpan ) {
887- replacement = (ReplacementSpan )span ;
888- } else {
889- // We might have a replacement that uses the draw
890- // state, otherwise measure state would suffice.
891- span .updateDrawState (wp );
892- }
893- }
894-
895- if (replacement != null ) {
896- x += handleReplacement (replacement , wp , i ,
897- mlimit , runIsRtl , c , x , top , y , bottom , fmi ,
898- needWidth || mlimit < measureLimit );
899- continue ;
900- }
951+ inext = metricAffectingSpans .getNextTransition (mStart + i , mStart + limit ) - mStart ;
952+ int mlimit = Math .min (inext , measureLimit );
953+
954+ ReplacementSpan replacement = null ;
955+
956+ for (int j = 0 ; j < metricAffectingSpans .numberOfSpans ; j ++) {
957+ // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
958+ // empty by construction. This special case in getSpans() explains the >= & <= tests
959+ if ((metricAffectingSpans .spanStarts [j ] >= mStart + mlimit ) ||
960+ (metricAffectingSpans .spanEnds [j ] <= mStart + i )) continue ;
961+ MetricAffectingSpan span = metricAffectingSpans .spans [j ];
962+ if (span instanceof ReplacementSpan ) {
963+ replacement = (ReplacementSpan )span ;
964+ } else {
965+ // We might have a replacement that uses the draw
966+ // state, otherwise measure state would suffice.
967+ span .updateDrawState (wp );
901968 }
902969 }
903970
904- if (mSpanned == null || c == null ) {
971+ if (replacement != null ) {
972+ x += handleReplacement (replacement , wp , i , mlimit , runIsRtl , c , x , top , y ,
973+ bottom , fmi , needWidth || mlimit < measureLimit );
974+ continue ;
975+ }
976+
977+ if (c == null ) {
905978 x += handleText (wp , i , mlimit , i , inext , runIsRtl , c , x , top ,
906979 y , bottom , fmi , needWidth || mlimit < measureLimit );
907980 } else {
908981 for (int j = i , jnext ; j < mlimit ; j = jnext ) {
909- jnext = mSpanned .nextSpanTransition (mStart + j ,
910- mStart + mlimit , CharacterStyle .class ) - mStart ;
911-
912- CharacterStyle [] spans = mSpanned .getSpans (mStart + j ,
913- mStart + jnext , CharacterStyle .class );
914- spans = TextUtils .removeEmptySpans (spans , mSpanned , CharacterStyle .class );
982+ jnext = characterStyleSpans .getNextTransition (mStart + j , mStart + mlimit ) -
983+ mStart ;
915984
916985 wp .set (mPaint );
917- for (int k = 0 ; k < spans .length ; k ++) {
918- CharacterStyle span = spans [k ];
986+ for (int k = 0 ; k < characterStyleSpans .numberOfSpans ; k ++) {
987+ // Intentionally using >= and <= as explained above
988+ if ((characterStyleSpans .spanStarts [k ] >= mStart + jnext ) ||
989+ (characterStyleSpans .spanEnds [k ] <= mStart + j )) continue ;
990+
991+ CharacterStyle span = characterStyleSpans .spans [k ];
919992 span .updateDrawState (wp );
920993 }
921994
@@ -925,7 +998,7 @@ private float handleRun(int start, int measureLimit,
925998 }
926999 }
9271000
928- return x - ox ;
1001+ return x - originalX ;
9291002 }
9301003
9311004 /**
@@ -970,8 +1043,7 @@ float ascent(int pos) {
9701043 }
9711044
9721045 pos += mStart ;
973- MetricAffectingSpan [] spans = mSpanned .getSpans (pos , pos + 1 ,
974- MetricAffectingSpan .class );
1046+ MetricAffectingSpan [] spans = mSpanned .getSpans (pos , pos + 1 , MetricAffectingSpan .class );
9751047 if (spans .length == 0 ) {
9761048 return mPaint .ascent ();
9771049 }
0 commit comments