• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/pango_util.h"
6 
7 #include <cairo/cairo.h>
8 #include <fontconfig/fontconfig.h>
9 #include <pango/pango.h>
10 #include <pango/pangocairo.h>
11 #include <string>
12 
13 #include <algorithm>
14 #include <map>
15 #include <vector>
16 
17 #include "base/logging.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/font.h"
21 #include "ui/gfx/font_render_params_linux.h"
22 #include "ui/gfx/platform_font_pango.h"
23 #include "ui/gfx/rect.h"
24 #include "ui/gfx/text_utils.h"
25 
26 #if defined(TOOLKIT_GTK)
27 #include <gdk/gdk.h>
28 #endif
29 
30 namespace gfx {
31 
32 namespace {
33 
34 // Marker for accelerators in the text.
35 const gunichar kAcceleratorChar = '&';
36 
37 // Return |cairo_font_options|. If needed, allocate and update it.
38 // TODO(derat): Return font-specific options: http://crbug.com/125235
GetCairoFontOptions()39 cairo_font_options_t* GetCairoFontOptions() {
40   // Font settings that we initialize once and then use when drawing text.
41   static cairo_font_options_t* cairo_font_options = NULL;
42   if (cairo_font_options)
43     return cairo_font_options;
44 
45   cairo_font_options = cairo_font_options_create();
46 
47   const FontRenderParams& params = GetDefaultFontRenderParams();
48   FontRenderParams::SubpixelRendering subpixel = params.subpixel_rendering;
49   if (!params.antialiasing) {
50     cairo_font_options_set_antialias(cairo_font_options, CAIRO_ANTIALIAS_NONE);
51   } else if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_NONE) {
52     cairo_font_options_set_antialias(cairo_font_options, CAIRO_ANTIALIAS_GRAY);
53   } else {
54     cairo_font_options_set_antialias(cairo_font_options,
55                                      CAIRO_ANTIALIAS_SUBPIXEL);
56     cairo_subpixel_order_t cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_DEFAULT;
57     if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_RGB)
58       cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB;
59     else if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_BGR)
60       cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR;
61     else if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_VRGB)
62       cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB;
63     else if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_VBGR)
64       cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR;
65     else
66       NOTREACHED() << "Unhandled subpixel rendering type " << subpixel;
67     cairo_font_options_set_subpixel_order(cairo_font_options,
68                                           cairo_subpixel_order);
69   }
70 
71   if (params.hinting == FontRenderParams::HINTING_NONE ||
72       params.subpixel_positioning) {
73     cairo_font_options_set_hint_style(cairo_font_options,
74                                       CAIRO_HINT_STYLE_NONE);
75     cairo_font_options_set_hint_metrics(cairo_font_options,
76                                         CAIRO_HINT_METRICS_OFF);
77   } else {
78     cairo_hint_style_t cairo_hint_style = CAIRO_HINT_STYLE_DEFAULT;
79     if (params.hinting == FontRenderParams::HINTING_SLIGHT)
80       cairo_hint_style = CAIRO_HINT_STYLE_SLIGHT;
81     else if (params.hinting == FontRenderParams::HINTING_MEDIUM)
82       cairo_hint_style = CAIRO_HINT_STYLE_MEDIUM;
83     else if (params.hinting == FontRenderParams::HINTING_FULL)
84       cairo_hint_style = CAIRO_HINT_STYLE_FULL;
85     else
86       NOTREACHED() << "Unhandled hinting style " << params.hinting;
87     cairo_font_options_set_hint_style(cairo_font_options, cairo_hint_style);
88     cairo_font_options_set_hint_metrics(cairo_font_options,
89                                         CAIRO_HINT_METRICS_ON);
90   }
91 
92   return cairo_font_options;
93 }
94 
95 // Returns the number of pixels in a point.
96 // - multiply a point size by this to get pixels ("device units")
97 // - divide a pixel size by this to get points
GetPixelsInPoint()98 float GetPixelsInPoint() {
99   static float pixels_in_point = 1.0;
100   static bool determined_value = false;
101 
102   if (!determined_value) {
103     // http://goo.gl/UIh5m: "This is a scale factor between points specified in
104     // a PangoFontDescription and Cairo units.  The default value is 96, meaning
105     // that a 10 point font will be 13 units high. (10 * 96. / 72. = 13.3)."
106     double pango_dpi = GetPangoResolution();
107     if (pango_dpi <= 0)
108       pango_dpi = 96.0;
109     pixels_in_point = pango_dpi / 72.0;  // 72 points in an inch
110     determined_value = true;
111   }
112 
113   return pixels_in_point;
114 }
115 
116 }  // namespace
117 
GetPangoContext()118 PangoContext* GetPangoContext() {
119 #if defined(TOOLKIT_GTK)
120   return gdk_pango_context_get();
121 #else
122   PangoFontMap* font_map = pango_cairo_font_map_get_default();
123   return pango_font_map_create_context(font_map);
124 #endif
125 }
126 
GetPangoResolution()127 double GetPangoResolution() {
128   static double resolution;
129   static bool determined_resolution = false;
130   if (!determined_resolution) {
131     determined_resolution = true;
132     PangoContext* default_context = GetPangoContext();
133     resolution = pango_cairo_context_get_resolution(default_context);
134     g_object_unref(default_context);
135   }
136   return resolution;
137 }
138 
139 // Pass a width greater than 0 to force wrapping and eliding.
SetupPangoLayoutWithoutFont(PangoLayout * layout,const base::string16 & text,int width,base::i18n::TextDirection text_direction,int flags)140 static void SetupPangoLayoutWithoutFont(
141     PangoLayout* layout,
142     const base::string16& text,
143     int width,
144     base::i18n::TextDirection text_direction,
145     int flags) {
146   cairo_font_options_t* cairo_font_options = GetCairoFontOptions();
147 
148   // If we got an explicit request to turn off subpixel rendering, disable it on
149   // a copy of the static font options object.
150   bool copied_cairo_font_options = false;
151   if ((flags & Canvas::NO_SUBPIXEL_RENDERING) &&
152       (cairo_font_options_get_antialias(cairo_font_options) ==
153        CAIRO_ANTIALIAS_SUBPIXEL)) {
154     cairo_font_options = cairo_font_options_copy(cairo_font_options);
155     copied_cairo_font_options = true;
156     cairo_font_options_set_antialias(cairo_font_options, CAIRO_ANTIALIAS_GRAY);
157   }
158 
159   // This needs to be done early on; it has no effect when called just before
160   // pango_cairo_show_layout().
161   pango_cairo_context_set_font_options(
162       pango_layout_get_context(layout), cairo_font_options);
163 
164   if (copied_cairo_font_options) {
165     cairo_font_options_destroy(cairo_font_options);
166     cairo_font_options = NULL;
167   }
168 
169   // Set Pango's base text direction explicitly from |text_direction|.
170   pango_layout_set_auto_dir(layout, FALSE);
171   pango_context_set_base_dir(pango_layout_get_context(layout),
172       (text_direction == base::i18n::RIGHT_TO_LEFT ?
173        PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR));
174 
175   if (width > 0)
176     pango_layout_set_width(layout, width * PANGO_SCALE);
177 
178   if (flags & Canvas::TEXT_ALIGN_CENTER) {
179     // We don't support center aligned w/ eliding.
180     DCHECK(gfx::Canvas::NO_ELLIPSIS);
181     pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
182   } else if (flags & Canvas::TEXT_ALIGN_RIGHT) {
183     pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT);
184   }
185 
186   if (flags & Canvas::NO_ELLIPSIS) {
187     pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
188     if (flags & Canvas::MULTI_LINE) {
189       pango_layout_set_wrap(layout,
190           (flags & Canvas::CHARACTER_BREAK) ?
191               PANGO_WRAP_WORD_CHAR : PANGO_WRAP_WORD);
192     }
193   } else if (text_direction == base::i18n::RIGHT_TO_LEFT) {
194     pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
195   } else {
196     // Fading the text will be handled in the draw operation.
197     // Ensure that the text is only on one line.
198     pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
199     pango_layout_set_width(layout, -1);
200   }
201 
202   // Set the resolution to match that used by Gtk. If we don't set the
203   // resolution and the resolution differs from the default, Gtk and Chrome end
204   // up drawing at different sizes.
205   double resolution = GetPangoResolution();
206   if (resolution > 0) {
207     pango_cairo_context_set_resolution(pango_layout_get_context(layout),
208                                        resolution);
209   }
210 
211   // Set text and accelerator character if needed.
212   if (flags & Canvas::SHOW_PREFIX) {
213     // Escape the text string to be used as markup.
214     std::string utf8 = UTF16ToUTF8(text);
215     gchar* escaped_text = g_markup_escape_text(utf8.c_str(), utf8.size());
216     pango_layout_set_markup_with_accel(layout,
217                                        escaped_text,
218                                        strlen(escaped_text),
219                                        kAcceleratorChar, NULL);
220     g_free(escaped_text);
221   } else {
222     std::string utf8;
223 
224     // Remove the ampersand character.  A double ampersand is output as
225     // a single ampersand.
226     if (flags & Canvas::HIDE_PREFIX) {
227       DCHECK_EQ(1, g_unichar_to_utf8(kAcceleratorChar, NULL));
228       base::string16 accelerator_removed =
229           RemoveAcceleratorChar(text,
230                                 static_cast<base::char16>(kAcceleratorChar),
231                                 NULL, NULL);
232       utf8 = UTF16ToUTF8(accelerator_removed);
233     } else {
234       utf8 = UTF16ToUTF8(text);
235     }
236 
237     pango_layout_set_text(layout, utf8.data(), utf8.size());
238   }
239 }
240 
SetupPangoLayout(PangoLayout * layout,const base::string16 & text,const Font & font,int width,base::i18n::TextDirection text_direction,int flags)241 void SetupPangoLayout(PangoLayout* layout,
242                       const base::string16& text,
243                       const Font& font,
244                       int width,
245                       base::i18n::TextDirection text_direction,
246                       int flags) {
247   SetupPangoLayoutWithoutFont(layout, text, width, text_direction, flags);
248 
249   ScopedPangoFontDescription desc(font.GetNativeFont());
250   pango_layout_set_font_description(layout, desc.get());
251 }
252 
SetupPangoLayoutWithFontDescription(PangoLayout * layout,const base::string16 & text,const std::string & font_description,int width,base::i18n::TextDirection text_direction,int flags)253 void SetupPangoLayoutWithFontDescription(
254     PangoLayout* layout,
255     const base::string16& text,
256     const std::string& font_description,
257     int width,
258     base::i18n::TextDirection text_direction,
259     int flags) {
260   SetupPangoLayoutWithoutFont(layout, text, width, text_direction, flags);
261 
262   ScopedPangoFontDescription desc(
263       pango_font_description_from_string(font_description.c_str()));
264   pango_layout_set_font_description(layout, desc.get());
265 }
266 
GetPangoFontSizeInPixels(PangoFontDescription * pango_font)267 size_t GetPangoFontSizeInPixels(PangoFontDescription* pango_font) {
268   size_t size_in_pixels = pango_font_description_get_size(pango_font);
269   if (pango_font_description_get_size_is_absolute(pango_font)) {
270     // If the size is absolute, then it's in Pango units rather than points.
271     // There are PANGO_SCALE Pango units in a device unit (pixel).
272     size_in_pixels /= PANGO_SCALE;
273   } else {
274     // Otherwise, we need to convert from points.
275     size_in_pixels = size_in_pixels * GetPixelsInPoint() / PANGO_SCALE;
276   }
277   return size_in_pixels;
278 }
279 
GetPangoFontMetrics(PangoFontDescription * desc)280 PangoFontMetrics* GetPangoFontMetrics(PangoFontDescription* desc) {
281   static std::map<int, PangoFontMetrics*>* desc_to_metrics = NULL;
282   static PangoContext* context = NULL;
283 
284   if (!context) {
285     context = GetPangoContext();
286     pango_context_set_language(context, pango_language_get_default());
287   }
288 
289   if (!desc_to_metrics)
290     desc_to_metrics = new std::map<int, PangoFontMetrics*>();
291 
292   const int desc_hash = pango_font_description_hash(desc);
293   std::map<int, PangoFontMetrics*>::iterator i =
294       desc_to_metrics->find(desc_hash);
295 
296   if (i == desc_to_metrics->end()) {
297     PangoFontMetrics* metrics = pango_context_get_metrics(context, desc, NULL);
298     desc_to_metrics->insert(std::make_pair(desc_hash, metrics));
299     return metrics;
300   }
301   return i->second;
302 }
303 
304 }  // namespace gfx
305