// 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 #include #include #include #include #include #include "base/logging.h" namespace headless { namespace { void GetFontFileNames(const FcFontSet* font_set, std::set* 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(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 ... 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 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(full_path.c_str())); } } closedir(fc_dir); CHECK(FcConfigSetCurrent(config)); // Retrieve font from both of fontconfig's font sets for pre-loading. std::set 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::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