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