Home | History | Annotate | Download | only in graphics
      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.graphics.Paint;
     22 import android.test.InstrumentationTestCase;
     23 import android.test.suitebuilder.annotation.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 
     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