1 /*
2  * Copyright (C) 2009 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.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotEquals;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertNotSame;
24 import static org.junit.Assert.assertNull;
25 import static org.junit.Assert.assertSame;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.content.Context;
30 import android.content.res.AssetManager;
31 import android.graphics.Paint;
32 import android.graphics.Typeface;
33 import android.graphics.Typeface.Builder;
34 import android.support.test.InstrumentationRegistry;
35 import android.support.test.filters.SmallTest;
36 import android.support.test.runner.AndroidJUnit4;
37 
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.util.Locale;
48 
49 @SmallTest
50 @RunWith(AndroidJUnit4.class)
51 public class TypefaceTest {
52     // generic family name for monospaced fonts
53     private static final String MONO = "monospace";
54     private static final String DEFAULT = (String)null;
55     private static final String INVALID = "invalid-family-name";
56 
57     private static final float GLYPH_1EM_WIDTH;
58     private static final float GLYPH_3EM_WIDTH;
59 
measureText(String text, Typeface typeface)60     private static float measureText(String text, Typeface typeface) {
61         final Paint paint = new Paint();
62         // Fix the locale so that fix the locale based fallback.
63         paint.setTextLocale(Locale.US);
64         paint.setTypeface(typeface);
65         return paint.measureText(text);
66     }
67 
68     static {
69         // 3em.ttf supports "a", "b", "c". The width of "a" is 3em, others are 1em.
70         final Context ctx = InstrumentationRegistry.getTargetContext();
71         final Typeface typeface = ctx.getResources().getFont(R.font.a3em);
72         GLYPH_3EM_WIDTH = measureText("a", typeface);
73         GLYPH_1EM_WIDTH = measureText("b", typeface);
74     }
75 
76     // list of family names to try when attempting to find a typeface with a given style
77     private static final String[] FAMILIES =
78             { (String) null, "monospace", "serif", "sans-serif", "cursive", "arial", "times" };
79 
80     private Context mContext;
81 
82     /**
83      * Create a typeface of the given style. If the default font does not support the style,
84      * a number of generic families are tried.
85      * @return The typeface or null, if no typeface with the given style can be found.
86      */
createTypeface(int style)87     private static Typeface createTypeface(int style) {
88         for (String family : FAMILIES) {
89             Typeface tf = Typeface.create(family, style);
90             if (tf.getStyle() == style) {
91                 return tf;
92             }
93         }
94         return null;
95     }
96 
97     @Before
setup()98     public void setup() {
99         mContext = InstrumentationRegistry.getTargetContext();
100     }
101 
102     @Test
testIsBold()103     public void testIsBold() {
104         Typeface typeface = createTypeface(Typeface.BOLD);
105         if (typeface != null) {
106             assertEquals(Typeface.BOLD, typeface.getStyle());
107             assertTrue(typeface.isBold());
108             assertFalse(typeface.isItalic());
109         }
110 
111         typeface = createTypeface(Typeface.ITALIC);
112         if (typeface != null) {
113             assertEquals(Typeface.ITALIC, typeface.getStyle());
114             assertFalse(typeface.isBold());
115             assertTrue(typeface.isItalic());
116         }
117 
118         typeface = createTypeface(Typeface.BOLD_ITALIC);
119         if (typeface != null) {
120             assertEquals(Typeface.BOLD_ITALIC, typeface.getStyle());
121             assertTrue(typeface.isBold());
122             assertTrue(typeface.isItalic());
123         }
124 
125         typeface = createTypeface(Typeface.NORMAL);
126         if (typeface != null) {
127             assertEquals(Typeface.NORMAL, typeface.getStyle());
128             assertFalse(typeface.isBold());
129             assertFalse(typeface.isItalic());
130         }
131     }
132 
133     @Test
testCreate()134     public void testCreate() {
135         Typeface typeface = Typeface.create(DEFAULT, Typeface.NORMAL);
136         assertNotNull(typeface);
137         typeface = Typeface.create(MONO, Typeface.BOLD);
138         assertNotNull(typeface);
139         typeface = Typeface.create(INVALID, Typeface.ITALIC);
140         assertNotNull(typeface);
141 
142         typeface = Typeface.create(typeface, Typeface.NORMAL);
143         assertNotNull(typeface);
144         typeface = Typeface.create(typeface, Typeface.BOLD);
145         assertNotNull(typeface);
146     }
147 
148     @Test
testDefaultFromStyle()149     public void testDefaultFromStyle() {
150         Typeface typeface = Typeface.defaultFromStyle(Typeface.NORMAL);
151         assertNotNull(typeface);
152         typeface = Typeface.defaultFromStyle(Typeface.BOLD);
153         assertNotNull(typeface);
154         typeface = Typeface.defaultFromStyle(Typeface.ITALIC);
155         assertNotNull(typeface);
156         typeface = Typeface.defaultFromStyle(Typeface.BOLD_ITALIC);
157         assertNotNull(typeface);
158     }
159 
160     @Test
testConstants()161     public void testConstants() {
162         assertNotNull(Typeface.DEFAULT);
163         assertNotNull(Typeface.DEFAULT_BOLD);
164         assertNotNull(Typeface.MONOSPACE);
165         assertNotNull(Typeface.SANS_SERIF);
166         assertNotNull(Typeface.SERIF);
167     }
168 
169     @Test(expected=NullPointerException.class)
testCreateFromAssetNull()170     public void testCreateFromAssetNull() {
171         // input abnormal params.
172         Typeface.createFromAsset(null, null);
173     }
174 
175     @Test(expected=NullPointerException.class)
testCreateFromAssetNullPath()176     public void testCreateFromAssetNullPath() {
177         // input abnormal params.
178         Typeface.createFromAsset(mContext.getAssets(), null);
179     }
180 
181     @Test(expected=RuntimeException.class)
testCreateFromAssetInvalidPath()182     public void testCreateFromAssetInvalidPath() {
183         // input abnormal params.
184         Typeface.createFromAsset(mContext.getAssets(), "invalid path");
185     }
186 
187     @Test
testCreateFromAsset()188     public void testCreateFromAsset() {
189         Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
190         assertNotNull(typeface);
191     }
192 
193     @Test(expected=NullPointerException.class)
testCreateFromFileByFileReferenceNull()194     public void testCreateFromFileByFileReferenceNull() {
195         // input abnormal params.
196         Typeface.createFromFile((File) null);
197     }
198 
199     @Test
testCreateFromFileByFileReference()200     public void testCreateFromFileByFileReference() throws IOException {
201         File file = new File(obtainPath());
202         Typeface typeface = Typeface.createFromFile(file);
203         assertNotNull(typeface);
204     }
205 
206     @Test(expected=RuntimeException.class)
testCreateFromFileWithInvalidPath()207     public void testCreateFromFileWithInvalidPath() throws IOException {
208         File file = new File("/invalid/path");
209         Typeface.createFromFile(file);
210     }
211 
212     @Test(expected=NullPointerException.class)
testCreateFromFileByFileNameNull()213     public void testCreateFromFileByFileNameNull() throws IOException {
214         // input abnormal params.
215         Typeface.createFromFile((String) null);
216     }
217 
218     @Test(expected=RuntimeException.class)
testCreateFromFileByInvalidFileName()219     public void testCreateFromFileByInvalidFileName() throws IOException {
220         // input abnormal params.
221         Typeface.createFromFile("/invalid/path");
222     }
223 
224     @Test
testCreateFromFileByFileName()225     public void testCreateFromFileByFileName() throws IOException {
226         Typeface typeface = Typeface.createFromFile(obtainPath());
227         assertNotNull(typeface);
228     }
229 
obtainPath()230     private String obtainPath() throws IOException {
231         File dir = mContext.getFilesDir();
232         dir.mkdirs();
233         File file = new File(dir, "test.jpg");
234         if (!file.createNewFile()) {
235             if (!file.exists()) {
236                 fail("Failed to create new File!");
237             }
238         }
239         InputStream is = mContext.getAssets().open("samplefont.ttf");
240         FileOutputStream fOutput = new FileOutputStream(file);
241         byte[] dataBuffer = new byte[1024];
242         int readLength = 0;
243         while ((readLength = is.read(dataBuffer)) != -1) {
244             fOutput.write(dataBuffer, 0, readLength);
245         }
246         is.close();
247         fOutput.close();
248         return (file.getPath());
249     }
250 
251     @Test
testInvalidCmapFont()252     public void testInvalidCmapFont() {
253         Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "bombfont.ttf");
254         assertNotNull(typeface);
255         final String testString = "abcde";
256         float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
257         float widthCustomTypeface = measureText(testString, typeface);
258         assertEquals(widthDefaultTypeface, widthCustomTypeface, 1.0f);
259     }
260 
261     @Test
testInvalidCmapFont2()262     public void testInvalidCmapFont2() {
263         Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "bombfont2.ttf");
264         assertNotNull(typeface);
265         final String testString = "abcde";
266         float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
267         float widthCustomTypeface = measureText(testString, typeface);
268         assertEquals(widthDefaultTypeface, widthCustomTypeface, 1.0f);
269     }
270 
271     @Test
testInvalidCmapFont_tooLargeCodePoints()272     public void testInvalidCmapFont_tooLargeCodePoints() {
273         // Following three font doen't have any coverage between U+0000..U+10FFFF. Just make sure
274         // they don't crash us.
275         final String[] INVALID_CMAP_FONTS = {
276             "out_of_unicode_start_cmap12.ttf",
277             "out_of_unicode_end_cmap12.ttf",
278             "too_large_start_cmap12.ttf",
279             "too_large_end_cmap12.ttf",
280         };
281         for (final String file : INVALID_CMAP_FONTS) {
282             final Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), file);
283             assertNotNull(typeface);
284         }
285     }
286 
287     @Test
testInvalidCmapFont_unsortedEntries()288     public void testInvalidCmapFont_unsortedEntries() {
289         // Following two font files have glyph for U+0400 and U+0100 but the fonts must not be used
290         // due to invalid cmap data. For more details, see each ttx source file.
291         final String[] INVALID_CMAP_FONTS = { "unsorted_cmap4.ttf", "unsorted_cmap12.ttf" };
292         for (final String file : INVALID_CMAP_FONTS) {
293             final Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), file);
294             assertNotNull(typeface);
295             final String testString = "\u0100\u0400";
296             final float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
297             final float widthCustomTypeface = measureText(testString, typeface);
298             assertEquals(widthDefaultTypeface, widthCustomTypeface, 0.0f);
299         }
300 
301         // Following two font files have glyph for U+0400 U+FE00 and U+0100 U+FE00 but the fonts
302         // must not be used due to invalid cmap data. For more details, see each ttx source file.
303         final String[] INVALID_CMAP_VS_FONTS = {
304             "unsorted_cmap14_default_uvs.ttf",
305             "unsorted_cmap14_non_default_uvs.ttf"
306         };
307         for (final String file : INVALID_CMAP_VS_FONTS) {
308             final Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), file);
309             assertNotNull(typeface);
310             final String testString = "\u0100\uFE00\u0400\uFE00";
311             final float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
312             final float widthCustomTypeface = measureText(testString, typeface);
313             assertEquals(widthDefaultTypeface, widthCustomTypeface, 0.0f);
314         }
315     }
316 
317     @Test
testCreateFromAsset_cachesTypeface()318     public void testCreateFromAsset_cachesTypeface() {
319         Typeface typeface1 = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
320         assertNotNull(typeface1);
321 
322         Typeface typeface2 = Typeface.createFromAsset(mContext.getAssets(), "samplefont.ttf");
323         assertNotNull(typeface2);
324         assertSame("Same font asset should return same Typeface object", typeface1, typeface2);
325 
326         Typeface typeface3 = Typeface.createFromAsset(mContext.getAssets(), "samplefont2.ttf");
327         assertNotNull(typeface3);
328         assertNotSame("Different font asset should return different Typeface object",
329                 typeface2, typeface3);
330 
331         Typeface typeface4 = Typeface.createFromAsset(mContext.getAssets(), "samplefont3.ttf");
332         assertNotNull(typeface4);
333         assertNotSame("Different font asset should return different Typeface object",
334                 typeface2, typeface4);
335         assertNotSame("Different font asset should return different Typeface object",
336                 typeface3, typeface4);
337     }
338 
339     @Test
testBadFont()340     public void testBadFont() {
341         Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), "ft45987.ttf");
342         assertNotNull(typeface);
343     }
344 
345     @Test
testTypefaceBuilder_AssetSource()346     public void testTypefaceBuilder_AssetSource() {
347         Typeface typeface1 = new Typeface.Builder(mContext.getAssets(), "samplefont.ttf").build();
348         assertNotNull(typeface1);
349 
350         Typeface typeface2 = new Typeface.Builder(mContext.getAssets(), "samplefont.ttf").build();
351         assertNotNull(typeface2);
352         assertSame("Same font asset should return same Typeface object", typeface1, typeface2);
353 
354         Typeface typeface3 = new Typeface.Builder(mContext.getAssets(), "samplefont2.ttf").build();
355         assertNotNull(typeface3);
356         assertNotSame("Different font asset should return different Typeface object",
357                 typeface2, typeface3);
358 
359         Typeface typeface4 = new Typeface.Builder(mContext.getAssets(), "samplefont3.ttf").build();
360         assertNotNull(typeface4);
361         assertNotSame("Different font asset should return different Typeface object",
362                 typeface2, typeface4);
363         assertNotSame("Different font asset should return different Typeface object",
364                 typeface3, typeface4);
365 
366         Typeface typeface5 = new Typeface.Builder(mContext.getAssets(), "samplefont.ttf")
367                 .setFontVariationSettings("'wdth' 1.0").build();
368         assertNotNull(typeface5);
369         assertNotSame("Different font font variation should return different Typeface object",
370                 typeface2, typeface5);
371 
372         Typeface typeface6 = new Typeface.Builder(mContext.getAssets(), "samplefont.ttf")
373                 .setFontVariationSettings("'wdth' 2.0").build();
374         assertNotNull(typeface6);
375         assertNotSame("Different font font variation should return different Typeface object",
376                 typeface2, typeface6);
377         assertNotSame("Different font font variation should return different Typeface object",
378                 typeface5, typeface6);
379 
380         // TODO: Add ttc index case. Need TTC file for CTS. (b/36731640)
381     }
382 
383     @Test
testTypefaceBuilder_FileSource()384     public void testTypefaceBuilder_FileSource() {
385         try {
386             File file = new File(obtainPath());
387             Typeface typeface1 = new Typeface.Builder(obtainPath()).build();
388             assertNotNull(typeface1);
389 
390             Typeface typeface2 = new Typeface.Builder(file).build();
391             assertNotNull(typeface2);
392 
393             Typeface typeface3 = new Typeface.Builder(file)
394                     .setFontVariationSettings("'wdth' 1.0")
395                     .build();
396             assertNotNull(typeface3);
397             assertNotSame(typeface1, typeface3);
398             assertNotSame(typeface2, typeface3);
399 
400             // TODO: Add ttc index case. Need TTC file for CTS.
401         } catch (IOException e) {
402             throw new RuntimeException(e);
403         }
404     }
405 
406     @Test
testTypefaceBuilder_fallback()407     public void testTypefaceBuilder_fallback() throws IOException {
408         final File validFile = new File(obtainPath());
409         final File invalidFile = new File("/some/invalid/path/to/font/file");
410         final AssetManager assets = mContext.getAssets();
411         // By default, returns null if no fallback font is specified.
412         assertNull(new Typeface.Builder(invalidFile).build());
413 
414         assertNull(new Typeface.Builder(validFile)
415                 .setTtcIndex(100 /* non-existing ttc index */).build());
416 
417         assertNull(new Typeface.Builder(assets, "invalid path").build());
418 
419         assertNull(new Typeface.Builder(assets, "samplefont.ttf")
420                 .setTtcIndex(100 /* non-existing ttc index */).build());
421 
422         // If fallback is set, the builder never returns null.
423         assertNotNull(new Typeface.Builder(invalidFile).setFallback("").build());
424 
425         assertNotNull(new Typeface.Builder(invalidFile).setFallback("invalid name").build());
426 
427         Typeface sansSerifTypeface = new Typeface.Builder(invalidFile)
428                 .setFallback("sans-serif").build();
429         assertNotNull(sansSerifTypeface);
430 
431         Typeface serifTypeface = new Typeface.Builder(invalidFile).setFallback("serif").build();
432         assertNotNull(serifTypeface);
433 
434         Typeface boldSansSerifTypeface = new Typeface.Builder(invalidFile)
435                 .setFallback("sans-serif").setWeight(700).build();
436         assertNotNull(boldSansSerifTypeface);
437 
438         Typeface boldSerifTypeface = new Typeface.Builder(invalidFile)
439                 .setFallback("serif").setWeight(700).build();
440         assertNotNull(boldSerifTypeface);
441 
442         Typeface italicSansSerifTypeface = new Typeface.Builder(invalidFile)
443                 .setFallback("sans-serif").setItalic(true).build();
444         assertNotNull(italicSansSerifTypeface);
445 
446         Typeface italicSerifTypeface = new Typeface.Builder(invalidFile)
447                 .setFallback("serif").setItalic(true).build();
448         assertNotNull(italicSerifTypeface);
449 
450         // All fallbacks should be different each other.
451         assertNotSame(sansSerifTypeface, serifTypeface);
452         assertNotSame(sansSerifTypeface, boldSansSerifTypeface);
453         assertNotSame(sansSerifTypeface, boldSerifTypeface);
454         assertNotSame(sansSerifTypeface, italicSansSerifTypeface);
455         assertNotSame(sansSerifTypeface, italicSerifTypeface);
456         assertNotSame(serifTypeface, boldSansSerifTypeface);
457         assertNotSame(serifTypeface, boldSerifTypeface);
458         assertNotSame(serifTypeface, italicSansSerifTypeface);
459         assertNotSame(serifTypeface, italicSerifTypeface);
460         assertNotSame(boldSansSerifTypeface, boldSerifTypeface);
461         assertNotSame(boldSansSerifTypeface, italicSansSerifTypeface);
462         assertNotSame(boldSansSerifTypeface, italicSerifTypeface);
463         assertNotSame(boldSerifTypeface, italicSansSerifTypeface);
464         assertNotSame(boldSerifTypeface, italicSerifTypeface);
465         assertNotSame(italicSansSerifTypeface, italicSerifTypeface);
466 
467         // Cache should work for the same fallback.
468         assertSame(sansSerifTypeface,
469                 new Typeface.Builder(assets, "samplefont.ttf").setFallback("sans-serif")
470                         .setTtcIndex(100 /* non-existing ttc index */).build());
471         assertSame(serifTypeface,
472                 new Typeface.Builder(assets, "samplefont.ttf").setFallback("serif")
473                         .setTtcIndex(100 /* non-existing ttc index */).build());
474         assertSame(boldSansSerifTypeface,
475                 new Typeface.Builder(assets, "samplefont.ttf").setFallback("sans-serif")
476                         .setTtcIndex(100 /* non-existing ttc index */).setWeight(700).build());
477         assertSame(boldSerifTypeface,
478                 new Typeface.Builder(assets, "samplefont.ttf").setFallback("serif")
479                         .setTtcIndex(100 /* non-existing ttc index */).setWeight(700).build());
480         assertSame(italicSansSerifTypeface,
481                 new Typeface.Builder(assets, "samplefont.ttf").setFallback("sans-serif")
482                         .setTtcIndex(100 /* non-existing ttc index */).setItalic(true).build());
483         assertSame(italicSerifTypeface,
484                 new Typeface.Builder(assets, "samplefont.ttf").setFallback("serif")
485                         .setTtcIndex(100 /* non-existing ttc index */).setItalic(true).build());
486     }
487 
488     @Test
testTypefaceBuilder_FileSourceFD()489     public void testTypefaceBuilder_FileSourceFD() {
490         try (FileInputStream fis = new FileInputStream(obtainPath())) {
491             assertNotNull(new Typeface.Builder(fis.getFD()).build());
492         } catch (IOException e) {
493             throw new RuntimeException(e);
494         }
495     }
496 
497     @Test
testTypeface_SupportedCmapEncodingTest()498     public void testTypeface_SupportedCmapEncodingTest() {
499         // We support the following combinations of cmap platfrom/endcoding pairs.
500         String[] fontPaths = {
501             "CmapPlatform0Encoding0.ttf",  // Platform ID == 0, Encoding ID == 0
502             "CmapPlatform0Encoding1.ttf",  // Platform ID == 0, Encoding ID == 1
503             "CmapPlatform0Encoding2.ttf",  // Platform ID == 0, Encoding ID == 2
504             "CmapPlatform0Encoding3.ttf",  // Platform ID == 0, Encoding ID == 3
505             "CmapPlatform0Encoding4.ttf",  // Platform ID == 0, Encoding ID == 4
506             "CmapPlatform0Encoding6.ttf",  // Platform ID == 0, Encoding ID == 6
507             "CmapPlatform3Encoding1.ttf",  // Platform ID == 3, Encoding ID == 1
508             "CmapPlatform3Encoding10.ttf",  // Platform ID == 3, Encoding ID == 10
509         };
510 
511         for (String fontPath : fontPaths) {
512             Typeface typeface = Typeface.createFromAsset(mContext.getAssets(), fontPath);
513             assertNotNull(typeface);
514             final String testString = "a";
515             float widthDefaultTypeface = measureText(testString, Typeface.DEFAULT);
516             float widthCustomTypeface = measureText(testString, typeface);
517             // The width of the glyph "a" from above fonts are 2em.
518             // So the width should be different from the default one.
519             assertNotEquals(widthDefaultTypeface, widthCustomTypeface, 1.0f);
520         }
521     }
522 
523     @Test
testTypefaceBuilder_customFallback()524     public void testTypefaceBuilder_customFallback() {
525         final String fontPath = "samplefont2.ttf";
526         final Typeface regularTypeface = new Typeface.Builder(mContext.getAssets(), fontPath)
527                 .setWeight(400).build();
528         final Typeface blackTypeface = new Typeface.Builder(mContext.getAssets(), fontPath)
529                 .setWeight(900).build();
530 
531         // W is not supported by samplefont2.ttf
532         final String testString = "WWWWWWWWWWWWWWWWWWWWW";
533 
534         final Paint p = new Paint();
535         p.setTextLocale(Locale.US);
536         p.setTextSize(128);
537 
538         p.setTypeface(regularTypeface);
539         final float widthFromRegular = p.measureText(testString);
540 
541         p.setTypeface(blackTypeface);
542         final float widthFromBlack = p.measureText(testString);
543 
544         assertNotEquals(widthFromRegular, widthFromBlack, 1.0f);
545     }
546 
547     @Test
testTypefaceCreate_withExactWeight()548     public void testTypefaceCreate_withExactWeight() {
549         // multiweight_family has following fonts.
550         // - a3em.ttf with weight = 100, style=normal configuration.
551         //   This font supports "a", "b", "c". The weight "a" is 3em, others are 1em.
552         // - b3em.ttf with weight = 400, style=normal configuration.
553         //   This font supports "a", "b", "c". The weight "b" is 3em, others are 1em.
554         // - c3em.ttf with weight = 700, style=normal configuration.
555         //   This font supports "a", "b", "c". The weight "c" is 3em, others are 1em.
556         final Typeface family = mContext.getResources().getFont(R.font.multiweight_family);
557         assertNotNull(family);
558 
559         // By default, the font which weight is 400 is selected.
560         assertEquals(GLYPH_1EM_WIDTH, measureText("a", family), 0f);
561         assertEquals(GLYPH_3EM_WIDTH, measureText("b", family), 0f);
562         assertEquals(GLYPH_1EM_WIDTH, measureText("c", family), 0f);
563 
564         // Draw with the font which weight is 100.
565         final Typeface thinFamily = Typeface.create(family, 100 /* weight */, false /* italic */);
566         assertEquals(GLYPH_3EM_WIDTH, measureText("a", thinFamily), 0f);
567         assertEquals(GLYPH_1EM_WIDTH, measureText("b", thinFamily), 0f);
568         assertEquals(GLYPH_1EM_WIDTH, measureText("c", thinFamily), 0f);
569 
570         // Draw with the font which weight is 700.
571         final Typeface boldFamily = Typeface.create(family, 700 /* weight */, false /* italic */);
572         assertEquals(GLYPH_1EM_WIDTH, measureText("a", boldFamily), 0f);
573         assertEquals(GLYPH_1EM_WIDTH, measureText("b", boldFamily), 0f);
574         assertEquals(GLYPH_3EM_WIDTH, measureText("c", boldFamily), 0f);
575     }
576 
577     @Test
testTypefaceCreate_withExactStyle()578     public void testTypefaceCreate_withExactStyle() {
579         // multiweight_family has following fonts.
580         // - a3em.ttf with weight = 400, style=normal configuration.
581         //   This font supports "a", "b", "c". The weight "a" is 3em, others are 1em.
582         // - b3em.ttf with weight = 400, style=italic configuration.
583         //   This font supports "a", "b", "c". The weight "b" is 3em, others are 1em.
584         // - c3em.ttf with weight = 700, style=italic configuration.
585         //   This font supports "a", "b", "c". The weight "c" is 3em, others are 1em.
586         final Typeface family = mContext.getResources().getFont(R.font.multistyle_family);
587         assertNotNull(family);
588 
589         // By default, the normal style font which weight is 400 is selected.
590         assertEquals(GLYPH_3EM_WIDTH, measureText("a", family), 0f);
591         assertEquals(GLYPH_1EM_WIDTH, measureText("b", family), 0f);
592         assertEquals(GLYPH_1EM_WIDTH, measureText("c", family), 0f);
593 
594         // Draw with the italic font.
595         final Typeface italicFamily = Typeface.create(family, 400 /* weight */, true /* italic */);
596         assertEquals(GLYPH_1EM_WIDTH, measureText("a", italicFamily), 0f);
597         assertEquals(GLYPH_3EM_WIDTH, measureText("b", italicFamily), 0f);
598         assertEquals(GLYPH_1EM_WIDTH, measureText("c", italicFamily), 0f);
599 
600         // Draw with the italic font which weigth is 700.
601         final Typeface boldItalicFamily =
602                 Typeface.create(family, 700 /* weight */, true /* italic */);
603         assertEquals(GLYPH_1EM_WIDTH, measureText("a", boldItalicFamily), 0f);
604         assertEquals(GLYPH_1EM_WIDTH, measureText("b", boldItalicFamily), 0f);
605         assertEquals(GLYPH_3EM_WIDTH, measureText("c", boldItalicFamily), 0f);
606     }
607 
608     @Test
testFontVariationSettings()609     public void testFontVariationSettings() {
610         // WeightEqualsEmVariableFont is a special font generating the outlines a glyph of 1/1000
611         // width of the given wght axis. For example, if 300 is given as the wght value to the font,
612         // the font will generate 0.3em of the glyph for the 'a'..'z' characters.
613         // The minimum, default, maximum value of 'wght' is 0, 0, 1000.
614         // No other axes are supported.
615 
616         final AssetManager am = mContext.getAssets();
617         final Paint paint = new Paint();
618         paint.setTextSize(100);  // Make 1em = 100px
619 
620         // By default, WeightEqualsEmVariableFont has 0 'wght' value.
621         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf").build());
622         assertEquals(0.0f, paint.measureText("a"), 0.0f);
623 
624         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
625                 .setFontVariationSettings("'wght' 100").build());
626         assertEquals(10.0f, paint.measureText("a"), 0.0f);
627 
628         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
629                 .setFontVariationSettings("'wght' 300").build());
630         assertEquals(30.0f, paint.measureText("a"), 0.0f);
631 
632         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
633                 .setFontVariationSettings("'wght' 800").build());
634         assertEquals(80.0f, paint.measureText("a"), 0.0f);
635 
636         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
637                 .setFontVariationSettings("'wght' 550").build());
638         assertEquals(55.0f, paint.measureText("a"), 0.0f);
639     }
640 
641     @Test
testFontVariationSettings_UnsupportedAxes()642     public void testFontVariationSettings_UnsupportedAxes() {
643         // WeightEqualsEmVariableFont is a special font generating the outlines a glyph of 1/1000
644         // width of the given wght axis. For example, if 300 is given as the wght value to the font,
645         // the font will generate 0.3em of the glyph for the 'a'..'z' characters.
646         // The minimum, default, maximum value of 'wght' is 0, 0, 1000.
647         // No other axes are supported.
648 
649         final AssetManager am = mContext.getAssets();
650         final Paint paint = new Paint();
651         paint.setTextSize(100);  // Make 1em = 100px
652 
653         // Unsupported axes do not affect the result.
654         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
655                 .setFontVariationSettings("'wght' 300, 'wdth' 10").build());
656         assertEquals(30.0f, paint.measureText("a"), 0.0f);
657 
658         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
659                 .setFontVariationSettings("'wdth' 10, 'wght' 300").build());
660         assertEquals(30.0f, paint.measureText("a"), 0.0f);
661     }
662 
663     @Test
testFontVariationSettings_OutOfRangeValue()664     public void testFontVariationSettings_OutOfRangeValue() {
665         // WeightEqualsEmVariableFont is a special font generating the outlines a glyph of 1/1000
666         // width of the given wght axis. For example, if 300 is given as the wght value to the font,
667         // the font will generate 0.3em of the glyph for the 'a'..'z' characters.
668         // The minimum, default, maximum value of 'wght' is 0, 0, 1000.
669         // No other axes are supported.
670 
671         final AssetManager am = mContext.getAssets();
672         final Paint paint = new Paint();
673         paint.setTextSize(100);  // Make 1em = 100px
674 
675         // Out of range value needs to be clipped at the minimum or maximum values.
676         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
677                 .setFontVariationSettings("'wght' -100").build());
678         assertEquals(0.0f, paint.measureText("a"), 0.0f);
679 
680         paint.setTypeface(new Typeface.Builder(am, "WeightEqualsEmVariableFont.ttf")
681                 .setFontVariationSettings("'wght' 1300").build());
682         assertEquals(100.0f, paint.measureText("a"), 0.0f);
683     }
684 
685     @Test
testTypefaceCreate_getWeight()686     public void testTypefaceCreate_getWeight() {
687         Typeface typeface = Typeface.DEFAULT;
688         assertEquals(400, typeface.getWeight());
689         assertFalse(typeface.isItalic());
690 
691         typeface = Typeface.create(Typeface.DEFAULT, 100, false /* italic */);
692         assertEquals(100, typeface.getWeight());
693         assertFalse(typeface.isItalic());
694 
695         typeface = Typeface.create(Typeface.DEFAULT, 100, true /* italic */);
696         assertEquals(100, typeface.getWeight());
697         assertTrue(typeface.isItalic());
698 
699         typeface = Typeface.create(Typeface.DEFAULT, 400, false /* italic */);
700         assertEquals(400, typeface.getWeight());
701         assertFalse(typeface.isItalic());
702 
703         typeface = Typeface.create(Typeface.DEFAULT, 400, true /* italic */);
704         assertEquals(400, typeface.getWeight());
705         assertTrue(typeface.isItalic());
706 
707         typeface = Typeface.create(Typeface.DEFAULT, 700, false /* italic */);
708         assertEquals(700, typeface.getWeight());
709         assertFalse(typeface.isItalic());
710 
711         typeface = Typeface.create(Typeface.DEFAULT, 700, true /* italic */);
712         assertEquals(700, typeface.getWeight());
713         assertTrue(typeface.isItalic());
714 
715         // Non-standard weight.
716         typeface = Typeface.create(Typeface.DEFAULT, 250, false /* italic */);
717         assertEquals(250, typeface.getWeight());
718         assertFalse(typeface.isItalic());
719 
720         typeface = Typeface.create(Typeface.DEFAULT, 250, true /* italic */);
721         assertEquals(250, typeface.getWeight());
722         assertTrue(typeface.isItalic());
723 
724     }
725 
726     @Test
testTypefaceCreate_customFont_getWeight()727     public void testTypefaceCreate_customFont_getWeight() {
728         final AssetManager am = mContext.getAssets();
729 
730         Typeface typeface = new Builder(am, "ascii_a3em_weight100_upright.ttf").build();
731         assertEquals(100, typeface.getWeight());
732         assertFalse(typeface.isItalic());
733 
734         typeface = new Builder(am, "ascii_b3em_weight100_italic.ttf").build();
735         assertEquals(100, typeface.getWeight());
736         assertTrue(typeface.isItalic());
737 
738     }
739 
740     @Test
testTypefaceCreate_customFont_customWeight()741     public void testTypefaceCreate_customFont_customWeight() {
742         final AssetManager am = mContext.getAssets();
743         Typeface typeface = new Builder(am, "ascii_a3em_weight100_upright.ttf")
744                 .setWeight(400).build();
745         assertEquals(400, typeface.getWeight());
746         assertFalse(typeface.isItalic());
747 
748         typeface = new Builder(am, "ascii_b3em_weight100_italic.ttf").setWeight(400).build();
749         assertEquals(400, typeface.getWeight());
750         assertTrue(typeface.isItalic());
751     }
752 
753     @Test
testTypefaceCreate_customFont_customItalic()754     public void testTypefaceCreate_customFont_customItalic() {
755         final AssetManager am = mContext.getAssets();
756 
757         Typeface typeface = new Builder(am, "ascii_a3em_weight100_upright.ttf")
758                 .setItalic(true).build();
759         assertEquals(100, typeface.getWeight());
760         assertTrue(typeface.isItalic());
761 
762         typeface = new Builder(am, "ascii_b3em_weight100_italic.ttf").setItalic(false).build();
763         assertEquals(100, typeface.getWeight());
764         assertFalse(typeface.isItalic());
765     }
766 }
767