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