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 17 package android.text; 18 19 import android.annotation.Nullable; 20 import android.graphics.Canvas; 21 import android.graphics.Paint; 22 import android.util.Log; 23 24 import com.android.internal.util.ArrayUtils; 25 import com.android.internal.util.GrowingArrayUtils; 26 27 import libcore.util.EmptyArray; 28 29 import java.lang.reflect.Array; 30 import java.util.IdentityHashMap; 31 32 /** 33 * This is the class for text whose content and markup can both be changed. 34 */ 35 public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable, 36 Appendable, GraphicsOperations { 37 private final static String TAG = "SpannableStringBuilder"; 38 /** 39 * Create a new SpannableStringBuilder with empty contents 40 */ 41 public SpannableStringBuilder() { 42 this(""); 43 } 44 45 /** 46 * Create a new SpannableStringBuilder containing a copy of the 47 * specified text, including its spans if any. 48 */ 49 public SpannableStringBuilder(CharSequence text) { 50 this(text, 0, text.length()); 51 } 52 53 /** 54 * Create a new SpannableStringBuilder containing a copy of the 55 * specified slice of the specified text, including its spans if any. 56 */ 57 public SpannableStringBuilder(CharSequence text, int start, int end) { 58 int srclen = end - start; 59 60 if (srclen < 0) throw new StringIndexOutOfBoundsException(); 61 62 mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen)); 63 mGapStart = srclen; 64 mGapLength = mText.length - srclen; 65 66 TextUtils.getChars(text, start, end, mText, 0); 67 68 mSpanCount = 0; 69 mSpanInsertCount = 0; 70 mSpans = EmptyArray.OBJECT; 71 mSpanStarts = EmptyArray.INT; 72 mSpanEnds = EmptyArray.INT; 73 mSpanFlags = EmptyArray.INT; 74 mSpanMax = EmptyArray.INT; 75 mSpanOrder = EmptyArray.INT; 76 mPrioSortBuffer = EmptyArray.INT; 77 mOrderSortBuffer = EmptyArray.INT; 78 79 if (text instanceof Spanned) { 80 Spanned sp = (Spanned) text; 81 Object[] spans = sp.getSpans(start, end, Object.class); 82 83 for (int i = 0; i < spans.length; i++) { 84 if (spans[i] instanceof NoCopySpan) { 85 continue; 86 } 87 88 int st = sp.getSpanStart(spans[i]) - start; 89 int en = sp.getSpanEnd(spans[i]) - start; 90 int fl = sp.getSpanFlags(spans[i]); 91 92 if (st < 0) 93 st = 0; 94 if (st > end - start) 95 st = end - start; 96 97 if (en < 0) 98 en = 0; 99 if (en > end - start) 100 en = end - start; 101 102 setSpan(false, spans[i], st, en, fl); 103 } 104 restoreInvariants(); 105 } 106 } 107 108 public static SpannableStringBuilder valueOf(CharSequence source) { 109 if (source instanceof SpannableStringBuilder) { 110 return (SpannableStringBuilder) source; 111 } else { 112 return new SpannableStringBuilder(source); 113 } 114 } 115 116 /** 117 * Return the char at the specified offset within the buffer. 118 */ 119 public char charAt(int where) { 120 int len = length(); 121 if (where < 0) { 122 throw new IndexOutOfBoundsException("charAt: " + where + " < 0"); 123 } else if (where >= len) { 124 throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len); 125 } 126 127 if (where >= mGapStart) 128 return mText[where + mGapLength]; 129 else 130 return mText[where]; 131 } 132 133 /** 134 * Return the number of chars in the buffer. 135 */ 136 public int length() { 137 return mText.length - mGapLength; 138 } 139 140 private void resizeFor(int size) { 141 final int oldLength = mText.length; 142 if (size + 1 <= oldLength) { 143 return; 144 } 145 146 char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size)); 147 System.arraycopy(mText, 0, newText, 0, mGapStart); 148 final int newLength = newText.length; 149 final int delta = newLength - oldLength; 150 final int after = oldLength - (mGapStart + mGapLength); 151 System.arraycopy(mText, oldLength - after, newText, newLength - after, after); 152 mText = newText; 153 154 mGapLength += delta; 155 if (mGapLength < 1) 156 new Exception("mGapLength < 1").printStackTrace(); 157 158 if (mSpanCount != 0) { 159 for (int i = 0; i < mSpanCount; i++) { 160 if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta; 161 if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta; 162 } 163 calcMax(treeRoot()); 164 } 165 } 166 167 private void moveGapTo(int where) { 168 if (where == mGapStart) 169 return; 170 171 boolean atEnd = (where == length()); 172 173 if (where < mGapStart) { 174 int overlap = mGapStart - where; 175 System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap); 176 } else /* where > mGapStart */ { 177 int overlap = where - mGapStart; 178 System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap); 179 } 180 181 // TODO: be more clever (although the win really isn't that big) 182 if (mSpanCount != 0) { 183 for (int i = 0; i < mSpanCount; i++) { 184 int start = mSpanStarts[i]; 185 int end = mSpanEnds[i]; 186 187 if (start > mGapStart) 188 start -= mGapLength; 189 if (start > where) 190 start += mGapLength; 191 else if (start == where) { 192 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; 193 194 if (flag == POINT || (atEnd && flag == PARAGRAPH)) 195 start += mGapLength; 196 } 197 198 if (end > mGapStart) 199 end -= mGapLength; 200 if (end > where) 201 end += mGapLength; 202 else if (end == where) { 203 int flag = (mSpanFlags[i] & END_MASK); 204 205 if (flag == POINT || (atEnd && flag == PARAGRAPH)) 206 end += mGapLength; 207 } 208 209 mSpanStarts[i] = start; 210 mSpanEnds[i] = end; 211 } 212 calcMax(treeRoot()); 213 } 214 215 mGapStart = where; 216 } 217 218 // Documentation from interface 219 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) { 220 return replace(where, where, tb, start, end); 221 } 222 223 // Documentation from interface 224 public SpannableStringBuilder insert(int where, CharSequence tb) { 225 return replace(where, where, tb, 0, tb.length()); 226 } 227 228 // Documentation from interface 229 public SpannableStringBuilder delete(int start, int end) { 230 SpannableStringBuilder ret = replace(start, end, "", 0, 0); 231 232 if (mGapLength > 2 * length()) 233 resizeFor(length()); 234 235 return ret; // == this 236 } 237 238 // Documentation from interface 239 public void clear() { 240 replace(0, length(), "", 0, 0); 241 mSpanInsertCount = 0; 242 } 243 244 // Documentation from interface 245 public void clearSpans() { 246 for (int i = mSpanCount - 1; i >= 0; i--) { 247 Object what = mSpans[i]; 248 int ostart = mSpanStarts[i]; 249 int oend = mSpanEnds[i]; 250 251 if (ostart > mGapStart) 252 ostart -= mGapLength; 253 if (oend > mGapStart) 254 oend -= mGapLength; 255 256 mSpanCount = i; 257 mSpans[i] = null; 258 259 sendSpanRemoved(what, ostart, oend); 260 } 261 if (mIndexOfSpan != null) { 262 mIndexOfSpan.clear(); 263 } 264 mSpanInsertCount = 0; 265 } 266 267 // Documentation from interface 268 public SpannableStringBuilder append(CharSequence text) { 269 int length = length(); 270 return replace(length, length, text, 0, text.length()); 271 } 272 273 /** 274 * Appends the character sequence {@code text} and spans {@code what} over the appended part. 275 * See {@link Spanned} for an explanation of what the flags mean. 276 * @param text the character sequence to append. 277 * @param what the object to be spanned over the appended text. 278 * @param flags see {@link Spanned}. 279 * @return this {@code SpannableStringBuilder}. 280 */ 281 public SpannableStringBuilder append(CharSequence text, Object what, int flags) { 282 int start = length(); 283 append(text); 284 setSpan(what, start, length(), flags); 285 return this; 286 } 287 288 // Documentation from interface 289 public SpannableStringBuilder append(CharSequence text, int start, int end) { 290 int length = length(); 291 return replace(length, length, text, start, end); 292 } 293 294 // Documentation from interface 295 public SpannableStringBuilder append(char text) { 296 return append(String.valueOf(text)); 297 } 298 299 // Returns true if a node was removed (so we can restart search from root) 300 private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i) { 301 if ((i & 1) != 0) { 302 // internal tree node 303 if (resolveGap(mSpanMax[i]) >= start && 304 removeSpansForChange(start, end, textIsRemoved, leftChild(i))) { 305 return true; 306 } 307 } 308 if (i < mSpanCount) { 309 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == 310 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE && 311 mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength && 312 mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength && 313 // The following condition indicates that the span would become empty 314 (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) { 315 mIndexOfSpan.remove(mSpans[i]); 316 removeSpan(i); 317 return true; 318 } 319 return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 && 320 removeSpansForChange(start, end, textIsRemoved, rightChild(i)); 321 } 322 return false; 323 } 324 325 private void change(int start, int end, CharSequence cs, int csStart, int csEnd) { 326 // Can be negative 327 final int replacedLength = end - start; 328 final int replacementLength = csEnd - csStart; 329 final int nbNewChars = replacementLength - replacedLength; 330 331 boolean changed = false; 332 for (int i = mSpanCount - 1; i >= 0; i--) { 333 int spanStart = mSpanStarts[i]; 334 if (spanStart > mGapStart) 335 spanStart -= mGapLength; 336 337 int spanEnd = mSpanEnds[i]; 338 if (spanEnd > mGapStart) 339 spanEnd -= mGapLength; 340 341 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { 342 int ost = spanStart; 343 int oen = spanEnd; 344 int clen = length(); 345 346 if (spanStart > start && spanStart <= end) { 347 for (spanStart = end; spanStart < clen; spanStart++) 348 if (spanStart > end && charAt(spanStart - 1) == '\n') 349 break; 350 } 351 352 if (spanEnd > start && spanEnd <= end) { 353 for (spanEnd = end; spanEnd < clen; spanEnd++) 354 if (spanEnd > end && charAt(spanEnd - 1) == '\n') 355 break; 356 } 357 358 if (spanStart != ost || spanEnd != oen) { 359 setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]); 360 changed = true; 361 } 362 } 363 364 int flags = 0; 365 if (spanStart == start) flags |= SPAN_START_AT_START; 366 else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END; 367 if (spanEnd == start) flags |= SPAN_END_AT_START; 368 else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END; 369 mSpanFlags[i] |= flags; 370 } 371 if (changed) { 372 restoreInvariants(); 373 } 374 375 moveGapTo(end); 376 377 if (nbNewChars >= mGapLength) { 378 resizeFor(mText.length + nbNewChars - mGapLength); 379 } 380 381 final boolean textIsRemoved = replacementLength == 0; 382 // The removal pass needs to be done before the gap is updated in order to broadcast the 383 // correct previous positions to the correct intersecting SpanWatchers 384 if (replacedLength > 0) { // no need for span fixup on pure insertion 385 while (mSpanCount > 0 && 386 removeSpansForChange(start, end, textIsRemoved, treeRoot())) { 387 // keep deleting spans as needed, and restart from root after every deletion 388 // because deletion can invalidate an index. 389 } 390 } 391 392 mGapStart += nbNewChars; 393 mGapLength -= nbNewChars; 394 395 if (mGapLength < 1) 396 new Exception("mGapLength < 1").printStackTrace(); 397 398 TextUtils.getChars(cs, csStart, csEnd, mText, start); 399 400 if (replacedLength > 0) { // no need for span fixup on pure insertion 401 // TODO potential optimization: only update bounds on intersecting spans 402 final boolean atEnd = (mGapStart + mGapLength == mText.length); 403 404 for (int i = 0; i < mSpanCount; i++) { 405 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; 406 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag, 407 atEnd, textIsRemoved); 408 409 final int endFlag = (mSpanFlags[i] & END_MASK); 410 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag, 411 atEnd, textIsRemoved); 412 } 413 // TODO potential optimization: only fix up invariants when bounds actually changed 414 restoreInvariants(); 415 } 416 417 if (cs instanceof Spanned) { 418 Spanned sp = (Spanned) cs; 419 Object[] spans = sp.getSpans(csStart, csEnd, Object.class); 420 421 for (int i = 0; i < spans.length; i++) { 422 int st = sp.getSpanStart(spans[i]); 423 int en = sp.getSpanEnd(spans[i]); 424 425 if (st < csStart) st = csStart; 426 if (en > csEnd) en = csEnd; 427 428 // Add span only if this object is not yet used as a span in this string 429 if (getSpanStart(spans[i]) < 0) { 430 int copySpanStart = st - csStart + start; 431 int copySpanEnd = en - csStart + start; 432 int copySpanFlags = sp.getSpanFlags(spans[i]) | SPAN_ADDED; 433 434 int flagsStart = (copySpanFlags & START_MASK) >> START_SHIFT; 435 int flagsEnd = copySpanFlags & END_MASK; 436 437 if(!isInvalidParagraphStart(copySpanStart, flagsStart) && 438 !isInvalidParagraphEnd(copySpanEnd, flagsEnd)) { 439 setSpan(false, spans[i], copySpanStart, copySpanEnd, copySpanFlags); 440 } 441 } 442 } 443 restoreInvariants(); 444 } 445 } 446 447 private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd, 448 boolean textIsRemoved) { 449 if (offset >= start && offset < mGapStart + mGapLength) { 450 if (flag == POINT) { 451 // A POINT located inside the replaced range should be moved to the end of the 452 // replaced text. 453 // The exception is when the point is at the start of the range and we are doing a 454 // text replacement (as opposed to a deletion): the point stays there. 455 if (textIsRemoved || offset > start) { 456 return mGapStart + mGapLength; 457 } 458 } else { 459 if (flag == PARAGRAPH) { 460 if (atEnd) { 461 return mGapStart + mGapLength; 462 } 463 } else { // MARK 464 // MARKs should be moved to the start, with the exception of a mark located at 465 // the end of the range (which will be < mGapStart + mGapLength since mGapLength 466 // is > 0, which should stay 'unchanged' at the end of the replaced text. 467 if (textIsRemoved || offset < mGapStart - nbNewChars) { 468 return start; 469 } else { 470 // Move to the end of replaced text (needed if nbNewChars != 0) 471 return mGapStart; 472 } 473 } 474 } 475 } 476 return offset; 477 } 478 479 // Note: caller is responsible for removing the mIndexOfSpan entry. 480 private void removeSpan(int i) { 481 Object object = mSpans[i]; 482 483 int start = mSpanStarts[i]; 484 int end = mSpanEnds[i]; 485 486 if (start > mGapStart) start -= mGapLength; 487 if (end > mGapStart) end -= mGapLength; 488 489 int count = mSpanCount - (i + 1); 490 System.arraycopy(mSpans, i + 1, mSpans, i, count); 491 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count); 492 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count); 493 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count); 494 System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count); 495 496 mSpanCount--; 497 498 invalidateIndex(i); 499 mSpans[mSpanCount] = null; 500 501 // Invariants must be restored before sending span removed notifications. 502 restoreInvariants(); 503 504 sendSpanRemoved(object, start, end); 505 } 506 507 // Documentation from interface 508 public SpannableStringBuilder replace(int start, int end, CharSequence tb) { 509 return replace(start, end, tb, 0, tb.length()); 510 } 511 512 // Documentation from interface 513 public SpannableStringBuilder replace(final int start, final int end, 514 CharSequence tb, int tbstart, int tbend) { 515 checkRange("replace", start, end); 516 517 int filtercount = mFilters.length; 518 for (int i = 0; i < filtercount; i++) { 519 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end); 520 521 if (repl != null) { 522 tb = repl; 523 tbstart = 0; 524 tbend = repl.length(); 525 } 526 } 527 528 final int origLen = end - start; 529 final int newLen = tbend - tbstart; 530 531 if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) { 532 // This is a no-op iif there are no spans in tb that would be added (with a 0-length) 533 // Early exit so that the text watchers do not get notified 534 return this; 535 } 536 537 TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class); 538 sendBeforeTextChanged(textWatchers, start, origLen, newLen); 539 540 // Try to keep the cursor / selection at the same relative position during 541 // a text replacement. If replaced or replacement text length is zero, this 542 // is already taken care of. 543 boolean adjustSelection = origLen != 0 && newLen != 0; 544 int selectionStart = 0; 545 int selectionEnd = 0; 546 if (adjustSelection) { 547 selectionStart = Selection.getSelectionStart(this); 548 selectionEnd = Selection.getSelectionEnd(this); 549 } 550 551 change(start, end, tb, tbstart, tbend); 552 553 if (adjustSelection) { 554 boolean changed = false; 555 if (selectionStart > start && selectionStart < end) { 556 final long diff = selectionStart - start; 557 final int offset = Math.toIntExact(diff * newLen / origLen); 558 selectionStart = start + offset; 559 560 changed = true; 561 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart, 562 Spanned.SPAN_POINT_POINT); 563 } 564 if (selectionEnd > start && selectionEnd < end) { 565 final long diff = selectionEnd - start; 566 final int offset = Math.toIntExact(diff * newLen / origLen); 567 selectionEnd = start + offset; 568 569 changed = true; 570 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd, 571 Spanned.SPAN_POINT_POINT); 572 } 573 if (changed) { 574 restoreInvariants(); 575 } 576 } 577 578 sendTextChanged(textWatchers, start, origLen, newLen); 579 sendAfterTextChanged(textWatchers); 580 581 // Span watchers need to be called after text watchers, which may update the layout 582 sendToSpanWatchers(start, end, newLen - origLen); 583 584 return this; 585 } 586 587 private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) { 588 if (text instanceof Spanned) { 589 Spanned spanned = (Spanned) text; 590 Object[] spans = spanned.getSpans(offset, offset, Object.class); 591 final int length = spans.length; 592 for (int i = 0; i < length; i++) { 593 Object span = spans[i]; 594 int flags = spanned.getSpanFlags(span); 595 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true; 596 } 597 } 598 return false; 599 } 600 601 private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) { 602 for (int i = 0; i < mSpanCount; i++) { 603 int spanFlags = mSpanFlags[i]; 604 605 // This loop handles only modified (not added) spans. 606 if ((spanFlags & SPAN_ADDED) != 0) continue; 607 int spanStart = mSpanStarts[i]; 608 int spanEnd = mSpanEnds[i]; 609 if (spanStart > mGapStart) spanStart -= mGapLength; 610 if (spanEnd > mGapStart) spanEnd -= mGapLength; 611 612 int newReplaceEnd = replaceEnd + nbNewChars; 613 boolean spanChanged = false; 614 615 int previousSpanStart = spanStart; 616 if (spanStart > newReplaceEnd) { 617 if (nbNewChars != 0) { 618 previousSpanStart -= nbNewChars; 619 spanChanged = true; 620 } 621 } else if (spanStart >= replaceStart) { 622 // No change if span start was already at replace interval boundaries before replace 623 if ((spanStart != replaceStart || 624 ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) && 625 (spanStart != newReplaceEnd || 626 ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) { 627 // TODO A correct previousSpanStart cannot be computed at this point. 628 // It would require to save all the previous spans' positions before the replace 629 // Using an invalid -1 value to convey this would break the broacast range 630 spanChanged = true; 631 } 632 } 633 634 int previousSpanEnd = spanEnd; 635 if (spanEnd > newReplaceEnd) { 636 if (nbNewChars != 0) { 637 previousSpanEnd -= nbNewChars; 638 spanChanged = true; 639 } 640 } else if (spanEnd >= replaceStart) { 641 // No change if span start was already at replace interval boundaries before replace 642 if ((spanEnd != replaceStart || 643 ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) && 644 (spanEnd != newReplaceEnd || 645 ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) { 646 // TODO same as above for previousSpanEnd 647 spanChanged = true; 648 } 649 } 650 651 if (spanChanged) { 652 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd); 653 } 654 mSpanFlags[i] &= ~SPAN_START_END_MASK; 655 } 656 657 // Handle added spans 658 for (int i = 0; i < mSpanCount; i++) { 659 int spanFlags = mSpanFlags[i]; 660 if ((spanFlags & SPAN_ADDED) != 0) { 661 mSpanFlags[i] &= ~SPAN_ADDED; 662 int spanStart = mSpanStarts[i]; 663 int spanEnd = mSpanEnds[i]; 664 if (spanStart > mGapStart) spanStart -= mGapLength; 665 if (spanEnd > mGapStart) spanEnd -= mGapLength; 666 sendSpanAdded(mSpans[i], spanStart, spanEnd); 667 } 668 } 669 } 670 671 /** 672 * Mark the specified range of text with the specified object. 673 * The flags determine how the span will behave when text is 674 * inserted at the start or end of the span's range. 675 */ 676 public void setSpan(Object what, int start, int end, int flags) { 677 setSpan(true, what, start, end, flags); 678 } 679 680 // Note: if send is false, then it is the caller's responsibility to restore 681 // invariants. If send is false and the span already exists, then this method 682 // will not change the index of any spans. 683 private void setSpan(boolean send, Object what, int start, int end, int flags) { 684 checkRange("setSpan", start, end); 685 686 int flagsStart = (flags & START_MASK) >> START_SHIFT; 687 if(isInvalidParagraphStart(start, flagsStart)) { 688 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary"); 689 } 690 691 int flagsEnd = flags & END_MASK; 692 if(isInvalidParagraphEnd(end, flagsEnd)) { 693 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary"); 694 } 695 696 // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE 697 if (flagsStart == POINT && flagsEnd == MARK && start == end) { 698 if (send) { 699 Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length"); 700 } 701 // Silently ignore invalid spans when they are created from this class. 702 // This avoids the duplication of the above test code before all the 703 // calls to setSpan that are done in this class 704 return; 705 } 706 707 int nstart = start; 708 int nend = end; 709 710 if (start > mGapStart) { 711 start += mGapLength; 712 } else if (start == mGapStart) { 713 if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length())) 714 start += mGapLength; 715 } 716 717 if (end > mGapStart) { 718 end += mGapLength; 719 } else if (end == mGapStart) { 720 if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length())) 721 end += mGapLength; 722 } 723 724 if (mIndexOfSpan != null) { 725 Integer index = mIndexOfSpan.get(what); 726 if (index != null) { 727 int i = index; 728 int ostart = mSpanStarts[i]; 729 int oend = mSpanEnds[i]; 730 731 if (ostart > mGapStart) 732 ostart -= mGapLength; 733 if (oend > mGapStart) 734 oend -= mGapLength; 735 736 mSpanStarts[i] = start; 737 mSpanEnds[i] = end; 738 mSpanFlags[i] = flags; 739 740 if (send) { 741 restoreInvariants(); 742 sendSpanChanged(what, ostart, oend, nstart, nend); 743 } 744 745 return; 746 } 747 } 748 749 mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what); 750 mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start); 751 mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end); 752 mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags); 753 mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount); 754 invalidateIndex(mSpanCount); 755 mSpanCount++; 756 mSpanInsertCount++; 757 // Make sure there is enough room for empty interior nodes. 758 // This magic formula computes the size of the smallest perfect binary 759 // tree no smaller than mSpanCount. 760 int sizeOfMax = 2 * treeRoot() + 1; 761 if (mSpanMax.length < sizeOfMax) { 762 mSpanMax = new int[sizeOfMax]; 763 } 764 765 if (send) { 766 restoreInvariants(); 767 sendSpanAdded(what, nstart, nend); 768 } 769 } 770 771 private final boolean isInvalidParagraphStart(int start, int flagsStart) { 772 if (flagsStart == PARAGRAPH) { 773 if (start != 0 && start != length()) { 774 char c = charAt(start - 1); 775 776 if (c != '\n') return true; 777 } 778 } 779 return false; 780 } 781 782 private final boolean isInvalidParagraphEnd(int end, int flagsEnd) { 783 if (flagsEnd == PARAGRAPH) { 784 if (end != 0 && end != length()) { 785 char c = charAt(end - 1); 786 787 if (c != '\n') return true; 788 } 789 } 790 return false; 791 } 792 793 /** 794 * Remove the specified markup object from the buffer. 795 */ 796 public void removeSpan(Object what) { 797 if (mIndexOfSpan == null) return; 798 Integer i = mIndexOfSpan.remove(what); 799 if (i != null) { 800 removeSpan(i.intValue()); 801 } 802 } 803 804 /** 805 * Return externally visible offset given offset into gapped buffer. 806 */ 807 private int resolveGap(int i) { 808 return i > mGapStart ? i - mGapLength : i; 809 } 810 811 /** 812 * Return the buffer offset of the beginning of the specified 813 * markup object, or -1 if it is not attached to this buffer. 814 */ 815 public int getSpanStart(Object what) { 816 if (mIndexOfSpan == null) return -1; 817 Integer i = mIndexOfSpan.get(what); 818 return i == null ? -1 : resolveGap(mSpanStarts[i]); 819 } 820 821 /** 822 * Return the buffer offset of the end of the specified 823 * markup object, or -1 if it is not attached to this buffer. 824 */ 825 public int getSpanEnd(Object what) { 826 if (mIndexOfSpan == null) return -1; 827 Integer i = mIndexOfSpan.get(what); 828 return i == null ? -1 : resolveGap(mSpanEnds[i]); 829 } 830 831 /** 832 * Return the flags of the end of the specified 833 * markup object, or 0 if it is not attached to this buffer. 834 */ 835 public int getSpanFlags(Object what) { 836 if (mIndexOfSpan == null) return 0; 837 Integer i = mIndexOfSpan.get(what); 838 return i == null ? 0 : mSpanFlags[i]; 839 } 840 841 /** 842 * Return an array of the spans of the specified type that overlap 843 * the specified range of the buffer. The kind may be Object.class to get 844 * a list of all the spans regardless of type. 845 */ 846 @SuppressWarnings("unchecked") 847 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) { 848 return getSpans(queryStart, queryEnd, kind, true); 849 } 850 851 /** 852 * Return an array of the spans of the specified type that overlap 853 * the specified range of the buffer. The kind may be Object.class to get 854 * a list of all the spans regardless of type. 855 * 856 * @param queryStart Start index. 857 * @param queryEnd End index. 858 * @param kind Class type to search for. 859 * @param sort If true the results are sorted by the insertion order. 860 * @param <T> 861 * @return Array of the spans. Empty array if no results are found. 862 * 863 * @hide 864 */ 865 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind, 866 boolean sort) { 867 if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class); 868 if (mSpanCount == 0) return ArrayUtils.emptyArray(kind); 869 int count = countSpans(queryStart, queryEnd, kind, treeRoot()); 870 if (count == 0) { 871 return ArrayUtils.emptyArray(kind); 872 } 873 874 // Safe conversion, but requires a suppressWarning 875 T[] ret = (T[]) Array.newInstance(kind, count); 876 if (sort) { 877 mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count); 878 mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count); 879 } 880 getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer, 881 mOrderSortBuffer, 0, sort); 882 if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer); 883 return ret; 884 } 885 886 private int countSpans(int queryStart, int queryEnd, Class kind, int i) { 887 int count = 0; 888 if ((i & 1) != 0) { 889 // internal tree node 890 int left = leftChild(i); 891 int spanMax = mSpanMax[left]; 892 if (spanMax > mGapStart) { 893 spanMax -= mGapLength; 894 } 895 if (spanMax >= queryStart) { 896 count = countSpans(queryStart, queryEnd, kind, left); 897 } 898 } 899 if (i < mSpanCount) { 900 int spanStart = mSpanStarts[i]; 901 if (spanStart > mGapStart) { 902 spanStart -= mGapLength; 903 } 904 if (spanStart <= queryEnd) { 905 int spanEnd = mSpanEnds[i]; 906 if (spanEnd > mGapStart) { 907 spanEnd -= mGapLength; 908 } 909 if (spanEnd >= queryStart && 910 (spanStart == spanEnd || queryStart == queryEnd || 911 (spanStart != queryEnd && spanEnd != queryStart)) && 912 (Object.class == kind || kind.isInstance(mSpans[i]))) { 913 count++; 914 } 915 if ((i & 1) != 0) { 916 count += countSpans(queryStart, queryEnd, kind, rightChild(i)); 917 } 918 } 919 } 920 return count; 921 } 922 923 /** 924 * Fills the result array with the spans found under the current interval tree node. 925 * 926 * @param queryStart Start index for the interval query. 927 * @param queryEnd End index for the interval query. 928 * @param kind Class type to search for. 929 * @param i Index of the current tree node. 930 * @param ret Array to be filled with results. 931 * @param priority Buffer to keep record of the priorities of spans found. 932 * @param insertionOrder Buffer to keep record of the insertion orders of spans found. 933 * @param count The number of found spans. 934 * @param sort Flag to fill the priority and insertion order buffers. If false then 935 * the spans with priority flag will be sorted in the result array. 936 * @param <T> 937 * @return The total number of spans found. 938 */ 939 @SuppressWarnings("unchecked") 940 private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind, 941 int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) { 942 if ((i & 1) != 0) { 943 // internal tree node 944 int left = leftChild(i); 945 int spanMax = mSpanMax[left]; 946 if (spanMax > mGapStart) { 947 spanMax -= mGapLength; 948 } 949 if (spanMax >= queryStart) { 950 count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority, 951 insertionOrder, count, sort); 952 } 953 } 954 if (i >= mSpanCount) return count; 955 int spanStart = mSpanStarts[i]; 956 if (spanStart > mGapStart) { 957 spanStart -= mGapLength; 958 } 959 if (spanStart <= queryEnd) { 960 int spanEnd = mSpanEnds[i]; 961 if (spanEnd > mGapStart) { 962 spanEnd -= mGapLength; 963 } 964 if (spanEnd >= queryStart && 965 (spanStart == spanEnd || queryStart == queryEnd || 966 (spanStart != queryEnd && spanEnd != queryStart)) && 967 (Object.class == kind || kind.isInstance(mSpans[i]))) { 968 int spanPriority = mSpanFlags[i] & SPAN_PRIORITY; 969 int target = count; 970 if (sort) { 971 priority[target] = spanPriority; 972 insertionOrder[target] = mSpanOrder[i]; 973 } else if (spanPriority != 0) { 974 //insertion sort for elements with priority 975 int j = 0; 976 for (; j < count; j++) { 977 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY; 978 if (spanPriority > p) break; 979 } 980 System.arraycopy(ret, j, ret, j + 1, count - j); 981 target = j; 982 } 983 ret[target] = (T) mSpans[i]; 984 count++; 985 } 986 if (count < ret.length && (i & 1) != 0) { 987 count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority, 988 insertionOrder, count, sort); 989 } 990 } 991 return count; 992 } 993 994 /** 995 * Check the size of the buffer and grow if required. 996 * 997 * @param buffer Buffer to be checked. 998 * @param size Required size. 999 * @return Same buffer instance if the current size is greater than required size. Otherwise a 1000 * new instance is created and returned. 1001 */ 1002 private final int[] checkSortBuffer(int[] buffer, int size) { 1003 if(size > buffer.length) { 1004 return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size)); 1005 } 1006 return buffer; 1007 } 1008 1009 /** 1010 * An iterative heap sort implementation. It will sort the spans using first their priority 1011 * then insertion order. A span with higher priority will be before a span with lower 1012 * priority. If priorities are the same, the spans will be sorted with insertion order. A 1013 * span with a lower insertion order will be before a span with a higher insertion order. 1014 * 1015 * @param array Span array to be sorted. 1016 * @param priority Priorities of the spans 1017 * @param insertionOrder Insertion orders of the spans 1018 * @param <T> Span object type. 1019 * @param <T> 1020 */ 1021 private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) { 1022 int size = array.length; 1023 for (int i = size / 2 - 1; i >= 0; i--) { 1024 siftDown(i, array, size, priority, insertionOrder); 1025 } 1026 1027 for (int i = size - 1; i > 0; i--) { 1028 T v = array[0]; 1029 int prio = priority[0]; 1030 int insertOrder = insertionOrder[0]; 1031 array[0] = array[i]; 1032 priority[0] = priority[i]; 1033 insertionOrder[0] = insertionOrder[i]; 1034 siftDown(0, array, i, priority, insertionOrder); 1035 array[i] = v; 1036 priority[i] = prio; 1037 insertionOrder[i] = insertOrder; 1038 } 1039 } 1040 1041 /** 1042 * Helper function for heap sort. 1043 * 1044 * @param index Index of the element to sift down. 1045 * @param array Span array to be sorted. 1046 * @param size Current heap size. 1047 * @param priority Priorities of the spans 1048 * @param insertionOrder Insertion orders of the spans 1049 * @param <T> Span object type. 1050 */ 1051 private final <T> void siftDown(int index, T[] array, int size, int[] priority, 1052 int[] insertionOrder) { 1053 T v = array[index]; 1054 int prio = priority[index]; 1055 int insertOrder = insertionOrder[index]; 1056 1057 int left = 2 * index + 1; 1058 while (left < size) { 1059 if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) { 1060 left++; 1061 } 1062 if (compareSpans(index, left, priority, insertionOrder) >= 0) { 1063 break; 1064 } 1065 array[index] = array[left]; 1066 priority[index] = priority[left]; 1067 insertionOrder[index] = insertionOrder[left]; 1068 index = left; 1069 left = 2 * index + 1; 1070 } 1071 array[index] = v; 1072 priority[index] = prio; 1073 insertionOrder[index] = insertOrder; 1074 } 1075 1076 /** 1077 * Compare two span elements in an array. Comparison is based first on the priority flag of 1078 * the span, and then the insertion order of the span. 1079 * 1080 * @param left Index of the element to compare. 1081 * @param right Index of the other element to compare. 1082 * @param priority Priorities of the spans 1083 * @param insertionOrder Insertion orders of the spans 1084 * @return 1085 */ 1086 private final int compareSpans(int left, int right, int[] priority, 1087 int[] insertionOrder) { 1088 int priority1 = priority[left]; 1089 int priority2 = priority[right]; 1090 if (priority1 == priority2) { 1091 return Integer.compare(insertionOrder[left], insertionOrder[right]); 1092 } 1093 // since high priority has to be before a lower priority, the arguments to compare are 1094 // opposite of the insertion order check. 1095 return Integer.compare(priority2, priority1); 1096 } 1097 1098 /** 1099 * Return the next offset after <code>start</code> but less than or 1100 * equal to <code>limit</code> where a span of the specified type 1101 * begins or ends. 1102 */ 1103 public int nextSpanTransition(int start, int limit, Class kind) { 1104 if (mSpanCount == 0) return limit; 1105 if (kind == null) { 1106 kind = Object.class; 1107 } 1108 return nextSpanTransitionRec(start, limit, kind, treeRoot()); 1109 } 1110 1111 private int nextSpanTransitionRec(int start, int limit, Class kind, int i) { 1112 if ((i & 1) != 0) { 1113 // internal tree node 1114 int left = leftChild(i); 1115 if (resolveGap(mSpanMax[left]) > start) { 1116 limit = nextSpanTransitionRec(start, limit, kind, left); 1117 } 1118 } 1119 if (i < mSpanCount) { 1120 int st = resolveGap(mSpanStarts[i]); 1121 int en = resolveGap(mSpanEnds[i]); 1122 if (st > start && st < limit && kind.isInstance(mSpans[i])) 1123 limit = st; 1124 if (en > start && en < limit && kind.isInstance(mSpans[i])) 1125 limit = en; 1126 if (st < limit && (i & 1) != 0) { 1127 limit = nextSpanTransitionRec(start, limit, kind, rightChild(i)); 1128 } 1129 } 1130 1131 return limit; 1132 } 1133 1134 /** 1135 * Return a new CharSequence containing a copy of the specified 1136 * range of this buffer, including the overlapping spans. 1137 */ 1138 public CharSequence subSequence(int start, int end) { 1139 return new SpannableStringBuilder(this, start, end); 1140 } 1141 1142 /** 1143 * Copy the specified range of chars from this buffer into the 1144 * specified array, beginning at the specified offset. 1145 */ 1146 public void getChars(int start, int end, char[] dest, int destoff) { 1147 checkRange("getChars", start, end); 1148 1149 if (end <= mGapStart) { 1150 System.arraycopy(mText, start, dest, destoff, end - start); 1151 } else if (start >= mGapStart) { 1152 System.arraycopy(mText, start + mGapLength, dest, destoff, end - start); 1153 } else { 1154 System.arraycopy(mText, start, dest, destoff, mGapStart - start); 1155 System.arraycopy(mText, mGapStart + mGapLength, 1156 dest, destoff + (mGapStart - start), 1157 end - mGapStart); 1158 } 1159 } 1160 1161 /** 1162 * Return a String containing a copy of the chars in this buffer. 1163 */ 1164 @Override 1165 public String toString() { 1166 int len = length(); 1167 char[] buf = new char[len]; 1168 1169 getChars(0, len, buf, 0); 1170 return new String(buf); 1171 } 1172 1173 /** 1174 * Return a String containing a copy of the chars in this buffer, limited to the 1175 * [start, end[ range. 1176 * @hide 1177 */ 1178 public String substring(int start, int end) { 1179 char[] buf = new char[end - start]; 1180 getChars(start, end, buf, 0); 1181 return new String(buf); 1182 } 1183 1184 /** 1185 * Returns the depth of TextWatcher callbacks. Returns 0 when the object is not handling 1186 * TextWatchers. A return value greater than 1 implies that a TextWatcher caused a change that 1187 * recursively triggered a TextWatcher. 1188 */ 1189 public int getTextWatcherDepth() { 1190 return mTextWatcherDepth; 1191 } 1192 1193 private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) { 1194 int n = watchers.length; 1195 1196 mTextWatcherDepth++; 1197 for (int i = 0; i < n; i++) { 1198 watchers[i].beforeTextChanged(this, start, before, after); 1199 } 1200 mTextWatcherDepth--; 1201 } 1202 1203 private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) { 1204 int n = watchers.length; 1205 1206 mTextWatcherDepth++; 1207 for (int i = 0; i < n; i++) { 1208 watchers[i].onTextChanged(this, start, before, after); 1209 } 1210 mTextWatcherDepth--; 1211 } 1212 1213 private void sendAfterTextChanged(TextWatcher[] watchers) { 1214 int n = watchers.length; 1215 1216 mTextWatcherDepth++; 1217 for (int i = 0; i < n; i++) { 1218 watchers[i].afterTextChanged(this); 1219 } 1220 mTextWatcherDepth--; 1221 } 1222 1223 private void sendSpanAdded(Object what, int start, int end) { 1224 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 1225 int n = recip.length; 1226 1227 for (int i = 0; i < n; i++) { 1228 recip[i].onSpanAdded(this, what, start, end); 1229 } 1230 } 1231 1232 private void sendSpanRemoved(Object what, int start, int end) { 1233 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 1234 int n = recip.length; 1235 1236 for (int i = 0; i < n; i++) { 1237 recip[i].onSpanRemoved(this, what, start, end); 1238 } 1239 } 1240 1241 private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) { 1242 // The bounds of a possible SpanWatcher are guaranteed to be set before this method is 1243 // called, so that the order of the span does not affect this broadcast. 1244 SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start), 1245 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class); 1246 int n = spanWatchers.length; 1247 for (int i = 0; i < n; i++) { 1248 spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end); 1249 } 1250 } 1251 1252 private static String region(int start, int end) { 1253 return "(" + start + " ... " + end + ")"; 1254 } 1255 1256 private void checkRange(final String operation, int start, int end) { 1257 if (end < start) { 1258 throw new IndexOutOfBoundsException(operation + " " + 1259 region(start, end) + " has end before start"); 1260 } 1261 1262 int len = length(); 1263 1264 if (start > len || end > len) { 1265 throw new IndexOutOfBoundsException(operation + " " + 1266 region(start, end) + " ends beyond length " + len); 1267 } 1268 1269 if (start < 0 || end < 0) { 1270 throw new IndexOutOfBoundsException(operation + " " + 1271 region(start, end) + " starts before 0"); 1272 } 1273 } 1274 1275 /* 1276 private boolean isprint(char c) { // XXX 1277 if (c >= ' ' && c <= '~') 1278 return true; 1279 else 1280 return false; 1281 } 1282 1283 private static final int startFlag(int flag) { 1284 return (flag >> 4) & 0x0F; 1285 } 1286 1287 private static final int endFlag(int flag) { 1288 return flag & 0x0F; 1289 } 1290 1291 public void dump() { // XXX 1292 for (int i = 0; i < mGapStart; i++) { 1293 System.out.print('|'); 1294 System.out.print(' '); 1295 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 1296 System.out.print(' '); 1297 } 1298 1299 for (int i = mGapStart; i < mGapStart + mGapLength; i++) { 1300 System.out.print('|'); 1301 System.out.print('('); 1302 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 1303 System.out.print(')'); 1304 } 1305 1306 for (int i = mGapStart + mGapLength; i < mText.length; i++) { 1307 System.out.print('|'); 1308 System.out.print(' '); 1309 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 1310 System.out.print(' '); 1311 } 1312 1313 System.out.print('\n'); 1314 1315 for (int i = 0; i < mText.length + 1; i++) { 1316 int found = 0; 1317 int wfound = 0; 1318 1319 for (int j = 0; j < mSpanCount; j++) { 1320 if (mSpanStarts[j] == i) { 1321 found = 1; 1322 wfound = j; 1323 break; 1324 } 1325 1326 if (mSpanEnds[j] == i) { 1327 found = 2; 1328 wfound = j; 1329 break; 1330 } 1331 } 1332 1333 if (found == 1) { 1334 if (startFlag(mSpanFlags[wfound]) == MARK) 1335 System.out.print("( "); 1336 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH) 1337 System.out.print("< "); 1338 else 1339 System.out.print("[ "); 1340 } else if (found == 2) { 1341 if (endFlag(mSpanFlags[wfound]) == POINT) 1342 System.out.print(") "); 1343 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH) 1344 System.out.print("> "); 1345 else 1346 System.out.print("] "); 1347 } else { 1348 System.out.print(" "); 1349 } 1350 } 1351 1352 System.out.print("\n"); 1353 } 1354 */ 1355 1356 /** 1357 * Don't call this yourself -- exists for Canvas to use internally. 1358 * {@hide} 1359 */ 1360 public void drawText(Canvas c, int start, int end, float x, float y, Paint p) { 1361 checkRange("drawText", start, end); 1362 1363 if (end <= mGapStart) { 1364 c.drawText(mText, start, end - start, x, y, p); 1365 } else if (start >= mGapStart) { 1366 c.drawText(mText, start + mGapLength, end - start, x, y, p); 1367 } else { 1368 char[] buf = TextUtils.obtain(end - start); 1369 1370 getChars(start, end, buf, 0); 1371 c.drawText(buf, 0, end - start, x, y, p); 1372 TextUtils.recycle(buf); 1373 } 1374 } 1375 1376 1377 /** 1378 * Don't call this yourself -- exists for Canvas to use internally. 1379 * {@hide} 1380 */ 1381 public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, 1382 float x, float y, boolean isRtl, Paint p) { 1383 checkRange("drawTextRun", start, end); 1384 1385 int contextLen = contextEnd - contextStart; 1386 int len = end - start; 1387 if (contextEnd <= mGapStart) { 1388 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p); 1389 } else if (contextStart >= mGapStart) { 1390 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength, 1391 contextLen, x, y, isRtl, p); 1392 } else { 1393 char[] buf = TextUtils.obtain(contextLen); 1394 getChars(contextStart, contextEnd, buf, 0); 1395 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p); 1396 TextUtils.recycle(buf); 1397 } 1398 } 1399 1400 /** 1401 * Don't call this yourself -- exists for Paint to use internally. 1402 * {@hide} 1403 */ 1404 public float measureText(int start, int end, Paint p) { 1405 checkRange("measureText", start, end); 1406 1407 float ret; 1408 1409 if (end <= mGapStart) { 1410 ret = p.measureText(mText, start, end - start); 1411 } else if (start >= mGapStart) { 1412 ret = p.measureText(mText, start + mGapLength, end - start); 1413 } else { 1414 char[] buf = TextUtils.obtain(end - start); 1415 1416 getChars(start, end, buf, 0); 1417 ret = p.measureText(buf, 0, end - start); 1418 TextUtils.recycle(buf); 1419 } 1420 1421 return ret; 1422 } 1423 1424 /** 1425 * Don't call this yourself -- exists for Paint to use internally. 1426 * {@hide} 1427 */ 1428 public int getTextWidths(int start, int end, float[] widths, Paint p) { 1429 checkRange("getTextWidths", start, end); 1430 1431 int ret; 1432 1433 if (end <= mGapStart) { 1434 ret = p.getTextWidths(mText, start, end - start, widths); 1435 } else if (start >= mGapStart) { 1436 ret = p.getTextWidths(mText, start + mGapLength, end - start, widths); 1437 } else { 1438 char[] buf = TextUtils.obtain(end - start); 1439 1440 getChars(start, end, buf, 0); 1441 ret = p.getTextWidths(buf, 0, end - start, widths); 1442 TextUtils.recycle(buf); 1443 } 1444 1445 return ret; 1446 } 1447 1448 /** 1449 * Don't call this yourself -- exists for Paint to use internally. 1450 * {@hide} 1451 */ 1452 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, 1453 float[] advances, int advancesPos, Paint p) { 1454 1455 float ret; 1456 1457 int contextLen = contextEnd - contextStart; 1458 int len = end - start; 1459 1460 if (end <= mGapStart) { 1461 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, 1462 isRtl, advances, advancesPos); 1463 } else if (start >= mGapStart) { 1464 ret = p.getTextRunAdvances(mText, start + mGapLength, len, 1465 contextStart + mGapLength, contextLen, isRtl, advances, advancesPos); 1466 } else { 1467 char[] buf = TextUtils.obtain(contextLen); 1468 getChars(contextStart, contextEnd, buf, 0); 1469 ret = p.getTextRunAdvances(buf, start - contextStart, len, 1470 0, contextLen, isRtl, advances, advancesPos); 1471 TextUtils.recycle(buf); 1472 } 1473 1474 return ret; 1475 } 1476 1477 /** 1478 * Returns the next cursor position in the run. This avoids placing the cursor between 1479 * surrogates, between characters that form conjuncts, between base characters and combining 1480 * marks, or within a reordering cluster. 1481 * 1482 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric 1483 * span enclosing the cursor in the direction of movement. 1484 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to 1485 * the start of the string.</p> 1486 * 1487 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position, 1488 * this returns -1. Otherwise this will never return a value before contextStart or after 1489 * contextEnd.</p> 1490 * 1491 * @param contextStart the start index of the context 1492 * @param contextEnd the (non-inclusive) end index of the context 1493 * @param dir either DIRECTION_RTL or DIRECTION_LTR 1494 * @param offset the cursor position to move from 1495 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER, 1496 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE, 1497 * CURSOR_AT_OR_BEFORE, or CURSOR_AT 1498 * @param p the Paint object that is requesting this information 1499 * @return the offset of the next position, or -1 1500 * @deprecated This is an internal method, refrain from using it in your code 1501 */ 1502 @Deprecated 1503 public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, 1504 int cursorOpt, Paint p) { 1505 1506 int ret; 1507 1508 int contextLen = contextEnd - contextStart; 1509 if (contextEnd <= mGapStart) { 1510 ret = p.getTextRunCursor(mText, contextStart, contextLen, 1511 dir, offset, cursorOpt); 1512 } else if (contextStart >= mGapStart) { 1513 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen, 1514 dir, offset + mGapLength, cursorOpt) - mGapLength; 1515 } else { 1516 char[] buf = TextUtils.obtain(contextLen); 1517 getChars(contextStart, contextEnd, buf, 0); 1518 ret = p.getTextRunCursor(buf, 0, contextLen, 1519 dir, offset - contextStart, cursorOpt) + contextStart; 1520 TextUtils.recycle(buf); 1521 } 1522 1523 return ret; 1524 } 1525 1526 // Documentation from interface 1527 public void setFilters(InputFilter[] filters) { 1528 if (filters == null) { 1529 throw new IllegalArgumentException(); 1530 } 1531 1532 mFilters = filters; 1533 } 1534 1535 // Documentation from interface 1536 public InputFilter[] getFilters() { 1537 return mFilters; 1538 } 1539 1540 // Same as SpannableStringInternal 1541 @Override 1542 public boolean equals(Object o) { 1543 if (o instanceof Spanned && 1544 toString().equals(o.toString())) { 1545 Spanned other = (Spanned) o; 1546 // Check span data 1547 Object[] otherSpans = other.getSpans(0, other.length(), Object.class); 1548 if (mSpanCount == otherSpans.length) { 1549 for (int i = 0; i < mSpanCount; ++i) { 1550 Object thisSpan = mSpans[i]; 1551 Object otherSpan = otherSpans[i]; 1552 if (thisSpan == this) { 1553 if (other != otherSpan || 1554 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || 1555 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || 1556 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { 1557 return false; 1558 } 1559 } else if (!thisSpan.equals(otherSpan) || 1560 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || 1561 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || 1562 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { 1563 return false; 1564 } 1565 } 1566 return true; 1567 } 1568 } 1569 return false; 1570 } 1571 1572 // Same as SpannableStringInternal 1573 @Override 1574 public int hashCode() { 1575 int hash = toString().hashCode(); 1576 hash = hash * 31 + mSpanCount; 1577 for (int i = 0; i < mSpanCount; ++i) { 1578 Object span = mSpans[i]; 1579 if (span != this) { 1580 hash = hash * 31 + span.hashCode(); 1581 } 1582 hash = hash * 31 + getSpanStart(span); 1583 hash = hash * 31 + getSpanEnd(span); 1584 hash = hash * 31 + getSpanFlags(span); 1585 } 1586 return hash; 1587 } 1588 1589 // Primitives for treating span list as binary tree 1590 1591 // The spans (along with start and end offsets and flags) are stored in linear arrays sorted 1592 // by start offset. For fast searching, there is a binary search structure imposed over these 1593 // arrays. This structure is inorder traversal of a perfect binary tree, a slightly unusual 1594 // but advantageous approach. 1595 1596 // The value-containing nodes are indexed 0 <= i < n (where n = mSpanCount), thus preserving 1597 // logic that accesses the values as a contiguous array. Other balanced binary tree approaches 1598 // (such as a complete binary tree) would require some shuffling of node indices. 1599 1600 // Basic properties of this structure: For a perfect binary tree of height m: 1601 // The tree has 2^(m+1) - 1 total nodes. 1602 // The root of the tree has index 2^m - 1. 1603 // All leaf nodes have even index, all interior nodes odd. 1604 // The height of a node of index i is the number of trailing ones in i's binary representation. 1605 // The left child of a node i of height h is i - 2^(h - 1). 1606 // The right child of a node i of height h is i + 2^(h - 1). 1607 1608 // Note that for arbitrary n, interior nodes of this tree may be >= n. Thus, the general 1609 // structure of a recursive traversal of node i is: 1610 // * traverse left child if i is an interior node 1611 // * process i if i < n 1612 // * traverse right child if i is an interior node and i < n 1613 1614 private int treeRoot() { 1615 return Integer.highestOneBit(mSpanCount) - 1; 1616 } 1617 1618 // (i+1) & ~i is equal to 2^(the number of trailing ones in i) 1619 private static int leftChild(int i) { 1620 return i - (((i + 1) & ~i) >> 1); 1621 } 1622 1623 private static int rightChild(int i) { 1624 return i + (((i + 1) & ~i) >> 1); 1625 } 1626 1627 // The span arrays are also augmented by an mSpanMax[] array that represents an interval tree 1628 // over the binary tree structure described above. For each node, the mSpanMax[] array contains 1629 // the maximum value of mSpanEnds of that node and its descendants. Thus, traversals can 1630 // easily reject subtrees that contain no spans overlapping the area of interest. 1631 1632 // Note that mSpanMax[] also has a valid valuefor interior nodes of index >= n, but which have 1633 // descendants of index < n. In these cases, it simply represents the maximum span end of its 1634 // descendants. This is a consequence of the perfect binary tree structure. 1635 private int calcMax(int i) { 1636 int max = 0; 1637 if ((i & 1) != 0) { 1638 // internal tree node 1639 max = calcMax(leftChild(i)); 1640 } 1641 if (i < mSpanCount) { 1642 max = Math.max(max, mSpanEnds[i]); 1643 if ((i & 1) != 0) { 1644 max = Math.max(max, calcMax(rightChild(i))); 1645 } 1646 } 1647 mSpanMax[i] = max; 1648 return max; 1649 } 1650 1651 // restores binary interval tree invariants after any mutation of span structure 1652 private void restoreInvariants() { 1653 if (mSpanCount == 0) return; 1654 1655 // invariant 1: span starts are nondecreasing 1656 1657 // This is a simple insertion sort because we expect it to be mostly sorted. 1658 for (int i = 1; i < mSpanCount; i++) { 1659 if (mSpanStarts[i] < mSpanStarts[i - 1]) { 1660 Object span = mSpans[i]; 1661 int start = mSpanStarts[i]; 1662 int end = mSpanEnds[i]; 1663 int flags = mSpanFlags[i]; 1664 int insertionOrder = mSpanOrder[i]; 1665 int j = i; 1666 do { 1667 mSpans[j] = mSpans[j - 1]; 1668 mSpanStarts[j] = mSpanStarts[j - 1]; 1669 mSpanEnds[j] = mSpanEnds[j - 1]; 1670 mSpanFlags[j] = mSpanFlags[j - 1]; 1671 mSpanOrder[j] = mSpanOrder[j - 1]; 1672 j--; 1673 } while (j > 0 && start < mSpanStarts[j - 1]); 1674 mSpans[j] = span; 1675 mSpanStarts[j] = start; 1676 mSpanEnds[j] = end; 1677 mSpanFlags[j] = flags; 1678 mSpanOrder[j] = insertionOrder; 1679 invalidateIndex(j); 1680 } 1681 } 1682 1683 // invariant 2: max is max span end for each node and its descendants 1684 calcMax(treeRoot()); 1685 1686 // invariant 3: mIndexOfSpan maps spans back to indices 1687 if (mIndexOfSpan == null) { 1688 mIndexOfSpan = new IdentityHashMap<Object, Integer>(); 1689 } 1690 for (int i = mLowWaterMark; i < mSpanCount; i++) { 1691 Integer existing = mIndexOfSpan.get(mSpans[i]); 1692 if (existing == null || existing != i) { 1693 mIndexOfSpan.put(mSpans[i], i); 1694 } 1695 } 1696 mLowWaterMark = Integer.MAX_VALUE; 1697 } 1698 1699 // Call this on any update to mSpans[], so that mIndexOfSpan can be updated 1700 private void invalidateIndex(int i) { 1701 mLowWaterMark = Math.min(i, mLowWaterMark); 1702 } 1703 1704 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 1705 private InputFilter[] mFilters = NO_FILTERS; 1706 1707 private char[] mText; 1708 private int mGapStart; 1709 private int mGapLength; 1710 1711 private Object[] mSpans; 1712 private int[] mSpanStarts; 1713 private int[] mSpanEnds; 1714 private int[] mSpanMax; // see calcMax() for an explanation of what this array stores 1715 private int[] mSpanFlags; 1716 private int[] mSpanOrder; // store the order of span insertion 1717 private int mSpanInsertCount; // counter for the span insertion 1718 private int[] mPrioSortBuffer; // buffer used to sort getSpans result 1719 private int[] mOrderSortBuffer; // buffer used to sort getSpans result 1720 1721 private int mSpanCount; 1722 private IdentityHashMap<Object, Integer> mIndexOfSpan; 1723 private int mLowWaterMark; // indices below this have not been touched 1724 1725 // TextWatcher callbacks may trigger changes that trigger more callbacks. This keeps track of 1726 // how deep the callbacks go. 1727 private int mTextWatcherDepth; 1728 1729 // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned} 1730 private static final int MARK = 1; 1731 private static final int POINT = 2; 1732 private static final int PARAGRAPH = 3; 1733 1734 private static final int START_MASK = 0xF0; 1735 private static final int END_MASK = 0x0F; 1736 private static final int START_SHIFT = 4; 1737 1738 // These bits are not (currently) used by SPANNED flags 1739 private static final int SPAN_ADDED = 0x800; 1740 private static final int SPAN_START_AT_START = 0x1000; 1741 private static final int SPAN_START_AT_END = 0x2000; 1742 private static final int SPAN_END_AT_START = 0x4000; 1743 private static final int SPAN_END_AT_END = 0x8000; 1744 private static final int SPAN_START_END_MASK = 0xF000; 1745 } 1746