Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.text;
     18 
     19 import android.graphics.Canvas;
     20 import android.graphics.Paint;
     21 
     22 import com.android.internal.util.ArrayUtils;
     23 
     24 import java.lang.reflect.Array;
     25 
     26 /**
     27  * This is the class for text whose content and markup can both be changed.
     28  */
     29 public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
     30         Appendable, GraphicsOperations {
     31     /**
     32      * Create a new SpannableStringBuilder with empty contents
     33      */
     34     public SpannableStringBuilder() {
     35         this("");
     36     }
     37 
     38     /**
     39      * Create a new SpannableStringBuilder containing a copy of the
     40      * specified text, including its spans if any.
     41      */
     42     public SpannableStringBuilder(CharSequence text) {
     43         this(text, 0, text.length());
     44     }
     45 
     46     /**
     47      * Create a new SpannableStringBuilder containing a copy of the
     48      * specified slice of the specified text, including its spans if any.
     49      */
     50     public SpannableStringBuilder(CharSequence text, int start, int end) {
     51         int srclen = end - start;
     52 
     53         int len = ArrayUtils.idealCharArraySize(srclen + 1);
     54         mText = new char[len];
     55         mGapStart = srclen;
     56         mGapLength = len - srclen;
     57 
     58         TextUtils.getChars(text, start, end, mText, 0);
     59 
     60         mSpanCount = 0;
     61         int alloc = ArrayUtils.idealIntArraySize(0);
     62         mSpans = new Object[alloc];
     63         mSpanStarts = new int[alloc];
     64         mSpanEnds = new int[alloc];
     65         mSpanFlags = new int[alloc];
     66 
     67         if (text instanceof Spanned) {
     68             Spanned sp = (Spanned) text;
     69             Object[] spans = sp.getSpans(start, end, Object.class);
     70 
     71             for (int i = 0; i < spans.length; i++) {
     72                 if (spans[i] instanceof NoCopySpan) {
     73                     continue;
     74                 }
     75 
     76                 int st = sp.getSpanStart(spans[i]) - start;
     77                 int en = sp.getSpanEnd(spans[i]) - start;
     78                 int fl = sp.getSpanFlags(spans[i]);
     79 
     80                 if (st < 0)
     81                     st = 0;
     82                 if (st > end - start)
     83                     st = end - start;
     84 
     85                 if (en < 0)
     86                     en = 0;
     87                 if (en > end - start)
     88                     en = end - start;
     89 
     90                 setSpan(spans[i], st, en, fl);
     91             }
     92         }
     93     }
     94 
     95     public static SpannableStringBuilder valueOf(CharSequence source) {
     96         if (source instanceof SpannableStringBuilder) {
     97             return (SpannableStringBuilder) source;
     98         } else {
     99             return new SpannableStringBuilder(source);
    100         }
    101     }
    102 
    103     /**
    104      * Return the char at the specified offset within the buffer.
    105      */
    106     public char charAt(int where) {
    107         int len = length();
    108         if (where < 0) {
    109             throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
    110         } else if (where >= len) {
    111             throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
    112         }
    113 
    114         if (where >= mGapStart)
    115             return mText[where + mGapLength];
    116         else
    117             return mText[where];
    118     }
    119 
    120     /**
    121      * Return the number of chars in the buffer.
    122      */
    123     public int length() {
    124         return mText.length - mGapLength;
    125     }
    126 
    127     private void resizeFor(int size) {
    128         int newlen = ArrayUtils.idealCharArraySize(size + 1);
    129         char[] newtext = new char[newlen];
    130 
    131         int after = mText.length - (mGapStart + mGapLength);
    132 
    133         System.arraycopy(mText, 0, newtext, 0, mGapStart);
    134         System.arraycopy(mText, mText.length - after,
    135                          newtext, newlen - after, after);
    136 
    137         for (int i = 0; i < mSpanCount; i++) {
    138             if (mSpanStarts[i] > mGapStart)
    139                 mSpanStarts[i] += newlen - mText.length;
    140             if (mSpanEnds[i] > mGapStart)
    141                 mSpanEnds[i] += newlen - mText.length;
    142         }
    143 
    144         int oldlen = mText.length;
    145         mText = newtext;
    146         mGapLength += mText.length - oldlen;
    147 
    148         if (mGapLength < 1)
    149             new Exception("mGapLength < 1").printStackTrace();
    150     }
    151 
    152     private void moveGapTo(int where) {
    153         if (where == mGapStart)
    154             return;
    155 
    156         boolean atend = (where == length());
    157 
    158         if (where < mGapStart) {
    159             int overlap = mGapStart - where;
    160 
    161             System.arraycopy(mText, where,
    162                              mText, mGapStart + mGapLength - overlap, overlap);
    163         } else /* where > mGapStart */ {
    164             int overlap = where - mGapStart;
    165 
    166             System.arraycopy(mText, where + mGapLength - overlap,
    167                              mText, mGapStart, overlap);
    168         }
    169 
    170         // XXX be more clever
    171         for (int i = 0; i < mSpanCount; i++) {
    172             int start = mSpanStarts[i];
    173             int end = mSpanEnds[i];
    174 
    175             if (start > mGapStart)
    176                 start -= mGapLength;
    177             if (start > where)
    178                 start += mGapLength;
    179             else if (start == where) {
    180                 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
    181 
    182                 if (flag == POINT || (atend && flag == PARAGRAPH))
    183                     start += mGapLength;
    184             }
    185 
    186             if (end > mGapStart)
    187                 end -= mGapLength;
    188             if (end > where)
    189                 end += mGapLength;
    190             else if (end == where) {
    191                 int flag = (mSpanFlags[i] & END_MASK);
    192 
    193                 if (flag == POINT || (atend && flag == PARAGRAPH))
    194                     end += mGapLength;
    195             }
    196 
    197             mSpanStarts[i] = start;
    198             mSpanEnds[i] = end;
    199         }
    200 
    201         mGapStart = where;
    202     }
    203 
    204     // Documentation from interface
    205     public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
    206         return replace(where, where, tb, start, end);
    207     }
    208 
    209     // Documentation from interface
    210     public SpannableStringBuilder insert(int where, CharSequence tb) {
    211         return replace(where, where, tb, 0, tb.length());
    212     }
    213 
    214     // Documentation from interface
    215     public SpannableStringBuilder delete(int start, int end) {
    216         SpannableStringBuilder ret = replace(start, end, "", 0, 0);
    217 
    218         if (mGapLength > 2 * length())
    219             resizeFor(length());
    220 
    221         return ret; // == this
    222     }
    223 
    224     // Documentation from interface
    225     public void clear() {
    226         replace(0, length(), "", 0, 0);
    227     }
    228 
    229     // Documentation from interface
    230     public void clearSpans() {
    231         for (int i = mSpanCount - 1; i >= 0; i--) {
    232             Object what = mSpans[i];
    233             int ostart = mSpanStarts[i];
    234             int oend = mSpanEnds[i];
    235 
    236             if (ostart > mGapStart)
    237                 ostart -= mGapLength;
    238             if (oend > mGapStart)
    239                 oend -= mGapLength;
    240 
    241             mSpanCount = i;
    242             mSpans[i] = null;
    243 
    244             sendSpanRemoved(what, ostart, oend);
    245         }
    246     }
    247 
    248     // Documentation from interface
    249     public SpannableStringBuilder append(CharSequence text) {
    250         int length = length();
    251         return replace(length, length, text, 0, text.length());
    252     }
    253 
    254     // Documentation from interface
    255     public SpannableStringBuilder append(CharSequence text, int start, int end) {
    256         int length = length();
    257         return replace(length, length, text, start, end);
    258     }
    259 
    260     // Documentation from interface
    261     public SpannableStringBuilder append(char text) {
    262         return append(String.valueOf(text));
    263     }
    264 
    265     private int change(int start, int end, CharSequence tb, int tbstart, int tbend) {
    266         return change(true, start, end, tb, tbstart, tbend);
    267     }
    268 
    269     private int change(boolean notify, int start, int end,
    270                        CharSequence tb, int tbstart, int tbend) {
    271         checkRange("replace", start, end);
    272         int ret = tbend - tbstart;
    273         TextWatcher[] recipients = null;
    274 
    275         if (notify) {
    276             recipients = sendTextWillChange(start, end - start, tbend - tbstart);
    277         }
    278 
    279         for (int i = mSpanCount - 1; i >= 0; i--) {
    280             if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
    281                 int st = mSpanStarts[i];
    282                 if (st > mGapStart)
    283                     st -= mGapLength;
    284 
    285                 int en = mSpanEnds[i];
    286                 if (en > mGapStart)
    287                     en -= mGapLength;
    288 
    289                 int ost = st;
    290                 int oen = en;
    291                 int clen = length();
    292 
    293                 if (st > start && st <= end) {
    294                     for (st = end; st < clen; st++)
    295                         if (st > end && charAt(st - 1) == '\n')
    296                             break;
    297                 }
    298 
    299                 if (en > start && en <= end) {
    300                     for (en = end; en < clen; en++)
    301                         if (en > end && charAt(en - 1) == '\n')
    302                             break;
    303                 }
    304 
    305                 if (st != ost || en != oen)
    306                     setSpan(mSpans[i], st, en, mSpanFlags[i]);
    307             }
    308         }
    309 
    310         moveGapTo(end);
    311 
    312         // Can be negative
    313         final int nbNewChars = (tbend - tbstart) - (end - start);
    314 
    315         if (nbNewChars >= mGapLength) {
    316             resizeFor(mText.length + nbNewChars - mGapLength);
    317         }
    318 
    319         mGapStart += nbNewChars;
    320         mGapLength -= nbNewChars;
    321 
    322         if (mGapLength < 1)
    323             new Exception("mGapLength < 1").printStackTrace();
    324 
    325         TextUtils.getChars(tb, tbstart, tbend, mText, start);
    326 
    327         if (tb instanceof Spanned) {
    328             Spanned sp = (Spanned) tb;
    329             Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
    330 
    331             for (int i = 0; i < spans.length; i++) {
    332                 int st = sp.getSpanStart(spans[i]);
    333                 int en = sp.getSpanEnd(spans[i]);
    334 
    335                 if (st < tbstart)
    336                     st = tbstart;
    337                 if (en > tbend)
    338                     en = tbend;
    339 
    340                 if (getSpanStart(spans[i]) < 0) {
    341                     setSpan(false, spans[i],
    342                             st - tbstart + start,
    343                             en - tbstart + start,
    344                             sp.getSpanFlags(spans[i]));
    345                 }
    346             }
    347         }
    348 
    349         // no need for span fixup on pure insertion
    350         if (tbend > tbstart && end - start == 0) {
    351             if (notify) {
    352                 sendTextChange(recipients, start, end - start, tbend - tbstart);
    353                 sendTextHasChanged(recipients);
    354             }
    355 
    356             return ret;
    357         }
    358 
    359         boolean atend = (mGapStart + mGapLength == mText.length);
    360 
    361         for (int i = mSpanCount - 1; i >= 0; i--) {
    362             if (mSpanStarts[i] >= start &&
    363                 mSpanStarts[i] < mGapStart + mGapLength) {
    364                 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
    365 
    366                 if (flag == POINT || (flag == PARAGRAPH && atend))
    367                     mSpanStarts[i] = mGapStart + mGapLength;
    368                 else
    369                     mSpanStarts[i] = start;
    370             }
    371 
    372             if (mSpanEnds[i] >= start &&
    373                 mSpanEnds[i] < mGapStart + mGapLength) {
    374                 int flag = (mSpanFlags[i] & END_MASK);
    375 
    376                 if (flag == POINT || (flag == PARAGRAPH && atend))
    377                     mSpanEnds[i] = mGapStart + mGapLength;
    378                 else
    379                     mSpanEnds[i] = start;
    380             }
    381 
    382             // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE
    383             if (mSpanEnds[i] < mSpanStarts[i]) {
    384                 removeSpan(i);
    385             }
    386         }
    387 
    388         if (notify) {
    389             sendTextChange(recipients, start, end - start, tbend - tbstart);
    390             sendTextHasChanged(recipients);
    391         }
    392 
    393         return ret;
    394     }
    395 
    396     private void removeSpan(int i) {
    397         Object object = mSpans[i];
    398 
    399         int start = mSpanStarts[i];
    400         int end = mSpanEnds[i];
    401 
    402         if (start > mGapStart) start -= mGapLength;
    403         if (end > mGapStart) end -= mGapLength;
    404 
    405         int count = mSpanCount - (i + 1);
    406         System.arraycopy(mSpans, i + 1, mSpans, i, count);
    407         System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
    408         System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
    409         System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
    410 
    411         mSpanCount--;
    412 
    413         mSpans[mSpanCount] = null;
    414 
    415         sendSpanRemoved(object, start, end);
    416     }
    417 
    418     // Documentation from interface
    419     public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
    420         return replace(start, end, tb, 0, tb.length());
    421     }
    422 
    423     // Documentation from interface
    424     public SpannableStringBuilder replace(final int start, final int end,
    425                         CharSequence tb, int tbstart, int tbend) {
    426         int filtercount = mFilters.length;
    427         for (int i = 0; i < filtercount; i++) {
    428             CharSequence repl = mFilters[i].filter(tb, tbstart, tbend,
    429                                                    this, start, end);
    430 
    431             if (repl != null) {
    432                 tb = repl;
    433                 tbstart = 0;
    434                 tbend = repl.length();
    435             }
    436         }
    437 
    438         if (end == start && tbstart == tbend) {
    439             return this;
    440         }
    441 
    442         if (end == start || tbstart == tbend) {
    443             change(start, end, tb, tbstart, tbend);
    444         } else {
    445             int selstart = Selection.getSelectionStart(this);
    446             int selend = Selection.getSelectionEnd(this);
    447 
    448             // XXX just make the span fixups in change() do the right thing
    449             // instead of this madness!
    450 
    451             checkRange("replace", start, end);
    452             moveGapTo(end);
    453             TextWatcher[] recipients;
    454 
    455             int origlen = end - start;
    456 
    457             recipients = sendTextWillChange(start, origlen, tbend - tbstart);
    458 
    459             if (mGapLength < 2)
    460                 resizeFor(length() + 1);
    461 
    462             for (int i = mSpanCount - 1; i >= 0; i--) {
    463                 if (mSpanStarts[i] == mGapStart)
    464                     mSpanStarts[i]++;
    465 
    466                 if (mSpanEnds[i] == mGapStart)
    467                     mSpanEnds[i]++;
    468             }
    469 
    470             mText[mGapStart] = ' ';
    471             mGapStart++;
    472             mGapLength--;
    473 
    474             if (mGapLength < 1) {
    475                 new Exception("mGapLength < 1").printStackTrace();
    476             }
    477 
    478             int inserted = change(false, start + 1, start + 1, tb, tbstart, tbend);
    479             change(false, start, start + 1, "", 0, 0);
    480             change(false, start + inserted, start + inserted + origlen, "", 0, 0);
    481 
    482             /*
    483              * Special case to keep the cursor in the same position
    484              * if it was somewhere in the middle of the replaced region.
    485              * If it was at the start or the end or crossing the whole
    486              * replacement, it should already be where it belongs.
    487              * TODO: Is there some more general mechanism that could
    488              * accomplish this?
    489              */
    490             if (selstart > start && selstart < end) {
    491                 long off = selstart - start;
    492 
    493                 off = off * inserted / (end - start);
    494                 selstart = (int) off + start;
    495 
    496                 setSpan(false, Selection.SELECTION_START, selstart, selstart,
    497                         Spanned.SPAN_POINT_POINT);
    498             }
    499             if (selend > start && selend < end) {
    500                 long off = selend - start;
    501 
    502                 off = off * inserted / (end - start);
    503                 selend = (int) off + start;
    504 
    505                 setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT);
    506             }
    507             sendTextChange(recipients, start, origlen, inserted);
    508             sendTextHasChanged(recipients);
    509         }
    510 
    511         return this;
    512     }
    513 
    514     /**
    515      * Mark the specified range of text with the specified object.
    516      * The flags determine how the span will behave when text is
    517      * inserted at the start or end of the span's range.
    518      */
    519     public void setSpan(Object what, int start, int end, int flags) {
    520         setSpan(true, what, start, end, flags);
    521     }
    522 
    523     private void setSpan(boolean send, Object what, int start, int end, int flags) {
    524         int nstart = start;
    525         int nend = end;
    526 
    527         checkRange("setSpan", start, end);
    528 
    529         if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) {
    530             if (start != 0 && start != length()) {
    531                 char c = charAt(start - 1);
    532 
    533                 if (c != '\n')
    534                     throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
    535             }
    536         }
    537 
    538         if ((flags & END_MASK) == PARAGRAPH) {
    539             if (end != 0 && end != length()) {
    540                 char c = charAt(end - 1);
    541 
    542                 if (c != '\n')
    543                     throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
    544             }
    545         }
    546 
    547         if (start > mGapStart) {
    548             start += mGapLength;
    549         } else if (start == mGapStart) {
    550             int flag = (flags & START_MASK) >> START_SHIFT;
    551 
    552             if (flag == POINT || (flag == PARAGRAPH && start == length()))
    553                 start += mGapLength;
    554         }
    555 
    556         if (end > mGapStart) {
    557             end += mGapLength;
    558         } else if (end == mGapStart) {
    559             int flag = (flags & END_MASK);
    560 
    561             if (flag == POINT || (flag == PARAGRAPH && end == length()))
    562                 end += mGapLength;
    563         }
    564 
    565         int count = mSpanCount;
    566         Object[] spans = mSpans;
    567 
    568         for (int i = 0; i < count; i++) {
    569             if (spans[i] == what) {
    570                 int ostart = mSpanStarts[i];
    571                 int oend = mSpanEnds[i];
    572 
    573                 if (ostart > mGapStart)
    574                     ostart -= mGapLength;
    575                 if (oend > mGapStart)
    576                     oend -= mGapLength;
    577 
    578                 mSpanStarts[i] = start;
    579                 mSpanEnds[i] = end;
    580                 mSpanFlags[i] = flags;
    581 
    582                 if (send)
    583                     sendSpanChanged(what, ostart, oend, nstart, nend);
    584 
    585                 return;
    586             }
    587         }
    588 
    589         if (mSpanCount + 1 >= mSpans.length) {
    590             int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
    591             Object[] newspans = new Object[newsize];
    592             int[] newspanstarts = new int[newsize];
    593             int[] newspanends = new int[newsize];
    594             int[] newspanflags = new int[newsize];
    595 
    596             System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
    597             System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
    598             System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
    599             System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
    600 
    601             mSpans = newspans;
    602             mSpanStarts = newspanstarts;
    603             mSpanEnds = newspanends;
    604             mSpanFlags = newspanflags;
    605         }
    606 
    607         mSpans[mSpanCount] = what;
    608         mSpanStarts[mSpanCount] = start;
    609         mSpanEnds[mSpanCount] = end;
    610         mSpanFlags[mSpanCount] = flags;
    611         mSpanCount++;
    612 
    613         if (send)
    614             sendSpanAdded(what, nstart, nend);
    615     }
    616 
    617     /**
    618      * Remove the specified markup object from the buffer.
    619      */
    620     public void removeSpan(Object what) {
    621         for (int i = mSpanCount - 1; i >= 0; i--) {
    622             if (mSpans[i] == what) {
    623                 removeSpan(i);
    624                 return;
    625             }
    626         }
    627     }
    628 
    629     /**
    630      * Return the buffer offset of the beginning of the specified
    631      * markup object, or -1 if it is not attached to this buffer.
    632      */
    633     public int getSpanStart(Object what) {
    634         int count = mSpanCount;
    635         Object[] spans = mSpans;
    636 
    637         for (int i = count - 1; i >= 0; i--) {
    638             if (spans[i] == what) {
    639                 int where = mSpanStarts[i];
    640 
    641                 if (where > mGapStart)
    642                     where -= mGapLength;
    643 
    644                 return where;
    645             }
    646         }
    647 
    648         return -1;
    649     }
    650 
    651     /**
    652      * Return the buffer offset of the end of the specified
    653      * markup object, or -1 if it is not attached to this buffer.
    654      */
    655     public int getSpanEnd(Object what) {
    656         int count = mSpanCount;
    657         Object[] spans = mSpans;
    658 
    659         for (int i = count - 1; i >= 0; i--) {
    660             if (spans[i] == what) {
    661                 int where = mSpanEnds[i];
    662 
    663                 if (where > mGapStart)
    664                     where -= mGapLength;
    665 
    666                 return where;
    667             }
    668         }
    669 
    670         return -1;
    671     }
    672 
    673     /**
    674      * Return the flags of the end of the specified
    675      * markup object, or 0 if it is not attached to this buffer.
    676      */
    677     public int getSpanFlags(Object what) {
    678         int count = mSpanCount;
    679         Object[] spans = mSpans;
    680 
    681         for (int i = count - 1; i >= 0; i--) {
    682             if (spans[i] == what) {
    683                 return mSpanFlags[i];
    684             }
    685         }
    686 
    687         return 0;
    688     }
    689 
    690     /**
    691      * Return an array of the spans of the specified type that overlap
    692      * the specified range of the buffer.  The kind may be Object.class to get
    693      * a list of all the spans regardless of type.
    694      */
    695     @SuppressWarnings("unchecked")
    696     public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
    697         if (kind == null) return ArrayUtils.emptyArray(kind);
    698 
    699         int spanCount = mSpanCount;
    700         Object[] spans = mSpans;
    701         int[] starts = mSpanStarts;
    702         int[] ends = mSpanEnds;
    703         int[] flags = mSpanFlags;
    704         int gapstart = mGapStart;
    705         int gaplen = mGapLength;
    706 
    707         int count = 0;
    708         T[] ret = null;
    709         T ret1 = null;
    710 
    711         for (int i = 0; i < spanCount; i++) {
    712             int spanStart = starts[i];
    713             if (spanStart > gapstart) {
    714                 spanStart -= gaplen;
    715             }
    716             if (spanStart > queryEnd) {
    717                 continue;
    718             }
    719 
    720             int spanEnd = ends[i];
    721             if (spanEnd > gapstart) {
    722                 spanEnd -= gaplen;
    723             }
    724             if (spanEnd < queryStart) {
    725                 continue;
    726             }
    727 
    728             if (spanStart != spanEnd && queryStart != queryEnd) {
    729                 if (spanStart == queryEnd)
    730                     continue;
    731                 if (spanEnd == queryStart)
    732                     continue;
    733             }
    734 
    735             // Expensive test, should be performed after the previous tests
    736             if (!kind.isInstance(spans[i])) continue;
    737 
    738             if (count == 0) {
    739                 // Safe conversion thanks to the isInstance test above
    740                 ret1 = (T) spans[i];
    741                 count++;
    742             } else {
    743                 if (count == 1) {
    744                     // Safe conversion, but requires a suppressWarning
    745                     ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
    746                     ret[0] = ret1;
    747                 }
    748 
    749                 int prio = flags[i] & SPAN_PRIORITY;
    750                 if (prio != 0) {
    751                     int j;
    752 
    753                     for (j = 0; j < count; j++) {
    754                         int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
    755 
    756                         if (prio > p) {
    757                             break;
    758                         }
    759                     }
    760 
    761                     System.arraycopy(ret, j, ret, j + 1, count - j);
    762                     // Safe conversion thanks to the isInstance test above
    763                     ret[j] = (T) spans[i];
    764                     count++;
    765                 } else {
    766                     // Safe conversion thanks to the isInstance test above
    767                     ret[count++] = (T) spans[i];
    768                 }
    769             }
    770         }
    771 
    772         if (count == 0) {
    773             return ArrayUtils.emptyArray(kind);
    774         }
    775         if (count == 1) {
    776             // Safe conversion, but requires a suppressWarning
    777             ret = (T[]) Array.newInstance(kind, 1);
    778             ret[0] = ret1;
    779             return ret;
    780         }
    781         if (count == ret.length) {
    782             return ret;
    783         }
    784 
    785         // Safe conversion, but requires a suppressWarning
    786         T[] nret = (T[]) Array.newInstance(kind, count);
    787         System.arraycopy(ret, 0, nret, 0, count);
    788         return nret;
    789     }
    790 
    791     /**
    792      * Return the next offset after <code>start</code> but less than or
    793      * equal to <code>limit</code> where a span of the specified type
    794      * begins or ends.
    795      */
    796     public int nextSpanTransition(int start, int limit, Class kind) {
    797         int count = mSpanCount;
    798         Object[] spans = mSpans;
    799         int[] starts = mSpanStarts;
    800         int[] ends = mSpanEnds;
    801         int gapstart = mGapStart;
    802         int gaplen = mGapLength;
    803 
    804         if (kind == null) {
    805             kind = Object.class;
    806         }
    807 
    808         for (int i = 0; i < count; i++) {
    809             int st = starts[i];
    810             int en = ends[i];
    811 
    812             if (st > gapstart)
    813                 st -= gaplen;
    814             if (en > gapstart)
    815                 en -= gaplen;
    816 
    817             if (st > start && st < limit && kind.isInstance(spans[i]))
    818                 limit = st;
    819             if (en > start && en < limit && kind.isInstance(spans[i]))
    820                 limit = en;
    821         }
    822 
    823         return limit;
    824     }
    825 
    826     /**
    827      * Return a new CharSequence containing a copy of the specified
    828      * range of this buffer, including the overlapping spans.
    829      */
    830     public CharSequence subSequence(int start, int end) {
    831         return new SpannableStringBuilder(this, start, end);
    832     }
    833 
    834     /**
    835      * Copy the specified range of chars from this buffer into the
    836      * specified array, beginning at the specified offset.
    837      */
    838     public void getChars(int start, int end, char[] dest, int destoff) {
    839         checkRange("getChars", start, end);
    840 
    841         if (end <= mGapStart) {
    842             System.arraycopy(mText, start, dest, destoff, end - start);
    843         } else if (start >= mGapStart) {
    844             System.arraycopy(mText, start + mGapLength,
    845                              dest, destoff, end - start);
    846         } else {
    847             System.arraycopy(mText, start, dest, destoff, mGapStart - start);
    848             System.arraycopy(mText, mGapStart + mGapLength,
    849                              dest, destoff + (mGapStart - start),
    850                              end - mGapStart);
    851         }
    852     }
    853 
    854     /**
    855      * Return a String containing a copy of the chars in this buffer.
    856      */
    857     @Override
    858     public String toString() {
    859         int len = length();
    860         char[] buf = new char[len];
    861 
    862         getChars(0, len, buf, 0);
    863         return new String(buf);
    864     }
    865 
    866     /**
    867      * Return a String containing a copy of the chars in this buffer, limited to the
    868      * [start, end[ range.
    869      * @hide
    870      */
    871     public String substring(int start, int end) {
    872         char[] buf = new char[end - start];
    873         getChars(start, end, buf, 0);
    874         return new String(buf);
    875     }
    876 
    877     private TextWatcher[] sendTextWillChange(int start, int before, int after) {
    878         TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class);
    879         int n = recip.length;
    880 
    881         for (int i = 0; i < n; i++) {
    882             recip[i].beforeTextChanged(this, start, before, after);
    883         }
    884 
    885         return recip;
    886     }
    887 
    888     private void sendTextChange(TextWatcher[] recip, int start, int before, int after) {
    889         int n = recip.length;
    890 
    891         for (int i = 0; i < n; i++) {
    892             recip[i].onTextChanged(this, start, before, after);
    893         }
    894     }
    895 
    896     private void sendTextHasChanged(TextWatcher[] recip) {
    897         int n = recip.length;
    898 
    899         for (int i = 0; i < n; i++) {
    900             recip[i].afterTextChanged(this);
    901         }
    902     }
    903 
    904     private void sendSpanAdded(Object what, int start, int end) {
    905         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
    906         int n = recip.length;
    907 
    908         for (int i = 0; i < n; i++) {
    909             recip[i].onSpanAdded(this, what, start, end);
    910         }
    911     }
    912 
    913     private void sendSpanRemoved(Object what, int start, int end) {
    914         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
    915         int n = recip.length;
    916 
    917         for (int i = 0; i < n; i++) {
    918             recip[i].onSpanRemoved(this, what, start, end);
    919         }
    920     }
    921 
    922     private void sendSpanChanged(Object what, int s, int e, int st, int en) {
    923         SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class);
    924         int n = recip.length;
    925 
    926         for (int i = 0; i < n; i++) {
    927             recip[i].onSpanChanged(this, what, s, e, st, en);
    928         }
    929     }
    930 
    931     private static String region(int start, int end) {
    932         return "(" + start + " ... " + end + ")";
    933     }
    934 
    935     private void checkRange(final String operation, int start, int end) {
    936         if (end < start) {
    937             throw new IndexOutOfBoundsException(operation + " " +
    938                                                 region(start, end) +
    939                                                 " has end before start");
    940         }
    941 
    942         int len = length();
    943 
    944         if (start > len || end > len) {
    945             throw new IndexOutOfBoundsException(operation + " " +
    946                                                 region(start, end) +
    947                                                 " ends beyond length " + len);
    948         }
    949 
    950         if (start < 0 || end < 0) {
    951             throw new IndexOutOfBoundsException(operation + " " +
    952                                                 region(start, end) +
    953                                                 " starts before 0");
    954         }
    955     }
    956 
    957 /*
    958     private boolean isprint(char c) { // XXX
    959         if (c >= ' ' && c <= '~')
    960             return true;
    961         else
    962             return false;
    963     }
    964 
    965     private static final int startFlag(int flag) {
    966         return (flag >> 4) & 0x0F;
    967     }
    968 
    969     private static final int endFlag(int flag) {
    970         return flag & 0x0F;
    971     }
    972 
    973     public void dump() { // XXX
    974         for (int i = 0; i < mGapStart; i++) {
    975             System.out.print('|');
    976             System.out.print(' ');
    977             System.out.print(isprint(mText[i]) ? mText[i] : '.');
    978             System.out.print(' ');
    979         }
    980 
    981         for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
    982             System.out.print('|');
    983             System.out.print('(');
    984             System.out.print(isprint(mText[i]) ? mText[i] : '.');
    985             System.out.print(')');
    986         }
    987 
    988         for (int i = mGapStart + mGapLength; i < mText.length; i++) {
    989             System.out.print('|');
    990             System.out.print(' ');
    991             System.out.print(isprint(mText[i]) ? mText[i] : '.');
    992             System.out.print(' ');
    993         }
    994 
    995         System.out.print('\n');
    996 
    997         for (int i = 0; i < mText.length + 1; i++) {
    998             int found = 0;
    999             int wfound = 0;
   1000 
   1001             for (int j = 0; j < mSpanCount; j++) {
   1002                 if (mSpanStarts[j] == i) {
   1003                     found = 1;
   1004                     wfound = j;
   1005                     break;
   1006                 }
   1007 
   1008                 if (mSpanEnds[j] == i) {
   1009                     found = 2;
   1010                     wfound = j;
   1011                     break;
   1012                 }
   1013             }
   1014 
   1015             if (found == 1) {
   1016                 if (startFlag(mSpanFlags[wfound]) == MARK)
   1017                     System.out.print("(   ");
   1018                 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
   1019                     System.out.print("<   ");
   1020                 else
   1021                     System.out.print("[   ");
   1022             } else if (found == 2) {
   1023                 if (endFlag(mSpanFlags[wfound]) == POINT)
   1024                     System.out.print(")   ");
   1025                 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
   1026                     System.out.print(">   ");
   1027                 else
   1028                     System.out.print("]   ");
   1029             } else {
   1030                 System.out.print("    ");
   1031             }
   1032         }
   1033 
   1034         System.out.print("\n");
   1035     }
   1036 */
   1037 
   1038     /**
   1039      * Don't call this yourself -- exists for Canvas to use internally.
   1040      * {@hide}
   1041      */
   1042     public void drawText(Canvas c, int start, int end,
   1043                          float x, float y, Paint p) {
   1044         checkRange("drawText", start, end);
   1045 
   1046         if (end <= mGapStart) {
   1047             c.drawText(mText, start, end - start, x, y, p);
   1048         } else if (start >= mGapStart) {
   1049             c.drawText(mText, start + mGapLength, end - start, x, y, p);
   1050         } else {
   1051             char[] buf = TextUtils.obtain(end - start);
   1052 
   1053             getChars(start, end, buf, 0);
   1054             c.drawText(buf, 0, end - start, x, y, p);
   1055             TextUtils.recycle(buf);
   1056         }
   1057     }
   1058 
   1059 
   1060     /**
   1061      * Don't call this yourself -- exists for Canvas to use internally.
   1062      * {@hide}
   1063      */
   1064     public void drawTextRun(Canvas c, int start, int end,
   1065             int contextStart, int contextEnd,
   1066             float x, float y, int flags, Paint p) {
   1067         checkRange("drawTextRun", start, end);
   1068 
   1069         int contextLen = contextEnd - contextStart;
   1070         int len = end - start;
   1071         if (contextEnd <= mGapStart) {
   1072             c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
   1073         } else if (contextStart >= mGapStart) {
   1074             c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
   1075                     contextLen, x, y, flags, p);
   1076         } else {
   1077             char[] buf = TextUtils.obtain(contextLen);
   1078             getChars(contextStart, contextEnd, buf, 0);
   1079             c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
   1080             TextUtils.recycle(buf);
   1081         }
   1082     }
   1083 
   1084    /**
   1085      * Don't call this yourself -- exists for Paint to use internally.
   1086      * {@hide}
   1087      */
   1088     public float measureText(int start, int end, Paint p) {
   1089         checkRange("measureText", start, end);
   1090 
   1091         float ret;
   1092 
   1093         if (end <= mGapStart) {
   1094             ret = p.measureText(mText, start, end - start);
   1095         } else if (start >= mGapStart) {
   1096             ret = p.measureText(mText, start + mGapLength, end - start);
   1097         } else {
   1098             char[] buf = TextUtils.obtain(end - start);
   1099 
   1100             getChars(start, end, buf, 0);
   1101             ret = p.measureText(buf, 0, end - start);
   1102             TextUtils.recycle(buf);
   1103         }
   1104 
   1105         return ret;
   1106     }
   1107 
   1108     /**
   1109      * Don't call this yourself -- exists for Paint to use internally.
   1110      * {@hide}
   1111      */
   1112     public int getTextWidths(int start, int end, float[] widths, Paint p) {
   1113         checkRange("getTextWidths", start, end);
   1114 
   1115         int ret;
   1116 
   1117         if (end <= mGapStart) {
   1118             ret = p.getTextWidths(mText, start, end - start, widths);
   1119         } else if (start >= mGapStart) {
   1120             ret = p.getTextWidths(mText, start + mGapLength, end - start,
   1121                                   widths);
   1122         } else {
   1123             char[] buf = TextUtils.obtain(end - start);
   1124 
   1125             getChars(start, end, buf, 0);
   1126             ret = p.getTextWidths(buf, 0, end - start, widths);
   1127             TextUtils.recycle(buf);
   1128         }
   1129 
   1130         return ret;
   1131     }
   1132 
   1133     /**
   1134      * Don't call this yourself -- exists for Paint to use internally.
   1135      * {@hide}
   1136      */
   1137     public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
   1138             float[] advances, int advancesPos, Paint p) {
   1139 
   1140         float ret;
   1141 
   1142         int contextLen = contextEnd - contextStart;
   1143         int len = end - start;
   1144 
   1145         if (end <= mGapStart) {
   1146             ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
   1147                     flags, advances, advancesPos);
   1148         } else if (start >= mGapStart) {
   1149             ret = p.getTextRunAdvances(mText, start + mGapLength, len,
   1150                     contextStart + mGapLength, contextLen, flags, advances, advancesPos);
   1151         } else {
   1152             char[] buf = TextUtils.obtain(contextLen);
   1153             getChars(contextStart, contextEnd, buf, 0);
   1154             ret = p.getTextRunAdvances(buf, start - contextStart, len,
   1155                     0, contextLen, flags, advances, advancesPos);
   1156             TextUtils.recycle(buf);
   1157         }
   1158 
   1159         return ret;
   1160     }
   1161 
   1162     /**
   1163      * Don't call this yourself -- exists for Paint to use internally.
   1164      * {@hide}
   1165      */
   1166     public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
   1167             float[] advances, int advancesPos, Paint p, int reserved) {
   1168 
   1169         float ret;
   1170 
   1171         int contextLen = contextEnd - contextStart;
   1172         int len = end - start;
   1173 
   1174         if (end <= mGapStart) {
   1175             ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
   1176                     flags, advances, advancesPos, reserved);
   1177         } else if (start >= mGapStart) {
   1178             ret = p.getTextRunAdvances(mText, start + mGapLength, len,
   1179                     contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
   1180         } else {
   1181             char[] buf = TextUtils.obtain(contextLen);
   1182             getChars(contextStart, contextEnd, buf, 0);
   1183             ret = p.getTextRunAdvances(buf, start - contextStart, len,
   1184                     0, contextLen, flags, advances, advancesPos, reserved);
   1185             TextUtils.recycle(buf);
   1186         }
   1187 
   1188         return ret;
   1189     }
   1190 
   1191     /**
   1192      * Returns the next cursor position in the run.  This avoids placing the cursor between
   1193      * surrogates, between characters that form conjuncts, between base characters and combining
   1194      * marks, or within a reordering cluster.
   1195      *
   1196      * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
   1197      * span enclosing the cursor in the direction of movement.
   1198      * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
   1199      * the start of the string.</p>
   1200      *
   1201      * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
   1202      * this returns -1.  Otherwise this will never return a value before contextStart or after
   1203      * contextEnd.</p>
   1204      *
   1205      * @param contextStart the start index of the context
   1206      * @param contextEnd the (non-inclusive) end index of the context
   1207      * @param flags either DIRECTION_RTL or DIRECTION_LTR
   1208      * @param offset the cursor position to move from
   1209      * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
   1210      * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
   1211      * CURSOR_AT_OR_BEFORE, or CURSOR_AT
   1212      * @param p the Paint object that is requesting this information
   1213      * @return the offset of the next position, or -1
   1214      * @deprecated This is an internal method, refrain from using it in your code
   1215      */
   1216     @Deprecated
   1217     public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
   1218             int cursorOpt, Paint p) {
   1219 
   1220         int ret;
   1221 
   1222         int contextLen = contextEnd - contextStart;
   1223         if (contextEnd <= mGapStart) {
   1224             ret = p.getTextRunCursor(mText, contextStart, contextLen,
   1225                     flags, offset, cursorOpt);
   1226         } else if (contextStart >= mGapStart) {
   1227             ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
   1228                     flags, offset + mGapLength, cursorOpt) - mGapLength;
   1229         } else {
   1230             char[] buf = TextUtils.obtain(contextLen);
   1231             getChars(contextStart, contextEnd, buf, 0);
   1232             ret = p.getTextRunCursor(buf, 0, contextLen,
   1233                     flags, offset - contextStart, cursorOpt) + contextStart;
   1234             TextUtils.recycle(buf);
   1235         }
   1236 
   1237         return ret;
   1238     }
   1239 
   1240     // Documentation from interface
   1241     public void setFilters(InputFilter[] filters) {
   1242         if (filters == null) {
   1243             throw new IllegalArgumentException();
   1244         }
   1245 
   1246         mFilters = filters;
   1247     }
   1248 
   1249     // Documentation from interface
   1250     public InputFilter[] getFilters() {
   1251         return mFilters;
   1252     }
   1253 
   1254     private static final InputFilter[] NO_FILTERS = new InputFilter[0];
   1255     private InputFilter[] mFilters = NO_FILTERS;
   1256 
   1257     private char[] mText;
   1258     private int mGapStart;
   1259     private int mGapLength;
   1260 
   1261     private Object[] mSpans;
   1262     private int[] mSpanStarts;
   1263     private int[] mSpanEnds;
   1264     private int[] mSpanFlags;
   1265     private int mSpanCount;
   1266 
   1267     private static final int POINT = 2;
   1268     private static final int PARAGRAPH = 3;
   1269 
   1270     private static final int START_MASK = 0xF0;
   1271     private static final int END_MASK = 0x0F;
   1272     private static final int START_SHIFT = 4;
   1273 }
   1274