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