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/platform_font_pango.h"
      6 
      7 #include <fontconfig/fontconfig.h>
      8 #include <pango/pango.h>
      9 
     10 #include <algorithm>
     11 #include <string>
     12 
     13 #include "base/logging.h"
     14 #include "base/strings/string_piece.h"
     15 #include "base/strings/string_split.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "third_party/skia/include/core/SkPaint.h"
     18 #include "third_party/skia/include/core/SkString.h"
     19 #include "third_party/skia/include/core/SkTypeface.h"
     20 #include "ui/gfx/canvas.h"
     21 #include "ui/gfx/font.h"
     22 #include "ui/gfx/font_list.h"
     23 #include "ui/gfx/linux_font_delegate.h"
     24 #include "ui/gfx/pango_util.h"
     25 #include "ui/gfx/text_utils.h"
     26 
     27 namespace {
     28 
     29 // The font family name which is used when a user's application font for
     30 // GNOME/KDE is a non-scalable one. The name should be listed in the
     31 // IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp.
     32 const char* kFallbackFontFamilyName = "sans";
     33 
     34 // Returns the available font family that best (in FontConfig's eyes) matches
     35 // the supplied list of family names.
     36 std::string FindBestMatchFontFamilyName(
     37     const std::vector<std::string>& family_names) {
     38   FcPattern* pattern = FcPatternCreate();
     39   for (std::vector<std::string>::const_iterator it = family_names.begin();
     40        it != family_names.end(); ++it) {
     41     FcValue fcvalue;
     42     fcvalue.type = FcTypeString;
     43     fcvalue.u.s = reinterpret_cast<const FcChar8*>(it->c_str());
     44     FcPatternAdd(pattern, FC_FAMILY, fcvalue, FcTrue /* append */);
     45   }
     46 
     47   FcConfigSubstitute(0, pattern, FcMatchPattern);
     48   FcDefaultSubstitute(pattern);
     49   FcResult result;
     50   FcPattern* match = FcFontMatch(0, pattern, &result);
     51   DCHECK(match) << "Could not find font";
     52   FcChar8* match_family = NULL;
     53   FcPatternGetString(match, FC_FAMILY, 0, &match_family);
     54   std::string font_family(reinterpret_cast<char*>(match_family));
     55   FcPatternDestroy(pattern);
     56   FcPatternDestroy(match);
     57   return font_family;
     58 }
     59 
     60 }  // namespace
     61 
     62 namespace gfx {
     63 
     64 // static
     65 Font* PlatformFontPango::default_font_ = NULL;
     66 
     67 #if defined(OS_CHROMEOS)
     68 // static
     69 std::string* PlatformFontPango::default_font_description_ = NULL;
     70 #endif
     71 
     72 ////////////////////////////////////////////////////////////////////////////////
     73 // PlatformFontPango, public:
     74 
     75 PlatformFontPango::PlatformFontPango() {
     76   if (default_font_ == NULL) {
     77     std::string font_name = GetDefaultFont();
     78 
     79     ScopedPangoFontDescription desc(
     80         pango_font_description_from_string(font_name.c_str()));
     81     default_font_ = new Font(desc.get());
     82 
     83     DCHECK(default_font_);
     84   }
     85 
     86   InitFromPlatformFont(
     87       static_cast<PlatformFontPango*>(default_font_->platform_font()));
     88 }
     89 
     90 PlatformFontPango::PlatformFontPango(NativeFont native_font) {
     91   std::vector<std::string> family_names;
     92   base::SplitString(pango_font_description_get_family(native_font), ',',
     93                     &family_names);
     94   std::string font_family = FindBestMatchFontFamilyName(family_names);
     95   InitWithNameAndSize(font_family, gfx::GetPangoFontSizeInPixels(native_font));
     96 
     97   int style = 0;
     98   if (pango_font_description_get_weight(native_font) == PANGO_WEIGHT_BOLD) {
     99     // TODO(davemoore) What should we do about other weights? We currently
    100     // only support BOLD.
    101     style |= gfx::Font::BOLD;
    102   }
    103   if (pango_font_description_get_style(native_font) == PANGO_STYLE_ITALIC) {
    104     // TODO(davemoore) What about PANGO_STYLE_OBLIQUE?
    105     style |= gfx::Font::ITALIC;
    106   }
    107   if (style != 0)
    108     style_ = style;
    109 }
    110 
    111 PlatformFontPango::PlatformFontPango(const std::string& font_name,
    112                                      int font_size) {
    113   InitWithNameAndSize(font_name, font_size);
    114 }
    115 
    116 double PlatformFontPango::underline_position() const {
    117   const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
    118   return underline_position_pixels_;
    119 }
    120 
    121 double PlatformFontPango::underline_thickness() const {
    122   const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
    123   return underline_thickness_pixels_;
    124 }
    125 
    126 ////////////////////////////////////////////////////////////////////////////////
    127 // PlatformFontPango, PlatformFont implementation:
    128 
    129 // static
    130 void PlatformFontPango::ReloadDefaultFont() {
    131   delete default_font_;
    132   default_font_ = NULL;
    133 }
    134 
    135 #if defined(OS_CHROMEOS)
    136 // static
    137 void PlatformFontPango::SetDefaultFontDescription(
    138     const std::string& font_description) {
    139   delete default_font_description_;
    140   default_font_description_ = new std::string(font_description);
    141 }
    142 
    143 #endif
    144 
    145 Font PlatformFontPango::DeriveFont(int size_delta, int style) const {
    146   // If the delta is negative, if must not push the size below 1
    147   if (size_delta < 0)
    148     DCHECK_LT(-size_delta, font_size_pixels_);
    149 
    150   if (style == style_) {
    151     // Fast path, we just use the same typeface at a different size
    152     return Font(new PlatformFontPango(typeface_,
    153                                       font_family_,
    154                                       font_size_pixels_ + size_delta,
    155                                       style_));
    156   }
    157 
    158   // If the style has changed we may need to load a new face
    159   int skstyle = SkTypeface::kNormal;
    160   if (gfx::Font::BOLD & style)
    161     skstyle |= SkTypeface::kBold;
    162   if (gfx::Font::ITALIC & style)
    163     skstyle |= SkTypeface::kItalic;
    164 
    165   skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(
    166       SkTypeface::CreateFromName(
    167           font_family_.c_str(),
    168           static_cast<SkTypeface::Style>(skstyle)));
    169 
    170   return Font(new PlatformFontPango(typeface,
    171                                     font_family_,
    172                                     font_size_pixels_ + size_delta,
    173                                     style));
    174 }
    175 
    176 int PlatformFontPango::GetHeight() const {
    177   return height_pixels_;
    178 }
    179 
    180 int PlatformFontPango::GetBaseline() const {
    181   return ascent_pixels_;
    182 }
    183 
    184 int PlatformFontPango::GetCapHeight() const {
    185   return cap_height_pixels_;
    186 }
    187 
    188 int PlatformFontPango::GetExpectedTextWidth(int length) const {
    189   double char_width = const_cast<PlatformFontPango*>(this)->GetAverageWidth();
    190   return round(static_cast<float>(length) * char_width);
    191 }
    192 
    193 int PlatformFontPango::GetStyle() const {
    194   return style_;
    195 }
    196 
    197 std::string PlatformFontPango::GetFontName() const {
    198   return font_family_;
    199 }
    200 
    201 std::string PlatformFontPango::GetActualFontNameForTesting() const {
    202   SkString family_name;
    203   typeface_->getFamilyName(&family_name);
    204   return family_name.c_str();
    205 }
    206 
    207 int PlatformFontPango::GetFontSize() const {
    208   return font_size_pixels_;
    209 }
    210 
    211 NativeFont PlatformFontPango::GetNativeFont() const {
    212   PangoFontDescription* pfd = pango_font_description_new();
    213   pango_font_description_set_family(pfd, GetFontName().c_str());
    214   // Set the absolute size to avoid overflowing UI elements.
    215   // pango_font_description_set_absolute_size() takes a size in Pango units.
    216   // There are PANGO_SCALE Pango units in one device unit.  Screen output
    217   // devices use pixels as their device units.
    218   pango_font_description_set_absolute_size(
    219       pfd, font_size_pixels_ * PANGO_SCALE);
    220 
    221   switch (GetStyle()) {
    222     case gfx::Font::NORMAL:
    223       // Nothing to do, should already be PANGO_STYLE_NORMAL.
    224       break;
    225     case gfx::Font::BOLD:
    226       pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD);
    227       break;
    228     case gfx::Font::ITALIC:
    229       pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC);
    230       break;
    231     case gfx::Font::UNDERLINE:
    232       // TODO(deanm): How to do underline?  Where do we use it?  Probably have
    233       // to paint it ourselves, see pango_font_metrics_get_underline_position.
    234       break;
    235   }
    236 
    237   return pfd;
    238 }
    239 
    240 ////////////////////////////////////////////////////////////////////////////////
    241 // PlatformFontPango, private:
    242 
    243 PlatformFontPango::PlatformFontPango(const skia::RefPtr<SkTypeface>& typeface,
    244                                      const std::string& name,
    245                                      int size,
    246                                      int style) {
    247   InitWithTypefaceNameSizeAndStyle(typeface, name, size, style);
    248 }
    249 
    250 PlatformFontPango::~PlatformFontPango() {}
    251 
    252 // static
    253 std::string PlatformFontPango::GetDefaultFont() {
    254 #if defined(OS_CHROMEOS)
    255   // Font name must have been provided by way of SetDefaultFontDescription().
    256   CHECK(default_font_description_);
    257   return *default_font_description_;
    258 #else
    259   const gfx::LinuxFontDelegate* delegate = gfx::LinuxFontDelegate::instance();
    260   if (delegate)
    261     return delegate->GetDefaultFontName();
    262 
    263   return "sans 10";
    264 #endif    // defined(OS_CHROMEOS)
    265 }
    266 
    267 void PlatformFontPango::InitWithNameAndSize(const std::string& font_name,
    268                                             int font_size) {
    269   DCHECK_GT(font_size, 0);
    270   std::string fallback;
    271 
    272   skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(
    273       SkTypeface::CreateFromName(font_name.c_str(), SkTypeface::kNormal));
    274   if (!typeface) {
    275     // A non-scalable font such as .pcf is specified. Falls back to a default
    276     // scalable font.
    277     typeface = skia::AdoptRef(
    278         SkTypeface::CreateFromName(
    279             kFallbackFontFamilyName, SkTypeface::kNormal));
    280     CHECK(typeface) << "Could not find any font: "
    281                     << font_name
    282                     << ", " << kFallbackFontFamilyName;
    283     fallback = kFallbackFontFamilyName;
    284   }
    285 
    286   InitWithTypefaceNameSizeAndStyle(typeface,
    287                                    fallback.empty() ? font_name : fallback,
    288                                    font_size,
    289                                    gfx::Font::NORMAL);
    290 }
    291 
    292 void PlatformFontPango::InitWithTypefaceNameSizeAndStyle(
    293     const skia::RefPtr<SkTypeface>& typeface,
    294     const std::string& font_family,
    295     int font_size,
    296     int style) {
    297   typeface_ = typeface;
    298   font_family_ = font_family;
    299   font_size_pixels_ = font_size;
    300   style_ = style;
    301   pango_metrics_inited_ = false;
    302   average_width_pixels_ = 0.0f;
    303   underline_position_pixels_ = 0.0f;
    304   underline_thickness_pixels_ = 0.0f;
    305 
    306   SkPaint paint;
    307   SkPaint::FontMetrics metrics;
    308   PaintSetup(&paint);
    309   paint.getFontMetrics(&metrics);
    310 
    311   ascent_pixels_ = SkScalarCeilToInt(-metrics.fAscent);
    312   height_pixels_ = ascent_pixels_ + SkScalarCeilToInt(metrics.fDescent);
    313   cap_height_pixels_ = SkScalarCeilToInt(metrics.fCapHeight);
    314 }
    315 
    316 void PlatformFontPango::InitFromPlatformFont(const PlatformFontPango* other) {
    317   typeface_ = other->typeface_;
    318   font_family_ = other->font_family_;
    319   font_size_pixels_ = other->font_size_pixels_;
    320   style_ = other->style_;
    321   height_pixels_ = other->height_pixels_;
    322   ascent_pixels_ = other->ascent_pixels_;
    323   cap_height_pixels_ = other->cap_height_pixels_;
    324   pango_metrics_inited_ = other->pango_metrics_inited_;
    325   average_width_pixels_ = other->average_width_pixels_;
    326   underline_position_pixels_ = other->underline_position_pixels_;
    327   underline_thickness_pixels_ = other->underline_thickness_pixels_;
    328 }
    329 
    330 void PlatformFontPango::PaintSetup(SkPaint* paint) const {
    331   paint->setAntiAlias(false);
    332   paint->setSubpixelText(false);
    333   paint->setTextSize(font_size_pixels_);
    334   paint->setTypeface(typeface_.get());
    335   paint->setFakeBoldText((gfx::Font::BOLD & style_) && !typeface_->isBold());
    336   paint->setTextSkewX((gfx::Font::ITALIC & style_) && !typeface_->isItalic() ?
    337                       -SK_Scalar1/4 : 0);
    338 }
    339 
    340 void PlatformFontPango::InitPangoMetrics() {
    341   if (!pango_metrics_inited_) {
    342     pango_metrics_inited_ = true;
    343     ScopedPangoFontDescription pango_desc(GetNativeFont());
    344     PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc.get());
    345 
    346     underline_position_pixels_ =
    347         pango_font_metrics_get_underline_position(pango_metrics) /
    348         PANGO_SCALE;
    349 
    350     // TODO(davemoore): Come up with a better solution.
    351     // This is a hack, but without doing this the underlines
    352     // we get end up fuzzy. So we align to the midpoint of a pixel.
    353     underline_position_pixels_ /= 2;
    354 
    355     underline_thickness_pixels_ =
    356         pango_font_metrics_get_underline_thickness(pango_metrics) /
    357         PANGO_SCALE;
    358 
    359     // First get the Pango-based width (converting from Pango units to pixels).
    360     const double pango_width_pixels =
    361         pango_font_metrics_get_approximate_char_width(pango_metrics) /
    362         PANGO_SCALE;
    363 
    364     // Yes, this is how Microsoft recommends calculating the dialog unit
    365     // conversions.
    366     const int text_width_pixels = GetStringWidth(
    367         base::ASCIIToUTF16(
    368             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"),
    369         FontList(Font(this)));
    370     const double dialog_units_pixels = (text_width_pixels / 26 + 1) / 2;
    371     average_width_pixels_ = std::min(pango_width_pixels, dialog_units_pixels);
    372   }
    373 }
    374 
    375 double PlatformFontPango::GetAverageWidth() const {
    376   const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
    377   return average_width_pixels_;
    378 }
    379 
    380 ////////////////////////////////////////////////////////////////////////////////
    381 // PlatformFont, public:
    382 
    383 // static
    384 PlatformFont* PlatformFont::CreateDefault() {
    385   return new PlatformFontPango;
    386 }
    387 
    388 // static
    389 PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
    390   return new PlatformFontPango(native_font);
    391 }
    392 
    393 // static
    394 PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
    395                                                   int font_size) {
    396   return new PlatformFontPango(font_name, font_size);
    397 }
    398 
    399 }  // namespace gfx
    400