1 // Copyright (c) 2012 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 "ui/gfx/font_fallback_win.h"
6
7 #include <algorithm>
8 #include <map>
9
10 #include "base/macros.h"
11 #include "base/memory/singleton.h"
12 #include "base/message_loop/message_loop_current.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/trace_event/trace_event.h"
19 #include "base/win/registry.h"
20 #include "ui/gfx/font.h"
21 #include "ui/gfx/font_fallback.h"
22 #include "ui/gfx/font_fallback_skia_impl.h"
23 #include "ui/gfx/platform_font.h"
24
25 namespace gfx {
26
27 namespace {
28
29 // Queries the registry to get a mapping from font filenames to font names.
QueryFontsFromRegistry(std::map<std::string,std::string> * map)30 void QueryFontsFromRegistry(std::map<std::string, std::string>* map) {
31 const wchar_t* kFonts =
32 L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
33
34 base::win::RegistryValueIterator it(HKEY_LOCAL_MACHINE, kFonts);
35 for (; it.Valid(); ++it) {
36 const std::string filename =
37 base::ToLowerASCII(base::WideToUTF8(it.Value()));
38 (*map)[filename] = base::WideToUTF8(it.Name());
39 }
40 }
41
42 // Fills |font_names| with a list of font families found in the font file at
43 // |filename|. Takes in a |font_map| from font filename to font families, which
44 // is filled-in by querying the registry, if empty.
GetFontNamesFromFilename(const std::string & filename,std::map<std::string,std::string> * font_map,std::vector<std::string> * font_names)45 void GetFontNamesFromFilename(const std::string& filename,
46 std::map<std::string, std::string>* font_map,
47 std::vector<std::string>* font_names) {
48 if (font_map->empty())
49 QueryFontsFromRegistry(font_map);
50
51 std::map<std::string, std::string>::const_iterator it =
52 font_map->find(base::ToLowerASCII(filename));
53 if (it == font_map->end())
54 return;
55
56 internal::ParseFontFamilyString(it->second, font_names);
57 }
58
59 // Returns true if |text| contains only ASCII digits.
ContainsOnlyDigits(const std::string & text)60 bool ContainsOnlyDigits(const std::string& text) {
61 return text.find_first_not_of("0123456789") == base::string16::npos;
62 }
63
64 // Appends a Font with the given |name| and |size| to |fonts| unless the last
65 // entry is already a font with that name.
AppendFont(const std::string & name,int size,std::vector<Font> * fonts)66 void AppendFont(const std::string& name, int size, std::vector<Font>* fonts) {
67 if (fonts->empty() || fonts->back().GetFontName() != name)
68 fonts->push_back(Font(name, size));
69 }
70
71 // Queries the registry to get a list of linked fonts for |font|.
QueryLinkedFontsFromRegistry(const Font & font,std::map<std::string,std::string> * font_map,std::vector<Font> * linked_fonts)72 void QueryLinkedFontsFromRegistry(const Font& font,
73 std::map<std::string, std::string>* font_map,
74 std::vector<Font>* linked_fonts) {
75 std::string logging_str;
76 const wchar_t* kSystemLink =
77 L"Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink";
78
79 base::win::RegKey key;
80 if (FAILED(key.Open(HKEY_LOCAL_MACHINE, kSystemLink, KEY_READ)))
81 return;
82
83 const std::wstring original_font_name = base::UTF8ToWide(font.GetFontName());
84 std::vector<std::wstring> values;
85 if (FAILED(key.ReadValues(original_font_name.c_str(), &values))) {
86 key.Close();
87 return;
88 }
89
90 base::StringAppendF(&logging_str, "Original font: %s\n",
91 font.GetFontName().c_str());
92
93 std::string filename;
94 std::string font_name;
95 for (size_t i = 0; i < values.size(); ++i) {
96 internal::ParseFontLinkEntry(
97 base::WideToUTF8(values[i]), &filename, &font_name);
98
99 base::StringAppendF(&logging_str, "fallback: '%s' '%s'\n",
100 font_name.c_str(), filename.c_str());
101
102 // If the font name is present, add that directly, otherwise add the
103 // font names corresponding to the filename.
104 if (!font_name.empty()) {
105 AppendFont(font_name, font.GetFontSize(), linked_fonts);
106 } else if (!filename.empty()) {
107 std::vector<std::string> filename_fonts;
108 GetFontNamesFromFilename(filename, font_map, &filename_fonts);
109 for (const std::string& filename_font : filename_fonts)
110 AppendFont(filename_font, font.GetFontSize(), linked_fonts);
111 }
112 }
113
114 key.Close();
115
116 for (const auto& resolved_font : *linked_fonts) {
117 base::StringAppendF(&logging_str, "resolved: '%s'\n",
118 resolved_font.GetFontName().c_str());
119 }
120
121 TRACE_EVENT1("fonts", "QueryLinkedFontsFromRegistry", "results", logging_str);
122 }
123
124 // CachedFontLinkSettings is a singleton cache of the Windows font settings
125 // from the registry. It maintains a cached view of the registry's list of
126 // system fonts and their font link chains.
127 class CachedFontLinkSettings {
128 public:
129 static CachedFontLinkSettings* GetInstance();
130
131 // Returns the linked fonts list correspond to |font|. Returned value will
132 // never be null.
133 const std::vector<Font>* GetLinkedFonts(const Font& font);
134
135 private:
136 friend struct base::DefaultSingletonTraits<CachedFontLinkSettings>;
137
138 CachedFontLinkSettings();
139 virtual ~CachedFontLinkSettings();
140
141 // Map of system fonts, from file names to font families.
142 std::map<std::string, std::string> cached_system_fonts_;
143
144 // Map from font names to vectors of linked fonts.
145 std::map<std::string, std::vector<Font> > cached_linked_fonts_;
146
147 DISALLOW_COPY_AND_ASSIGN(CachedFontLinkSettings);
148 };
149
150 // static
GetInstance()151 CachedFontLinkSettings* CachedFontLinkSettings::GetInstance() {
152 return base::Singleton<
153 CachedFontLinkSettings,
154 base::LeakySingletonTraits<CachedFontLinkSettings>>::get();
155 }
156
GetLinkedFonts(const Font & font)157 const std::vector<Font>* CachedFontLinkSettings::GetLinkedFonts(
158 const Font& font) {
159 SCOPED_UMA_HISTOGRAM_LONG_TIMER("FontFallback.GetLinkedFonts.Timing");
160 const std::string& font_name = font.GetFontName();
161 std::map<std::string, std::vector<Font> >::const_iterator it =
162 cached_linked_fonts_.find(font_name);
163 if (it != cached_linked_fonts_.end())
164 return &it->second;
165
166 TRACE_EVENT1("fonts", "CachedFontLinkSettings::GetLinkedFonts", "font_name",
167 font_name);
168
169 SCOPED_UMA_HISTOGRAM_LONG_TIMER(
170 "FontFallback.GetLinkedFonts.CacheMissTiming");
171 std::vector<Font>* linked_fonts = &cached_linked_fonts_[font_name];
172 QueryLinkedFontsFromRegistry(font, &cached_system_fonts_, linked_fonts);
173 UMA_HISTOGRAM_COUNTS_100("FontFallback.GetLinkedFonts.FontCount",
174 linked_fonts->size());
175 return linked_fonts;
176 }
177
CachedFontLinkSettings()178 CachedFontLinkSettings::CachedFontLinkSettings() {
179 }
180
~CachedFontLinkSettings()181 CachedFontLinkSettings::~CachedFontLinkSettings() {
182 }
183
184 } // namespace
185
186 namespace internal {
187
ParseFontLinkEntry(const std::string & entry,std::string * filename,std::string * font_name)188 void ParseFontLinkEntry(const std::string& entry,
189 std::string* filename,
190 std::string* font_name) {
191 std::vector<std::string> parts = base::SplitString(
192 entry, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
193 filename->clear();
194 font_name->clear();
195 if (parts.size() > 0)
196 *filename = parts[0];
197 // The second entry may be the font name or the first scaling factor, if the
198 // entry does not contain a font name. If it contains only digits, assume it
199 // is a scaling factor.
200 if (parts.size() > 1 && !ContainsOnlyDigits(parts[1]))
201 *font_name = parts[1];
202 }
203
ParseFontFamilyString(const std::string & family,std::vector<std::string> * font_names)204 void ParseFontFamilyString(const std::string& family,
205 std::vector<std::string>* font_names) {
206 // The entry is comma separated, having the font filename as the first value
207 // followed optionally by the font family name and a pair of integer scaling
208 // factors.
209 // TODO(asvitkine): Should we support these scaling factors?
210 *font_names = base::SplitString(family, "&", base::TRIM_WHITESPACE,
211 base::SPLIT_WANT_ALL);
212 if (!font_names->empty()) {
213 const size_t index = font_names->back().find('(');
214 if (index != std::string::npos) {
215 font_names->back().resize(index);
216 base::TrimWhitespaceASCII(font_names->back(), base::TRIM_TRAILING,
217 &font_names->back());
218 }
219 }
220 }
221
222 } // namespace internal
223
GetFallbackFonts(const Font & font)224 std::vector<Font> GetFallbackFonts(const Font& font) {
225 TRACE_EVENT0("fonts", "gfx::GetFallbackFonts");
226 std::string font_family = font.GetFontName();
227 CachedFontLinkSettings* font_link = CachedFontLinkSettings::GetInstance();
228 // GetLinkedFonts doesn't care about the font size, so we always pass 10.
229 return *font_link->GetLinkedFonts(Font(font_family, 10));
230 }
231
GetFallbackFont(const Font & font,const std::string & locale,base::StringPiece16 text,Font * result)232 bool GetFallbackFont(const Font& font,
233 const std::string& locale,
234 base::StringPiece16 text,
235 Font* result) {
236 TRACE_EVENT0("fonts", "gfx::GetFallbackFont");
237 // Creating a DirectWrite font fallback can be expensive. It's ok in the
238 // browser process because we can use the shared system fallback, but in the
239 // renderer this can cause hangs. Code that needs font fallback in the
240 // renderer should instead use the font proxy.
241 DCHECK(base::MessageLoopCurrentForUI::IsSet());
242
243 // The text passed must be at least length 1.
244 if (text.empty())
245 return false;
246
247 // Check that we have at least as much text as was claimed. If we have less
248 // text than expected then DirectWrite will become confused and crash. This
249 // shouldn't happen, but crbug.com/624905 shows that it happens sometimes.
250 constexpr base::char16 kNulCharacter = '\0';
251 if (text.find(kNulCharacter) != base::StringPiece16::npos)
252 return false;
253
254 sk_sp<SkTypeface> fallback_typeface =
255 GetSkiaFallbackTypeface(font, locale, text);
256
257 if (!fallback_typeface)
258 return false;
259
260 // Fallback needs to keep the exact SkTypeface, as re-matching the font using
261 // family name and styling information loses access to the underlying platform
262 // font handles and is not guaranteed to result in the correct typeface, see
263 // https://crbug.com/1003829
264 *result = Font(PlatformFont::CreateFromSkTypeface(
265 std::move(fallback_typeface), font.GetFontSize(), base::nullopt));
266 return true;
267 }
268
269 } // namespace gfx
270