OLD | NEW |
(Empty) | |
| 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 { |
| 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 |
| 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 entry, *result; |
| 74 while (readdir_r(fc_dir, &entry, &result) == 0 && result != NULL) { |
| 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 |
| 113 void ReleaseFonts() { |
| 114 CHECK(g_config); |
| 115 FcConfigDestroy(g_config); |
| 116 FcFini(); |
| 117 } |
| 118 |
| 119 } // namespace headless |
OLD | NEW |