1 /* 2 * Copyright (C) 2008 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 static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNull; 22 import static org.junit.Assert.assertSame; 23 import static org.junit.Assert.assertTrue; 24 import static org.junit.Assert.fail; 25 26 import android.graphics.Bitmap; 27 import android.graphics.Canvas; 28 import android.graphics.Paint; 29 import android.graphics.Path; 30 import android.graphics.Rect; 31 import android.graphics.RectF; 32 import android.platform.test.annotations.Presubmit; 33 import android.support.test.filters.SmallTest; 34 import android.support.test.runner.AndroidJUnit4; 35 import android.text.Layout.Alignment; 36 import android.text.style.StrikethroughSpan; 37 38 import org.junit.Before; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Locale; 45 46 @Presubmit 47 @SmallTest 48 @RunWith(AndroidJUnit4.class) 49 public class LayoutTest { 50 private static final int LINE_COUNT = 5; 51 private static final int LINE_HEIGHT = 12; 52 private static final int LINE_DESCENT = 4; 53 private static final CharSequence LAYOUT_TEXT = "alwei\t;sdfs\ndf @"; 54 55 private SpannableString mSpannedText; 56 57 private int mWidth; 58 private Layout.Alignment mAlign; 59 private float mSpacingMult; 60 private float mSpacingAdd; 61 private TextPaint mTextPaint; 62 63 @Before 64 public void setup() { 65 mTextPaint = new TextPaint(); 66 mSpannedText = new SpannableString(LAYOUT_TEXT); 67 mSpannedText.setSpan(new StrikethroughSpan(), 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 68 mWidth = 11; 69 mAlign = Alignment.ALIGN_CENTER; 70 mSpacingMult = 1; 71 mSpacingAdd = 2; 72 } 73 74 @Test 75 public void testConstructor() { 76 new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, mSpacingMult, mSpacingAdd); 77 } 78 79 @Test(expected = IllegalArgumentException.class) 80 public void testConstructorNull() { 81 new MockLayout(null, null, -1, null, 0, 0); 82 } 83 84 @Test 85 public void testGetText() { 86 CharSequence text = "test case 1"; 87 Layout layout = new MockLayout(text, mTextPaint, mWidth, 88 mAlign, mSpacingMult, mSpacingAdd); 89 assertEquals(text, layout.getText()); 90 91 layout = new MockLayout(null, mTextPaint, mWidth, mAlign, mSpacingMult, mSpacingAdd); 92 assertNull(layout.getText()); 93 } 94 95 @Test 96 public void testGetPaint() { 97 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 98 mAlign, mSpacingMult, mSpacingAdd); 99 100 assertSame(mTextPaint, layout.getPaint()); 101 102 layout = new MockLayout(LAYOUT_TEXT, null, mWidth, mAlign, mSpacingMult, mSpacingAdd); 103 assertNull(layout.getPaint()); 104 } 105 106 @Test 107 public void testGetWidth() { 108 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, 10, 109 mAlign, mSpacingMult, mSpacingAdd); 110 assertEquals(10, layout.getWidth()); 111 112 layout = new MockLayout(LAYOUT_TEXT, mTextPaint, 0, mAlign, mSpacingMult, mSpacingAdd); 113 assertEquals(0, layout.getWidth()); 114 } 115 116 @Test 117 public void testGetEllipsizedWidth() { 118 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, 15, 119 mAlign, mSpacingMult, mSpacingAdd); 120 assertEquals(15, layout.getEllipsizedWidth()); 121 122 layout = new MockLayout(LAYOUT_TEXT, mTextPaint, 0, mAlign, mSpacingMult, mSpacingAdd); 123 assertEquals(0, layout.getEllipsizedWidth()); 124 } 125 126 @Test 127 public void testIncreaseWidthTo() { 128 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 129 mAlign, mSpacingMult, mSpacingAdd); 130 int oldWidth = layout.getWidth(); 131 132 layout.increaseWidthTo(oldWidth); 133 assertEquals(oldWidth, layout.getWidth()); 134 135 try { 136 layout.increaseWidthTo(oldWidth - 1); 137 fail("should throw runtime exception attempted to reduce Layout width"); 138 } catch (RuntimeException e) { 139 } 140 141 layout.increaseWidthTo(oldWidth + 1); 142 assertEquals(oldWidth + 1, layout.getWidth()); 143 } 144 145 @Test 146 public void testGetHeight() { 147 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 148 mAlign, mSpacingMult, mSpacingAdd); 149 assertEquals(60, layout.getHeight()); 150 } 151 152 @Test 153 public void testGetAlignment() { 154 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 155 mAlign, mSpacingMult, mSpacingAdd); 156 assertSame(mAlign, layout.getAlignment()); 157 158 layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, null, mSpacingMult, mSpacingAdd); 159 assertNull(layout.getAlignment()); 160 } 161 162 @Test 163 public void testGetSpacingMultiplier() { 164 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, -1, mSpacingAdd); 165 assertEquals(-1.0f, layout.getSpacingMultiplier(), 0.0f); 166 167 layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, 5, mSpacingAdd); 168 assertEquals(5.0f, layout.getSpacingMultiplier(), 0.0f); 169 } 170 171 @Test 172 public void testGetSpacingAdd() { 173 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, mSpacingMult, -1); 174 assertEquals(-1.0f, layout.getSpacingAdd(), 0.0f); 175 176 layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, mAlign, mSpacingMult, 20); 177 assertEquals(20.0f, layout.getSpacingAdd(), 0.0f); 178 } 179 180 @Test 181 public void testGetLineBounds() { 182 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 183 mAlign, mSpacingMult, mSpacingAdd); 184 Rect bounds = new Rect(); 185 186 assertEquals(32, layout.getLineBounds(2, bounds)); 187 assertEquals(0, bounds.left); 188 assertEquals(mWidth, bounds.right); 189 assertEquals(24, bounds.top); 190 assertEquals(36, bounds.bottom); 191 } 192 193 @Test 194 public void testGetLineForVertical() { 195 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 196 mAlign, mSpacingMult, mSpacingAdd); 197 assertEquals(0, layout.getLineForVertical(-1)); 198 assertEquals(0, layout.getLineForVertical(0)); 199 assertEquals(0, layout.getLineForVertical(LINE_COUNT)); 200 assertEquals(LINE_COUNT - 1, layout.getLineForVertical(1000)); 201 } 202 203 @Test 204 public void testGetLineForOffset() { 205 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 206 mAlign, mSpacingMult, mSpacingAdd); 207 assertEquals(0, layout.getLineForOffset(-1)); 208 assertEquals(1, layout.getLineForOffset(1)); 209 assertEquals(LINE_COUNT - 1, layout.getLineForOffset(LINE_COUNT - 1)); 210 assertEquals(LINE_COUNT - 1, layout.getLineForOffset(1000)); 211 } 212 213 @Test 214 public void testGetLineEnd() { 215 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 216 mAlign, mSpacingMult, mSpacingAdd); 217 assertEquals(2, layout.getLineEnd(1)); 218 } 219 220 @Test 221 public void testGetLineExtra_returnsZeroByDefault() { 222 final String text = "a\nb\nc\n"; 223 final Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 224 mAlign, 100 /* spacingMult*/, 100 /*spacingAdd*/); 225 final int lineCount = text.split("\n").length; 226 for (int i = 0; i < lineCount; i++) { 227 assertEquals(0, layout.getLineExtra(i)); 228 } 229 } 230 231 @Test 232 public void testGetLineVisibleEnd() { 233 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 234 mAlign, mSpacingMult, mSpacingAdd); 235 236 assertEquals(2, layout.getLineVisibleEnd(1)); 237 assertEquals(LINE_COUNT, layout.getLineVisibleEnd(LINE_COUNT - 1)); 238 assertEquals(LAYOUT_TEXT.length(), layout.getLineVisibleEnd(LAYOUT_TEXT.length() - 1)); 239 try { 240 layout.getLineVisibleEnd(LAYOUT_TEXT.length()); 241 fail("should throw .StringIndexOutOfBoundsException here"); 242 } catch (StringIndexOutOfBoundsException e) { 243 } 244 } 245 246 @Test 247 public void testGetLineBottom() { 248 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 249 mAlign, mSpacingMult, mSpacingAdd); 250 assertEquals(LINE_HEIGHT, layout.getLineBottom(0)); 251 } 252 253 @Test 254 public void testGetLineBaseline() { 255 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 256 mAlign, mSpacingMult, mSpacingAdd); 257 assertEquals(8, layout.getLineBaseline(0)); 258 } 259 260 @Test 261 public void testGetLineAscent() { 262 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 263 mAlign, mSpacingMult, mSpacingAdd); 264 assertEquals(-8, layout.getLineAscent(0)); 265 } 266 267 @Test 268 public void testGetParagraphAlignment() { 269 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 270 mAlign, mSpacingMult, mSpacingAdd); 271 assertSame(mAlign, layout.getParagraphAlignment(0)); 272 273 layout = new MockLayout(mSpannedText, mTextPaint, mWidth, 274 mAlign, mSpacingMult, mSpacingAdd); 275 assertSame(mAlign, layout.getParagraphAlignment(0)); 276 assertSame(mAlign, layout.getParagraphAlignment(1)); 277 } 278 279 @Test 280 public void testGetParagraphLeft() { 281 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 282 mAlign, mSpacingMult, mSpacingAdd); 283 assertEquals(0, layout.getParagraphLeft(0)); 284 } 285 286 @Test 287 public void testGetParagraphRight() { 288 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 289 mAlign, mSpacingMult, mSpacingAdd); 290 assertEquals(mWidth, layout.getParagraphRight(0)); 291 } 292 293 @Test 294 public void testGetSelectionWithEmptySelection() { 295 final Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 296 mAlign, mSpacingMult, mSpacingAdd); 297 298 /* 299 * When the selection is empty (i.e. the start and the end index are the same), we do not 300 * expect any rectangles to be generated. 301 */ 302 303 layout.getSelection(5 /* startIndex */, 5 /* endIndex */, 304 (left, top, right, bottom, textSelectionLayout) -> fail( 305 String.format(Locale.getDefault(), 306 "Did not expect any rectangles, got a rectangle with (left: %f," 307 + " top: %f), (right: %f, bottom: %f)", 308 left, top, right, bottom))); 309 } 310 311 @Test 312 public void testGetSelectionWithASingleLineSelection() { 313 final Layout layout = new StaticLayout("abc", mTextPaint, Integer.MAX_VALUE, 314 Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); 315 316 final List<RectF> rectangles = new ArrayList<>(); 317 318 layout.getSelection(0 /* startIndex */, 1 /* endIndex */, 319 (left, top, right, bottom, textSelectionLayout) -> rectangles.add( 320 new RectF(left, top, right, bottom))); 321 322 /* 323 * The selection we expect will only cover the letter "a". Hence, we expect one rectangle 324 * to be generated and this rectangle should start at the top left of the canvas and should 325 * end somewhere to the right and down. 326 * 327 * | a | b c 328 * 329 */ 330 331 assertEquals(1, rectangles.size()); 332 333 final RectF rectangle = rectangles.get(0); 334 335 assertEquals(0, rectangle.left, 0.0f); 336 assertEquals(0, rectangle.top, 0.0f); 337 assertTrue(rectangle.right > 0); 338 assertTrue(rectangle.bottom > 0); 339 } 340 341 @Test 342 public void 343 testGetSelectionWithMultilineSelection_secondLineSelectionEndsBeforeFirstCharacter() { 344 final Layout layout = new StaticLayout("a\nb\nc", mTextPaint, Integer.MAX_VALUE, 345 Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); 346 347 final List<RectF> rectangles = new ArrayList<>(); 348 349 layout.getSelection(0 /* startIndex */, 2 /* endIndex */, 350 (left, top, right, bottom, textSelectionLayout) -> rectangles.add( 351 new RectF(left, top, right, bottom))); 352 353 /* 354 * The selection that will be selected is "a\n" - the selection starts at the beginning 355 * of the first line and ends at the start of the second line. This means the selection 356 * highlight will span from the beginning of the first line to the end of the first line 357 * and will appear as a zero width line at the beginning of the second line. 358 * 359 * Hence, we expect three rectangles - one that will select the "a" on the first line, 360 * one that will extend the selection from the "a" to the end of the first line and one 361 * that will prepare the selection for the second line. 362 * 363 * | a | *topToEndOfLineRectangle* | 364 * | b 365 * c 366 */ 367 368 assertEquals(3, rectangles.size()); 369 370 final RectF topRectangle = rectangles.get(0); 371 final RectF topToEndOfLineRectangle = rectangles.get(1); 372 final RectF bottomLineStartRectangle = rectangles.get(2); 373 374 assertFalse(topRectangle.intersect(bottomLineStartRectangle)); 375 assertTrue(topRectangle.top < bottomLineStartRectangle.top); 376 assertTrue(topRectangle.left == bottomLineStartRectangle.left); 377 378 assertFalse(topRectangle.intersect(topToEndOfLineRectangle)); 379 assertEquals(Integer.MAX_VALUE, topToEndOfLineRectangle.right, 1); 380 assertTrue(topRectangle.top == topToEndOfLineRectangle.top); 381 assertTrue(topRectangle.right == topToEndOfLineRectangle.left); 382 assertTrue(topRectangle.bottom == topToEndOfLineRectangle.bottom); 383 384 assertEquals(0, bottomLineStartRectangle.left, 0.0f); 385 assertEquals(0, bottomLineStartRectangle.right, 0.0f); 386 } 387 388 @Test 389 public void testGetSelectionWithMultilineSelection_secondLineSelectionEndsAfterACharacter() { 390 final Layout layout = new StaticLayout("a\nb\nc", mTextPaint, Integer.MAX_VALUE, 391 Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); 392 393 final List<RectF> rectangles = new ArrayList<>(); 394 395 layout.getSelection(0 /* startIndex */, 3 /* endIndex */, 396 (left, top, right, bottom, textSelectionLayout) -> rectangles.add( 397 new RectF(left, top, right, bottom))); 398 399 /* 400 * The selection that will be selected is "a\nb" - the selection starts at the beginning 401 * of the first line and ends at the end of the letter "b". This means the selection 402 * highlight will span from the beginning of the first line to the end of the first line 403 * and will also cover the letter "b" on the second line. 404 * 405 * We expect four rectangles - one that will select the "a" on the first line, 406 * one that will extend the selection from the "a" to the end of the first line the one 407 * from the previous case that will prepare the selection for the second line and finally 408 * one that will select the letter b. 409 * 410 * | a | *topToEndOfLineRectangle* | 411 * || b | 412 * c 413 */ 414 415 assertEquals(4, rectangles.size()); 416 417 final RectF topRectangle = rectangles.get(0); 418 final RectF topToEndOfLineRectangle = rectangles.get(1); 419 final RectF bottomRectangle = rectangles.get(2); 420 final RectF bottomLineStartRectangle = rectangles.get(3); 421 422 assertTrue(topRectangle.top == topToEndOfLineRectangle.top); 423 assertTrue(bottomLineStartRectangle.top == bottomRectangle.top); 424 assertTrue(bottomLineStartRectangle.bottom == bottomRectangle.bottom); 425 assertEquals(0, bottomLineStartRectangle.left, 0.0f); 426 assertEquals(0, bottomLineStartRectangle.right, 0.0f); 427 assertEquals(0, bottomRectangle.left, 0.0f); 428 assertTrue(bottomRectangle.right > 0); 429 } 430 431 @Test 432 public void testGetSelectionPathWithASingleLineSelection() { 433 final Layout layout = new StaticLayout("abc", mTextPaint, Integer.MAX_VALUE, 434 Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); 435 436 final List<RectF> rectangles = new ArrayList<>(); 437 438 layout.getSelection(0 /* startIndex */, 1 /* endIndex */, 439 (left, top, right, bottom, textSelectionLayout) -> rectangles.add( 440 new RectF(left, top, right, bottom))); 441 442 /* 443 * In the single line selection case, we expect that only one rectangle covering the letter 444 * "a" will be generated. Hence, we expect that the generated path will only consist of 445 * that rectangle as well. 446 * 447 * | a | b c 448 * 449 */ 450 451 assertEquals(1, rectangles.size()); 452 453 final RectF rectangle = rectangles.get(0); 454 455 final Path generatedPath = new Path(); 456 layout.getSelectionPath(0 /* startIndex */, 1 /* endIndex */, generatedPath); 457 458 final RectF pathRectangle = new RectF(); 459 460 assertTrue(generatedPath.isRect(pathRectangle)); 461 assertEquals(rectangle, pathRectangle); 462 } 463 464 @Test 465 public void testGetSelection_latinTextDirection() { 466 final Layout layout = new StaticLayout("abc", mTextPaint, Integer.MAX_VALUE, 467 Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); 468 469 layout.getSelection(0 /* startIndex */, 2 /* endIndex */, 470 (left, top, right, bottom, textSelectionLayout) -> 471 assertEquals(Layout.TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT, 472 textSelectionLayout)); 473 } 474 475 @Test 476 public void testGetSelection_arabicTextDirection() { 477 final Layout layout = new StaticLayout("", mTextPaint, Integer.MAX_VALUE, 478 Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); 479 480 layout.getSelection(0 /* startIndex */, 2 /* endIndex */, 481 (left, top, right, bottom, textSelectionLayout) -> 482 assertEquals(Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT, 483 textSelectionLayout)); 484 } 485 486 @Test 487 public void testGetSelection_mixedLatinAndArabicTextDirection() { 488 final Layout layout = new StaticLayout("abc", mTextPaint, Integer.MAX_VALUE, 489 Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); 490 491 final List<Integer> layouts = new ArrayList<>(2); 492 493 layout.getSelection(0 /* startIndex */, 6 /* endIndex */, 494 (left, top, right, bottom, textSelectionLayout) -> layouts.add( 495 textSelectionLayout)); 496 497 assertEquals(2, layouts.size()); 498 assertEquals(Layout.TEXT_SELECTION_LAYOUT_LEFT_TO_RIGHT, (long) layouts.get(0)); 499 assertEquals(Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT, (long) layouts.get(1)); 500 } 501 502 @Test 503 public void testIsSpanned() { 504 MockLayout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 505 mAlign, mSpacingMult, mSpacingAdd); 506 // default is not spanned text 507 assertFalse(layout.mockIsSpanned()); 508 509 // try to create a spanned text 510 layout = new MockLayout(mSpannedText, mTextPaint, mWidth, 511 mAlign, mSpacingMult, mSpacingAdd); 512 assertTrue(layout.mockIsSpanned()); 513 } 514 515 private static final class MockLayout extends Layout { 516 MockLayout(CharSequence text, TextPaint paint, int width, 517 Alignment align, float spacingmult, float spacingadd) { 518 super(text, paint, width, align, spacingmult, spacingadd); 519 } 520 521 protected boolean mockIsSpanned() { 522 return super.isSpanned(); 523 } 524 525 @Override 526 public int getBottomPadding() { 527 return 0; 528 } 529 530 @Override 531 public int getEllipsisCount(int line) { 532 return 0; 533 } 534 535 @Override 536 public int getEllipsisStart(int line) { 537 return 0; 538 } 539 540 @Override 541 public boolean getLineContainsTab(int line) { 542 return false; 543 } 544 545 @Override 546 public int getLineCount() { 547 return LINE_COUNT; 548 } 549 550 @Override 551 public int getLineDescent(int line) { 552 return LINE_DESCENT; 553 } 554 555 @Override 556 public Directions getLineDirections(int line) { 557 return Layout.DIRS_ALL_LEFT_TO_RIGHT; 558 } 559 560 @Override 561 public int getLineStart(int line) { 562 if (line < 0) { 563 return 0; 564 } 565 return line; 566 } 567 568 @Override 569 public int getLineTop(int line) { 570 if (line < 0) { 571 return 0; 572 } 573 return LINE_HEIGHT * (line); 574 } 575 576 @Override 577 public int getParagraphDirection(int line) { 578 return 0; 579 } 580 581 @Override 582 public int getTopPadding() { 583 return 0; 584 } 585 } 586 587 @Test 588 public void testGetLineWidth() { 589 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 590 mAlign, mSpacingMult, mSpacingAdd); 591 for (int i = 0; i < LINE_COUNT; i++) { 592 int start = layout.getLineStart(i); 593 int end = layout.getLineEnd(i); 594 String text = LAYOUT_TEXT.toString().substring(start, end); 595 assertEquals(mTextPaint.measureText(text), layout.getLineWidth(i), 1.0f); 596 } 597 } 598 599 @Test 600 public void testGetCursorPath() { 601 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 602 mAlign, mSpacingMult, mSpacingAdd); 603 Path path = new Path(); 604 final float epsilon = 1.0f; 605 for (int i = 0; i < LINE_COUNT; i++) { 606 layout.getCursorPath(i, path, LAYOUT_TEXT); 607 RectF bounds = new RectF(); 608 path.computeBounds(bounds, false); 609 assertTrue(bounds.top >= layout.getLineTop(i) - epsilon); 610 assertTrue(bounds.bottom <= layout.getLineBottom(i) + epsilon); 611 } 612 } 613 614 @Test 615 public void testDraw() { 616 Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, 617 mAlign, mSpacingMult, mSpacingAdd); 618 final int width = 256; 619 final int height = 256; 620 MockCanvas c = new MockCanvas(width, height); 621 layout.draw(c); 622 List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands(); 623 assertEquals(LINE_COUNT, drawCommands.size()); 624 for (int i = 0; i < LINE_COUNT; i++) { 625 MockCanvas.DrawCommand drawCommand = drawCommands.get(i); 626 int start = layout.getLineStart(i); 627 int end = layout.getLineEnd(i); 628 assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text); 629 float expected_y = (i + 1) * LINE_HEIGHT - LINE_DESCENT; 630 assertEquals(expected_y, drawCommand.y, 0.0f); 631 } 632 } 633 634 private final class MockCanvas extends Canvas { 635 636 class DrawCommand { 637 public final String text; 638 public final float x; 639 public final float y; 640 641 DrawCommand(String text, float x, float y) { 642 this.text = text; 643 this.x = x; 644 this.y = y; 645 } 646 } 647 648 List<DrawCommand> mDrawCommands; 649 650 MockCanvas(int width, int height) { 651 super(); 652 mDrawCommands = new ArrayList<>(); 653 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 654 setBitmap(bitmap); 655 } 656 657 // Drawing text with either drawText or drawTextRun is valid; we don't care which. 658 // We also don't care which of the string representations is used. 659 660 @Override 661 public void drawText(String text, int start, int end, float x, float y, Paint p) { 662 mDrawCommands.add(new DrawCommand(text.substring(start, end), x, y)); 663 } 664 665 @Override 666 public void drawText(CharSequence text, int start, int end, float x, float y, Paint p) { 667 drawText(text.toString(), start, end, x, y, p); 668 } 669 670 @Override 671 public void drawText(char[] text, int index, int count, float x, float y, Paint p) { 672 mDrawCommands.add(new DrawCommand(new String(text, index, count), x, y)); 673 } 674 675 @Override 676 public void drawTextRun(CharSequence text, int start, int end, int contextStart, 677 int contextEnd, float x, float y, boolean isRtl, Paint paint) { 678 drawText(text, start, end, x, y, paint); 679 } 680 681 @Override 682 public void drawTextRun(char[] text, int index, int count, int contextIndex, 683 int contextCount, float x, float y, boolean isRtl, Paint paint) { 684 drawText(text, index, count, x, y, paint); 685 } 686 687 List<DrawCommand> getDrawCommands() { 688 return mDrawCommands; 689 } 690 } 691 } 692 693