• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.fonts;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.graphics.FontListParser;
22 import android.graphics.Typeface;
23 import android.text.FontConfig;
24 import android.util.ArrayMap;
25 import android.util.Log;
26 
27 import com.android.internal.annotations.GuardedBy;
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import org.xmlpull.v1.XmlPullParserException;
31 
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.nio.ByteBuffer;
36 import java.nio.channels.FileChannel;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 
43 /**
44  * Provides the system font configurations.
45  */
46 public final class SystemFonts {
47     private static final String TAG = "SystemFonts";
48 
49     private static final String FONTS_XML = "/system/etc/fonts.xml";
50     /** @hide */
51     public static final String SYSTEM_FONT_DIR = "/system/fonts/";
52     private static final String OEM_XML = "/product/etc/fonts_customization.xml";
53     /** @hide */
54     public static final String OEM_FONT_DIR = "/product/fonts/";
55 
SystemFonts()56     private SystemFonts() {}  // Do not instansiate.
57 
58     private static final Object LOCK = new Object();
59     private static @GuardedBy("sLock") Set<Font> sAvailableFonts;
60 
61     /**
62      * Returns all available font files in the system.
63      *
64      * @return a set of system fonts
65      */
getAvailableFonts()66     public static @NonNull Set<Font> getAvailableFonts() {
67         synchronized (LOCK) {
68             if (sAvailableFonts == null) {
69                 sAvailableFonts = Font.getAvailableFonts();
70             }
71             return sAvailableFonts;
72         }
73     }
74 
75     /**
76      * @hide
77      */
resetAvailableFonts()78     public static void resetAvailableFonts() {
79         synchronized (LOCK) {
80             sAvailableFonts = null;
81         }
82     }
83 
mmap(@onNull String fullPath)84     private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
85         try (FileInputStream file = new FileInputStream(fullPath)) {
86             final FileChannel fileChannel = file.getChannel();
87             final long fontSize = fileChannel.size();
88             return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
89         } catch (IOException e) {
90             return null;
91         }
92     }
93 
pushFamilyToFallback(@onNull FontConfig.FontFamily xmlFamily, @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap, @NonNull Map<String, ByteBuffer> cache)94     private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily,
95             @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
96             @NonNull Map<String, ByteBuffer> cache) {
97 
98         final String languageTags = xmlFamily.getLocaleList().toLanguageTags();
99         final int variant = xmlFamily.getVariant();
100 
101         final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
102         final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts =
103                 new ArrayMap<>();
104 
105         // Collect default fallback and specific fallback fonts.
106         for (final FontConfig.Font font : xmlFamily.getFonts()) {
107             final String fallbackName = font.getFontFamilyName();
108             if (fallbackName == null) {
109                 defaultFonts.add(font);
110             } else {
111                 ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
112                 if (fallback == null) {
113                     fallback = new ArrayList<>();
114                     specificFallbackFonts.put(fallbackName, fallback);
115                 }
116                 fallback.add(font);
117             }
118         }
119 
120         final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
121                 xmlFamily.getName(), defaultFonts, languageTags, variant, cache);
122 
123         // Insert family into fallback map.
124         for (int i = 0; i < fallbackMap.size(); i++) {
125             String name = fallbackMap.keyAt(i);
126             final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(name);
127             if (fallback == null) {
128                 String familyName = xmlFamily.getName();
129                 if (defaultFamily != null
130                         // do not add myself to the fallback chain.
131                         && (familyName == null || !familyName.equals(name))) {
132                     fallbackMap.valueAt(i).add(defaultFamily);
133                 }
134             } else {
135                 final FontFamily family = createFontFamily(
136                         xmlFamily.getName(), fallback, languageTags, variant, cache);
137                 if (family != null) {
138                     fallbackMap.valueAt(i).add(family);
139                 } else if (defaultFamily != null) {
140                     fallbackMap.valueAt(i).add(defaultFamily);
141                 } else {
142                     // There is no valid for for default fallback. Ignore.
143                 }
144             }
145         }
146     }
147 
createFontFamily(@onNull String familyName, @NonNull List<FontConfig.Font> fonts, @NonNull String languageTags, @FontConfig.FontFamily.Variant int variant, @NonNull Map<String, ByteBuffer> cache)148     private static @Nullable FontFamily createFontFamily(@NonNull String familyName,
149             @NonNull List<FontConfig.Font> fonts,
150             @NonNull String languageTags,
151             @FontConfig.FontFamily.Variant int variant,
152             @NonNull Map<String, ByteBuffer> cache) {
153         if (fonts.size() == 0) {
154             return null;
155         }
156 
157         FontFamily.Builder b = null;
158         for (int i = 0; i < fonts.size(); i++) {
159             final FontConfig.Font fontConfig = fonts.get(i);
160             final String fullPath = fontConfig.getFile().getAbsolutePath();
161             ByteBuffer buffer = cache.get(fullPath);
162             if (buffer == null) {
163                 if (cache.containsKey(fullPath)) {
164                     continue;  // Already failed to mmap. Skip it.
165                 }
166                 buffer = mmap(fullPath);
167                 cache.put(fullPath, buffer);
168                 if (buffer == null) {
169                     continue;
170                 }
171             }
172 
173             final Font font;
174             try {
175                 font = new Font.Builder(buffer, new File(fullPath), languageTags)
176                         .setWeight(fontConfig.getStyle().getWeight())
177                         .setSlant(fontConfig.getStyle().getSlant())
178                         .setTtcIndex(fontConfig.getTtcIndex())
179                         .setFontVariationSettings(fontConfig.getFontVariationSettings())
180                         .build();
181             } catch (IOException e) {
182                 throw new RuntimeException(e);  // Never reaches here
183             }
184 
185             if (b == null) {
186                 b = new FontFamily.Builder(font);
187             } else {
188                 b.addFont(font);
189             }
190         }
191         return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */);
192     }
193 
appendNamedFamily(@onNull FontConfig.FontFamily xmlFamily, @NonNull ArrayMap<String, ByteBuffer> bufferCache, @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap)194     private static void appendNamedFamily(@NonNull FontConfig.FontFamily xmlFamily,
195             @NonNull ArrayMap<String, ByteBuffer> bufferCache,
196             @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap) {
197         final String familyName = xmlFamily.getName();
198         final FontFamily family = createFontFamily(
199                 familyName, xmlFamily.getFontList(),
200                 xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
201                 bufferCache);
202         if (family == null) {
203             return;
204         }
205         final ArrayList<FontFamily> fallback = new ArrayList<>();
206         fallback.add(family);
207         fallbackListMap.put(familyName, fallback);
208     }
209 
210     /**
211      * Get the updated FontConfig.
212      *
213      * @param updatableFontMap a font mapping of updated font files.
214      * @hide
215      */
getSystemFontConfig( @ullable Map<String, File> updatableFontMap, long lastModifiedDate, int configVersion )216     public static @NonNull FontConfig getSystemFontConfig(
217             @Nullable Map<String, File> updatableFontMap,
218             long lastModifiedDate,
219             int configVersion
220     ) {
221         return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
222                 updatableFontMap, lastModifiedDate, configVersion);
223     }
224 
225     /**
226      * Get the system preinstalled FontConfig.
227      * @hide
228      */
getSystemPreinstalledFontConfig()229     public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
230         return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
231                 0, 0);
232     }
233 
getSystemFontConfigInternal( @onNull String fontsXml, @NonNull String systemFontDir, @Nullable String oemXml, @Nullable String productFontDir, @Nullable Map<String, File> updatableFontMap, long lastModifiedDate, int configVersion )234     /* package */ static @NonNull FontConfig getSystemFontConfigInternal(
235             @NonNull String fontsXml,
236             @NonNull String systemFontDir,
237             @Nullable String oemXml,
238             @Nullable String productFontDir,
239             @Nullable Map<String, File> updatableFontMap,
240             long lastModifiedDate,
241             int configVersion
242     ) {
243         try {
244             return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir,
245                                                 updatableFontMap, lastModifiedDate, configVersion);
246         } catch (IOException e) {
247             Log.e(TAG, "Failed to open/read system font configurations.", e);
248             return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
249         } catch (XmlPullParserException e) {
250             Log.e(TAG, "Failed to parse the system font configuration.", e);
251             return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
252         }
253     }
254 
255     /**
256      * Build the system fallback from FontConfig.
257      * @hide
258      */
259     @VisibleForTesting
buildSystemFallback(FontConfig fontConfig)260     public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig) {
261         return buildSystemFallback(fontConfig, new ArrayMap<>());
262     }
263 
264     /** @hide */
265     @VisibleForTesting
buildSystemFallback(FontConfig fontConfig, ArrayMap<String, ByteBuffer> outBufferCache)266     public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig,
267             ArrayMap<String, ByteBuffer> outBufferCache) {
268         final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>();
269         final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
270 
271         final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
272         // First traverse families which have a 'name' attribute to create fallback map.
273         for (final FontConfig.FontFamily xmlFamily : xmlFamilies) {
274             final String familyName = xmlFamily.getName();
275             if (familyName == null) {
276                 continue;
277             }
278             appendNamedFamily(xmlFamily, outBufferCache, fallbackListMap);
279         }
280 
281         // Then, add fallback fonts to the each fallback map.
282         for (int i = 0; i < xmlFamilies.size(); i++) {
283             final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
284             // The first family (usually the sans-serif family) is always placed immediately
285             // after the primary family in the fallback.
286             if (i == 0 || xmlFamily.getName() == null) {
287                 pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
288             }
289         }
290 
291         // Build the font map and fallback map.
292         for (int i = 0; i < fallbackListMap.size(); i++) {
293             final String fallbackName = fallbackListMap.keyAt(i);
294             final List<FontFamily> familyList = fallbackListMap.valueAt(i);
295             fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0]));
296         }
297 
298         return fallbackMap;
299     }
300 
301     /**
302      * Build the system Typeface mappings from FontConfig and FallbackMap.
303      * @hide
304      */
305     @VisibleForTesting
buildSystemTypefaces( FontConfig fontConfig, Map<String, FontFamily[]> fallbackMap)306     public static Map<String, Typeface> buildSystemTypefaces(
307             FontConfig fontConfig,
308             Map<String, FontFamily[]> fallbackMap) {
309         final ArrayMap<String, Typeface> result = new ArrayMap<>();
310         Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
311         return result;
312     }
313 }
314