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/string_util.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "third_party/icu/source/common/unicode/rbbi.h" 17 #include "third_party/icu/source/common/unicode/utf16.h" 18 #include "third_party/skia/include/core/SkTypeface.h" 19 #include "third_party/skia/include/effects/SkGradientShader.h" 20 #include "ui/gfx/canvas.h" 21 #include "ui/gfx/insets.h" 22 #include "ui/gfx/render_text_harfbuzz.h" 23 #include "ui/gfx/scoped_canvas.h" 24 #include "ui/gfx/skia_util.h" 25 #include "ui/gfx/switches.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 // Converts a FontRenderParams::Hinting value to the corresponding 159 // SkPaint::Hinting value. 160 SkPaint::Hinting FontRenderParamsHintingToSkPaintHinting( 161 FontRenderParams::Hinting params_hinting) { 162 switch (params_hinting) { 163 case FontRenderParams::HINTING_NONE: return SkPaint::kNo_Hinting; 164 case FontRenderParams::HINTING_SLIGHT: return SkPaint::kSlight_Hinting; 165 case FontRenderParams::HINTING_MEDIUM: return SkPaint::kNormal_Hinting; 166 case FontRenderParams::HINTING_FULL: return SkPaint::kFull_Hinting; 167 } 168 return SkPaint::kNo_Hinting; 169 } 170 171 } // namespace 172 173 namespace internal { 174 175 // Value of |underline_thickness_| that indicates that underline metrics have 176 // not been set explicitly. 177 const SkScalar kUnderlineMetricsNotSet = -1.0f; 178 179 SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas) 180 : canvas_(canvas), 181 canvas_skia_(canvas->sk_canvas()), 182 underline_thickness_(kUnderlineMetricsNotSet), 183 underline_position_(0.0f) { 184 DCHECK(canvas_skia_); 185 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 186 paint_.setStyle(SkPaint::kFill_Style); 187 paint_.setAntiAlias(true); 188 paint_.setSubpixelText(true); 189 paint_.setLCDRenderText(true); 190 paint_.setHinting(SkPaint::kNormal_Hinting); 191 } 192 193 SkiaTextRenderer::~SkiaTextRenderer() { 194 } 195 196 void SkiaTextRenderer::SetDrawLooper(SkDrawLooper* draw_looper) { 197 paint_.setLooper(draw_looper); 198 } 199 200 void SkiaTextRenderer::SetFontRenderParams(const FontRenderParams& params, 201 bool background_is_transparent) { 202 ApplyRenderParams(params, background_is_transparent, &paint_); 203 } 204 205 void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) { 206 paint_.setTypeface(typeface); 207 } 208 209 void SkiaTextRenderer::SetTextSize(SkScalar size) { 210 paint_.setTextSize(size); 211 } 212 213 void SkiaTextRenderer::SetFontFamilyWithStyle(const std::string& family, 214 int style) { 215 DCHECK(!family.empty()); 216 217 skia::RefPtr<SkTypeface> typeface = CreateSkiaTypeface(family.c_str(), style); 218 if (typeface) { 219 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here. 220 SetTypeface(typeface.get()); 221 222 // Enable fake bold text if bold style is needed but new typeface does not 223 // have it. 224 paint_.setFakeBoldText((style & Font::BOLD) && !typeface->isBold()); 225 } 226 } 227 228 void SkiaTextRenderer::SetForegroundColor(SkColor foreground) { 229 paint_.setColor(foreground); 230 } 231 232 void SkiaTextRenderer::SetShader(SkShader* shader) { 233 paint_.setShader(shader); 234 } 235 236 void SkiaTextRenderer::SetUnderlineMetrics(SkScalar thickness, 237 SkScalar position) { 238 underline_thickness_ = thickness; 239 underline_position_ = position; 240 } 241 242 void SkiaTextRenderer::DrawPosText(const SkPoint* pos, 243 const uint16* glyphs, 244 size_t glyph_count) { 245 const size_t byte_length = glyph_count * sizeof(glyphs[0]); 246 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_); 247 } 248 249 void SkiaTextRenderer::DrawDecorations(int x, int y, int width, bool underline, 250 bool strike, bool diagonal_strike) { 251 if (underline) 252 DrawUnderline(x, y, width); 253 if (strike) 254 DrawStrike(x, y, width); 255 if (diagonal_strike) { 256 if (!diagonal_) 257 diagonal_.reset(new DiagonalStrike(canvas_, Point(x, y), paint_)); 258 diagonal_->AddPiece(width, paint_.getColor()); 259 } else if (diagonal_) { 260 EndDiagonalStrike(); 261 } 262 } 263 264 void SkiaTextRenderer::EndDiagonalStrike() { 265 if (diagonal_) { 266 diagonal_->Draw(); 267 diagonal_.reset(); 268 } 269 } 270 271 void SkiaTextRenderer::DrawUnderline(int x, int y, int width) { 272 SkRect r = SkRect::MakeLTRB(x, y + underline_position_, x + width, 273 y + underline_position_ + underline_thickness_); 274 if (underline_thickness_ == kUnderlineMetricsNotSet) { 275 const SkScalar text_size = paint_.getTextSize(); 276 r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y); 277 r.fBottom = r.fTop + SkScalarMul(text_size, kLineThickness); 278 } 279 canvas_skia_->drawRect(r, paint_); 280 } 281 282 void SkiaTextRenderer::DrawStrike(int x, int y, int width) const { 283 const SkScalar text_size = paint_.getTextSize(); 284 const SkScalar height = SkScalarMul(text_size, kLineThickness); 285 const SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y); 286 const SkRect r = SkRect::MakeLTRB(x, offset, x + width, offset + height); 287 canvas_skia_->drawRect(r, paint_); 288 } 289 290 SkiaTextRenderer::DiagonalStrike::DiagonalStrike(Canvas* canvas, 291 Point start, 292 const SkPaint& paint) 293 : canvas_(canvas), 294 start_(start), 295 paint_(paint), 296 total_length_(0) { 297 } 298 299 SkiaTextRenderer::DiagonalStrike::~DiagonalStrike() { 300 } 301 302 void SkiaTextRenderer::DiagonalStrike::AddPiece(int length, SkColor color) { 303 pieces_.push_back(Piece(length, color)); 304 total_length_ += length; 305 } 306 307 void SkiaTextRenderer::DiagonalStrike::Draw() { 308 const SkScalar text_size = paint_.getTextSize(); 309 const SkScalar offset = SkScalarMul(text_size, kDiagonalStrikeMarginOffset); 310 const int thickness = 311 SkScalarCeilToInt(SkScalarMul(text_size, kLineThickness) * 2); 312 const int height = SkScalarCeilToInt(text_size - offset); 313 const Point end = start_ + Vector2d(total_length_, -height); 314 const int clip_height = height + 2 * thickness; 315 316 paint_.setAntiAlias(true); 317 paint_.setStrokeWidth(thickness); 318 319 const bool clipped = pieces_.size() > 1; 320 SkCanvas* sk_canvas = canvas_->sk_canvas(); 321 int x = start_.x(); 322 323 for (size_t i = 0; i < pieces_.size(); ++i) { 324 paint_.setColor(pieces_[i].second); 325 326 if (clipped) { 327 canvas_->Save(); 328 sk_canvas->clipRect(RectToSkRect( 329 Rect(x, end.y() - thickness, pieces_[i].first, clip_height))); 330 } 331 332 canvas_->DrawLine(start_, end, paint_); 333 334 if (clipped) 335 canvas_->Restore(); 336 337 x += pieces_[i].first; 338 } 339 } 340 341 StyleIterator::StyleIterator(const BreakList<SkColor>& colors, 342 const std::vector<BreakList<bool> >& styles) 343 : colors_(colors), 344 styles_(styles) { 345 color_ = colors_.breaks().begin(); 346 for (size_t i = 0; i < styles_.size(); ++i) 347 style_.push_back(styles_[i].breaks().begin()); 348 } 349 350 StyleIterator::~StyleIterator() {} 351 352 Range StyleIterator::GetRange() const { 353 Range range(colors_.GetRange(color_)); 354 for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) 355 range = range.Intersect(styles_[i].GetRange(style_[i])); 356 return range; 357 } 358 359 void StyleIterator::UpdatePosition(size_t position) { 360 color_ = colors_.GetBreak(position); 361 for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) 362 style_[i] = styles_[i].GetBreak(position); 363 } 364 365 LineSegment::LineSegment() : run(0) {} 366 367 LineSegment::~LineSegment() {} 368 369 Line::Line() : preceding_heights(0), baseline(0) {} 370 371 Line::~Line() {} 372 373 skia::RefPtr<SkTypeface> CreateSkiaTypeface(const std::string& family, 374 int style) { 375 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style); 376 return skia::AdoptRef(SkTypeface::CreateFromName(family.c_str(), skia_style)); 377 } 378 379 void ApplyRenderParams(const FontRenderParams& params, 380 bool background_is_transparent, 381 SkPaint* paint) { 382 paint->setAntiAlias(params.antialiasing); 383 paint->setLCDRenderText(!background_is_transparent && 384 params.subpixel_rendering != FontRenderParams::SUBPIXEL_RENDERING_NONE); 385 paint->setSubpixelText(params.subpixel_positioning); 386 paint->setAutohinted(params.autohinter); 387 paint->setHinting(FontRenderParamsHintingToSkPaintHinting(params.hinting)); 388 } 389 390 } // namespace internal 391 392 RenderText::~RenderText() { 393 } 394 395 RenderText* RenderText::CreateInstance() { 396 #if defined(OS_MACOSX) && defined(TOOLKIT_VIEWS) 397 // Use the more complete HarfBuzz implementation for Views controls on Mac. 398 return new RenderTextHarfBuzz; 399 #else 400 if (CommandLine::ForCurrentProcess()->HasSwitch( 401 switches::kEnableHarfBuzzRenderText)) { 402 return new RenderTextHarfBuzz; 403 } 404 return CreateNativeInstance(); 405 #endif 406 } 407 408 void RenderText::SetText(const base::string16& text) { 409 DCHECK(!composition_range_.IsValid()); 410 if (text_ == text) 411 return; 412 text_ = text; 413 414 // Adjust ranged styles and colors to accommodate a new text length. 415 // Clear style ranges as they might break new text graphemes and apply 416 // the first style to the whole text instead. 417 const size_t text_length = text_.length(); 418 colors_.SetMax(text_length); 419 for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) { 420 BreakList<bool>& break_list = styles_[style]; 421 break_list.SetValue(break_list.breaks().begin()->second); 422 break_list.SetMax(text_length); 423 } 424 cached_bounds_and_offset_valid_ = false; 425 426 // Reset selection model. SetText should always followed by SetSelectionModel 427 // or SetCursorPosition in upper layer. 428 SetSelectionModel(SelectionModel()); 429 430 // Invalidate the cached text direction if it depends on the text contents. 431 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) 432 text_direction_ = base::i18n::UNKNOWN_DIRECTION; 433 434 obscured_reveal_index_ = -1; 435 UpdateLayoutText(); 436 } 437 438 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { 439 if (horizontal_alignment_ != alignment) { 440 horizontal_alignment_ = alignment; 441 display_offset_ = Vector2d(); 442 cached_bounds_and_offset_valid_ = false; 443 } 444 } 445 446 void RenderText::SetFontList(const FontList& font_list) { 447 font_list_ = font_list; 448 const int font_style = font_list.GetFontStyle(); 449 SetStyle(BOLD, (font_style & gfx::Font::BOLD) != 0); 450 SetStyle(ITALIC, (font_style & gfx::Font::ITALIC) != 0); 451 SetStyle(UNDERLINE, (font_style & gfx::Font::UNDERLINE) != 0); 452 baseline_ = kInvalidBaseline; 453 cached_bounds_and_offset_valid_ = false; 454 ResetLayout(); 455 } 456 457 void RenderText::SetCursorEnabled(bool cursor_enabled) { 458 cursor_enabled_ = cursor_enabled; 459 cached_bounds_and_offset_valid_ = false; 460 } 461 462 void RenderText::ToggleInsertMode() { 463 insert_mode_ = !insert_mode_; 464 cached_bounds_and_offset_valid_ = false; 465 } 466 467 void RenderText::SetObscured(bool obscured) { 468 if (obscured != obscured_) { 469 obscured_ = obscured; 470 obscured_reveal_index_ = -1; 471 cached_bounds_and_offset_valid_ = false; 472 UpdateLayoutText(); 473 } 474 } 475 476 void RenderText::SetObscuredRevealIndex(int index) { 477 if (obscured_reveal_index_ == index) 478 return; 479 480 obscured_reveal_index_ = index; 481 cached_bounds_and_offset_valid_ = false; 482 UpdateLayoutText(); 483 } 484 485 void RenderText::SetReplaceNewlineCharsWithSymbols(bool replace) { 486 replace_newline_chars_with_symbols_ = replace; 487 cached_bounds_and_offset_valid_ = false; 488 UpdateLayoutText(); 489 } 490 491 void RenderText::SetMultiline(bool multiline) { 492 if (multiline != multiline_) { 493 multiline_ = multiline; 494 cached_bounds_and_offset_valid_ = false; 495 lines_.clear(); 496 } 497 } 498 499 void RenderText::SetElideBehavior(ElideBehavior elide_behavior) { 500 // TODO(skanuj) : Add a test for triggering layout change. 501 if (elide_behavior_ != elide_behavior) { 502 elide_behavior_ = elide_behavior; 503 UpdateLayoutText(); 504 } 505 } 506 507 void RenderText::SetDisplayRect(const Rect& r) { 508 if (r != display_rect_) { 509 display_rect_ = r; 510 baseline_ = kInvalidBaseline; 511 cached_bounds_and_offset_valid_ = false; 512 lines_.clear(); 513 if (elide_behavior_ != NO_ELIDE) 514 UpdateLayoutText(); 515 } 516 } 517 518 void RenderText::SetCursorPosition(size_t position) { 519 MoveCursorTo(position, false); 520 } 521 522 void RenderText::MoveCursor(BreakType break_type, 523 VisualCursorDirection direction, 524 bool select) { 525 SelectionModel cursor(cursor_position(), selection_model_.caret_affinity()); 526 // Cancelling a selection moves to the edge of the selection. 527 if (break_type != LINE_BREAK && !selection().is_empty() && !select) { 528 SelectionModel selection_start = GetSelectionModelForSelectionStart(); 529 int start_x = GetCursorBounds(selection_start, true).x(); 530 int cursor_x = GetCursorBounds(cursor, true).x(); 531 // Use the selection start if it is left (when |direction| is CURSOR_LEFT) 532 // or right (when |direction| is CURSOR_RIGHT) of the selection end. 533 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x) 534 cursor = selection_start; 535 // Use the nearest word boundary in the proper |direction| for word breaks. 536 if (break_type == WORD_BREAK) 537 cursor = GetAdjacentSelectionModel(cursor, break_type, direction); 538 // Use an adjacent selection model if the cursor is not at a valid position. 539 if (!IsValidCursorIndex(cursor.caret_pos())) 540 cursor = GetAdjacentSelectionModel(cursor, CHARACTER_BREAK, direction); 541 } else { 542 cursor = GetAdjacentSelectionModel(cursor, break_type, direction); 543 } 544 if (select) 545 cursor.set_selection_start(selection().start()); 546 MoveCursorTo(cursor); 547 } 548 549 bool RenderText::MoveCursorTo(const SelectionModel& model) { 550 // Enforce valid selection model components. 551 size_t text_length = text().length(); 552 Range range(std::min(model.selection().start(), text_length), 553 std::min(model.caret_pos(), text_length)); 554 // The current model only supports caret positions at valid cursor indices. 555 if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end())) 556 return false; 557 SelectionModel sel(range, model.caret_affinity()); 558 bool changed = sel != selection_model_; 559 SetSelectionModel(sel); 560 return changed; 561 } 562 563 bool RenderText::SelectRange(const Range& range) { 564 Range sel(std::min(range.start(), text().length()), 565 std::min(range.end(), text().length())); 566 // Allow selection bounds at valid indicies amid multi-character graphemes. 567 if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end())) 568 return false; 569 LogicalCursorDirection affinity = 570 (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD; 571 SetSelectionModel(SelectionModel(sel, affinity)); 572 return true; 573 } 574 575 bool RenderText::IsPointInSelection(const Point& point) { 576 if (selection().is_empty()) 577 return false; 578 SelectionModel cursor = FindCursorPosition(point); 579 return RangeContainsCaret( 580 selection(), cursor.caret_pos(), cursor.caret_affinity()); 581 } 582 583 void RenderText::ClearSelection() { 584 SetSelectionModel(SelectionModel(cursor_position(), 585 selection_model_.caret_affinity())); 586 } 587 588 void RenderText::SelectAll(bool reversed) { 589 const size_t length = text().length(); 590 const Range all = reversed ? Range(length, 0) : Range(0, length); 591 const bool success = SelectRange(all); 592 DCHECK(success); 593 } 594 595 void RenderText::SelectWord() { 596 if (obscured_) { 597 SelectAll(false); 598 return; 599 } 600 601 size_t selection_max = selection().GetMax(); 602 603 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); 604 bool success = iter.Init(); 605 DCHECK(success); 606 if (!success) 607 return; 608 609 size_t selection_min = selection().GetMin(); 610 if (selection_min == text().length() && selection_min != 0) 611 --selection_min; 612 613 for (; selection_min != 0; --selection_min) { 614 if (iter.IsStartOfWord(selection_min) || 615 iter.IsEndOfWord(selection_min)) 616 break; 617 } 618 619 if (selection_min == selection_max && selection_max != text().length()) 620 ++selection_max; 621 622 for (; selection_max < text().length(); ++selection_max) 623 if (iter.IsEndOfWord(selection_max) || iter.IsStartOfWord(selection_max)) 624 break; 625 626 const bool reversed = selection().is_reversed(); 627 MoveCursorTo(reversed ? selection_max : selection_min, false); 628 MoveCursorTo(reversed ? selection_min : selection_max, true); 629 } 630 631 const Range& RenderText::GetCompositionRange() const { 632 return composition_range_; 633 } 634 635 void RenderText::SetCompositionRange(const Range& composition_range) { 636 CHECK(!composition_range.IsValid() || 637 Range(0, text_.length()).Contains(composition_range)); 638 composition_range_.set_end(composition_range.end()); 639 composition_range_.set_start(composition_range.start()); 640 ResetLayout(); 641 } 642 643 void RenderText::SetColor(SkColor value) { 644 colors_.SetValue(value); 645 } 646 647 void RenderText::ApplyColor(SkColor value, const Range& range) { 648 colors_.ApplyValue(value, range); 649 } 650 651 void RenderText::SetStyle(TextStyle style, bool value) { 652 styles_[style].SetValue(value); 653 654 cached_bounds_and_offset_valid_ = false; 655 ResetLayout(); 656 } 657 658 void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) { 659 // Do not change styles mid-grapheme to avoid breaking ligatures. 660 const size_t start = IsValidCursorIndex(range.start()) ? range.start() : 661 IndexOfAdjacentGrapheme(range.start(), CURSOR_BACKWARD); 662 const size_t end = IsValidCursorIndex(range.end()) ? range.end() : 663 IndexOfAdjacentGrapheme(range.end(), CURSOR_FORWARD); 664 styles_[style].ApplyValue(value, Range(start, end)); 665 666 cached_bounds_and_offset_valid_ = false; 667 ResetLayout(); 668 } 669 670 bool RenderText::GetStyle(TextStyle style) const { 671 return (styles_[style].breaks().size() == 1) && 672 styles_[style].breaks().front().second; 673 } 674 675 void RenderText::SetDirectionalityMode(DirectionalityMode mode) { 676 if (mode == directionality_mode_) 677 return; 678 679 directionality_mode_ = mode; 680 text_direction_ = base::i18n::UNKNOWN_DIRECTION; 681 cached_bounds_and_offset_valid_ = false; 682 ResetLayout(); 683 } 684 685 base::i18n::TextDirection RenderText::GetTextDirection() { 686 if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) { 687 switch (directionality_mode_) { 688 case DIRECTIONALITY_FROM_TEXT: 689 // Derive the direction from the display text, which differs from text() 690 // in the case of obscured (password) textfields. 691 text_direction_ = 692 base::i18n::GetFirstStrongCharacterDirection(GetLayoutText()); 693 break; 694 case DIRECTIONALITY_FROM_UI: 695 text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT : 696 base::i18n::LEFT_TO_RIGHT; 697 break; 698 case DIRECTIONALITY_FORCE_LTR: 699 text_direction_ = base::i18n::LEFT_TO_RIGHT; 700 break; 701 case DIRECTIONALITY_FORCE_RTL: 702 text_direction_ = base::i18n::RIGHT_TO_LEFT; 703 break; 704 default: 705 NOTREACHED(); 706 } 707 } 708 709 return text_direction_; 710 } 711 712 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { 713 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ? 714 CURSOR_RIGHT : CURSOR_LEFT; 715 } 716 717 SizeF RenderText::GetStringSizeF() { 718 const Size size = GetStringSize(); 719 return SizeF(size.width(), size.height()); 720 } 721 722 float RenderText::GetContentWidth() { 723 return GetStringSizeF().width() + (cursor_enabled_ ? 1 : 0); 724 } 725 726 int RenderText::GetBaseline() { 727 if (baseline_ == kInvalidBaseline) 728 baseline_ = DetermineBaselineCenteringText(display_rect(), font_list()); 729 DCHECK_NE(kInvalidBaseline, baseline_); 730 return baseline_; 731 } 732 733 void RenderText::Draw(Canvas* canvas) { 734 EnsureLayout(); 735 736 if (clip_to_display_rect()) { 737 Rect clip_rect(display_rect()); 738 clip_rect.Inset(ShadowValue::GetMargin(shadows_)); 739 740 canvas->Save(); 741 canvas->ClipRect(clip_rect); 742 } 743 744 if (!text().empty() && focused()) 745 DrawSelection(canvas); 746 747 if (cursor_enabled() && cursor_visible() && focused()) 748 DrawCursor(canvas, selection_model_); 749 750 if (!text().empty()) 751 DrawVisualText(canvas); 752 753 if (clip_to_display_rect()) 754 canvas->Restore(); 755 } 756 757 void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) { 758 // Paint cursor. Replace cursor is drawn as rectangle for now. 759 // TODO(msw): Draw a better cursor with a better indication of association. 760 canvas->FillRect(GetCursorBounds(position, true), cursor_color_); 761 } 762 763 bool RenderText::IsValidLogicalIndex(size_t index) { 764 // Check that the index is at a valid code point (not mid-surrgate-pair) and 765 // that it's not truncated from the layout text (its glyph may be shown). 766 // 767 // Indices within truncated text are disallowed so users can easily interact 768 // with the underlying truncated text using the ellipsis as a proxy. This lets 769 // users select all text, select the truncated text, and transition from the 770 // last rendered glyph to the end of the text without getting invisible cursor 771 // positions nor needing unbounded arrow key presses to traverse the ellipsis. 772 return index == 0 || index == text().length() || 773 (index < text().length() && 774 (truncate_length_ == 0 || index < truncate_length_) && 775 IsValidCodePointIndex(text(), index)); 776 } 777 778 Rect RenderText::GetCursorBounds(const SelectionModel& caret, 779 bool insert_mode) { 780 // TODO(ckocagil): Support multiline. This function should return the height 781 // of the line the cursor is on. |GetStringSize()| now returns 782 // the multiline size, eliminate its use here. 783 784 EnsureLayout(); 785 size_t caret_pos = caret.caret_pos(); 786 DCHECK(IsValidLogicalIndex(caret_pos)); 787 // In overtype mode, ignore the affinity and always indicate that we will 788 // overtype the next character. 789 LogicalCursorDirection caret_affinity = 790 insert_mode ? caret.caret_affinity() : CURSOR_FORWARD; 791 int x = 0, width = 1; 792 Size size = GetStringSize(); 793 if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) { 794 // The caret is attached to the boundary. Always return a 1-dip width caret, 795 // since there is nothing to overtype. 796 if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0)) 797 x = size.width(); 798 } else { 799 size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ? 800 caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD); 801 Range xspan(GetGlyphBounds(grapheme_start)); 802 if (insert_mode) { 803 x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start(); 804 } else { // overtype mode 805 x = xspan.GetMin(); 806 width = xspan.length(); 807 } 808 } 809 return Rect(ToViewPoint(Point(x, 0)), Size(width, size.height())); 810 } 811 812 const Rect& RenderText::GetUpdatedCursorBounds() { 813 UpdateCachedBoundsAndOffset(); 814 return cursor_bounds_; 815 } 816 817 size_t RenderText::IndexOfAdjacentGrapheme(size_t index, 818 LogicalCursorDirection direction) { 819 if (index > text().length()) 820 return text().length(); 821 822 EnsureLayout(); 823 824 if (direction == CURSOR_FORWARD) { 825 while (index < text().length()) { 826 index++; 827 if (IsValidCursorIndex(index)) 828 return index; 829 } 830 return text().length(); 831 } 832 833 while (index > 0) { 834 index--; 835 if (IsValidCursorIndex(index)) 836 return index; 837 } 838 return 0; 839 } 840 841 SelectionModel RenderText::GetSelectionModelForSelectionStart() { 842 const Range& sel = selection(); 843 if (sel.is_empty()) 844 return selection_model_; 845 return SelectionModel(sel.start(), 846 sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD); 847 } 848 849 const Vector2d& RenderText::GetUpdatedDisplayOffset() { 850 UpdateCachedBoundsAndOffset(); 851 return display_offset_; 852 } 853 854 void RenderText::SetDisplayOffset(int horizontal_offset) { 855 const int extra_content = GetContentWidth() - display_rect_.width(); 856 const int cursor_width = cursor_enabled_ ? 1 : 0; 857 858 int min_offset = 0; 859 int max_offset = 0; 860 if (extra_content > 0) { 861 switch (GetCurrentHorizontalAlignment()) { 862 case ALIGN_LEFT: 863 min_offset = -extra_content; 864 break; 865 case ALIGN_RIGHT: 866 max_offset = extra_content; 867 break; 868 case ALIGN_CENTER: 869 // The extra space reserved for cursor at the end of the text is ignored 870 // when centering text. So, to calculate the valid range for offset, we 871 // exclude that extra space, calculate the range, and add it back to the 872 // range (if cursor is enabled). 873 min_offset = -(extra_content - cursor_width + 1) / 2 - cursor_width; 874 max_offset = (extra_content - cursor_width) / 2; 875 break; 876 default: 877 break; 878 } 879 } 880 if (horizontal_offset < min_offset) 881 horizontal_offset = min_offset; 882 else if (horizontal_offset > max_offset) 883 horizontal_offset = max_offset; 884 885 cached_bounds_and_offset_valid_ = true; 886 display_offset_.set_x(horizontal_offset); 887 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_); 888 } 889 890 RenderText::RenderText() 891 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT), 892 directionality_mode_(DIRECTIONALITY_FROM_TEXT), 893 text_direction_(base::i18n::UNKNOWN_DIRECTION), 894 cursor_enabled_(true), 895 cursor_visible_(false), 896 insert_mode_(true), 897 cursor_color_(kDefaultColor), 898 selection_color_(kDefaultColor), 899 selection_background_focused_color_(kDefaultSelectionBackgroundColor), 900 focused_(false), 901 composition_range_(Range::InvalidRange()), 902 colors_(kDefaultColor), 903 styles_(NUM_TEXT_STYLES), 904 composition_and_selection_styles_applied_(false), 905 obscured_(false), 906 obscured_reveal_index_(-1), 907 truncate_length_(0), 908 elide_behavior_(NO_ELIDE), 909 replace_newline_chars_with_symbols_(true), 910 multiline_(false), 911 background_is_transparent_(false), 912 clip_to_display_rect_(true), 913 baseline_(kInvalidBaseline), 914 cached_bounds_and_offset_valid_(false) { 915 } 916 917 SelectionModel RenderText::GetAdjacentSelectionModel( 918 const SelectionModel& current, 919 BreakType break_type, 920 VisualCursorDirection direction) { 921 EnsureLayout(); 922 923 if (break_type == LINE_BREAK || text().empty()) 924 return EdgeSelectionModel(direction); 925 if (break_type == CHARACTER_BREAK) 926 return AdjacentCharSelectionModel(current, direction); 927 DCHECK(break_type == WORD_BREAK); 928 return AdjacentWordSelectionModel(current, direction); 929 } 930 931 SelectionModel RenderText::EdgeSelectionModel( 932 VisualCursorDirection direction) { 933 if (direction == GetVisualDirectionOfLogicalEnd()) 934 return SelectionModel(text().length(), CURSOR_FORWARD); 935 return SelectionModel(0, CURSOR_BACKWARD); 936 } 937 938 void RenderText::SetSelectionModel(const SelectionModel& model) { 939 DCHECK_LE(model.selection().GetMax(), text().length()); 940 selection_model_ = model; 941 cached_bounds_and_offset_valid_ = false; 942 } 943 944 const base::string16& RenderText::GetLayoutText() const { 945 return layout_text_; 946 } 947 948 const BreakList<size_t>& RenderText::GetLineBreaks() { 949 if (line_breaks_.max() != 0) 950 return line_breaks_; 951 952 const base::string16& layout_text = GetLayoutText(); 953 const size_t text_length = layout_text.length(); 954 line_breaks_.SetValue(0); 955 line_breaks_.SetMax(text_length); 956 base::i18n::BreakIterator iter(layout_text, 957 base::i18n::BreakIterator::BREAK_LINE); 958 const bool success = iter.Init(); 959 DCHECK(success); 960 if (success) { 961 do { 962 line_breaks_.ApplyValue(iter.pos(), Range(iter.pos(), text_length)); 963 } while (iter.Advance()); 964 } 965 return line_breaks_; 966 } 967 968 void RenderText::ApplyCompositionAndSelectionStyles() { 969 // Save the underline and color breaks to undo the temporary styles later. 970 DCHECK(!composition_and_selection_styles_applied_); 971 saved_colors_ = colors_; 972 saved_underlines_ = styles_[UNDERLINE]; 973 974 // Apply an underline to the composition range in |underlines|. 975 if (composition_range_.IsValid() && !composition_range_.is_empty()) 976 styles_[UNDERLINE].ApplyValue(true, composition_range_); 977 978 // Apply the selected text color to the [un-reversed] selection range. 979 if (!selection().is_empty() && focused()) { 980 const Range range(selection().GetMin(), selection().GetMax()); 981 colors_.ApplyValue(selection_color_, range); 982 } 983 composition_and_selection_styles_applied_ = true; 984 } 985 986 void RenderText::UndoCompositionAndSelectionStyles() { 987 // Restore the underline and color breaks to undo the temporary styles. 988 DCHECK(composition_and_selection_styles_applied_); 989 colors_ = saved_colors_; 990 styles_[UNDERLINE] = saved_underlines_; 991 composition_and_selection_styles_applied_ = false; 992 } 993 994 Vector2d RenderText::GetLineOffset(size_t line_number) { 995 Vector2d offset = display_rect().OffsetFromOrigin(); 996 // TODO(ckocagil): Apply the display offset for multiline scrolling. 997 if (!multiline()) 998 offset.Add(GetUpdatedDisplayOffset()); 999 else 1000 offset.Add(Vector2d(0, lines_[line_number].preceding_heights)); 1001 offset.Add(GetAlignmentOffset(line_number)); 1002 return offset; 1003 } 1004 1005 Point RenderText::ToTextPoint(const Point& point) { 1006 return point - GetLineOffset(0); 1007 // TODO(ckocagil): Convert multiline view space points to text space. 1008 } 1009 1010 Point RenderText::ToViewPoint(const Point& point) { 1011 if (!multiline()) 1012 return point + GetLineOffset(0); 1013 1014 // TODO(ckocagil): Traverse individual line segments for RTL support. 1015 DCHECK(!lines_.empty()); 1016 int x = point.x(); 1017 size_t line = 0; 1018 for (; line < lines_.size() && x > lines_[line].size.width(); ++line) 1019 x -= lines_[line].size.width(); 1020 return Point(x, point.y()) + GetLineOffset(line); 1021 } 1022 1023 std::vector<Rect> RenderText::TextBoundsToViewBounds(const Range& x) { 1024 std::vector<Rect> rects; 1025 1026 if (!multiline()) { 1027 rects.push_back(Rect(ToViewPoint(Point(x.GetMin(), 0)), 1028 Size(x.length(), GetStringSize().height()))); 1029 return rects; 1030 } 1031 1032 EnsureLayout(); 1033 1034 // Each line segment keeps its position in text coordinates. Traverse all line 1035 // segments and if the segment intersects with the given range, add the view 1036 // rect corresponding to the intersection to |rects|. 1037 for (size_t line = 0; line < lines_.size(); ++line) { 1038 int line_x = 0; 1039 const Vector2d offset = GetLineOffset(line); 1040 for (size_t i = 0; i < lines_[line].segments.size(); ++i) { 1041 const internal::LineSegment* segment = &lines_[line].segments[i]; 1042 const Range intersection = segment->x_range.Intersect(x); 1043 if (!intersection.is_empty()) { 1044 Rect rect(line_x + intersection.start() - segment->x_range.start(), 1045 0, intersection.length(), lines_[line].size.height()); 1046 rects.push_back(rect + offset); 1047 } 1048 line_x += segment->x_range.length(); 1049 } 1050 } 1051 1052 return rects; 1053 } 1054 1055 HorizontalAlignment RenderText::GetCurrentHorizontalAlignment() { 1056 if (horizontal_alignment_ != ALIGN_TO_HEAD) 1057 return horizontal_alignment_; 1058 return GetTextDirection() == base::i18n::RIGHT_TO_LEFT ? ALIGN_RIGHT 1059 : ALIGN_LEFT; 1060 } 1061 1062 Vector2d RenderText::GetAlignmentOffset(size_t line_number) { 1063 // TODO(ckocagil): Enable |lines_| usage in other platforms. 1064 #if defined(OS_WIN) 1065 DCHECK_LT(line_number, lines_.size()); 1066 #endif 1067 Vector2d offset; 1068 HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment(); 1069 if (horizontal_alignment != ALIGN_LEFT) { 1070 #if defined(OS_WIN) 1071 const int width = lines_[line_number].size.width() + 1072 (cursor_enabled_ ? 1 : 0); 1073 #else 1074 const int width = GetContentWidth(); 1075 #endif 1076 offset.set_x(display_rect().width() - width); 1077 // Put any extra margin pixel on the left to match legacy behavior. 1078 if (horizontal_alignment == ALIGN_CENTER) 1079 offset.set_x((offset.x() + 1) / 2); 1080 } 1081 1082 // Vertically center the text. 1083 if (multiline_) { 1084 const int text_height = lines_.back().preceding_heights + 1085 lines_.back().size.height(); 1086 offset.set_y((display_rect_.height() - text_height) / 2); 1087 } else { 1088 offset.set_y(GetBaseline() - GetLayoutTextBaseline()); 1089 } 1090 1091 return offset; 1092 } 1093 1094 void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) { 1095 const int width = display_rect().width(); 1096 if (multiline() || elide_behavior_ != FADE_TAIL || GetContentWidth() <= width) 1097 return; 1098 1099 const int gradient_width = CalculateFadeGradientWidth(font_list(), width); 1100 if (gradient_width == 0) 1101 return; 1102 1103 HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment(); 1104 Rect solid_part = display_rect(); 1105 Rect left_part; 1106 Rect right_part; 1107 if (horizontal_alignment != ALIGN_LEFT) { 1108 left_part = solid_part; 1109 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0); 1110 solid_part.Inset(gradient_width, 0, 0, 0); 1111 } 1112 if (horizontal_alignment != ALIGN_RIGHT) { 1113 right_part = solid_part; 1114 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0); 1115 solid_part.Inset(0, 0, gradient_width, 0); 1116 } 1117 1118 Rect text_rect = display_rect(); 1119 text_rect.Inset(GetAlignmentOffset(0).x(), 0, 0, 0); 1120 1121 // TODO(msw): Use the actual text colors corresponding to each faded part. 1122 skia::RefPtr<SkShader> shader = CreateFadeShader( 1123 text_rect, left_part, right_part, colors_.breaks().front().second); 1124 if (shader) 1125 renderer->SetShader(shader.get()); 1126 } 1127 1128 void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { 1129 skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(shadows_); 1130 renderer->SetDrawLooper(looper.get()); 1131 } 1132 1133 // static 1134 bool RenderText::RangeContainsCaret(const Range& range, 1135 size_t caret_pos, 1136 LogicalCursorDirection caret_affinity) { 1137 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9). 1138 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ? 1139 caret_pos - 1 : caret_pos + 1; 1140 return range.Contains(Range(caret_pos, adjacent)); 1141 } 1142 1143 void RenderText::MoveCursorTo(size_t position, bool select) { 1144 size_t cursor = std::min(position, text().length()); 1145 if (IsValidCursorIndex(cursor)) 1146 SetSelectionModel(SelectionModel( 1147 Range(select ? selection().start() : cursor, cursor), 1148 (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD)); 1149 } 1150 1151 void RenderText::UpdateLayoutText() { 1152 layout_text_.clear(); 1153 line_breaks_.SetMax(0); 1154 1155 if (obscured_) { 1156 size_t obscured_text_length = 1157 static_cast<size_t>(UTF16IndexToOffset(text_, 0, text_.length())); 1158 layout_text_.assign(obscured_text_length, kPasswordReplacementChar); 1159 1160 if (obscured_reveal_index_ >= 0 && 1161 obscured_reveal_index_ < static_cast<int>(text_.length())) { 1162 // Gets the index range in |text_| to be revealed. 1163 size_t start = obscured_reveal_index_; 1164 U16_SET_CP_START(text_.data(), 0, start); 1165 size_t end = start; 1166 UChar32 unused_char; 1167 U16_NEXT(text_.data(), end, text_.length(), unused_char); 1168 1169 // Gets the index in |layout_text_| to be replaced. 1170 const size_t cp_start = 1171 static_cast<size_t>(UTF16IndexToOffset(text_, 0, start)); 1172 if (layout_text_.length() > cp_start) 1173 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); 1174 } 1175 } else { 1176 layout_text_ = text_; 1177 } 1178 1179 const base::string16& text = layout_text_; 1180 if (truncate_length_ > 0 && truncate_length_ < text.length()) { 1181 // Truncate the text at a valid character break and append an ellipsis. 1182 icu::StringCharacterIterator iter(text.c_str()); 1183 // Respect ELIDE_HEAD and ELIDE_MIDDLE preferences during truncation. 1184 if (elide_behavior_ == ELIDE_HEAD) { 1185 iter.setIndex32(text.length() - truncate_length_ + 1); 1186 layout_text_.assign(kEllipsisUTF16 + text.substr(iter.getIndex())); 1187 } else if (elide_behavior_ == ELIDE_MIDDLE) { 1188 iter.setIndex32(truncate_length_ / 2); 1189 const size_t ellipsis_start = iter.getIndex(); 1190 iter.setIndex32(text.length() - (truncate_length_ / 2)); 1191 const size_t ellipsis_end = iter.getIndex(); 1192 DCHECK_LE(ellipsis_start, ellipsis_end); 1193 layout_text_.assign(text.substr(0, ellipsis_start) + kEllipsisUTF16 + 1194 text.substr(ellipsis_end)); 1195 } else { 1196 iter.setIndex32(truncate_length_ - 1); 1197 layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16); 1198 } 1199 } 1200 1201 if (elide_behavior_ != NO_ELIDE && elide_behavior_ != FADE_TAIL && 1202 !layout_text_.empty() && GetContentWidth() > display_rect_.width()) { 1203 // This doesn't trim styles so ellipsis may get rendered as a different 1204 // style than the preceding text. See crbug.com/327850. 1205 layout_text_.assign( 1206 Elide(layout_text_, display_rect_.width(), elide_behavior_)); 1207 } 1208 1209 // Replace the newline character with a newline symbol in single line mode. 1210 static const base::char16 kNewline[] = { '\n', 0 }; 1211 static const base::char16 kNewlineSymbol[] = { 0x2424, 0 }; 1212 if (!multiline_ && replace_newline_chars_with_symbols_) 1213 base::ReplaceChars(layout_text_, kNewline, kNewlineSymbol, &layout_text_); 1214 1215 ResetLayout(); 1216 } 1217 1218 base::string16 RenderText::Elide(const base::string16& text, 1219 float available_width, 1220 ElideBehavior behavior) { 1221 if (available_width <= 0 || text.empty()) 1222 return base::string16(); 1223 if (behavior == ELIDE_EMAIL) 1224 return ElideEmail(text, available_width); 1225 1226 // Create a RenderText copy with attributes that affect the rendering width. 1227 scoped_ptr<RenderText> render_text(CreateInstance()); 1228 render_text->SetFontList(font_list_); 1229 render_text->SetDirectionalityMode(directionality_mode_); 1230 render_text->SetCursorEnabled(cursor_enabled_); 1231 render_text->set_truncate_length(truncate_length_); 1232 render_text->styles_ = styles_; 1233 render_text->colors_ = colors_; 1234 render_text->SetText(text); 1235 if (render_text->GetContentWidth() <= available_width) 1236 return text; 1237 1238 const base::string16 ellipsis = base::string16(kEllipsisUTF16); 1239 const bool insert_ellipsis = (behavior != TRUNCATE); 1240 const bool elide_in_middle = (behavior == ELIDE_MIDDLE); 1241 const bool elide_at_beginning = (behavior == ELIDE_HEAD); 1242 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning); 1243 1244 render_text->SetText(ellipsis); 1245 const float ellipsis_width = render_text->GetContentWidth(); 1246 1247 if (insert_ellipsis && (ellipsis_width > available_width)) 1248 return base::string16(); 1249 1250 // Use binary search to compute the elided text. 1251 size_t lo = 0; 1252 size_t hi = text.length() - 1; 1253 const base::i18n::TextDirection text_direction = GetTextDirection(); 1254 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { 1255 // Restore colors. They will be truncated to size by SetText. 1256 render_text->colors_ = colors_; 1257 base::string16 new_text = 1258 slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL); 1259 render_text->SetText(new_text); 1260 1261 // This has to be an additional step so that the ellipsis is rendered with 1262 // same style as trailing part of the text. 1263 if (insert_ellipsis && behavior == ELIDE_TAIL) { 1264 // When ellipsis follows text whose directionality is not the same as that 1265 // of the whole text, it will be rendered with the directionality of the 1266 // whole text. Since we want ellipsis to indicate continuation of the 1267 // preceding text, we force the directionality of ellipsis to be same as 1268 // the preceding text using LTR or RTL markers. 1269 base::i18n::TextDirection trailing_text_direction = 1270 base::i18n::GetLastStrongCharacterDirection(new_text); 1271 new_text.append(ellipsis); 1272 if (trailing_text_direction != text_direction) { 1273 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT) 1274 new_text += base::i18n::kLeftToRightMark; 1275 else 1276 new_text += base::i18n::kRightToLeftMark; 1277 } 1278 render_text->SetText(new_text); 1279 } 1280 1281 // Restore styles. Make sure style ranges don't break new text graphemes. 1282 render_text->styles_ = styles_; 1283 for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) { 1284 BreakList<bool>& break_list = render_text->styles_[style]; 1285 break_list.SetMax(render_text->text_.length()); 1286 Range range; 1287 while (range.end() < break_list.max()) { 1288 BreakList<bool>::const_iterator current_break = 1289 break_list.GetBreak(range.end()); 1290 range = break_list.GetRange(current_break); 1291 if (range.end() < break_list.max() && 1292 !render_text->IsValidCursorIndex(range.end())) { 1293 range.set_end(render_text->IndexOfAdjacentGrapheme(range.end(), 1294 CURSOR_FORWARD)); 1295 break_list.ApplyValue(current_break->second, range); 1296 } 1297 } 1298 } 1299 1300 // We check the width of the whole desired string at once to ensure we 1301 // handle kerning/ligatures/etc. correctly. 1302 const float guess_width = render_text->GetContentWidth(); 1303 if (guess_width == available_width) 1304 break; 1305 if (guess_width > available_width) { 1306 hi = guess - 1; 1307 // Move back on the loop terminating condition when the guess is too wide. 1308 if (hi < lo) 1309 lo = hi; 1310 } else { 1311 lo = guess + 1; 1312 } 1313 } 1314 1315 return render_text->text(); 1316 } 1317 1318 base::string16 RenderText::ElideEmail(const base::string16& email, 1319 float available_width) { 1320 // The returned string will have at least one character besides the ellipsis 1321 // on either side of '@'; if that's impossible, a single ellipsis is returned. 1322 // If possible, only the username is elided. Otherwise, the domain is elided 1323 // in the middle, splitting available width equally with the elided username. 1324 // If the username is short enough that it doesn't need half the available 1325 // width, the elided domain will occupy that extra width. 1326 1327 // Split the email into its local-part (username) and domain-part. The email 1328 // spec allows for @ symbols in the username under some special requirements, 1329 // but not in the domain part, so splitting at the last @ symbol is safe. 1330 const size_t split_index = email.find_last_of('@'); 1331 DCHECK_NE(split_index, base::string16::npos); 1332 base::string16 username = email.substr(0, split_index); 1333 base::string16 domain = email.substr(split_index + 1); 1334 DCHECK(!username.empty()); 1335 DCHECK(!domain.empty()); 1336 1337 // Subtract the @ symbol from the available width as it is mandatory. 1338 const base::string16 kAtSignUTF16 = base::ASCIIToUTF16("@"); 1339 available_width -= GetStringWidthF(kAtSignUTF16, font_list()); 1340 1341 // Check whether eliding the domain is necessary: if eliding the username 1342 // is sufficient, the domain will not be elided. 1343 const float full_username_width = GetStringWidthF(username, font_list()); 1344 const float available_domain_width = available_width - 1345 std::min(full_username_width, 1346 GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16, font_list())); 1347 if (GetStringWidthF(domain, font_list()) > available_domain_width) { 1348 // Elide the domain so that it only takes half of the available width. 1349 // Should the username not need all the width available in its half, the 1350 // domain will occupy the leftover width. 1351 // If |desired_domain_width| is greater than |available_domain_width|: the 1352 // minimal username elision allowed by the specifications will not fit; thus 1353 // |desired_domain_width| must be <= |available_domain_width| at all cost. 1354 const float desired_domain_width = 1355 std::min<float>(available_domain_width, 1356 std::max<float>(available_width - full_username_width, 1357 available_width / 2)); 1358 domain = Elide(domain, desired_domain_width, ELIDE_MIDDLE); 1359 // Failing to elide the domain such that at least one character remains 1360 // (other than the ellipsis itself) remains: return a single ellipsis. 1361 if (domain.length() <= 1U) 1362 return base::string16(kEllipsisUTF16); 1363 } 1364 1365 // Fit the username in the remaining width (at this point the elided username 1366 // is guaranteed to fit with at least one character remaining given all the 1367 // precautions taken earlier). 1368 available_width -= GetStringWidthF(domain, font_list()); 1369 username = Elide(username, available_width, ELIDE_TAIL); 1370 return username + kAtSignUTF16 + domain; 1371 } 1372 1373 void RenderText::UpdateCachedBoundsAndOffset() { 1374 if (cached_bounds_and_offset_valid_) 1375 return; 1376 1377 // TODO(ckocagil): Add support for scrolling multiline text. 1378 1379 int delta_x = 0; 1380 1381 if (cursor_enabled()) { 1382 // When cursor is enabled, ensure it is visible. For this, set the valid 1383 // flag true and calculate the current cursor bounds using the stale 1384 // |display_offset_|. Then calculate the change in offset needed to move the 1385 // cursor into the visible area. 1386 cached_bounds_and_offset_valid_ = true; 1387 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_); 1388 1389 // TODO(bidi): Show RTL glyphs at the cursor position for ALIGN_LEFT, etc. 1390 if (cursor_bounds_.right() > display_rect_.right()) 1391 delta_x = display_rect_.right() - cursor_bounds_.right(); 1392 else if (cursor_bounds_.x() < display_rect_.x()) 1393 delta_x = display_rect_.x() - cursor_bounds_.x(); 1394 } 1395 1396 SetDisplayOffset(display_offset_.x() + delta_x); 1397 } 1398 1399 void RenderText::DrawSelection(Canvas* canvas) { 1400 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1401 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) 1402 canvas->FillRect(*i, selection_background_focused_color_); 1403 } 1404 1405 } // namespace gfx 1406