Index: headless/public/util/fontconfig.cc |
diff --git a/headless/public/util/fontconfig.cc b/headless/public/util/fontconfig.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d859c19063f45d3daa05952e8b161fb2e5a62b2c |
--- /dev/null |
+++ b/headless/public/util/fontconfig.cc |
@@ -0,0 +1,119 @@ |
+// Copyright 2017 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 "headless/public/util/fontconfig.h" |
+ |
+// Should be included before freetype.h. |
+#include <ft2build.h> |
+ |
+#include <dirent.h> |
+#include <fontconfig/fontconfig.h> |
+#include <freetype/freetype.h> |
+#include <set> |
+#include <string> |
+ |
+#include "base/logging.h" |
+ |
+namespace headless { |
+namespace { |
+void GetFontFileNames(const FcFontSet* font_set, |
+ std::set<std::string>* file_names) { |
+ if (font_set == NULL) |
+ return; |
+ for (int i = 0; i < font_set->nfont; ++i) { |
+ FcPattern* pattern = font_set->fonts[i]; |
+ FcValue font_file; |
+ if (FcPatternGet(pattern, "file", 0, &font_file) == FcResultMatch) { |
+ file_names->insert(reinterpret_cast<const char*>(font_file.u.s)); |
+ } else { |
+ VLOG(1) << "Failed to find filename."; |
+ FcPatternPrint(pattern); |
+ } |
+ } |
+} |
+FcConfig* g_config = nullptr; |
+} // namespace |
+ |
+void InitFonts(const char* fontconfig_path) { |
+ // The following is roughly equivalent to calling FcInit(). We jump through |
+ // a bunch of hoops here to avoid using fontconfig's directory scanning |
+ // logic. The problem with fontconfig is that it follows symlinks when doing |
+ // recursive directory scans. |
+ // |
+ // The approach below ignores any <dir>...</dir> entries in fonts.conf. This |
+ // is deliberate. Specifying dirs is problematic because they're either |
+ // absolute or relative to our process's current working directory which |
+ // could be anything. Instead we assume that all font files will be in the |
+ // same directory as fonts.conf. We'll scan + load them here. |
+ FcConfig* config = FcConfigCreate(); |
+ g_config = config; |
+ CHECK(config); |
+ |
+ // FcConfigParseAndLoad is a seriously goofy function. Depending on whether |
+ // name passed in begins with a slash, it will treat it either as a file name |
+ // to be found in the directory where it expects to find the font |
+ // configuration OR it will will treat it as a directory where it expects to |
+ // find fonts.conf. The latter behavior is the one we want. Passing |
+ // fontconfig_path via the environment is a quick and dirty way to get |
+ // uniform behavior regardless whether it's a relative path or not. |
+ setenv("FONTCONFIG_PATH", fontconfig_path, 1); |
+ CHECK(FcConfigParseAndLoad(config, nullptr, FcTrue)) |
+ << "Failed to load font configuration. FONTCONFIG_PATH=" |
+ << fontconfig_path; |
+ |
+ DIR* fc_dir = opendir(fontconfig_path); |
+ CHECK(fc_dir) << "Failed to open font directory " << fontconfig_path << ": " |
+ << strerror(errno); |
+ |
+ // The fonts must be loaded in a consistent order. This makes rendered results |
+ // stable across runs, otherwise replacement font picks are random |
+ // and cause flakiness. |
+ std::set<std::string> fonts; |
+ struct dirent entry, *result; |
+ while (readdir_r(fc_dir, &entry, &result) == 0 && result != NULL) { |
+ fonts.insert(result->d_name); |
+ } |
+ for (const std::string& font : fonts) { |
+ const std::string full_path = fontconfig_path + ("/" + font); |
+ struct stat statbuf; |
+ CHECK_EQ(0, stat(full_path.c_str(), &statbuf)) |
+ << "Failed to stat " << full_path << ": " << strerror(errno); |
+ if (S_ISREG(statbuf.st_mode)) { |
+ // FcConfigAppFontAddFile will silently ignore non-fonts. |
+ FcConfigAppFontAddFile( |
+ config, reinterpret_cast<const FcChar8*>(full_path.c_str())); |
+ } |
+ } |
+ closedir(fc_dir); |
+ CHECK(FcConfigSetCurrent(config)); |
+ |
+ // Retrieve font from both of fontconfig's font sets for pre-loading. |
+ std::set<std::string> font_files; |
+ GetFontFileNames(FcConfigGetFonts(NULL, FcSetSystem), &font_files); |
+ GetFontFileNames(FcConfigGetFonts(NULL, FcSetApplication), &font_files); |
+ CHECK_GT(font_files.size(), 0u) |
+ << "Font configuration doesn't contain any fonts!"; |
+ |
+ // Get freetype to load every font file we know about. This will cause the |
+ // font files to get cached in memory. Once that's done we shouldn't have to |
+ // access the file system for fonts at all. |
+ FT_Library library; |
+ FT_Init_FreeType(&library); |
+ for (std::set<std::string>::const_iterator iter = font_files.begin(); |
+ iter != font_files.end(); ++iter) { |
+ FT_Face face; |
+ CHECK_EQ(0, FT_New_Face(library, iter->c_str(), 0, &face)) |
+ << "Failed to load font face: " << *iter; |
+ FT_Done_Face(face); |
+ } |
+ FT_Done_FreeType(library); // Cached stuff will stick around... ? |
+} |
+ |
+void ReleaseFonts() { |
+ CHECK(g_config); |
+ FcConfigDestroy(g_config); |
+ FcFini(); |
+} |
+ |
+} // namespace headless |