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