| 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
|
|
|