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