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