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