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