Home | History | Annotate | Download | only in gfx
      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.
     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.
     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.
     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.
     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|.
     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
    130 CachedFontLinkSettings* CachedFontLinkSettings::GetInstance() {
    131   return Singleton<CachedFontLinkSettings,
    132                    LeakySingletonTraits<CachedFontLinkSettings> >::get();
    133 }
    134 
    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 
    149 CachedFontLinkSettings::CachedFontLinkSettings() {
    150 }
    151 
    152 CachedFontLinkSettings::~CachedFontLinkSettings() {
    153 }
    154 
    155 // Callback to |EnumEnhMetaFile()| to intercept font creation.
    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 
    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 
    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 
    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 
    214 LinkedFontsIterator::~LinkedFontsIterator() {
    215 }
    216 
    217 void LinkedFontsIterator::SetNextFont(Font font) {
    218   next_font_ = font;
    219   next_font_set_ = true;
    220 }
    221 
    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 
    242 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 
    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 
    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