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.
5 #include "ui/gfx/font_fallback_win.h"
11 #include "base/memory/singleton.h"
12 #include "base/profiler/scoped_tracker.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/win/registry.h"
17 #include "ui/gfx/font.h"
18 #include "ui/gfx/font_fallback.h"
24 // Queries the registry to get a mapping from font filenames to font names.
25 void QueryFontsFromRegistry(std::map
<std::string
, std::string
>* map
) {
26 const wchar_t* kFonts
=
27 L
"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
29 base::win::RegistryValueIterator
it(HKEY_LOCAL_MACHINE
, kFonts
);
30 for (; it
.Valid(); ++it
) {
31 const std::string filename
=
32 base::ToLowerASCII(base::WideToUTF8(it
.Value()));
33 (*map
)[filename
] = base::WideToUTF8(it
.Name());
37 // Fills |font_names| with a list of font families found in the font file at
38 // |filename|. Takes in a |font_map| from font filename to font families, which
39 // is filled-in by querying the registry, if empty.
40 void GetFontNamesFromFilename(const std::string
& filename
,
41 std::map
<std::string
, std::string
>* font_map
,
42 std::vector
<std::string
>* font_names
) {
43 if (font_map
->empty())
44 QueryFontsFromRegistry(font_map
);
46 std::map
<std::string
, std::string
>::const_iterator it
=
47 font_map
->find(base::ToLowerASCII(filename
));
48 if (it
== font_map
->end())
51 internal::ParseFontFamilyString(it
->second
, font_names
);
54 // Returns true if |text| contains only ASCII digits.
55 bool ContainsOnlyDigits(const std::string
& text
) {
56 return text
.find_first_not_of("0123456789") == base::string16::npos
;
59 // Appends a Font with the given |name| and |size| to |fonts| unless the last
60 // entry is already a font with that name.
61 void AppendFont(const std::string
& name
, int size
, std::vector
<Font
>* fonts
) {
62 if (fonts
->empty() || fonts
->back().GetFontName() != name
)
63 fonts
->push_back(Font(name
, size
));
66 // Queries the registry to get a list of linked fonts for |font|.
67 void QueryLinkedFontsFromRegistry(const Font
& font
,
68 std::map
<std::string
, std::string
>* font_map
,
69 std::vector
<Font
>* linked_fonts
) {
70 const wchar_t* kSystemLink
=
71 L
"Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink";
73 base::win::RegKey key
;
74 if (FAILED(key
.Open(HKEY_LOCAL_MACHINE
, kSystemLink
, KEY_READ
)))
77 const std::wstring original_font_name
= base::UTF8ToWide(font
.GetFontName());
78 std::vector
<std::wstring
> values
;
79 if (FAILED(key
.ReadValues(original_font_name
.c_str(), &values
))) {
85 std::string font_name
;
86 for (size_t i
= 0; i
< values
.size(); ++i
) {
87 internal::ParseFontLinkEntry(
88 base::WideToUTF8(values
[i
]), &filename
, &font_name
);
89 // If the font name is present, add that directly, otherwise add the
90 // font names corresponding to the filename.
91 if (!font_name
.empty()) {
92 AppendFont(font_name
, font
.GetFontSize(), linked_fonts
);
93 } else if (!filename
.empty()) {
94 std::vector
<std::string
> font_names
;
95 GetFontNamesFromFilename(filename
, font_map
, &font_names
);
96 for (size_t i
= 0; i
< font_names
.size(); ++i
)
97 AppendFont(font_names
[i
], font
.GetFontSize(), linked_fonts
);
104 // CachedFontLinkSettings is a singleton cache of the Windows font settings
105 // from the registry. It maintains a cached view of the registry's list of
106 // system fonts and their font link chains.
107 class CachedFontLinkSettings
{
109 static CachedFontLinkSettings
* GetInstance();
111 // Returns the linked fonts list correspond to |font|. Returned value will
113 const std::vector
<Font
>* GetLinkedFonts(const Font
& font
);
116 friend struct base::DefaultSingletonTraits
<CachedFontLinkSettings
>;
118 CachedFontLinkSettings();
119 virtual ~CachedFontLinkSettings();
121 // Map of system fonts, from file names to font families.
122 std::map
<std::string
, std::string
> cached_system_fonts_
;
124 // Map from font names to vectors of linked fonts.
125 std::map
<std::string
, std::vector
<Font
> > cached_linked_fonts_
;
127 DISALLOW_COPY_AND_ASSIGN(CachedFontLinkSettings
);
131 CachedFontLinkSettings
* CachedFontLinkSettings::GetInstance() {
132 return base::Singleton
<
133 CachedFontLinkSettings
,
134 base::LeakySingletonTraits
<CachedFontLinkSettings
>>::get();
137 const std::vector
<Font
>* CachedFontLinkSettings::GetLinkedFonts(
139 const std::string
& font_name
= font
.GetFontName();
140 std::map
<std::string
, std::vector
<Font
> >::const_iterator it
=
141 cached_linked_fonts_
.find(font_name
);
142 if (it
!= cached_linked_fonts_
.end())
145 cached_linked_fonts_
[font_name
] = std::vector
<Font
>();
146 std::vector
<Font
>* linked_fonts
= &cached_linked_fonts_
[font_name
];
148 // TODO(ckocagil): Remove ScopedTracker below once crbug.com/441028 is fixed.
149 tracked_objects::ScopedTracker
tracking_profile(
150 FROM_HERE_WITH_EXPLICIT_FUNCTION(
151 "441028 QueryLinkedFontsFromRegistry()"));
153 QueryLinkedFontsFromRegistry(font
, &cached_system_fonts_
, linked_fonts
);
157 CachedFontLinkSettings::CachedFontLinkSettings() {
160 CachedFontLinkSettings::~CachedFontLinkSettings() {
163 // Callback to |EnumEnhMetaFile()| to intercept font creation.
164 int CALLBACK
MetaFileEnumProc(HDC hdc
,
166 CONST ENHMETARECORD
* record
,
169 if (record
->iType
== EMR_EXTCREATEFONTINDIRECTW
) {
170 const EMREXTCREATEFONTINDIRECTW
* create_font_record
=
171 reinterpret_cast<const EMREXTCREATEFONTINDIRECTW
*>(record
);
172 *reinterpret_cast<LOGFONT
*>(log_font
) = create_font_record
->elfw
.elfLogFont
;
181 void ParseFontLinkEntry(const std::string
& entry
,
182 std::string
* filename
,
183 std::string
* font_name
) {
184 std::vector
<std::string
> parts
= base::SplitString(
185 entry
, ",", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
188 if (parts
.size() > 0)
189 *filename
= parts
[0];
190 // The second entry may be the font name or the first scaling factor, if the
191 // entry does not contain a font name. If it contains only digits, assume it
192 // is a scaling factor.
193 if (parts
.size() > 1 && !ContainsOnlyDigits(parts
[1]))
194 *font_name
= parts
[1];
197 void ParseFontFamilyString(const std::string
& family
,
198 std::vector
<std::string
>* font_names
) {
199 // The entry is comma separated, having the font filename as the first value
200 // followed optionally by the font family name and a pair of integer scaling
202 // TODO(asvitkine): Should we support these scaling factors?
203 *font_names
= base::SplitString(
204 family
, "&", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
205 if (!font_names
->empty()) {
206 const size_t index
= font_names
->back().find('(');
207 if (index
!= std::string::npos
) {
208 font_names
->back().resize(index
);
209 base::TrimWhitespace(font_names
->back(), base::TRIM_TRAILING
,
210 &font_names
->back());
215 LinkedFontsIterator::LinkedFontsIterator(Font font
)
216 : original_font_(font
),
217 next_font_set_(false),
219 linked_font_index_(0) {
220 SetNextFont(original_font_
);
223 LinkedFontsIterator::~LinkedFontsIterator() {
226 void LinkedFontsIterator::SetNextFont(Font font
) {
228 next_font_set_
= true;
231 bool LinkedFontsIterator::NextFont(Font
* font
) {
232 if (next_font_set_
) {
233 next_font_set_
= false;
234 current_font_
= next_font_
;
235 *font
= current_font_
;
239 // First time through, get the linked fonts list.
240 if (linked_fonts_
== NULL
)
241 linked_fonts_
= GetLinkedFonts();
243 if (linked_font_index_
== linked_fonts_
->size())
246 current_font_
= linked_fonts_
->at(linked_font_index_
++);
247 *font
= current_font_
;
251 const std::vector
<Font
>* LinkedFontsIterator::GetLinkedFonts() const {
252 CachedFontLinkSettings
* font_link
= CachedFontLinkSettings::GetInstance();
254 // First, try to get the list for the original font.
255 const std::vector
<Font
>* fonts
= font_link
->GetLinkedFonts(original_font_
);
257 // If there are no linked fonts for the original font, try querying the
258 // ones for the current font. This may happen if the first font is a custom
259 // font that has no linked fonts in the registry.
261 // Note: One possibility would be to always merge both lists of fonts,
262 // but it is not clear whether there are any real world scenarios
263 // where this would actually help.
265 fonts
= font_link
->GetLinkedFonts(current_font_
);
270 } // namespace internal
272 std::vector
<std::string
> GetFallbackFontFamilies(
273 const std::string
& font_family
) {
274 // LinkedFontsIterator doesn't care about the font size, so we always pass 10.
275 internal::LinkedFontsIterator
linked_fonts(Font(font_family
, 10));
276 std::vector
<std::string
> fallback_fonts
;
278 while (linked_fonts
.NextFont(¤t
))
279 fallback_fonts
.push_back(current
.GetFontName());
280 return fallback_fonts
;
283 bool GetUniscribeFallbackFont(const Font
& font
,
287 // Adapted from WebKit's |FontCache::GetFontDataForCharacters()|.
288 // Uniscribe doesn't expose a method to query fallback fonts, so this works by
289 // drawing the text to an EMF object with Uniscribe's ScriptStringOut and then
290 // inspecting the EMF object to figure out which font Uniscribe used.
292 // DirectWrite in Windows 8.1 provides a cleaner alternative:
293 // http://msdn.microsoft.com/en-us/library/windows/desktop/dn280480.aspx
295 static HDC hdc
= CreateCompatibleDC(NULL
);
297 // Use a meta file to intercept the fallback font chosen by Uniscribe.
298 HDC meta_file_dc
= CreateEnhMetaFile(hdc
, NULL
, NULL
, NULL
);
302 SelectObject(meta_file_dc
, font
.GetNativeFont());
304 SCRIPT_STRING_ANALYSIS script_analysis
;
306 ScriptStringAnalyse(meta_file_dc
, text
, text_length
, 0, -1,
307 SSA_METAFILE
| SSA_FALLBACK
| SSA_GLYPHS
| SSA_LINK
,
308 0, NULL
, NULL
, NULL
, NULL
, NULL
, &script_analysis
);
310 if (SUCCEEDED(hresult
)) {
311 hresult
= ScriptStringOut(script_analysis
, 0, 0, 0, NULL
, 0, 0, FALSE
);
312 ScriptStringFree(&script_analysis
);
315 bool found_fallback
= false;
316 HENHMETAFILE meta_file
= CloseEnhMetaFile(meta_file_dc
);
317 if (SUCCEEDED(hresult
)) {
319 log_font
.lfFaceName
[0] = 0;
320 EnumEnhMetaFile(0, meta_file
, MetaFileEnumProc
, &log_font
, NULL
);
321 if (log_font
.lfFaceName
[0]) {
322 *result
= Font(base::UTF16ToUTF8(log_font
.lfFaceName
),
324 found_fallback
= true;
327 DeleteEnhMetaFile(meta_file
);
329 return found_fallback
;