1/* 2 * Copyright (C) 2015 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 17package android.graphics; 18 19import static org.junit.Assert.assertNotEquals; 20 21import android.graphics.Paint; 22import android.test.InstrumentationTestCase; 23import android.test.suitebuilder.annotation.SmallTest; 24 25import java.util.Arrays; 26import java.util.HashSet; 27 28/** 29 * PaintTest tests {@link Paint}. 30 */ 31public class PaintTest extends InstrumentationTestCase { 32 private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf"; 33 34 static void assertEquals(String message, float[] expected, float[] actual) { 35 if (expected.length != actual.length) { 36 fail(message + " expected array length:<" + expected.length + "> but was:<" 37 + actual.length + ">"); 38 } 39 for (int i = 0; i < expected.length; ++i) { 40 if (expected[i] != actual[i]) { 41 fail(message + " expected array element[" +i + "]:<" + expected[i] + ">but was:<" 42 + actual[i] + ">"); 43 } 44 } 45 } 46 47 static class HintingTestCase { 48 public final String mText; 49 public final float mTextSize; 50 public final float[] mWidthWithoutHinting; 51 public final float[] mWidthWithHinting; 52 53 public HintingTestCase(String text, float textSize, float[] widthWithoutHinting, 54 float[] widthWithHinting) { 55 mText = text; 56 mTextSize = textSize; 57 mWidthWithoutHinting = widthWithoutHinting; 58 mWidthWithHinting = widthWithHinting; 59 } 60 } 61 62 // Following test cases are only valid for HintedAdvanceWidthTest-Regular.ttf in assets/fonts. 63 HintingTestCase[] HINTING_TESTCASES = { 64 new HintingTestCase("H", 11f, new float[] { 7f }, new float[] { 13f }), 65 new HintingTestCase("O", 11f, new float[] { 7f }, new float[] { 13f }), 66 67 new HintingTestCase("H", 13f, new float[] { 8f }, new float[] { 14f }), 68 new HintingTestCase("O", 13f, new float[] { 9f }, new float[] { 15f }), 69 70 new HintingTestCase("HO", 11f, new float[] { 7f, 7f }, new float[] { 13f, 13f }), 71 new HintingTestCase("OH", 11f, new float[] { 7f, 7f }, new float[] { 13f, 13f }), 72 73 new HintingTestCase("HO", 13f, new float[] { 8f, 9f }, new float[] { 14f, 15f }), 74 new HintingTestCase("OH", 13f, new float[] { 9f, 8f }, new float[] { 15f, 14f }), 75 }; 76 77 @SmallTest 78 public void testHintingWidth() { 79 final Typeface fontTypeface = Typeface.createFromAsset( 80 getInstrumentation().getContext().getAssets(), FONT_PATH); 81 Paint paint = new Paint(); 82 paint.setTypeface(fontTypeface); 83 84 for (int i = 0; i < HINTING_TESTCASES.length; ++i) { 85 HintingTestCase testCase = HINTING_TESTCASES[i]; 86 87 paint.setTextSize(testCase.mTextSize); 88 89 float[] widths = new float[testCase.mText.length()]; 90 91 paint.setHinting(Paint.HINTING_OFF); 92 paint.getTextWidths(String.valueOf(testCase.mText), widths); 93 assertEquals("Text width of '" + testCase.mText + "' without hinting is not expected.", 94 testCase.mWidthWithoutHinting, widths); 95 96 paint.setHinting(Paint.HINTING_ON); 97 paint.getTextWidths(String.valueOf(testCase.mText), widths); 98 assertEquals("Text width of '" + testCase.mText + "' with hinting is not expected.", 99 testCase.mWidthWithHinting, widths); 100 } 101 } 102 103 private static class HasGlyphTestCase { 104 public final int mBaseCodepoint; 105 public final HashSet<Integer> mVariationSelectors; 106 107 public HasGlyphTestCase(int baseCodepoint, Integer[] variationSelectors) { 108 mBaseCodepoint = baseCodepoint; 109 mVariationSelectors = new HashSet<>(Arrays.asList(variationSelectors)); 110 } 111 } 112 113 private static String codePointsToString(int[] codepoints) { 114 StringBuilder sb = new StringBuilder(); 115 for (int codepoint : codepoints) { 116 sb.append(Character.toChars(codepoint)); 117 } 118 return sb.toString(); 119 } 120 121 public void testHasGlyph_variationSelectors() { 122 final Typeface fontTypeface = Typeface.createFromAsset( 123 getInstrumentation().getContext().getAssets(), "fonts/hasGlyphTestFont.ttf"); 124 Paint p = new Paint(); 125 p.setTypeface(fontTypeface); 126 127 // Usually latin letters U+0061..U+0064 and Mahjong Tiles U+1F000..U+1F003 don't have 128 // variation selectors. This test may fail if system pre-installed fonts have a variation 129 // selector support for U+0061..U+0064 and U+1F000..U+1F003. 130 HasGlyphTestCase[] HAS_GLYPH_TEST_CASES = { 131 new HasGlyphTestCase(0x0061, new Integer[] {0xFE00, 0xE0100, 0xE0101, 0xE0102}), 132 new HasGlyphTestCase(0x0062, new Integer[] {0xFE01, 0xE0101, 0xE0102, 0xE0103}), 133 new HasGlyphTestCase(0x0063, new Integer[] {}), 134 new HasGlyphTestCase(0x0064, new Integer[] {0xFE02, 0xE0102, 0xE0103}), 135 136 new HasGlyphTestCase(0x1F000, new Integer[] {0xFE00, 0xE0100, 0xE0101, 0xE0102}), 137 new HasGlyphTestCase(0x1F001, new Integer[] {0xFE01, 0xE0101, 0xE0102, 0xE0103}), 138 new HasGlyphTestCase(0x1F002, new Integer[] {}), 139 new HasGlyphTestCase(0x1F003, new Integer[] {0xFE02, 0xE0102, 0xE0103}), 140 }; 141 142 for (HasGlyphTestCase testCase : HAS_GLYPH_TEST_CASES) { 143 for (int vs = 0xFE00; vs <= 0xE01EF; ++vs) { 144 // Move to variation selector supplements after variation selectors. 145 if (vs == 0xFF00) { 146 vs = 0xE0100; 147 } 148 final String signature = 149 "hasGlyph(U+" + Integer.toHexString(testCase.mBaseCodepoint) + 150 " U+" + Integer.toHexString(vs) + ")"; 151 final String testString = 152 codePointsToString(new int[] {testCase.mBaseCodepoint, vs}); 153 if (vs == 0xFE0E // U+FE0E is the text presentation emoji. hasGlyph is expected to 154 // return true for that variation selector if the font has the base 155 // glyph. 156 || testCase.mVariationSelectors.contains(vs)) { 157 assertTrue(signature + " is expected to be true", p.hasGlyph(testString)); 158 } else { 159 assertFalse(signature + " is expected to be false", p.hasGlyph(testString)); 160 } 161 } 162 } 163 } 164 165 public void testGetTextRunAdvances() { 166 { 167 // LTR 168 String text = "abcdef"; 169 assertGetTextRunAdvances(text, 0, text.length(), 0, text.length(), false, true); 170 assertGetTextRunAdvances(text, 1, text.length() - 1, 0, text.length(), false, false); 171 } 172 { 173 // RTL 174 final String text = 175 "\u0645\u0627\u0020\u0647\u064A\u0020\u0627\u0644\u0634" + 176 "\u0641\u0631\u0629\u0020\u0627\u0644\u0645\u0648\u062D" + 177 "\u062F\u0629\u0020\u064A\u0648\u0646\u064A\u0643\u0648" + 178 "\u062F\u061F"; 179 assertGetTextRunAdvances(text, 0, text.length(), 0, text.length(), true, true); 180 assertGetTextRunAdvances(text, 1, text.length() - 1, 0, text.length(), true, false); 181 } 182 } 183 184 private void assertGetTextRunAdvances(String str, int start, int end, 185 int contextStart, int contextEnd, boolean isRtl, boolean compareWithOtherMethods) { 186 Paint p = new Paint(); 187 188 final int count = end - start; 189 final float[][] advanceArrays = new float[4][count]; 190 191 final float advance = p.getTextRunAdvances(str, start, end, contextStart, contextEnd, 192 isRtl, advanceArrays[0], 0); 193 194 char chars[] = str.toCharArray(); 195 final float advance_c = p.getTextRunAdvances(chars, start, count, contextStart, 196 contextEnd - contextStart, isRtl, advanceArrays[1], 0); 197 assertEquals(advance, advance_c, 1.0f); 198 199 for (int c = 1; c < count; ++c) { 200 final float firstPartAdvance = p.getTextRunAdvances(str, start, start + c, 201 contextStart, contextEnd, isRtl, advanceArrays[2], 0); 202 final float secondPartAdvance = p.getTextRunAdvances(str, start + c, end, 203 contextStart, contextEnd, isRtl, advanceArrays[2], c); 204 assertEquals(advance, firstPartAdvance + secondPartAdvance, 1.0f); 205 206 207 final float firstPartAdvance_c = p.getTextRunAdvances(chars, start, c, 208 contextStart, contextEnd - contextStart, isRtl, advanceArrays[3], 0); 209 final float secondPartAdvance_c = p.getTextRunAdvances(chars, start + c, 210 count - c, contextStart, contextEnd - contextStart, isRtl, 211 advanceArrays[3], c); 212 assertEquals(advance, firstPartAdvance_c + secondPartAdvance_c, 1.0f); 213 assertEquals(firstPartAdvance, firstPartAdvance_c, 1.0f); 214 assertEquals(secondPartAdvance, secondPartAdvance_c, 1.0f); 215 216 for (int i = 1; i < advanceArrays.length; i++) { 217 for (int j = 0; j < count; j++) { 218 assertEquals(advanceArrays[0][j], advanceArrays[i][j], 1.0f); 219 } 220 } 221 222 // Compare results with measureText, getRunAdvance, and getTextWidths. 223 if (compareWithOtherMethods && start == contextStart && end == contextEnd) { 224 assertEquals(advance, p.measureText(str, start, end), 1.0f); 225 assertEquals(advance, p.getRunAdvance( 226 str, start, end, contextStart, contextEnd, isRtl, end), 1.0f); 227 228 final float[] widths = new float[count]; 229 p.getTextWidths(str, start, end, widths); 230 for (int i = 0; i < count; i++) { 231 assertEquals(advanceArrays[0][i], widths[i], 1.0f); 232 } 233 } 234 } 235 } 236 237 public void testGetTextRunAdvances_invalid() { 238 Paint p = new Paint(); 239 String text = "test"; 240 241 try { 242 p.getTextRunAdvances((String)null, 0, 0, 0, 0, false, null, 0); 243 fail("Should throw an IllegalArgumentException."); 244 } catch (IllegalArgumentException e) { 245 } 246 247 try { 248 p.getTextRunAdvances((CharSequence)null, 0, 0, 0, 0, false, null, 0); 249 fail("Should throw an IllegalArgumentException."); 250 } catch (IllegalArgumentException e) { 251 } 252 253 try { 254 p.getTextRunAdvances((char[])null, 0, 0, 0, 0, false, null, 0); 255 fail("Should throw an IllegalArgumentException."); 256 } catch (IllegalArgumentException e) { 257 } 258 259 try { 260 p.getTextRunAdvances(text, 0, text.length(), 0, text.length(), false, 261 new float[text.length() - 1], 0); 262 fail("Should throw an IndexOutOfBoundsException."); 263 } catch (IndexOutOfBoundsException e) { 264 } 265 266 try { 267 p.getTextRunAdvances(text, 0, text.length(), 0, text.length(), false, 268 new float[text.length()], 1); 269 fail("Should throw an IndexOutOfBoundsException."); 270 } catch (IndexOutOfBoundsException e) { 271 } 272 273 // 0 > contextStart 274 try { 275 p.getTextRunAdvances(text, 0, text.length(), -1, text.length(), false, null, 0); 276 fail("Should throw an IndexOutOfBoundsException."); 277 } catch (IndexOutOfBoundsException e) { 278 } 279 280 // contextStart > start 281 try { 282 p.getTextRunAdvances(text, 0, text.length(), 1, text.length(), false, null, 0); 283 fail("Should throw an IndexOutOfBoundsException."); 284 } catch (IndexOutOfBoundsException e) { 285 } 286 287 // start > end 288 try { 289 p.getTextRunAdvances(text, 1, 0, 0, text.length(), false, null, 0); 290 fail("Should throw an IndexOutOfBoundsException."); 291 } catch (IndexOutOfBoundsException e) { 292 } 293 294 // end > contextEnd 295 try { 296 p.getTextRunAdvances(text, 0, text.length(), 0, text.length() - 1, false, null, 0); 297 fail("Should throw an IndexOutOfBoundsException."); 298 } catch (IndexOutOfBoundsException e) { 299 } 300 301 // contextEnd > text.length 302 try { 303 p.getTextRunAdvances(text, 0, text.length(), 0, text.length() + 1, false, null, 0); 304 fail("Should throw an IndexOutOfBoundsException."); 305 } catch (IndexOutOfBoundsException e) { 306 } 307 } 308 309 public void testMeasureTextBidi() { 310 Paint p = new Paint(); 311 { 312 String bidiText = "abc \u0644\u063A\u0629 def"; 313 p.setBidiFlags(Paint.BIDI_LTR); 314 float width = p.measureText(bidiText, 0, 4); 315 p.setBidiFlags(Paint.BIDI_RTL); 316 width += p.measureText(bidiText, 4, 7); 317 p.setBidiFlags(Paint.BIDI_LTR); 318 width += p.measureText(bidiText, 7, bidiText.length()); 319 assertEquals(width, p.measureText(bidiText), 1.0f); 320 } 321 { 322 String bidiText = "abc \u0644\u063A\u0629 def"; 323 p.setBidiFlags(Paint.BIDI_DEFAULT_LTR); 324 float width = p.measureText(bidiText, 0, 4); 325 width += p.measureText(bidiText, 4, 7); 326 width += p.measureText(bidiText, 7, bidiText.length()); 327 assertEquals(width, p.measureText(bidiText), 1.0f); 328 } 329 { 330 String bidiText = "abc \u0644\u063A\u0629 def"; 331 p.setBidiFlags(Paint.BIDI_FORCE_LTR); 332 float width = p.measureText(bidiText, 0, 4); 333 width += p.measureText(bidiText, 4, 7); 334 width += p.measureText(bidiText, 7, bidiText.length()); 335 assertEquals(width, p.measureText(bidiText), 1.0f); 336 } 337 { 338 String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629"; 339 p.setBidiFlags(Paint.BIDI_RTL); 340 float width = p.measureText(bidiText, 0, 4); 341 p.setBidiFlags(Paint.BIDI_LTR); 342 width += p.measureText(bidiText, 4, 7); 343 p.setBidiFlags(Paint.BIDI_RTL); 344 width += p.measureText(bidiText, 7, bidiText.length()); 345 assertEquals(width, p.measureText(bidiText), 1.0f); 346 } 347 { 348 String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629"; 349 p.setBidiFlags(Paint.BIDI_DEFAULT_RTL); 350 float width = p.measureText(bidiText, 0, 4); 351 width += p.measureText(bidiText, 4, 7); 352 width += p.measureText(bidiText, 7, bidiText.length()); 353 assertEquals(width, p.measureText(bidiText), 1.0f); 354 } 355 { 356 String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629"; 357 p.setBidiFlags(Paint.BIDI_FORCE_RTL); 358 float width = p.measureText(bidiText, 0, 4); 359 width += p.measureText(bidiText, 4, 7); 360 width += p.measureText(bidiText, 7, bidiText.length()); 361 assertEquals(width, p.measureText(bidiText), 1.0f); 362 } 363 } 364 365 public void testSetGetWordSpacing() { 366 Paint p = new Paint(); 367 assertEquals(0.0f, p.getWordSpacing()); // The default value should be 0. 368 p.setWordSpacing(1.0f); 369 assertEquals(1.0f, p.getWordSpacing()); 370 p.setWordSpacing(-2.0f); 371 assertEquals(-2.0f, p.getWordSpacing()); 372 } 373 374 public void testGetUnderlinePositionAndThickness() { 375 final Typeface fontTypeface = Typeface.createFromAsset( 376 getInstrumentation().getContext().getAssets(), "fonts/underlineTestFont.ttf"); 377 final Paint p = new Paint(); 378 final int textSize = 100; 379 p.setTextSize(textSize); 380 381 final float origPosition = p.getUnderlinePosition(); 382 final float origThickness = p.getUnderlineThickness(); 383 384 p.setTypeface(fontTypeface); 385 assertNotEquals(origPosition, p.getUnderlinePosition()); 386 assertNotEquals(origThickness, p.getUnderlineThickness()); 387 388 // -200 (underlinePosition in 'post' table, negative means below the baseline) 389 // ÷ 1000 (unitsPerEm in 'head' table) 390 // × 100 (text size) 391 // × -1 (negated, since we consider below the baseline positive) 392 // = 20 393 assertEquals(20.0f, p.getUnderlinePosition(), 0.5f); 394 // 300 (underlineThickness in 'post' table) 395 // ÷ 1000 (unitsPerEm in 'head' table) 396 // × 100 (text size) 397 // = 30 398 assertEquals(30.0f, p.getUnderlineThickness(), 0.5f); 399 } 400} 401