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