1 /* 2 * Copyright (C) 2018 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.graphics.text.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertTrue; 23 24 import android.content.Context; 25 import android.content.res.AssetManager; 26 import android.graphics.Bitmap; 27 import android.graphics.Canvas; 28 import android.graphics.Paint; 29 import android.graphics.Rect; 30 import android.graphics.Typeface; 31 import android.graphics.text.MeasuredText; 32 import android.text.PrecomputedText; 33 import android.text.SpannableStringBuilder; 34 import android.text.TextPaint; 35 36 import androidx.test.InstrumentationRegistry; 37 import androidx.test.filters.SmallTest; 38 import androidx.test.runner.AndroidJUnit4; 39 40 import org.junit.BeforeClass; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 @SmallTest 45 @RunWith(AndroidJUnit4.class) 46 public class MeasuredTextTest { 47 private static Paint sPaint; 48 49 @BeforeClass 50 public static void classSetUp() { 51 sPaint = new Paint(); 52 Context context = InstrumentationRegistry.getTargetContext(); 53 AssetManager am = context.getAssets(); 54 Typeface tf = new Typeface.Builder(am, "fonts/layout/linebreak.ttf").build(); 55 sPaint.setTypeface(tf); 56 sPaint.setTextSize(10.0f); // Make 1em = 10px 57 } 58 59 @Test 60 public void testBuilder() { 61 String text = "Hello, World"; 62 new MeasuredText.Builder(text.toCharArray()) 63 .appendStyleRun(sPaint, text.length(), false /* isRtl */).build(); 64 } 65 66 @Test 67 public void testBuilder_FromExistingMeasuredText() { 68 String text = "Hello, World"; 69 final MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) 70 .appendStyleRun(sPaint, text.length(), false /* isRtl */).build(); 71 assertNotNull(new MeasuredText.Builder(mt) 72 .appendStyleRun(sPaint, text.length(), true /* isRtl */).build()); 73 } 74 75 @Test(expected = IllegalArgumentException.class) 76 public void testBuilder_FromExistingMeasuredText_differentLayoutParam() { 77 String text = "Hello, World"; 78 final MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) 79 .setComputeLayout(false) 80 .appendStyleRun(sPaint, text.length(), false /* isRtl */).build(); 81 new MeasuredText.Builder(mt) 82 .appendStyleRun(sPaint, text.length(), true /* isRtl */).build(); 83 } 84 85 @Test(expected = IllegalArgumentException.class) 86 public void testBuilder_FromExistingMeasuredText_differentHyphenationParam() { 87 String text = "Hello, World"; 88 final MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) 89 .setComputeHyphenation(false) 90 .appendStyleRun(sPaint, text.length(), false /* isRtl */).build(); 91 new MeasuredText.Builder(mt) 92 .setComputeHyphenation(true) 93 .appendStyleRun(sPaint, text.length(), true /* isRtl */).build(); 94 } 95 96 @Test(expected = NullPointerException.class) 97 public void testBuilder_NullText() { 98 new MeasuredText.Builder((char[]) null); 99 } 100 101 @Test(expected = NullPointerException.class) 102 public void testBuilder_NullMeasuredText() { 103 new MeasuredText.Builder((MeasuredText) null); 104 } 105 106 @Test(expected = NullPointerException.class) 107 public void testBuilder_NullPaint() { 108 String text = "Hello, World"; 109 new MeasuredText.Builder(text.toCharArray()).appendStyleRun(null, text.length(), false); 110 } 111 112 @Test 113 public void testGetWidth() { 114 String text = "Hello, World"; 115 MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) 116 .appendStyleRun(sPaint, text.length(), false /* isRtl */).build(); 117 assertEquals(0.0f, mt.getWidth(0, 0), 0.0f); 118 assertEquals(10.0f, mt.getWidth(0, 1), 0.0f); 119 assertEquals(20.0f, mt.getWidth(0, 2), 0.0f); 120 assertEquals(10.0f, mt.getWidth(1, 2), 0.0f); 121 assertEquals(20.0f, mt.getWidth(1, 3), 0.0f); 122 } 123 124 @Test(expected = IllegalArgumentException.class) 125 public void testGetWidth_StartSmallerThanZero() { 126 String text = "Hello, World"; 127 new MeasuredText.Builder(text.toCharArray()) 128 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 129 .build() 130 .getWidth(-1, 0); 131 } 132 133 @Test(expected = IllegalArgumentException.class) 134 public void testGetWidth_StartLargerThanLength() { 135 String text = "Hello, World"; 136 new MeasuredText.Builder(text.toCharArray()) 137 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 138 .build() 139 .getWidth(text.length() + 1, 0); 140 } 141 142 @Test(expected = IllegalArgumentException.class) 143 public void testGetWidth_EndSmallerThanZero() { 144 String text = "Hello, World"; 145 new MeasuredText.Builder(text.toCharArray()) 146 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 147 .build() 148 .getWidth(0, -1); 149 } 150 151 @Test(expected = IllegalArgumentException.class) 152 public void testGetWidth_EndLargerThanLength() { 153 String text = "Hello, World"; 154 new MeasuredText.Builder(text.toCharArray()) 155 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 156 .build() 157 .getWidth(0, text.length() + 1); 158 } 159 160 @Test(expected = IllegalArgumentException.class) 161 public void testGetWidth_StartLargerThanEnd() { 162 String text = "Hello, World"; 163 new MeasuredText.Builder(text.toCharArray()) 164 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 165 .build() 166 .getWidth(1, 0); 167 } 168 169 @Test 170 public void testGetBounds() { 171 String text = "Hello, World"; 172 MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) 173 .appendStyleRun(sPaint, text.length(), false /* isRtl */).build(); 174 final Rect emptyRect = new Rect(0, 0, 0, 0); 175 final Rect singleCharRect = new Rect(0, -10, 10, 0); 176 final Rect twoCharRect = new Rect(0, -10, 20, 0); 177 Rect out = new Rect(); 178 mt.getBounds(0, 0, out); 179 assertEquals(emptyRect, out); 180 mt.getBounds(0, 1, out); 181 assertEquals(singleCharRect, out); 182 mt.getBounds(0, 2, out); 183 assertEquals(twoCharRect, out); 184 mt.getBounds(1, 2, out); 185 assertEquals(singleCharRect, out); 186 mt.getBounds(1, 3, out); 187 assertEquals(twoCharRect, out); 188 } 189 190 @Test(expected = IllegalArgumentException.class) 191 public void testGetBounds_StartSmallerThanZero() { 192 String text = "Hello, World"; 193 Rect rect = new Rect(); 194 new MeasuredText.Builder(text.toCharArray()) 195 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 196 .build() 197 .getBounds(-1, 0, rect); 198 } 199 200 @Test(expected = IllegalArgumentException.class) 201 public void testGetBounds_StartLargerThanLength() { 202 String text = "Hello, World"; 203 Rect rect = new Rect(); 204 new MeasuredText.Builder(text.toCharArray()) 205 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 206 .build() 207 .getBounds(text.length() + 1, 0, rect); 208 } 209 210 @Test(expected = IllegalArgumentException.class) 211 public void testGetBounds_EndSmallerThanZero() { 212 String text = "Hello, World"; 213 Rect rect = new Rect(); 214 new MeasuredText.Builder(text.toCharArray()) 215 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 216 .build() 217 .getBounds(0, -1, rect); 218 } 219 220 @Test(expected = IllegalArgumentException.class) 221 public void testGetBounds_EndLargerThanLength() { 222 String text = "Hello, World"; 223 Rect rect = new Rect(); 224 new MeasuredText.Builder(text.toCharArray()) 225 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 226 .build() 227 .getBounds(0, text.length() + 1, rect); 228 } 229 230 @Test(expected = IllegalArgumentException.class) 231 public void testGetBounds_StartLargerThanEnd() { 232 String text = "Hello, World"; 233 Rect rect = new Rect(); 234 new MeasuredText.Builder(text.toCharArray()) 235 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 236 .build() 237 .getBounds(1, 0, rect); 238 } 239 240 @Test(expected = NullPointerException.class) 241 public void testGetBounds_NullRect() { 242 String text = "Hello, World"; 243 Rect rect = new Rect(); 244 new MeasuredText.Builder(text.toCharArray()) 245 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 246 .build() 247 .getBounds(0, 0, null); 248 } 249 250 @Test 251 public void testGetCharWidthAt() { 252 String text = "Hello, World"; 253 MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) 254 .appendStyleRun(sPaint, text.length(), false /* isRtl */).build(); 255 assertEquals(10.0f, mt.getCharWidthAt(0), 0.0f); 256 assertEquals(10.0f, mt.getCharWidthAt(1), 0.0f); 257 assertEquals(10.0f, mt.getCharWidthAt(2), 0.0f); 258 assertEquals(10.0f, mt.getCharWidthAt(3), 0.0f); 259 } 260 261 @Test(expected = IllegalArgumentException.class) 262 public void testGetCharWidthAt_OffsetSmallerThanZero() { 263 String text = "Hello, World"; 264 new MeasuredText.Builder(text.toCharArray()) 265 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 266 .build() 267 .getCharWidthAt(-1); 268 } 269 270 @Test(expected = IllegalArgumentException.class) 271 public void testGetCharWidthAt_OffsetLargerThanLength() { 272 String text = "Hello, World"; 273 new MeasuredText.Builder(text.toCharArray()) 274 .appendStyleRun(sPaint, text.length(), false /* isRtl */) 275 .build() 276 .getCharWidthAt(text.length()); 277 } 278 279 @Test(expected = IllegalStateException.class) 280 public void testBuilder_reuse_throw_exception() { 281 String text = "Hello, World"; 282 MeasuredText.Builder b = new MeasuredText.Builder(text.toCharArray()) 283 .appendStyleRun(sPaint, text.length(), false /* isRtl */); 284 b.build(); 285 b.build(); 286 } 287 288 @Test(expected = IllegalArgumentException.class) 289 public void testBuilder_tooSmallLengthStyle() { 290 String text = "Hello, World"; 291 new MeasuredText.Builder(text.toCharArray()).appendStyleRun(sPaint, -1, false /* isRtl */); 292 } 293 294 @Test(expected = IllegalArgumentException.class) 295 public void testBuilder_tooLargeLengthStyle() { 296 String text = "Hello, World"; 297 new MeasuredText.Builder(text.toCharArray()) 298 .appendStyleRun(sPaint, text.length() + 1, false /* isRtl */); 299 } 300 301 @Test(expected = IllegalArgumentException.class) 302 public void testBuilder_tooSmallLengthReplacement() { 303 String text = "Hello, World"; 304 new MeasuredText.Builder(text.toCharArray()).appendReplacementRun(sPaint, -1, 1.0f); 305 } 306 307 @Test(expected = IllegalArgumentException.class) 308 public void testBuilder_tooLargeLengthReplacement() { 309 String text = "Hello, World"; 310 new MeasuredText.Builder(text.toCharArray()) 311 .appendReplacementRun(sPaint, text.length() + 1, 1.0f); 312 } 313 314 @Test(expected = IllegalStateException.class) 315 public void testBuilder_notEnoughStyle() { 316 String text = "Hello, World"; 317 new MeasuredText.Builder(text.toCharArray()) 318 .appendReplacementRun(sPaint, text.length() - 1, 1.0f).build(); 319 } 320 321 public Bitmap makeBitmapFromSsb(String text, TextPaint paint, boolean isRtl) { 322 // drawTetRun is not aware of multiple style text. 323 final SpannableStringBuilder ssb = new SpannableStringBuilder(text); 324 final Bitmap ssbBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); 325 final Canvas ssbCanvas = new Canvas(ssbBitmap); 326 ssbCanvas.save(); 327 ssbCanvas.translate(0, 0); 328 ssbCanvas.drawTextRun(ssb, 0, ssb.length(), 0, ssb.length(), 329 0.0f /* x */, 240.0f /* y */, isRtl, paint); 330 ssbCanvas.restore(); 331 return ssbBitmap; 332 } 333 334 public Bitmap makeBitmapFromMtWithSamePaint(String text, TextPaint paint, boolean isRtl) { 335 // MeasuredText uses measured result if the given paint is equal to the applied one. 336 final MeasuredText mt = new MeasuredText.Builder(text.toCharArray()) 337 .appendStyleRun(paint, text.length(), isRtl) 338 .build(); 339 final Bitmap mtBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); 340 final Canvas mtCanvas = new Canvas(mtBitmap); 341 mtCanvas.save(); 342 mtCanvas.translate(0, 0); 343 mtCanvas.drawTextRun(mt, 0, text.length(), 0, text.length(), 344 0.0f /* x */, 240.0f /* y */, isRtl, paint); 345 mtCanvas.restore(); 346 return mtBitmap; 347 } 348 349 public Bitmap makeBitmapFromMtWithDifferentPaint(String text, TextPaint paint, boolean isRtl) { 350 // If different paint is provided when drawing, MeasuredText discards the measured result 351 // and recompute immediately. Thus the final output must be the same with given one. 352 final MeasuredText mt2 = new MeasuredText.Builder(text.toCharArray()) 353 .appendStyleRun(new Paint(), text.length(), isRtl) 354 .build(); 355 final Bitmap mt2Bitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); 356 final Canvas mt2Canvas = new Canvas(mt2Bitmap); 357 mt2Canvas.save(); 358 mt2Canvas.translate(0, 0); 359 mt2Canvas.drawTextRun(mt2, 0, text.length(), 0, text.length(), 360 0.0f /* x */, 240.0f /* y */, isRtl, paint); 361 mt2Canvas.restore(); 362 return mt2Bitmap; 363 } 364 365 public Bitmap makeBitmapFromPct(String text, TextPaint paint, boolean isRtl) { 366 final PrecomputedText pct = PrecomputedText.create( 367 text, new PrecomputedText.Params.Builder(paint).build()); 368 final Bitmap pctBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); 369 final Canvas pctCanvas = new Canvas(pctBitmap); 370 pctCanvas.save(); 371 pctCanvas.translate(0, 0); 372 pctCanvas.drawTextRun(pct, 0, text.length(), 0, text.length(), 373 0.0f /* x */, 240.0f /* y */, isRtl, paint); 374 pctCanvas.restore(); 375 return pctBitmap; 376 } 377 378 @Test 379 public void testCanvasDrawTextRun_sameOutputTestForLatinText() { 380 final TextPaint paint = new TextPaint(); 381 paint.setTextSize(30.0f); 382 383 final String text = "Hello, World"; 384 385 final Bitmap blankBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); 386 final Bitmap ssbBitmap = makeBitmapFromSsb(text, paint, false); 387 assertFalse(blankBitmap.sameAs(ssbBitmap)); 388 389 assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithSamePaint(text, paint, false))); 390 assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithDifferentPaint(text, paint, false))); 391 assertTrue(ssbBitmap.sameAs(makeBitmapFromPct(text, paint, false))); 392 } 393 394 @Test 395 public void testCanvasDrawTextRun_sameOutputTestForCJKText() { 396 final TextPaint paint = new TextPaint(); 397 paint.setTextSize(30.0f); 398 399 final String text = "\u3042\u3044\u3046\u3048\u304A"; 400 401 final Bitmap blankBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); 402 final Bitmap ssbBitmap = makeBitmapFromSsb(text, paint, false); 403 assertFalse(blankBitmap.sameAs(ssbBitmap)); 404 405 assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithSamePaint(text, paint, false))); 406 assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithDifferentPaint(text, paint, false))); 407 assertTrue(ssbBitmap.sameAs(makeBitmapFromPct(text, paint, false))); 408 } 409 410 @Test 411 public void testCanvasDrawTextRun_sameOutputTestForRTLText() { 412 final TextPaint paint = new TextPaint(); 413 paint.setTextSize(30.0f); 414 415 final String text = "\u05D0\u05D1\u05D2\u05D3\u05D4"; 416 417 final Bitmap blankBitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); 418 final Bitmap ssbBitmap = makeBitmapFromSsb(text, paint, true); 419 assertFalse(blankBitmap.sameAs(ssbBitmap)); 420 421 assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithSamePaint(text, paint, true))); 422 assertTrue(ssbBitmap.sameAs(makeBitmapFromMtWithDifferentPaint(text, paint, true))); 423 assertTrue(ssbBitmap.sameAs(makeBitmapFromPct(text, paint, true))); 424 } 425 } 426