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