| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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 "gfx/platform_font_gtk.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <fontconfig/fontconfig.h> | |
| 9 #include <gdk/gdk.h> | |
| 10 #include <gtk/gtk.h> | |
| 11 #include <map> | |
| 12 #include <pango/pango.h> | |
| 13 | |
| 14 #include "base/logging.h" | |
| 15 #include "base/string_piece.h" | |
| 16 #include "base/utf_string_conversions.h" | |
| 17 #include "gfx/canvas_skia.h" | |
| 18 #include "gfx/font.h" | |
| 19 #include "gfx/gtk_util.h" | |
| 20 #include "third_party/skia/include/core/SkTypeface.h" | |
| 21 #include "third_party/skia/include/core/SkPaint.h" | |
| 22 | |
| 23 namespace { | |
| 24 | |
| 25 // The font family name which is used when a user's application font for | |
| 26 // GNOME/KDE is a non-scalable one. The name should be listed in the | |
| 27 // IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp. | |
| 28 const char* kFallbackFontFamilyName = "sans"; | |
| 29 | |
| 30 // Returns the number of pixels in a point. | |
| 31 // - multiply a point size by this to get pixels ("device units") | |
| 32 // - divide a pixel size by this to get points | |
| 33 float GetPixelsInPoint() { | |
| 34 static float pixels_in_point = 1.0; | |
| 35 static bool determined_value = false; | |
| 36 | |
| 37 if (!determined_value) { | |
| 38 // http://goo.gl/UIh5m: "This is a scale factor between points specified in | |
| 39 // a PangoFontDescription and Cairo units. The default value is 96, meaning | |
| 40 // that a 10 point font will be 13 units high. (10 * 96. / 72. = 13.3)." | |
| 41 double pango_dpi = gfx::GetPangoResolution(); | |
| 42 if (pango_dpi <= 0) | |
| 43 pango_dpi = 96.0; | |
| 44 pixels_in_point = pango_dpi / 72.0; // 72 points in an inch | |
| 45 determined_value = true; | |
| 46 } | |
| 47 | |
| 48 return pixels_in_point; | |
| 49 } | |
| 50 | |
| 51 // Retrieves the pango metrics for a pango font description. Caches the metrics | |
| 52 // and never frees them. The metrics objects are relatively small and | |
| 53 // very expensive to look up. | |
| 54 PangoFontMetrics* GetPangoFontMetrics(PangoFontDescription* desc) { | |
| 55 static std::map<int, PangoFontMetrics*>* desc_to_metrics = NULL; | |
| 56 static PangoContext* context = NULL; | |
| 57 | |
| 58 if (!context) { | |
| 59 context = gdk_pango_context_get_for_screen(gdk_screen_get_default()); | |
| 60 pango_context_set_language(context, pango_language_get_default()); | |
| 61 } | |
| 62 | |
| 63 if (!desc_to_metrics) { | |
| 64 desc_to_metrics = new std::map<int, PangoFontMetrics*>(); | |
| 65 } | |
| 66 | |
| 67 int desc_hash = pango_font_description_hash(desc); | |
| 68 std::map<int, PangoFontMetrics*>::iterator i = | |
| 69 desc_to_metrics->find(desc_hash); | |
| 70 | |
| 71 if (i == desc_to_metrics->end()) { | |
| 72 PangoFontMetrics* metrics = pango_context_get_metrics(context, desc, NULL); | |
| 73 (*desc_to_metrics)[desc_hash] = metrics; | |
| 74 return metrics; | |
| 75 } else { | |
| 76 return i->second; | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 // Find the best match font for |family_name| in the same way as Skia | |
| 81 // to make sure CreateFont() successfully creates a default font. In | |
| 82 // Skia, it only checks the best match font. If it failed to find | |
| 83 // one, SkTypeface will be NULL for that font family. It eventually | |
| 84 // causes a segfault. For example, family_name = "Sans" and system | |
| 85 // may have various fonts. The first font family in FcPattern will be | |
| 86 // "DejaVu Sans" but a font family returned by FcFontMatch will be "VL | |
| 87 // PGothic". In this case, SkTypeface for "Sans" returns NULL even if | |
| 88 // the system has a font for "Sans" font family. See FontMatch() in | |
| 89 // skia/ports/SkFontHost_fontconfig.cpp for more detail. | |
| 90 string16 FindBestMatchFontFamilyName(const char* family_name) { | |
| 91 FcPattern* pattern = FcPatternCreate(); | |
| 92 FcValue fcvalue; | |
| 93 fcvalue.type = FcTypeString; | |
| 94 char* family_name_copy = strdup(family_name); | |
| 95 fcvalue.u.s = reinterpret_cast<FcChar8*>(family_name_copy); | |
| 96 FcPatternAdd(pattern, FC_FAMILY, fcvalue, 0); | |
| 97 FcConfigSubstitute(0, pattern, FcMatchPattern); | |
| 98 FcDefaultSubstitute(pattern); | |
| 99 FcResult result; | |
| 100 FcPattern* match = FcFontMatch(0, pattern, &result); | |
| 101 DCHECK(match) << "Could not find font: " << family_name; | |
| 102 FcChar8* match_family; | |
| 103 FcPatternGetString(match, FC_FAMILY, 0, &match_family); | |
| 104 | |
| 105 string16 font_family = UTF8ToUTF16(reinterpret_cast<char*>(match_family)); | |
| 106 FcPatternDestroy(match); | |
| 107 FcPatternDestroy(pattern); | |
| 108 free(family_name_copy); | |
| 109 return font_family; | |
| 110 } | |
| 111 | |
| 112 } // namespace | |
| 113 | |
| 114 namespace gfx { | |
| 115 | |
| 116 Font* PlatformFontGtk::default_font_ = NULL; | |
| 117 | |
| 118 //////////////////////////////////////////////////////////////////////////////// | |
| 119 // PlatformFontGtk, public: | |
| 120 | |
| 121 PlatformFontGtk::PlatformFontGtk() { | |
| 122 if (default_font_ == NULL) { | |
| 123 GtkSettings* settings = gtk_settings_get_default(); | |
| 124 | |
| 125 gchar* font_name = NULL; | |
| 126 g_object_get(settings, "gtk-font-name", &font_name, NULL); | |
| 127 | |
| 128 // Temporary CHECK for helping track down | |
| 129 // http://code.google.com/p/chromium/issues/detail?id=12530 | |
| 130 CHECK(font_name) << " Unable to get gtk-font-name for default font."; | |
| 131 | |
| 132 PangoFontDescription* desc = | |
| 133 pango_font_description_from_string(font_name); | |
| 134 default_font_ = new Font(desc); | |
| 135 pango_font_description_free(desc); | |
| 136 g_free(font_name); | |
| 137 | |
| 138 DCHECK(default_font_); | |
| 139 } | |
| 140 | |
| 141 InitFromPlatformFont( | |
| 142 static_cast<PlatformFontGtk*>(default_font_->platform_font())); | |
| 143 } | |
| 144 | |
| 145 PlatformFontGtk::PlatformFontGtk(const Font& other) { | |
| 146 InitFromPlatformFont( | |
| 147 static_cast<PlatformFontGtk*>(other.platform_font())); | |
| 148 } | |
| 149 | |
| 150 PlatformFontGtk::PlatformFontGtk(NativeFont native_font) { | |
| 151 const char* family_name = pango_font_description_get_family(native_font); | |
| 152 | |
| 153 gint size_in_pixels = 0; | |
| 154 if (pango_font_description_get_size_is_absolute(native_font)) { | |
| 155 // If the size is absolute, then it's in Pango units rather than points. | |
| 156 // There are PANGO_SCALE Pango units in a device unit (pixel). | |
| 157 size_in_pixels = pango_font_description_get_size(native_font) / PANGO_SCALE; | |
| 158 } else { | |
| 159 // Otherwise, we need to convert from points. | |
| 160 size_in_pixels = | |
| 161 pango_font_description_get_size(native_font) * GetPixelsInPoint() / | |
| 162 PANGO_SCALE; | |
| 163 } | |
| 164 | |
| 165 // Find best match font for |family_name| to make sure we can get | |
| 166 // a SkTypeface for the default font. | |
| 167 // TODO(agl): remove this. | |
| 168 string16 font_family = FindBestMatchFontFamilyName(family_name); | |
| 169 | |
| 170 InitWithNameAndSize(font_family, size_in_pixels); | |
| 171 int style = 0; | |
| 172 if (pango_font_description_get_weight(native_font) == PANGO_WEIGHT_BOLD) { | |
| 173 // TODO(davemoore) What should we do about other weights? We currently | |
| 174 // only support BOLD. | |
| 175 style |= gfx::Font::BOLD; | |
| 176 } | |
| 177 if (pango_font_description_get_style(native_font) == PANGO_STYLE_ITALIC) { | |
| 178 // TODO(davemoore) What about PANGO_STYLE_OBLIQUE? | |
| 179 style |= gfx::Font::ITALIC; | |
| 180 } | |
| 181 if (style != 0) | |
| 182 style_ = style; | |
| 183 } | |
| 184 | |
| 185 PlatformFontGtk::PlatformFontGtk(const string16& font_name, | |
| 186 int font_size) { | |
| 187 InitWithNameAndSize(font_name, font_size); | |
| 188 } | |
| 189 | |
| 190 double PlatformFontGtk::underline_position() const { | |
| 191 const_cast<PlatformFontGtk*>(this)->InitPangoMetrics(); | |
| 192 return underline_position_pixels_; | |
| 193 } | |
| 194 | |
| 195 double PlatformFontGtk::underline_thickness() const { | |
| 196 const_cast<PlatformFontGtk*>(this)->InitPangoMetrics(); | |
| 197 return underline_thickness_pixels_; | |
| 198 } | |
| 199 | |
| 200 //////////////////////////////////////////////////////////////////////////////// | |
| 201 // PlatformFontGtk, PlatformFont implementation: | |
| 202 | |
| 203 Font PlatformFontGtk::DeriveFont(int size_delta, int style) const { | |
| 204 // If the delta is negative, if must not push the size below 1 | |
| 205 if (size_delta < 0) | |
| 206 DCHECK_LT(-size_delta, font_size_pixels_); | |
| 207 | |
| 208 if (style == style_) { | |
| 209 // Fast path, we just use the same typeface at a different size | |
| 210 return Font(new PlatformFontGtk(typeface_, | |
| 211 font_family_, | |
| 212 font_size_pixels_ + size_delta, | |
| 213 style_)); | |
| 214 } | |
| 215 | |
| 216 // If the style has changed we may need to load a new face | |
| 217 int skstyle = SkTypeface::kNormal; | |
| 218 if (gfx::Font::BOLD & style) | |
| 219 skstyle |= SkTypeface::kBold; | |
| 220 if (gfx::Font::ITALIC & style) | |
| 221 skstyle |= SkTypeface::kItalic; | |
| 222 | |
| 223 SkTypeface* typeface = SkTypeface::CreateFromName( | |
| 224 UTF16ToUTF8(font_family_).c_str(), | |
| 225 static_cast<SkTypeface::Style>(skstyle)); | |
| 226 SkAutoUnref tf_helper(typeface); | |
| 227 | |
| 228 return Font(new PlatformFontGtk(typeface, | |
| 229 font_family_, | |
| 230 font_size_pixels_ + size_delta, | |
| 231 style)); | |
| 232 } | |
| 233 | |
| 234 int PlatformFontGtk::GetHeight() const { | |
| 235 return height_pixels_; | |
| 236 } | |
| 237 | |
| 238 int PlatformFontGtk::GetBaseline() const { | |
| 239 return ascent_pixels_; | |
| 240 } | |
| 241 | |
| 242 int PlatformFontGtk::GetAverageCharacterWidth() const { | |
| 243 return SkScalarRound(average_width_pixels_); | |
| 244 } | |
| 245 | |
| 246 int PlatformFontGtk::GetStringWidth(const string16& text) const { | |
| 247 int width = 0, height = 0; | |
| 248 CanvasSkia::SizeStringInt(text, Font(const_cast<PlatformFontGtk*>(this)), | |
| 249 &width, &height, gfx::Canvas::NO_ELLIPSIS); | |
| 250 return width; | |
| 251 } | |
| 252 | |
| 253 int PlatformFontGtk::GetExpectedTextWidth(int length) const { | |
| 254 double char_width = const_cast<PlatformFontGtk*>(this)->GetAverageWidth(); | |
| 255 return round(static_cast<float>(length) * char_width); | |
| 256 } | |
| 257 | |
| 258 int PlatformFontGtk::GetStyle() const { | |
| 259 return style_; | |
| 260 } | |
| 261 | |
| 262 string16 PlatformFontGtk::GetFontName() const { | |
| 263 return font_family_; | |
| 264 } | |
| 265 | |
| 266 int PlatformFontGtk::GetFontSize() const { | |
| 267 return font_size_pixels_; | |
| 268 } | |
| 269 | |
| 270 NativeFont PlatformFontGtk::GetNativeFont() const { | |
| 271 PangoFontDescription* pfd = pango_font_description_new(); | |
| 272 pango_font_description_set_family(pfd, UTF16ToUTF8(GetFontName()).c_str()); | |
| 273 // Set the absolute size to avoid overflowing UI elements. | |
| 274 // pango_font_description_set_absolute_size() takes a size in Pango units. | |
| 275 // There are PANGO_SCALE Pango units in one device unit. Screen output | |
| 276 // devices use pixels as their device units. | |
| 277 pango_font_description_set_absolute_size( | |
| 278 pfd, font_size_pixels_ * PANGO_SCALE); | |
| 279 | |
| 280 switch (GetStyle()) { | |
| 281 case gfx::Font::NORMAL: | |
| 282 // Nothing to do, should already be PANGO_STYLE_NORMAL. | |
| 283 break; | |
| 284 case gfx::Font::BOLD: | |
| 285 pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD); | |
| 286 break; | |
| 287 case gfx::Font::ITALIC: | |
| 288 pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC); | |
| 289 break; | |
| 290 case gfx::Font::UNDERLINED: | |
| 291 // TODO(deanm): How to do underlined? Where do we use it? Probably have | |
| 292 // to paint it ourselves, see pango_font_metrics_get_underline_position. | |
| 293 break; | |
| 294 } | |
| 295 | |
| 296 return pfd; | |
| 297 } | |
| 298 | |
| 299 //////////////////////////////////////////////////////////////////////////////// | |
| 300 // PlatformFontGtk, private: | |
| 301 | |
| 302 PlatformFontGtk::PlatformFontGtk(SkTypeface* typeface, | |
| 303 const string16& name, | |
| 304 int size, | |
| 305 int style) { | |
| 306 InitWithTypefaceNameSizeAndStyle(typeface, name, size, style); | |
| 307 } | |
| 308 | |
| 309 PlatformFontGtk::~PlatformFontGtk() {} | |
| 310 | |
| 311 void PlatformFontGtk::InitWithNameAndSize(const string16& font_name, | |
| 312 int font_size) { | |
| 313 DCHECK_GT(font_size, 0); | |
| 314 string16 fallback; | |
| 315 | |
| 316 SkTypeface* typeface = SkTypeface::CreateFromName( | |
| 317 UTF16ToUTF8(font_name).c_str(), SkTypeface::kNormal); | |
| 318 if (!typeface) { | |
| 319 // A non-scalable font such as .pcf is specified. Falls back to a default | |
| 320 // scalable font. | |
| 321 typeface = SkTypeface::CreateFromName( | |
| 322 kFallbackFontFamilyName, SkTypeface::kNormal); | |
| 323 CHECK(typeface) << "Could not find any font: " | |
| 324 << UTF16ToUTF8(font_name) | |
| 325 << ", " << kFallbackFontFamilyName; | |
| 326 fallback = UTF8ToUTF16(kFallbackFontFamilyName); | |
| 327 } | |
| 328 SkAutoUnref typeface_helper(typeface); | |
| 329 | |
| 330 InitWithTypefaceNameSizeAndStyle(typeface, | |
| 331 fallback.empty() ? font_name : fallback, | |
| 332 font_size, | |
| 333 gfx::Font::NORMAL); | |
| 334 } | |
| 335 | |
| 336 void PlatformFontGtk::InitWithTypefaceNameSizeAndStyle( | |
| 337 SkTypeface* typeface, | |
| 338 const string16& font_family, | |
| 339 int font_size, | |
| 340 int style) { | |
| 341 typeface_helper_.reset(new SkAutoUnref(typeface)); | |
| 342 typeface_ = typeface; | |
| 343 typeface_->ref(); | |
| 344 font_family_ = font_family; | |
| 345 font_size_pixels_ = font_size; | |
| 346 style_ = style; | |
| 347 pango_metrics_inited_ = false; | |
| 348 average_width_pixels_ = 0.0f; | |
| 349 underline_position_pixels_ = 0.0f; | |
| 350 underline_thickness_pixels_ = 0.0f; | |
| 351 | |
| 352 SkPaint paint; | |
| 353 SkPaint::FontMetrics metrics; | |
| 354 PaintSetup(&paint); | |
| 355 paint.getFontMetrics(&metrics); | |
| 356 | |
| 357 ascent_pixels_ = SkScalarCeil(-metrics.fAscent); | |
| 358 height_pixels_ = ascent_pixels_ + SkScalarCeil(metrics.fDescent); | |
| 359 } | |
| 360 | |
| 361 void PlatformFontGtk::InitFromPlatformFont(const PlatformFontGtk* other) { | |
| 362 typeface_helper_.reset(new SkAutoUnref(other->typeface_)); | |
| 363 typeface_ = other->typeface_; | |
| 364 typeface_->ref(); | |
| 365 font_family_ = other->font_family_; | |
| 366 font_size_pixels_ = other->font_size_pixels_; | |
| 367 style_ = other->style_; | |
| 368 height_pixels_ = other->height_pixels_; | |
| 369 ascent_pixels_ = other->ascent_pixels_; | |
| 370 pango_metrics_inited_ = other->pango_metrics_inited_; | |
| 371 average_width_pixels_ = other->average_width_pixels_; | |
| 372 underline_position_pixels_ = other->underline_position_pixels_; | |
| 373 underline_thickness_pixels_ = other->underline_thickness_pixels_; | |
| 374 } | |
| 375 | |
| 376 void PlatformFontGtk::PaintSetup(SkPaint* paint) const { | |
| 377 paint->setAntiAlias(false); | |
| 378 paint->setSubpixelText(false); | |
| 379 paint->setTextSize(font_size_pixels_); | |
| 380 paint->setTypeface(typeface_); | |
| 381 paint->setFakeBoldText((gfx::Font::BOLD & style_) && !typeface_->isBold()); | |
| 382 paint->setTextSkewX((gfx::Font::ITALIC & style_) && !typeface_->isItalic() ? | |
| 383 -SK_Scalar1/4 : 0); | |
| 384 } | |
| 385 | |
| 386 void PlatformFontGtk::InitPangoMetrics() { | |
| 387 if (!pango_metrics_inited_) { | |
| 388 pango_metrics_inited_ = true; | |
| 389 PangoFontDescription* pango_desc = GetNativeFont(); | |
| 390 PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc); | |
| 391 | |
| 392 underline_position_pixels_ = | |
| 393 pango_font_metrics_get_underline_position(pango_metrics) / | |
| 394 PANGO_SCALE; | |
| 395 | |
| 396 // TODO(davemoore): Come up with a better solution. | |
| 397 // This is a hack, but without doing this the underlines | |
| 398 // we get end up fuzzy. So we align to the midpoint of a pixel. | |
| 399 underline_position_pixels_ /= 2; | |
| 400 | |
| 401 underline_thickness_pixels_ = | |
| 402 pango_font_metrics_get_underline_thickness(pango_metrics) / | |
| 403 PANGO_SCALE; | |
| 404 | |
| 405 // First get the Pango-based width (converting from Pango units to pixels). | |
| 406 double pango_width_pixels = | |
| 407 pango_font_metrics_get_approximate_char_width(pango_metrics) / | |
| 408 PANGO_SCALE; | |
| 409 | |
| 410 // Yes, this is how Microsoft recommends calculating the dialog unit | |
| 411 // conversions. | |
| 412 int text_width_pixels = GetStringWidth( | |
| 413 ASCIIToUTF16("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")); | |
| 414 double dialog_units_pixels = (text_width_pixels / 26 + 1) / 2; | |
| 415 average_width_pixels_ = std::min(pango_width_pixels, dialog_units_pixels); | |
| 416 pango_font_description_free(pango_desc); | |
| 417 } | |
| 418 } | |
| 419 | |
| 420 | |
| 421 double PlatformFontGtk::GetAverageWidth() const { | |
| 422 const_cast<PlatformFontGtk*>(this)->InitPangoMetrics(); | |
| 423 return average_width_pixels_; | |
| 424 } | |
| 425 | |
| 426 //////////////////////////////////////////////////////////////////////////////// | |
| 427 // PlatformFont, public: | |
| 428 | |
| 429 // static | |
| 430 PlatformFont* PlatformFont::CreateDefault() { | |
| 431 return new PlatformFontGtk; | |
| 432 } | |
| 433 | |
| 434 // static | |
| 435 PlatformFont* PlatformFont::CreateFromFont(const Font& other) { | |
| 436 return new PlatformFontGtk(other); | |
| 437 } | |
| 438 | |
| 439 // static | |
| 440 PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) { | |
| 441 return new PlatformFontGtk(native_font); | |
| 442 } | |
| 443 | |
| 444 // static | |
| 445 PlatformFont* PlatformFont::CreateFromNameAndSize(const string16& font_name, | |
| 446 int font_size) { | |
| 447 return new PlatformFontGtk(font_name, font_size); | |
| 448 } | |
| 449 | |
| 450 } // namespace gfx | |
| OLD | NEW |