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 17 package android.graphics; 18 19 import static org.junit.Assert.assertNotEquals; 20 21 import android.test.InstrumentationTestCase; 22 23 import androidx.test.filters.SmallTest; 24 25 import java.util.Arrays; 26 import java.util.HashSet; 27 28 /** 29 * PaintTest tests {@link Paint}. 30 */ 31 public class PaintTest extends InstrumentationTestCase { 32 private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf"; 33 assertEquals(String message, float[] expected, float[] actual)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 HintingTestCase(String text, float textSize, float[] widthWithoutHinting, float[] widthWithHinting)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 testHintingWidth()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 HasGlyphTestCase(int baseCodepoint, Integer[] variationSelectors)107 public HasGlyphTestCase(int baseCodepoint, Integer[] variationSelectors) { 108 mBaseCodepoint = baseCodepoint; 109 mVariationSelectors = new HashSet<>(Arrays.asList(variationSelectors)); 110 } 111 } 112 codePointsToString(int[] codepoints)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 testHasGlyph_variationSelectors()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 testGetTextRunAdvances()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 assertGetTextRunAdvances(String str, int start, int end, int contextStart, int contextEnd, boolean isRtl, boolean compareWithOtherMethods)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 int contextCount = contextEnd - contextStart; 190 final float[][] advanceArrays = new float[2][count]; 191 char chars[] = str.toCharArray(); 192 final float advance = p.getTextRunAdvances(chars, start, count, 193 contextStart, contextCount, isRtl, advanceArrays[0], 0); 194 for (int c = 1; c < count; ++c) { 195 final float firstPartAdvance = p.getTextRunAdvances(chars, start, c, 196 contextStart, contextCount, isRtl, advanceArrays[1], 0); 197 final float secondPartAdvance = p.getTextRunAdvances(chars, start + c, count - c, 198 contextStart, contextCount, isRtl, advanceArrays[1], c); 199 assertEquals(advance, firstPartAdvance + secondPartAdvance, 1.0f); 200 201 for (int j = 0; j < count; j++) { 202 assertEquals(advanceArrays[0][j], advanceArrays[1][j], 1.0f); 203 } 204 205 206 // Compare results with measureText, getRunAdvance, and getTextWidths. 207 if (compareWithOtherMethods && start == contextStart && end == contextEnd) { 208 assertEquals(advance, p.measureText(str, start, end), 1.0f); 209 assertEquals(advance, p.getRunAdvance( 210 chars, start, count, contextStart, contextCount, isRtl, end), 1.0f); 211 212 final float[] widths = new float[count]; 213 p.getTextWidths(str, start, end, widths); 214 for (int i = 0; i < count; i++) { 215 assertEquals(advanceArrays[0][i], widths[i], 1.0f); 216 } 217 } 218 } 219 } 220 testGetTextRunAdvances_invalid()221 public void testGetTextRunAdvances_invalid() { 222 Paint p = new Paint(); 223 char[] text = "test".toCharArray(); 224 225 try { 226 p.getTextRunAdvances((char[])null, 0, 0, 0, 0, false, null, 0); 227 fail("Should throw an IllegalArgumentException."); 228 } catch (IllegalArgumentException e) { 229 } 230 231 try { 232 p.getTextRunAdvances(text, 0, text.length, 0, text.length, false, 233 new float[text.length - 1], 0); 234 fail("Should throw an IndexOutOfBoundsException."); 235 } catch (IndexOutOfBoundsException e) { 236 } 237 238 try { 239 p.getTextRunAdvances(text, 0, text.length, 0, text.length, false, 240 new float[text.length], 1); 241 fail("Should throw an IndexOutOfBoundsException."); 242 } catch (IndexOutOfBoundsException e) { 243 } 244 245 // 0 > contextStart 246 try { 247 p.getTextRunAdvances(text, 0, text.length, -1, text.length, false, null, 0); 248 fail("Should throw an IndexOutOfBoundsException."); 249 } catch (IndexOutOfBoundsException e) { 250 } 251 252 // contextStart > start 253 try { 254 p.getTextRunAdvances(text, 0, text.length, 1, text.length, false, null, 0); 255 fail("Should throw an IndexOutOfBoundsException."); 256 } catch (IndexOutOfBoundsException e) { 257 } 258 259 // end > contextEnd 260 try { 261 p.getTextRunAdvances(text, 0, text.length, 0, text.length - 1, false, null, 0); 262 fail("Should throw an IndexOutOfBoundsException."); 263 } catch (IndexOutOfBoundsException e) { 264 } 265 266 // contextEnd > text.length 267 try { 268 p.getTextRunAdvances(text, 0, text.length, 0, text.length + 1, false, null, 0); 269 fail("Should throw an IndexOutOfBoundsException."); 270 } catch (IndexOutOfBoundsException e) { 271 } 272 } 273 testMeasureTextBidi()274 public void testMeasureTextBidi() { 275 Paint p = new Paint(); 276 { 277 String bidiText = "abc \u0644\u063A\u0629 def"; 278 p.setBidiFlags(Paint.BIDI_LTR); 279 float width = p.measureText(bidiText, 0, 4); 280 p.setBidiFlags(Paint.BIDI_RTL); 281 width += p.measureText(bidiText, 4, 7); 282 p.setBidiFlags(Paint.BIDI_LTR); 283 width += p.measureText(bidiText, 7, bidiText.length()); 284 assertEquals(width, p.measureText(bidiText), 1.0f); 285 } 286 { 287 String bidiText = "abc \u0644\u063A\u0629 def"; 288 p.setBidiFlags(Paint.BIDI_DEFAULT_LTR); 289 float width = p.measureText(bidiText, 0, 4); 290 width += p.measureText(bidiText, 4, 7); 291 width += p.measureText(bidiText, 7, bidiText.length()); 292 assertEquals(width, p.measureText(bidiText), 1.0f); 293 } 294 { 295 String bidiText = "abc \u0644\u063A\u0629 def"; 296 p.setBidiFlags(Paint.BIDI_FORCE_LTR); 297 float width = p.measureText(bidiText, 0, 4); 298 width += p.measureText(bidiText, 4, 7); 299 width += p.measureText(bidiText, 7, bidiText.length()); 300 assertEquals(width, p.measureText(bidiText), 1.0f); 301 } 302 { 303 String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629"; 304 p.setBidiFlags(Paint.BIDI_RTL); 305 float width = p.measureText(bidiText, 0, 4); 306 p.setBidiFlags(Paint.BIDI_LTR); 307 width += p.measureText(bidiText, 4, 7); 308 p.setBidiFlags(Paint.BIDI_RTL); 309 width += p.measureText(bidiText, 7, bidiText.length()); 310 assertEquals(width, p.measureText(bidiText), 1.0f); 311 } 312 { 313 String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629"; 314 p.setBidiFlags(Paint.BIDI_DEFAULT_RTL); 315 float width = p.measureText(bidiText, 0, 4); 316 width += p.measureText(bidiText, 4, 7); 317 width += p.measureText(bidiText, 7, bidiText.length()); 318 assertEquals(width, p.measureText(bidiText), 1.0f); 319 } 320 { 321 String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629"; 322 p.setBidiFlags(Paint.BIDI_FORCE_RTL); 323 float width = p.measureText(bidiText, 0, 4); 324 width += p.measureText(bidiText, 4, 7); 325 width += p.measureText(bidiText, 7, bidiText.length()); 326 assertEquals(width, p.measureText(bidiText), 1.0f); 327 } 328 } 329 testSetGetWordSpacing()330 public void testSetGetWordSpacing() { 331 Paint p = new Paint(); 332 assertEquals(0.0f, p.getWordSpacing()); // The default value should be 0. 333 p.setWordSpacing(1.0f); 334 assertEquals(1.0f, p.getWordSpacing()); 335 p.setWordSpacing(-2.0f); 336 assertEquals(-2.0f, p.getWordSpacing()); 337 } 338 testGetUnderlinePositionAndThickness()339 public void testGetUnderlinePositionAndThickness() { 340 final Typeface fontTypeface = Typeface.createFromAsset( 341 getInstrumentation().getContext().getAssets(), "fonts/underlineTestFont.ttf"); 342 final Paint p = new Paint(); 343 final int textSize = 100; 344 p.setTextSize(textSize); 345 346 final float origPosition = p.getUnderlinePosition(); 347 final float origThickness = p.getUnderlineThickness(); 348 349 p.setTypeface(fontTypeface); 350 assertNotEquals(origPosition, p.getUnderlinePosition()); 351 assertNotEquals(origThickness, p.getUnderlineThickness()); 352 353 // -200 (underlinePosition in 'post' table, negative means below the baseline) 354 // ÷ 1000 (unitsPerEm in 'head' table) 355 // × 100 (text size) 356 // × -1 (negated, since we consider below the baseline positive) 357 // = 20 358 assertEquals(20.0f, p.getUnderlinePosition(), 0.5f); 359 // 300 (underlineThickness in 'post' table) 360 // ÷ 1000 (unitsPerEm in 'head' table) 361 // × 100 (text size) 362 // = 30 363 assertEquals(30.0f, p.getUnderlineThickness(), 0.5f); 364 } 365 } 366