script-astra/Android/Sdk/sources/android-35/android/graphics/fonts/SystemFonts.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

524 lines
21 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 static android.text.FontConfig.Customization.LocaleFallback.OPERATION_APPEND;
import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_PREPEND;
import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_REPLACE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.FontListParser;
import android.graphics.Typeface;
import android.os.LocaleList;
import android.text.FontConfig;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
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.Collections;
import java.util.List;
import java.util.Locale;
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 FONTS_XML = "/system/etc/font_fallback.xml";
private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml";
/** @hide */
public static final String SYSTEM_FONT_DIR = "/system/fonts/";
private static final String OEM_XML = "/product/etc/fonts_customization.xml";
/** @hide */
public static final String OEM_FONT_DIR = "/product/fonts/";
private SystemFonts() {} // Do not instansiate.
private static final Object LOCK = new Object();
private static @GuardedBy("sLock") Set<Font> sAvailableFonts;
/**
* Returns all available font files in the system.
*
* @return a set of system fonts
*/
public static @NonNull Set<Font> getAvailableFonts() {
synchronized (LOCK) {
if (sAvailableFonts == null) {
sAvailableFonts = Font.getAvailableFonts();
}
return sAvailableFonts;
}
}
/**
* @hide
*/
public static void resetAvailableFonts() {
synchronized (LOCK) {
sAvailableFonts = null;
}
}
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) {
return null;
}
}
/** @hide */
@VisibleForTesting
public static @FontFamily.Builder.VariableFontFamilyType int resolveVarFamilyType(
@NonNull FontConfig.FontFamily xmlFamily,
@Nullable String familyName) {
int wghtCount = 0;
int italCount = 0;
int targetFonts = 0;
boolean hasItalicFont = false;
List<FontConfig.Font> fonts = xmlFamily.getFontList();
for (int i = 0; i < fonts.size(); ++i) {
FontConfig.Font font = fonts.get(i);
if (familyName == null) { // for default family
if (font.getFontFamilyName() != null) {
continue; // this font is not for the default family.
}
} else { // for the specific family
if (!familyName.equals(font.getFontFamilyName())) {
continue; // this font is not for given family.
}
}
final int varTypeAxes = font.getVarTypeAxes();
if (varTypeAxes == 0) {
// If we see static font, we can immediately return as VAR_TYPE_NONE.
return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
}
if ((varTypeAxes & FontConfig.Font.VAR_TYPE_AXES_WGHT) != 0) {
wghtCount++;
}
if ((varTypeAxes & FontConfig.Font.VAR_TYPE_AXES_ITAL) != 0) {
italCount++;
}
if (font.getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC) {
hasItalicFont = true;
}
targetFonts++;
}
if (italCount == 0) { // No ital font.
if (targetFonts == 1 && wghtCount == 1) {
// If there is only single font that has wght, use it for regular style and
// use synthetic bolding for italic.
return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
} else if (targetFonts == 2 && wghtCount == 2 && hasItalicFont) {
// If there are two fonts and italic font is available, use them for regular and
// italic separately. (It is impossible to have two italic fonts. It will end up
// with Typeface creation failure.)
return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
}
} else if (italCount == 1) {
// If ital font is included, a single font should support both wght and ital.
if (wghtCount == 1 && targetFonts == 1) {
return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
}
}
// Otherwise, unsupported.
return FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
}
private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily,
@NonNull ArrayMap<String, NativeFamilyListSet> fallbackMap,
@NonNull Map<String, ByteBuffer> cache) {
final String languageTags = xmlFamily.getLocaleList().toLanguageTags();
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.getFontFamilyName();
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(
defaultFonts, languageTags, variant, resolveVarFamilyType(xmlFamily, null), false,
cache);
// Insert family into fallback map.
for (int i = 0; i < fallbackMap.size(); i++) {
final String name = fallbackMap.keyAt(i);
final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i);
int identityHash = System.identityHashCode(xmlFamily);
if (familyListSet.seenXmlFamilies.get(identityHash, -1) != -1) {
continue;
} else {
familyListSet.seenXmlFamilies.append(identityHash, 1);
}
final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(name);
if (fallback == null) {
if (defaultFamily != null) {
familyListSet.familyList.add(defaultFamily);
}
} else {
final FontFamily family = createFontFamily(fallback, languageTags, variant,
resolveVarFamilyType(xmlFamily, name), false, cache);
if (family != null) {
familyListSet.familyList.add(family);
} else if (defaultFamily != null) {
familyListSet.familyList.add(defaultFamily);
} else {
// There is no valid for default fallback. Ignore.
}
}
}
}
private static @Nullable FontFamily createFontFamily(
@NonNull List<FontConfig.Font> fonts,
@NonNull String languageTags,
@FontConfig.FontFamily.Variant int variant,
int varFamilyType,
boolean isDefaultFallback,
@NonNull Map<String, ByteBuffer> cache) {
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.getFile().getAbsolutePath();
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.getStyle().getWeight())
.setSlant(fontConfig.getStyle().getSlant())
.setTtcIndex(fontConfig.getTtcIndex())
.setFontVariationSettings(fontConfig.getFontVariationSettings())
.build();
} catch (IOException e) {
throw new RuntimeException(e); // Never reaches here
}
if (b == null) {
b = new FontFamily.Builder(font);
} else {
b.addFont(font);
}
}
return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */,
isDefaultFallback, varFamilyType);
}
private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList,
@NonNull ArrayMap<String, ByteBuffer> bufferCache,
@NonNull ArrayMap<String, NativeFamilyListSet> fallbackListMap) {
final String familyName = namedFamilyList.getName();
final NativeFamilyListSet familyListSet = new NativeFamilyListSet();
final List<FontConfig.FontFamily> xmlFamilies = namedFamilyList.getFamilies();
for (int i = 0; i < xmlFamilies.size(); ++i) {
FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
final FontFamily family = createFontFamily(
xmlFamily.getFontList(),
xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
resolveVarFamilyType(xmlFamily,
null /* all fonts under named family should be treated as default */),
true, // named family is always default
bufferCache);
if (family == null) {
return;
}
familyListSet.familyList.add(family);
familyListSet.seenXmlFamilies.append(System.identityHashCode(xmlFamily), 1);
}
fallbackListMap.put(familyName, familyListSet);
}
/**
* Get the updated FontConfig.
*
* @param updatableFontMap a font mapping of updated font files.
* @hide
*/
public static @NonNull FontConfig getSystemFontConfig(
@Nullable Map<String, File> updatableFontMap,
long lastModifiedDate,
int configVersion
) {
final String fontsXml;
if (com.android.text.flags.Flags.newFontsFallbackXml()) {
fontsXml = FONTS_XML;
} else {
fontsXml = LEGACY_FONTS_XML;
}
return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
updatableFontMap, lastModifiedDate, configVersion);
}
/**
* Get the updated FontConfig.
*
* @param updatableFontMap a font mapping of updated font files.
* @hide
*/
public static @NonNull FontConfig getSystemFontConfigForTesting(
@NonNull String fontsXml,
@Nullable Map<String, File> updatableFontMap,
long lastModifiedDate,
int configVersion
) {
return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
updatableFontMap, lastModifiedDate, configVersion);
}
/**
* Get the system preinstalled FontConfig.
* @hide
*/
public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
final String fontsXml;
if (com.android.text.flags.Flags.newFontsFallbackXml()) {
fontsXml = FONTS_XML;
} else {
fontsXml = LEGACY_FONTS_XML;
}
return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
0, 0);
}
/**
* @hide
*/
public static @NonNull FontConfig getSystemPreinstalledFontConfigFromLegacyXml() {
return getSystemFontConfigInternal(LEGACY_FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
null, 0, 0);
}
/* package */ static @NonNull FontConfig getSystemFontConfigInternal(
@NonNull String fontsXml,
@NonNull String systemFontDir,
@Nullable String oemXml,
@Nullable String productFontDir,
@Nullable Map<String, File> updatableFontMap,
long lastModifiedDate,
int configVersion
) {
try {
Log.i(TAG, "Loading font config from " + fontsXml);
return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir,
updatableFontMap, lastModifiedDate, configVersion);
} catch (IOException e) {
Log.e(TAG, "Failed to open/read system font configurations.", e);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
Collections.emptyList(), Collections.emptyList(), 0, 0);
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse the system font configuration.", e);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
Collections.emptyList(), Collections.emptyList(), 0, 0);
}
}
/**
* Build the system fallback from FontConfig.
* @hide
*/
@VisibleForTesting
public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig) {
return buildSystemFallback(fontConfig, new ArrayMap<>());
}
private static final class NativeFamilyListSet {
public List<FontFamily> familyList = new ArrayList<>();
public SparseIntArray seenXmlFamilies = new SparseIntArray();
}
/** @hide */
@VisibleForTesting
public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig,
ArrayMap<String, ByteBuffer> outBufferCache) {
final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>();
final List<FontConfig.Customization.LocaleFallback> localeFallbacks =
fontConfig.getLocaleFallbackCustomizations();
final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists();
for (int i = 0; i < namedFamilies.size(); ++i) {
FontConfig.NamedFamilyList namedFamilyList = namedFamilies.get(i);
appendNamedFamilyList(namedFamilyList, outBufferCache, fallbackListMap);
}
// Then, add fallback fonts to the fallback map.
final List<FontConfig.Customization.LocaleFallback> customizations = new ArrayList<>();
final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
final SparseIntArray seenCustomization = new SparseIntArray();
for (int i = 0; i < xmlFamilies.size(); i++) {
final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
customizations.clear();
for (int j = 0; j < localeFallbacks.size(); ++j) {
if (seenCustomization.get(j, -1) != -1) {
continue; // The customization is already applied.
}
FontConfig.Customization.LocaleFallback localeFallback = localeFallbacks.get(j);
if (scriptMatch(xmlFamily.getLocaleList(), localeFallback.getScript())) {
customizations.add(localeFallback);
seenCustomization.put(j, 1);
}
}
if (customizations.isEmpty()) {
pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
} else {
for (int j = 0; j < customizations.size(); ++j) {
FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
if (localeFallback.getOperation() == OPERATION_PREPEND) {
pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
outBufferCache);
}
}
boolean isReplaced = false;
for (int j = 0; j < customizations.size(); ++j) {
FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
if (localeFallback.getOperation() == OPERATION_REPLACE) {
pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
outBufferCache);
isReplaced = true;
}
}
if (!isReplaced) { // If nothing is replaced, push the original one.
pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
}
for (int j = 0; j < customizations.size(); ++j) {
FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
if (localeFallback.getOperation() == OPERATION_APPEND) {
pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
outBufferCache);
}
}
}
}
// Build the font map and fallback map.
final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>();
for (int i = 0; i < fallbackListMap.size(); i++) {
final String fallbackName = fallbackListMap.keyAt(i);
final List<FontFamily> familyList = fallbackListMap.valueAt(i).familyList;
fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0]));
}
return fallbackMap;
}
/**
* Build the system Typeface mappings from FontConfig and FallbackMap.
* @hide
*/
@VisibleForTesting
public static Map<String, Typeface> buildSystemTypefaces(
FontConfig fontConfig,
Map<String, FontFamily[]> fallbackMap) {
final ArrayMap<String, Typeface> result = new ArrayMap<>();
Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
return result;
}
private static boolean scriptMatch(LocaleList localeList, String targetScript) {
if (localeList == null || localeList.isEmpty()) {
return false;
}
for (int i = 0; i < localeList.size(); ++i) {
Locale locale = localeList.get(i);
if (locale == null) {
continue;
}
String baseScript = FontConfig.resolveScript(locale);
if (baseScript.equals(targetScript)) {
return true;
}
// Subtag match
if (targetScript.equals("Bopo") && baseScript.equals("Hanb")) {
// Hanb is Han with Bopomofo.
return true;
} else if (targetScript.equals("Hani")) {
if (baseScript.equals("Hanb") || baseScript.equals("Hans")
|| baseScript.equals("Hant") || baseScript.equals("Kore")
|| baseScript.equals("Jpan")) {
// Han id suppoted by Taiwanese, Traditional Chinese, Simplified Chinese, Korean
// and Japanese.
return true;
}
} else if (targetScript.equals("Hira") || targetScript.equals("Hrkt")
|| targetScript.equals("Kana")) {
if (baseScript.equals("Jpan") || baseScript.equals("Hrkt")) {
// Hiragana, Hiragana-Katakana, Katakana is supported by Japanese and
// Hiragana-Katakana script.
return true;
}
}
}
return false;
}
}