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.cts; 18 19 import android.graphics.Typeface; 20 import android.test.AndroidTestCase; 21 import android.text.Editable; 22 import android.text.Layout; 23 import android.text.Layout.Alignment; 24 import android.text.SpannableString; 25 import android.text.SpannableStringBuilder; 26 import android.text.Spanned; 27 import android.text.SpannedString; 28 import android.text.StaticLayout; 29 import android.text.TextDirectionHeuristics; 30 import android.text.TextPaint; 31 import android.text.TextUtils; 32 import android.text.TextUtils.TruncateAt; 33 import android.text.method.cts.EditorState; 34 import android.text.style.StyleSpan; 35 36 import java.text.Normalizer; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Locale; 40 41 public class StaticLayoutTest extends AndroidTestCase { 42 private static final float SPACE_MULTI = 1.0f; 43 private static final float SPACE_ADD = 0.0f; 44 private static final int DEFAULT_OUTER_WIDTH = 150; 45 46 private static final int LAST_LINE = 5; 47 private static final int LINE_COUNT = 6; 48 private static final int LARGER_THAN_LINE_COUNT = 50; 49 50 /* the first line must have one tab. the others not. totally 6 lines 51 */ 52 private static final CharSequence LAYOUT_TEXT = "CharSe\tq\nChar" 53 + "Sequence\nCharSequence\nHelllo\n, world\nLongLongLong"; 54 55 private static final CharSequence LAYOUT_TEXT_SINGLE_LINE = "CharSequence"; 56 57 private static final int VERTICAL_BELOW_TEXT = 1000; 58 59 private static final Alignment DEFAULT_ALIGN = Alignment.ALIGN_CENTER; 60 61 private static final int ELLIPSIZE_WIDTH = 8; 62 63 private StaticLayout mDefaultLayout; 64 private TextPaint mDefaultPaint; 65 66 private class TestingTextPaint extends TextPaint { 67 // need to have a subclass to insure measurement happens in Java and not C++ 68 } 69 70 @Override setUp()71 protected void setUp() throws Exception { 72 super.setUp(); 73 if (mDefaultPaint == null) { 74 mDefaultPaint = new TextPaint(); 75 } 76 if (mDefaultLayout == null) { 77 mDefaultLayout = createDefaultStaticLayout(); 78 } 79 } 80 createDefaultStaticLayout()81 private StaticLayout createDefaultStaticLayout() { 82 return new StaticLayout(LAYOUT_TEXT, mDefaultPaint, 83 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 84 } 85 createEllipsizeStaticLayout()86 private StaticLayout createEllipsizeStaticLayout() { 87 return new StaticLayout(LAYOUT_TEXT, 0, LAYOUT_TEXT.length(), mDefaultPaint, 88 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true, 89 TextUtils.TruncateAt.MIDDLE, ELLIPSIZE_WIDTH); 90 } 91 createEllipsizeStaticLayout(CharSequence text, TextUtils.TruncateAt ellipsize, int maxLines)92 private StaticLayout createEllipsizeStaticLayout(CharSequence text, 93 TextUtils.TruncateAt ellipsize, int maxLines) { 94 return new StaticLayout(text, 0, text.length(), 95 mDefaultPaint, DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, 96 TextDirectionHeuristics.FIRSTSTRONG_LTR, 97 SPACE_MULTI, SPACE_ADD, true /* include pad */, 98 ellipsize, 99 ELLIPSIZE_WIDTH, 100 maxLines); 101 } 102 103 104 105 /** 106 * Constructor test 107 */ testConstructor()108 public void testConstructor() { 109 new StaticLayout(LAYOUT_TEXT, mDefaultPaint, DEFAULT_OUTER_WIDTH, 110 DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 111 112 new StaticLayout(LAYOUT_TEXT, 0, LAYOUT_TEXT.length(), mDefaultPaint, 113 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 114 115 new StaticLayout(LAYOUT_TEXT, 0, LAYOUT_TEXT.length(), mDefaultPaint, 116 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, false, null, 0); 117 118 try { 119 new StaticLayout(null, null, -1, null, 0, 0, true); 120 fail("should throw NullPointerException here"); 121 } catch (NullPointerException e) { 122 } 123 } 124 testBuilder()125 public void testBuilder() { 126 { 127 // Obtain. 128 StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0, 129 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); 130 StaticLayout layout = builder.build(); 131 // Check values passed to obtain(). 132 assertEquals(LAYOUT_TEXT, layout.getText()); 133 assertEquals(mDefaultPaint, layout.getPaint()); 134 assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth()); 135 // Check default values. 136 assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, 137 layout.getTextDirectionHeuristic()); 138 assertEquals(Alignment.ALIGN_NORMAL, layout.getAlignment()); 139 assertEquals(0.0f, layout.getSpacingAdd()); 140 assertEquals(1.0f, layout.getSpacingMultiplier()); 141 assertEquals(DEFAULT_OUTER_WIDTH, layout.getEllipsizedWidth()); 142 } 143 { 144 // Obtain with null objects. 145 StaticLayout.Builder builder = StaticLayout.Builder.obtain(null, 0, 0, null, 0); 146 try { 147 StaticLayout layout = builder.build(); 148 fail("should throw NullPointerException here"); 149 } catch (NullPointerException e) { 150 } 151 } 152 { 153 // setText. 154 StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0, 155 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); 156 builder.setText(LAYOUT_TEXT_SINGLE_LINE); 157 StaticLayout layout = builder.build(); 158 assertEquals(LAYOUT_TEXT_SINGLE_LINE, layout.getText()); 159 } 160 { 161 // setAlignment. 162 StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0, 163 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); 164 builder.setAlignment(DEFAULT_ALIGN); 165 StaticLayout layout = builder.build(); 166 assertEquals(DEFAULT_ALIGN, layout.getAlignment()); 167 } 168 { 169 // setTextDirection. 170 StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0, 171 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); 172 builder.setTextDirection(TextDirectionHeuristics.RTL); 173 StaticLayout layout = builder.build(); 174 // Always returns TextDirectionHeuristics.FIRSTSTRONG_LTR. 175 assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, 176 layout.getTextDirectionHeuristic()); 177 } 178 { 179 // setLineSpacing. 180 StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0, 181 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); 182 builder.setLineSpacing(1.0f, 2.0f); 183 StaticLayout layout = builder.build(); 184 assertEquals(1.0f, layout.getSpacingAdd()); 185 assertEquals(2.0f, layout.getSpacingMultiplier()); 186 } 187 { 188 // setEllipsizedWidth and setEllipsize. 189 StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0, 190 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); 191 builder.setEllipsize(TruncateAt.END); 192 builder.setEllipsizedWidth(ELLIPSIZE_WIDTH); 193 StaticLayout layout = builder.build(); 194 assertEquals(ELLIPSIZE_WIDTH, layout.getEllipsizedWidth()); 195 assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth()); 196 assertTrue(layout.getEllipsisCount(0) == 0); 197 assertTrue(layout.getEllipsisCount(5) > 0); 198 } 199 { 200 // setMaxLines. 201 StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0, 202 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); 203 builder.setMaxLines(1); 204 builder.setEllipsize(TruncateAt.END); 205 StaticLayout layout = builder.build(); 206 assertTrue(layout.getEllipsisCount(0) > 0); 207 assertEquals(1, layout.getLineCount()); 208 } 209 { 210 // Setter methods that cannot be directly tested. 211 // setBreakStrategy, setHyphenationFrequency, setIncludePad, and setIndents. 212 StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0, 213 LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); 214 builder.setBreakStrategy(StaticLayout.BREAK_STRATEGY_HIGH_QUALITY); 215 builder.setHyphenationFrequency(StaticLayout.HYPHENATION_FREQUENCY_FULL); 216 builder.setIncludePad(true); 217 builder.setIndents(null, null); 218 StaticLayout layout = builder.build(); 219 assertNotNull(layout); 220 } 221 } 222 223 /* 224 * Get the line number corresponding to the specified vertical position. 225 * If you ask for a position above 0, you get 0. above 0 means pixel above the fire line 226 * if you ask for a position in the range of the height, return the pixel in line 227 * if you ask for a position below the bottom of the text, you get the last line. 228 * Test 4 values containing -1, 0, normal number and > count 229 */ testGetLineForVertical()230 public void testGetLineForVertical() { 231 assertEquals(0, mDefaultLayout.getLineForVertical(-1)); 232 assertEquals(0, mDefaultLayout.getLineForVertical(0)); 233 assertTrue(mDefaultLayout.getLineForVertical(50) > 0); 234 assertEquals(LAST_LINE, mDefaultLayout.getLineForVertical(VERTICAL_BELOW_TEXT)); 235 } 236 237 /** 238 * Return the number of lines of text in this layout. 239 */ testGetLineCount()240 public void testGetLineCount() { 241 assertEquals(LINE_COUNT, mDefaultLayout.getLineCount()); 242 } 243 244 /* 245 * Return the vertical position of the top of the specified line. 246 * If the specified line is one beyond the last line, returns the bottom of the last line. 247 * A line of text contains top and bottom in height. this method just get the top of a line 248 * Test 4 values containing -1, 0, normal number and > count 249 */ testGetLineTop()250 public void testGetLineTop() { 251 assertTrue(mDefaultLayout.getLineTop(0) >= 0); 252 assertTrue(mDefaultLayout.getLineTop(1) > mDefaultLayout.getLineTop(0)); 253 254 try { 255 mDefaultLayout.getLineTop(-1); 256 fail("should throw ArrayIndexOutOfBoundsException"); 257 } catch (ArrayIndexOutOfBoundsException e) { 258 } 259 260 try { 261 mDefaultLayout.getLineTop(LARGER_THAN_LINE_COUNT ); 262 fail("should throw ArrayIndexOutOfBoundsException"); 263 } catch (ArrayIndexOutOfBoundsException e) { 264 } 265 } 266 267 /** 268 * Return the descent of the specified line. 269 * This method just like getLineTop, descent means the bottom pixel of the line 270 * Test 4 values containing -1, 0, normal number and > count 271 */ testGetLineDescent()272 public void testGetLineDescent() { 273 assertTrue(mDefaultLayout.getLineDescent(0) > 0); 274 assertTrue(mDefaultLayout.getLineDescent(1) > 0); 275 276 try { 277 mDefaultLayout.getLineDescent(-1); 278 fail("should throw ArrayIndexOutOfBoundsException"); 279 } catch (ArrayIndexOutOfBoundsException e) { 280 } 281 282 try { 283 mDefaultLayout.getLineDescent(LARGER_THAN_LINE_COUNT ); 284 fail("should throw ArrayIndexOutOfBoundsException"); 285 } catch (ArrayIndexOutOfBoundsException e) { 286 } 287 } 288 289 /** 290 * Returns the primary directionality of the paragraph containing the specified line. 291 * By default, each line should be same 292 */ testGetParagraphDirection()293 public void testGetParagraphDirection() { 294 assertEquals(mDefaultLayout.getParagraphDirection(0), 295 mDefaultLayout.getParagraphDirection(1)); 296 try { 297 mDefaultLayout.getParagraphDirection(-1); 298 fail("should throw ArrayIndexOutOfBoundsException"); 299 } catch (ArrayIndexOutOfBoundsException e) { 300 } 301 302 try { 303 mDefaultLayout.getParagraphDirection(LARGER_THAN_LINE_COUNT); 304 fail("should throw ArrayIndexOutOfBoundsException"); 305 } catch (ArrayIndexOutOfBoundsException e) { 306 } 307 } 308 309 /** 310 * Return the text offset of the beginning of the specified line. 311 * If the specified line is one beyond the last line, returns the end of the last line. 312 * Test 4 values containing -1, 0, normal number and > count 313 * Each line's offset must >= 0 314 */ testGetLineStart()315 public void testGetLineStart() { 316 assertTrue(mDefaultLayout.getLineStart(0) >= 0); 317 assertTrue(mDefaultLayout.getLineStart(1) >= 0); 318 319 try { 320 mDefaultLayout.getLineStart(-1); 321 fail("should throw ArrayIndexOutOfBoundsException"); 322 } catch (ArrayIndexOutOfBoundsException e) { 323 } 324 325 try { 326 mDefaultLayout.getLineStart(LARGER_THAN_LINE_COUNT); 327 fail("should throw ArrayIndexOutOfBoundsException"); 328 } catch (ArrayIndexOutOfBoundsException e) { 329 } 330 } 331 332 /* 333 * Returns whether the specified line contains one or more tabs. 334 */ testGetContainsTab()335 public void testGetContainsTab() { 336 assertTrue(mDefaultLayout.getLineContainsTab(0)); 337 assertFalse(mDefaultLayout.getLineContainsTab(1)); 338 339 try { 340 mDefaultLayout.getLineContainsTab(-1); 341 fail("should throw ArrayIndexOutOfBoundsException"); 342 } catch (ArrayIndexOutOfBoundsException e) { 343 } 344 345 try { 346 mDefaultLayout.getLineContainsTab(LARGER_THAN_LINE_COUNT ); 347 fail("should throw ArrayIndexOutOfBoundsException"); 348 } catch (ArrayIndexOutOfBoundsException e) { 349 } 350 } 351 352 /** 353 * Returns an array of directionalities for the specified line. 354 * The array alternates counts of characters in left-to-right 355 * and right-to-left segments of the line. 356 * We can not check the return value, for Directions's field is package private 357 * So only check it not null 358 */ testGetLineDirections()359 public void testGetLineDirections() { 360 assertNotNull(mDefaultLayout.getLineDirections(0)); 361 assertNotNull(mDefaultLayout.getLineDirections(1)); 362 363 try { 364 mDefaultLayout.getLineDirections(-1); 365 fail("should throw ArrayIndexOutOfBoundsException"); 366 } catch (ArrayIndexOutOfBoundsException e) { 367 } 368 369 try { 370 mDefaultLayout.getLineDirections(LARGER_THAN_LINE_COUNT); 371 fail("should throw ArrayIndexOutOfBoundsException"); 372 } catch (ArrayIndexOutOfBoundsException e) { 373 } 374 } 375 376 /** 377 * Returns the (negative) number of extra pixels of ascent padding 378 * in the top line of the Layout. 379 */ testGetTopPadding()380 public void testGetTopPadding() { 381 assertTrue(mDefaultLayout.getTopPadding() < 0); 382 } 383 384 /** 385 * Returns the number of extra pixels of descent padding in the bottom line of the Layout. 386 */ 387 public void testGetBottomPadding() { 388 assertTrue(mDefaultLayout.getBottomPadding() > 0); 389 } 390 391 /* 392 * Returns the number of characters to be ellipsized away, or 0 if no ellipsis is to take place. 393 * So each line must >= 0 394 */ testGetEllipsisCount()395 public void testGetEllipsisCount() { 396 // Multilines (6 lines) and TruncateAt.START so no ellipsis at all 397 mDefaultLayout = createEllipsizeStaticLayout(LAYOUT_TEXT, 398 TextUtils.TruncateAt.MIDDLE, 399 Integer.MAX_VALUE /* maxLines */); 400 401 assertTrue(mDefaultLayout.getEllipsisCount(0) == 0); 402 assertTrue(mDefaultLayout.getEllipsisCount(1) == 0); 403 assertTrue(mDefaultLayout.getEllipsisCount(2) == 0); 404 assertTrue(mDefaultLayout.getEllipsisCount(3) == 0); 405 assertTrue(mDefaultLayout.getEllipsisCount(4) == 0); 406 assertTrue(mDefaultLayout.getEllipsisCount(5) == 0); 407 408 try { 409 mDefaultLayout.getEllipsisCount(-1); 410 fail("should throw ArrayIndexOutOfBoundsException"); 411 } catch (ArrayIndexOutOfBoundsException e) { 412 } 413 414 try { 415 mDefaultLayout.getEllipsisCount(LARGER_THAN_LINE_COUNT); 416 fail("should throw ArrayIndexOutOfBoundsException"); 417 } catch (ArrayIndexOutOfBoundsException e) { 418 } 419 420 // Multilines (6 lines) and TruncateAt.MIDDLE so no ellipsis at all 421 mDefaultLayout = createEllipsizeStaticLayout(LAYOUT_TEXT, 422 TextUtils.TruncateAt.MIDDLE, 423 Integer.MAX_VALUE /* maxLines */); 424 425 assertTrue(mDefaultLayout.getEllipsisCount(0) == 0); 426 assertTrue(mDefaultLayout.getEllipsisCount(1) == 0); 427 assertTrue(mDefaultLayout.getEllipsisCount(2) == 0); 428 assertTrue(mDefaultLayout.getEllipsisCount(3) == 0); 429 assertTrue(mDefaultLayout.getEllipsisCount(4) == 0); 430 assertTrue(mDefaultLayout.getEllipsisCount(5) == 0); 431 432 // Multilines (6 lines) and TruncateAt.END so ellipsis only on the last line 433 mDefaultLayout = createEllipsizeStaticLayout(LAYOUT_TEXT, 434 TextUtils.TruncateAt.END, 435 Integer.MAX_VALUE /* maxLines */); 436 437 assertTrue(mDefaultLayout.getEllipsisCount(0) == 0); 438 assertTrue(mDefaultLayout.getEllipsisCount(1) == 0); 439 assertTrue(mDefaultLayout.getEllipsisCount(2) == 0); 440 assertTrue(mDefaultLayout.getEllipsisCount(3) == 0); 441 assertTrue(mDefaultLayout.getEllipsisCount(4) == 0); 442 assertTrue(mDefaultLayout.getEllipsisCount(5) > 0); 443 444 // Multilines (6 lines) and TruncateAt.MARQUEE so ellipsis only on the last line 445 mDefaultLayout = createEllipsizeStaticLayout(LAYOUT_TEXT, 446 TextUtils.TruncateAt.END, 447 Integer.MAX_VALUE /* maxLines */); 448 449 assertTrue(mDefaultLayout.getEllipsisCount(0) == 0); 450 assertTrue(mDefaultLayout.getEllipsisCount(1) == 0); 451 assertTrue(mDefaultLayout.getEllipsisCount(2) == 0); 452 assertTrue(mDefaultLayout.getEllipsisCount(3) == 0); 453 assertTrue(mDefaultLayout.getEllipsisCount(4) == 0); 454 assertTrue(mDefaultLayout.getEllipsisCount(5) > 0); 455 } 456 457 /* 458 * Return the offset of the first character to be ellipsized away 459 * relative to the start of the line. 460 * (So 0 if the beginning of the line is ellipsized, not getLineStart().) 461 */ testGetEllipsisStart()462 public void testGetEllipsisStart() { 463 mDefaultLayout = createEllipsizeStaticLayout(); 464 assertTrue(mDefaultLayout.getEllipsisStart(0) >= 0); 465 assertTrue(mDefaultLayout.getEllipsisStart(1) >= 0); 466 467 try { 468 mDefaultLayout.getEllipsisStart(-1); 469 fail("should throw ArrayIndexOutOfBoundsException"); 470 } catch (ArrayIndexOutOfBoundsException e) { 471 } 472 473 try { 474 mDefaultLayout.getEllipsisStart(LARGER_THAN_LINE_COUNT); 475 fail("should throw ArrayIndexOutOfBoundsException"); 476 } catch (ArrayIndexOutOfBoundsException e) { 477 } 478 } 479 480 /* 481 * Return the width to which this Layout is ellipsizing 482 * or getWidth() if it is not doing anything special. 483 * The constructor's Argument TextUtils.TruncateAt defines which EllipsizedWidth to use 484 * ellipsizedWidth if argument is not null 485 * outerWidth if argument is null 486 */ testGetEllipsizedWidth()487 public void testGetEllipsizedWidth() { 488 int ellipsizedWidth = 60; 489 int outerWidth = 100; 490 StaticLayout layout = new StaticLayout(LAYOUT_TEXT, 0, LAYOUT_TEXT.length(), 491 mDefaultPaint, outerWidth, DEFAULT_ALIGN, SPACE_MULTI, 492 SPACE_ADD, false, TextUtils.TruncateAt.END, ellipsizedWidth); 493 assertEquals(ellipsizedWidth, layout.getEllipsizedWidth()); 494 495 layout = new StaticLayout(LAYOUT_TEXT, 0, LAYOUT_TEXT.length(), 496 mDefaultPaint, outerWidth, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, 497 false, null, ellipsizedWidth); 498 assertEquals(outerWidth, layout.getEllipsizedWidth()); 499 } 500 testEllipsis_singleLine()501 public void testEllipsis_singleLine() { 502 { 503 // Single line case and TruncateAt.END so that we have some ellipsis 504 StaticLayout layout = createEllipsizeStaticLayout(LAYOUT_TEXT_SINGLE_LINE, 505 TextUtils.TruncateAt.END, 1); 506 assertTrue(layout.getEllipsisCount(0) > 0); 507 } 508 { 509 // Single line case and TruncateAt.MIDDLE so that we have some ellipsis 510 StaticLayout layout = createEllipsizeStaticLayout(LAYOUT_TEXT_SINGLE_LINE, 511 TextUtils.TruncateAt.MIDDLE, 1); 512 assertTrue(layout.getEllipsisCount(0) > 0); 513 } 514 { 515 // Single line case and TruncateAt.END so that we have some ellipsis 516 StaticLayout layout = createEllipsizeStaticLayout(LAYOUT_TEXT_SINGLE_LINE, 517 TextUtils.TruncateAt.END, 1); 518 assertTrue(layout.getEllipsisCount(0) > 0); 519 } 520 { 521 // Single line case and TruncateAt.MARQUEE so that we have NO ellipsis 522 StaticLayout layout = createEllipsizeStaticLayout(LAYOUT_TEXT_SINGLE_LINE, 523 TextUtils.TruncateAt.MARQUEE, 1); 524 assertTrue(layout.getEllipsisCount(0) == 0); 525 } 526 527 final String text = "\u3042" // HIRAGANA LETTER A 528 + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; 529 final float textWidth = mDefaultPaint.measureText(text); 530 final int halfWidth = (int)(textWidth / 2.0f); 531 { 532 StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint, 533 halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR, 534 SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.END, halfWidth, 1); 535 assertTrue(layout.getEllipsisCount(0) > 0); 536 assertTrue(layout.getEllipsisStart(0) > 0); 537 } 538 { 539 StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint, 540 halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR, 541 SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.START, halfWidth, 1); 542 assertTrue(layout.getEllipsisCount(0) > 0); 543 assertEquals(0, mDefaultLayout.getEllipsisStart(0)); 544 } 545 { 546 StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint, 547 halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR, 548 SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.MIDDLE, halfWidth, 1); 549 assertTrue(layout.getEllipsisCount(0) > 0); 550 assertTrue(layout.getEllipsisStart(0) > 0); 551 } 552 { 553 StaticLayout layout = new StaticLayout(text, 0, text.length(), mDefaultPaint, 554 halfWidth, DEFAULT_ALIGN, TextDirectionHeuristics.FIRSTSTRONG_LTR, 555 SPACE_MULTI, SPACE_ADD, false, TextUtils.TruncateAt.MARQUEE, halfWidth, 1); 556 assertEquals(0, layout.getEllipsisCount(0)); 557 } 558 } 559 560 /** 561 * scenario description: 562 * 1. set the text. 563 * 2. change the text 564 * 3. Check the text won't change to the StaticLayout 565 */ testImmutableStaticLayout()566 public void testImmutableStaticLayout() { 567 Editable editable = Editable.Factory.getInstance().newEditable("123\t\n555"); 568 StaticLayout layout = new StaticLayout(editable, mDefaultPaint, 569 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 570 571 assertEquals(2, layout.getLineCount()); 572 assertTrue(mDefaultLayout.getLineContainsTab(0)); 573 574 // change the text 575 editable.delete(0, editable.length() - 1); 576 577 assertEquals(2, layout.getLineCount()); 578 assertTrue(layout.getLineContainsTab(0)); 579 580 } 581 582 // String wrapper for testing not well known implementation of CharSequence. 583 private class FakeCharSequence implements CharSequence { 584 private String mStr; 585 FakeCharSequence(String str)586 public FakeCharSequence(String str) { 587 mStr = str; 588 } 589 590 @Override charAt(int index)591 public char charAt(int index) { 592 return mStr.charAt(index); 593 } 594 595 @Override length()596 public int length() { 597 return mStr.length(); 598 } 599 600 @Override subSequence(int start, int end)601 public CharSequence subSequence(int start, int end) { 602 return mStr.subSequence(start, end); 603 } 604 605 @Override toString()606 public String toString() { 607 return mStr; 608 } 609 }; 610 buildTestCharSequences(String testString, Normalizer.Form[] forms)611 private List<CharSequence> buildTestCharSequences(String testString, Normalizer.Form[] forms) { 612 List<CharSequence> result = new ArrayList<CharSequence>(); 613 614 List<String> normalizedStrings = new ArrayList<String>(); 615 for (Normalizer.Form form: forms) { 616 normalizedStrings.add(Normalizer.normalize(testString, form)); 617 } 618 619 for (String str: normalizedStrings) { 620 result.add(str); 621 result.add(new SpannedString(str)); 622 result.add(new SpannableString(str)); 623 result.add(new SpannableStringBuilder(str)); // as a GraphicsOperations implementation. 624 result.add(new FakeCharSequence(str)); // as a not well known implementation. 625 } 626 return result; 627 } 628 buildTestMessage(CharSequence seq)629 private String buildTestMessage(CharSequence seq) { 630 String normalized; 631 if (Normalizer.isNormalized(seq, Normalizer.Form.NFC)) { 632 normalized = "NFC"; 633 } else if (Normalizer.isNormalized(seq, Normalizer.Form.NFD)) { 634 normalized = "NFD"; 635 } else if (Normalizer.isNormalized(seq, Normalizer.Form.NFKC)) { 636 normalized = "NFKC"; 637 } else if (Normalizer.isNormalized(seq, Normalizer.Form.NFKD)) { 638 normalized = "NFKD"; 639 } else { 640 throw new IllegalStateException("Normalized form is not NFC/NFD/NFKC/NFKD"); 641 } 642 643 StringBuilder builder = new StringBuilder(); 644 for (int i = 0; i < seq.length(); ++i) { 645 builder.append(String.format("0x%04X ", Integer.valueOf(seq.charAt(i)))); 646 } 647 648 return "testString: \"" + seq.toString() + "\"[" + builder.toString() + "]" + 649 ", class: " + seq.getClass().getName() + 650 ", Normalization: " + normalized; 651 } 652 testGetOffset_ASCII()653 public void testGetOffset_ASCII() { 654 String testStrings[] = { "abcde", "ab\ncd", "ab\tcd", "ab\n\nc", "ab\n\tc" }; 655 656 for (String testString: testStrings) { 657 for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) { 658 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 659 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 660 661 String testLabel = buildTestMessage(seq); 662 663 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(0)); 664 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(1)); 665 assertEquals(testLabel, 1, layout.getOffsetToLeftOf(2)); 666 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(3)); 667 assertEquals(testLabel, 3, layout.getOffsetToLeftOf(4)); 668 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(5)); 669 670 assertEquals(testLabel, 1, layout.getOffsetToRightOf(0)); 671 assertEquals(testLabel, 2, layout.getOffsetToRightOf(1)); 672 assertEquals(testLabel, 3, layout.getOffsetToRightOf(2)); 673 assertEquals(testLabel, 4, layout.getOffsetToRightOf(3)); 674 assertEquals(testLabel, 5, layout.getOffsetToRightOf(4)); 675 assertEquals(testLabel, 5, layout.getOffsetToRightOf(5)); 676 } 677 } 678 679 String testString = "ab\r\nde"; 680 for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) { 681 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 682 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 683 684 String testLabel = buildTestMessage(seq); 685 686 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(0)); 687 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(1)); 688 assertEquals(testLabel, 1, layout.getOffsetToLeftOf(2)); 689 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(3)); 690 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(4)); 691 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(5)); 692 assertEquals(testLabel, 5, layout.getOffsetToLeftOf(6)); 693 694 assertEquals(testLabel, 1, layout.getOffsetToRightOf(0)); 695 assertEquals(testLabel, 2, layout.getOffsetToRightOf(1)); 696 assertEquals(testLabel, 4, layout.getOffsetToRightOf(2)); 697 assertEquals(testLabel, 4, layout.getOffsetToRightOf(3)); 698 assertEquals(testLabel, 5, layout.getOffsetToRightOf(4)); 699 assertEquals(testLabel, 6, layout.getOffsetToRightOf(5)); 700 assertEquals(testLabel, 6, layout.getOffsetToRightOf(6)); 701 } 702 } 703 testGetOffset_UNICODE()704 public void testGetOffset_UNICODE() { 705 String testStrings[] = new String[] { 706 // Cyrillic alphabets. 707 "\u0410\u0411\u0412\u0413\u0414", 708 // Japanese Hiragana Characters. 709 "\u3042\u3044\u3046\u3048\u304A", 710 }; 711 712 for (String testString: testStrings) { 713 for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) { 714 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 715 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 716 717 String testLabel = buildTestMessage(seq); 718 719 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(0)); 720 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(1)); 721 assertEquals(testLabel, 1, layout.getOffsetToLeftOf(2)); 722 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(3)); 723 assertEquals(testLabel, 3, layout.getOffsetToLeftOf(4)); 724 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(5)); 725 726 assertEquals(testLabel, 1, layout.getOffsetToRightOf(0)); 727 assertEquals(testLabel, 2, layout.getOffsetToRightOf(1)); 728 assertEquals(testLabel, 3, layout.getOffsetToRightOf(2)); 729 assertEquals(testLabel, 4, layout.getOffsetToRightOf(3)); 730 assertEquals(testLabel, 5, layout.getOffsetToRightOf(4)); 731 assertEquals(testLabel, 5, layout.getOffsetToRightOf(5)); 732 } 733 } 734 } 735 testGetOffset_UNICODE_Normalization()736 public void testGetOffset_UNICODE_Normalization() { 737 // "A" with acute, circumflex, tilde, diaeresis, ring above. 738 String testString = "\u00C1\u00C2\u00C3\u00C4\u00C5"; 739 Normalizer.Form[] oneUnicodeForms = { Normalizer.Form.NFC, Normalizer.Form.NFKC }; 740 for (CharSequence seq: buildTestCharSequences(testString, oneUnicodeForms)) { 741 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 742 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 743 744 String testLabel = buildTestMessage(seq); 745 746 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(0)); 747 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(1)); 748 assertEquals(testLabel, 1, layout.getOffsetToLeftOf(2)); 749 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(3)); 750 assertEquals(testLabel, 3, layout.getOffsetToLeftOf(4)); 751 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(5)); 752 753 assertEquals(testLabel, 1, layout.getOffsetToRightOf(0)); 754 assertEquals(testLabel, 2, layout.getOffsetToRightOf(1)); 755 assertEquals(testLabel, 3, layout.getOffsetToRightOf(2)); 756 assertEquals(testLabel, 4, layout.getOffsetToRightOf(3)); 757 assertEquals(testLabel, 5, layout.getOffsetToRightOf(4)); 758 assertEquals(testLabel, 5, layout.getOffsetToRightOf(5)); 759 } 760 761 Normalizer.Form[] twoUnicodeForms = { Normalizer.Form.NFD, Normalizer.Form.NFKD }; 762 for (CharSequence seq: buildTestCharSequences(testString, twoUnicodeForms)) { 763 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 764 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 765 766 String testLabel = buildTestMessage(seq); 767 768 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(0)); 769 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(1)); 770 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(2)); 771 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(3)); 772 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(4)); 773 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(5)); 774 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(6)); 775 assertEquals(testLabel, 6, layout.getOffsetToLeftOf(7)); 776 assertEquals(testLabel, 6, layout.getOffsetToLeftOf(8)); 777 assertEquals(testLabel, 8, layout.getOffsetToLeftOf(9)); 778 assertEquals(testLabel, 8, layout.getOffsetToLeftOf(10)); 779 780 assertEquals(testLabel, 2, layout.getOffsetToRightOf(0)); 781 assertEquals(testLabel, 2, layout.getOffsetToRightOf(1)); 782 assertEquals(testLabel, 4, layout.getOffsetToRightOf(2)); 783 assertEquals(testLabel, 4, layout.getOffsetToRightOf(3)); 784 assertEquals(testLabel, 6, layout.getOffsetToRightOf(4)); 785 assertEquals(testLabel, 6, layout.getOffsetToRightOf(5)); 786 assertEquals(testLabel, 8, layout.getOffsetToRightOf(6)); 787 assertEquals(testLabel, 8, layout.getOffsetToRightOf(7)); 788 assertEquals(testLabel, 10, layout.getOffsetToRightOf(8)); 789 assertEquals(testLabel, 10, layout.getOffsetToRightOf(9)); 790 assertEquals(testLabel, 10, layout.getOffsetToRightOf(10)); 791 } 792 } 793 testGetOffset_UNICODE_SurrogatePairs()794 public void testGetOffset_UNICODE_SurrogatePairs() { 795 // Emoticons for surrogate pairs tests. 796 String testString = 797 "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03\uD83D\uDE04"; 798 for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) { 799 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 800 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 801 802 String testLabel = buildTestMessage(seq); 803 804 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(0)); 805 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(1)); 806 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(2)); 807 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(3)); 808 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(4)); 809 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(5)); 810 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(6)); 811 assertEquals(testLabel, 6, layout.getOffsetToLeftOf(7)); 812 assertEquals(testLabel, 6, layout.getOffsetToLeftOf(8)); 813 assertEquals(testLabel, 8, layout.getOffsetToLeftOf(9)); 814 assertEquals(testLabel, 8, layout.getOffsetToLeftOf(10)); 815 816 assertEquals(testLabel, 2, layout.getOffsetToRightOf(0)); 817 assertEquals(testLabel, 2, layout.getOffsetToRightOf(1)); 818 assertEquals(testLabel, 4, layout.getOffsetToRightOf(2)); 819 assertEquals(testLabel, 4, layout.getOffsetToRightOf(3)); 820 assertEquals(testLabel, 6, layout.getOffsetToRightOf(4)); 821 assertEquals(testLabel, 6, layout.getOffsetToRightOf(5)); 822 assertEquals(testLabel, 8, layout.getOffsetToRightOf(6)); 823 assertEquals(testLabel, 8, layout.getOffsetToRightOf(7)); 824 assertEquals(testLabel, 10, layout.getOffsetToRightOf(8)); 825 assertEquals(testLabel, 10, layout.getOffsetToRightOf(9)); 826 assertEquals(testLabel, 10, layout.getOffsetToRightOf(10)); 827 } 828 } 829 testGetOffset_UNICODE_Thai()830 public void testGetOffset_UNICODE_Thai() { 831 // Thai Characters. The expected cursorable boundary is 832 // | \u0E02 | \u0E2D | \u0E1A | \u0E04\u0E38 | \u0E13 | 833 String testString = "\u0E02\u0E2D\u0E1A\u0E04\u0E38\u0E13"; 834 for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) { 835 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 836 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 837 838 String testLabel = buildTestMessage(seq); 839 840 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(0)); 841 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(1)); 842 assertEquals(testLabel, 1, layout.getOffsetToLeftOf(2)); 843 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(3)); 844 assertEquals(testLabel, 3, layout.getOffsetToLeftOf(4)); 845 assertEquals(testLabel, 3, layout.getOffsetToLeftOf(5)); 846 assertEquals(testLabel, 5, layout.getOffsetToLeftOf(6)); 847 848 assertEquals(testLabel, 1, layout.getOffsetToRightOf(0)); 849 assertEquals(testLabel, 2, layout.getOffsetToRightOf(1)); 850 assertEquals(testLabel, 3, layout.getOffsetToRightOf(2)); 851 assertEquals(testLabel, 5, layout.getOffsetToRightOf(3)); 852 assertEquals(testLabel, 5, layout.getOffsetToRightOf(4)); 853 assertEquals(testLabel, 6, layout.getOffsetToRightOf(5)); 854 assertEquals(testLabel, 6, layout.getOffsetToRightOf(6)); 855 } 856 } 857 testGetOffset_UNICODE_Hebrew()858 public void testGetOffset_UNICODE_Hebrew() { 859 String testString = "\u05DE\u05E1\u05E2\u05D3\u05D4"; // Hebrew Characters 860 for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) { 861 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 862 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, 863 TextDirectionHeuristics.RTL, SPACE_MULTI, SPACE_ADD, true); 864 865 String testLabel = buildTestMessage(seq); 866 867 assertEquals(testLabel, 1, layout.getOffsetToLeftOf(0)); 868 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(1)); 869 assertEquals(testLabel, 3, layout.getOffsetToLeftOf(2)); 870 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(3)); 871 assertEquals(testLabel, 5, layout.getOffsetToLeftOf(4)); 872 assertEquals(testLabel, 5, layout.getOffsetToLeftOf(5)); 873 874 assertEquals(testLabel, 0, layout.getOffsetToRightOf(0)); 875 assertEquals(testLabel, 0, layout.getOffsetToRightOf(1)); 876 assertEquals(testLabel, 1, layout.getOffsetToRightOf(2)); 877 assertEquals(testLabel, 2, layout.getOffsetToRightOf(3)); 878 assertEquals(testLabel, 3, layout.getOffsetToRightOf(4)); 879 assertEquals(testLabel, 4, layout.getOffsetToRightOf(5)); 880 } 881 } 882 testGetOffset_UNICODE_Arabic()883 public void testGetOffset_UNICODE_Arabic() { 884 // Arabic Characters. The expected cursorable boundary is 885 // | \u0623 \u064F | \u0633 \u0652 | \u0631 \u064E | \u0629 \u064C |"; 886 String testString = "\u0623\u064F\u0633\u0652\u0631\u064E\u0629\u064C"; 887 888 Normalizer.Form[] oneUnicodeForms = { Normalizer.Form.NFC, Normalizer.Form.NFKC }; 889 for (CharSequence seq: buildTestCharSequences(testString, oneUnicodeForms)) { 890 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 891 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 892 893 String testLabel = buildTestMessage(seq); 894 895 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(0)); 896 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(1)); 897 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(2)); 898 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(3)); 899 assertEquals(testLabel, 6, layout.getOffsetToLeftOf(4)); 900 assertEquals(testLabel, 6, layout.getOffsetToLeftOf(5)); 901 assertEquals(testLabel, 8, layout.getOffsetToLeftOf(6)); 902 assertEquals(testLabel, 8, layout.getOffsetToLeftOf(7)); 903 assertEquals(testLabel, 8, layout.getOffsetToLeftOf(8)); 904 905 assertEquals(testLabel, 0, layout.getOffsetToRightOf(0)); 906 assertEquals(testLabel, 0, layout.getOffsetToRightOf(1)); 907 assertEquals(testLabel, 0, layout.getOffsetToRightOf(2)); 908 assertEquals(testLabel, 2, layout.getOffsetToRightOf(3)); 909 assertEquals(testLabel, 2, layout.getOffsetToRightOf(4)); 910 assertEquals(testLabel, 4, layout.getOffsetToRightOf(5)); 911 assertEquals(testLabel, 4, layout.getOffsetToRightOf(6)); 912 assertEquals(testLabel, 6, layout.getOffsetToRightOf(7)); 913 assertEquals(testLabel, 6, layout.getOffsetToRightOf(8)); 914 } 915 } 916 testGetOffset_UNICODE_Bidi()917 public void testGetOffset_UNICODE_Bidi() { 918 // String having RTL characters and LTR characters 919 920 // LTR Context 921 // The first and last two characters are LTR characters. 922 String testString = "\u0061\u0062\u05DE\u05E1\u05E2\u0063\u0064"; 923 // Logical order: [L1] [L2] [R1] [R2] [R3] [L3] [L4] 924 // 0 1 2 3 4 5 6 7 925 // Display order: [L1] [L2] [R3] [R2] [R1] [L3] [L4] 926 // 0 1 2 4 3 5 6 7 927 // [L?] means ?th LTR character and [R?] means ?th RTL character. 928 for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) { 929 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 930 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 931 932 String testLabel = buildTestMessage(seq); 933 934 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(0)); 935 assertEquals(testLabel, 0, layout.getOffsetToLeftOf(1)); 936 assertEquals(testLabel, 1, layout.getOffsetToLeftOf(2)); 937 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(3)); 938 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(4)); 939 assertEquals(testLabel, 3, layout.getOffsetToLeftOf(5)); 940 assertEquals(testLabel, 5, layout.getOffsetToLeftOf(6)); 941 assertEquals(testLabel, 6, layout.getOffsetToLeftOf(7)); 942 943 assertEquals(testLabel, 1, layout.getOffsetToRightOf(0)); 944 assertEquals(testLabel, 2, layout.getOffsetToRightOf(1)); 945 assertEquals(testLabel, 4, layout.getOffsetToRightOf(2)); 946 assertEquals(testLabel, 5, layout.getOffsetToRightOf(3)); 947 assertEquals(testLabel, 3, layout.getOffsetToRightOf(4)); 948 assertEquals(testLabel, 6, layout.getOffsetToRightOf(5)); 949 assertEquals(testLabel, 7, layout.getOffsetToRightOf(6)); 950 assertEquals(testLabel, 7, layout.getOffsetToRightOf(7)); 951 } 952 953 // RTL Context 954 // The first and last two characters are RTL characters. 955 String testString2 = "\u05DE\u05E1\u0063\u0064\u0065\u05DE\u05E1"; 956 // Logical order: [R1] [R2] [L1] [L2] [L3] [R3] [R4] 957 // 0 1 2 3 4 5 6 7 958 // Display order: [R4] [R3] [L1] [L2] [L3] [R2] [R1] 959 // 7 6 5 3 4 2 1 0 960 // [L?] means ?th LTR character and [R?] means ?th RTL character. 961 for (CharSequence seq: buildTestCharSequences(testString2, Normalizer.Form.values())) { 962 StaticLayout layout = new StaticLayout(seq, mDefaultPaint, 963 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 964 965 String testLabel = buildTestMessage(seq); 966 967 assertEquals(testLabel, 1, layout.getOffsetToLeftOf(0)); 968 assertEquals(testLabel, 2, layout.getOffsetToLeftOf(1)); 969 assertEquals(testLabel, 4, layout.getOffsetToLeftOf(2)); 970 assertEquals(testLabel, 5, layout.getOffsetToLeftOf(3)); 971 assertEquals(testLabel, 3, layout.getOffsetToLeftOf(4)); 972 assertEquals(testLabel, 6, layout.getOffsetToLeftOf(5)); 973 assertEquals(testLabel, 7, layout.getOffsetToLeftOf(6)); 974 assertEquals(testLabel, 7, layout.getOffsetToLeftOf(7)); 975 976 assertEquals(testLabel, 0, layout.getOffsetToRightOf(0)); 977 assertEquals(testLabel, 0, layout.getOffsetToRightOf(1)); 978 assertEquals(testLabel, 1, layout.getOffsetToRightOf(2)); 979 assertEquals(testLabel, 4, layout.getOffsetToRightOf(3)); 980 assertEquals(testLabel, 2, layout.getOffsetToRightOf(4)); 981 assertEquals(testLabel, 3, layout.getOffsetToRightOf(5)); 982 assertEquals(testLabel, 5, layout.getOffsetToRightOf(6)); 983 assertEquals(testLabel, 6, layout.getOffsetToRightOf(7)); 984 } 985 } 986 moveCursorToRightCursorableOffset(EditorState state)987 private void moveCursorToRightCursorableOffset(EditorState state) { 988 assertEquals("The editor has selection", state.mSelectionStart, state.mSelectionEnd); 989 StaticLayout layout = StaticLayout.Builder.obtain(state.mText, 0, state.mText.length(), 990 mDefaultPaint, DEFAULT_OUTER_WIDTH).build(); 991 final int newOffset = layout.getOffsetToRightOf(state.mSelectionStart); 992 state.mSelectionStart = state.mSelectionEnd = newOffset; 993 } 994 moveCursorToLeftCursorableOffset(EditorState state)995 private void moveCursorToLeftCursorableOffset(EditorState state) { 996 assertEquals("The editor has selection", state.mSelectionStart, state.mSelectionEnd); 997 StaticLayout layout = StaticLayout.Builder.obtain(state.mText, 0, state.mText.length(), 998 mDefaultPaint, DEFAULT_OUTER_WIDTH).build(); 999 final int newOffset = layout.getOffsetToLeftOf(state.mSelectionStart); 1000 state.mSelectionStart = state.mSelectionEnd = newOffset; 1001 } 1002 testGetOffset_Emoji()1003 public void testGetOffset_Emoji() { 1004 EditorState state = new EditorState(); 1005 1006 // Emojis 1007 // U+00A9 is COPYRIGHT SIGN. 1008 state.setByString("| U+00A9 U+00A9 U+00A9"); 1009 moveCursorToRightCursorableOffset(state); 1010 state.assertEquals("U+00A9 | U+00A9 U+00A9"); 1011 moveCursorToRightCursorableOffset(state); 1012 state.assertEquals("U+00A9 U+00A9 | U+00A9"); 1013 moveCursorToRightCursorableOffset(state); 1014 state.assertEquals("U+00A9 U+00A9 U+00A9 |"); 1015 moveCursorToRightCursorableOffset(state); 1016 state.assertEquals("U+00A9 U+00A9 U+00A9 |"); 1017 moveCursorToLeftCursorableOffset(state); 1018 state.assertEquals("U+00A9 U+00A9 | U+00A9"); 1019 moveCursorToLeftCursorableOffset(state); 1020 state.assertEquals("U+00A9 | U+00A9 U+00A9"); 1021 moveCursorToLeftCursorableOffset(state); 1022 state.assertEquals("| U+00A9 U+00A9 U+00A9"); 1023 moveCursorToLeftCursorableOffset(state); 1024 state.assertEquals("| U+00A9 U+00A9 U+00A9"); 1025 1026 // Surrogate pairs 1027 // U+1F468 is MAN. 1028 state.setByString("| U+1F468 U+1F468 U+1F468"); 1029 moveCursorToRightCursorableOffset(state); 1030 state.assertEquals("U+1F468 | U+1F468 U+1F468"); 1031 moveCursorToRightCursorableOffset(state); 1032 state.assertEquals("U+1F468 U+1F468 | U+1F468"); 1033 moveCursorToRightCursorableOffset(state); 1034 state.assertEquals("U+1F468 U+1F468 U+1F468 |"); 1035 moveCursorToRightCursorableOffset(state); 1036 state.assertEquals("U+1F468 U+1F468 U+1F468 |"); 1037 moveCursorToLeftCursorableOffset(state); 1038 state.assertEquals("U+1F468 U+1F468 | U+1F468"); 1039 moveCursorToLeftCursorableOffset(state); 1040 state.assertEquals("U+1F468 | U+1F468 U+1F468"); 1041 moveCursorToLeftCursorableOffset(state); 1042 state.assertEquals("| U+1F468 U+1F468 U+1F468"); 1043 moveCursorToLeftCursorableOffset(state); 1044 state.assertEquals("| U+1F468 U+1F468 U+1F468"); 1045 1046 // Keycaps 1047 // U+20E3 is COMBINING ENCLOSING KEYCAP. 1048 state.setByString("| '1' U+20E3 '1' U+20E3 '1' U+20E3"); 1049 moveCursorToRightCursorableOffset(state); 1050 state.assertEquals("'1' U+20E3 | '1' U+20E3 '1' U+20E3"); 1051 moveCursorToRightCursorableOffset(state); 1052 state.assertEquals("'1' U+20E3 '1' U+20E3 | '1' U+20E3"); 1053 moveCursorToRightCursorableOffset(state); 1054 state.assertEquals("'1' U+20E3 '1' U+20E3 '1' U+20E3 |"); 1055 moveCursorToRightCursorableOffset(state); 1056 state.assertEquals("'1' U+20E3 '1' U+20E3 '1' U+20E3 |"); 1057 moveCursorToLeftCursorableOffset(state); 1058 state.assertEquals("'1' U+20E3 '1' U+20E3 | '1' U+20E3"); 1059 moveCursorToLeftCursorableOffset(state); 1060 state.assertEquals("'1' U+20E3 | '1' U+20E3 '1' U+20E3"); 1061 moveCursorToLeftCursorableOffset(state); 1062 state.assertEquals("| '1' U+20E3 '1' U+20E3 '1' U+20E3"); 1063 moveCursorToLeftCursorableOffset(state); 1064 state.assertEquals("| '1' U+20E3 '1' U+20E3 '1' U+20E3"); 1065 1066 // Variation selectors 1067 // U+00A9 is COPYRIGHT SIGN, U+FE0E is VARIATION SELECTOR-15. U+FE0F is VARIATION 1068 // SELECTOR-16. 1069 state.setByString("| U+00A9 U+FE0E U+00A9 U+FE0F U+00A9 U+FE0E"); 1070 moveCursorToRightCursorableOffset(state); 1071 state.assertEquals("U+00A9 U+FE0E | U+00A9 U+FE0F U+00A9 U+FE0E"); 1072 moveCursorToRightCursorableOffset(state); 1073 state.assertEquals("U+00A9 U+FE0E U+00A9 U+FE0F | U+00A9 U+FE0E"); 1074 moveCursorToRightCursorableOffset(state); 1075 state.assertEquals("U+00A9 U+FE0E U+00A9 U+FE0F U+00A9 U+FE0E |"); 1076 moveCursorToRightCursorableOffset(state); 1077 state.assertEquals("U+00A9 U+FE0E U+00A9 U+FE0F U+00A9 U+FE0E |"); 1078 moveCursorToLeftCursorableOffset(state); 1079 state.assertEquals("U+00A9 U+FE0E U+00A9 U+FE0F | U+00A9 U+FE0E"); 1080 moveCursorToLeftCursorableOffset(state); 1081 state.assertEquals("U+00A9 U+FE0E | U+00A9 U+FE0F U+00A9 U+FE0E"); 1082 moveCursorToLeftCursorableOffset(state); 1083 state.assertEquals("| U+00A9 U+FE0E U+00A9 U+FE0F U+00A9 U+FE0E"); 1084 moveCursorToLeftCursorableOffset(state); 1085 state.assertEquals("| U+00A9 U+FE0E U+00A9 U+FE0F U+00A9 U+FE0E"); 1086 1087 // Keycap + variation selector 1088 state.setByString("| '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3"); 1089 moveCursorToRightCursorableOffset(state); 1090 state.assertEquals("'1' U+FE0E U+20E3 | '1' U+FE0E U+20E3 '1' U+FE0E U+20E3"); 1091 moveCursorToRightCursorableOffset(state); 1092 state.assertEquals("'1' U+FE0E U+20E3 '1' U+FE0E U+20E3 | '1' U+FE0E U+20E3"); 1093 moveCursorToRightCursorableOffset(state); 1094 state.assertEquals("'1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 |"); 1095 moveCursorToRightCursorableOffset(state); 1096 state.assertEquals("'1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 |"); 1097 moveCursorToLeftCursorableOffset(state); 1098 state.assertEquals("'1' U+FE0E U+20E3 '1' U+FE0E U+20E3 | '1' U+FE0E U+20E3"); 1099 moveCursorToLeftCursorableOffset(state); 1100 state.assertEquals("'1' U+FE0E U+20E3 | '1' U+FE0E U+20E3 '1' U+FE0E U+20E3"); 1101 moveCursorToLeftCursorableOffset(state); 1102 state.assertEquals("| '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3"); 1103 moveCursorToLeftCursorableOffset(state); 1104 state.assertEquals("| '1' U+FE0E U+20E3 '1' U+FE0E U+20E3 '1' U+FE0E U+20E3"); 1105 1106 // Flags 1107 // U+1F1E6 U+1F1E8 is Ascension Island flag. 1108 state.setByString("| U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8"); 1109 moveCursorToRightCursorableOffset(state); 1110 state.assertEquals("U+1F1E6 U+1F1E8 | U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8"); 1111 moveCursorToRightCursorableOffset(state); 1112 state.assertEquals("U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 | U+1F1E6 U+1F1E8"); 1113 moveCursorToRightCursorableOffset(state); 1114 state.assertEquals("U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 |"); 1115 moveCursorToRightCursorableOffset(state); 1116 state.assertEquals("U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 |"); 1117 moveCursorToLeftCursorableOffset(state); 1118 state.assertEquals("U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 | U+1F1E6 U+1F1E8"); 1119 moveCursorToLeftCursorableOffset(state); 1120 state.assertEquals("U+1F1E6 U+1F1E8 | U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8"); 1121 moveCursorToLeftCursorableOffset(state); 1122 state.assertEquals("| U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8"); 1123 moveCursorToLeftCursorableOffset(state); 1124 state.assertEquals("| U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8 U+1F1E6 U+1F1E8"); 1125 } 1126 testGetOffsetForHorizontal_Multilines()1127 public void testGetOffsetForHorizontal_Multilines() { 1128 // Emoticons for surrogate pairs tests. 1129 String testString = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03\uD83D\uDE04"; 1130 final float width = mDefaultPaint.measureText(testString, 0, 6); 1131 StaticLayout layout = new StaticLayout(testString, mDefaultPaint, (int)width, 1132 DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 1133 // We expect the line break to be after the third emoticon, but we allow flexibility of the 1134 // line break algorithm as long as the break is within the string. These other cases might 1135 // happen if for example the font has kerning between emoticons. 1136 final int lineBreakOffset = layout.getOffsetForHorizontal(1, 0.0f); 1137 assertEquals(0, layout.getLineForOffset(lineBreakOffset - 1)); 1138 1139 assertEquals(0, layout.getOffsetForHorizontal(0, 0.0f)); 1140 assertEquals(lineBreakOffset - 2, layout.getOffsetForHorizontal(0, width)); 1141 assertEquals(lineBreakOffset - 2, layout.getOffsetForHorizontal(0, width * 2)); 1142 1143 final int lineCount = layout.getLineCount(); 1144 assertEquals(testString.length(), layout.getOffsetForHorizontal(lineCount - 1, width)); 1145 assertEquals(testString.length(), layout.getOffsetForHorizontal(lineCount - 1, width * 2)); 1146 } 1147 testIsRtlCharAt()1148 public void testIsRtlCharAt() { 1149 { 1150 String testString = "ab(\u0623\u0624)c\u0625"; 1151 StaticLayout layout = new StaticLayout(testString, mDefaultPaint, 1152 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 1153 1154 assertFalse(layout.isRtlCharAt(0)); 1155 assertFalse(layout.isRtlCharAt(1)); 1156 assertFalse(layout.isRtlCharAt(2)); 1157 assertTrue(layout.isRtlCharAt(3)); 1158 assertTrue(layout.isRtlCharAt(4)); 1159 assertFalse(layout.isRtlCharAt(5)); 1160 assertFalse(layout.isRtlCharAt(6)); 1161 assertTrue(layout.isRtlCharAt(7)); 1162 } 1163 { 1164 String testString = "\u0623\u0624(ab)\u0625c"; 1165 StaticLayout layout = new StaticLayout(testString, mDefaultPaint, 1166 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 1167 1168 assertTrue(layout.isRtlCharAt(0)); 1169 assertTrue(layout.isRtlCharAt(1)); 1170 assertTrue(layout.isRtlCharAt(2)); 1171 assertFalse(layout.isRtlCharAt(3)); 1172 assertFalse(layout.isRtlCharAt(4)); 1173 assertTrue(layout.isRtlCharAt(5)); 1174 assertTrue(layout.isRtlCharAt(6)); 1175 assertFalse(layout.isRtlCharAt(7)); 1176 assertFalse(layout.isRtlCharAt(8)); 1177 } 1178 } 1179 testGetHorizontal()1180 public void testGetHorizontal() { 1181 String testString = "abc\u0623\u0624\u0625def"; 1182 StaticLayout layout = new StaticLayout(testString, mDefaultPaint, 1183 DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 1184 1185 assertEquals(layout.getPrimaryHorizontal(0), layout.getSecondaryHorizontal(0)); 1186 assertTrue(layout.getPrimaryHorizontal(0) < layout.getPrimaryHorizontal(3)); 1187 assertTrue(layout.getPrimaryHorizontal(3) < layout.getSecondaryHorizontal(3)); 1188 assertTrue(layout.getPrimaryHorizontal(4) < layout.getSecondaryHorizontal(3)); 1189 assertEquals(layout.getPrimaryHorizontal(4), layout.getSecondaryHorizontal(4)); 1190 assertEquals(layout.getPrimaryHorizontal(3), layout.getSecondaryHorizontal(6)); 1191 assertEquals(layout.getPrimaryHorizontal(6), layout.getSecondaryHorizontal(3)); 1192 assertEquals(layout.getPrimaryHorizontal(7), layout.getSecondaryHorizontal(7)); 1193 } 1194 1195 public void testVeryLargeString() { 1196 final int MAX_COUNT = 1 << 21; 1197 final int WORD_SIZE = 32; 1198 char[] longText = new char[MAX_COUNT]; 1199 for (int n = 0; n < MAX_COUNT; n++) { 1200 longText[n] = (n % WORD_SIZE) == 0 ? ' ' : 'm'; 1201 } 1202 String longTextString = new String(longText); 1203 TextPaint paint = new TestingTextPaint(); 1204 StaticLayout layout = new StaticLayout(longTextString, paint, DEFAULT_OUTER_WIDTH, 1205 DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true); 1206 assertNotNull(layout); 1207 } 1208 1209 public void testDoesntCrashWhenWordStyleOverlap() { 1210 // test case where word boundary overlaps multiple style spans 1211 SpannableStringBuilder text = new SpannableStringBuilder("word boundaries, overlap style"); 1212 // span covers "boundaries" 1213 text.setSpan(new StyleSpan(Typeface.BOLD), 1214 "word ".length(), "word boundaries".length(), 1215 Spanned.SPAN_INCLUSIVE_INCLUSIVE); 1216 mDefaultPaint.setTextLocale(Locale.US); 1217 StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), 1218 mDefaultPaint, DEFAULT_OUTER_WIDTH) 1219 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) // enable hyphenation 1220 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) 1221 .build(); 1222 assertNotNull(layout); 1223 } 1224 } 1225