// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "gfx/platform_font_gtk.h" #include #include #include #include #include #include #include "base/logging.h" #include "base/string_piece.h" #include "base/sys_string_conversions.h" #include "base/utf_string_conversions.h" #include "gfx/canvas_skia.h" #include "gfx/font.h" #include "gfx/gtk_util.h" #include "third_party/skia/include/core/SkTypeface.h" #include "third_party/skia/include/core/SkPaint.h" namespace { // The font family name which is used when a user's application font for // GNOME/KDE is a non-scalable one. The name should be listed in the // IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp. const char* kFallbackFontFamilyName = "sans"; // Retrieves the pango metrics for a pango font description. Caches the metrics // and never frees them. The metrics objects are relatively small and // very expensive to look up. PangoFontMetrics* GetPangoFontMetrics(PangoFontDescription* desc) { static std::map* desc_to_metrics = NULL; static PangoContext* context = NULL; if (!context) { context = gdk_pango_context_get_for_screen(gdk_screen_get_default()); pango_context_set_language(context, pango_language_get_default()); } if (!desc_to_metrics) { desc_to_metrics = new std::map(); } int desc_hash = pango_font_description_hash(desc); std::map::iterator i = desc_to_metrics->find(desc_hash); if (i == desc_to_metrics->end()) { PangoFontMetrics* metrics = pango_context_get_metrics(context, desc, NULL); (*desc_to_metrics)[desc_hash] = metrics; return metrics; } else { return i->second; } } // Find the best match font for |family_name| in the same way as Skia // to make sure CreateFont() successfully creates a default font. In // Skia, it only checks the best match font. If it failed to find // one, SkTypeface will be NULL for that font family. It eventually // causes a segfault. For example, family_name = "Sans" and system // may have various fonts. The first font family in FcPattern will be // "DejaVu Sans" but a font family returned by FcFontMatch will be "VL // PGothic". In this case, SkTypeface for "Sans" returns NULL even if // the system has a font for "Sans" font family. See FontMatch() in // skia/ports/SkFontHost_fontconfig.cpp for more detail. std::wstring FindBestMatchFontFamilyName(const char* family_name) { FcPattern* pattern = FcPatternCreate(); FcValue fcvalue; fcvalue.type = FcTypeString; char* family_name_copy = strdup(family_name); fcvalue.u.s = reinterpret_cast(family_name_copy); FcPatternAdd(pattern, FC_FAMILY, fcvalue, 0); FcConfigSubstitute(0, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); FcResult result; FcPattern* match = FcFontMatch(0, pattern, &result); DCHECK(match) << "Could not find font: " << family_name; FcChar8* match_family; FcPatternGetString(match, FC_FAMILY, 0, &match_family); std::wstring font_family = UTF8ToWide(reinterpret_cast(match_family)); FcPatternDestroy(match); FcPatternDestroy(pattern); free(family_name_copy); return font_family; } } // namespace namespace gfx { Font* PlatformFontGtk::default_font_ = NULL; //////////////////////////////////////////////////////////////////////////////// // PlatformFontGtk, public: PlatformFontGtk::PlatformFontGtk() { if (default_font_ == NULL) { GtkSettings* settings = gtk_settings_get_default(); gchar* font_name = NULL; g_object_get(settings, "gtk-font-name", &font_name, NULL); // Temporary CHECK for helping track down // http://code.google.com/p/chromium/issues/detail?id=12530 CHECK(font_name) << " Unable to get gtk-font-name for default font."; PangoFontDescription* desc = pango_font_description_from_string(font_name); default_font_ = new Font(desc); pango_font_description_free(desc); g_free(font_name); DCHECK(default_font_); } InitFromPlatformFont( static_cast(default_font_->platform_font())); } PlatformFontGtk::PlatformFontGtk(const Font& other) { InitFromPlatformFont( static_cast(other.platform_font())); } PlatformFontGtk::PlatformFontGtk(NativeFont native_font) { gint size = pango_font_description_get_size(native_font); const char* family_name = pango_font_description_get_family(native_font); if (pango_font_description_get_size_is_absolute(native_font)) { // font_size_ is treated as scaled (see comment in GetPangoScaleFactor). If // we get here the font size is absolute though, and we need to invert the // scale so that when scaled in the rest of this class everything lines up. size /= GetPangoScaleFactor(); } // Find best match font for |family_name| to make sure we can get // a SkTypeface for the default font. // TODO(agl): remove this. std::wstring font_family = FindBestMatchFontFamilyName(family_name); InitWithNameAndSize(font_family, size / PANGO_SCALE); int style = 0; if (pango_font_description_get_weight(native_font) == PANGO_WEIGHT_BOLD) { // TODO(davemoore) What should we do about other weights? We currently // only support BOLD. style |= gfx::Font::BOLD; } if (pango_font_description_get_style(native_font) == PANGO_STYLE_ITALIC) { // TODO(davemoore) What about PANGO_STYLE_OBLIQUE? style |= gfx::Font::ITALIC; } if (style != 0) style_ = style; } PlatformFontGtk::PlatformFontGtk(const std::wstring& font_name, int font_size) { InitWithNameAndSize(font_name, font_size); } double PlatformFontGtk::underline_position() const { const_cast(this)->InitPangoMetrics(); return underline_position_; } double PlatformFontGtk::underline_thickness() const { const_cast(this)->InitPangoMetrics(); return underline_thickness_; } //////////////////////////////////////////////////////////////////////////////// // PlatformFontGtk, PlatformFont implementation: Font PlatformFontGtk::DeriveFont(int size_delta, int style) const { // If the delta is negative, if must not push the size below 1 if (size_delta < 0) DCHECK_LT(-size_delta, font_size_); if (style == style_) { // Fast path, we just use the same typeface at a different size return Font(new PlatformFontGtk(typeface_, font_family_, font_size_ + size_delta, style_)); } // If the style has changed we may need to load a new face int skstyle = SkTypeface::kNormal; if (gfx::Font::BOLD & style) skstyle |= SkTypeface::kBold; if (gfx::Font::ITALIC & style) skstyle |= SkTypeface::kItalic; SkTypeface* typeface = SkTypeface::CreateFromName( base::SysWideToUTF8(font_family_).c_str(), static_cast(skstyle)); SkAutoUnref tf_helper(typeface); return Font(new PlatformFontGtk(typeface, font_family_, font_size_ + size_delta, style)); } int PlatformFontGtk::GetHeight() const { return height_; } int PlatformFontGtk::GetBaseline() const { return ascent_; } int PlatformFontGtk::GetAverageCharacterWidth() const { return SkScalarRound(average_width_); } int PlatformFontGtk::GetStringWidth(const std::wstring& text) const { int width = 0, height = 0; CanvasSkia::SizeStringInt(WideToUTF16Hack(text), Font(const_cast(this)), &width, &height, gfx::Canvas::NO_ELLIPSIS); return width; } int PlatformFontGtk::GetExpectedTextWidth(int length) const { double char_width = const_cast(this)->GetAverageWidth(); return round(static_cast(length) * char_width); } int PlatformFontGtk::GetStyle() const { return style_; } const std::wstring& PlatformFontGtk::GetFontName() const { return font_family_; } int PlatformFontGtk::GetFontSize() const { return font_size_; } NativeFont PlatformFontGtk::GetNativeFont() const { PangoFontDescription* pfd = pango_font_description_new(); pango_font_description_set_family(pfd, WideToUTF8(GetFontName()).c_str()); // Set the absolute size to avoid overflowing UI elements. pango_font_description_set_absolute_size(pfd, GetFontSize() * PANGO_SCALE * GetPangoScaleFactor()); switch (GetStyle()) { case gfx::Font::NORMAL: // Nothing to do, should already be PANGO_STYLE_NORMAL. break; case gfx::Font::BOLD: pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD); break; case gfx::Font::ITALIC: pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC); break; case gfx::Font::UNDERLINED: // TODO(deanm): How to do underlined? Where do we use it? Probably have // to paint it ourselves, see pango_font_metrics_get_underline_position. break; } return pfd; } //////////////////////////////////////////////////////////////////////////////// // PlatformFontGtk, private: PlatformFontGtk::PlatformFontGtk(SkTypeface* typeface, const std::wstring& name, int size, int style) { InitWithTypefaceNameSizeAndStyle(typeface, name, size, style); } PlatformFontGtk::~PlatformFontGtk() {} void PlatformFontGtk::InitWithNameAndSize(const std::wstring& font_name, int font_size) { DCHECK_GT(font_size, 0); std::wstring fallback; SkTypeface* typeface = SkTypeface::CreateFromName( base::SysWideToUTF8(font_name).c_str(), SkTypeface::kNormal); if (!typeface) { // A non-scalable font such as .pcf is specified. Falls back to a default // scalable font. typeface = SkTypeface::CreateFromName( kFallbackFontFamilyName, SkTypeface::kNormal); CHECK(typeface) << "Could not find any font: " << base::SysWideToUTF8(font_name) << ", " << kFallbackFontFamilyName; fallback = base::SysUTF8ToWide(kFallbackFontFamilyName); } SkAutoUnref typeface_helper(typeface); InitWithTypefaceNameSizeAndStyle(typeface, fallback.empty() ? font_name : fallback, font_size, gfx::Font::NORMAL); } void PlatformFontGtk::InitWithTypefaceNameSizeAndStyle( SkTypeface* typeface, const std::wstring& font_family, int font_size, int style) { typeface_helper_.reset(new SkAutoUnref(typeface)); typeface_ = typeface; typeface_->ref(); font_family_ = font_family; font_size_ = font_size; style_ = style; pango_metrics_inited_ = false; average_width_ = 0.0f; underline_position_ = 0.0f; underline_thickness_ = 0.0f; SkPaint paint; SkPaint::FontMetrics metrics; PaintSetup(&paint); paint.getFontMetrics(&metrics); ascent_ = SkScalarCeil(-metrics.fAscent); height_ = ascent_ + SkScalarCeil(metrics.fDescent); } void PlatformFontGtk::InitFromPlatformFont(const PlatformFontGtk* other) { typeface_helper_.reset(new SkAutoUnref(other->typeface_)); typeface_ = other->typeface_; typeface_->ref(); font_family_ = other->font_family_; font_size_ = other->font_size_; style_ = other->style_; height_ = other->height_; ascent_ = other->ascent_; pango_metrics_inited_ = other->pango_metrics_inited_; average_width_ = other->average_width_; underline_position_ = other->underline_position_; underline_thickness_ = other->underline_thickness_; } void PlatformFontGtk::PaintSetup(SkPaint* paint) const { paint->setAntiAlias(false); paint->setSubpixelText(false); paint->setTextSize( SkFloatToScalar(font_size_ * GetPangoScaleFactor())); paint->setTypeface(typeface_); paint->setFakeBoldText((gfx::Font::BOLD & style_) && !typeface_->isBold()); paint->setTextSkewX((gfx::Font::ITALIC & style_) && !typeface_->isItalic() ? -SK_Scalar1/4 : 0); } void PlatformFontGtk::InitPangoMetrics() { if (!pango_metrics_inited_) { pango_metrics_inited_ = true; PangoFontDescription* pango_desc = GetNativeFont(); PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc); underline_position_ = pango_font_metrics_get_underline_position(pango_metrics); underline_position_ /= PANGO_SCALE; // todo(davemoore) Come up with a better solution. // This is a hack, but without doing this the underlines // we get end up fuzzy. So we align to the midpoint of a pixel. underline_position_ /= 2; underline_thickness_ = pango_font_metrics_get_underline_thickness(pango_metrics); underline_thickness_ /= PANGO_SCALE; // First get the pango based width double pango_width = pango_font_metrics_get_approximate_char_width(pango_metrics); pango_width /= PANGO_SCALE; // Yes, this is how Microsoft recommends calculating the dialog unit // conversions. int text_width = GetStringWidth( L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); double dialog_units = (text_width / 26 + 1) / 2; average_width_ = std::min(pango_width, dialog_units); pango_font_description_free(pango_desc); } } float PlatformFontGtk::GetPangoScaleFactor() { // Pango scales font sizes. This returns the scale factor. See // pango_cairo_context_set_resolution for details. // NOTE: this isn't entirely accurate, in that Pango also consults the // FC_PIXEL_SIZE first (see get_font_size in pangocairo-fcfont), but this // seems to give us the same sizes as used by Pango for all our fonts in both // English and Thai. static float scale_factor = gfx::GetPangoResolution(); static bool determined_scale = false; if (!determined_scale) { if (scale_factor <= 0) scale_factor = 1; else scale_factor /= 72.0; determined_scale = true; } return scale_factor; } double PlatformFontGtk::GetAverageWidth() const { const_cast(this)->InitPangoMetrics(); return average_width_; } //////////////////////////////////////////////////////////////////////////////// // PlatformFont, public: // static PlatformFont* PlatformFont::CreateDefault() { return new PlatformFontGtk; } // static PlatformFont* PlatformFont::CreateFromFont(const Font& other) { return new PlatformFontGtk(other); } // static PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) { return new PlatformFontGtk(native_font); } // static PlatformFont* PlatformFont::CreateFromNameAndSize(const std::wstring& font_name, int font_size) { return new PlatformFontGtk(font_name, font_size); } } // namespace gfx