1 /*
2  * Copyright (C) 2008 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.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertTrue;
22 
23 import android.content.Context;
24 import android.content.res.AssetManager;
25 import android.content.res.Resources;
26 import android.graphics.fonts.FontFamily;
27 import android.graphics.fonts.SystemFonts;
28 import android.os.SharedMemory;
29 import android.text.FontConfig;
30 import android.util.ArrayMap;
31 
32 import androidx.test.InstrumentationRegistry;
33 import androidx.test.filters.LargeTest;
34 import androidx.test.filters.MediumTest;
35 import androidx.test.filters.SmallTest;
36 import androidx.test.runner.AndroidJUnit4;
37 
38 import com.android.frameworks.coretests.R;
39 
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 
43 import java.nio.ByteOrder;
44 import java.util.HashMap;
45 import java.util.Map;
46 import java.util.Random;
47 
48 @RunWith(AndroidJUnit4.class)
49 public class TypefaceTest {
50 
51     // create array of all std faces
52     private final Typeface[] mFaces = new Typeface[] {
53         Typeface.create(Typeface.SANS_SERIF, 0),
54         Typeface.create(Typeface.SANS_SERIF, 1),
55         Typeface.create(Typeface.SERIF, 0),
56         Typeface.create(Typeface.SERIF, 1),
57         Typeface.create(Typeface.SERIF, 2),
58         Typeface.create(Typeface.SERIF, 3),
59         Typeface.create(Typeface.MONOSPACE, 0)
60     };
61 
62     private static final int[] STYLES = {
63         Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC,
64     };
65 
66     @SmallTest
67     @Test
testBasic()68     public void testBasic() throws Exception {
69         assertTrue("basic", Typeface.DEFAULT != null);
70         assertTrue("basic", Typeface.DEFAULT_BOLD != null);
71         assertTrue("basic", Typeface.SANS_SERIF != null);
72         assertTrue("basic", Typeface.SERIF != null);
73         assertTrue("basic", Typeface.MONOSPACE != null);
74     }
75 
76     @SmallTest
77     @Test
testDefaults()78     public void testDefaults() {
79         for (int style : STYLES) {
80             String msg = "style = " + style;
81             assertNotNull(msg, Typeface.defaultFromStyle(style));
82             assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
83         }
84     }
85 
86     @SmallTest
87     @Test
testUnique()88     public void testUnique() throws Exception {
89         final int n = mFaces.length;
90         for (int i = 0; i < n; i++) {
91             for (int j = i + 1; j < n; j++) {
92                 assertTrue("unique", mFaces[i] != mFaces[j]);
93             }
94         }
95     }
96 
97     @SmallTest
98     @Test
testStyles()99     public void testStyles() throws Exception {
100         assertTrue("style", mFaces[0].getStyle() == Typeface.NORMAL);
101         assertTrue("style", mFaces[1].getStyle() == Typeface.BOLD);
102         assertTrue("style", mFaces[2].getStyle() == Typeface.NORMAL);
103         assertTrue("style", mFaces[3].getStyle() == Typeface.BOLD);
104         assertTrue("style", mFaces[4].getStyle() == Typeface.ITALIC);
105         assertTrue("style", mFaces[5].getStyle() == Typeface.BOLD_ITALIC);
106         assertTrue("style", mFaces[6].getStyle() == Typeface.NORMAL);
107     }
108 
109     @MediumTest
110     @Test
testUniformY()111     public void testUniformY() throws Exception {
112         Paint p = new Paint();
113         final int n = mFaces.length;
114         for (int i = 1; i <= 36; i++) {
115             p.setTextSize(i);
116             float ascent = 0;
117             float descent = 0;
118             for (int j = 0; j < n; j++) {
119                 p.setTypeface(mFaces[j]);
120                 Paint.FontMetrics fm = p.getFontMetrics();
121                 if (j == 0) {
122                     ascent = fm.ascent;
123                     descent = fm.descent;
124                 } else {
125                     assertTrue("fontMetrics", fm.ascent == ascent);
126                     assertTrue("fontMetrics", fm.descent == descent);
127                 }
128             }
129         }
130     }
131 
132     @LargeTest
133     @Test
testMultithreadCacheStressTest()134     public void testMultithreadCacheStressTest() {
135         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
136         final Resources res = context.getResources();
137         final AssetManager assets = res.getAssets();
138         final Typeface[] baseTypefaces = {
139             null,
140             Typeface.SANS_SERIF,
141             Typeface.SERIF,
142             Typeface.MONOSPACE,
143             res.getFont(R.font.samplefont),
144             res.getFont(R.font.samplefont2),
145             res.getFont(R.font.samplefont3),
146             res.getFont(R.font.samplefont4),
147             res.getFont(R.font.samplexmlfont),
148             Typeface.createFromAsset(assets, "fonts/a3em.ttf"),
149             Typeface.createFromAsset(assets, "fonts/b3em.ttf"),
150             Typeface.createFromAsset(assets, "fonts/c3em.ttf"),
151             Typeface.createFromAsset(assets, "fonts/all2em.ttf"),
152             Typeface.createFromAsset(assets, "fonts/hasGlyphTestFont.ttf"),
153             Typeface.createFromAsset(assets, "fonts/samplefont1.ttf"),
154             Typeface.createFromAsset(assets, "fonts/no_coverage.ttf"),
155         };
156 
157         final int loopCount = 10000;
158 
159         final Runnable threadedCreater = () -> {
160             final Random random = new Random();
161             for (int i = 0; i < loopCount; ++i) {
162                 final Typeface base = baseTypefaces[random.nextInt(baseTypefaces.length)];
163                 if (random.nextBoolean()) {
164                     final int style = random.nextInt(3);
165                     final Typeface result = Typeface.create(base, style);
166                     assertEquals(style, result.getStyle());
167                 } else {
168                     final int weight = 100 * (random.nextInt(10) + 1);  // [100, 1000]
169                     final boolean italic = random.nextBoolean();
170                     final Typeface result = Typeface.create(base, weight, italic);
171                     assertEquals(italic, result.isItalic());
172                     assertEquals(weight, result.getWeight());
173                 }
174             }
175         };
176 
177         final int threadCount = 4;
178         final Thread[] threads = new Thread[threadCount];
179         for (int i = 0; i < threadCount; ++i) {
180             threads[i] = new Thread(threadedCreater);
181         }
182 
183         for (int i = 0; i < threadCount; ++i) {
184             threads[i].start();
185         }
186 
187         for (int i = 0; i < threadCount; ++i) {
188             try {
189                 threads[i].join();
190             } catch (InterruptedException e) {
191                 // ignore
192             }
193         }
194 
195     }
196 
197     @SmallTest
198     @Test
testSerialize()199     public void testSerialize() throws Exception {
200         FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig();
201         Map<String, FontFamily[]> fallbackMap = SystemFonts.buildSystemFallback(fontConfig);
202         Map<String, Typeface> systemFontMap = SystemFonts.buildSystemTypefaces(fontConfig,
203                 fallbackMap);
204         SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
205         Map<String, Typeface> copiedFontMap = new ArrayMap<>();
206         try {
207             Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
208                     copiedFontMap);
209             assertEquals(systemFontMap.size(), copiedFontMap.size());
210             for (String key : systemFontMap.keySet()) {
211                 assertTrue(copiedFontMap.containsKey(key));
212                 Typeface original = systemFontMap.get(key);
213                 Typeface copied = copiedFontMap.get(key);
214                 assertEquals(original.getStyle(), copied.getStyle());
215                 assertEquals(original.getWeight(), copied.getWeight());
216                 assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
217             }
218         } finally {
219             for (Typeface typeface : copiedFontMap.values()) {
220                 typeface.releaseNativeObjectForTest();
221             }
222         }
223     }
224 
225     @SmallTest
226     @Test
testSetSystemFontMap()227     public void testSetSystemFontMap() throws Exception {
228 
229         // Typeface.setSystemFontMap mutate the returned map. So copying for the backup.
230         HashMap<String, Typeface> backup = new HashMap<>(Typeface.getSystemFontMap());
231 
232         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
233         Resources res = context.getResources();
234         Map<String, Typeface> fontMap = Map.of(
235                 "sans-serif", Typeface.create(res.getFont(R.font.samplefont), Typeface.NORMAL),
236                 "serif", Typeface.create(res.getFont(R.font.samplefont2), Typeface.NORMAL),
237                 "monospace", Typeface.create(res.getFont(R.font.samplefont3), Typeface.NORMAL),
238                 "sample", Typeface.create(res.getFont(R.font.samplefont4), Typeface.NORMAL),
239                 "sample-italic", Typeface.create(res.getFont(R.font.samplefont4), Typeface.ITALIC));
240 
241         try {
242             Typeface.setSystemFontMap(fontMap);
243 
244             // Test public static final fields
245             assertEquals(fontMap.get("sans-serif"), Typeface.DEFAULT);
246             assertEquals(Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
247             assertEquals(fontMap.get("sans-serif"), Typeface.SANS_SERIF);
248             assertEquals(fontMap.get("serif"), Typeface.SERIF);
249             assertEquals(fontMap.get("monospace"), Typeface.MONOSPACE);
250 
251             // Test defaults
252             assertEquals(fontMap.get("sans-serif"), Typeface.defaultFromStyle(Typeface.NORMAL));
253             for (int style : STYLES) {
254                 String msg = "style = " + style;
255                 assertNotNull(msg, Typeface.defaultFromStyle(style));
256                 assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
257             }
258 
259             // Test create()
260             assertEquals(fontMap.get("sample"), Typeface.create("sample", Typeface.NORMAL));
261             assertEquals(
262                     fontMap.get("sample-italic"),
263                     Typeface.create("sample-italic", Typeface.ITALIC));
264         } finally {
265             // This tests breaks many default font configuration and break the assumption of the
266             // subsequent test cases. To recover the original configuration, call the
267             // setSystemFontMap function with the original data even if it is a test target.
268             // Ideally, this test should be isolated and app should be restart after this test
269             // been executed.
270             Typeface.setSystemFontMap(backup);
271         }
272     }
273 
measureText(Typeface typeface, String text)274     private static float measureText(Typeface typeface, String text) {
275         Paint paint = new Paint();
276         paint.setTypeface(typeface);
277         return paint.measureText(text);
278     }
279 }
280