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