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.
FindBestMatchFontFamilyName(const std::vector<std::string> & 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
PlatformFontPango()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
PlatformFontPango(NativeFont native_font)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
PlatformFontPango(const std::string & font_name,int font_size)113 PlatformFontPango::PlatformFontPango(const std::string& font_name,
114 int font_size) {
115 InitWithNameAndSize(font_name, font_size);
116 }
117
underline_position() const118 double PlatformFontPango::underline_position() const {
119 const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
120 return underline_position_pixels_;
121 }
122
underline_thickness() const123 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
ReloadDefaultFont()132 void PlatformFontPango::ReloadDefaultFont() {
133 delete default_font_;
134 default_font_ = NULL;
135 }
136
137 #if defined(OS_CHROMEOS)
138 // static
SetDefaultFontDescription(const std::string & font_description)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
DeriveFont(int size_delta,int style) const147 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
GetHeight() const178 int PlatformFontPango::GetHeight() const {
179 return height_pixels_;
180 }
181
GetBaseline() const182 int PlatformFontPango::GetBaseline() const {
183 return ascent_pixels_;
184 }
185
GetCapHeight() const186 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
GetAverageCharacterWidth() const196 int PlatformFontPango::GetAverageCharacterWidth() const {
197 const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
198 return SkScalarRound(average_width_pixels_);
199 }
200
GetStringWidth(const base::string16 & text) const201 int PlatformFontPango::GetStringWidth(const base::string16& text) const {
202 return Canvas::GetStringWidth(text,
203 Font(const_cast<PlatformFontPango*>(this)));
204 }
205
GetExpectedTextWidth(int length) const206 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
GetStyle() const211 int PlatformFontPango::GetStyle() const {
212 return style_;
213 }
214
GetFontName() const215 std::string PlatformFontPango::GetFontName() const {
216 return font_family_;
217 }
218
GetActualFontNameForTesting() const219 std::string PlatformFontPango::GetActualFontNameForTesting() const {
220 SkString family_name;
221 typeface_->getFamilyName(&family_name);
222 return family_name.c_str();
223 }
224
GetFontSize() const225 int PlatformFontPango::GetFontSize() const {
226 return font_size_pixels_;
227 }
228
GetNativeFont() const229 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
PlatformFontPango(const skia::RefPtr<SkTypeface> & typeface,const std::string & name,int size,int style)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
~PlatformFontPango()268 PlatformFontPango::~PlatformFontPango() {}
269
270 // static
GetDefaultFont()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
InitWithNameAndSize(const std::string & font_name,int font_size)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
InitWithTypefaceNameSizeAndStyle(const skia::RefPtr<SkTypeface> & typeface,const std::string & font_family,int font_size,int style)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
InitFromPlatformFont(const PlatformFontPango * other)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
PaintSetup(SkPaint * paint) const358 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
InitPangoMetrics()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
GetAverageWidth() const401 double PlatformFontPango::GetAverageWidth() const {
402 const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
403 return average_width_pixels_;
404 }
405
406 ////////////////////////////////////////////////////////////////////////////////
407 // PlatformFont, public:
408
409 // static
CreateDefault()410 PlatformFont* PlatformFont::CreateDefault() {
411 return new PlatformFontPango;
412 }
413
414 // static
CreateFromNativeFont(NativeFont native_font)415 PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
416 return new PlatformFontPango(native_font);
417 }
418
419 // static
CreateFromNameAndSize(const std::string & font_name,int font_size)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