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