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