• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <usp10.h>
8 
9 #include <map>
10 
11 #include "base/memory/singleton.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/win/registry.h"
16 #include "ui/gfx/font.h"
17 #include "ui/gfx/font_fallback.h"
18 
19 namespace gfx {
20 
21 namespace {
22 
23 // Queries the registry to get a mapping from font filenames to font names.
QueryFontsFromRegistry(std::map<std::string,std::string> * map)24 void QueryFontsFromRegistry(std::map<std::string, std::string>* map) {
25   const wchar_t* kFonts =
26       L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
27 
28   base::win::RegistryValueIterator it(HKEY_LOCAL_MACHINE, kFonts);
29   for (; it.Valid(); ++it) {
30     const std::string filename =
31         base::StringToLowerASCII(base::WideToUTF8(it.Value()));
32     (*map)[filename] = base::WideToUTF8(it.Name());
33   }
34 }
35 
36 // Fills |font_names| with a list of font families found in the font file at
37 // |filename|. Takes in a |font_map| from font filename to font families, which
38 // 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)39 void GetFontNamesFromFilename(const std::string& filename,
40                               std::map<std::string, std::string>* font_map,
41                               std::vector<std::string>* font_names) {
42   if (font_map->empty())
43     QueryFontsFromRegistry(font_map);
44 
45   std::map<std::string, std::string>::const_iterator it =
46       font_map->find(base::StringToLowerASCII(filename));
47   if (it == font_map->end())
48     return;
49 
50   internal::ParseFontFamilyString(it->second, font_names);
51 }
52 
53 // Returns true if |text| contains only ASCII digits.
ContainsOnlyDigits(const std::string & text)54 bool ContainsOnlyDigits(const std::string& text) {
55   return text.find_first_not_of("0123456789") == base::string16::npos;
56 }
57 
58 // Appends a Font with the given |name| and |size| to |fonts| unless the last
59 // entry is already a font with that name.
AppendFont(const std::string & name,int size,std::vector<Font> * fonts)60 void AppendFont(const std::string& name, int size, std::vector<Font>* fonts) {
61   if (fonts->empty() || fonts->back().GetFontName() != name)
62     fonts->push_back(Font(name, size));
63 }
64 
65 // 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)66 void QueryLinkedFontsFromRegistry(const Font& font,
67                                   std::map<std::string, std::string>* font_map,
68                                   std::vector<Font>* linked_fonts) {
69   const wchar_t* kSystemLink =
70       L"Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink";
71 
72   base::win::RegKey key;
73   if (FAILED(key.Open(HKEY_LOCAL_MACHINE, kSystemLink, KEY_READ)))
74     return;
75 
76   const std::wstring original_font_name = base::UTF8ToWide(font.GetFontName());
77   std::vector<std::wstring> values;
78   if (FAILED(key.ReadValues(original_font_name.c_str(), &values))) {
79     key.Close();
80     return;
81   }
82 
83   std::string filename;
84   std::string font_name;
85   for (size_t i = 0; i < values.size(); ++i) {
86     internal::ParseFontLinkEntry(
87         base::WideToUTF8(values[i]), &filename, &font_name);
88     // If the font name is present, add that directly, otherwise add the
89     // font names corresponding to the filename.
90     if (!font_name.empty()) {
91       AppendFont(font_name, font.GetFontSize(), linked_fonts);
92     } else if (!filename.empty()) {
93       std::vector<std::string> font_names;
94       GetFontNamesFromFilename(filename, font_map, &font_names);
95       for (size_t i = 0; i < font_names.size(); ++i)
96         AppendFont(font_names[i], font.GetFontSize(), linked_fonts);
97     }
98   }
99 
100   key.Close();
101 }
102 
103 // CachedFontLinkSettings is a singleton cache of the Windows font settings
104 // from the registry. It maintains a cached view of the registry's list of
105 // system fonts and their font link chains.
106 class CachedFontLinkSettings {
107  public:
108   static CachedFontLinkSettings* GetInstance();
109 
110   // Returns the linked fonts list correspond to |font|. Returned value will
111   // never be null.
112   const std::vector<Font>* GetLinkedFonts(const Font& font);
113 
114  private:
115   friend struct DefaultSingletonTraits<CachedFontLinkSettings>;
116 
117   CachedFontLinkSettings();
118   virtual ~CachedFontLinkSettings();
119 
120   // Map of system fonts, from file names to font families.
121   std::map<std::string, std::string> cached_system_fonts_;
122 
123   // Map from font names to vectors of linked fonts.
124   std::map<std::string, std::vector<Font> > cached_linked_fonts_;
125 
126   DISALLOW_COPY_AND_ASSIGN(CachedFontLinkSettings);
127 };
128 
129 // static
GetInstance()130 CachedFontLinkSettings* CachedFontLinkSettings::GetInstance() {
131   return Singleton<CachedFontLinkSettings,
132                    LeakySingletonTraits<CachedFontLinkSettings> >::get();
133 }
134 
GetLinkedFonts(const Font & font)135 const std::vector<Font>* CachedFontLinkSettings::GetLinkedFonts(
136     const Font& font) {
137   const std::string& font_name = font.GetFontName();
138   std::map<std::string, std::vector<Font> >::const_iterator it =
139       cached_linked_fonts_.find(font_name);
140   if (it != cached_linked_fonts_.end())
141     return &it->second;
142 
143   cached_linked_fonts_[font_name] = std::vector<Font>();
144   std::vector<Font>* linked_fonts = &cached_linked_fonts_[font_name];
145   QueryLinkedFontsFromRegistry(font, &cached_system_fonts_, linked_fonts);
146   return linked_fonts;
147 }
148 
CachedFontLinkSettings()149 CachedFontLinkSettings::CachedFontLinkSettings() {
150 }
151 
~CachedFontLinkSettings()152 CachedFontLinkSettings::~CachedFontLinkSettings() {
153 }
154 
155 // Callback to |EnumEnhMetaFile()| to intercept font creation.
MetaFileEnumProc(HDC hdc,HANDLETABLE * table,CONST ENHMETARECORD * record,int table_entries,LPARAM log_font)156 int CALLBACK MetaFileEnumProc(HDC hdc,
157                               HANDLETABLE* table,
158                               CONST ENHMETARECORD* record,
159                               int table_entries,
160                               LPARAM log_font) {
161   if (record->iType == EMR_EXTCREATEFONTINDIRECTW) {
162     const EMREXTCREATEFONTINDIRECTW* create_font_record =
163         reinterpret_cast<const EMREXTCREATEFONTINDIRECTW*>(record);
164     *reinterpret_cast<LOGFONT*>(log_font) = create_font_record->elfw.elfLogFont;
165   }
166   return 1;
167 }
168 
169 }  // namespace
170 
171 namespace internal {
172 
ParseFontLinkEntry(const std::string & entry,std::string * filename,std::string * font_name)173 void ParseFontLinkEntry(const std::string& entry,
174                         std::string* filename,
175                         std::string* font_name) {
176   std::vector<std::string> parts;
177   base::SplitString(entry, ',', &parts);
178   filename->clear();
179   font_name->clear();
180   if (parts.size() > 0)
181     *filename = parts[0];
182   // The second entry may be the font name or the first scaling factor, if the
183   // entry does not contain a font name. If it contains only digits, assume it
184   // is a scaling factor.
185   if (parts.size() > 1 && !ContainsOnlyDigits(parts[1]))
186     *font_name = parts[1];
187 }
188 
ParseFontFamilyString(const std::string & family,std::vector<std::string> * font_names)189 void ParseFontFamilyString(const std::string& family,
190                            std::vector<std::string>* font_names) {
191   // The entry is comma separated, having the font filename as the first value
192   // followed optionally by the font family name and a pair of integer scaling
193   // factors.
194   // TODO(asvitkine): Should we support these scaling factors?
195   base::SplitString(family, '&', font_names);
196   if (!font_names->empty()) {
197     const size_t index = font_names->back().find('(');
198     if (index != std::string::npos) {
199       font_names->back().resize(index);
200       base::TrimWhitespace(font_names->back(), base::TRIM_TRAILING,
201                            &font_names->back());
202     }
203   }
204 }
205 
LinkedFontsIterator(Font font)206 LinkedFontsIterator::LinkedFontsIterator(Font font)
207     : original_font_(font),
208       next_font_set_(false),
209       linked_fonts_(NULL),
210       linked_font_index_(0) {
211   SetNextFont(original_font_);
212 }
213 
~LinkedFontsIterator()214 LinkedFontsIterator::~LinkedFontsIterator() {
215 }
216 
SetNextFont(Font font)217 void LinkedFontsIterator::SetNextFont(Font font) {
218   next_font_ = font;
219   next_font_set_ = true;
220 }
221 
NextFont(Font * font)222 bool LinkedFontsIterator::NextFont(Font* font) {
223   if (next_font_set_) {
224     next_font_set_ = false;
225     current_font_ = next_font_;
226     *font = current_font_;
227     return true;
228   }
229 
230   // First time through, get the linked fonts list.
231   if (linked_fonts_ == NULL)
232     linked_fonts_ = GetLinkedFonts();
233 
234   if (linked_font_index_ == linked_fonts_->size())
235     return false;
236 
237   current_font_ = linked_fonts_->at(linked_font_index_++);
238   *font = current_font_;
239   return true;
240 }
241 
GetLinkedFonts() const242 const std::vector<Font>* LinkedFontsIterator::GetLinkedFonts() const {
243   CachedFontLinkSettings* font_link = CachedFontLinkSettings::GetInstance();
244 
245   // First, try to get the list for the original font.
246   const std::vector<Font>* fonts = font_link->GetLinkedFonts(original_font_);
247 
248   // If there are no linked fonts for the original font, try querying the
249   // ones for the current font. This may happen if the first font is a custom
250   // font that has no linked fonts in the registry.
251   //
252   // Note: One possibility would be to always merge both lists of fonts,
253   //       but it is not clear whether there are any real world scenarios
254   //       where this would actually help.
255   if (fonts->empty())
256     fonts = font_link->GetLinkedFonts(current_font_);
257 
258   return fonts;
259 }
260 
261 }  // namespace internal
262 
GetFallbackFontFamilies(const std::string & font_family)263 std::vector<std::string> GetFallbackFontFamilies(
264     const std::string& font_family) {
265   // LinkedFontsIterator doesn't care about the font size, so we always pass 10.
266   internal::LinkedFontsIterator linked_fonts(Font(font_family, 10));
267   std::vector<std::string> fallback_fonts;
268   Font current;
269   while (linked_fonts.NextFont(&current))
270     fallback_fonts.push_back(current.GetFontName());
271   return fallback_fonts;
272 }
273 
GetUniscribeFallbackFont(const Font & font,const wchar_t * text,int text_length,Font * result)274 bool GetUniscribeFallbackFont(const Font& font,
275                               const wchar_t* text,
276                               int text_length,
277                               Font* result) {
278   // Adapted from WebKit's |FontCache::GetFontDataForCharacters()|.
279   // Uniscribe doesn't expose a method to query fallback fonts, so this works by
280   // drawing the text to an EMF object with Uniscribe's ScriptStringOut and then
281   // inspecting the EMF object to figure out which font Uniscribe used.
282   //
283   // DirectWrite in Windows 8.1 provides a cleaner alternative:
284   // http://msdn.microsoft.com/en-us/library/windows/desktop/dn280480.aspx
285 
286   static HDC hdc = CreateCompatibleDC(NULL);
287 
288   // Use a meta file to intercept the fallback font chosen by Uniscribe.
289   HDC meta_file_dc = CreateEnhMetaFile(hdc, NULL, NULL, NULL);
290   if (!meta_file_dc)
291     return false;
292 
293   SelectObject(meta_file_dc, font.GetNativeFont());
294 
295   SCRIPT_STRING_ANALYSIS script_analysis;
296   HRESULT hresult =
297       ScriptStringAnalyse(meta_file_dc, text, text_length, 0, -1,
298                           SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK,
299                           0, NULL, NULL, NULL, NULL, NULL, &script_analysis);
300 
301   if (SUCCEEDED(hresult)) {
302     hresult = ScriptStringOut(script_analysis, 0, 0, 0, NULL, 0, 0, FALSE);
303     ScriptStringFree(&script_analysis);
304   }
305 
306   bool found_fallback = false;
307   HENHMETAFILE meta_file = CloseEnhMetaFile(meta_file_dc);
308   if (SUCCEEDED(hresult)) {
309     LOGFONT log_font;
310     log_font.lfFaceName[0] = 0;
311     EnumEnhMetaFile(0, meta_file, MetaFileEnumProc, &log_font, NULL);
312     if (log_font.lfFaceName[0]) {
313       *result = Font(base::UTF16ToUTF8(log_font.lfFaceName),
314                      font.GetFontSize());
315       found_fallback = true;
316     }
317   }
318   DeleteEnhMetaFile(meta_file);
319 
320   return found_fallback;
321 }
322 
323 }  // namespace gfx
324