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/SkTypeface.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/font.h"
21 #include "ui/gfx/pango_util.h"
23 #if defined(TOOLKIT_GTK)
30 // The font family name which is used when a user's application font for
31 // GNOME/KDE is a non-scalable one. The name should be listed in the
32 // IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp.
33 const char* kFallbackFontFamilyName
= "sans";
35 // Returns the available font family that best (in FontConfig's eyes) matches
36 // the supplied list of family names.
37 std::string
FindBestMatchFontFamilyName(
38 const std::vector
<std::string
>& family_names
) {
39 FcPattern
* pattern
= FcPatternCreate();
40 for (std::vector
<std::string
>::const_iterator it
= family_names
.begin();
41 it
!= family_names
.end(); ++it
) {
43 fcvalue
.type
= FcTypeString
;
44 fcvalue
.u
.s
= reinterpret_cast<const FcChar8
*>(it
->c_str());
45 FcPatternAdd(pattern
, FC_FAMILY
, fcvalue
, FcTrue
/* append */);
48 FcConfigSubstitute(0, pattern
, FcMatchPattern
);
49 FcDefaultSubstitute(pattern
);
51 FcPattern
* match
= FcFontMatch(0, pattern
, &result
);
52 DCHECK(match
) << "Could not find font";
53 FcChar8
* match_family
= NULL
;
54 FcPatternGetString(match
, FC_FAMILY
, 0, &match_family
);
55 std::string
font_family(reinterpret_cast<char*>(match_family
));
56 FcPatternDestroy(pattern
);
57 FcPatternDestroy(match
);
66 Font
* PlatformFontPango::default_font_
= NULL
;
68 #if defined(OS_CHROMEOS)
70 std::string
* PlatformFontPango::default_font_description_
= NULL
;
73 ////////////////////////////////////////////////////////////////////////////////
74 // PlatformFontPango, public:
76 PlatformFontPango::PlatformFontPango() {
77 if (default_font_
== NULL
) {
78 std::string font_name
= GetDefaultFont();
80 ScopedPangoFontDescription
desc(
81 pango_font_description_from_string(font_name
.c_str()));
82 default_font_
= new Font(desc
.get());
84 DCHECK(default_font_
);
88 static_cast<PlatformFontPango
*>(default_font_
->platform_font()));
91 PlatformFontPango::PlatformFontPango(NativeFont native_font
) {
92 std::vector
<std::string
> family_names
;
93 base::SplitString(pango_font_description_get_family(native_font
), ',',
95 std::string font_family
= FindBestMatchFontFamilyName(family_names
);
96 InitWithNameAndSize(font_family
, gfx::GetPangoFontSizeInPixels(native_font
));
99 if (pango_font_description_get_weight(native_font
) == PANGO_WEIGHT_BOLD
) {
100 // TODO(davemoore) What should we do about other weights? We currently
101 // only support BOLD.
102 style
|= gfx::Font::BOLD
;
104 if (pango_font_description_get_style(native_font
) == PANGO_STYLE_ITALIC
) {
105 // TODO(davemoore) What about PANGO_STYLE_OBLIQUE?
106 style
|= gfx::Font::ITALIC
;
112 PlatformFontPango::PlatformFontPango(const std::string
& font_name
,
114 InitWithNameAndSize(font_name
, font_size
);
117 double PlatformFontPango::underline_position() const {
118 const_cast<PlatformFontPango
*>(this)->InitPangoMetrics();
119 return underline_position_pixels_
;
122 double PlatformFontPango::underline_thickness() const {
123 const_cast<PlatformFontPango
*>(this)->InitPangoMetrics();
124 return underline_thickness_pixels_
;
127 ////////////////////////////////////////////////////////////////////////////////
128 // PlatformFontPango, PlatformFont implementation:
131 void PlatformFontPango::ReloadDefaultFont() {
132 delete default_font_
;
133 default_font_
= NULL
;
136 #if defined(OS_CHROMEOS)
138 void PlatformFontPango::SetDefaultFontDescription(
139 const std::string
& font_description
) {
140 delete default_font_description_
;
141 default_font_description_
= new std::string(font_description
);
146 Font
PlatformFontPango::DeriveFont(int size_delta
, int style
) const {
147 // If the delta is negative, if must not push the size below 1
149 DCHECK_LT(-size_delta
, font_size_pixels_
);
151 if (style
== style_
) {
152 // Fast path, we just use the same typeface at a different size
153 return Font(new PlatformFontPango(typeface_
,
155 font_size_pixels_
+ size_delta
,
159 // If the style has changed we may need to load a new face
160 int skstyle
= SkTypeface::kNormal
;
161 if (gfx::Font::BOLD
& style
)
162 skstyle
|= SkTypeface::kBold
;
163 if (gfx::Font::ITALIC
& style
)
164 skstyle
|= SkTypeface::kItalic
;
166 skia::RefPtr
<SkTypeface
> typeface
= skia::AdoptRef(
167 SkTypeface::CreateFromName(
168 font_family_
.c_str(),
169 static_cast<SkTypeface::Style
>(skstyle
)));
171 return Font(new PlatformFontPango(typeface
,
173 font_size_pixels_
+ size_delta
,
177 int PlatformFontPango::GetHeight() const {
178 return height_pixels_
;
181 int PlatformFontPango::GetBaseline() const {
182 return ascent_pixels_
;
185 int PlatformFontPango::GetCapHeight() const {
186 // Return the ascent as an approximation because Pango doesn't support cap
188 // TODO(yukishiino): Come up with a better approximation of cap height, or
189 // support cap height metrics. Another option is to have a hard-coded table
190 // of cap height for major fonts used in Chromium/Chrome.
191 // See http://crbug.com/249507
192 return ascent_pixels_
;
195 int PlatformFontPango::GetAverageCharacterWidth() const {
196 const_cast<PlatformFontPango
*>(this)->InitPangoMetrics();
197 return SkScalarRound(average_width_pixels_
);
200 int PlatformFontPango::GetStringWidth(const base::string16
& text
) const {
201 return Canvas::GetStringWidth(text
,
202 Font(const_cast<PlatformFontPango
*>(this)));
205 int PlatformFontPango::GetExpectedTextWidth(int length
) const {
206 double char_width
= const_cast<PlatformFontPango
*>(this)->GetAverageWidth();
207 return round(static_cast<float>(length
) * char_width
);
210 int PlatformFontPango::GetStyle() const {
214 std::string
PlatformFontPango::GetFontName() const {
218 int PlatformFontPango::GetFontSize() const {
219 return font_size_pixels_
;
222 NativeFont
PlatformFontPango::GetNativeFont() const {
223 PangoFontDescription
* pfd
= pango_font_description_new();
224 pango_font_description_set_family(pfd
, GetFontName().c_str());
225 // Set the absolute size to avoid overflowing UI elements.
226 // pango_font_description_set_absolute_size() takes a size in Pango units.
227 // There are PANGO_SCALE Pango units in one device unit. Screen output
228 // devices use pixels as their device units.
229 pango_font_description_set_absolute_size(
230 pfd
, font_size_pixels_
* PANGO_SCALE
);
232 switch (GetStyle()) {
233 case gfx::Font::NORMAL
:
234 // Nothing to do, should already be PANGO_STYLE_NORMAL.
236 case gfx::Font::BOLD
:
237 pango_font_description_set_weight(pfd
, PANGO_WEIGHT_BOLD
);
239 case gfx::Font::ITALIC
:
240 pango_font_description_set_style(pfd
, PANGO_STYLE_ITALIC
);
242 case gfx::Font::UNDERLINE
:
243 // TODO(deanm): How to do underline? Where do we use it? Probably have
244 // to paint it ourselves, see pango_font_metrics_get_underline_position.
251 ////////////////////////////////////////////////////////////////////////////////
252 // PlatformFontPango, private:
254 PlatformFontPango::PlatformFontPango(const skia::RefPtr
<SkTypeface
>& typeface
,
255 const std::string
& name
,
258 InitWithTypefaceNameSizeAndStyle(typeface
, name
, size
, style
);
261 PlatformFontPango::~PlatformFontPango() {}
264 std::string
PlatformFontPango::GetDefaultFont() {
265 #if !defined(TOOLKIT_GTK)
266 #if defined(OS_CHROMEOS)
267 // Font name must have been provided by way of SetDefaultFontDescription().
268 CHECK(default_font_description_
);
269 return *default_font_description_
;
272 #endif // defined(OS_CHROMEOS)
274 GtkSettings
* settings
= gtk_settings_get_default();
276 gchar
* font_name
= NULL
;
277 g_object_get(settings
, "gtk-font-name", &font_name
, NULL
);
279 // Temporary CHECK for helping track down
280 // http://code.google.com/p/chromium/issues/detail?id=12530
281 CHECK(font_name
) << " Unable to get gtk-font-name for default font.";
283 std::string default_font
= std::string(font_name
);
286 #endif // !defined(TOOLKIT_GTK)
290 void PlatformFontPango::InitWithNameAndSize(const std::string
& font_name
,
292 DCHECK_GT(font_size
, 0);
293 std::string fallback
;
295 skia::RefPtr
<SkTypeface
> typeface
= skia::AdoptRef(
296 SkTypeface::CreateFromName(font_name
.c_str(), SkTypeface::kNormal
));
298 // A non-scalable font such as .pcf is specified. Falls back to a default
300 typeface
= skia::AdoptRef(
301 SkTypeface::CreateFromName(
302 kFallbackFontFamilyName
, SkTypeface::kNormal
));
303 CHECK(typeface
) << "Could not find any font: "
305 << ", " << kFallbackFontFamilyName
;
306 fallback
= kFallbackFontFamilyName
;
309 InitWithTypefaceNameSizeAndStyle(typeface
,
310 fallback
.empty() ? font_name
: fallback
,
315 void PlatformFontPango::InitWithTypefaceNameSizeAndStyle(
316 const skia::RefPtr
<SkTypeface
>& typeface
,
317 const std::string
& font_family
,
320 typeface_
= typeface
;
321 font_family_
= font_family
;
322 font_size_pixels_
= font_size
;
324 pango_metrics_inited_
= false;
325 average_width_pixels_
= 0.0f
;
326 underline_position_pixels_
= 0.0f
;
327 underline_thickness_pixels_
= 0.0f
;
330 SkPaint::FontMetrics metrics
;
332 paint
.getFontMetrics(&metrics
);
334 ascent_pixels_
= SkScalarCeil(-metrics
.fAscent
);
335 height_pixels_
= ascent_pixels_
+ SkScalarCeil(metrics
.fDescent
);
338 void PlatformFontPango::InitFromPlatformFont(const PlatformFontPango
* other
) {
339 typeface_
= other
->typeface_
;
340 font_family_
= other
->font_family_
;
341 font_size_pixels_
= other
->font_size_pixels_
;
342 style_
= other
->style_
;
343 height_pixels_
= other
->height_pixels_
;
344 ascent_pixels_
= other
->ascent_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(
388 ASCIIToUTF16("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
389 const double dialog_units_pixels
= (text_width_pixels
/ 26 + 1) / 2;
390 average_width_pixels_
= std::min(pango_width_pixels
, dialog_units_pixels
);
394 double PlatformFontPango::GetAverageWidth() const {
395 const_cast<PlatformFontPango
*>(this)->InitPangoMetrics();
396 return average_width_pixels_
;
399 ////////////////////////////////////////////////////////////////////////////////
400 // PlatformFont, public:
403 PlatformFont
* PlatformFont::CreateDefault() {
404 return new PlatformFontPango
;
408 PlatformFont
* PlatformFont::CreateFromNativeFont(NativeFont native_font
) {
409 return new PlatformFontPango(native_font
);
413 PlatformFont
* PlatformFont::CreateFromNameAndSize(const std::string
& font_name
,
415 return new PlatformFontPango(font_name
, font_size
);