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