Files
frameworks_base/graphics/java/android/graphics/fonts/SystemFonts.java
Seigo Nonaka f69e7037ba Pass custom fallback info to minikin
The custom fallback info is used for determine the priority between
custom fallback and locale fallback. The custom fallback should be
given priority over locale fallback.

Bug: 116512426
Test: minikin_tests
Test: atest TypefaceCustomFallbackBuilderTest
Change-Id: I79cc82fa990d7705ea5c40e9b0be40d062c218dc
2018-11-07 03:52:35 +00:00

320 lines
13 KiB
Java

/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics.fonts;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.FontListParser;
import android.text.FontConfig;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Provides the system font configurations.
*/
public final class SystemFonts {
private static final String TAG = "SystemFonts";
private static final String DEFAULT_FAMILY = "sans-serif";
private SystemFonts() {} // Do not instansiate.
private static final Map<String, FontFamily[]> sSystemFallbackMap;
private static final FontConfig.Alias[] sAliases;
private static final List<Font> sAvailableFonts;
/**
* Returns all available font files in the system.
*
* @return a set of system fonts
*/
public static @NonNull Set<Font> getAvailableFonts() {
HashSet<Font> set = new HashSet<>();
set.addAll(sAvailableFonts);
return set;
}
/**
* Returns fallback list for the given family name.
*
* If no fallback found for the given family name, returns fallback for the default family.
*
* @param familyName family name, e.g. "serif"
* @hide
*/
public static @NonNull FontFamily[] getSystemFallback(@Nullable String familyName) {
final FontFamily[] families = sSystemFallbackMap.get(familyName);
return families == null ? sSystemFallbackMap.get(DEFAULT_FAMILY) : families;
}
/**
* Returns raw system fallback map.
*
* This method is intended to be used only by Typeface static initializer.
* @hide
*/
public static @NonNull Map<String, FontFamily[]> getRawSystemFallbackMap() {
return sSystemFallbackMap;
}
/**
* Returns a list of aliases.
*
* This method is intended to be used only by Typeface static initializer.
* @hide
*/
public static @NonNull FontConfig.Alias[] getAliases() {
return sAliases;
}
private static @Nullable ByteBuffer mmap(@NonNull String fullPath) {
try (FileInputStream file = new FileInputStream(fullPath)) {
final FileChannel fileChannel = file.getChannel();
final long fontSize = fileChannel.size();
return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
} catch (IOException e) {
Log.e(TAG, "Error mapping font file " + fullPath);
return null;
}
}
private static void pushFamilyToFallback(@NonNull FontConfig.Family xmlFamily,
@NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
@NonNull Map<String, ByteBuffer> cache,
@NonNull ArrayList<Font> availableFonts) {
final String languageTags = xmlFamily.getLanguages();
final int variant = xmlFamily.getVariant();
final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
final ArrayMap<String, ArrayList<FontConfig.Font>> specificFallbackFonts = new ArrayMap<>();
// Collect default fallback and specific fallback fonts.
for (final FontConfig.Font font : xmlFamily.getFonts()) {
final String fallbackName = font.getFallbackFor();
if (fallbackName == null) {
defaultFonts.add(font);
} else {
ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(fallbackName);
if (fallback == null) {
fallback = new ArrayList<>();
specificFallbackFonts.put(fallbackName, fallback);
}
fallback.add(font);
}
}
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
xmlFamily.getName(), defaultFonts, languageTags, variant, cache, availableFonts);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
final ArrayList<FontConfig.Font> fallback =
specificFallbackFonts.get(fallbackMap.keyAt(i));
if (fallback == null) {
if (defaultFamily != null) {
fallbackMap.valueAt(i).add(defaultFamily);
}
} else {
final FontFamily family = createFontFamily(
xmlFamily.getName(), fallback, languageTags, variant, cache,
availableFonts);
if (family != null) {
fallbackMap.valueAt(i).add(family);
} else if (defaultFamily != null) {
fallbackMap.valueAt(i).add(defaultFamily);
} else {
// There is no valid for for default fallback. Ignore.
}
}
}
}
private static @Nullable FontFamily createFontFamily(@NonNull String familyName,
@NonNull List<FontConfig.Font> fonts,
@NonNull String languageTags,
@FontConfig.Family.Variant int variant,
@NonNull Map<String, ByteBuffer> cache,
@NonNull ArrayList<Font> availableFonts) {
if (fonts.size() == 0) {
return null;
}
FontFamily.Builder b = null;
for (int i = 0; i < fonts.size(); i++) {
final FontConfig.Font fontConfig = fonts.get(i);
final String fullPath = fontConfig.getFontName();
ByteBuffer buffer = cache.get(fullPath);
if (buffer == null) {
if (cache.containsKey(fullPath)) {
continue; // Already failed to mmap. Skip it.
}
buffer = mmap(fullPath);
cache.put(fullPath, buffer);
if (buffer == null) {
continue;
}
}
final Font font;
try {
font = new Font.Builder(buffer, new File(fullPath), languageTags)
.setWeight(fontConfig.getWeight())
.setSlant(fontConfig.isItalic() ? FontStyle.FONT_SLANT_ITALIC
: FontStyle.FONT_SLANT_UPRIGHT)
.setTtcIndex(fontConfig.getTtcIndex())
.setFontVariationSettings(fontConfig.getAxes())
.build();
} catch (IOException e) {
throw new RuntimeException(e); // Never reaches here
}
availableFonts.add(font);
if (b == null) {
b = new FontFamily.Builder(font);
} else {
b.addFont(font);
}
}
return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */);
}
private static void appendNamedFamily(@NonNull FontConfig.Family xmlFamily,
@NonNull HashMap<String, ByteBuffer> bufferCache,
@NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap,
@NonNull ArrayList<Font> availableFonts) {
final String familyName = xmlFamily.getName();
final FontFamily family = createFontFamily(
familyName, Arrays.asList(xmlFamily.getFonts()),
xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, availableFonts);
if (family == null) {
return;
}
final ArrayList<FontFamily> fallback = new ArrayList<>();
fallback.add(family);
fallbackListMap.put(familyName, fallback);
}
/**
* Build the system fallback from xml file.
*
* @param xmlPath A full path string to the fonts.xml file.
* @param fontDir A full path string to the system font directory. This must end with
* slash('/').
* @param fallbackMap An output system fallback map. Caller must pass empty map.
* @return a list of aliases
* @hide
*/
@VisibleForTesting
public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath,
@NonNull String fontDir,
@NonNull FontCustomizationParser.Result oemCustomization,
@NonNull ArrayMap<String, FontFamily[]> fallbackMap,
@NonNull ArrayList<Font> availableFonts) {
try {
final FileInputStream fontsIn = new FileInputStream(xmlPath);
final FontConfig fontConfig = FontListParser.parse(fontsIn, fontDir);
final HashMap<String, ByteBuffer> bufferCache = new HashMap<String, ByteBuffer>();
final FontConfig.Family[] xmlFamilies = fontConfig.getFamilies();
final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
// First traverse families which have a 'name' attribute to create fallback map.
for (final FontConfig.Family xmlFamily : xmlFamilies) {
final String familyName = xmlFamily.getName();
if (familyName == null) {
continue;
}
appendNamedFamily(xmlFamily, bufferCache, fallbackListMap, availableFonts);
}
for (int i = 0; i < oemCustomization.mAdditionalNamedFamilies.size(); ++i) {
appendNamedFamily(oemCustomization.mAdditionalNamedFamilies.get(i),
bufferCache, fallbackListMap, availableFonts);
}
// Then, add fallback fonts to the each fallback map.
for (int i = 0; i < xmlFamilies.length; i++) {
final FontConfig.Family xmlFamily = xmlFamilies[i];
// The first family (usually the sans-serif family) is always placed immediately
// after the primary family in the fallback.
if (i == 0 || xmlFamily.getName() == null) {
pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, availableFonts);
}
}
// Build the font map and fallback map.
for (int i = 0; i < fallbackListMap.size(); i++) {
final String fallbackName = fallbackListMap.keyAt(i);
final List<FontFamily> familyList = fallbackListMap.valueAt(i);
final FontFamily[] families = familyList.toArray(new FontFamily[familyList.size()]);
fallbackMap.put(fallbackName, families);
}
final ArrayList<FontConfig.Alias> list = new ArrayList<>();
list.addAll(Arrays.asList(fontConfig.getAliases()));
list.addAll(oemCustomization.mAdditionalAliases);
return list.toArray(new FontConfig.Alias[list.size()]);
} catch (IOException | XmlPullParserException e) {
Log.e(TAG, "Failed initialize system fallbacks.", e);
return ArrayUtils.emptyArray(FontConfig.Alias.class);
}
}
private static FontCustomizationParser.Result readFontCustomization(
@NonNull String customizeXml, @NonNull String customFontsDir) {
try (FileInputStream f = new FileInputStream(customizeXml)) {
return FontCustomizationParser.parse(f, customFontsDir);
} catch (IOException e) {
return new FontCustomizationParser.Result();
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse font customization XML", e);
return new FontCustomizationParser.Result();
}
}
static {
final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
final ArrayList<Font> availableFonts = new ArrayList<>();
final FontCustomizationParser.Result oemCustomization =
readFontCustomization("/product/etc/fonts_customization.xml", "/product/fonts/");
sAliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/",
oemCustomization, systemFallbackMap, availableFonts);
sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
sAvailableFonts = Collections.unmodifiableList(availableFonts);
}
}