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 <map>
8
9#include "base/memory/singleton.h"
10#include "base/strings/string_split.h"
11#include "base/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/win/registry.h"
14#include "ui/gfx/font.h"
15
16namespace gfx {
17
18namespace {
19
20// Queries the registry to get a mapping from font filenames to font names.
21void QueryFontsFromRegistry(std::map<std::string, std::string>* map) {
22  const wchar_t* kFonts =
23      L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
24
25  base::win::RegistryValueIterator it(HKEY_LOCAL_MACHINE, kFonts);
26  for (; it.Valid(); ++it) {
27    const std::string filename =
28        StringToLowerASCII(base::WideToUTF8(it.Value()));
29    (*map)[filename] = base::WideToUTF8(it.Name());
30  }
31}
32
33// Fills |font_names| with a list of font families found in the font file at
34// |filename|. Takes in a |font_map| from font filename to font families, which
35// is filled-in by querying the registry, if empty.
36void GetFontNamesFromFilename(const std::string& filename,
37                              std::map<std::string, std::string>* font_map,
38                              std::vector<std::string>* font_names) {
39  if (font_map->empty())
40    QueryFontsFromRegistry(font_map);
41
42  std::map<std::string, std::string>::const_iterator it =
43      font_map->find(StringToLowerASCII(filename));
44  if (it == font_map->end())
45    return;
46
47  internal::ParseFontFamilyString(it->second, font_names);
48}
49
50// Returns true if |text| contains only ASCII digits.
51bool ContainsOnlyDigits(const std::string& text) {
52  return text.find_first_not_of("0123456789") == base::string16::npos;
53}
54
55// Appends a Font with the given |name| and |size| to |fonts| unless the last
56// entry is already a font with that name.
57void AppendFont(const std::string& name, int size, std::vector<Font>* fonts) {
58  if (fonts->empty() || fonts->back().GetFontName() != name)
59    fonts->push_back(Font(name, size));
60}
61
62// Queries the registry to get a list of linked fonts for |font|.
63void QueryLinkedFontsFromRegistry(const Font& font,
64                                  std::map<std::string, std::string>* font_map,
65                                  std::vector<Font>* linked_fonts) {
66  const wchar_t* kSystemLink =
67      L"Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink";
68
69  base::win::RegKey key;
70  if (FAILED(key.Open(HKEY_LOCAL_MACHINE, kSystemLink, KEY_READ)))
71    return;
72
73  const std::wstring original_font_name = base::UTF8ToWide(font.GetFontName());
74  std::vector<std::wstring> values;
75  if (FAILED(key.ReadValues(original_font_name.c_str(), &values))) {
76    key.Close();
77    return;
78  }
79
80  std::string filename;
81  std::string font_name;
82  for (size_t i = 0; i < values.size(); ++i) {
83    internal::ParseFontLinkEntry(
84        base::WideToUTF8(values[i]), &filename, &font_name);
85    // If the font name is present, add that directly, otherwise add the
86    // font names corresponding to the filename.
87    if (!font_name.empty()) {
88      AppendFont(font_name, font.GetFontSize(), linked_fonts);
89    } else if (!filename.empty()) {
90      std::vector<std::string> font_names;
91      GetFontNamesFromFilename(filename, font_map, &font_names);
92      for (size_t i = 0; i < font_names.size(); ++i)
93        AppendFont(font_names[i], font.GetFontSize(), linked_fonts);
94    }
95  }
96
97  key.Close();
98}
99
100// CachedFontLinkSettings is a singleton cache of the Windows font settings
101// from the registry. It maintains a cached view of the registry's list of
102// system fonts and their font link chains.
103class CachedFontLinkSettings {
104 public:
105  static CachedFontLinkSettings* GetInstance();
106
107  // Returns the linked fonts list correspond to |font|. Returned value will
108  // never be null.
109  const std::vector<Font>* GetLinkedFonts(const Font& font);
110
111 private:
112  friend struct DefaultSingletonTraits<CachedFontLinkSettings>;
113
114  CachedFontLinkSettings();
115  virtual ~CachedFontLinkSettings();
116
117  // Map of system fonts, from file names to font families.
118  std::map<std::string, std::string> cached_system_fonts_;
119
120  // Map from font names to vectors of linked fonts.
121  std::map<std::string, std::vector<Font> > cached_linked_fonts_;
122
123  DISALLOW_COPY_AND_ASSIGN(CachedFontLinkSettings);
124};
125
126// static
127CachedFontLinkSettings* CachedFontLinkSettings::GetInstance() {
128  return Singleton<CachedFontLinkSettings,
129                   LeakySingletonTraits<CachedFontLinkSettings> >::get();
130}
131
132const std::vector<Font>* CachedFontLinkSettings::GetLinkedFonts(
133    const Font& font) {
134  const std::string& font_name = font.GetFontName();
135  std::map<std::string, std::vector<Font> >::const_iterator it =
136      cached_linked_fonts_.find(font_name);
137  if (it != cached_linked_fonts_.end())
138    return &it->second;
139
140  cached_linked_fonts_[font_name] = std::vector<Font>();
141  std::vector<Font>* linked_fonts = &cached_linked_fonts_[font_name];
142  QueryLinkedFontsFromRegistry(font, &cached_system_fonts_, linked_fonts);
143  return linked_fonts;
144}
145
146CachedFontLinkSettings::CachedFontLinkSettings() {
147}
148
149CachedFontLinkSettings::~CachedFontLinkSettings() {
150}
151
152}  // namespace
153
154namespace internal {
155
156void ParseFontLinkEntry(const std::string& entry,
157                        std::string* filename,
158                        std::string* font_name) {
159  std::vector<std::string> parts;
160  base::SplitString(entry, ',', &parts);
161  filename->clear();
162  font_name->clear();
163  if (parts.size() > 0)
164    *filename = parts[0];
165  // The second entry may be the font name or the first scaling factor, if the
166  // entry does not contain a font name. If it contains only digits, assume it
167  // is a scaling factor.
168  if (parts.size() > 1 && !ContainsOnlyDigits(parts[1]))
169    *font_name = parts[1];
170}
171
172void ParseFontFamilyString(const std::string& family,
173                           std::vector<std::string>* font_names) {
174  // The entry is comma separated, having the font filename as the first value
175  // followed optionally by the font family name and a pair of integer scaling
176  // factors.
177  // TODO(asvitkine): Should we support these scaling factors?
178  base::SplitString(family, '&', font_names);
179  if (!font_names->empty()) {
180    const size_t index = font_names->back().find('(');
181    if (index != std::string::npos) {
182      font_names->back().resize(index);
183      base::TrimWhitespace(font_names->back(), base::TRIM_TRAILING,
184                           &font_names->back());
185    }
186  }
187}
188
189}  // namespace internal
190
191LinkedFontsIterator::LinkedFontsIterator(Font font)
192    : original_font_(font),
193      next_font_set_(false),
194      linked_fonts_(NULL),
195      linked_font_index_(0) {
196  SetNextFont(original_font_);
197}
198
199LinkedFontsIterator::~LinkedFontsIterator() {
200}
201
202void LinkedFontsIterator::SetNextFont(Font font) {
203  next_font_ = font;
204  next_font_set_ = true;
205}
206
207bool LinkedFontsIterator::NextFont(Font* font) {
208  if (next_font_set_) {
209    next_font_set_ = false;
210    current_font_ = next_font_;
211    *font = current_font_;
212    return true;
213  }
214
215  // First time through, get the linked fonts list.
216  if (linked_fonts_ == NULL)
217    linked_fonts_ = GetLinkedFonts();
218
219  if (linked_font_index_ == linked_fonts_->size())
220    return false;
221
222  current_font_ = linked_fonts_->at(linked_font_index_++);
223  *font = current_font_;
224  return true;
225}
226
227const std::vector<Font>* LinkedFontsIterator::GetLinkedFonts() const {
228  CachedFontLinkSettings* font_link = CachedFontLinkSettings::GetInstance();
229
230  // First, try to get the list for the original font.
231  const std::vector<Font>* fonts = font_link->GetLinkedFonts(original_font_);
232
233  // If there are no linked fonts for the original font, try querying the
234  // ones for the current font. This may happen if the first font is a custom
235  // font that has no linked fonts in the registry.
236  //
237  // Note: One possibility would be to always merge both lists of fonts,
238  //       but it is not clear whether there are any real world scenarios
239  //       where this would actually help.
240  if (fonts->empty())
241    fonts = font_link->GetLinkedFonts(current_font_);
242
243  return fonts;
244}
245
246}  // namespace gfx
247