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 static android.text.Layout.Alignment.ALIGN_NORMAL; 20 import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.assertNotNull; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assert.fail; 27 import static org.mockito.Mockito.mock; 28 29 import android.graphics.Paint.FontMetricsInt; 30 import android.support.test.filters.SmallTest; 31 import android.support.test.runner.AndroidJUnit4; 32 import android.text.DynamicLayout; 33 import android.text.Layout; 34 import android.text.SpannableStringBuilder; 35 import android.text.StaticLayout; 36 import android.text.TextPaint; 37 import android.text.TextUtils; 38 import android.text.style.TypefaceSpan; 39 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 @SmallTest 45 @RunWith(AndroidJUnit4.class) 46 public class DynamicLayoutTest { 47 48 private static final float SPACING_MULT_NO_SCALE = 1.0f; 49 private static final float SPACING_ADD_NO_SCALE = 0.0f; 50 private static final int DEFAULT_OUTER_WIDTH = 150; 51 private static final int ELLIPSIZE_WIDTH = 8; 52 private static final CharSequence SINGLELINE_CHAR_SEQUENCE = "......"; 53 private static final String[] TEXT = {"CharSequence\n", "Char\tSequence\n", "CharSequence"}; 54 private static final CharSequence MULTLINE_CHAR_SEQUENCE = TEXT[0] + TEXT[1] + TEXT[2]; 55 private static final Layout.Alignment DEFAULT_ALIGN = Layout.Alignment.ALIGN_CENTER; 56 private TextPaint mDefaultPaint; 57 58 private static final int LINE0 = 0; 59 private static final int LINE0_TOP = 0; 60 private static final int LINE1 = 1; 61 private static final int LINE2 = 2; 62 private static final int LINE3 = 3; 63 64 private static final int ELLIPSIS_UNDEFINED = 0x80000000; 65 66 private DynamicLayout mDynamicLayout; 67 68 @Before 69 public void setup() { 70 mDefaultPaint = new TextPaint(); 71 mDynamicLayout = new DynamicLayout(MULTLINE_CHAR_SEQUENCE, 72 mDefaultPaint, 73 DEFAULT_OUTER_WIDTH, 74 DEFAULT_ALIGN, 75 SPACING_MULT_NO_SCALE, 76 SPACING_ADD_NO_SCALE, 77 true); 78 } 79 80 @Test 81 public void testConstructors() { 82 new DynamicLayout(SINGLELINE_CHAR_SEQUENCE, 83 MULTLINE_CHAR_SEQUENCE, 84 mDefaultPaint, 85 DEFAULT_OUTER_WIDTH, 86 DEFAULT_ALIGN, 87 SPACING_MULT_NO_SCALE, 88 SPACING_ADD_NO_SCALE, 89 true); 90 new DynamicLayout(SINGLELINE_CHAR_SEQUENCE, 91 MULTLINE_CHAR_SEQUENCE, 92 mDefaultPaint, 93 DEFAULT_OUTER_WIDTH, 94 DEFAULT_ALIGN, 95 SPACING_MULT_NO_SCALE, 96 SPACING_ADD_NO_SCALE, 97 true, 98 TextUtils.TruncateAt.START, 99 DEFAULT_OUTER_WIDTH); 100 new DynamicLayout(MULTLINE_CHAR_SEQUENCE, 101 mDefaultPaint, 102 DEFAULT_OUTER_WIDTH, 103 DEFAULT_ALIGN, 104 SPACING_MULT_NO_SCALE, 105 SPACING_ADD_NO_SCALE, 106 true); 107 } 108 109 @Test 110 public void testEllipsis() { 111 final DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE, 112 MULTLINE_CHAR_SEQUENCE, 113 mDefaultPaint, 114 DEFAULT_OUTER_WIDTH, 115 DEFAULT_ALIGN, 116 SPACING_MULT_NO_SCALE, 117 SPACING_ADD_NO_SCALE, 118 true, 119 TextUtils.TruncateAt.START, 120 DEFAULT_OUTER_WIDTH); 121 assertEquals(0, dynamicLayout.getEllipsisCount(LINE1)); 122 assertEquals(ELLIPSIS_UNDEFINED, dynamicLayout.getEllipsisStart(LINE1)); 123 assertEquals(DEFAULT_OUTER_WIDTH, dynamicLayout.getEllipsizedWidth()); 124 } 125 126 /* 127 * Test whether include the padding to calculate the layout. 128 * 1. Include padding while calculate the layout. 129 * 2. Don't include padding while calculate the layout. 130 */ 131 @Test 132 public void testIncludePadding() { 133 final FontMetricsInt fontMetricsInt = mDefaultPaint.getFontMetricsInt(); 134 135 DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE, 136 mDefaultPaint, 137 DEFAULT_OUTER_WIDTH, 138 DEFAULT_ALIGN, 139 SPACING_MULT_NO_SCALE, 140 SPACING_ADD_NO_SCALE, 141 true); 142 assertEquals(fontMetricsInt.top - fontMetricsInt.ascent, dynamicLayout.getTopPadding()); 143 assertEquals(fontMetricsInt.bottom - fontMetricsInt.descent, 144 dynamicLayout.getBottomPadding()); 145 146 dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE, 147 mDefaultPaint, 148 DEFAULT_OUTER_WIDTH, 149 DEFAULT_ALIGN, 150 SPACING_MULT_NO_SCALE, 151 SPACING_ADD_NO_SCALE, 152 false); 153 assertEquals(0, dynamicLayout.getTopPadding()); 154 assertEquals(0, dynamicLayout.getBottomPadding()); 155 } 156 157 /* 158 * Test the line count and whether include the Tab the layout. 159 * 1. Include Tab. 2. Don't include Tab Use the Y-coordinate to calculate the line number 160 * Test the line top 161 * 1. the Y-coordinate of line top.2. the Y-coordinate of baseline. 162 */ 163 @Test 164 public void testLineLayout() { 165 assertEquals(TEXT.length, mDynamicLayout.getLineCount()); 166 assertFalse(mDynamicLayout.getLineContainsTab(LINE0)); 167 assertTrue(mDynamicLayout.getLineContainsTab(LINE1)); 168 169 assertEquals(LINE0_TOP, mDynamicLayout.getLineTop(LINE0)); 170 171 assertEquals(mDynamicLayout.getLineBottom(LINE0), mDynamicLayout.getLineTop(LINE1)); 172 assertEquals(mDynamicLayout.getLineBottom(LINE1), mDynamicLayout.getLineTop(LINE2)); 173 assertEquals(mDynamicLayout.getLineBottom(LINE2), mDynamicLayout.getLineTop(LINE3)); 174 175 try { 176 assertEquals(mDynamicLayout.getLineBottom(mDynamicLayout.getLineCount()), 177 mDynamicLayout.getLineTop(mDynamicLayout.getLineCount() + 1)); 178 fail("Test DynamicLayout fail, should throw IndexOutOfBoundsException."); 179 } catch (IndexOutOfBoundsException e) { 180 // expected 181 } 182 183 assertEquals(mDynamicLayout.getLineDescent(LINE0) - mDynamicLayout.getLineAscent(LINE0), 184 mDynamicLayout.getLineBottom(LINE0)); 185 186 assertEquals(mDynamicLayout.getLineDescent(LINE1) - mDynamicLayout.getLineAscent(LINE1), 187 mDynamicLayout.getLineBottom(LINE1) - mDynamicLayout.getLineBottom(LINE0)); 188 189 assertEquals(mDynamicLayout.getLineDescent(LINE2) - mDynamicLayout.getLineAscent(LINE2), 190 mDynamicLayout.getLineBottom(LINE2) - mDynamicLayout.getLineBottom(LINE1)); 191 192 assertEquals(LINE0, mDynamicLayout.getLineForVertical(mDynamicLayout.getLineTop(LINE0))); 193 194 assertNotNull(mDynamicLayout.getLineDirections(LINE0)); 195 assertEquals(Layout.DIR_LEFT_TO_RIGHT, mDynamicLayout.getParagraphDirection(LINE0)); 196 197 assertEquals(0, mDynamicLayout.getLineStart(LINE0)); 198 assertEquals(TEXT[0].length(), mDynamicLayout.getLineStart(LINE1)); 199 assertEquals(TEXT[0].length() + TEXT[1].length(), mDynamicLayout.getLineStart(LINE2)); 200 } 201 202 @Test 203 public void testLineSpacing() { 204 SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc"); 205 final float spacingMultiplier = 2f; 206 final float spacingAdd = 4; 207 final int width = 1000; 208 final TextPaint textPaint = new TextPaint(); 209 // create the DynamicLayout 210 final DynamicLayout dynamicLayout = new DynamicLayout(text, 211 textPaint, 212 width, 213 ALIGN_NORMAL, 214 spacingMultiplier, 215 spacingAdd, 216 false /*includepad*/); 217 218 // create a StaticLayout with same text, this will define the expectations 219 Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd, 220 spacingMultiplier); 221 assertLineSpecs(expected, dynamicLayout); 222 223 // add a new line to the end, DynamicLayout will re-calculate 224 text = text.append("\nd"); 225 expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd, 226 spacingMultiplier); 227 assertLineSpecs(expected, dynamicLayout); 228 229 // insert a next line and a char as the new second line 230 text = text.insert(TextUtils.indexOf(text, '\n') + 1, "a1\n"); 231 expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd, 232 spacingMultiplier); 233 assertLineSpecs(expected, dynamicLayout); 234 } 235 236 @Test 237 public void testLineSpacing_textEndingWithNextLine() { 238 final SpannableStringBuilder text = new SpannableStringBuilder("a\n"); 239 final float spacingMultiplier = 2f; 240 final float spacingAdd = 4f; 241 final int width = 1000; 242 final TextPaint textPaint = new TextPaint(); 243 // create the DynamicLayout 244 final DynamicLayout dynamicLayout = new DynamicLayout(text, 245 textPaint, 246 width, 247 ALIGN_NORMAL, 248 spacingMultiplier, 249 spacingAdd, 250 false /*includepad*/); 251 252 // create a StaticLayout with same text, this will define the expectations 253 final Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd, 254 spacingMultiplier); 255 assertLineSpecs(expected, dynamicLayout); 256 } 257 258 private Layout createStaticLayout(CharSequence text, TextPaint textPaint, int width, 259 float spacingAdd, float spacingMultiplier) { 260 return StaticLayout.Builder.obtain(text, 0, 261 text.length(), textPaint, width) 262 .setAlignment(ALIGN_NORMAL) 263 .setIncludePad(false) 264 .setLineSpacing(spacingAdd, spacingMultiplier) 265 .build(); 266 } 267 268 private void assertLineSpecs(Layout expected, DynamicLayout actual) { 269 final int lineCount = expected.getLineCount(); 270 assertTrue(lineCount > 1); 271 assertEquals(lineCount, actual.getLineCount()); 272 273 for (int i = 0; i < lineCount; i++) { 274 assertEquals(expected.getLineTop(i), actual.getLineTop(i)); 275 assertEquals(expected.getLineDescent(i), actual.getLineDescent(i)); 276 assertEquals(expected.getLineBaseline(i), actual.getLineBaseline(i)); 277 assertEquals(expected.getLineBottom(i), actual.getLineBottom(i)); 278 } 279 } 280 281 @Test 282 public void testLineSpacing_notAffectedByPreviousEllipsization() { 283 // Create an ellipsized DynamicLayout, but throw it away. 284 final String ellipsizedText = "Some arbitrary relatively long text"; 285 final DynamicLayout ellipsizedLayout = new DynamicLayout( 286 ellipsizedText, 287 ellipsizedText, 288 mDefaultPaint, 289 1 << 20 /* width */, 290 DEFAULT_ALIGN, 291 SPACING_MULT_NO_SCALE, 292 SPACING_ADD_NO_SCALE, 293 true /* include pad */, 294 TextUtils.TruncateAt.END, 295 2 * (int) mDefaultPaint.getTextSize() /* ellipsizedWidth */); 296 297 // Now try to measure linespacing in a non-ellipsized DynamicLayout. 298 final String text = "a\nb\nc"; 299 final float spacingMultiplier = 2f; 300 final float spacingAdd = 4f; 301 final int width = 1000; 302 final TextPaint textPaint = new TextPaint(); 303 // create the DynamicLayout 304 final DynamicLayout dynamicLayout = new DynamicLayout(text, 305 textPaint, 306 width, 307 ALIGN_NORMAL, 308 spacingMultiplier, 309 spacingAdd, 310 false /*includepad*/); 311 312 // create a StaticLayout with same text, this will define the expectations 313 Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd, 314 spacingMultiplier); 315 assertLineSpecs(expected, dynamicLayout); 316 } 317 318 @Test 319 public void testBuilder_obtain() { 320 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE, 321 mDefaultPaint, DEFAULT_OUTER_WIDTH); 322 final DynamicLayout layout = builder.build(); 323 // Check values passed to obtain(). 324 assertEquals(MULTLINE_CHAR_SEQUENCE, layout.getText()); 325 assertEquals(mDefaultPaint, layout.getPaint()); 326 assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth()); 327 // Check default values. 328 assertEquals(Layout.Alignment.ALIGN_NORMAL, layout.getAlignment()); 329 assertEquals(0.0f, layout.getSpacingAdd(), 0.0f); 330 assertEquals(1.0f, layout.getSpacingMultiplier(), 0.0f); 331 assertEquals(DEFAULT_OUTER_WIDTH, layout.getEllipsizedWidth()); 332 } 333 334 @Test(expected = NullPointerException.class) 335 public void testBuilder_obtainWithNullText() { 336 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(null, mDefaultPaint, 0); 337 final DynamicLayout layout = builder.build(); 338 } 339 340 @Test(expected = NullPointerException.class) 341 public void testBuilder_obtainWithNullPaint() { 342 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE, 343 null, 0); 344 final DynamicLayout layout = builder.build(); 345 } 346 347 @Test 348 public void testBuilder_setDisplayTest() { 349 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE, 350 mDefaultPaint, DEFAULT_OUTER_WIDTH); 351 builder.setDisplayText(SINGLELINE_CHAR_SEQUENCE); 352 final DynamicLayout layout = builder.build(); 353 assertEquals(SINGLELINE_CHAR_SEQUENCE, layout.getText()); 354 } 355 356 @Test 357 public void testBuilder_setAlignment() { 358 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE, 359 mDefaultPaint, DEFAULT_OUTER_WIDTH); 360 builder.setAlignment(DEFAULT_ALIGN); 361 final DynamicLayout layout = builder.build(); 362 assertEquals(DEFAULT_ALIGN, layout.getAlignment()); 363 } 364 365 @Test 366 public void testBuilder_setLineSpacing() { 367 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE, 368 mDefaultPaint, DEFAULT_OUTER_WIDTH); 369 builder.setLineSpacing(1.0f, 2.0f); 370 final DynamicLayout layout = builder.build(); 371 assertEquals(1.0f, layout.getSpacingAdd(), 0.0f); 372 assertEquals(2.0f, layout.getSpacingMultiplier(), 0.0f); 373 } 374 375 @Test 376 public void testBuilder_ellipsization() { 377 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE, 378 mDefaultPaint, DEFAULT_OUTER_WIDTH); 379 builder.setEllipsize(TextUtils.TruncateAt.END) 380 .setEllipsizedWidth(ELLIPSIZE_WIDTH); 381 final DynamicLayout layout = builder.build(); 382 assertEquals(ELLIPSIZE_WIDTH, layout.getEllipsizedWidth()); 383 assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth()); 384 for (int i = 0; i < TEXT.length; i++) { 385 if (i == TEXT.length - 1) { // last line 386 assertTrue(layout.getEllipsisCount(i) > 0); 387 } else { 388 assertEquals(0, layout.getEllipsisCount(i)); 389 } 390 } 391 } 392 393 @Test 394 public void testBuilder_otherSetters() { 395 // Setter methods that cannot be directly tested. 396 // setBreakStrategy, setHyphenationFrequency, setIncludePad, and setJustificationMode. 397 final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE, 398 mDefaultPaint, DEFAULT_OUTER_WIDTH); 399 builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) 400 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL) 401 .setIncludePad(true) 402 .setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD); 403 final DynamicLayout layout = builder.build(); 404 assertNotNull(layout); 405 } 406 407 @Test 408 public void testReflow_afterSpanChangedShouldNotThrowException() { 409 final SpannableStringBuilder builder = new SpannableStringBuilder("crash crash crash!!"); 410 411 final TypefaceSpan span = mock(TypefaceSpan.class); 412 builder.setSpan(span, 1, 4, SPAN_EXCLUSIVE_EXCLUSIVE); 413 414 final DynamicLayout layout = DynamicLayout.Builder.obtain(builder, 415 new TextPaint(), Integer.MAX_VALUE).build(); 416 try { 417 builder.insert(1, "Hello there\n\n"); 418 } catch (Throwable e) { 419 throw new RuntimeException("Inserting text into DynamicLayout should not crash", e); 420 } 421 } 422 423 } 424