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