TextLine.java revision 673e42fafd4088970ec95e1f13c61dc83132c74e
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.text; 18 19import com.android.internal.util.ArrayUtils; 20 21import android.graphics.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.Paint; 24import android.graphics.Paint.FontMetricsInt; 25import android.graphics.RectF; 26import android.text.Layout.Directions; 27import android.text.Layout.TabStops; 28import android.text.style.CharacterStyle; 29import android.text.style.MetricAffectingSpan; 30import android.text.style.ReplacementSpan; 31import android.util.Log; 32 33/** 34 * Represents a line of styled text, for measuring in visual order and 35 * for rendering. 36 * 37 * <p>Get a new instance using obtain(), and when finished with it, return it 38 * to the pool using recycle(). 39 * 40 * <p>Call set to prepare the instance for use, then either draw, measure, 41 * metrics, or caretToLeftRightOf. 42 * 43 * @hide 44 */ 45class TextLine { 46 private TextPaint mPaint; 47 private CharSequence mText; 48 private int mStart; 49 private int mLen; 50 private int mDir; 51 private Directions mDirections; 52 private boolean mHasTabs; 53 private TabStops mTabs; 54 private char[] mChars; 55 private boolean mCharsValid; 56 private Spanned mSpanned; 57 private final TextPaint mWorkPaint = new TextPaint(); 58 59 private static TextLine[] cached = new TextLine[3]; 60 61 /** 62 * Returns a new TextLine from the shared pool. 63 * 64 * @return an uninitialized TextLine 65 */ 66 static TextLine obtain() { 67 TextLine tl; 68 synchronized (cached) { 69 for (int i = cached.length; --i >= 0;) { 70 if (cached[i] != null) { 71 tl = cached[i]; 72 cached[i] = null; 73 return tl; 74 } 75 } 76 } 77 tl = new TextLine(); 78 Log.v("TLINE", "new: " + tl); 79 return tl; 80 } 81 82 /** 83 * Puts a TextLine back into the shared pool. Do not use this TextLine once 84 * it has been returned. 85 * @param tl the textLine 86 * @return null, as a convenience from clearing references to the provided 87 * TextLine 88 */ 89 static TextLine recycle(TextLine tl) { 90 tl.mText = null; 91 tl.mPaint = null; 92 tl.mDirections = null; 93 if (tl.mLen < 250) { 94 synchronized(cached) { 95 for (int i = 0; i < cached.length; ++i) { 96 if (cached[i] == null) { 97 cached[i] = tl; 98 break; 99 } 100 } 101 } 102 } 103 return null; 104 } 105 106 /** 107 * Initializes a TextLine and prepares it for use. 108 * 109 * @param paint the base paint for the line 110 * @param text the text, can be Styled 111 * @param start the start of the line relative to the text 112 * @param limit the limit of the line relative to the text 113 * @param dir the paragraph direction of this line 114 * @param directions the directions information of this line 115 * @param hasTabs true if the line might contain tabs or emoji 116 * @param tabStops the tabStops. Can be null. 117 */ 118 void set(TextPaint paint, CharSequence text, int start, int limit, int dir, 119 Directions directions, boolean hasTabs, TabStops tabStops) { 120 mPaint = paint; 121 mText = text; 122 mStart = start; 123 mLen = limit - start; 124 mDir = dir; 125 mDirections = directions; 126 mHasTabs = hasTabs; 127 mSpanned = null; 128 129 boolean hasReplacement = false; 130 if (text instanceof Spanned) { 131 mSpanned = (Spanned) text; 132 hasReplacement = mSpanned.getSpans(start, limit, 133 ReplacementSpan.class).length > 0; 134 } 135 136 mCharsValid = hasReplacement || hasTabs || 137 directions != Layout.DIRS_ALL_LEFT_TO_RIGHT; 138 139 if (mCharsValid) { 140 if (mChars == null || mChars.length < mLen) { 141 mChars = new char[ArrayUtils.idealCharArraySize(mLen)]; 142 } 143 TextUtils.getChars(text, start, limit, mChars, 0); 144 if (hasReplacement) { 145 // Handle these all at once so we don't have to do it as we go. 146 // Replace the first character of each replacement run with the 147 // object-replacement character and the remainder with zero width 148 // non-break space aka BOM. Cursor movement code skips these 149 // zero-width characters. 150 char[] chars = mChars; 151 for (int i = start, inext; i < limit; i = inext) { 152 inext = mSpanned.nextSpanTransition(i, limit, 153 ReplacementSpan.class); 154 if (mSpanned.getSpans(i, inext, ReplacementSpan.class) 155 .length > 0) { // transition into a span 156 chars[i - start] = '\ufffc'; 157 for (int j = i - start + 1, e = inext - start; j < e; ++j) { 158 chars[j] = '\ufeff'; // used as ZWNBS, marks positions to skip 159 } 160 } 161 } 162 } 163 } 164 mTabs = tabStops; 165 } 166 167 /** 168 * Renders the TextLine. 169 * 170 * @param c the canvas to render on 171 * @param x the leading margin position 172 * @param top the top of the line 173 * @param y the baseline 174 * @param bottom the bottom of the line 175 */ 176 void draw(Canvas c, float x, int top, int y, int bottom) { 177 if (!mHasTabs) { 178 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { 179 drawRun(c, 0, 0, mLen, false, x, top, y, bottom, false); 180 return; 181 } 182 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { 183 drawRun(c, 0, 0, mLen, true, x, top, y, bottom, false); 184 return; 185 } 186 } 187 188 float h = 0; 189 int[] runs = mDirections.mDirections; 190 RectF emojiRect = null; 191 192 int lastRunIndex = runs.length - 2; 193 for (int i = 0; i < runs.length; i += 2) { 194 int runStart = runs[i]; 195 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK); 196 if (runLimit > mLen) { 197 runLimit = mLen; 198 } 199 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0; 200 201 int segstart = runStart; 202 char[] chars = mChars; 203 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { 204 int codept = 0; 205 Bitmap bm = null; 206 207 if (mHasTabs && j < runLimit) { 208 codept = mChars[j]; 209 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) { 210 codept = Character.codePointAt(mChars, j); 211 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) { 212 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept); 213 } else if (codept > 0xffff) { 214 ++j; 215 continue; 216 } 217 } 218 } 219 220 if (j == runLimit || codept == '\t' || bm != null) { 221 h += drawRun(c, i, segstart, j, runIsRtl, x+h, top, y, bottom, 222 i != lastRunIndex || j != mLen); 223 224 if (codept == '\t') { 225 h = mDir * nextTab(h * mDir); 226 } else if (bm != null) { 227 float bmAscent = ascent(j); 228 float bitmapHeight = bm.getHeight(); 229 float scale = -bmAscent / bitmapHeight; 230 float width = bm.getWidth() * scale; 231 232 if (emojiRect == null) { 233 emojiRect = new RectF(); 234 } 235 emojiRect.set(x + h, y + bmAscent, 236 x + h + width, y); 237 c.drawBitmap(bm, null, emojiRect, mPaint); 238 h += width; 239 j++; 240 } 241 segstart = j + 1; 242 } 243 } 244 } 245 } 246 247 /** 248 * Returns metrics information for the entire line. 249 * 250 * @param fmi receives font metrics information, can be null 251 * @return the signed width of the line 252 */ 253 float metrics(FontMetricsInt fmi) { 254 return measure(mLen, false, fmi); 255 } 256 257 /** 258 * Returns information about a position on the line. 259 * 260 * @param offset the line-relative character offset, between 0 and the 261 * line length, inclusive 262 * @param trailing true to measure the trailing edge of the character 263 * before offset, false to measure the leading edge of the character 264 * at offset. 265 * @param fmi receives metrics information about the requested 266 * character, can be null. 267 * @return the signed offset from the leading margin to the requested 268 * character edge. 269 */ 270 float measure(int offset, boolean trailing, FontMetricsInt fmi) { 271 int target = trailing ? offset - 1 : offset; 272 if (target < 0) { 273 return 0; 274 } 275 276 float h = 0; 277 278 if (!mHasTabs) { 279 if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { 280 return measureRun(0, 0, offset, mLen, false, fmi); 281 } 282 if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { 283 return measureRun(0, 0, offset, mLen, true, fmi); 284 } 285 } 286 287 char[] chars = mChars; 288 int[] runs = mDirections.mDirections; 289 for (int i = 0; i < runs.length; i += 2) { 290 int runStart = runs[i]; 291 int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK); 292 if (runLimit > mLen) { 293 runLimit = mLen; 294 } 295 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0; 296 297 int segstart = runStart; 298 for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { 299 int codept = 0; 300 Bitmap bm = null; 301 302 if (mHasTabs && j < runLimit) { 303 codept = chars[j]; 304 if (codept >= 0xd800 && codept < 0xdc00 && j + 1 < runLimit) { 305 codept = Character.codePointAt(chars, j); 306 if (codept >= Layout.MIN_EMOJI && codept <= Layout.MAX_EMOJI) { 307 bm = Layout.EMOJI_FACTORY.getBitmapFromAndroidPua(codept); 308 } else if (codept > 0xffff) { 309 ++j; 310 continue; 311 } 312 } 313 } 314 315 if (j == runLimit || codept == '\t' || bm != null) { 316 boolean inSegment = target >= segstart && target < j; 317 318 boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; 319 if (inSegment && advance) { 320 return h += measureRun(i, segstart, offset, j, runIsRtl, fmi); 321 } 322 323 float w = measureRun(i, segstart, j, j, runIsRtl, fmi); 324 h += advance ? w : -w; 325 326 if (inSegment) { 327 return h += measureRun(i, segstart, offset, j, runIsRtl, null); 328 } 329 330 if (codept == '\t') { 331 if (offset == j) { 332 return h; 333 } 334 h = mDir * nextTab(h * mDir); 335 if (target == j) { 336 return h; 337 } 338 } 339 340 if (bm != null) { 341 float bmAscent = ascent(j); 342 float wid = bm.getWidth() * -bmAscent / bm.getHeight(); 343 h += mDir * wid; 344 j++; 345 } 346 347 segstart = j + 1; 348 } 349 } 350 } 351 352 return h; 353 } 354 355 /** 356 * Draws a unidirectional (but possibly multi-styled) run of text. 357 * 358 * @param c the canvas to draw on 359 * @param runIndex the index of this directional run 360 * @param start the line-relative start 361 * @param limit the line-relative limit 362 * @param runIsRtl true if the run is right-to-left 363 * @param x the position of the run that is closest to the leading margin 364 * @param top the top of the line 365 * @param y the baseline 366 * @param bottom the bottom of the line 367 * @param needWidth true if the width value is required. 368 * @return the signed width of the run, based on the paragraph direction. 369 * Only valid if needWidth is true. 370 */ 371 private float drawRun(Canvas c, int runIndex, int start, 372 int limit, boolean runIsRtl, float x, int top, int y, int bottom, 373 boolean needWidth) { 374 375 if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { 376 float w = -measureRun(runIndex, start, limit, limit, runIsRtl, null); 377 handleRun(runIndex, start, limit, limit, runIsRtl, c, x + w, top, 378 y, bottom, null, false); 379 return w; 380 } 381 382 return handleRun(runIndex, start, limit, limit, runIsRtl, c, x, top, 383 y, bottom, null, needWidth); 384 } 385 386 /** 387 * Measures a unidirectional (but possibly multi-styled) run of text. 388 * 389 * @param runIndex the run index 390 * @param start the line-relative start of the run 391 * @param offset the offset to measure to, between start and limit inclusive 392 * @param limit the line-relative limit of the run 393 * @param runIsRtl true if the run is right-to-left 394 * @param fmi receives metrics information about the requested 395 * run, can be null. 396 * @return the signed width from the start of the run to the leading edge 397 * of the character at offset, based on the run (not paragraph) direction 398 */ 399 private float measureRun(int runIndex, int start, 400 int offset, int limit, boolean runIsRtl, FontMetricsInt fmi) { 401 return handleRun(runIndex, start, offset, limit, runIsRtl, null, 402 0, 0, 0, 0, fmi, true); 403 } 404 405 /** 406 * Walk the cursor through this line, skipping conjuncts and 407 * zero-width characters. 408 * 409 * <p>This function cannot properly walk the cursor off the ends of the line 410 * since it does not know about any shaping on the previous/following line 411 * that might affect the cursor position. Callers must either avoid these 412 * situations or handle the result specially. 413 * 414 * @param cursor the starting position of the cursor, between 0 and the 415 * length of the line, inclusive 416 * @param toLeft true if the caret is moving to the left. 417 * @return the new offset. If it is less than 0 or greater than the length 418 * of the line, the previous/following line should be examined to get the 419 * actual offset. 420 */ 421 int getOffsetToLeftRightOf(int cursor, boolean toLeft) { 422 // 1) The caret marks the leading edge of a character. The character 423 // logically before it might be on a different level, and the active caret 424 // position is on the character at the lower level. If that character 425 // was the previous character, the caret is on its trailing edge. 426 // 2) Take this character/edge and move it in the indicated direction. 427 // This gives you a new character and a new edge. 428 // 3) This position is between two visually adjacent characters. One of 429 // these might be at a lower level. The active position is on the 430 // character at the lower level. 431 // 4) If the active position is on the trailing edge of the character, 432 // the new caret position is the following logical character, else it 433 // is the character. 434 435 int lineStart = 0; 436 int lineEnd = mLen; 437 boolean paraIsRtl = mDir == -1; 438 int[] runs = mDirections.mDirections; 439 440 int runIndex, runLevel = 0, runStart = lineStart, runLimit = lineEnd, newCaret = -1; 441 boolean trailing = false; 442 443 if (cursor == lineStart) { 444 runIndex = -2; 445 } else if (cursor == lineEnd) { 446 runIndex = runs.length; 447 } else { 448 // First, get information about the run containing the character with 449 // the active caret. 450 for (runIndex = 0; runIndex < runs.length; runIndex += 2) { 451 runStart = lineStart + runs[runIndex]; 452 if (cursor >= runStart) { 453 runLimit = runStart + (runs[runIndex+1] & Layout.RUN_LENGTH_MASK); 454 if (runLimit > lineEnd) { 455 runLimit = lineEnd; 456 } 457 if (cursor < runLimit) { 458 runLevel = (runs[runIndex+1] >>> Layout.RUN_LEVEL_SHIFT) & 459 Layout.RUN_LEVEL_MASK; 460 if (cursor == runStart) { 461 // The caret is on a run boundary, see if we should 462 // use the position on the trailing edge of the previous 463 // logical character instead. 464 int prevRunIndex, prevRunLevel, prevRunStart, prevRunLimit; 465 int pos = cursor - 1; 466 for (prevRunIndex = 0; prevRunIndex < runs.length; prevRunIndex += 2) { 467 prevRunStart = lineStart + runs[prevRunIndex]; 468 if (pos >= prevRunStart) { 469 prevRunLimit = prevRunStart + 470 (runs[prevRunIndex+1] & Layout.RUN_LENGTH_MASK); 471 if (prevRunLimit > lineEnd) { 472 prevRunLimit = lineEnd; 473 } 474 if (pos < prevRunLimit) { 475 prevRunLevel = (runs[prevRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) 476 & Layout.RUN_LEVEL_MASK; 477 if (prevRunLevel < runLevel) { 478 // Start from logically previous character. 479 runIndex = prevRunIndex; 480 runLevel = prevRunLevel; 481 runStart = prevRunStart; 482 runLimit = prevRunLimit; 483 trailing = true; 484 break; 485 } 486 } 487 } 488 } 489 } 490 break; 491 } 492 } 493 } 494 495 // caret might be == lineEnd. This is generally a space or paragraph 496 // separator and has an associated run, but might be the end of 497 // text, in which case it doesn't. If that happens, we ran off the 498 // end of the run list, and runIndex == runs.length. In this case, 499 // we are at a run boundary so we skip the below test. 500 if (runIndex != runs.length) { 501 boolean runIsRtl = (runLevel & 0x1) != 0; 502 boolean advance = toLeft == runIsRtl; 503 if (cursor != (advance ? runLimit : runStart) || advance != trailing) { 504 // Moving within or into the run, so we can move logically. 505 newCaret = getOffsetBeforeAfter(runIndex, runStart, runLimit, 506 runIsRtl, cursor, advance); 507 // If the new position is internal to the run, we're at the strong 508 // position already so we're finished. 509 if (newCaret != (advance ? runLimit : runStart)) { 510 return newCaret; 511 } 512 } 513 } 514 } 515 516 // If newCaret is -1, we're starting at a run boundary and crossing 517 // into another run. Otherwise we've arrived at a run boundary, and 518 // need to figure out which character to attach to. Note we might 519 // need to run this twice, if we cross a run boundary and end up at 520 // another run boundary. 521 while (true) { 522 boolean advance = toLeft == paraIsRtl; 523 int otherRunIndex = runIndex + (advance ? 2 : -2); 524 if (otherRunIndex >= 0 && otherRunIndex < runs.length) { 525 int otherRunStart = lineStart + runs[otherRunIndex]; 526 int otherRunLimit = otherRunStart + 527 (runs[otherRunIndex+1] & Layout.RUN_LENGTH_MASK); 528 if (otherRunLimit > lineEnd) { 529 otherRunLimit = lineEnd; 530 } 531 int otherRunLevel = (runs[otherRunIndex+1] >>> Layout.RUN_LEVEL_SHIFT) & 532 Layout.RUN_LEVEL_MASK; 533 boolean otherRunIsRtl = (otherRunLevel & 1) != 0; 534 535 advance = toLeft == otherRunIsRtl; 536 if (newCaret == -1) { 537 newCaret = getOffsetBeforeAfter(otherRunIndex, otherRunStart, 538 otherRunLimit, otherRunIsRtl, 539 advance ? otherRunStart : otherRunLimit, advance); 540 if (newCaret == (advance ? otherRunLimit : otherRunStart)) { 541 // Crossed and ended up at a new boundary, 542 // repeat a second and final time. 543 runIndex = otherRunIndex; 544 runLevel = otherRunLevel; 545 continue; 546 } 547 break; 548 } 549 550 // The new caret is at a boundary. 551 if (otherRunLevel < runLevel) { 552 // The strong character is in the other run. 553 newCaret = advance ? otherRunStart : otherRunLimit; 554 } 555 break; 556 } 557 558 if (newCaret == -1) { 559 // We're walking off the end of the line. The paragraph 560 // level is always equal to or lower than any internal level, so 561 // the boundaries get the strong caret. 562 newCaret = advance ? mLen + 1 : -1; 563 break; 564 } 565 566 // Else we've arrived at the end of the line. That's a strong position. 567 // We might have arrived here by crossing over a run with no internal 568 // breaks and dropping out of the above loop before advancing one final 569 // time, so reset the caret. 570 // Note, we use '<=' below to handle a situation where the only run 571 // on the line is a counter-directional run. If we're not advancing, 572 // we can end up at the 'lineEnd' position but the caret we want is at 573 // the lineStart. 574 if (newCaret <= lineEnd) { 575 newCaret = advance ? lineEnd : lineStart; 576 } 577 break; 578 } 579 580 return newCaret; 581 } 582 583 /** 584 * Returns the next valid offset within this directional run, skipping 585 * conjuncts and zero-width characters. This should not be called to walk 586 * off the end of the line, since the returned values might not be valid 587 * on neighboring lines. If the returned offset is less than zero or 588 * greater than the line length, the offset should be recomputed on the 589 * preceding or following line, respectively. 590 * 591 * @param runIndex the run index 592 * @param runStart the start of the run 593 * @param runLimit the limit of the run 594 * @param runIsRtl true if the run is right-to-left 595 * @param offset the offset 596 * @param after true if the new offset should logically follow the provided 597 * offset 598 * @return the new offset 599 */ 600 private int getOffsetBeforeAfter(int runIndex, int runStart, int runLimit, 601 boolean runIsRtl, int offset, boolean after) { 602 603 if (runIndex < 0 || offset == (after ? mLen : 0)) { 604 // Walking off end of line. Since we don't know 605 // what cursor positions are available on other lines, we can't 606 // return accurate values. These are a guess. 607 if (after) { 608 return TextUtils.getOffsetAfter(mText, offset + mStart) - mStart; 609 } 610 return TextUtils.getOffsetBefore(mText, offset + mStart) - mStart; 611 } 612 613 TextPaint wp = mWorkPaint; 614 wp.set(mPaint); 615 616 int spanStart = runStart; 617 int spanLimit; 618 if (mSpanned == null) { 619 spanLimit = runLimit; 620 } else { 621 int target = after ? offset + 1 : offset; 622 int limit = mStart + runLimit; 623 while (true) { 624 spanLimit = mSpanned.nextSpanTransition(mStart + spanStart, limit, 625 MetricAffectingSpan.class) - mStart; 626 if (spanLimit >= target) { 627 break; 628 } 629 spanStart = spanLimit; 630 } 631 632 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + spanStart, 633 mStart + spanLimit, MetricAffectingSpan.class); 634 635 if (spans.length > 0) { 636 ReplacementSpan replacement = null; 637 for (int j = 0; j < spans.length; j++) { 638 MetricAffectingSpan span = spans[j]; 639 if (span instanceof ReplacementSpan) { 640 replacement = (ReplacementSpan)span; 641 } else { 642 span.updateMeasureState(wp); 643 } 644 } 645 646 if (replacement != null) { 647 // If we have a replacement span, we're moving either to 648 // the start or end of this span. 649 return after ? spanLimit : spanStart; 650 } 651 } 652 } 653 654 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; 655 int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE; 656 if (mCharsValid) { 657 return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart, 658 flags, offset, cursorOpt); 659 } else { 660 return wp.getTextRunCursor(mText, mStart + spanStart, 661 mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart; 662 } 663 } 664 665 /** 666 * Utility function for measuring and rendering text. The text must 667 * not include a tab or emoji. 668 * 669 * @param wp the working paint 670 * @param start the start of the text 671 * @param end the end of the text 672 * @param runIsRtl true if the run is right-to-left 673 * @param c the canvas, can be null if rendering is not needed 674 * @param x the edge of the run closest to the leading margin 675 * @param top the top of the line 676 * @param y the baseline 677 * @param bottom the bottom of the line 678 * @param fmi receives metrics information, can be null 679 * @param needWidth true if the width of the run is needed 680 * @return the signed width of the run based on the run direction; only 681 * valid if needWidth is true 682 */ 683 private float handleText(TextPaint wp, int start, int end, 684 int contextStart, int contextEnd, boolean runIsRtl, 685 Canvas c, float x, int top, int y, int bottom, 686 FontMetricsInt fmi, boolean needWidth) { 687 688 float ret = 0; 689 690 int runLen = end - start; 691 int contextLen = contextEnd - contextStart; 692 if (needWidth || (c != null && (wp.bgColor != 0 || runIsRtl))) { 693 int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; 694 if (mCharsValid) { 695 ret = wp.getTextRunAdvances(mChars, start, runLen, 696 contextStart, contextLen, flags, null, 0); 697 } else { 698 int delta = mStart; 699 ret = wp.getTextRunAdvances(mText, delta + start, 700 delta + end, delta + contextStart, delta + contextEnd, 701 flags, null, 0); 702 } 703 } 704 705 if (fmi != null) { 706 wp.getFontMetricsInt(fmi); 707 } 708 709 if (c != null) { 710 if (runIsRtl) { 711 x -= ret; 712 } 713 714 if (wp.bgColor != 0) { 715 int color = wp.getColor(); 716 Paint.Style s = wp.getStyle(); 717 wp.setColor(wp.bgColor); 718 wp.setStyle(Paint.Style.FILL); 719 720 c.drawRect(x, top, x + ret, bottom, wp); 721 722 wp.setStyle(s); 723 wp.setColor(color); 724 } 725 726 drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl, 727 x, y + wp.baselineShift); 728 } 729 730 return runIsRtl ? -ret : ret; 731 } 732 733 /** 734 * Utility function for measuring and rendering a replacement. 735 * 736 * @param replacement the replacement 737 * @param wp the work paint 738 * @param runIndex the run index 739 * @param start the start of the run 740 * @param limit the limit of the run 741 * @param runIsRtl true if the run is right-to-left 742 * @param c the canvas, can be null if not rendering 743 * @param x the edge of the replacement closest to the leading margin 744 * @param top the top of the line 745 * @param y the baseline 746 * @param bottom the bottom of the line 747 * @param fmi receives metrics information, can be null 748 * @param needWidth true if the width of the replacement is needed 749 * @return the signed width of the run based on the run direction; only 750 * valid if needWidth is true 751 */ 752 private float handleReplacement(ReplacementSpan replacement, TextPaint wp, 753 int runIndex, int start, int limit, boolean runIsRtl, Canvas c, 754 float x, int top, int y, int bottom, FontMetricsInt fmi, 755 boolean needWidth) { 756 757 float ret = 0; 758 759 int textStart = mStart + start; 760 int textLimit = mStart + limit; 761 762 if (needWidth || (c != null && runIsRtl)) { 763 ret = replacement.getSize(wp, mText, textStart, textLimit, fmi); 764 } 765 766 if (c != null) { 767 if (runIsRtl) { 768 x -= ret; 769 } 770 replacement.draw(c, mText, textStart, textLimit, 771 x, top, y, bottom, wp); 772 } 773 774 return runIsRtl ? -ret : ret; 775 } 776 777 /** 778 * Utility function for handling a unidirectional run. The run must not 779 * contain tabs or emoji but can contain styles. 780 * 781 * @param runIndex the run index 782 * @param start the line-relative start of the run 783 * @param measureLimit the offset to measure to, between start and limit inclusive 784 * @param limit the limit of the run 785 * @param runIsRtl true if the run is right-to-left 786 * @param c the canvas, can be null 787 * @param x the end of the run closest to the leading margin 788 * @param top the top of the line 789 * @param y the baseline 790 * @param bottom the bottom of the line 791 * @param fmi receives metrics information, can be null 792 * @param needWidth true if the width is required 793 * @return the signed width of the run based on the run direction; only 794 * valid if needWidth is true 795 */ 796 private float handleRun(int runIndex, int start, int measureLimit, 797 int limit, boolean runIsRtl, Canvas c, float x, int top, int y, 798 int bottom, FontMetricsInt fmi, boolean needWidth) { 799 800 // Shaping needs to take into account context up to metric boundaries, 801 // but rendering needs to take into account character style boundaries. 802 // So we iterate through metric runs to get metric bounds, 803 // then within each metric run iterate through character style runs 804 // for the run bounds. 805 float ox = x; 806 for (int i = start, inext; i < measureLimit; i = inext) { 807 TextPaint wp = mWorkPaint; 808 wp.set(mPaint); 809 810 int mlimit; 811 if (mSpanned == null) { 812 inext = limit; 813 mlimit = measureLimit; 814 } else { 815 inext = mSpanned.nextSpanTransition(mStart + i, mStart + limit, 816 MetricAffectingSpan.class) - mStart; 817 818 mlimit = inext < measureLimit ? inext : measureLimit; 819 MetricAffectingSpan[] spans = mSpanned.getSpans(mStart + i, 820 mStart + mlimit, MetricAffectingSpan.class); 821 822 if (spans.length > 0) { 823 ReplacementSpan replacement = null; 824 for (int j = 0; j < spans.length; j++) { 825 MetricAffectingSpan span = spans[j]; 826 if (span instanceof ReplacementSpan) { 827 replacement = (ReplacementSpan)span; 828 } else { 829 // We might have a replacement that uses the draw 830 // state, otherwise measure state would suffice. 831 span.updateDrawState(wp); 832 } 833 } 834 835 if (replacement != null) { 836 x += handleReplacement(replacement, wp, runIndex, i, 837 mlimit, runIsRtl, c, x, top, y, bottom, fmi, 838 needWidth || mlimit < measureLimit); 839 continue; 840 } 841 } 842 } 843 844 if (mSpanned == null || c == null) { 845 x += handleText(wp, i, mlimit, i, inext, runIsRtl, c, x, top, 846 y, bottom, fmi, needWidth || mlimit < measureLimit); 847 } else { 848 for (int j = i, jnext; j < mlimit; j = jnext) { 849 jnext = mSpanned.nextSpanTransition(mStart + j, 850 mStart + mlimit, CharacterStyle.class) - mStart; 851 852 CharacterStyle[] spans = mSpanned.getSpans(mStart + j, 853 mStart + jnext, CharacterStyle.class); 854 855 wp.set(mPaint); 856 for (int k = 0; k < spans.length; k++) { 857 CharacterStyle span = spans[k]; 858 span.updateDrawState(wp); 859 } 860 861 x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x, 862 top, y, bottom, fmi, needWidth || jnext < measureLimit); 863 } 864 } 865 } 866 867 return x - ox; 868 } 869 870 /** 871 * Render a text run with the set-up paint. 872 * 873 * @param c the canvas 874 * @param wp the paint used to render the text 875 * @param start the start of the run 876 * @param end the end of the run 877 * @param contextStart the start of context for the run 878 * @param contextEnd the end of the context for the run 879 * @param runIsRtl true if the run is right-to-left 880 * @param x the x position of the left edge of the run 881 * @param y the baseline of the run 882 */ 883 private void drawTextRun(Canvas c, TextPaint wp, int start, int end, 884 int contextStart, int contextEnd, boolean runIsRtl, float x, int y) { 885 886 int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR; 887 if (mCharsValid) { 888 int count = end - start; 889 int contextCount = contextEnd - contextStart; 890 c.drawTextRun(mChars, start, count, contextStart, contextCount, 891 x, y, flags, wp); 892 } else { 893 int delta = mStart; 894 c.drawTextRun(mText, delta + start, delta + end, 895 delta + contextStart, delta + contextEnd, x, y, flags, wp); 896 } 897 } 898 899 /** 900 * Returns the ascent of the text at start. This is used for scaling 901 * emoji. 902 * 903 * @param pos the line-relative position 904 * @return the ascent of the text at start 905 */ 906 float ascent(int pos) { 907 if (mSpanned == null) { 908 return mPaint.ascent(); 909 } 910 911 pos += mStart; 912 MetricAffectingSpan[] spans = mSpanned.getSpans(pos, pos + 1, 913 MetricAffectingSpan.class); 914 if (spans.length == 0) { 915 return mPaint.ascent(); 916 } 917 918 TextPaint wp = mWorkPaint; 919 wp.set(mPaint); 920 for (MetricAffectingSpan span : spans) { 921 span.updateMeasureState(wp); 922 } 923 return wp.ascent(); 924 } 925 926 /** 927 * Returns the next tab position. 928 * 929 * @param h the (unsigned) offset from the leading margin 930 * @return the (unsigned) tab position after this offset 931 */ 932 float nextTab(float h) { 933 if (mTabs != null) { 934 return mTabs.nextTab(h); 935 } 936 return TabStops.nextDefaultStop(h, TAB_INCREMENT); 937 } 938 939 private static final int TAB_INCREMENT = 20; 940} 941