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/render_text_pango.h" 6 7 #include <pango/pangocairo.h> 8 #include <algorithm> 9 #include <string> 10 #include <vector> 11 12 #include "base/i18n/break_iterator.h" 13 #include "base/logging.h" 14 #include "third_party/skia/include/core/SkTypeface.h" 15 #include "ui/gfx/canvas.h" 16 #include "ui/gfx/font.h" 17 #include "ui/gfx/font_render_params_linux.h" 18 #include "ui/gfx/pango_util.h" 19 #include "ui/gfx/utf16_indexing.h" 20 21 namespace gfx { 22 23 namespace { 24 25 // Returns the preceding element in a GSList (O(n)). 26 GSList* GSListPrevious(GSList* head, GSList* item) { 27 GSList* prev = NULL; 28 for (GSList* cur = head; cur != item; cur = cur->next) { 29 DCHECK(cur); 30 prev = cur; 31 } 32 return prev; 33 } 34 35 // Returns true if the given visual cursor |direction| is logically forward 36 // motion in the given Pango |item|. 37 bool IsForwardMotion(VisualCursorDirection direction, const PangoItem* item) { 38 bool rtl = item->analysis.level & 1; 39 return rtl == (direction == CURSOR_LEFT); 40 } 41 42 // Checks whether |range| contains |index|. This is not the same as calling 43 // |range.Contains(gfx::Range(index))| - as that would return true when 44 // |index| == |range.end()|. 45 bool IndexInRange(const Range& range, size_t index) { 46 return index >= range.start() && index < range.end(); 47 } 48 49 // Sets underline metrics on |renderer| according to Pango font |desc|. 50 void SetPangoUnderlineMetrics(PangoFontDescription *desc, 51 internal::SkiaTextRenderer* renderer) { 52 PangoFontMetrics* metrics = GetPangoFontMetrics(desc); 53 int thickness = pango_font_metrics_get_underline_thickness(metrics); 54 // Pango returns the position "above the baseline". Change its sign to convert 55 // it to a vertical offset from the baseline. 56 int position = -pango_font_metrics_get_underline_position(metrics); 57 pango_quantize_line_geometry(&thickness, &position); 58 // Note: pango_quantize_line_geometry() guarantees pixel boundaries, so 59 // PANGO_PIXELS() is safe to use. 60 renderer->SetUnderlineMetrics(PANGO_PIXELS(thickness), 61 PANGO_PIXELS(position)); 62 } 63 64 } // namespace 65 66 // TODO(xji): index saved in upper layer is utf16 index. Pango uses utf8 index. 67 // Since caret_pos is used internally, we could save utf8 index for caret_pos 68 // to avoid conversion. 69 70 RenderTextPango::RenderTextPango() 71 : layout_(NULL), 72 current_line_(NULL), 73 log_attrs_(NULL), 74 num_log_attrs_(0), 75 layout_text_(NULL) { 76 } 77 78 RenderTextPango::~RenderTextPango() { 79 ResetLayout(); 80 } 81 82 Size RenderTextPango::GetStringSize() { 83 EnsureLayout(); 84 int width = 0, height = 0; 85 pango_layout_get_pixel_size(layout_, &width, &height); 86 // Keep a consistent height between this particular string's PangoLayout and 87 // potentially larger text supported by the FontList. 88 // For example, if a text field contains a Japanese character, which is 89 // smaller than Latin ones, and then later a Latin one is inserted, this 90 // ensures that the text baseline does not shift. 91 return Size(width, std::max(height, font_list().GetHeight())); 92 } 93 94 SelectionModel RenderTextPango::FindCursorPosition(const Point& point) { 95 EnsureLayout(); 96 97 if (text().empty()) 98 return SelectionModel(0, CURSOR_FORWARD); 99 100 Point p(ToTextPoint(point)); 101 102 // When the point is outside of text, return HOME/END position. 103 if (p.x() < 0) 104 return EdgeSelectionModel(CURSOR_LEFT); 105 if (p.x() > GetStringSize().width()) 106 return EdgeSelectionModel(CURSOR_RIGHT); 107 108 int caret_pos = 0, trailing = 0; 109 pango_layout_xy_to_index(layout_, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE, 110 &caret_pos, &trailing); 111 112 DCHECK_GE(trailing, 0); 113 if (trailing > 0) { 114 caret_pos = g_utf8_offset_to_pointer(layout_text_ + caret_pos, 115 trailing) - layout_text_; 116 DCHECK_LE(static_cast<size_t>(caret_pos), strlen(layout_text_)); 117 } 118 119 return SelectionModel(LayoutIndexToTextIndex(caret_pos), 120 (trailing > 0) ? CURSOR_BACKWARD : CURSOR_FORWARD); 121 } 122 123 std::vector<RenderText::FontSpan> RenderTextPango::GetFontSpansForTesting() { 124 EnsureLayout(); 125 126 std::vector<RenderText::FontSpan> spans; 127 for (GSList* it = current_line_->runs; it; it = it->next) { 128 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(it->data)->item; 129 const int start = LayoutIndexToTextIndex(item->offset); 130 const int end = LayoutIndexToTextIndex(item->offset + item->length); 131 const Range range(start, end); 132 133 ScopedPangoFontDescription desc(pango_font_describe(item->analysis.font)); 134 spans.push_back(RenderText::FontSpan(Font(desc.get()), range)); 135 } 136 137 return spans; 138 } 139 140 int RenderTextPango::GetLayoutTextBaseline() { 141 EnsureLayout(); 142 return PANGO_PIXELS(pango_layout_get_baseline(layout_)); 143 } 144 145 SelectionModel RenderTextPango::AdjacentCharSelectionModel( 146 const SelectionModel& selection, 147 VisualCursorDirection direction) { 148 GSList* run = GetRunContainingCaret(selection); 149 if (!run) { 150 // The cursor is not in any run: we're at the visual and logical edge. 151 SelectionModel edge = EdgeSelectionModel(direction); 152 if (edge.caret_pos() == selection.caret_pos()) 153 return edge; 154 else 155 run = (direction == CURSOR_RIGHT) ? 156 current_line_->runs : g_slist_last(current_line_->runs); 157 } else { 158 // If the cursor is moving within the current run, just move it by one 159 // grapheme in the appropriate direction. 160 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; 161 size_t caret = selection.caret_pos(); 162 if (IsForwardMotion(direction, item)) { 163 if (caret < LayoutIndexToTextIndex(item->offset + item->length)) { 164 caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD); 165 return SelectionModel(caret, CURSOR_BACKWARD); 166 } 167 } else { 168 if (caret > LayoutIndexToTextIndex(item->offset)) { 169 caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD); 170 return SelectionModel(caret, CURSOR_FORWARD); 171 } 172 } 173 // The cursor is at the edge of a run; move to the visually adjacent run. 174 // TODO(xji): Keep a vector of runs to avoid using a singly-linked list. 175 run = (direction == CURSOR_RIGHT) ? 176 run->next : GSListPrevious(current_line_->runs, run); 177 if (!run) 178 return EdgeSelectionModel(direction); 179 } 180 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; 181 return IsForwardMotion(direction, item) ? 182 FirstSelectionModelInsideRun(item) : LastSelectionModelInsideRun(item); 183 } 184 185 SelectionModel RenderTextPango::AdjacentWordSelectionModel( 186 const SelectionModel& selection, 187 VisualCursorDirection direction) { 188 if (obscured()) 189 return EdgeSelectionModel(direction); 190 191 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); 192 bool success = iter.Init(); 193 DCHECK(success); 194 if (!success) 195 return selection; 196 197 SelectionModel cur(selection); 198 for (;;) { 199 cur = AdjacentCharSelectionModel(cur, direction); 200 GSList* run = GetRunContainingCaret(cur); 201 if (!run) 202 break; 203 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; 204 size_t cursor = cur.caret_pos(); 205 if (IsForwardMotion(direction, item) ? 206 iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor)) 207 break; 208 } 209 210 return cur; 211 } 212 213 Range RenderTextPango::GetGlyphBounds(size_t index) { 214 PangoRectangle pos; 215 pango_layout_index_to_pos(layout_, TextIndexToLayoutIndex(index), &pos); 216 // TODO(derat): Support fractional ranges for subpixel positioning? 217 return Range(PANGO_PIXELS(pos.x), PANGO_PIXELS(pos.x + pos.width)); 218 } 219 220 std::vector<Rect> RenderTextPango::GetSubstringBounds(const Range& range) { 221 DCHECK_LE(range.GetMax(), text().length()); 222 if (range.is_empty()) 223 return std::vector<Rect>(); 224 225 EnsureLayout(); 226 int* ranges = NULL; 227 int n_ranges = 0; 228 pango_layout_line_get_x_ranges(current_line_, 229 TextIndexToLayoutIndex(range.GetMin()), 230 TextIndexToLayoutIndex(range.GetMax()), 231 &ranges, 232 &n_ranges); 233 234 const int height = GetStringSize().height(); 235 236 std::vector<Rect> bounds; 237 for (int i = 0; i < n_ranges; ++i) { 238 // TODO(derat): Support fractional bounds for subpixel positioning? 239 int x = PANGO_PIXELS(ranges[2 * i]); 240 int width = PANGO_PIXELS(ranges[2 * i + 1]) - x; 241 Rect rect(x, 0, width, height); 242 rect.set_origin(ToViewPoint(rect.origin())); 243 bounds.push_back(rect); 244 } 245 g_free(ranges); 246 return bounds; 247 } 248 249 size_t RenderTextPango::TextIndexToLayoutIndex(size_t index) const { 250 DCHECK(layout_); 251 ptrdiff_t offset = gfx::UTF16IndexToOffset(text(), 0, index); 252 // Clamp layout indices to the length of the text actually used for layout. 253 offset = std::min<size_t>(offset, g_utf8_strlen(layout_text_, -1)); 254 const char* layout_pointer = g_utf8_offset_to_pointer(layout_text_, offset); 255 return (layout_pointer - layout_text_); 256 } 257 258 size_t RenderTextPango::LayoutIndexToTextIndex(size_t index) const { 259 DCHECK(layout_); 260 const char* layout_pointer = layout_text_ + index; 261 const long offset = g_utf8_pointer_to_offset(layout_text_, layout_pointer); 262 return gfx::UTF16OffsetToIndex(text(), 0, offset); 263 } 264 265 bool RenderTextPango::IsCursorablePosition(size_t position) { 266 if (position == 0 && text().empty()) 267 return true; 268 if (position >= text().length()) 269 return position == text().length(); 270 if (!gfx::IsValidCodePointIndex(text(), position)) 271 return false; 272 273 EnsureLayout(); 274 ptrdiff_t offset = gfx::UTF16IndexToOffset(text(), 0, position); 275 // Check that the index corresponds with a valid text code point, that it is 276 // marked as a legitimate cursor position by Pango, and that it is not 277 // truncated from layout text (its glyph is shown on screen). 278 return (offset < num_log_attrs_ && log_attrs_[offset].is_cursor_position && 279 offset < g_utf8_strlen(layout_text_, -1)); 280 } 281 282 void RenderTextPango::ResetLayout() { 283 // set_cached_bounds_and_offset_valid(false) is done in RenderText for every 284 // operation that triggers ResetLayout(). 285 if (layout_) { 286 // TODO(msw): Keep |layout_| across text changes, etc.; it can be re-used. 287 g_object_unref(layout_); 288 layout_ = NULL; 289 } 290 if (current_line_) { 291 pango_layout_line_unref(current_line_); 292 current_line_ = NULL; 293 } 294 if (log_attrs_) { 295 g_free(log_attrs_); 296 log_attrs_ = NULL; 297 num_log_attrs_ = 0; 298 } 299 layout_text_ = NULL; 300 } 301 302 void RenderTextPango::EnsureLayout() { 303 if (layout_ == NULL) { 304 cairo_surface_t* surface = 305 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); 306 CHECK_EQ(CAIRO_STATUS_SUCCESS, cairo_surface_status(surface)); 307 cairo_t* cr = cairo_create(surface); 308 CHECK_EQ(CAIRO_STATUS_SUCCESS, cairo_status(cr)); 309 310 layout_ = pango_cairo_create_layout(cr); 311 CHECK_NE(static_cast<PangoLayout*>(NULL), layout_); 312 cairo_destroy(cr); 313 cairo_surface_destroy(surface); 314 315 SetupPangoLayoutWithFontDescription(layout_, 316 GetLayoutText(), 317 font_list().GetFontDescriptionString(), 318 0, 319 GetTextDirection(), 320 Canvas::DefaultCanvasTextAlignment()); 321 322 // No width set so that the x-axis position is relative to the start of the 323 // text. ToViewPoint and ToTextPoint take care of the position conversion 324 // between text space and view spaces. 325 pango_layout_set_width(layout_, -1); 326 // TODO(xji): If RenderText will be used for displaying purpose, such as 327 // label, we will need to remove the single-line-mode setting. 328 pango_layout_set_single_paragraph_mode(layout_, true); 329 330 layout_text_ = pango_layout_get_text(layout_); 331 SetupPangoAttributes(layout_); 332 333 current_line_ = pango_layout_get_line_readonly(layout_, 0); 334 CHECK_NE(static_cast<PangoLayoutLine*>(NULL), current_line_); 335 pango_layout_line_ref(current_line_); 336 337 pango_layout_get_log_attrs(layout_, &log_attrs_, &num_log_attrs_); 338 } 339 } 340 341 void RenderTextPango::SetupPangoAttributes(PangoLayout* layout) { 342 PangoAttrList* attrs = pango_attr_list_new(); 343 344 // Splitting text runs to accommodate styling can break Arabic glyph shaping. 345 // Only split text runs as needed for bold and italic font styles changes. 346 BreakList<bool>::const_iterator bold = styles()[BOLD].breaks().begin(); 347 BreakList<bool>::const_iterator italic = styles()[ITALIC].breaks().begin(); 348 while (bold != styles()[BOLD].breaks().end() && 349 italic != styles()[ITALIC].breaks().end()) { 350 const int style = (bold->second ? Font::BOLD : 0) | 351 (italic->second ? Font::ITALIC : 0); 352 const size_t bold_end = styles()[BOLD].GetRange(bold).end(); 353 const size_t italic_end = styles()[ITALIC].GetRange(italic).end(); 354 const size_t style_end = std::min(bold_end, italic_end); 355 if (style != font_list().GetFontStyle()) { 356 FontList derived_font_list = font_list().DeriveFontList(style); 357 ScopedPangoFontDescription desc(pango_font_description_from_string( 358 derived_font_list.GetFontDescriptionString().c_str())); 359 360 PangoAttribute* pango_attr = pango_attr_font_desc_new(desc.get()); 361 pango_attr->start_index = 362 TextIndexToLayoutIndex(std::max(bold->first, italic->first)); 363 pango_attr->end_index = TextIndexToLayoutIndex(style_end); 364 pango_attr_list_insert(attrs, pango_attr); 365 } 366 bold += bold_end == style_end ? 1 : 0; 367 italic += italic_end == style_end ? 1 : 0; 368 } 369 DCHECK(bold == styles()[BOLD].breaks().end()); 370 DCHECK(italic == styles()[ITALIC].breaks().end()); 371 372 pango_layout_set_attributes(layout, attrs); 373 pango_attr_list_unref(attrs); 374 } 375 376 void RenderTextPango::DrawVisualText(Canvas* canvas) { 377 DCHECK(layout_); 378 379 // Skia will draw glyphs with respect to the baseline. 380 Vector2d offset(GetLineOffset(0) + Vector2d(0, GetLayoutTextBaseline())); 381 382 SkScalar x = SkIntToScalar(offset.x()); 383 SkScalar y = SkIntToScalar(offset.y()); 384 385 std::vector<SkPoint> pos; 386 std::vector<uint16> glyphs; 387 388 internal::SkiaTextRenderer renderer(canvas); 389 ApplyFadeEffects(&renderer); 390 ApplyTextShadows(&renderer); 391 392 // TODO(derat): Use font-specific params: http://crbug.com/125235 393 const gfx::FontRenderParams& render_params = 394 gfx::GetDefaultFontRenderParams(); 395 const bool use_subpixel_rendering = 396 render_params.subpixel_rendering != 397 gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE; 398 renderer.SetFontSmoothingSettings( 399 render_params.antialiasing, 400 use_subpixel_rendering && !background_is_transparent()); 401 402 // Temporarily apply composition underlines and selection colors. 403 ApplyCompositionAndSelectionStyles(); 404 405 internal::StyleIterator style(colors(), styles()); 406 for (GSList* it = current_line_->runs; it; it = it->next) { 407 PangoLayoutRun* run = reinterpret_cast<PangoLayoutRun*>(it->data); 408 int glyph_count = run->glyphs->num_glyphs; 409 // TODO(msw): Skip painting runs outside the display rect area, like Win. 410 if (glyph_count == 0) 411 continue; 412 413 ScopedPangoFontDescription desc( 414 pango_font_describe(run->item->analysis.font)); 415 416 const std::string family_name = 417 pango_font_description_get_family(desc.get()); 418 renderer.SetTextSize(GetPangoFontSizeInPixels(desc.get())); 419 420 glyphs.resize(glyph_count); 421 pos.resize(glyph_count); 422 423 // Track the current glyph and the glyph at the start of its styled range. 424 int glyph_index = 0; 425 int style_start_glyph_index = glyph_index; 426 427 // Track the x-coordinates for each styled range (|x| marks the current). 428 SkScalar style_start_x = x; 429 430 // Track the current style and its text (not layout) index range. 431 style.UpdatePosition(GetGlyphTextIndex(run, style_start_glyph_index)); 432 Range style_range = style.GetRange(); 433 434 do { 435 const PangoGlyphInfo& glyph = run->glyphs->glyphs[glyph_index]; 436 glyphs[glyph_index] = static_cast<uint16>(glyph.glyph); 437 // Use pango_units_to_double() rather than PANGO_PIXELS() here, so units 438 // are not rounded to the pixel grid if subpixel positioning is enabled. 439 pos[glyph_index].set(x + pango_units_to_double(glyph.geometry.x_offset), 440 y + pango_units_to_double(glyph.geometry.y_offset)); 441 x += pango_units_to_double(glyph.geometry.width); 442 443 ++glyph_index; 444 const size_t glyph_text_index = (glyph_index == glyph_count) ? 445 style_range.end() : GetGlyphTextIndex(run, glyph_index); 446 if (!IndexInRange(style_range, glyph_text_index)) { 447 // TODO(asvitkine): For cases like "fi", where "fi" is a single glyph 448 // but can span multiple styles, Pango splits the 449 // styles evenly over the glyph. We can do this too by 450 // clipping and drawing the glyph several times. 451 renderer.SetForegroundColor(style.color()); 452 const int font_style = (style.style(BOLD) ? Font::BOLD : 0) | 453 (style.style(ITALIC) ? Font::ITALIC : 0); 454 renderer.SetFontFamilyWithStyle(family_name, font_style); 455 renderer.DrawPosText(&pos[style_start_glyph_index], 456 &glyphs[style_start_glyph_index], 457 glyph_index - style_start_glyph_index); 458 if (style.style(UNDERLINE)) 459 SetPangoUnderlineMetrics(desc.get(), &renderer); 460 renderer.DrawDecorations(style_start_x, y, x - style_start_x, 461 style.style(UNDERLINE), style.style(STRIKE), 462 style.style(DIAGONAL_STRIKE)); 463 style.UpdatePosition(glyph_text_index); 464 style_range = style.GetRange(); 465 style_start_glyph_index = glyph_index; 466 style_start_x = x; 467 } 468 } while (glyph_index < glyph_count); 469 } 470 471 // Undo the temporarily applied composition underlines and selection colors. 472 UndoCompositionAndSelectionStyles(); 473 } 474 475 GSList* RenderTextPango::GetRunContainingCaret( 476 const SelectionModel& caret) const { 477 size_t position = TextIndexToLayoutIndex(caret.caret_pos()); 478 LogicalCursorDirection affinity = caret.caret_affinity(); 479 GSList* run = current_line_->runs; 480 while (run) { 481 PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item; 482 Range item_range(item->offset, item->offset + item->length); 483 if (RangeContainsCaret(item_range, position, affinity)) 484 return run; 485 run = run->next; 486 } 487 return NULL; 488 } 489 490 SelectionModel RenderTextPango::FirstSelectionModelInsideRun( 491 const PangoItem* item) { 492 size_t caret = IndexOfAdjacentGrapheme( 493 LayoutIndexToTextIndex(item->offset), CURSOR_FORWARD); 494 return SelectionModel(caret, CURSOR_BACKWARD); 495 } 496 497 SelectionModel RenderTextPango::LastSelectionModelInsideRun( 498 const PangoItem* item) { 499 size_t caret = IndexOfAdjacentGrapheme( 500 LayoutIndexToTextIndex(item->offset + item->length), CURSOR_BACKWARD); 501 return SelectionModel(caret, CURSOR_FORWARD); 502 } 503 504 size_t RenderTextPango::GetGlyphTextIndex(PangoLayoutRun* run, 505 int glyph_index) const { 506 return LayoutIndexToTextIndex(run->item->offset + 507 run->glyphs->log_clusters[glyph_index]); 508 } 509 510 RenderText* RenderText::CreateInstance() { 511 return new RenderTextPango; 512 } 513 514 } // namespace gfx 515