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 */ SpannableStringBuilder()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 */ SpannableStringBuilder(CharSequence text)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 */ SpannableStringBuilder(CharSequence text, int start, int end)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 valueOf(CharSequence source)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 */ charAt(int where)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 */ length()136 public int length() { 137 return mText.length - mGapLength; 138 } 139 resizeFor(int size)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 moveGapTo(int where)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 insert(int where, CharSequence tb, int start, int end)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 insert(int where, CharSequence tb)224 public SpannableStringBuilder insert(int where, CharSequence tb) { 225 return replace(where, where, tb, 0, tb.length()); 226 } 227 228 // Documentation from interface delete(int start, int end)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 clear()239 public void clear() { 240 replace(0, length(), "", 0, 0); 241 mSpanInsertCount = 0; 242 } 243 244 // Documentation from interface clearSpans()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 append(CharSequence text)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 */ append(CharSequence text, Object what, int flags)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 append(CharSequence text, int start, int end)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 append(char text)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) removeSpansForChange(int start, int end, boolean textIsRemoved, int i)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 change(int start, int end, CharSequence cs, int csStart, int csEnd)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 updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd, boolean textIsRemoved)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. removeSpan(int i)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 replace(int start, int end, CharSequence tb)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 replace(final int start, final int end, CharSequence tb, int tbstart, int tbend)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 hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset)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 sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars)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 */ setSpan(Object what, int start, int end, int flags)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. setSpan(boolean send, Object what, int start, int end, int flags)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 isInvalidParagraphStart(int start, int flagsStart)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 isInvalidParagraphEnd(int end, int flagsEnd)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 */ removeSpan(Object what)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 */ resolveGap(int i)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 */ getSpanStart(Object what)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 */ getSpanEnd(Object what)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 */ getSpanFlags(Object what)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") getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind)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 */ getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind, boolean sort)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 countSpans(int queryStart, int queryEnd, Class kind, int i)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") getSpansRec(int queryStart, int queryEnd, Class<T> kind, int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort)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 */ checkSortBuffer(int[] buffer, int size)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 */ sort(T[] array, int[] priority, int[] insertionOrder)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 */ siftDown(int index, T[] array, int size, int[] priority, int[] insertionOrder)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 */ compareSpans(int left, int right, int[] priority, int[] insertionOrder)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 */ nextSpanTransition(int start, int limit, Class kind)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 nextSpanTransitionRec(int start, int limit, Class kind, int i)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 */ subSequence(int start, int end)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 */ getChars(int start, int end, char[] dest, int destoff)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 toString()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 */ substring(int start, int end)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 */ getTextWatcherDepth()1189 public int getTextWatcherDepth() { 1190 return mTextWatcherDepth; 1191 } 1192 sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after)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 sendTextChanged(TextWatcher[] watchers, int start, int before, int after)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 sendAfterTextChanged(TextWatcher[] watchers)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 sendSpanAdded(Object what, int start, int end)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 sendSpanRemoved(Object what, int start, int end)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 sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end)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 region(int start, int end)1252 private static String region(int start, int end) { 1253 return "(" + start + " ... " + end + ")"; 1254 } 1255 checkRange(final String operation, int start, int end)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 */ drawText(Canvas c, int start, int end, float x, float y, Paint p)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 */ drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)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 */ measureText(int start, int end, Paint p)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 */ getTextWidths(int start, int end, float[] widths, Paint p)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 */ getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesPos, Paint p)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 getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p)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 setFilters(InputFilter[] filters)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 getFilters()1536 public InputFilter[] getFilters() { 1537 return mFilters; 1538 } 1539 1540 // Same as SpannableStringInternal 1541 @Override equals(Object o)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 hashCode()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 treeRoot()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) leftChild(int i)1619 private static int leftChild(int i) { 1620 return i - (((i + 1) & ~i) >> 1); 1621 } 1622 rightChild(int i)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. calcMax(int i)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 restoreInvariants()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 invalidateIndex(int i)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