1 /* 2 * Copyright (C) 2017 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.TextDirectionHeuristics.LTR; 20 import static android.text.TextDirectionHeuristics.RTL; 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.assertNotSame; 26 import static org.junit.Assert.assertTrue; 27 import static org.junit.Assert.fail; 28 29 import android.content.Context; 30 import android.graphics.Bitmap; 31 import android.graphics.Canvas; 32 import android.graphics.Color; 33 import android.graphics.Rect; 34 import android.graphics.Typeface; 35 import android.text.Layout; 36 import android.text.PrecomputedText; 37 import android.text.PrecomputedText.Params; 38 import android.text.Spannable; 39 import android.text.SpannableStringBuilder; 40 import android.text.Spanned; 41 import android.text.TextDirectionHeuristics; 42 import android.text.TextPaint; 43 import android.text.style.BackgroundColorSpan; 44 import android.text.style.LocaleSpan; 45 import android.text.style.TextAppearanceSpan; 46 import android.text.style.TypefaceSpan; 47 48 import androidx.annotation.IntRange; 49 import androidx.annotation.NonNull; 50 import androidx.test.InstrumentationRegistry; 51 import androidx.test.filters.SmallTest; 52 import androidx.test.runner.AndroidJUnit4; 53 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 57 import java.util.Locale; 58 59 @SmallTest 60 @RunWith(AndroidJUnit4.class) 61 public class PrecomputedTextTest { 62 63 private static final CharSequence NULL_CHAR_SEQUENCE = null; 64 private static final String STRING = "Hello, World!"; 65 private static final String MULTIPARA_STRING = "Hello,\nWorld!"; 66 67 private static final int SPAN_START = 3; 68 private static final int SPAN_END = 7; 69 private static final LocaleSpan SPAN = new LocaleSpan(Locale.US); 70 private static final Spanned SPANNED; 71 static { 72 final SpannableStringBuilder ssb = new SpannableStringBuilder(STRING); ssb.setSpan(SPAN, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)73 ssb.setSpan(SPAN, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 74 SPANNED = ssb; 75 } 76 77 private static final TextPaint PAINT = new TextPaint(); 78 79 @Test testParams_create()80 public void testParams_create() { 81 assertNotNull(new Params.Builder(PAINT).build()); 82 assertNotNull(new Params.Builder(PAINT) 83 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE).build()); 84 assertNotNull(new Params.Builder(PAINT) 85 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) 86 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL).build()); 87 assertNotNull(new Params.Builder(PAINT) 88 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) 89 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) 90 .setTextDirection(LTR).build()); 91 } 92 93 @Test testParams_SetGet()94 public void testParams_SetGet() { 95 assertEquals(Layout.BREAK_STRATEGY_SIMPLE, new Params.Builder(PAINT) 96 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE).build().getBreakStrategy()); 97 assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, new Params.Builder(PAINT) 98 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE).build() 99 .getHyphenationFrequency()); 100 assertEquals(RTL, new Params.Builder(PAINT).setTextDirection(RTL).build() 101 .getTextDirection()); 102 } 103 104 @Test testParams_GetDefaultValues()105 public void testParams_GetDefaultValues() { 106 assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY, 107 new Params.Builder(PAINT).build().getBreakStrategy()); 108 assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, 109 new Params.Builder(PAINT).build().getHyphenationFrequency()); 110 assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, 111 new Params.Builder(PAINT).build().getTextDirection()); 112 } 113 114 @Test testParams_equals()115 public void testParams_equals() { 116 final Params base = new Params.Builder(PAINT) 117 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) 118 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) 119 .setTextDirection(LTR).build(); 120 121 assertFalse(base.equals(null)); 122 assertTrue(base.equals(base)); 123 assertFalse(base.equals(new Object())); 124 125 Params other = new Params.Builder(PAINT) 126 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) 127 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) 128 .setTextDirection(LTR).build(); 129 assertTrue(base.equals(other)); 130 assertTrue(other.equals(base)); 131 assertEquals(base.hashCode(), other.hashCode()); 132 133 other = new Params.Builder(PAINT) 134 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) 135 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) 136 .setTextDirection(LTR).build(); 137 assertFalse(base.equals(other)); 138 assertFalse(other.equals(base)); 139 140 other = new Params.Builder(PAINT) 141 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) 142 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) 143 .setTextDirection(LTR).build(); 144 assertFalse(base.equals(other)); 145 assertFalse(other.equals(base)); 146 147 148 other = new Params.Builder(PAINT) 149 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) 150 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) 151 .setTextDirection(RTL).build(); 152 assertFalse(base.equals(other)); 153 assertFalse(other.equals(base)); 154 155 156 TextPaint anotherPaint = new TextPaint(PAINT); 157 anotherPaint.setTextSize(PAINT.getTextSize() * 2.0f); 158 other = new Params.Builder(anotherPaint) 159 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) 160 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) 161 .setTextDirection(LTR).build(); 162 assertFalse(base.equals(other)); 163 assertFalse(other.equals(base)); 164 165 } 166 167 @Test testCreate_withNull()168 public void testCreate_withNull() { 169 final Params param = new Params.Builder(PAINT).build(); 170 try { 171 PrecomputedText.create(NULL_CHAR_SEQUENCE, param); 172 fail(); 173 } catch (NullPointerException e) { 174 // pass 175 } 176 try { 177 PrecomputedText.create(STRING, null); 178 fail(); 179 } catch (NullPointerException e) { 180 // pass 181 } 182 } 183 184 @Test testCreateForDifferentDirection()185 public void testCreateForDifferentDirection() { 186 final Params param = new Params.Builder(PAINT).setTextDirection(LTR).build(); 187 final PrecomputedText textWithLTR = PrecomputedText.create(STRING, param); 188 final Params newParam = new Params.Builder(PAINT).setTextDirection(RTL).build(); 189 final PrecomputedText textWithRTL = PrecomputedText.create(textWithLTR, newParam); 190 assertNotNull(textWithRTL); 191 assertNotSame(textWithLTR, textWithRTL); 192 assertEquals(textWithLTR.toString(), textWithRTL.toString()); 193 } 194 195 @Test testCharSequenceInteface()196 public void testCharSequenceInteface() { 197 final Params param = new Params.Builder(PAINT).build(); 198 final CharSequence s = PrecomputedText.create(STRING, param); 199 assertEquals(STRING.length(), s.length()); 200 assertEquals('H', s.charAt(0)); 201 assertEquals('e', s.charAt(1)); 202 assertEquals('l', s.charAt(2)); 203 assertEquals('l', s.charAt(3)); 204 assertEquals('o', s.charAt(4)); 205 assertEquals(',', s.charAt(5)); 206 assertEquals("Hello, World!", s.toString()); 207 208 final CharSequence s3 = s.subSequence(0, 3); 209 assertEquals(3, s3.length()); 210 assertEquals('H', s3.charAt(0)); 211 assertEquals('e', s3.charAt(1)); 212 assertEquals('l', s3.charAt(2)); 213 214 } 215 216 @Test testSpannedInterface_Spanned()217 public void testSpannedInterface_Spanned() { 218 final Params param = new Params.Builder(PAINT).build(); 219 final Spanned s = PrecomputedText.create(SPANNED, param); 220 final LocaleSpan[] spans = s.getSpans(0, s.length(), LocaleSpan.class); 221 assertNotNull(spans); 222 assertEquals(1, spans.length); 223 assertEquals(SPAN, spans[0]); 224 225 assertEquals(SPAN_START, s.getSpanStart(SPAN)); 226 assertEquals(SPAN_END, s.getSpanEnd(SPAN)); 227 assertTrue((s.getSpanFlags(SPAN) & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) != 0); 228 229 assertEquals(SPAN_START, s.nextSpanTransition(0, s.length(), LocaleSpan.class)); 230 assertEquals(SPAN_END, s.nextSpanTransition(SPAN_START, s.length(), LocaleSpan.class)); 231 } 232 233 @Test testSpannedInterface_Spannable()234 public void testSpannedInterface_Spannable() { 235 final BackgroundColorSpan span = new BackgroundColorSpan(Color.RED); 236 final Params param = new Params.Builder(PAINT).build(); 237 final Spannable s = PrecomputedText.create(STRING, param); 238 assertEquals(0, s.getSpans(0, s.length(), BackgroundColorSpan.class).length); 239 240 s.setSpan(span, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 241 242 final BackgroundColorSpan[] spans = s.getSpans(0, s.length(), BackgroundColorSpan.class); 243 assertEquals(SPAN_START, s.getSpanStart(span)); 244 assertEquals(SPAN_END, s.getSpanEnd(span)); 245 assertTrue((s.getSpanFlags(span) & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) != 0); 246 247 assertEquals(SPAN_START, s.nextSpanTransition(0, s.length(), BackgroundColorSpan.class)); 248 assertEquals(SPAN_END, 249 s.nextSpanTransition(SPAN_START, s.length(), BackgroundColorSpan.class)); 250 251 s.removeSpan(span); 252 assertEquals(0, s.getSpans(0, s.length(), BackgroundColorSpan.class).length); 253 } 254 255 @Test(expected = IllegalArgumentException.class) testSpannedInterface_Spannable_setSpan_MetricsAffectingSpan()256 public void testSpannedInterface_Spannable_setSpan_MetricsAffectingSpan() { 257 final Params param = new Params.Builder(PAINT).build(); 258 final Spannable s = PrecomputedText.create(SPANNED, param); 259 s.setSpan(SPAN, SPAN_START, SPAN_END, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 260 } 261 262 @Test(expected = IllegalArgumentException.class) testSpannedInterface_Spannable_removeSpan_MetricsAffectingSpan()263 public void testSpannedInterface_Spannable_removeSpan_MetricsAffectingSpan() { 264 final Params param = new Params.Builder(PAINT).build(); 265 final Spannable s = PrecomputedText.create(SPANNED, param); 266 s.removeSpan(SPAN); 267 } 268 269 @Test testSpannedInterface_String()270 public void testSpannedInterface_String() { 271 final Params param = new Params.Builder(PAINT).build(); 272 final Spanned s = PrecomputedText.create(STRING, param); 273 LocaleSpan[] spans = s.getSpans(0, s.length(), LocaleSpan.class); 274 assertNotNull(spans); 275 assertEquals(0, spans.length); 276 277 assertEquals(-1, s.getSpanStart(SPAN)); 278 assertEquals(-1, s.getSpanEnd(SPAN)); 279 assertEquals(0, s.getSpanFlags(SPAN)); 280 281 assertEquals(s.length(), s.nextSpanTransition(0, s.length(), LocaleSpan.class)); 282 } 283 284 @Test testGetParagraphCount()285 public void testGetParagraphCount() { 286 final Params param = new Params.Builder(PAINT).build(); 287 final PrecomputedText pm = PrecomputedText.create(STRING, param); 288 assertEquals(1, pm.getParagraphCount()); 289 assertEquals(0, pm.getParagraphStart(0)); 290 assertEquals(STRING.length(), pm.getParagraphEnd(0)); 291 292 final PrecomputedText pm1 = PrecomputedText.create(MULTIPARA_STRING, param); 293 assertEquals(2, pm1.getParagraphCount()); 294 assertEquals(0, pm1.getParagraphStart(0)); 295 assertEquals(7, pm1.getParagraphEnd(0)); 296 assertEquals(7, pm1.getParagraphStart(1)); 297 assertEquals(pm1.length(), pm1.getParagraphEnd(1)); 298 } 299 300 @Test testGetWidth()301 public void testGetWidth() { 302 final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 303 304 // The test font has following coverage and width. 305 // U+0020: 10em 306 // U+002E (.): 10em 307 // U+0043 (C): 100em 308 // U+0049 (I): 1em 309 // U+004C (L): 50em 310 // U+0056 (V): 5em 311 // U+0058 (X): 10em 312 // U+005F (_): 0em 313 // U+FFFD (invalid surrogate will be replaced to this): 7em 314 // U+10331 (\uD800\uDF31): 10em 315 final Typeface tf = new Typeface.Builder(context.getAssets(), 316 "fonts/StaticLayoutLineBreakingTestFont.ttf").build(); 317 final TextPaint paint = new TextPaint(); 318 paint.setTypeface(tf); 319 paint.setTextSize(1); // Make 1em = 1px 320 321 final Params param = new Params.Builder(paint).build(); 322 assertEquals(0.0f, PrecomputedText.create("", param).getWidth(0, 0), 0.0f); 323 324 assertEquals(0.0f, PrecomputedText.create("I", param).getWidth(0, 0), 0.0f); 325 assertEquals(0.0f, PrecomputedText.create("I", param).getWidth(1, 1), 0.0f); 326 assertEquals(1.0f, PrecomputedText.create("I", param).getWidth(0, 1), 0.0f); 327 328 assertEquals(0.0f, PrecomputedText.create("V", param).getWidth(0, 0), 0.0f); 329 assertEquals(0.0f, PrecomputedText.create("V", param).getWidth(1, 1), 0.0f); 330 assertEquals(5.0f, PrecomputedText.create("V", param).getWidth(0, 1), 0.0f); 331 332 assertEquals(0.0f, PrecomputedText.create("IV", param).getWidth(0, 0), 0.0f); 333 assertEquals(0.0f, PrecomputedText.create("IV", param).getWidth(1, 1), 0.0f); 334 assertEquals(0.0f, PrecomputedText.create("IV", param).getWidth(2, 2), 0.0f); 335 assertEquals(1.0f, PrecomputedText.create("IV", param).getWidth(0, 1), 0.0f); 336 assertEquals(5.0f, PrecomputedText.create("IV", param).getWidth(1, 2), 0.0f); 337 assertEquals(6.0f, PrecomputedText.create("IV", param).getWidth(0, 2), 0.0f); 338 339 assertEquals(0.0f, PrecomputedText.create("I\nV", param).getWidth(0, 0), 0.0f); 340 assertEquals(0.0f, PrecomputedText.create("I\nV", param).getWidth(1, 1), 0.0f); 341 assertEquals(0.0f, PrecomputedText.create("I\nV", param).getWidth(2, 2), 0.0f); 342 assertEquals(0.0f, PrecomputedText.create("I\nV", param).getWidth(3, 3), 0.0f); 343 assertEquals(1.0f, PrecomputedText.create("I\nV", param).getWidth(0, 1), 0.0f); 344 assertEquals(1.0f, PrecomputedText.create("I\nV", param).getWidth(0, 2), 0.0f); 345 assertEquals(5.0f, PrecomputedText.create("I\nV", param).getWidth(2, 3), 0.0f); 346 } 347 348 @Test testGetWidth_multiStyle()349 public void testGetWidth_multiStyle() { 350 final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 351 final SpannableStringBuilder ssb = new SpannableStringBuilder("II"); 352 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 1 /* text size */, 353 null /* color */, null /* link color */), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 354 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */, 355 null /* color */, null /* link color */), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 356 357 final Typeface tf = new Typeface.Builder(context.getAssets(), 358 "fonts/StaticLayoutLineBreakingTestFont.ttf").build(); 359 final TextPaint paint = new TextPaint(); 360 paint.setTypeface(tf); 361 paint.setTextSize(1); // Make 1em = 1px 362 363 final Params param = new Params.Builder(paint).build(); 364 365 assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(0, 0), 0.0f); 366 assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(1, 1), 0.0f); 367 assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(2, 2), 0.0f); 368 369 assertEquals(1.0f, PrecomputedText.create(ssb, param).getWidth(0, 1), 0.0f); 370 assertEquals(5.0f, PrecomputedText.create(ssb, param).getWidth(1, 2), 0.0f); 371 372 assertEquals(6.0f, PrecomputedText.create(ssb, param).getWidth(0, 2), 0.0f); 373 } 374 375 @Test testGetWidth_multiStyle2()376 public void testGetWidth_multiStyle2() { 377 final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 378 final SpannableStringBuilder ssb = new SpannableStringBuilder("IVI"); 379 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 1 /* text size */, 380 null /* color */, null /* link color */), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 381 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */, 382 null /* color */, null /* link color */), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 383 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */, 384 null /* color */, null /* link color */), 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 385 386 final Typeface tf = new Typeface.Builder(context.getAssets(), 387 "fonts/StaticLayoutLineBreakingTestFont.ttf").build(); 388 final TextPaint paint = new TextPaint(); 389 paint.setTypeface(tf); 390 paint.setTextSize(1); // Make 1em = 1px 391 392 final Params param = new Params.Builder(paint).build(); 393 394 assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(0, 0), 0.0f); 395 assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(1, 1), 0.0f); 396 assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(2, 2), 0.0f); 397 assertEquals(0.0f, PrecomputedText.create(ssb, param).getWidth(3, 3), 0.0f); 398 399 assertEquals(1.0f, PrecomputedText.create(ssb, param).getWidth(0, 1), 0.0f); 400 assertEquals(25.0f, PrecomputedText.create(ssb, param).getWidth(1, 2), 0.0f); 401 assertEquals(5.0f, PrecomputedText.create(ssb, param).getWidth(2, 3), 0.0f); 402 403 assertEquals(26.0f, PrecomputedText.create(ssb, param).getWidth(0, 2), 0.0f); 404 assertEquals(30.0f, PrecomputedText.create(ssb, param).getWidth(1, 3), 0.0f); 405 assertEquals(31.0f, PrecomputedText.create(ssb, param).getWidth(0, 3), 0.0f); 406 } 407 408 @Test(expected = IllegalArgumentException.class) testGetWidth_negative_start_offset()409 public void testGetWidth_negative_start_offset() { 410 final Params param = new Params.Builder(PAINT).build(); 411 PrecomputedText.create("a", param).getWidth(-1, 0); 412 } 413 414 @Test(expected = IllegalArgumentException.class) testGetWidth_negative_end_offset()415 public void testGetWidth_negative_end_offset() { 416 final Params param = new Params.Builder(PAINT).build(); 417 PrecomputedText.create("a", param).getWidth(0, -1); 418 } 419 420 @Test(expected = IllegalArgumentException.class) testGetWidth_index_out_of_bounds_start_offset()421 public void testGetWidth_index_out_of_bounds_start_offset() { 422 final Params param = new Params.Builder(PAINT).build(); 423 PrecomputedText.create("a", param).getWidth(2, 2); 424 } 425 426 @Test(expected = IllegalArgumentException.class) testGetWidth_index_out_of_bounds_end_offset()427 public void testGetWidth_index_out_of_bounds_end_offset() { 428 final Params param = new Params.Builder(PAINT).build(); 429 PrecomputedText.create("a", param).getWidth(0, 2); 430 } 431 432 @Test(expected = IllegalArgumentException.class) testGetWidth_reverse_offset()433 public void testGetWidth_reverse_offset() { 434 final Params param = new Params.Builder(PAINT).build(); 435 PrecomputedText.create("a", param).getWidth(1, 0); 436 } 437 438 @Test(expected = IllegalArgumentException.class) testGetWidth_across_paragraph_boundary()439 public void testGetWidth_across_paragraph_boundary() { 440 final Params param = new Params.Builder(PAINT).build(); 441 PrecomputedText.create("a\nb", param).getWidth(0, 3); 442 } 443 444 @Test testGetBounds()445 public void testGetBounds() { 446 final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 447 448 // The test font has following coverage and width. 449 // U+0020: 10em 450 // U+002E (.): 10em 451 // U+0043 (C): 100em 452 // U+0049 (I): 1em 453 // U+004C (L): 50em 454 // U+0056 (V): 5em 455 // U+0058 (X): 10em 456 // U+005F (_): 0em 457 // U+FFFD (invalid surrogate will be replaced to this): 7em 458 // U+10331 (\uD800\uDF31): 10em 459 final Typeface tf = new Typeface.Builder(context.getAssets(), 460 "fonts/StaticLayoutLineBreakingTestFont.ttf").build(); 461 final TextPaint paint = new TextPaint(); 462 paint.setTypeface(tf); 463 paint.setTextSize(1); // Make 1em = 1px 464 465 final Params param = new Params.Builder(paint).build(); 466 final Rect rect = new Rect(); 467 468 rect.set(0, 0, 0, 0); 469 PrecomputedText.create("", param).getBounds(0, 0, rect); 470 assertEquals(new Rect(0, 0, 0, 0), rect); 471 472 rect.set(0, 0, 0, 0); 473 PrecomputedText.create("I", param).getBounds(0, 1, rect); 474 assertEquals(new Rect(0, -1, 1, 0), rect); 475 476 rect.set(0, 0, 0, 0); 477 PrecomputedText.create("I", param).getBounds(1, 1, rect); 478 assertEquals(new Rect(0, 0, 0, 0), rect); 479 480 rect.set(0, 0, 0, 0); 481 PrecomputedText.create("IV", param).getBounds(0, 0, rect); 482 assertEquals(new Rect(0, 0, 0, 0), rect); 483 484 rect.set(0, 0, 0, 0); 485 PrecomputedText.create("IV", param).getBounds(0, 0, rect); 486 assertEquals(new Rect(0, 0, 0, 0), rect); 487 488 rect.set(0, 0, 0, 0); 489 PrecomputedText.create("IV", param).getBounds(1, 1, rect); 490 assertEquals(new Rect(0, 0, 0, 0), rect); 491 492 rect.set(0, 0, 0, 0); 493 PrecomputedText.create("IV", param).getBounds(2, 2, rect); 494 assertEquals(new Rect(0, 0, 0, 0), rect); 495 496 rect.set(0, 0, 0, 0); 497 PrecomputedText.create("IV", param).getBounds(0, 1, rect); 498 assertEquals(new Rect(0, -1, 1, 0), rect); 499 500 rect.set(0, 0, 0, 0); 501 PrecomputedText.create("IV", param).getBounds(1, 2, rect); 502 assertEquals(new Rect(0, -5, 5, 0), rect); 503 504 rect.set(0, 0, 0, 0); 505 PrecomputedText.create("IV", param).getBounds(0, 2, rect); 506 assertEquals(new Rect(0, -5, 6, 0), rect); 507 } 508 509 @Test testGetBounds_multiStyle()510 public void testGetBounds_multiStyle() { 511 final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 512 final SpannableStringBuilder ssb = new SpannableStringBuilder("II"); 513 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 1 /* text size */, 514 null /* color */, null /* link color */), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 515 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */, 516 null /* color */, null /* link color */), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 517 518 final Typeface tf = new Typeface.Builder(context.getAssets(), 519 "fonts/StaticLayoutLineBreakingTestFont.ttf").build(); 520 final TextPaint paint = new TextPaint(); 521 paint.setTypeface(tf); 522 paint.setTextSize(1); // Make 1em = 1px 523 524 final Params param = new Params.Builder(paint).build(); 525 final Rect rect = new Rect(); 526 527 rect.set(0, 0, 0, 0); 528 PrecomputedText.create(ssb, param).getBounds(0, 0, rect); 529 assertEquals(new Rect(0, 0, 0, 0), rect); 530 531 rect.set(0, 0, 0, 0); 532 PrecomputedText.create(ssb, param).getBounds(0, 1, rect); 533 assertEquals(new Rect(0, -1, 1, 0), rect); 534 535 rect.set(0, 0, 0, 0); 536 PrecomputedText.create(ssb, param).getBounds(1, 2, rect); 537 assertEquals(new Rect(0, -5, 5, 0), rect); 538 539 rect.set(0, 0, 0, 0); 540 PrecomputedText.create(ssb, param).getBounds(0, 2, rect); 541 assertEquals(new Rect(0, -5, 6, 0), rect); 542 } 543 544 @Test testGetBounds_multiStyle2()545 public void testGetBounds_multiStyle2() { 546 final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 547 final SpannableStringBuilder ssb = new SpannableStringBuilder("IVI"); 548 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 1 /* text size */, 549 null /* color */, null /* link color */), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 550 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */, 551 null /* color */, null /* link color */), 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 552 ssb.setSpan(new TextAppearanceSpan(null /* family */, Typeface.NORMAL, 5 /* text size */, 553 null /* color */, null /* link color */), 2, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 554 555 final Typeface tf = new Typeface.Builder(context.getAssets(), 556 "fonts/StaticLayoutLineBreakingTestFont.ttf").build(); 557 final TextPaint paint = new TextPaint(); 558 paint.setTypeface(tf); 559 paint.setTextSize(1); // Make 1em = 1px 560 561 final Params param = new Params.Builder(paint).build(); 562 final Rect rect = new Rect(); 563 564 rect.set(0, 0, 0, 0); 565 PrecomputedText.create(ssb, param).getBounds(0, 0, rect); 566 assertEquals(new Rect(0, 0, 0, 0), rect); 567 568 rect.set(0, 0, 0, 0); 569 PrecomputedText.create(ssb, param).getBounds(0, 1, rect); 570 assertEquals(new Rect(0, -1, 1, 0), rect); 571 572 rect.set(0, 0, 0, 0); 573 PrecomputedText.create(ssb, param).getBounds(1, 2, rect); 574 assertEquals(new Rect(0, -25, 25, 0), rect); 575 576 rect.set(0, 0, 0, 0); 577 PrecomputedText.create(ssb, param).getBounds(2, 3, rect); 578 assertEquals(new Rect(0, -5, 5, 0), rect); 579 580 rect.set(0, 0, 0, 0); 581 PrecomputedText.create(ssb, param).getBounds(0, 2, rect); 582 assertEquals(new Rect(0, -25, 26, 0), rect); 583 584 rect.set(0, 0, 0, 0); 585 PrecomputedText.create(ssb, param).getBounds(1, 3, rect); 586 assertEquals(new Rect(0, -25, 30, 0), rect); 587 588 rect.set(0, 0, 0, 0); 589 PrecomputedText.create(ssb, param).getBounds(0, 3, rect); 590 assertEquals(new Rect(0, -25, 31, 0), rect); 591 } 592 593 @Test(expected = IllegalArgumentException.class) testGetBounds_negative_start_offset()594 public void testGetBounds_negative_start_offset() { 595 final Rect rect = new Rect(); 596 final Params param = new Params.Builder(PAINT).build(); 597 PrecomputedText.create("a", param).getBounds(-1, 0, rect); 598 } 599 600 @Test(expected = IllegalArgumentException.class) testGetBounds_negative_end_offset()601 public void testGetBounds_negative_end_offset() { 602 final Rect rect = new Rect(); 603 final Params param = new Params.Builder(PAINT).build(); 604 PrecomputedText.create("a", param).getBounds(0, -1, rect); 605 } 606 607 @Test(expected = IllegalArgumentException.class) testGetBounds_index_out_of_bounds_start_offset()608 public void testGetBounds_index_out_of_bounds_start_offset() { 609 final Rect rect = new Rect(); 610 final Params param = new Params.Builder(PAINT).build(); 611 PrecomputedText.create("a", param).getBounds(2, 2, rect); 612 } 613 614 @Test(expected = IllegalArgumentException.class) testGetBounds_index_out_of_bounds_end_offset()615 public void testGetBounds_index_out_of_bounds_end_offset() { 616 final Rect rect = new Rect(); 617 final Params param = new Params.Builder(PAINT).build(); 618 PrecomputedText.create("a", param).getBounds(0, 2, rect); 619 } 620 621 @Test(expected = IllegalArgumentException.class) testGetBounds_reverse_offset()622 public void testGetBounds_reverse_offset() { 623 final Rect rect = new Rect(); 624 final Params param = new Params.Builder(PAINT).build(); 625 PrecomputedText.create("a", param).getBounds(1, 0, rect); 626 } 627 628 @Test(expected = NullPointerException.class) testGetBounds_null_rect()629 public void testGetBounds_null_rect() { 630 final Params param = new Params.Builder(PAINT).build(); 631 PrecomputedText.create("a", param).getBounds(0, 1, null); 632 } 633 634 @Test(expected = IllegalArgumentException.class) testGetBounds_across_paragraph_boundary()635 public void testGetBounds_across_paragraph_boundary() { 636 final Rect rect = new Rect(); 637 final Params param = new Params.Builder(PAINT).build(); 638 PrecomputedText.create("a\nb", param).getBounds(0, 3, rect); 639 } 640 drawToBitmap(@onNull CharSequence cs, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @IntRange(from = 0) int ctxStart, @IntRange(from = 0) int ctxEnd, @NonNull TextPaint paint)641 private static Bitmap drawToBitmap(@NonNull CharSequence cs, 642 @IntRange(from = 0) int start, @IntRange(from = 0) int end, 643 @IntRange(from = 0) int ctxStart, @IntRange(from = 0) int ctxEnd, 644 @NonNull TextPaint paint) { 645 646 Rect rect = new Rect(); 647 paint.getTextBounds(cs.toString(), start, end, rect); 648 final Bitmap bmp = Bitmap.createBitmap(rect.width(), 649 rect.height(), Bitmap.Config.ARGB_8888); 650 final Canvas c = new Canvas(bmp); 651 c.save(); 652 c.translate(0, 0); 653 c.drawTextRun(cs, start, end, ctxStart, ctxEnd, 0, 0, false /* isRtl */, paint); 654 c.restore(); 655 return bmp; 656 } 657 assertSameOutput(@onNull CharSequence cs, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @IntRange(from = 0) int ctxStart, @IntRange(from = 0) int ctxEnd, @NonNull TextPaint paint)658 private static void assertSameOutput(@NonNull CharSequence cs, 659 @IntRange(from = 0) int start, @IntRange(from = 0) int end, 660 @IntRange(from = 0) int ctxStart, @IntRange(from = 0) int ctxEnd, 661 @NonNull TextPaint paint) { 662 final Params params = new Params.Builder(paint).build(); 663 final PrecomputedText pt = PrecomputedText.create(cs, params); 664 665 final Params rtlParams = new Params.Builder(paint) 666 .setTextDirection(TextDirectionHeuristics.RTL).build(); 667 final PrecomputedText rtlPt = PrecomputedText.create(cs, rtlParams); 668 // FIRSTSTRONG_LTR is the default direction. 669 final PrecomputedText ptFromRtl = PrecomputedText.create(rtlPt, 670 new Params.Builder(params).setTextDirection( 671 TextDirectionHeuristics.FIRSTSTRONG_LTR).build()); 672 673 final Bitmap originalDrawOutput = drawToBitmap(cs, start, end, ctxStart, ctxEnd, paint); 674 final Bitmap precomputedDrawOutput = drawToBitmap(pt, start, end, ctxStart, ctxEnd, paint); 675 final Bitmap precomputedFromDifferentDirectionDrawOutput = 676 drawToBitmap(pt, start, end, ctxStart, ctxEnd, paint); 677 assertTrue(originalDrawOutput.sameAs(precomputedDrawOutput)); 678 assertTrue(originalDrawOutput.sameAs(precomputedFromDifferentDirectionDrawOutput)); 679 } 680 681 @Test testDrawText()682 public void testDrawText() { 683 final TextPaint paint = new TextPaint(); 684 paint.setTextSize(32.0f); 685 686 final SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World"); 687 assertSameOutput(ssb, 0, ssb.length(), 0, ssb.length(), paint); 688 assertSameOutput(ssb, 3, ssb.length() - 3, 0, ssb.length(), paint); 689 assertSameOutput(ssb, 5, ssb.length() - 5, 2, ssb.length() - 2, paint); 690 } 691 692 @Test testDrawText_MultiStyle()693 public void testDrawText_MultiStyle() { 694 final TextPaint paint = new TextPaint(); 695 paint.setTextSize(32.0f); 696 697 final SpannableStringBuilder ssb = new SpannableStringBuilder("Hello, World"); 698 ssb.setSpan(new TypefaceSpan("serif"), 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 699 assertSameOutput(ssb, 0, ssb.length(), 0, ssb.length(), paint); 700 assertSameOutput(ssb, 3, ssb.length() - 3, 0, ssb.length(), paint); 701 assertSameOutput(ssb, 5, ssb.length() - 5, 2, ssb.length() - 2, paint); 702 } 703 704 @Test testDrawText_MultiParagraph()705 public void testDrawText_MultiParagraph() { 706 final TextPaint paint = new TextPaint(); 707 paint.setTextSize(32.0f); 708 709 final SpannableStringBuilder ssb = new SpannableStringBuilder( 710 "Hello, World\nHello, Android"); 711 712 // The first line 713 final int firstLineLen = "Hello, World\n".length(); 714 assertSameOutput(ssb, 0, firstLineLen, 0, firstLineLen, paint); 715 assertSameOutput(ssb, 3, firstLineLen - 3, 0, firstLineLen, paint); 716 assertSameOutput(ssb, 3, firstLineLen - 3, 2, firstLineLen - 2, paint); 717 718 // The second line. 719 assertSameOutput(ssb, firstLineLen, ssb.length(), firstLineLen, ssb.length(), paint); 720 assertSameOutput(ssb, firstLineLen + 3, ssb.length() - 3, 721 firstLineLen, ssb.length(), paint); 722 assertSameOutput(ssb, firstLineLen + 5, ssb.length() - 5, 723 firstLineLen + 2, ssb.length() - 2, paint); 724 725 // Across the paragraph 726 assertSameOutput(ssb, 0, ssb.length(), 0, ssb.length(), paint); 727 assertSameOutput(ssb, 3, firstLineLen - 3, 0, ssb.length(), paint); 728 assertSameOutput(ssb, 3, firstLineLen - 3, 2, ssb.length() - 2, paint); 729 } 730 } 731