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 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 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 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 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. 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 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 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 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 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