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