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