struct AndroidIcuAPI { /* The result is case folded UTF-8. This is normalized case, it isn't upper or lower case. */
SkString casefold(SkSpan<constchar> s) const { return fHasICU ? this->icuCaseFold(s) : this->cToWLower(s);
}
// bcp47 is ascii only and only does 1:1 replacements for case folding. staticvoid normalizeAsciiCase(SkSpan<char> s) {
std::transform(s.begin(), s.end(), s.begin(),
[](char c){ return (c < 'A' || 'Z' < c) ? c : c + 'a' - 'A'; });
}
// A font may have many localized family names.
sk_sp<SkTypeface::LocalizedStrings> names(typeface->createFamilyNameIterator());
SkTypeface::LocalizedString localeName; while (names->next(&localeName)) { if (localeName.fString != name) { this->addSystemTypeface(typeface, localeName.fString, false);
}
}
}
// xmlFamilies may have an entry with "sans-serif" but there may be no matches (even for // just file name, like on Xiaomi HyperOS devices). In such cases assume Roboto (if it // exists) should be used for "sans-serif" and aliases.
[&]() { if (this->onMatchAFamily("sans-serif")) { return;
} if constexpr (kSkFontMgrVerbose) { SkDebugf("SKIA: Found no sans-serif matches\n"); }
auto&& matchXmlFamily = [&xmlFamilies](std::string_view familyName) -> FontFamily* { for (const std::unique_ptr<FontFamily>& xmlFamily : xmlFamilies) { for (auto&& xmlName : xmlFamily->fNames) { if (xmlName.equals(familyName.data(), familyName.size())) { return &*xmlFamily;
}
}
} return nullptr;
};
FontFamily* xmlSansSerif = matchXmlFamily("sans-serif"); if (!xmlSansSerif) { return;
} if constexpr (kSkFontMgrVerbose) { SkDebugf("SKIA: Found xml sans-serif\n"); }
sk_sp<SkFontStyleSet_AndroidNDK> roboto = this->onMatchAFamily("Roboto"); if (!roboto) { return;
} if constexpr (kSkFontMgrVerbose) { SkDebugf("SKIA: Found Roboto\n"); }
for (auto&& xmlName : xmlSansSerif->fNames) { if constexpr (kSkFontMgrVerbose) { SkDebugf("SKIA: Alias %s\n", xmlName.c_str()); }
SkString normalizedName(fICU.casefold({xmlName.data(), xmlName.size()}));
fNameToFamilyMap.emplace_back(NameToFamily{xmlName, normalizedName, roboto.get()});
}
}();
// fFallbackStyleSets was populated by addSystemTypeface, remove all nullptr but keep order.
SkFontStyleSet_AndroidNDK** newEnd =
std::remove(fFallbackStyleSets.begin(), fFallbackStyleSets.end(), nullptr);
fFallbackStyleSets.resize_back(newEnd - fFallbackStyleSets.data()); if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: Fallback list size %d out of %d\n",
fFallbackStyleSets.size(), fNameToFamilyMap.size());
}
if (fStyleSets.empty()) { if constexpr (kSkFontMgrVerbose) { SkDebugf("SKIA: No fonts!"); }
} else { this->findDefaultStyleSet();
}
}
protected: /** Returns not how many families we have, but how many unique names *existamongthefamilies.
*/ int onCountFamilies() const override { return fNameToFamilyMap.size();
}
static TArray<SkString> GetExtraFamilyNames( const SkAFont& font, const SkTypeface& typeface, const SkSpan<const std::unique_ptr<FontFamily>> xmlFamilies)
{ // The NDK does not report aliases like 'serif', 'sans-serif`, 'monospace', etc. // If a font matches an entry in fonts.xml, add the fonts.xml family name as well.
// In Android <= 14 AFont reports the variation as specified in fonts.xml. // In Android >= 15 AFont only reports fixed axes.
variation::Storage variationStorage;
SkSpan<variation::Coordinate> variation = variation::Get(typeface, variationStorage);
variation::Sort(variation::Normalize(variation));
variation::Storage xmlVariationStorage;
TArray<SkString> extraFamilyNames; for (const std::unique_ptr<FontFamily>& xmlFamily : xmlFamilies) { if (xmlFamily->fNames.empty()) { continue;
}
for (const FontFileInfo& xmlFont : xmlFamily->fFonts) {
SkString pathName(xmlFamily->fBasePath);
pathName.append(xmlFont.fFileName); if (!pathName.equals(font.getFontFilePath())) { continue;
}
if (font.getCollectionIndex() != static_cast<size_t>(xmlFont.fIndex)) { continue;
}
SkSpan<variation::Coordinate> xmlVariation =
variation::Get(*xmlFont.fTypeface, xmlVariationStorage); if (variation.size() != xmlVariation.size()) {
SkDEBUGFAIL("Clone does not have same number of axes."); continue;
}
variation::Sort(variation::Normalize(xmlVariation)); if (!std::equal(variation.begin(), variation.end(),
xmlVariation.begin(), variation::coordinateEqual))
{ continue;
}
std::unique_ptr<SkStreamAsset>* streamPtr = streamForPath.find(filePath); if (!streamPtr) {
streamPtr = streamForPath.set(filePath, SkStream::MakeFromFile(filePath.c_str()));
} if (!*streamPtr) { if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: Font file %s cannot be opened.\n", filePath.c_str());
} return nullptr;
}
std::unique_ptr<SkStreamAsset> stream = (*streamPtr)->duplicate(); if (!stream) { if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: Font file %s could not be duplicated.\n", filePath.c_str());
} return nullptr;
}
size_t collectionIndex = font.getCollectionIndex(); if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: Making font from %s#%zu\n", filePath.c_str(), collectionIndex);
} if (!SkTFitsIn<int>(collectionIndex)) { if constexpr (kSkFontMgrVerbose) { SkDebugf("SKIA: Collection index invalid!"); } return nullptr;
}
size_t requestAxisCount = font.getAxisCount(); if (!SkTFitsIn<int>(requestAxisCount)) { if constexpr (kSkFontMgrVerbose) { SkDebugf("SKIA: Axis count unreasonable!"); } return nullptr;
}
variation::Storage requestAxisValues(requestAxisCount);
std::optional<int> requestedWidth; for (size_t i = 0; i < requestAxisCount; ++i) {
uint32_t tag = font.getAxisTag(i); float value = font.getAxisValue(i);
requestAxisValues[i] = { tag, value }; if (tag == variation::wdthTag) { // Set the width based on the requested `wdth` axis value.
requestedWidth = SkFontDescriptor::SkFontStyleWidthForWidthAxisValue(value);
}
}
SkFontArguments::VariationPosition requestedPosition = {
requestAxisValues.get(), SkTo<int>(requestAxisCount)
}; // TODO: this creates the proxy with the given stream, so always cacheFontFiles. auto proxy = fScanner->MakeFromStream(
std::move(stream),
SkFontArguments()
.setCollectionIndex(collectionIndex)
.setVariationDesignPosition(requestedPosition)); if (!proxy) { if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: Font file %s exists, but is not a valid font.\n", filePath.c_str());
} return nullptr;
}
variation::Storage variationStorage;
SkSpan<variation::Coordinate> variation = variation::Get(*proxy, variationStorage);
SkTypeface_AndroidNDK::AutoAxis autoAxis(variation);
autoAxis.remove(SkTypeface_AndroidNDK::AutoAxis(SkSpan(requestedPosition.coordinates,
requestedPosition.coordinateCount)));
SkFontStyle style = proxy->fontStyle(); int weight = SkTo<int>(font.getWeight());
SkFontStyle::Slant slant = style.slant(); if (font.isItalic()) {
slant = SkFontStyle::kItalic_Slant;
} int width = requestedWidth.value_or(style.width());
style = SkFontStyle(weight, width, slant);
// The family name(s) are not reported. // This would be very helpful for aliases, like "sans-serif", "Arial", etc.
TArray<SkString> extraFamilyNames = GetExtraFamilyNames(font, *proxy, xmlFamilies);
// HACK: For backwards compatibility NotoSansSymbols-Regular-Subsetted needs "und-Zsym". // Base Android appears to hack this into its fallback list for similar reasons. static constexpr char kNotoSansSymbols[] = "NotoSansSymbols-Regular-Subsetted"; if (postscriptName.equals(kNotoSansSymbols, std::size(kNotoSansSymbols)-1) &&
(!aLangs || aLangs[0] == '\0') &&
proxy->unicharToGlyph(0x2603) != 0)
{ if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: Hacking in und-Zsym for NotoSansSymbols-Regular-Subsetted\n");
}
aLangs = "und-Zsym";
}
// HACK: Some Android versions have a variable Roboto font named Roboto but also use // a font named RobotoStatic (which does not claim to be Roboto) for the 400 weight. // If RobotoStatic is found but does not have the name "Roboto", add it. // Fixed in U "[2nd attempt] Revive use of VF font for regular style of roboto font" // https://android.googlesource.com/platform/frameworks/base/+/89abe560d722a6f4136b7a05d80f23b269413aad static constexpr char kRobotoStatic[] = "RobotoStatic-Regular"; static constexpr char kRoboto[] = "Roboto"; if (postscriptName.equals(kRobotoStatic, std::size(kRobotoStatic)-1) &&
std::none_of(extraFamilyNames.begin(), extraFamilyNames.end(),
[](SkString& n){return n.equals(kRoboto, std::size(kRoboto)-1);}))
{ if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: Hacking in Roboto for RobotoStatic-Regular\n");
}
extraFamilyNames.push_back(SkString(kRoboto, std::size(kRoboto)-1));
}
} if (aLangs) { if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: %s ALangs %s\n", familyName.c_str(), aLangs);
} // Format: ',' or '\0' are terminators, '\0' is the final terminator. constchar* begin = aLangs; constchar* end = aLangs; while (true) { while (*end != '\0' && *end != ',') {
++end;
} const size_t size = end - begin; if (size) {
skLangs.emplace_back(SkSpan(begin, size));
} if (*end == '\0') { break;
}
++end;
begin = end;
}
} if constexpr (kSkFontMgrVerbose) { for (auto&& lang : skLangs) {
SkDebugf("SKIA: %s Lang %s Script %s Region %s\n",
familyName.c_str(),
lang.getLanguage().c_str(),
lang.getScript().c_str(),
lang.getRegion().c_str());
}
}
if constexpr (kSkFontMgrVerbose) {
SkDebugf("SKIA: New typeface %s [%d %d %d]\n", familyName.c_str(), style.weight(),
style.width(), style.slant());
}
if (face->unicharToGlyph(character) == 0) { returnfalse;
}
if constexpr (kSkFontMgrVerbose) {
SkString foundName;
face->getFamilyName(&foundName); using SkUnicharUnsigned = std::make_unsigned_t<decltype(character)>;
SkDebugf("SKIA: Found U%c%" PRIx32 " in \"%s\" lang \"%s\" " "script \"%s\" region \"%s\" scope %s step %zu.\n", "+-"[character < 0], static_cast<SkUnicharUnsigned>(std::abs(character)),
foundName.c_str(), langTag.getLanguage().c_str(), langTag.getScript().c_str(),
langTag.getRegion().c_str(), scope, *step);
} returntrue;
}
sk_sp<SkTypeface> findByCharacterLocaleFamily(
SkTypeface_AndroidNDK* familyFace, const SkFontStyle& style, const SkALanguage& langTag,
SkUnichar character) const
{
size_t step = 0; // First look at the familyFace if (familyFace && has_locale_and_character(familyFace, langTag, character, "face", &step)) { return sk_ref_sp(familyFace);
}
// Look through the styles that match in each family. for (SkFontStyleSet_AndroidNDK* styleSet : fFallbackStyleSets) {
sk_sp<SkTypeface_AndroidNDK> face(styleSet->matchAStyle(style)); if (has_locale_and_character(face.get(), langTag, character, "style", &step)) { return adjustForStyle(std::move(face), style, *fCache);
}
}
// Look through everything.
// Android by default has a setup like // /system/fonts/NotoSansSymbols-Regular-Subsetted.ttf#0 // /system/fonts/NotoSansSymbols-Regular-Subsetted2.ttf#0 // Which are both "Noto Sans Symbols" so end up in a "family" together. However, these // are not in the same family, these are two different fonts in different families and // should have been given different names. Internally this works because these are // in separate <family> tags, but the NDK API doesn't provide that information. // While Android internally depends on all fonts in a family having the same characters // mapped, this cannot be relied upon when guessing at the families by name.
for (SkFontStyleSet_AndroidNDK* styleSet : fFallbackStyleSets) { for (const sk_sp<SkTypeface_AndroidNDK>& face : styleSet->fStyles) { if (has_locale_and_character(face.get(), langTag, character, "anything", &step)) { return adjustForStyle(sk_sp(face), style, *fCache);
}
}
}
if constexpr (kSkFontMgrVerbose) { using SkUnicharUnsigned = std::make_unsigned_t<decltype(character)>;
SkDebugf("SKIA: No font had U%c%" PRIx32 "\n", "+-"[character < 0], static_cast<SkUnicharUnsigned>(std::abs(character)));
} return nullptr;
}
sk_sp<SkTypeface> onLegacyMakeTypeface(constchar name[], SkFontStyle style) const override { if (name) { // On Android, we must return nullptr when we can't find the requested // named typeface so that the system/app can provide their own recovery // mechanism. On other platforms we'd provide a typeface from the // default family instead. return sk_sp<SkTypeface>(this->onMatchFamilyStyle(name, style));
} if (fDefaultStyleSet) { return sk_sp<SkTypeface>(fDefaultStyleSet->matchStyle(style));
} return nullptr;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.37Bemerkung:
(vorverarbeitet am 2026-09-30)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.