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