1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/gfx/font_fallback_linux.h"
6 
7 #include <fontconfig/fontconfig.h>
8 
9 #include <map>
10 #include <memory>
11 #include <string>
12 
13 #include "base/containers/mru_cache.h"
14 #include "base/files/file_path.h"
15 #include "base/lazy_instance.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/no_destructor.h"
18 #include "base/trace_event/trace_event.h"
19 #include "third_party/icu/source/common/unicode/uchar.h"
20 #include "third_party/icu/source/common/unicode/utf16.h"
21 #include "third_party/skia/include/core/SkFontMgr.h"
22 #include "ui/gfx/font.h"
23 #include "ui/gfx/font_fallback.h"
24 #include "ui/gfx/linux/fontconfig_util.h"
25 #include "ui/gfx/platform_font.h"
26 
27 #if defined(OS_BSD)
28 #include <unistd.h>
29 #endif
30 
31 namespace gfx {
32 
33 namespace {
34 
35 const char kFontFormatTrueType[] = "TrueType";
36 const char kFontFormatCFF[] = "CFF";
37 
IsValidFontFromPattern(FcPattern * pattern)38 bool IsValidFontFromPattern(FcPattern* pattern) {
39   // Ignore any bitmap fonts users may still have installed from last
40   // century.
41   if (!IsFontScalable(pattern))
42     return false;
43 
44   // Take only supported font formats on board.
45   std::string format = GetFontFormat(pattern);
46   if (format != kFontFormatTrueType && format != kFontFormatCFF)
47     return false;
48 
49   // Ignore any fonts FontConfig knows about, but that we don't have
50   // permission to read.
51   base::FilePath font_path = GetFontPath(pattern);
52   if (font_path.empty() || access(font_path.AsUTF8Unsafe().c_str(), R_OK))
53     return false;
54 
55   return true;
56 }
57 
58 // This class uniquely identified a typeface. A typeface can be identified by
59 // its file path and it's ttc index.
60 class TypefaceCacheKey {
61  public:
TypefaceCacheKey(const base::FilePath & font_path,int ttc_index)62   TypefaceCacheKey(const base::FilePath& font_path, int ttc_index)
63       : font_path_(font_path), ttc_index_(ttc_index) {}
64 
font_path() const65   const base::FilePath& font_path() const { return font_path_; }
ttc_index() const66   int ttc_index() const { return ttc_index_; }
67 
operator <(const TypefaceCacheKey & other) const68   bool operator<(const TypefaceCacheKey& other) const {
69     return std::tie(ttc_index_, font_path_) <
70            std::tie(other.ttc_index_, other.font_path_);
71   }
72 
73  private:
74   base::FilePath font_path_;
75   int ttc_index_;
76 
77   DISALLOW_ASSIGN(TypefaceCacheKey);
78 };
79 
80 // Returns a SkTypeface for a given font path and ttc_index. The typeface is
81 // cached to avoid reloading the font from file. SkTypeface is not caching
82 // these requests.
GetSkTypefaceFromPathAndIndex(const base::FilePath & font_path,int ttc_index)83 sk_sp<SkTypeface> GetSkTypefaceFromPathAndIndex(const base::FilePath& font_path,
84                                                 int ttc_index) {
85   using TypefaceCache = std::map<TypefaceCacheKey, sk_sp<SkTypeface>>;
86   static base::NoDestructor<TypefaceCache> typeface_cache;
87 
88   if (font_path.empty())
89     return nullptr;
90 
91   TypefaceCache* cache = typeface_cache.get();
92   TypefaceCacheKey key(font_path, ttc_index);
93   TypefaceCache::iterator entry = cache->find(key);
94   if (entry != cache->end())
95     return sk_sp<SkTypeface>(entry->second);
96 
97   sk_sp<SkFontMgr> font_mgr = SkFontMgr::RefDefault();
98   std::string filename = font_path.AsUTF8Unsafe();
99   sk_sp<SkTypeface> typeface =
100       font_mgr->makeFromFile(filename.c_str(), ttc_index);
101   (*cache)[key] = typeface;
102 
103   return sk_sp<SkTypeface>(typeface);
104 }
105 
106 // Implements a fallback font cache over FontConfig API.
107 //
108 // A MRU cache is kept from a font to its potential fallback fonts.
109 // The key (e.g. FallbackFontEntry) contains the font for which
110 // fallback font must be returned.
111 //
112 // For each key, the cache is keeping a set (e.g. FallbackFontEntries) of
113 // potential fallback font (e.g. FallbackFontEntry). Each fallback font entry
114 // contains the supported codepoints (e.g. charset). The fallback font returned
115 // by GetFallbackFont(...) depends on the input text and is using the charset
116 // to determine the best candidate.
117 class FallbackFontKey {
118  public:
FallbackFontKey(std::string locale,Font font)119   FallbackFontKey(std::string locale, Font font)
120       : locale_(locale), font_(font) {}
121 
122   FallbackFontKey(const FallbackFontKey&) = default;
123   ~FallbackFontKey() = default;
124 
operator <(const FallbackFontKey & other) const125   bool operator<(const FallbackFontKey& other) const {
126     if (font_.GetFontSize() != other.font_.GetFontSize())
127       return font_.GetFontSize() < other.font_.GetFontSize();
128     if (font_.GetStyle() != other.font_.GetStyle())
129       return font_.GetStyle() < other.font_.GetStyle();
130     if (font_.GetFontName() != other.font_.GetFontName())
131       return font_.GetFontName() < other.font_.GetFontName();
132     return locale_ < other.locale_;
133   }
134 
135  private:
136   std::string locale_;
137   Font font_;
138 
139   DISALLOW_ASSIGN(FallbackFontKey);
140 };
141 
142 class FallbackFontEntry {
143  public:
FallbackFontEntry(const base::FilePath & font_path,int ttc_index,FontRenderParams font_params,FcCharSet * charset)144   FallbackFontEntry(const base::FilePath& font_path,
145                     int ttc_index,
146                     FontRenderParams font_params,
147                     FcCharSet* charset)
148       : font_path_(font_path),
149         ttc_index_(ttc_index),
150         font_params_(font_params),
151         charset_(FcCharSetCopy(charset)) {}
152 
FallbackFontEntry(const FallbackFontEntry & other)153   FallbackFontEntry(const FallbackFontEntry& other)
154       : font_path_(other.font_path_),
155         ttc_index_(other.ttc_index_),
156         font_params_(other.font_params_),
157         charset_(FcCharSetCopy(other.charset_)) {}
158 
~FallbackFontEntry()159   ~FallbackFontEntry() { FcCharSetDestroy(charset_); }
160 
font_path() const161   const base::FilePath& font_path() const { return font_path_; }
ttc_index() const162   int ttc_index() const { return ttc_index_; }
font_params() const163   FontRenderParams font_params() const { return font_params_; }
164 
165   // Returns whether the fallback font support the codepoint.
HasGlyphForCharacter(UChar32 c) const166   bool HasGlyphForCharacter(UChar32 c) const {
167     return FcCharSetHasChar(charset_, static_cast<FcChar32>(c));
168   }
169 
170  private:
171   // Font identity fields.
172   base::FilePath font_path_;
173   int ttc_index_;
174 
175   // Font rendering parameters.
176   FontRenderParams font_params_;
177 
178   // Font code points coverage.
179   FcCharSet* charset_;
180 
181   DISALLOW_ASSIGN(FallbackFontEntry);
182 };
183 
184 using FallbackFontEntries = std::vector<FallbackFontEntry>;
185 using FallbackFontEntriesCache =
186     base::MRUCache<FallbackFontKey, FallbackFontEntries>;
187 
188 // The fallback font cache is a mapping from a font to the potential fallback
189 // fonts with their codepoint coverage.
GetFallbackFontEntriesCacheInstance()190 FallbackFontEntriesCache* GetFallbackFontEntriesCacheInstance() {
191   constexpr int kFallbackFontCacheSize = 256;
192   static base::NoDestructor<FallbackFontEntriesCache> cache(
193       kFallbackFontCacheSize);
194   return cache.get();
195 }
196 
197 // The fallback fonts cache is a mapping from a font family name to its
198 // potential fallback fonts.
199 using FallbackFontList = std::vector<Font>;
200 using FallbackFontListCache = base::MRUCache<std::string, FallbackFontList>;
201 
GetFallbackFontListCacheInstance()202 FallbackFontListCache* GetFallbackFontListCacheInstance() {
203   constexpr int kFallbackCacheSize = 64;
204   static base::NoDestructor<FallbackFontListCache> fallback_cache(
205       kFallbackCacheSize);
206   return fallback_cache.get();
207 }
208 
209 }  // namespace
210 
GetFallbackFontEntriesCacheSizeForTesting()211 size_t GetFallbackFontEntriesCacheSizeForTesting() {
212   return GetFallbackFontEntriesCacheInstance()->size();
213 }
214 
GetFallbackFontListCacheSizeForTesting()215 size_t GetFallbackFontListCacheSizeForTesting() {
216   return GetFallbackFontListCacheInstance()->size();
217 }
218 
ClearAllFontFallbackCachesForTesting()219 void ClearAllFontFallbackCachesForTesting() {
220   GetFallbackFontEntriesCacheInstance()->Clear();
221   GetFallbackFontListCacheInstance()->Clear();
222 }
223 
GetFallbackFont(const Font & font,const std::string & locale,base::StringPiece16 text,Font * result)224 bool GetFallbackFont(const Font& font,
225                      const std::string& locale,
226                      base::StringPiece16 text,
227                      Font* result) {
228   TRACE_EVENT0("fonts", "gfx::GetFallbackFont");
229 
230   // The text passed must be at least length 1.
231   if (text.empty())
232     return false;
233 
234   FallbackFontEntriesCache* cache = GetFallbackFontEntriesCacheInstance();
235   FallbackFontKey key(locale, font);
236   FallbackFontEntriesCache::iterator cache_entry = cache->Get(key);
237 
238   // The cache entry for this font is missing, build it.
239   if (cache_entry == cache->end()) {
240     ScopedFcPattern pattern(FcPatternCreate());
241 
242     // Add pattern for family name.
243     std::string font_family = font.GetFontName();
244     FcPatternAddString(pattern.get(), FC_FAMILY,
245                        reinterpret_cast<const FcChar8*>(font_family.c_str()));
246 
247     // Prefer scalable font.
248     FcPatternAddBool(pattern.get(), FC_SCALABLE, FcTrue);
249 
250     // Add pattern for locale.
251     FcPatternAddString(pattern.get(), FC_LANG,
252                        reinterpret_cast<const FcChar8*>(locale.c_str()));
253 
254     // Add pattern for font style.
255     if ((font.GetStyle() & gfx::Font::ITALIC) != 0)
256       FcPatternAddInteger(pattern.get(), FC_SLANT, FC_SLANT_ITALIC);
257 
258     // Match a font fallback.
259     FcConfig* config = GetGlobalFontConfig();
260     FcConfigSubstitute(config, pattern.get(), FcMatchPattern);
261     FcDefaultSubstitute(pattern.get());
262 
263     FallbackFontEntries fallback_font_entries;
264     FcResult fc_result;
265     FcFontSet* fonts =
266         FcFontSort(config, pattern.get(), FcTrue, nullptr, &fc_result);
267     if (fonts) {
268       // Add each potential fallback font returned by font-config to the
269       // set of fallback fonts and keep track of their codepoints coverage.
270       for (int i = 0; i < fonts->nfont; ++i) {
271         FcPattern* current_font = fonts->fonts[i];
272         if (!IsValidFontFromPattern(current_font))
273           continue;
274 
275         // Retrieve the font identity fields.
276         base::FilePath font_path = GetFontPath(current_font);
277         int font_ttc_index = GetFontTtcIndex(current_font);
278 
279         // Retrieve the charset of the current font.
280         FcCharSet* char_set = nullptr;
281         fc_result = FcPatternGetCharSet(current_font, FC_CHARSET, 0, &char_set);
282         if (fc_result != FcResultMatch || char_set == nullptr)
283           continue;
284 
285         // Retrieve the font render params.
286         FontRenderParams font_params;
287         GetFontRenderParamsFromFcPattern(current_font, &font_params);
288 
289         fallback_font_entries.push_back(FallbackFontEntry(
290             font_path, font_ttc_index, font_params, char_set));
291       }
292       FcFontSetDestroy(fonts);
293     }
294 
295     cache_entry = cache->Put(key, std::move(fallback_font_entries));
296   }
297 
298   // Try each font in the cache to find the one with the highest coverage.
299   size_t fewest_missing_glyphs = text.length() + 1;
300   const FallbackFontEntry* prefered_entry = nullptr;
301 
302   for (const auto& entry : cache_entry->second) {
303     // Validate that every character has a known glyph in the font.
304     size_t missing_glyphs = 0;
305     size_t matching_glyphs = 0;
306     size_t i = 0;
307     while (i < text.length()) {
308       UChar32 c = 0;
309       U16_NEXT(text.data(), i, text.length(), c);
310       if (entry.HasGlyphForCharacter(c)) {
311         ++matching_glyphs;
312       } else {
313         ++missing_glyphs;
314       }
315     }
316 
317     if (matching_glyphs > 0 && missing_glyphs < fewest_missing_glyphs) {
318       fewest_missing_glyphs = missing_glyphs;
319       prefered_entry = &entry;
320     }
321 
322     // The font has coverage for the given text and is a valid fallback font.
323     if (missing_glyphs == 0)
324       break;
325   }
326 
327   // No fonts can be used as font fallback.
328   if (!prefered_entry)
329     return false;
330 
331   sk_sp<SkTypeface> typeface = GetSkTypefaceFromPathAndIndex(
332       prefered_entry->font_path(), prefered_entry->ttc_index());
333   // The file can't be parsed (e.g. corrupt). This font can't be used as a
334   // fallback font.
335   if (!typeface)
336     return false;
337 
338   Font fallback_font(PlatformFont::CreateFromSkTypeface(
339       typeface, font.GetFontSize(), prefered_entry->font_params()));
340 
341   *result = fallback_font;
342   return true;
343 }
344 
GetFallbackFonts(const Font & font)345 std::vector<Font> GetFallbackFonts(const Font& font) {
346   TRACE_EVENT0("fonts", "gfx::GetFallbackFonts");
347 
348   std::string font_family = font.GetFontName();
349 
350   // Lookup in the cache for already processed family.
351   FallbackFontListCache* font_cache = GetFallbackFontListCacheInstance();
352   auto cached_fallback_fonts = font_cache->Get(font_family);
353   if (cached_fallback_fonts != font_cache->end()) {
354     // Already in cache.
355     return cached_fallback_fonts->second;
356   }
357 
358   // Retrieve the font fallbacks for a given family name.
359   FallbackFontList fallback_fonts;
360   FcPattern* pattern = FcPatternCreate();
361   FcPatternAddString(pattern, FC_FAMILY,
362                      reinterpret_cast<const FcChar8*>(font_family.c_str()));
363 
364   FcConfig* config = GetGlobalFontConfig();
365   if (FcConfigSubstitute(config, pattern, FcMatchPattern) == FcTrue) {
366     FcDefaultSubstitute(pattern);
367     FcResult result;
368     FcFontSet* fonts = FcFontSort(config, pattern, FcTrue, nullptr, &result);
369     if (fonts) {
370       std::set<std::string> fallback_names;
371       for (int i = 0; i < fonts->nfont; ++i) {
372         std::string name_str = GetFontName(fonts->fonts[i]);
373         if (name_str.empty())
374           continue;
375 
376         // FontConfig returns multiple fonts with the same family name and
377         // different configurations. Check to prevent duplicate family names.
378         if (fallback_names.insert(name_str).second)
379           fallback_fonts.push_back(Font(name_str, 13));
380       }
381       FcFontSetDestroy(fonts);
382     }
383   }
384   FcPatternDestroy(pattern);
385 
386   // Store the font fallbacks to the cache.
387   font_cache->Put(font_family, fallback_fonts);
388 
389   return fallback_fonts;
390 }
391 
392 namespace {
393 
394 class CachedFont {
395  public:
396   // Note: We pass the charset explicitly as callers
397   // should not create CachedFont entries without knowing
398   // that the FcPattern contains a valid charset.
CachedFont(FcPattern * pattern,FcCharSet * char_set)399   CachedFont(FcPattern* pattern, FcCharSet* char_set)
400       : supported_characters_(char_set) {
401     DCHECK(pattern);
402     DCHECK(char_set);
403     fallback_font_.name = GetFontName(pattern);
404     fallback_font_.filepath = GetFontPath(pattern);
405     fallback_font_.ttc_index = GetFontTtcIndex(pattern);
406     fallback_font_.is_bold = IsFontBold(pattern);
407     fallback_font_.is_italic = IsFontItalic(pattern);
408   }
409 
fallback_font() const410   const FallbackFontData& fallback_font() const { return fallback_font_; }
411 
HasGlyphForCharacter(UChar32 c) const412   bool HasGlyphForCharacter(UChar32 c) const {
413     return supported_characters_ && FcCharSetHasChar(supported_characters_, c);
414   }
415 
416  private:
417   FallbackFontData fallback_font_;
418   // supported_characters_ is owned by the parent
419   // FcFontSet and should never be freed.
420   FcCharSet* supported_characters_;
421 };
422 
423 class CachedFontSet {
424  public:
425   // CachedFontSet takes ownership of the passed FcFontSet.
CreateForLocale(const std::string & locale)426   static std::unique_ptr<CachedFontSet> CreateForLocale(
427       const std::string& locale) {
428     FcFontSet* font_set = CreateFcFontSetForLocale(locale);
429     return base::WrapUnique(new CachedFontSet(font_set));
430   }
431 
~CachedFontSet()432   ~CachedFontSet() {
433     fallback_list_.clear();
434     FcFontSetDestroy(font_set_);
435   }
436 
GetFallbackFontForChar(UChar32 c,FallbackFontData * fallback_font)437   bool GetFallbackFontForChar(UChar32 c, FallbackFontData* fallback_font) {
438     TRACE_EVENT0("fonts", "gfx::CachedFontSet::GetFallbackFontForChar");
439 
440     for (const auto& cached_font : fallback_list_) {
441       if (cached_font.HasGlyphForCharacter(c)) {
442         *fallback_font = cached_font.fallback_font();
443         return true;
444       }
445     }
446     return false;
447   }
448 
449  private:
CreateFcFontSetForLocale(const std::string & locale)450   static FcFontSet* CreateFcFontSetForLocale(const std::string& locale) {
451     FcPattern* pattern = FcPatternCreate();
452 
453     if (!locale.empty()) {
454       // FcChar* is unsigned char* so we have to cast.
455       FcPatternAddString(pattern, FC_LANG,
456                          reinterpret_cast<const FcChar8*>(locale.c_str()));
457     }
458 
459     FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
460 
461     FcConfigSubstitute(0, pattern, FcMatchPattern);
462     FcDefaultSubstitute(pattern);
463 
464     if (locale.empty())
465       FcPatternDel(pattern, FC_LANG);
466 
467     // The result parameter returns if any fonts were found.
468     // We already handle 0 fonts correctly, so we ignore the param.
469     FcResult result;
470     FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result);
471     FcPatternDestroy(pattern);
472 
473     // The caller will take ownership of this FcFontSet.
474     return font_set;
475   }
476 
CachedFontSet(FcFontSet * font_set)477   CachedFontSet(FcFontSet* font_set) : font_set_(font_set) {
478     FillFallbackList();
479   }
480 
FillFallbackList()481   void FillFallbackList() {
482     TRACE_EVENT0("fonts", "gfx::CachedFontSet::FillFallbackList");
483 
484     DCHECK(fallback_list_.empty());
485     if (!font_set_)
486       return;
487 
488     for (int i = 0; i < font_set_->nfont; ++i) {
489       FcPattern* pattern = font_set_->fonts[i];
490 
491       if (!IsValidFontFromPattern(pattern))
492         continue;
493 
494       // Make sure this font can tell us what characters it has glyphs for.
495       FcCharSet* char_set;
496       if (FcPatternGetCharSet(pattern, FC_CHARSET, 0, &char_set) !=
497           FcResultMatch)
498         continue;
499 
500       fallback_list_.emplace_back(pattern, char_set);
501     }
502   }
503 
504   FcFontSet* font_set_;  // Owned by this object.
505   // CachedFont has a FcCharset* which points into the FcFontSet.
506   // If the FcFontSet is ever destroyed, the fallback list
507   // must be cleared first.
508   std::vector<CachedFont> fallback_list_;
509 
510   DISALLOW_COPY_AND_ASSIGN(CachedFontSet);
511 };
512 
513 typedef std::map<std::string, std::unique_ptr<CachedFontSet>> FontSetCache;
514 base::LazyInstance<FontSetCache>::Leaky g_font_sets_by_locale =
515     LAZY_INSTANCE_INITIALIZER;
516 
517 }  // namespace
518 
519 FallbackFontData::FallbackFontData() = default;
520 FallbackFontData::FallbackFontData(const FallbackFontData& other) = default;
521 
GetFallbackFontForChar(UChar32 c,const std::string & locale,FallbackFontData * fallback_font)522 bool GetFallbackFontForChar(UChar32 c,
523                             const std::string& locale,
524                             FallbackFontData* fallback_font) {
525   auto& cached_font_set = g_font_sets_by_locale.Get()[locale];
526   if (!cached_font_set)
527     cached_font_set = CachedFontSet::CreateForLocale(locale);
528   return cached_font_set->GetFallbackFontForChar(c, fallback_font);
529 }
530 
531 }  // namespace gfx
532