// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/gfx/font_fallback_linux.h" #include #include #include #include #include "base/containers/mru_cache.h" #include "base/files/file_path.h" #include "base/lazy_instance.h" #include "base/memory/ptr_util.h" #include "base/no_destructor.h" #include "base/trace_event/trace_event.h" #include "third_party/icu/source/common/unicode/uchar.h" #include "third_party/icu/source/common/unicode/utf16.h" #include "third_party/skia/include/core/SkFontMgr.h" #include "ui/gfx/font.h" #include "ui/gfx/font_fallback.h" #include "ui/gfx/linux/fontconfig_util.h" #include "ui/gfx/platform_font.h" namespace gfx { namespace { const char kFontFormatTrueType[] = "TrueType"; const char kFontFormatCFF[] = "CFF"; bool IsValidFontFromPattern(FcPattern* pattern) { // Ignore any bitmap fonts users may still have installed from last // century. if (!IsFontScalable(pattern)) return false; // Take only supported font formats on board. std::string format = GetFontFormat(pattern); if (format != kFontFormatTrueType && format != kFontFormatCFF) return false; // Ignore any fonts FontConfig knows about, but that we don't have // permission to read. base::FilePath font_path = GetFontPath(pattern); if (font_path.empty() || access(font_path.AsUTF8Unsafe().c_str(), R_OK)) return false; return true; } // This class uniquely identified a typeface. A typeface can be identified by // its file path and it's ttc index. class TypefaceCacheKey { public: TypefaceCacheKey(const base::FilePath& font_path, int ttc_index) : font_path_(font_path), ttc_index_(ttc_index) {} TypefaceCacheKey(const TypefaceCacheKey&) = default; TypefaceCacheKey& operator=(const TypefaceCacheKey&) = default; const base::FilePath& font_path() const { return font_path_; } int ttc_index() const { return ttc_index_; } bool operator<(const TypefaceCacheKey& other) const { return std::tie(ttc_index_, font_path_) < std::tie(other.ttc_index_, other.font_path_); } private: base::FilePath font_path_; int ttc_index_; }; // Returns a SkTypeface for a given font path and ttc_index. The typeface is // cached to avoid reloading the font from file. SkTypeface is not caching // these requests. sk_sp GetSkTypefaceFromPathAndIndex(const base::FilePath& font_path, int ttc_index) { using TypefaceCache = std::map>; static base::NoDestructor typeface_cache; if (font_path.empty()) return nullptr; TypefaceCache* cache = typeface_cache.get(); TypefaceCacheKey key(font_path, ttc_index); TypefaceCache::iterator entry = cache->find(key); if (entry != cache->end()) return sk_sp(entry->second); sk_sp font_mgr = SkFontMgr::RefDefault(); std::string filename = font_path.AsUTF8Unsafe(); sk_sp typeface = font_mgr->makeFromFile(filename.c_str(), ttc_index); (*cache)[key] = typeface; return sk_sp(typeface); } // Implements a fallback font cache over FontConfig API. // // A MRU cache is kept from a font to its potential fallback fonts. // The key (e.g. FallbackFontEntry) contains the font for which // fallback font must be returned. // // For each key, the cache is keeping a set (e.g. FallbackFontEntries) of // potential fallback font (e.g. FallbackFontEntry). Each fallback font entry // contains the supported codepoints (e.g. charset). The fallback font returned // by GetFallbackFont(...) depends on the input text and is using the charset // to determine the best candidate. class FallbackFontKey { public: FallbackFontKey(std::string locale, Font font) : locale_(locale), font_(font) {} FallbackFontKey(const FallbackFontKey&) = default; ~FallbackFontKey() = default; bool operator<(const FallbackFontKey& other) const { if (font_.GetFontSize() != other.font_.GetFontSize()) return font_.GetFontSize() < other.font_.GetFontSize(); if (font_.GetStyle() != other.font_.GetStyle()) return font_.GetStyle() < other.font_.GetStyle(); if (font_.GetFontName() != other.font_.GetFontName()) return font_.GetFontName() < other.font_.GetFontName(); return locale_ < other.locale_; } private: std::string locale_; Font font_; DISALLOW_ASSIGN(FallbackFontKey); }; class FallbackFontEntry { public: FallbackFontEntry(const base::FilePath& font_path, int ttc_index, FontRenderParams font_params, FcCharSet* charset) : font_path_(font_path), ttc_index_(ttc_index), font_params_(font_params), charset_(FcCharSetCopy(charset)) {} FallbackFontEntry(const FallbackFontEntry& other) : font_path_(other.font_path_), ttc_index_(other.ttc_index_), font_params_(other.font_params_), charset_(FcCharSetCopy(other.charset_)) {} ~FallbackFontEntry() { FcCharSetDestroy(charset_); } const base::FilePath& font_path() const { return font_path_; } int ttc_index() const { return ttc_index_; } FontRenderParams font_params() const { return font_params_; } // Returns whether the fallback font support the codepoint. bool HasGlyphForCharacter(UChar32 c) const { return FcCharSetHasChar(charset_, static_cast(c)); } private: // Font identity fields. base::FilePath font_path_; int ttc_index_; // Font rendering parameters. FontRenderParams font_params_; // Font code points coverage. FcCharSet* charset_; DISALLOW_ASSIGN(FallbackFontEntry); }; using FallbackFontEntries = std::vector; using FallbackFontEntriesCache = base::MRUCache; // The fallback font cache is a mapping from a font to the potential fallback // fonts with their codepoint coverage. FallbackFontEntriesCache* GetFallbackFontEntriesCacheInstance() { constexpr int kFallbackFontCacheSize = 256; static base::NoDestructor cache( kFallbackFontCacheSize); return cache.get(); } // The fallback fonts cache is a mapping from a font family name to its // potential fallback fonts. using FallbackFontList = std::vector; using FallbackFontListCache = base::MRUCache; FallbackFontListCache* GetFallbackFontListCacheInstance() { constexpr int kFallbackCacheSize = 64; static base::NoDestructor fallback_cache( kFallbackCacheSize); return fallback_cache.get(); } } // namespace size_t GetFallbackFontEntriesCacheSizeForTesting() { return GetFallbackFontEntriesCacheInstance()->size(); } size_t GetFallbackFontListCacheSizeForTesting() { return GetFallbackFontListCacheInstance()->size(); } void ClearAllFontFallbackCachesForTesting() { GetFallbackFontEntriesCacheInstance()->Clear(); GetFallbackFontListCacheInstance()->Clear(); } bool GetFallbackFont(const Font& font, const std::string& locale, base::StringPiece16 text, Font* result) { TRACE_EVENT0("fonts", "gfx::GetFallbackFont"); // The text passed must be at least length 1. if (text.empty()) return false; FallbackFontEntriesCache* cache = GetFallbackFontEntriesCacheInstance(); FallbackFontKey key(locale, font); FallbackFontEntriesCache::iterator cache_entry = cache->Get(key); // The cache entry for this font is missing, build it. if (cache_entry == cache->end()) { ScopedFcPattern pattern(FcPatternCreate()); // Add pattern for family name. std::string font_family = font.GetFontName(); FcPatternAddString(pattern.get(), FC_FAMILY, reinterpret_cast(font_family.c_str())); // Prefer scalable font. FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue); // Add pattern for locale. FcPatternAddString(pattern.get(), FC_LANG, reinterpret_cast(locale.c_str())); // Add pattern for font style. if ((font.GetStyle() & gfx::Font::ITALIC) != 0) FcPatternAddInteger(pattern.get(), FC_SLANT, FC_SLANT_ITALIC); // Match a font fallback. FcConfig* config = GetGlobalFontConfig(); FcConfigSubstitute(config, pattern.get(), FcMatchPattern); FcDefaultSubstitute(pattern.get()); FallbackFontEntries fallback_font_entries; FcResult fc_result; FcFontSet* fonts = FcFontSort(config, pattern.get(), FcTrue, nullptr, &fc_result); if (fonts) { // Add each potential fallback font returned by font-config to the // set of fallback fonts and keep track of their codepoints coverage. for (int i = 0; i < fonts->nfont; ++i) { FcPattern* current_font = fonts->fonts[i]; if (!IsValidFontFromPattern(current_font)) continue; // Retrieve the font identity fields. base::FilePath font_path = GetFontPath(current_font); int font_ttc_index = GetFontTtcIndex(current_font); // Retrieve the charset of the current font. FcCharSet* char_set = nullptr; fc_result = FcPatternGetCharSet(current_font, FC_CHARSET, 0, &char_set); if (fc_result != FcResultMatch || char_set == nullptr) continue; // Retrieve the font render params. FontRenderParams font_params; GetFontRenderParamsFromFcPattern(current_font, &font_params); fallback_font_entries.push_back(FallbackFontEntry( font_path, font_ttc_index, font_params, char_set)); } FcFontSetDestroy(fonts); } cache_entry = cache->Put(key, std::move(fallback_font_entries)); } // Try each font in the cache to find the one with the highest coverage. size_t fewest_missing_glyphs = text.length() + 1; const FallbackFontEntry* prefered_entry = nullptr; for (const auto& entry : cache_entry->second) { // Validate that every character has a known glyph in the font. size_t missing_glyphs = 0; size_t matching_glyphs = 0; size_t i = 0; while (i < text.length()) { UChar32 c = 0; U16_NEXT(text.data(), i, text.length(), c); if (entry.HasGlyphForCharacter(c)) { ++matching_glyphs; } else { ++missing_glyphs; } } if (matching_glyphs > 0 && missing_glyphs < fewest_missing_glyphs) { fewest_missing_glyphs = missing_glyphs; prefered_entry = &entry; } // The font has coverage for the given text and is a valid fallback font. if (missing_glyphs == 0) break; } // No fonts can be used as font fallback. if (!prefered_entry) return false; sk_sp typeface = GetSkTypefaceFromPathAndIndex( prefered_entry->font_path(), prefered_entry->ttc_index()); // The file can't be parsed (e.g. corrupt). This font can't be used as a // fallback font. if (!typeface) return false; Font fallback_font(PlatformFont::CreateFromSkTypeface( typeface, font.GetFontSize(), prefered_entry->font_params())); *result = fallback_font; return true; } std::vector GetFallbackFonts(const Font& font) { TRACE_EVENT0("fonts", "gfx::GetFallbackFonts"); std::string font_family = font.GetFontName(); // Lookup in the cache for already processed family. FallbackFontListCache* font_cache = GetFallbackFontListCacheInstance(); auto cached_fallback_fonts = font_cache->Get(font_family); if (cached_fallback_fonts != font_cache->end()) { // Already in cache. return cached_fallback_fonts->second; } // Retrieve the font fallbacks for a given family name. FallbackFontList fallback_fonts; FcPattern* pattern = FcPatternCreate(); FcPatternAddString(pattern, FC_FAMILY, reinterpret_cast(font_family.c_str())); FcConfig* config = GetGlobalFontConfig(); if (FcConfigSubstitute(config, pattern, FcMatchPattern) == FcTrue) { FcDefaultSubstitute(pattern); FcResult result; FcFontSet* fonts = FcFontSort(config, pattern, FcTrue, nullptr, &result); if (fonts) { std::set fallback_names; for (int i = 0; i < fonts->nfont; ++i) { std::string name_str = GetFontName(fonts->fonts[i]); if (name_str.empty()) continue; // FontConfig returns multiple fonts with the same family name and // different configurations. Check to prevent duplicate family names. if (fallback_names.insert(name_str).second) fallback_fonts.push_back(Font(name_str, 13)); } FcFontSetDestroy(fonts); } } FcPatternDestroy(pattern); // Store the font fallbacks to the cache. font_cache->Put(font_family, fallback_fonts); return fallback_fonts; } namespace { class CachedFont { public: // Note: We pass the charset explicitly as callers // should not create CachedFont entries without knowing // that the FcPattern contains a valid charset. CachedFont(FcPattern* pattern, FcCharSet* char_set) : supported_characters_(char_set) { DCHECK(pattern); DCHECK(char_set); fallback_font_.name = GetFontName(pattern); fallback_font_.filepath = GetFontPath(pattern); fallback_font_.ttc_index = GetFontTtcIndex(pattern); fallback_font_.is_bold = IsFontBold(pattern); fallback_font_.is_italic = IsFontItalic(pattern); } const FallbackFontData& fallback_font() const { return fallback_font_; } bool HasGlyphForCharacter(UChar32 c) const { return supported_characters_ && FcCharSetHasChar(supported_characters_, c); } private: FallbackFontData fallback_font_; // supported_characters_ is owned by the parent // FcFontSet and should never be freed. FcCharSet* supported_characters_; }; class CachedFontSet { public: // CachedFontSet takes ownership of the passed FcFontSet. static std::unique_ptr CreateForLocale( const std::string& locale) { FcFontSet* font_set = CreateFcFontSetForLocale(locale); return base::WrapUnique(new CachedFontSet(font_set)); } ~CachedFontSet() { fallback_list_.clear(); FcFontSetDestroy(font_set_); } bool GetFallbackFontForChar(UChar32 c, FallbackFontData* fallback_font) { TRACE_EVENT0("fonts", "gfx::CachedFontSet::GetFallbackFontForChar"); for (const auto& cached_font : fallback_list_) { if (cached_font.HasGlyphForCharacter(c)) { *fallback_font = cached_font.fallback_font(); return true; } } return false; } private: static FcFontSet* CreateFcFontSetForLocale(const std::string& locale) { FcPattern* pattern = FcPatternCreate(); if (!locale.empty()) { // FcChar* is unsigned char* so we have to cast. FcPatternAddString(pattern, FC_LANG, reinterpret_cast(locale.c_str())); } FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); FcConfigSubstitute(0, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); if (locale.empty()) FcPatternDel(pattern, FC_LANG); // The result parameter returns if any fonts were found. // We already handle 0 fonts correctly, so we ignore the param. FcResult result; FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result); FcPatternDestroy(pattern); // The caller will take ownership of this FcFontSet. return font_set; } CachedFontSet(FcFontSet* font_set) : font_set_(font_set) { FillFallbackList(); } void FillFallbackList() { TRACE_EVENT0("fonts", "gfx::CachedFontSet::FillFallbackList"); DCHECK(fallback_list_.empty()); if (!font_set_) return; for (int i = 0; i < font_set_->nfont; ++i) { FcPattern* pattern = font_set_->fonts[i]; if (!IsValidFontFromPattern(pattern)) continue; // Make sure this font can tell us what characters it has glyphs for. FcCharSet* char_set; if (FcPatternGetCharSet(pattern, FC_CHARSET, 0, &char_set) != FcResultMatch) continue; fallback_list_.emplace_back(pattern, char_set); } } FcFontSet* font_set_; // Owned by this object. // CachedFont has a FcCharset* which points into the FcFontSet. // If the FcFontSet is ever destroyed, the fallback list // must be cleared first. std::vector fallback_list_; DISALLOW_COPY_AND_ASSIGN(CachedFontSet); }; typedef std::map> FontSetCache; base::LazyInstance::Leaky g_font_sets_by_locale = LAZY_INSTANCE_INITIALIZER; } // namespace FallbackFontData::FallbackFontData() = default; FallbackFontData::FallbackFontData(const FallbackFontData& other) = default; FallbackFontData& FallbackFontData::operator=(const FallbackFontData& other) = default; bool GetFallbackFontForChar(UChar32 c, const std::string& locale, FallbackFontData* fallback_font) { auto& cached_font_set = g_font_sets_by_locale.Get()[locale]; if (!cached_font_set) cached_font_set = CachedFontSet::CreateForLocale(locale); return cached_font_set->GetFallbackFontForChar(c, fallback_font); } } // namespace gfx