1 // Copyright 2017 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 "headless/public/util/fontconfig.h"
6 
7 // Should be included before freetype.h.
8 #include <ft2build.h>
9 
10 #include <dirent.h>
11 #include <fontconfig/fontconfig.h>
12 #include <freetype/freetype.h>
13 #include <set>
14 #include <string>
15 
16 #include "base/logging.h"
17 
18 namespace headless {
19 namespace {
GetFontFileNames(const FcFontSet * font_set,std::set<std::string> * file_names)20 void GetFontFileNames(const FcFontSet* font_set,
21                       std::set<std::string>* file_names) {
22   if (font_set == NULL)
23     return;
24   for (int i = 0; i < font_set->nfont; ++i) {
25     FcPattern* pattern = font_set->fonts[i];
26     FcValue font_file;
27     if (FcPatternGet(pattern, "file", 0, &font_file) == FcResultMatch) {
28       file_names->insert(reinterpret_cast<const char*>(font_file.u.s));
29     } else {
30       VLOG(1) << "Failed to find filename.";
31       FcPatternPrint(pattern);
32     }
33   }
34 }
35 FcConfig* g_config = nullptr;
36 }  // namespace
37 
InitFonts(const char * fontconfig_path)38 void InitFonts(const char* fontconfig_path) {
39   // The following is roughly equivalent to calling FcInit().  We jump through
40   // a bunch of hoops here to avoid using fontconfig's directory scanning
41   // logic. The problem with fontconfig is that it follows symlinks when doing
42   // recursive directory scans.
43   //
44   // The approach below ignores any <dir>...</dir> entries in fonts.conf.  This
45   // is deliberate.  Specifying dirs is problematic because they're either
46   // absolute or relative to our process's current working directory which
47   // could be anything.  Instead we assume that all font files will be in the
48   // same directory as fonts.conf.  We'll scan + load them here.
49   FcConfig* config = FcConfigCreate();
50   g_config = config;
51   CHECK(config);
52 
53   // FcConfigParseAndLoad is a seriously goofy function. Depending on whether
54   // name passed in begins with a slash, it will treat it either as a file name
55   // to be found in the directory where it expects to find the font
56   // configuration OR it will will treat it as a directory where it expects to
57   // find fonts.conf. The latter behavior is the one we want. Passing
58   // fontconfig_path via the environment is a quick and dirty way to get
59   // uniform behavior regardless whether it's a relative path or not.
60   setenv("FONTCONFIG_PATH", fontconfig_path, 1);
61   CHECK(FcConfigParseAndLoad(config, nullptr, FcTrue))
62       << "Failed to load font configuration.  FONTCONFIG_PATH="
63       << fontconfig_path;
64 
65   DIR* fc_dir = opendir(fontconfig_path);
66   CHECK(fc_dir) << "Failed to open font directory " << fontconfig_path << ": "
67                 << strerror(errno);
68 
69   // The fonts must be loaded in a consistent order. This makes rendered results
70   // stable across runs, otherwise replacement font picks are random
71   // and cause flakiness.
72   std::set<std::string> fonts;
73   struct dirent *result;
74   while ((result = readdir(fc_dir))) {
75     fonts.insert(result->d_name);
76   }
77   for (const std::string& font : fonts) {
78     const std::string full_path = fontconfig_path + ("/" + font);
79     struct stat statbuf;
80     CHECK_EQ(0, stat(full_path.c_str(), &statbuf))
81         << "Failed to stat " << full_path << ": " << strerror(errno);
82     if (S_ISREG(statbuf.st_mode)) {
83       // FcConfigAppFontAddFile will silently ignore non-fonts.
84       FcConfigAppFontAddFile(
85           config, reinterpret_cast<const FcChar8*>(full_path.c_str()));
86     }
87   }
88   closedir(fc_dir);
89   CHECK(FcConfigSetCurrent(config));
90 
91   // Retrieve font from both of fontconfig's font sets for pre-loading.
92   std::set<std::string> font_files;
93   GetFontFileNames(FcConfigGetFonts(NULL, FcSetSystem), &font_files);
94   GetFontFileNames(FcConfigGetFonts(NULL, FcSetApplication), &font_files);
95   CHECK_GT(font_files.size(), 0u)
96       << "Font configuration doesn't contain any fonts!";
97 
98   // Get freetype to load every font file we know about.  This will cause the
99   // font files to get cached in memory.  Once that's done we shouldn't have to
100   // access the file system for fonts at all.
101   FT_Library library;
102   FT_Init_FreeType(&library);
103   for (std::set<std::string>::const_iterator iter = font_files.begin();
104        iter != font_files.end(); ++iter) {
105     FT_Face face;
106     CHECK_EQ(0, FT_New_Face(library, iter->c_str(), 0, &face))
107         << "Failed to load font face: " << *iter;
108     FT_Done_Face(face);
109   }
110   FT_Done_FreeType(library);  // Cached stuff will stick around... ?
111 }
112 
ReleaseFonts()113 void ReleaseFonts() {
114   CHECK(g_config);
115   FcConfigDestroy(g_config);
116   FcFini();
117 }
118 
119 }  // namespace headless
120