#include "ui/gfx/font_fallback_linux.h"
#include <fontconfig/fontconfig.h>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include "base/compiler_specific.h"
#include "base/containers/lru_cache.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/trace_event/trace_event.h"
#include "skia/ext/font_utils.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) {
if (!IsFontScalable(pattern))
return false;
std::string format = GetFontFormat(pattern);
if (format != kFontFormatTrueType && format != kFontFormatCFF)
return false;
base::FilePath font_path = GetFontPath(pattern);
if (font_path.empty() || access(font_path.AsUTF8Unsafe().c_str(), R_OK))
return false;
return true;
}
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_;
};
sk_sp<SkTypeface> GetSkTypefaceFromPathAndIndex(const base::FilePath& font_path,
int ttc_index) {
using TypefaceCache = std::map<TypefaceCacheKey, sk_sp<SkTypeface>>;
static base::NoDestructor<TypefaceCache> 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<SkTypeface>(entry->second);
sk_sp<SkFontMgr> font_mgr = skia::DefaultFontMgr();
std::string filename = font_path.AsUTF8Unsafe();
sk_sp<SkTypeface> typeface =
font_mgr->makeFromFile(filename.c_str(), ttc_index);
(*cache)[key] = typeface;
return sk_sp<SkTypeface>(typeface);
}
class FallbackFontKey {
public:
FallbackFontKey(std::string locale, Font font)
: locale_(locale), font_(font) {}
FallbackFontKey(const FallbackFontKey&) = default;
FallbackFontKey& operator=(const FallbackFontKey&) = delete;
~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_;
};
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& operator=(const FallbackFontEntry&) = delete;
~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_; }
bool HasGlyphForCharacter(UChar32 c) const {
return FcCharSetHasChar(charset_, static_cast<FcChar32>(c));
}
private:
base::FilePath font_path_;
int ttc_index_;
FontRenderParams font_params_;
raw_ptr<FcCharSet> charset_;
};
using FallbackFontEntries = std::vector<FallbackFontEntry>;
using FallbackFontEntriesCache =
base::LRUCache<FallbackFontKey, FallbackFontEntries>;
FallbackFontEntriesCache* GetFallbackFontEntriesCacheInstance() {
constexpr int kFallbackFontCacheSize = 256;
static base::NoDestructor<FallbackFontEntriesCache> cache(
kFallbackFontCacheSize);
return cache.get();
}
using FallbackFontList = std::vector<Font>;
using FallbackFontListCache = base::LRUCache<std::string, FallbackFontList>;
FallbackFontListCache* GetFallbackFontListCacheInstance() {
constexpr int kFallbackCacheSize = 64;
static base::NoDestructor<FallbackFontListCache> fallback_cache(
kFallbackCacheSize);
return fallback_cache.get();
}
}
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,
std::u16string_view text,
Font* result) {
TRACE_EVENT0("fonts", "gfx::GetFallbackFont");
if (text.empty())
return false;
FallbackFontEntriesCache* cache = GetFallbackFontEntriesCacheInstance();
FallbackFontKey key(locale, font);
FallbackFontEntriesCache::iterator cache_entry = cache->Get(key);
if (cache_entry == cache->end()) {
ScopedFcPattern pattern(FcPatternCreate());
std::string font_family = font.GetFontName();
FcPatternAddString(pattern.get(), FC_FAMILY,
reinterpret_cast<const FcChar8*>(font_family.c_str()));
FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue);
FcPatternAddString(pattern.get(), FC_LANG,
reinterpret_cast<const FcChar8*>(locale.c_str()));
if ((font.GetStyle() & gfx::Font::ITALIC) != 0)
FcPatternAddInteger(pattern.get(), FC_SLANT, FC_SLANT_ITALIC);
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) {
for (int i = 0; i < fonts->nfont; ++i) {
FcPattern* current_font = UNSAFE_TODO(fonts->fonts[i]);
if (!IsValidFontFromPattern(current_font))
continue;
base::FilePath font_path = GetFontPath(current_font);
int font_ttc_index = GetFontTtcIndex(current_font);
FcCharSet* char_set = nullptr;
fc_result = FcPatternGetCharSet(current_font, FC_CHARSET, 0, &char_set);
if (fc_result != FcResultMatch || char_set == nullptr)
continue;
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));
}
size_t fewest_missing_glyphs = text.length() + 1;
const FallbackFontEntry* prefered_entry = nullptr;
for (const auto& entry : cache_entry->second) {
size_t missing_glyphs = 0;
size_t matching_glyphs = 0;
size_t i = 0;
while (i < text.length()) {
UChar32 c = 0;
UNSAFE_TODO(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;
}
if (missing_glyphs == 0)
break;
}
if (!prefered_entry)
return false;
sk_sp<SkTypeface> typeface = GetSkTypefaceFromPathAndIndex(
prefered_entry->font_path(), prefered_entry->ttc_index());
if (!typeface)
return false;
Font fallback_font(PlatformFont::CreateFromSkTypeface(
typeface, font.GetFontSize(), prefered_entry->font_params()));
*result = fallback_font;
return true;
}
std::vector<Font> GetFallbackFonts(const Font& font) {
TRACE_EVENT0("fonts", "gfx::GetFallbackFonts");
std::string font_family = font.GetFontName();
FallbackFontListCache* font_cache = GetFallbackFontListCacheInstance();
auto cached_fallback_fonts = font_cache->Get(font_family);
if (cached_fallback_fonts != font_cache->end()) {
return cached_fallback_fonts->second;
}
FallbackFontList fallback_fonts;
FcPattern* pattern = FcPatternCreate();
FcPatternAddString(pattern, FC_FAMILY,
reinterpret_cast<const FcChar8*>(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<std::string> fallback_names;
for (int i = 0; i < fonts->nfont; ++i) {
std::string name_str = GetFontName(UNSAFE_TODO(fonts->fonts[i]));
if (name_str.empty())
continue;
if (fallback_names.insert(name_str).second)
fallback_fonts.push_back(Font(name_str, 13));
}
FcFontSetDestroy(fonts);
}
}
FcPatternDestroy(pattern);
font_cache->Put(font_family, fallback_fonts);
return fallback_fonts;
}
namespace {
class CachedFont {
public:
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_;
raw_ptr<FcCharSet> supported_characters_;
};
class CachedFontSet {
public:
static std::unique_ptr<CachedFontSet> CreateForLocale(
const std::string& locale) {
FcFontSet* font_set = CreateFcFontSetForLocale(locale);
return base::WrapUnique(new CachedFontSet(font_set));
}
CachedFontSet(const CachedFontSet&) = delete;
CachedFontSet& operator=(const CachedFontSet&) = delete;
~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()) {
FcPatternAddString(pattern, FC_LANG,
reinterpret_cast<const FcChar8*>(locale.c_str()));
}
FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
FcConfigSubstitute(0, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
if (locale.empty())
FcPatternDel(pattern, FC_LANG);
FcResult result;
FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result);
FcPatternDestroy(pattern);
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 = UNSAFE_TODO(font_set_->fonts[i]);
if (!IsValidFontFromPattern(pattern))
continue;
FcCharSet* char_set;
if (FcPatternGetCharSet(pattern, FC_CHARSET, 0, &char_set) !=
FcResultMatch)
continue;
fallback_list_.emplace_back(pattern, char_set);
}
}
raw_ptr<FcFontSet> font_set_;
std::vector<CachedFont> fallback_list_;
};
typedef std::map<std::string, std::unique_ptr<CachedFontSet>> FontSetCache;
FontSetCache& GetFontSetsByLocale() {
static base::NoDestructor<FontSetCache> font_sets_by_locale;
return *font_sets_by_locale;
}
}
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 = GetFontSetsByLocale()[locale];
if (!cached_font_set)
cached_font_set = CachedFontSet::CreateForLocale(locale);
return cached_font_set->GetFallbackFontForChar(c, fallback_font);
}
}