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.
5 #include "ui/gfx/platform_font_pango.h"
7 #include <fontconfig/fontconfig.h>
8 #include <pango/pango.h>
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/font_list.h"
23 #include "ui/gfx/linux_font_delegate.h"
24 #include "ui/gfx/pango_util.h"
25 #include "ui/gfx/text_utils.h"
27 #if defined(TOOLKIT_GTK)
34 // The font family name which is used when a user's application font for
35 // GNOME/KDE is a non-scalable one. The name should be listed in the
36 // IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp.
37 const char* kFallbackFontFamilyName = "sans";
39 // Returns the available font family that best (in FontConfig's eyes) matches
40 // the supplied list of family names.
41 std::string FindBestMatchFontFamilyName(
42 const std::vector<std::string>& family_names) {
43 FcPattern* pattern = FcPatternCreate();
44 for (std::vector<std::string>::const_iterator it = family_names.begin();
45 it != family_names.end(); ++it) {
47 fcvalue.type = FcTypeString;
48 fcvalue.u.s = reinterpret_cast<const FcChar8*>(it->c_str());
49 FcPatternAdd(pattern, FC_FAMILY, fcvalue, FcTrue /* append */);
52 FcConfigSubstitute(0, pattern, FcMatchPattern);
53 FcDefaultSubstitute(pattern);
55 FcPattern* match = FcFontMatch(0, pattern, &result);
56 DCHECK(match) << "Could not find font";
57 FcChar8* match_family = NULL;
58 FcPatternGetString(match, FC_FAMILY, 0, &match_family);
59 std::string font_family(reinterpret_cast<char*>(match_family));
60 FcPatternDestroy(pattern);
61 FcPatternDestroy(match);
70 Font* PlatformFontPango::default_font_ = NULL;
72 #if defined(OS_CHROMEOS)
74 std::string* PlatformFontPango::default_font_description_ = NULL;
77 ////////////////////////////////////////////////////////////////////////////////
78 // PlatformFontPango, public:
80 PlatformFontPango::PlatformFontPango() {
81 if (default_font_ == NULL) {
82 std::string font_name = GetDefaultFont();
84 ScopedPangoFontDescription desc(
85 pango_font_description_from_string(font_name.c_str()));
86 default_font_ = new Font(desc.get());
88 DCHECK(default_font_);
92 static_cast<PlatformFontPango*>(default_font_->platform_font()));
95 PlatformFontPango::PlatformFontPango(NativeFont native_font) {
96 std::vector<std::string> family_names;
97 base::SplitString(pango_font_description_get_family(native_font), ',',
99 std::string font_family = FindBestMatchFontFamilyName(family_names);
100 InitWithNameAndSize(font_family, gfx::GetPangoFontSizeInPixels(native_font));
103 if (pango_font_description_get_weight(native_font) == PANGO_WEIGHT_BOLD) {
104 // TODO(davemoore) What should we do about other weights? We currently
105 // only support BOLD.
106 style |= gfx::Font::BOLD;
108 if (pango_font_description_get_style(native_font) == PANGO_STYLE_ITALIC) {
109 // TODO(davemoore) What about PANGO_STYLE_OBLIQUE?
110 style |= gfx::Font::ITALIC;
116 PlatformFontPango::PlatformFontPango(const std::string& font_name,
118 InitWithNameAndSize(font_name, font_size);
121 double PlatformFontPango::underline_position() const {
122 const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
123 return underline_position_pixels_;
126 double PlatformFontPango::underline_thickness() const {
127 const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
128 return underline_thickness_pixels_;
131 ////////////////////////////////////////////////////////////////////////////////
132 // PlatformFontPango, PlatformFont implementation:
135 void PlatformFontPango::ReloadDefaultFont() {
136 delete default_font_;
137 default_font_ = NULL;
140 #if defined(OS_CHROMEOS)
142 void PlatformFontPango::SetDefaultFontDescription(
143 const std::string& font_description) {
144 delete default_font_description_;
145 default_font_description_ = new std::string(font_description);
150 Font PlatformFontPango::DeriveFont(int size_delta, int style) const {
151 // If the delta is negative, if must not push the size below 1
153 DCHECK_LT(-size_delta, font_size_pixels_);
155 if (style == style_) {
156 // Fast path, we just use the same typeface at a different size
157 return Font(new PlatformFontPango(typeface_,
159 font_size_pixels_ + size_delta,
163 // If the style has changed we may need to load a new face
164 int skstyle = SkTypeface::kNormal;
165 if (gfx::Font::BOLD & style)
166 skstyle |= SkTypeface::kBold;
167 if (gfx::Font::ITALIC & style)
168 skstyle |= SkTypeface::kItalic;
170 skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(
171 SkTypeface::CreateFromName(
172 font_family_.c_str(),
173 static_cast<SkTypeface::Style>(skstyle)));
175 return Font(new PlatformFontPango(typeface,
177 font_size_pixels_ + size_delta,
181 int PlatformFontPango::GetHeight() const {
182 return height_pixels_;
185 int PlatformFontPango::GetBaseline() const {
186 return ascent_pixels_;
189 int PlatformFontPango::GetCapHeight() const {
190 return cap_height_pixels_;
193 int PlatformFontPango::GetExpectedTextWidth(int length) const {
194 double char_width = const_cast<PlatformFontPango*>(this)->GetAverageWidth();
195 return round(static_cast<float>(length) * char_width);
198 int PlatformFontPango::GetStyle() const {
202 std::string PlatformFontPango::GetFontName() const {
206 std::string PlatformFontPango::GetActualFontNameForTesting() const {
207 SkString family_name;
208 typeface_->getFamilyName(&family_name);
209 return family_name.c_str();
212 int PlatformFontPango::GetFontSize() const {
213 return font_size_pixels_;
216 NativeFont PlatformFontPango::GetNativeFont() const {
217 PangoFontDescription* pfd = pango_font_description_new();
218 pango_font_description_set_family(pfd, GetFontName().c_str());
219 // Set the absolute size to avoid overflowing UI elements.
220 // pango_font_description_set_absolute_size() takes a size in Pango units.
221 // There are PANGO_SCALE Pango units in one device unit. Screen output
222 // devices use pixels as their device units.
223 pango_font_description_set_absolute_size(
224 pfd, font_size_pixels_ * PANGO_SCALE);
226 switch (GetStyle()) {
227 case gfx::Font::NORMAL:
228 // Nothing to do, should already be PANGO_STYLE_NORMAL.
230 case gfx::Font::BOLD:
231 pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD);
233 case gfx::Font::ITALIC:
234 pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC);
236 case gfx::Font::UNDERLINE:
237 // TODO(deanm): How to do underline? Where do we use it? Probably have
238 // to paint it ourselves, see pango_font_metrics_get_underline_position.
245 ////////////////////////////////////////////////////////////////////////////////
246 // PlatformFontPango, private:
248 PlatformFontPango::PlatformFontPango(const skia::RefPtr<SkTypeface>& typeface,
249 const std::string& name,
252 InitWithTypefaceNameSizeAndStyle(typeface, name, size, style);
255 PlatformFontPango::~PlatformFontPango() {}
258 std::string PlatformFontPango::GetDefaultFont() {
259 #if !defined(TOOLKIT_GTK)
260 #if defined(OS_CHROMEOS)
261 // Font name must have been provided by way of SetDefaultFontDescription().
262 CHECK(default_font_description_);
263 return *default_font_description_;
265 const gfx::LinuxFontDelegate* delegate = gfx::LinuxFontDelegate::instance();
267 return delegate->GetDefaultFontName();
270 #endif // defined(OS_CHROMEOS)
272 GtkSettings* settings = gtk_settings_get_default();
274 gchar* font_name = NULL;
275 g_object_get(settings, "gtk-font-name", &font_name, NULL);
277 // Temporary CHECK for helping track down
278 // http://code.google.com/p/chromium/issues/detail?id=12530
279 CHECK(font_name) << " Unable to get gtk-font-name for default font.";
281 std::string default_font = std::string(font_name);
284 #endif // !defined(TOOLKIT_GTK)
288 void PlatformFontPango::InitWithNameAndSize(const std::string& font_name,
290 DCHECK_GT(font_size, 0);
291 std::string fallback;
293 skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(
294 SkTypeface::CreateFromName(font_name.c_str(), SkTypeface::kNormal));
296 // A non-scalable font such as .pcf is specified. Falls back to a default
298 typeface = skia::AdoptRef(
299 SkTypeface::CreateFromName(
300 kFallbackFontFamilyName, SkTypeface::kNormal));
301 CHECK(typeface) << "Could not find any font: "
303 << ", " << kFallbackFontFamilyName;
304 fallback = kFallbackFontFamilyName;
307 InitWithTypefaceNameSizeAndStyle(typeface,
308 fallback.empty() ? font_name : fallback,
313 void PlatformFontPango::InitWithTypefaceNameSizeAndStyle(
314 const skia::RefPtr<SkTypeface>& typeface,
315 const std::string& font_family,
318 typeface_ = typeface;
319 font_family_ = font_family;
320 font_size_pixels_ = font_size;
322 pango_metrics_inited_ = false;
323 average_width_pixels_ = 0.0f;
324 underline_position_pixels_ = 0.0f;
325 underline_thickness_pixels_ = 0.0f;
328 SkPaint::FontMetrics metrics;
330 paint.getFontMetrics(&metrics);
332 ascent_pixels_ = SkScalarCeilToInt(-metrics.fAscent);
333 height_pixels_ = ascent_pixels_ + SkScalarCeilToInt(metrics.fDescent);
334 cap_height_pixels_ = SkScalarCeilToInt(metrics.fCapHeight);
337 void PlatformFontPango::InitFromPlatformFont(const PlatformFontPango* other) {
338 typeface_ = other->typeface_;
339 font_family_ = other->font_family_;
340 font_size_pixels_ = other->font_size_pixels_;
341 style_ = other->style_;
342 height_pixels_ = other->height_pixels_;
343 ascent_pixels_ = other->ascent_pixels_;
344 cap_height_pixels_ = other->cap_height_pixels_;
345 pango_metrics_inited_ = other->pango_metrics_inited_;
346 average_width_pixels_ = other->average_width_pixels_;
347 underline_position_pixels_ = other->underline_position_pixels_;
348 underline_thickness_pixels_ = other->underline_thickness_pixels_;
351 void PlatformFontPango::PaintSetup(SkPaint* paint) const {
352 paint->setAntiAlias(false);
353 paint->setSubpixelText(false);
354 paint->setTextSize(font_size_pixels_);
355 paint->setTypeface(typeface_.get());
356 paint->setFakeBoldText((gfx::Font::BOLD & style_) && !typeface_->isBold());
357 paint->setTextSkewX((gfx::Font::ITALIC & style_) && !typeface_->isItalic() ?
361 void PlatformFontPango::InitPangoMetrics() {
362 if (!pango_metrics_inited_) {
363 pango_metrics_inited_ = true;
364 ScopedPangoFontDescription pango_desc(GetNativeFont());
365 PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc.get());
367 underline_position_pixels_ =
368 pango_font_metrics_get_underline_position(pango_metrics) /
371 // TODO(davemoore): Come up with a better solution.
372 // This is a hack, but without doing this the underlines
373 // we get end up fuzzy. So we align to the midpoint of a pixel.
374 underline_position_pixels_ /= 2;
376 underline_thickness_pixels_ =
377 pango_font_metrics_get_underline_thickness(pango_metrics) /
380 // First get the Pango-based width (converting from Pango units to pixels).
381 const double pango_width_pixels =
382 pango_font_metrics_get_approximate_char_width(pango_metrics) /
385 // Yes, this is how Microsoft recommends calculating the dialog unit
387 const int text_width_pixels = GetStringWidth(
389 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"),
390 FontList(Font(this)));
391 const double dialog_units_pixels = (text_width_pixels / 26 + 1) / 2;
392 average_width_pixels_ = std::min(pango_width_pixels, dialog_units_pixels);
396 double PlatformFontPango::GetAverageWidth() const {
397 const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
398 return average_width_pixels_;
401 ////////////////////////////////////////////////////////////////////////////////
402 // PlatformFont, public:
405 PlatformFont* PlatformFont::CreateDefault() {
406 return new PlatformFontPango;
410 PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
411 return new PlatformFontPango(native_font);
415 PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
417 return new PlatformFontPango(font_name, font_size);