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.h" 6 7 #include <algorithm> 8 #include <climits> 9 10 #include "base/command_line.h" 11 #include "base/i18n/break_iterator.h" 12 #include "base/logging.h" 13 #include "base/stl_util.h" 14 #include "base/strings/utf_string_conversions.h" 15 #include "third_party/icu/source/common/unicode/rbbi.h" 16 #include "third_party/icu/source/common/unicode/utf16.h" 17 #include "third_party/skia/include/core/SkTypeface.h" 18 #include "third_party/skia/include/effects/SkGradientShader.h" 19 #include "ui/gfx/canvas.h" 20 #include "ui/gfx/insets.h" 21 #include "ui/gfx/render_text_harfbuzz.h" 22 #include "ui/gfx/scoped_canvas.h" 23 #include "ui/gfx/skia_util.h" 24 #include "ui/gfx/switches.h" 25 #include "ui/gfx/text_constants.h" 26 #include "ui/gfx/text_elider.h" 27 #include "ui/gfx/text_utils.h" 28 #include "ui/gfx/utf16_indexing.h" 29 30 namespace gfx { 31 32 namespace { 33 34 // All chars are replaced by this char when the password style is set. 35 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*' 36 // that's available in the font (find_invisible_char() in gtkentry.c). 37 const base::char16 kPasswordReplacementChar = '*'; 38 39 // Default color used for the text and cursor. 40 const SkColor kDefaultColor = SK_ColorBLACK; 41 42 // Default color used for drawing selection background. 43 const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY; 44 45 // Fraction of the text size to lower a strike through below the baseline. 46 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21); 47 // Fraction of the text size to lower an underline below the baseline. 48 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); 49 // Fraction of the text size to use for a strike through or under-line. 50 const SkScalar kLineThickness = (SK_Scalar1 / 18); 51 // Fraction of the text size to use for a top margin of a diagonal strike. 52 const SkScalar kDiagonalStrikeMarginOffset = (SK_Scalar1 / 4); 53 54 // Invalid value of baseline. Assigning this value to |baseline_| causes 55 // re-calculation of baseline. 56 const int kInvalidBaseline = INT_MAX; 57 58 // Returns the baseline, with which the text best appears vertically centered. 59 int DetermineBaselineCenteringText(const Rect& display_rect, 60 const FontList& font_list) { 61 const int display_height = display_rect.height(); 62 const int font_height = font_list.GetHeight(); 63 // Lower and upper bound of baseline shift as we try to show as much area of 64 // text as possible. In particular case of |display_height| == |font_height|, 65 // we do not want to shift the baseline. 66 const int min_shift = std::min(0, display_height - font_height); 67 const int max_shift = std::abs(display_height - font_height); 68 const int baseline = font_list.GetBaseline(); 69 const int cap_height = font_list.GetCapHeight(); 70 const int internal_leading = baseline - cap_height; 71 // Some platforms don't support getting the cap height, and simply return 72 // the entire font ascent from GetCapHeight(). Centering the ascent makes 73 // the font look too low, so if GetCapHeight() returns the ascent, center 74 // the entire font height instead. 75 const int space = 76 display_height - ((internal_leading != 0) ? cap_height : font_height); 77 const int baseline_shift = space / 2 - internal_leading; 78 return baseline + std::max(min_shift, std::min(max_shift, baseline_shift)); 79 } 80 81 // Converts |Font::FontStyle| flags to |SkTypeface::Style| flags. 82 SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) { 83 int skia_style = SkTypeface::kNormal; 84 skia_style |= (font_style & Font::BOLD) ? SkTypeface::kBold : 0; 85 skia_style |= (font_style & Font::ITALIC) ? SkTypeface::kItalic : 0; 86 return static_cast<SkTypeface::Style>(skia_style); 87 } 88 89 // Given |font| and |display_width|, returns the width of the fade gradient. 90 int CalculateFadeGradientWidth(const FontList& font_list, int display_width) { 91 // Fade in/out about 2.5 characters of the beginning/end of the string. 92 // The .5 here is helpful if one of the characters is a space. 93 // Use a quarter of the display width if the display width is very short. 94 const int average_character_width = font_list.GetExpectedTextWidth(1); 95 const double gradient_width = std::min(average_character_width * 2.5, 96 display_width / 4.0); 97 DCHECK_GE(gradient_width, 0.0); 98 return static_cast<int>(floor(gradient_width + 0.5)); 99 } 100 101 // Appends to |positions| and |colors| values corresponding to the fade over 102 // |fade_rect| from color |c0| to color |c1|. 103 void AddFadeEffect(const Rect& text_rect, 104 const Rect& fade_rect, 105 SkColor c0, 106 SkColor c1, 107 std::vector<SkScalar>* positions, 108 std::vector<SkColor>* colors) { 109 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x()); 110 const SkScalar width = static_cast<SkScalar>(fade_rect.width()); 111 const SkScalar p0 = left / text_rect.width(); 112 const SkScalar p1 = (left + width) / text_rect.width(); 113 // Prepend 0.0 to |positions|, as required by Skia. 114 if (positions->empty() && p0 != 0.0) { 115 positions->push_back(0.0); 116 colors->push_back(c0); 117 } 118 positions->push_back(p0); 119 colors->push_back(c0); 120 positions->push_back(p1); 121 colors->push_back(c1); 122 } 123 124 // Creates a SkShader to fade the text, with |left_part| specifying the left 125 // fade effect, if any, and |right_part| specifying the right fade effect. 126 skia::RefPtr<SkShader> CreateFadeShader(const Rect& text_rect, 127 const Rect& left_part, 128 const Rect& right_part, 129 SkColor color) { 130 // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color. 131 const SkColor fade_color = SkColorSetA(color, 51); 132 std::vector<SkScalar> positions; 133 std::vector<SkColor> colors; 134 135 if (!left_part.IsEmpty()) 136 AddFadeEffect(text_rect, left_part, fade_color, color, 137 &positions, &colors); 138 if (!right_part.IsEmpty()) 139 AddFadeEffect(text_rect, right_part, color, fade_color, 140 &positions, &colors); 141 DCHECK(!positions.empty()); 142 143 // Terminate |positions| with 1.0, as required by Skia. 144 if (positions.back() != 1.0) { 145 positions.push_back(1.0); 146 colors.push_back(colors.back()); 147 } 148 149 SkPoint points[2]; 150 points[0].iset(text_rect.x(), text_rect.y()); 151 points[1].iset(text_rect.right(), text_rect.y()); 152 153 return skia::AdoptRef( 154 SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0], 155 colors.size(), SkShader::kClamp_TileMode)); 156 } 157 158 } // namespace 159 160 namespace internal { 161 162 // Value of |underline_thickness_| that indicates that underline metrics have 163 // not been set explicitly. 164 const SkScalar kUnderlineMetricsNotSet = -1.0f; 165 166 SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas) 167 : canvas_(canvas), 168 canvas_skia_(canvas->sk_canvas()), 169 started_drawing_(false), 170 underline_thickness_(kUnderlineMetricsNotSet), 171 underline_position_(0.0f) { 172 DCHECK(canvas_skia_); 173 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 174 paint_.setStyle(SkPaint::kFill_Style); 175 paint_.setAntiAlias(true); 176 paint_.setSubpixelText(true); 177 paint_.setLCDRenderText(true); 178 paint_.setHinting(SkPaint::kNormal_Hinting); 179 bounds_.setEmpty(); 180 } 181 182 SkiaTextRenderer::~SkiaTextRenderer() { 183 // Work-around for http://crbug.com/122743, where non-ClearType text is 184 // rendered with incorrect gamma when using the fade shader. Draw the text 185 // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode. 186 // 187 // TODO(asvitkine): Remove this work-around once the Skia bug is fixed. 188 // http://code.google.com/p/skia/issues/detail?id=590 189 if (deferred_fade_shader_.get()) { 190 paint_.setShader(deferred_fade_shader_.get()); 191 paint_.setXfermodeMode(SkXfermode::kDstIn_Mode); 192 canvas_skia_->drawRect(bounds_, paint_); 193 canvas_skia_->restore(); 194 } 195 } 196 197 void SkiaTextRenderer::SetDrawLooper(SkDrawLooper* draw_looper) { 198 paint_.setLooper(draw_looper); 199 } 200 201 void SkiaTextRenderer::SetFontSmoothingSettings(bool antialiasing, 202 bool subpixel_rendering, 203 bool subpixel_positioning) { 204 paint_.setAntiAlias(antialiasing); 205 paint_.setLCDRenderText(subpixel_rendering); 206 paint_.setSubpixelText(subpixel_positioning); 207 } 208 209 void SkiaTextRenderer::SetFontHinting(SkPaint::Hinting hinting) { 210 paint_.setHinting(hinting); 211 } 212 213 void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) { 214 paint_.setTypeface(typeface); 215 } 216 217 void SkiaTextRenderer::SetTextSize(SkScalar size) { 218 paint_.setTextSize(size); 219 } 220 221 void SkiaTextRenderer::SetFontFamilyWithStyle(const std::string& family, 222 int style) { 223 DCHECK(!family.empty()); 224 225 skia::RefPtr<SkTypeface> typeface = CreateSkiaTypeface(family.c_str(), style); 226 if (typeface) { 227 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here. 228 SetTypeface(typeface.get()); 229 230 // Enable fake bold text if bold style is needed but new typeface does not 231 // have it. 232 paint_.setFakeBoldText((style & Font::BOLD) && !typeface->isBold()); 233 } 234 } 235 236 void SkiaTextRenderer::SetForegroundColor(SkColor foreground) { 237 paint_.setColor(foreground); 238 } 239 240 void SkiaTextRenderer::SetShader(SkShader* shader, const Rect& bounds) { 241 bounds_ = RectToSkRect(bounds); 242 paint_.setShader(shader); 243 } 244 245 void SkiaTextRenderer::SetUnderlineMetrics(SkScalar thickness, 246 SkScalar position) { 247 underline_thickness_ = thickness; 248 underline_position_ = position; 249 } 250 251 void SkiaTextRenderer::DrawPosText(const SkPoint* pos, 252 const uint16* glyphs, 253 size_t glyph_count) { 254 if (!started_drawing_) { 255 started_drawing_ = true; 256 // Work-around for http://crbug.com/122743, where non-ClearType text is 257 // rendered with incorrect gamma when using the fade shader. Draw the text 258 // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode. 259 // 260 // Skip this when there is a looper which seems not working well with 261 // deferred paint. Currently a looper is only used for text shadows. 262 // 263 // TODO(asvitkine): Remove this work-around once the Skia bug is fixed. 264 // http://code.google.com/p/skia/issues/detail?id=590 265 if (!paint_.isLCDRenderText() && 266 paint_.getShader() && 267 !paint_.getLooper()) { 268 deferred_fade_shader_ = skia::SharePtr(paint_.getShader()); 269 paint_.setShader(NULL); 270 canvas_skia_->saveLayer(&bounds_, NULL); 271 } 272 } 273 274 const size_t byte_length = glyph_count * sizeof(glyphs[0]); 275 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_); 276 } 277 278 void SkiaTextRenderer::DrawDecorations(int x, int y, int width, bool underline, 279 bool strike, bool diagonal_strike) { 280 if (underline) 281 DrawUnderline(x, y, width); 282 if (strike) 283 DrawStrike(x, y, width); 284 if (diagonal_strike) { 285 if (!diagonal_) 286 diagonal_.reset(new DiagonalStrike(canvas_, Point(x, y), paint_)); 287 diagonal_->AddPiece(width, paint_.getColor()); 288 } else if (diagonal_) { 289 EndDiagonalStrike(); 290 } 291 } 292 293 void SkiaTextRenderer::EndDiagonalStrike() { 294 if (diagonal_) { 295 diagonal_->Draw(); 296 diagonal_.reset(); 297 } 298 } 299 300 void SkiaTextRenderer::DrawUnderline(int x, int y, int width) { 301 SkRect r = SkRect::MakeLTRB(x, y + underline_position_, x + width, 302 y + underline_position_ + underline_thickness_); 303 if (underline_thickness_ == kUnderlineMetricsNotSet) { 304 const SkScalar text_size = paint_.getTextSize(); 305 r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y); 306 r.fBottom = r.fTop + SkScalarMul(text_size, kLineThickness); 307 } 308 canvas_skia_->drawRect(r, paint_); 309 } 310 311 void SkiaTextRenderer::DrawStrike(int x, int y, int width) const { 312 const SkScalar text_size = paint_.getTextSize(); 313 const SkScalar height = SkScalarMul(text_size, kLineThickness); 314 const SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y); 315 const SkRect r = SkRect::MakeLTRB(x, offset, x + width, offset + height); 316 canvas_skia_->drawRect(r, paint_); 317 } 318 319 SkiaTextRenderer::DiagonalStrike::DiagonalStrike(Canvas* canvas, 320 Point start, 321 const SkPaint& paint) 322 : canvas_(canvas), 323 matrix_(canvas->sk_canvas()->getTotalMatrix()), 324 start_(start), 325 paint_(paint), 326 total_length_(0) { 327 } 328 329 SkiaTextRenderer::DiagonalStrike::~DiagonalStrike() { 330 } 331 332 void SkiaTextRenderer::DiagonalStrike::AddPiece(int length, SkColor color) { 333 pieces_.push_back(Piece(length, color)); 334 total_length_ += length; 335 } 336 337 void SkiaTextRenderer::DiagonalStrike::Draw() { 338 const SkScalar text_size = paint_.getTextSize(); 339 const SkScalar offset = SkScalarMul(text_size, kDiagonalStrikeMarginOffset); 340 const int thickness = 341 SkScalarCeilToInt(SkScalarMul(text_size, kLineThickness) * 2); 342 const int height = SkScalarCeilToInt(text_size - offset); 343 const Point end = start_ + Vector2d(total_length_, -height); 344 const int clip_height = height + 2 * thickness; 345 346 paint_.setAntiAlias(true); 347 paint_.setStrokeWidth(thickness); 348 349 ScopedCanvas scoped_canvas(canvas_); 350 351 SkCanvas* sk_canvas = canvas_->sk_canvas(); 352 sk_canvas->setMatrix(matrix_); 353 354 const bool clipped = pieces_.size() > 1; 355 int x = start_.x(); 356 for (size_t i = 0; i < pieces_.size(); ++i) { 357 paint_.setColor(pieces_[i].second); 358 359 if (clipped) { 360 sk_canvas->clipRect(RectToSkRect( 361 Rect(x, end.y() - thickness, pieces_[i].first, clip_height)), 362 SkRegion::kReplace_Op); 363 } 364 365 canvas_->DrawLine(start_, end, paint_); 366 367 x += pieces_[i].first; 368 } 369 } 370 371 StyleIterator::StyleIterator(const BreakList<SkColor>& colors, 372 const std::vector<BreakList<bool> >& styles) 373 : colors_(colors), 374 styles_(styles) { 375 color_ = colors_.breaks().begin(); 376 for (size_t i = 0; i < styles_.size(); ++i) 377 style_.push_back(styles_[i].breaks().begin()); 378 } 379 380 StyleIterator::~StyleIterator() {} 381 382 Range StyleIterator::GetRange() const { 383 Range range(colors_.GetRange(color_)); 384 for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) 385 range = range.Intersect(styles_[i].GetRange(style_[i])); 386 return range; 387 } 388 389 void StyleIterator::UpdatePosition(size_t position) { 390 color_ = colors_.GetBreak(position); 391 for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) 392 style_[i] = styles_[i].GetBreak(position); 393 } 394 395 LineSegment::LineSegment() : run(0) {} 396 397 LineSegment::~LineSegment() {} 398 399 Line::Line() : preceding_heights(0), baseline(0) {} 400 401 Line::~Line() {} 402 403 skia::RefPtr<SkTypeface> CreateSkiaTypeface(const std::string& family, 404 int style) { 405 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style); 406 return skia::AdoptRef(SkTypeface::CreateFromName(family.c_str(), skia_style)); 407 } 408 409 } // namespace internal 410 411 RenderText::~RenderText() { 412 } 413 414 RenderText* RenderText::CreateInstance() { 415 #if defined(OS_MACOSX) && defined(TOOLKIT_VIEWS) 416 // Use the more complete HarfBuzz implementation for Views controls on Mac. 417 return new RenderTextHarfBuzz; 418 #else 419 if (CommandLine::ForCurrentProcess()->HasSwitch( 420 switches::kEnableHarfBuzzRenderText)) { 421 return new RenderTextHarfBuzz; 422 } 423 return CreateNativeInstance(); 424 #endif 425 } 426 427 void RenderText::SetText(const base::string16& text) { 428 DCHECK(!composition_range_.IsValid()); 429 if (text_ == text) 430 return; 431 text_ = text; 432 433 // Adjust ranged styles and colors to accommodate a new text length. 434 const size_t text_length = text_.length(); 435 colors_.SetMax(text_length); 436 for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) 437 styles_[style].SetMax(text_length); 438 cached_bounds_and_offset_valid_ = false; 439 440 // Reset selection model. SetText should always followed by SetSelectionModel 441 // or SetCursorPosition in upper layer. 442 SetSelectionModel(SelectionModel()); 443 444 // Invalidate the cached text direction if it depends on the text contents. 445 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) 446 text_direction_ = base::i18n::UNKNOWN_DIRECTION; 447 448 obscured_reveal_index_ = -1; 449 UpdateLayoutText(); 450 } 451 452 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { 453 if (horizontal_alignment_ != alignment) { 454 horizontal_alignment_ = alignment; 455 display_offset_ = Vector2d(); 456 cached_bounds_and_offset_valid_ = false; 457 } 458 } 459 460 void RenderText::SetFontList(const FontList& font_list) { 461 font_list_ = font_list; 462 baseline_ = kInvalidBaseline; 463 cached_bounds_and_offset_valid_ = false; 464 ResetLayout(); 465 } 466 467 void RenderText::SetCursorEnabled(bool cursor_enabled) { 468 cursor_enabled_ = cursor_enabled; 469 cached_bounds_and_offset_valid_ = false; 470 } 471 472 void RenderText::ToggleInsertMode() { 473 insert_mode_ = !insert_mode_; 474 cached_bounds_and_offset_valid_ = false; 475 } 476 477 void RenderText::SetObscured(bool obscured) { 478 if (obscured != obscured_) { 479 obscured_ = obscured; 480 obscured_reveal_index_ = -1; 481 cached_bounds_and_offset_valid_ = false; 482 UpdateLayoutText(); 483 } 484 } 485 486 void RenderText::SetObscuredRevealIndex(int index) { 487 if (obscured_reveal_index_ == index) 488 return; 489 490 obscured_reveal_index_ = index; 491 cached_bounds_and_offset_valid_ = false; 492 UpdateLayoutText(); 493 } 494 495 void RenderText::SetMultiline(bool multiline) { 496 if (multiline != multiline_) { 497 multiline_ = multiline; 498 cached_bounds_and_offset_valid_ = false; 499 lines_.clear(); 500 } 501 } 502 503 void RenderText::SetElideBehavior(ElideBehavior elide_behavior) { 504 // TODO(skanuj) : Add a test for triggering layout change. 505 if (elide_behavior_ != elide_behavior) { 506 elide_behavior_ = elide_behavior; 507 UpdateLayoutText(); 508 } 509 } 510 511 void RenderText::SetDisplayRect(const Rect& r) { 512 if (r != display_rect_) { 513 display_rect_ = r; 514 baseline_ = kInvalidBaseline; 515 cached_bounds_and_offset_valid_ = false; 516 lines_.clear(); 517 if (elide_behavior_ != TRUNCATE) 518 UpdateLayoutText(); 519 } 520 } 521 522 void RenderText::SetCursorPosition(size_t position) { 523 MoveCursorTo(position, false); 524 } 525 526 void RenderText::MoveCursor(BreakType break_type, 527 VisualCursorDirection direction, 528 bool select) { 529 SelectionModel cursor(cursor_position(), selection_model_.caret_affinity()); 530 // Cancelling a selection moves to the edge of the selection. 531 if (break_type != LINE_BREAK && !selection().is_empty() && !select) { 532 SelectionModel selection_start = GetSelectionModelForSelectionStart(); 533 int start_x = GetCursorBounds(selection_start, true).x(); 534 int cursor_x = GetCursorBounds(cursor, true).x(); 535 // Use the selection start if it is left (when |direction| is CURSOR_LEFT) 536 // or right (when |direction| is CURSOR_RIGHT) of the selection end. 537 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x) 538 cursor = selection_start; 539 // Use the nearest word boundary in the proper |direction| for word breaks. 540 if (break_type == WORD_BREAK) 541 cursor = GetAdjacentSelectionModel(cursor, break_type, direction); 542 // Use an adjacent selection model if the cursor is not at a valid position. 543 if (!IsValidCursorIndex(cursor.caret_pos())) 544 cursor = GetAdjacentSelectionModel(cursor, CHARACTER_BREAK, direction); 545 } else { 546 cursor = GetAdjacentSelectionModel(cursor, break_type, direction); 547 } 548 if (select) 549 cursor.set_selection_start(selection().start()); 550 MoveCursorTo(cursor); 551 } 552 553 bool RenderText::MoveCursorTo(const SelectionModel& model) { 554 // Enforce valid selection model components. 555 size_t text_length = text().length(); 556 Range range(std::min(model.selection().start(), text_length), 557 std::min(model.caret_pos(), text_length)); 558 // The current model only supports caret positions at valid cursor indices. 559 if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end())) 560 return false; 561 SelectionModel sel(range, model.caret_affinity()); 562 bool changed = sel != selection_model_; 563 SetSelectionModel(sel); 564 return changed; 565 } 566 567 bool RenderText::SelectRange(const Range& range) { 568 Range sel(std::min(range.start(), text().length()), 569 std::min(range.end(), text().length())); 570 // Allow selection bounds at valid indicies amid multi-character graphemes. 571 if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end())) 572 return false; 573 LogicalCursorDirection affinity = 574 (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD; 575 SetSelectionModel(SelectionModel(sel, affinity)); 576 return true; 577 } 578 579 bool RenderText::IsPointInSelection(const Point& point) { 580 if (selection().is_empty()) 581 return false; 582 SelectionModel cursor = FindCursorPosition(point); 583 return RangeContainsCaret( 584 selection(), cursor.caret_pos(), cursor.caret_affinity()); 585 } 586 587 void RenderText::ClearSelection() { 588 SetSelectionModel(SelectionModel(cursor_position(), 589 selection_model_.caret_affinity())); 590 } 591 592 void RenderText::SelectAll(bool reversed) { 593 const size_t length = text().length(); 594 const Range all = reversed ? Range(length, 0) : Range(0, length); 595 const bool success = SelectRange(all); 596 DCHECK(success); 597 } 598 599 void RenderText::SelectWord() { 600 if (obscured_) { 601 SelectAll(false); 602 return; 603 } 604 605 size_t selection_max = selection().GetMax(); 606 607 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); 608 bool success = iter.Init(); 609 DCHECK(success); 610 if (!success) 611 return; 612 613 size_t selection_min = selection().GetMin(); 614 if (selection_min == text().length() && selection_min != 0) 615 --selection_min; 616 617 for (; selection_min != 0; --selection_min) { 618 if (iter.IsStartOfWord(selection_min) || 619 iter.IsEndOfWord(selection_min)) 620 break; 621 } 622 623 if (selection_min == selection_max && selection_max != text().length()) 624 ++selection_max; 625 626 for (; selection_max < text().length(); ++selection_max) 627 if (iter.IsEndOfWord(selection_max) || iter.IsStartOfWord(selection_max)) 628 break; 629 630 const bool reversed = selection().is_reversed(); 631 MoveCursorTo(reversed ? selection_max : selection_min, false); 632 MoveCursorTo(reversed ? selection_min : selection_max, true); 633 } 634 635 const Range& RenderText::GetCompositionRange() const { 636 return composition_range_; 637 } 638 639 void RenderText::SetCompositionRange(const Range& composition_range) { 640 CHECK(!composition_range.IsValid() || 641 Range(0, text_.length()).Contains(composition_range)); 642 composition_range_.set_end(composition_range.end()); 643 composition_range_.set_start(composition_range.start()); 644 ResetLayout(); 645 } 646 647 void RenderText::SetColor(SkColor value) { 648 colors_.SetValue(value); 649 650 #if defined(OS_WIN) 651 // TODO(msw): Windows applies colors and decorations in the layout process. 652 cached_bounds_and_offset_valid_ = false; 653 ResetLayout(); 654 #endif 655 } 656 657 void RenderText::ApplyColor(SkColor value, const Range& range) { 658 colors_.ApplyValue(value, range); 659 660 #if defined(OS_WIN) 661 // TODO(msw): Windows applies colors and decorations in the layout process. 662 cached_bounds_and_offset_valid_ = false; 663 ResetLayout(); 664 #endif 665 } 666 667 void RenderText::SetStyle(TextStyle style, bool value) { 668 styles_[style].SetValue(value); 669 670 // Only invalidate the layout on font changes; not for colors or decorations. 671 bool invalidate = (style == BOLD) || (style == ITALIC); 672 #if defined(OS_WIN) 673 // TODO(msw): Windows applies colors and decorations in the layout process. 674 invalidate = true; 675 #endif 676 if (invalidate) { 677 cached_bounds_and_offset_valid_ = false; 678 ResetLayout(); 679 } 680 } 681 682 void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) { 683 styles_[style].ApplyValue(value, range); 684 685 // Only invalidate the layout on font changes; not for colors or decorations. 686 bool invalidate = (style == BOLD) || (style == ITALIC); 687 #if defined(OS_WIN) 688 // TODO(msw): Windows applies colors and decorations in the layout process. 689 invalidate = true; 690 #endif 691 if (invalidate) { 692 cached_bounds_and_offset_valid_ = false; 693 ResetLayout(); 694 } 695 } 696 697 bool RenderText::GetStyle(TextStyle style) const { 698 return (styles_[style].breaks().size() == 1) && 699 styles_[style].breaks().front().second; 700 } 701 702 void RenderText::SetDirectionalityMode(DirectionalityMode mode) { 703 if (mode == directionality_mode_) 704 return; 705 706 directionality_mode_ = mode; 707 text_direction_ = base::i18n::UNKNOWN_DIRECTION; 708 cached_bounds_and_offset_valid_ = false; 709 ResetLayout(); 710 } 711 712 base::i18n::TextDirection RenderText::GetTextDirection() { 713 if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) { 714 switch (directionality_mode_) { 715 case DIRECTIONALITY_FROM_TEXT: 716 // Derive the direction from the display text, which differs from text() 717 // in the case of obscured (password) textfields. 718 text_direction_ = 719 base::i18n::GetFirstStrongCharacterDirection(GetLayoutText()); 720 break; 721 case DIRECTIONALITY_FROM_UI: 722 text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT : 723 base::i18n::LEFT_TO_RIGHT; 724 break; 725 case DIRECTIONALITY_FORCE_LTR: 726 text_direction_ = base::i18n::LEFT_TO_RIGHT; 727 break; 728 case DIRECTIONALITY_FORCE_RTL: 729 text_direction_ = base::i18n::RIGHT_TO_LEFT; 730 break; 731 default: 732 NOTREACHED(); 733 } 734 } 735 736 return text_direction_; 737 } 738 739 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { 740 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ? 741 CURSOR_RIGHT : CURSOR_LEFT; 742 } 743 744 SizeF RenderText::GetStringSizeF() { 745 const Size size = GetStringSize(); 746 return SizeF(size.width(), size.height()); 747 } 748 749 int RenderText::GetContentWidth() { 750 return GetStringSize().width() + (cursor_enabled_ ? 1 : 0); 751 } 752 753 int RenderText::GetBaseline() { 754 if (baseline_ == kInvalidBaseline) 755 baseline_ = DetermineBaselineCenteringText(display_rect(), font_list()); 756 DCHECK_NE(kInvalidBaseline, baseline_); 757 return baseline_; 758 } 759 760 void RenderText::Draw(Canvas* canvas) { 761 EnsureLayout(); 762 763 if (clip_to_display_rect()) { 764 Rect clip_rect(display_rect()); 765 clip_rect.Inset(ShadowValue::GetMargin(shadows_)); 766 767 canvas->Save(); 768 canvas->ClipRect(clip_rect); 769 } 770 771 if (!text().empty() && focused()) 772 DrawSelection(canvas); 773 774 if (cursor_enabled() && cursor_visible() && focused()) 775 DrawCursor(canvas, selection_model_); 776 777 if (!text().empty()) 778 DrawVisualText(canvas); 779 780 if (clip_to_display_rect()) 781 canvas->Restore(); 782 } 783 784 void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) { 785 // Paint cursor. Replace cursor is drawn as rectangle for now. 786 // TODO(msw): Draw a better cursor with a better indication of association. 787 canvas->FillRect(GetCursorBounds(position, true), cursor_color_); 788 } 789 790 bool RenderText::IsValidLogicalIndex(size_t index) { 791 // Check that the index is at a valid code point (not mid-surrgate-pair) and 792 // that it's not truncated from the layout text (its glyph may be shown). 793 // 794 // Indices within truncated text are disallowed so users can easily interact 795 // with the underlying truncated text using the ellipsis as a proxy. This lets 796 // users select all text, select the truncated text, and transition from the 797 // last rendered glyph to the end of the text without getting invisible cursor 798 // positions nor needing unbounded arrow key presses to traverse the ellipsis. 799 return index == 0 || index == text().length() || 800 (index < text().length() && 801 (truncate_length_ == 0 || index < truncate_length_) && 802 IsValidCodePointIndex(text(), index)); 803 } 804 805 Rect RenderText::GetCursorBounds(const SelectionModel& caret, 806 bool insert_mode) { 807 // TODO(ckocagil): Support multiline. This function should return the height 808 // of the line the cursor is on. |GetStringSize()| now returns 809 // the multiline size, eliminate its use here. 810 811 EnsureLayout(); 812 size_t caret_pos = caret.caret_pos(); 813 DCHECK(IsValidLogicalIndex(caret_pos)); 814 // In overtype mode, ignore the affinity and always indicate that we will 815 // overtype the next character. 816 LogicalCursorDirection caret_affinity = 817 insert_mode ? caret.caret_affinity() : CURSOR_FORWARD; 818 int x = 0, width = 1; 819 Size size = GetStringSize(); 820 if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) { 821 // The caret is attached to the boundary. Always return a 1-dip width caret, 822 // since there is nothing to overtype. 823 if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0)) 824 x = size.width(); 825 } else { 826 size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ? 827 caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD); 828 Range xspan(GetGlyphBounds(grapheme_start)); 829 if (insert_mode) { 830 x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start(); 831 } else { // overtype mode 832 x = xspan.GetMin(); 833 width = xspan.length(); 834 } 835 } 836 return Rect(ToViewPoint(Point(x, 0)), Size(width, size.height())); 837 } 838 839 const Rect& RenderText::GetUpdatedCursorBounds() { 840 UpdateCachedBoundsAndOffset(); 841 return cursor_bounds_; 842 } 843 844 size_t RenderText::IndexOfAdjacentGrapheme(size_t index, 845 LogicalCursorDirection direction) { 846 if (index > text().length()) 847 return text().length(); 848 849 EnsureLayout(); 850 851 if (direction == CURSOR_FORWARD) { 852 while (index < text().length()) { 853 index++; 854 if (IsValidCursorIndex(index)) 855 return index; 856 } 857 return text().length(); 858 } 859 860 while (index > 0) { 861 index--; 862 if (IsValidCursorIndex(index)) 863 return index; 864 } 865 return 0; 866 } 867 868 SelectionModel RenderText::GetSelectionModelForSelectionStart() { 869 const Range& sel = selection(); 870 if (sel.is_empty()) 871 return selection_model_; 872 return SelectionModel(sel.start(), 873 sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD); 874 } 875 876 RenderText::RenderText() 877 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT), 878 directionality_mode_(DIRECTIONALITY_FROM_TEXT), 879 text_direction_(base::i18n::UNKNOWN_DIRECTION), 880 cursor_enabled_(true), 881 cursor_visible_(false), 882 insert_mode_(true), 883 cursor_color_(kDefaultColor), 884 selection_color_(kDefaultColor), 885 selection_background_focused_color_(kDefaultSelectionBackgroundColor), 886 focused_(false), 887 composition_range_(Range::InvalidRange()), 888 colors_(kDefaultColor), 889 styles_(NUM_TEXT_STYLES), 890 composition_and_selection_styles_applied_(false), 891 obscured_(false), 892 obscured_reveal_index_(-1), 893 truncate_length_(0), 894 elide_behavior_(TRUNCATE), 895 multiline_(false), 896 background_is_transparent_(false), 897 clip_to_display_rect_(true), 898 baseline_(kInvalidBaseline), 899 cached_bounds_and_offset_valid_(false) { 900 } 901 902 const Vector2d& RenderText::GetUpdatedDisplayOffset() { 903 UpdateCachedBoundsAndOffset(); 904 return display_offset_; 905 } 906 907 SelectionModel RenderText::GetAdjacentSelectionModel( 908 const SelectionModel& current, 909 BreakType break_type, 910 VisualCursorDirection direction) { 911 EnsureLayout(); 912 913 if (break_type == LINE_BREAK || text().empty()) 914 return EdgeSelectionModel(direction); 915 if (break_type == CHARACTER_BREAK) 916 return AdjacentCharSelectionModel(current, direction); 917 DCHECK(break_type == WORD_BREAK); 918 return AdjacentWordSelectionModel(current, direction); 919 } 920 921 SelectionModel RenderText::EdgeSelectionModel( 922 VisualCursorDirection direction) { 923 if (direction == GetVisualDirectionOfLogicalEnd()) 924 return SelectionModel(text().length(), CURSOR_FORWARD); 925 return SelectionModel(0, CURSOR_BACKWARD); 926 } 927 928 void RenderText::SetSelectionModel(const SelectionModel& model) { 929 DCHECK_LE(model.selection().GetMax(), text().length()); 930 selection_model_ = model; 931 cached_bounds_and_offset_valid_ = false; 932 } 933 934 const base::string16& RenderText::GetLayoutText() const { 935 return layout_text_; 936 } 937 938 const BreakList<size_t>& RenderText::GetLineBreaks() { 939 if (line_breaks_.max() != 0) 940 return line_breaks_; 941 942 const base::string16& layout_text = GetLayoutText(); 943 const size_t text_length = layout_text.length(); 944 line_breaks_.SetValue(0); 945 line_breaks_.SetMax(text_length); 946 base::i18n::BreakIterator iter(layout_text, 947 base::i18n::BreakIterator::BREAK_LINE); 948 const bool success = iter.Init(); 949 DCHECK(success); 950 if (success) { 951 do { 952 line_breaks_.ApplyValue(iter.pos(), Range(iter.pos(), text_length)); 953 } while (iter.Advance()); 954 } 955 return line_breaks_; 956 } 957 958 void RenderText::ApplyCompositionAndSelectionStyles() { 959 // Save the underline and color breaks to undo the temporary styles later. 960 DCHECK(!composition_and_selection_styles_applied_); 961 saved_colors_ = colors_; 962 saved_underlines_ = styles_[UNDERLINE]; 963 964 // Apply an underline to the composition range in |underlines|. 965 if (composition_range_.IsValid() && !composition_range_.is_empty()) 966 styles_[UNDERLINE].ApplyValue(true, composition_range_); 967 968 // Apply the selected text color to the [un-reversed] selection range. 969 if (!selection().is_empty() && focused()) { 970 const Range range(selection().GetMin(), selection().GetMax()); 971 colors_.ApplyValue(selection_color_, range); 972 } 973 composition_and_selection_styles_applied_ = true; 974 } 975 976 void RenderText::UndoCompositionAndSelectionStyles() { 977 // Restore the underline and color breaks to undo the temporary styles. 978 DCHECK(composition_and_selection_styles_applied_); 979 colors_ = saved_colors_; 980 styles_[UNDERLINE] = saved_underlines_; 981 composition_and_selection_styles_applied_ = false; 982 } 983 984 Vector2d RenderText::GetLineOffset(size_t line_number) { 985 Vector2d offset = display_rect().OffsetFromOrigin(); 986 // TODO(ckocagil): Apply the display offset for multiline scrolling. 987 if (!multiline()) 988 offset.Add(GetUpdatedDisplayOffset()); 989 else 990 offset.Add(Vector2d(0, lines_[line_number].preceding_heights)); 991 offset.Add(GetAlignmentOffset(line_number)); 992 return offset; 993 } 994 995 Point RenderText::ToTextPoint(const Point& point) { 996 return point - GetLineOffset(0); 997 // TODO(ckocagil): Convert multiline view space points to text space. 998 } 999 1000 Point RenderText::ToViewPoint(const Point& point) { 1001 if (!multiline()) 1002 return point + GetLineOffset(0); 1003 1004 // TODO(ckocagil): Traverse individual line segments for RTL support. 1005 DCHECK(!lines_.empty()); 1006 int x = point.x(); 1007 size_t line = 0; 1008 for (; line < lines_.size() && x > lines_[line].size.width(); ++line) 1009 x -= lines_[line].size.width(); 1010 return Point(x, point.y()) + GetLineOffset(line); 1011 } 1012 1013 std::vector<Rect> RenderText::TextBoundsToViewBounds(const Range& x) { 1014 std::vector<Rect> rects; 1015 1016 if (!multiline()) { 1017 rects.push_back(Rect(ToViewPoint(Point(x.GetMin(), 0)), 1018 Size(x.length(), GetStringSize().height()))); 1019 return rects; 1020 } 1021 1022 EnsureLayout(); 1023 1024 // Each line segment keeps its position in text coordinates. Traverse all line 1025 // segments and if the segment intersects with the given range, add the view 1026 // rect corresponding to the intersection to |rects|. 1027 for (size_t line = 0; line < lines_.size(); ++line) { 1028 int line_x = 0; 1029 const Vector2d offset = GetLineOffset(line); 1030 for (size_t i = 0; i < lines_[line].segments.size(); ++i) { 1031 const internal::LineSegment* segment = &lines_[line].segments[i]; 1032 const Range intersection = segment->x_range.Intersect(x); 1033 if (!intersection.is_empty()) { 1034 Rect rect(line_x + intersection.start() - segment->x_range.start(), 1035 0, intersection.length(), lines_[line].size.height()); 1036 rects.push_back(rect + offset); 1037 } 1038 line_x += segment->x_range.length(); 1039 } 1040 } 1041 1042 return rects; 1043 } 1044 1045 Vector2d RenderText::GetAlignmentOffset(size_t line_number) { 1046 // TODO(ckocagil): Enable |lines_| usage in other platforms. 1047 #if defined(OS_WIN) 1048 DCHECK_LT(line_number, lines_.size()); 1049 #endif 1050 Vector2d offset; 1051 if (horizontal_alignment_ != ALIGN_LEFT) { 1052 #if defined(OS_WIN) 1053 const int width = lines_[line_number].size.width() + 1054 (cursor_enabled_ ? 1 : 0); 1055 #else 1056 const int width = GetContentWidth(); 1057 #endif 1058 offset.set_x(display_rect().width() - width); 1059 if (horizontal_alignment_ == ALIGN_CENTER) 1060 offset.set_x(offset.x() / 2); 1061 } 1062 1063 // Vertically center the text. 1064 if (multiline_) { 1065 const int text_height = lines_.back().preceding_heights + 1066 lines_.back().size.height(); 1067 offset.set_y((display_rect_.height() - text_height) / 2); 1068 } else { 1069 offset.set_y(GetBaseline() - GetLayoutTextBaseline()); 1070 } 1071 1072 return offset; 1073 } 1074 1075 void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) { 1076 const int width = display_rect().width(); 1077 if (multiline() || elide_behavior_ != FADE_TAIL || GetContentWidth() <= width) 1078 return; 1079 1080 const int gradient_width = CalculateFadeGradientWidth(font_list(), width); 1081 if (gradient_width == 0) 1082 return; 1083 1084 Rect solid_part = display_rect(); 1085 Rect left_part; 1086 Rect right_part; 1087 if (horizontal_alignment_ != ALIGN_LEFT) { 1088 left_part = solid_part; 1089 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0); 1090 solid_part.Inset(gradient_width, 0, 0, 0); 1091 } 1092 if (horizontal_alignment_ != ALIGN_RIGHT) { 1093 right_part = solid_part; 1094 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0); 1095 solid_part.Inset(0, 0, gradient_width, 0); 1096 } 1097 1098 Rect text_rect = display_rect(); 1099 text_rect.Inset(GetAlignmentOffset(0).x(), 0, 0, 0); 1100 1101 // TODO(msw): Use the actual text colors corresponding to each faded part. 1102 skia::RefPtr<SkShader> shader = CreateFadeShader( 1103 text_rect, left_part, right_part, colors_.breaks().front().second); 1104 if (shader) 1105 renderer->SetShader(shader.get(), display_rect()); 1106 } 1107 1108 void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { 1109 skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(shadows_); 1110 renderer->SetDrawLooper(looper.get()); 1111 } 1112 1113 // static 1114 bool RenderText::RangeContainsCaret(const Range& range, 1115 size_t caret_pos, 1116 LogicalCursorDirection caret_affinity) { 1117 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9). 1118 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ? 1119 caret_pos - 1 : caret_pos + 1; 1120 return range.Contains(Range(caret_pos, adjacent)); 1121 } 1122 1123 void RenderText::MoveCursorTo(size_t position, bool select) { 1124 size_t cursor = std::min(position, text().length()); 1125 if (IsValidCursorIndex(cursor)) 1126 SetSelectionModel(SelectionModel( 1127 Range(select ? selection().start() : cursor, cursor), 1128 (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD)); 1129 } 1130 1131 void RenderText::UpdateLayoutText() { 1132 layout_text_.clear(); 1133 line_breaks_.SetMax(0); 1134 1135 if (obscured_) { 1136 size_t obscured_text_length = 1137 static_cast<size_t>(UTF16IndexToOffset(text_, 0, text_.length())); 1138 layout_text_.assign(obscured_text_length, kPasswordReplacementChar); 1139 1140 if (obscured_reveal_index_ >= 0 && 1141 obscured_reveal_index_ < static_cast<int>(text_.length())) { 1142 // Gets the index range in |text_| to be revealed. 1143 size_t start = obscured_reveal_index_; 1144 U16_SET_CP_START(text_.data(), 0, start); 1145 size_t end = start; 1146 UChar32 unused_char; 1147 U16_NEXT(text_.data(), end, text_.length(), unused_char); 1148 1149 // Gets the index in |layout_text_| to be replaced. 1150 const size_t cp_start = 1151 static_cast<size_t>(UTF16IndexToOffset(text_, 0, start)); 1152 if (layout_text_.length() > cp_start) 1153 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); 1154 } 1155 } else { 1156 layout_text_ = text_; 1157 } 1158 1159 const base::string16& text = layout_text_; 1160 if (truncate_length_ > 0 && truncate_length_ < text.length()) { 1161 // Truncate the text at a valid character break and append an ellipsis. 1162 icu::StringCharacterIterator iter(text.c_str()); 1163 iter.setIndex32(truncate_length_ - 1); 1164 layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16); 1165 } 1166 1167 if (elide_behavior_ != TRUNCATE && elide_behavior_ != FADE_TAIL && 1168 display_rect_.width() > 0 && !layout_text_.empty() && 1169 GetContentWidth() > display_rect_.width()) { 1170 // This doesn't trim styles so ellipsis may get rendered as a different 1171 // style than the preceding text. See crbug.com/327850. 1172 layout_text_.assign(ElideText(layout_text_)); 1173 } 1174 1175 ResetLayout(); 1176 } 1177 1178 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc 1179 // See crbug.com/327846 1180 base::string16 RenderText::ElideText(const base::string16& text) { 1181 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE); 1182 // Create a RenderText copy with attributes that affect the rendering width. 1183 scoped_ptr<RenderText> render_text(CreateInstance()); 1184 render_text->SetFontList(font_list_); 1185 render_text->SetDirectionalityMode(directionality_mode_); 1186 render_text->SetCursorEnabled(cursor_enabled_); 1187 1188 render_text->styles_ = styles_; 1189 render_text->colors_ = colors_; 1190 render_text->SetText(text); 1191 const int current_text_pixel_width = render_text->GetContentWidth(); 1192 1193 const base::string16 ellipsis = base::string16(kEllipsisUTF16); 1194 const bool elide_in_middle = false; 1195 const bool elide_at_beginning = false; 1196 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning); 1197 1198 // Pango will return 0 width for absurdly long strings. Cut the string in 1199 // half and try again. 1200 // This is caused by an int overflow in Pango (specifically, in 1201 // pango_glyph_string_extents_range). It's actually more subtle than just 1202 // returning 0, since on super absurdly long strings, the int can wrap and 1203 // return positive numbers again. Detecting that is probably not worth it 1204 // (eliding way too much from a ridiculous string is probably still 1205 // ridiculous), but we should check other widths for bogus values as well. 1206 if (current_text_pixel_width <= 0 && !text.empty()) 1207 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis)); 1208 1209 if (current_text_pixel_width <= display_rect_.width()) 1210 return text; 1211 1212 render_text->SetText(base::string16()); 1213 render_text->SetText(ellipsis); 1214 const int ellipsis_width = render_text->GetContentWidth(); 1215 1216 if (insert_ellipsis && (ellipsis_width >= display_rect_.width())) 1217 return base::string16(); 1218 1219 // Use binary search to compute the elided text. 1220 size_t lo = 0; 1221 size_t hi = text.length() - 1; 1222 const base::i18n::TextDirection text_direction = GetTextDirection(); 1223 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { 1224 // Restore styles and colors. They will be truncated to size by SetText. 1225 render_text->styles_ = styles_; 1226 render_text->colors_ = colors_; 1227 base::string16 new_text = slicer.CutString(guess, false); 1228 render_text->SetText(new_text); 1229 1230 // This has to be an additional step so that the ellipsis is rendered with 1231 // same style as trailing part of the text. 1232 if (insert_ellipsis) { 1233 // When ellipsis follows text whose directionality is not the same as that 1234 // of the whole text, it will be rendered with the directionality of the 1235 // whole text. Since we want ellipsis to indicate continuation of the 1236 // preceding text, we force the directionality of ellipsis to be same as 1237 // the preceding text using LTR or RTL markers. 1238 base::i18n::TextDirection trailing_text_direction = 1239 base::i18n::GetLastStrongCharacterDirection(new_text); 1240 new_text.append(ellipsis); 1241 if (trailing_text_direction != text_direction) { 1242 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT) 1243 new_text += base::i18n::kLeftToRightMark; 1244 else 1245 new_text += base::i18n::kRightToLeftMark; 1246 } 1247 render_text->SetText(new_text); 1248 } 1249 1250 // We check the width of the whole desired string at once to ensure we 1251 // handle kerning/ligatures/etc. correctly. 1252 const int guess_width = render_text->GetContentWidth(); 1253 if (guess_width == display_rect_.width()) 1254 break; 1255 if (guess_width > display_rect_.width()) { 1256 hi = guess - 1; 1257 // Move back if we are on loop terminating condition, and guess is wider 1258 // than available. 1259 if (hi < lo) 1260 lo = hi; 1261 } else { 1262 lo = guess + 1; 1263 } 1264 } 1265 1266 return render_text->text(); 1267 } 1268 1269 void RenderText::UpdateCachedBoundsAndOffset() { 1270 if (cached_bounds_and_offset_valid_) 1271 return; 1272 1273 // TODO(ckocagil): Add support for scrolling multiline text. 1274 1275 // First, set the valid flag true to calculate the current cursor bounds using 1276 // the stale |display_offset_|. Applying |delta_offset| at the end of this 1277 // function will set |cursor_bounds_| and |display_offset_| to correct values. 1278 cached_bounds_and_offset_valid_ = true; 1279 if (cursor_enabled()) 1280 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_); 1281 1282 // Update |display_offset_| to ensure the current cursor is visible. 1283 const int display_width = display_rect_.width(); 1284 const int content_width = GetContentWidth(); 1285 1286 int delta_x = 0; 1287 if (content_width <= display_width || !cursor_enabled()) { 1288 // Don't pan if the text fits in the display width or when the cursor is 1289 // disabled. 1290 delta_x = -display_offset_.x(); 1291 } else if (cursor_bounds_.right() > display_rect_.right()) { 1292 // TODO(xji): when the character overflow is a RTL character, currently, if 1293 // we pan cursor at the rightmost position, the entered RTL character is not 1294 // displayed. Should pan cursor to show the last logical characters. 1295 // 1296 // Pan to show the cursor when it overflows to the right. 1297 delta_x = display_rect_.right() - cursor_bounds_.right(); 1298 } else if (cursor_bounds_.x() < display_rect_.x()) { 1299 // TODO(xji): have similar problem as above when overflow character is a 1300 // LTR character. 1301 // 1302 // Pan to show the cursor when it overflows to the left. 1303 delta_x = display_rect_.x() - cursor_bounds_.x(); 1304 } else if (display_offset_.x() != 0) { 1305 // Reduce the pan offset to show additional overflow text when the display 1306 // width increases. 1307 const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1; 1308 const int offset = negate_rtl * display_offset_.x(); 1309 if (display_width > (content_width + offset)) { 1310 delta_x = negate_rtl * (display_width - (content_width + offset)); 1311 } 1312 } 1313 1314 Vector2d delta_offset(delta_x, 0); 1315 display_offset_ += delta_offset; 1316 cursor_bounds_ += delta_offset; 1317 } 1318 1319 void RenderText::DrawSelection(Canvas* canvas) { 1320 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1321 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) 1322 canvas->FillRect(*i, selection_background_focused_color_); 1323 } 1324 1325 } // namespace gfx 1326