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