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.
@@ -850,6 +852,73 @@ private float handleReplacement(ReplacementSpan replacement, TextPaint wp,
850852 return runIsRtl ? -ret : ret ;
851853 }
852854
855+ private static class SpanSet <E > {
856+ final int numberOfSpans ;
857+ final E [] spans ;
858+ final int [] spanStarts ;
859+ final int [] spanEnds ;
860+ final int [] spanFlags ;
861+
862+ @ SuppressWarnings ("unchecked" )
863+ SpanSet (Spanned spanned , int start , int limit , Class <? extends E > type ) {
864+ final E [] allSpans = spanned .getSpans (start , limit , type );
865+ final int length = allSpans .length ;
866+ // These arrays may end up being too large because of empty spans
867+ spans = (E []) Array .newInstance (type , length );
868+ spanStarts = new int [length ];
869+ spanEnds = new int [length ];
870+ spanFlags = new int [length ];
871+
872+ int count = 0 ;
873+ for (int i = 0 ; i < length ; i ++) {
874+ final E span = allSpans [i ];
875+
876+ final int spanStart = spanned .getSpanStart (span );
877+ final int spanEnd = spanned .getSpanEnd (span );
878+ if (spanStart == spanEnd ) continue ;
879+
880+ final int spanFlag = spanned .getSpanFlags (span );
881+ final int priority = spanFlag & Spanned .SPAN_PRIORITY ;
882+ if (priority != 0 && count != 0 ) {
883+ int j ;
884+
885+ for (j = 0 ; j < count ; j ++) {
886+ final int otherPriority = spanFlags [j ] & Spanned .SPAN_PRIORITY ;
887+ if (priority > otherPriority ) break ;
888+ }
889+
890+ System .arraycopy (spans , j , spans , j + 1 , count - j );
891+ System .arraycopy (spanStarts , j , spanStarts , j + 1 , count - j );
892+ System .arraycopy (spanEnds , j , spanEnds , j + 1 , count - j );
893+ System .arraycopy (spanFlags , j , spanFlags , j + 1 , count - j );
894+
895+ spans [j ] = span ;
896+ spanStarts [j ] = spanStart ;
897+ spanEnds [j ] = spanEnd ;
898+ spanFlags [j ] = spanFlag ;
899+ } else {
900+ spans [i ] = span ;
901+ spanStarts [i ] = spanStart ;
902+ spanEnds [i ] = spanEnd ;
903+ spanFlags [i ] = spanFlag ;
904+ }
905+
906+ count ++;
907+ }
908+ numberOfSpans = count ;
909+ }
910+
911+ int getNextTransition (int start , int limit ) {
912+ for (int i = 0 ; i < numberOfSpans ; i ++) {
913+ final int spanStart = spanStarts [i ];
914+ final int spanEnd = spanEnds [i ];
915+ if (spanStart > start && spanStart < limit ) limit = spanStart ;
916+ if (spanEnd > start && spanEnd < limit ) limit = spanEnd ;
917+ }
918+ return limit ;
919+ }
920+ }
921+
853922 /**
854923 * Utility function for handling a unidirectional run. The run must not
855924 * contain tabs or emoji but can contain styles.
@@ -883,66 +952,70 @@ private float handleRun(int start, int measureLimit,
883952 return 0f ;
884953 }
885954
955+ if (mSpanned == null ) {
956+ TextPaint wp = mWorkPaint ;
957+ wp .set (mPaint );
958+ final int mlimit = measureLimit ;
959+ return handleText (wp , start , mlimit , start , limit , runIsRtl , c , x , top ,
960+ y , bottom , fmi , needWidth || mlimit < measureLimit );
961+ }
962+
963+ final SpanSet <MetricAffectingSpan > metricAffectingSpans = new SpanSet <MetricAffectingSpan >(
964+ mSpanned , mStart + start , mStart + limit , MetricAffectingSpan .class );
965+ final SpanSet <CharacterStyle > characterStyleSpans = new SpanSet <CharacterStyle >(
966+ mSpanned , mStart + start , mStart + limit , CharacterStyle .class );
967+
886968 // Shaping needs to take into account context up to metric boundaries,
887969 // but rendering needs to take into account character style boundaries.
888970 // So we iterate through metric runs to get metric bounds,
889971 // then within each metric run iterate through character style runs
890972 // for the run bounds.
891- float ox = x ;
973+ final float originalX = x ;
892974 for (int i = start , inext ; i < measureLimit ; i = inext ) {
893975 TextPaint wp = mWorkPaint ;
894976 wp .set (mPaint );
895977
896- int mlimit ;
897- if (mSpanned == null ) {
898- inext = limit ;
899- mlimit = measureLimit ;
900- } else {
901- inext = mSpanned .nextSpanTransition (mStart + i , mStart + limit ,
902- MetricAffectingSpan .class ) - mStart ;
903-
904- mlimit = inext < measureLimit ? inext : measureLimit ;
905- MetricAffectingSpan [] spans = mSpanned .getSpans (mStart + i ,
906- mStart + mlimit , MetricAffectingSpan .class );
907- spans = TextUtils .removeEmptySpans (spans , mSpanned , MetricAffectingSpan .class );
908-
909- if (spans .length > 0 ) {
910- ReplacementSpan replacement = null ;
911- for (int j = 0 ; j < spans .length ; j ++) {
912- MetricAffectingSpan span = spans [j ];
913- if (span instanceof ReplacementSpan ) {
914- replacement = (ReplacementSpan )span ;
915- } else {
916- // We might have a replacement that uses the draw
917- // state, otherwise measure state would suffice.
918- span .updateDrawState (wp );
919- }
920- }
921-
922- if (replacement != null ) {
923- x += handleReplacement (replacement , wp , i ,
924- mlimit , runIsRtl , c , x , top , y , bottom , fmi ,
925- needWidth || mlimit < measureLimit );
926- continue ;
927- }
978+ inext = metricAffectingSpans .getNextTransition (mStart + i , mStart + limit ) - mStart ;
979+ int mlimit = Math .min (inext , measureLimit );
980+
981+ ReplacementSpan replacement = null ;
982+
983+ for (int j = 0 ; j < metricAffectingSpans .numberOfSpans ; j ++) {
984+ // Both intervals [spanStarts..spanEnds] and [mStart + i..mStart + mlimit] are NOT
985+ // empty by construction. This special case in getSpans() explains the >= & <= tests
986+ if ((metricAffectingSpans .spanStarts [j ] >= mStart + mlimit ) ||
987+ (metricAffectingSpans .spanEnds [j ] <= mStart + i )) continue ;
988+ MetricAffectingSpan span = metricAffectingSpans .spans [j ];
989+ if (span instanceof ReplacementSpan ) {
990+ replacement = (ReplacementSpan )span ;
991+ } else {
992+ // We might have a replacement that uses the draw
993+ // state, otherwise measure state would suffice.
994+ span .updateDrawState (wp );
928995 }
929996 }
930997
931- if (mSpanned == null || c == null ) {
998+ if (replacement != null ) {
999+ x += handleReplacement (replacement , wp , i , mlimit , runIsRtl , c , x , top , y ,
1000+ bottom , fmi , needWidth || mlimit < measureLimit );
1001+ continue ;
1002+ }
1003+
1004+ if (c == null ) {
9321005 x += handleText (wp , i , mlimit , i , inext , runIsRtl , c , x , top ,
9331006 y , bottom , fmi , needWidth || mlimit < measureLimit );
9341007 } else {
9351008 for (int j = i , jnext ; j < mlimit ; j = jnext ) {
936- jnext = mSpanned .nextSpanTransition (mStart + j ,
937- mStart + mlimit , CharacterStyle .class ) - mStart ;
938-
939- CharacterStyle [] spans = mSpanned .getSpans (mStart + j ,
940- mStart + jnext , CharacterStyle .class );
941- spans = TextUtils .removeEmptySpans (spans , mSpanned , CharacterStyle .class );
1009+ jnext = characterStyleSpans .getNextTransition (mStart + j , mStart + mlimit ) -
1010+ mStart ;
9421011
9431012 wp .set (mPaint );
944- for (int k = 0 ; k < spans .length ; k ++) {
945- CharacterStyle span = spans [k ];
1013+ for (int k = 0 ; k < characterStyleSpans .numberOfSpans ; k ++) {
1014+ // Intentionally using >= and <= as explained above
1015+ if ((characterStyleSpans .spanStarts [k ] >= mStart + jnext ) ||
1016+ (characterStyleSpans .spanEnds [k ] <= mStart + j )) continue ;
1017+
1018+ CharacterStyle span = characterStyleSpans .spans [k ];
9461019 span .updateDrawState (wp );
9471020 }
9481021
@@ -952,7 +1025,7 @@ private float handleRun(int start, int measureLimit,
9521025 }
9531026 }
9541027
955- return x - ox ;
1028+ return x - originalX ;
9561029 }
9571030
9581031 /**
@@ -997,8 +1070,7 @@ float ascent(int pos) {
9971070 }
9981071
9991072 pos += mStart ;
1000- MetricAffectingSpan [] spans = mSpanned .getSpans (pos , pos + 1 ,
1001- MetricAffectingSpan .class );
1073+ MetricAffectingSpan [] spans = mSpanned .getSpans (pos , pos + 1 , MetricAffectingSpan .class );
10021074 if (spans .length == 0 ) {
10031075 return mPaint .ascent ();
10041076 }
0 commit comments