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 <limits.h>
8 
9 #include <algorithm>
10 #include <climits>
11 
12 #include "base/command_line.h"
13 #include "base/i18n/break_iterator.h"
14 #include "base/i18n/char_iterator.h"
15 #include "base/logging.h"
16 #include "base/numerics/ranges.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/trace_event/trace_event.h"
21 #include "build/build_config.h"
22 #include "cc/paint/paint_canvas.h"
23 #include "cc/paint/paint_shader.h"
24 #include "third_party/icu/source/common/unicode/rbbi.h"
25 #include "third_party/icu/source/common/unicode/uchar.h"
26 #include "third_party/icu/source/common/unicode/utf16.h"
27 #include "third_party/skia/include/core/SkDrawLooper.h"
28 #include "third_party/skia/include/core/SkFontStyle.h"
29 #include "third_party/skia/include/core/SkTypeface.h"
30 #include "third_party/skia/include/effects/SkGradientShader.h"
31 #include "ui/gfx/canvas.h"
32 #include "ui/gfx/geometry/insets.h"
33 #include "ui/gfx/geometry/safe_integer_conversions.h"
34 #include "ui/gfx/platform_font.h"
35 #include "ui/gfx/render_text_harfbuzz.h"
36 #include "ui/gfx/scoped_canvas.h"
37 #include "ui/gfx/skia_paint_util.h"
38 #include "ui/gfx/skia_util.h"
39 #include "ui/gfx/text_elider.h"
40 #include "ui/gfx/text_utils.h"
41 #include "ui/gfx/utf16_indexing.h"
42 
43 namespace gfx {
44 
45 namespace {
46 
47 // Replacement codepoint for elided text.
48 constexpr base::char16 kEllipsisCodepoint = 0x2026;
49 
50 // Default color used for the text and cursor.
51 const SkColor kDefaultColor = SK_ColorBLACK;
52 
53 // Default color used for drawing selection background.
54 const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY;
55 
56 // Fraction of the text size to raise the center of a strike-through line above
57 // the baseline.
58 const SkScalar kStrikeThroughOffset = (SK_Scalar1 * 65 / 252);
59 // Fraction of the text size to lower an underline below the baseline.
60 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
61 // Default fraction of the text size to use for a strike-through or underline.
62 const SkScalar kLineThicknessFactor = (SK_Scalar1 / 18);
63 
64 // Invalid value of baseline.  Assigning this value to |baseline_| causes
65 // re-calculation of baseline.
66 const int kInvalidBaseline = INT_MAX;
67 
68 // Float comparison needs epsilon to consider rounding errors in float
69 // arithmetic. Epsilon should be dependent on the context and here, we are
70 // dealing with glyph widths, use a fairly large number.
71 const float kFloatComparisonEpsilon = 0.001f;
Clamp(float f)72 float Clamp(float f) {
73   return f < kFloatComparisonEpsilon ? 0 : f;
74 }
75 
76 // Given |font| and |display_width|, returns the width of the fade gradient.
CalculateFadeGradientWidth(const FontList & font_list,int display_width)77 int CalculateFadeGradientWidth(const FontList& font_list, int display_width) {
78   // Fade in/out about 3 characters of the beginning/end of the string.
79   // Use a 1/3 of the display width if the display width is very short.
80   const int narrow_width = font_list.GetExpectedTextWidth(3);
81   const int gradient_width =
82       std::min(narrow_width, gfx::ToRoundedInt(display_width / 3.f));
83   DCHECK_GE(gradient_width, 0);
84   return gradient_width;
85 }
86 
87 // Appends to |positions| and |colors| values corresponding to the fade over
88 // |fade_rect| from color |c0| to color |c1|.
AddFadeEffect(const Rect & text_rect,const Rect & fade_rect,SkColor c0,SkColor c1,std::vector<SkScalar> * positions,std::vector<SkColor> * colors)89 void AddFadeEffect(const Rect& text_rect,
90                    const Rect& fade_rect,
91                    SkColor c0,
92                    SkColor c1,
93                    std::vector<SkScalar>* positions,
94                    std::vector<SkColor>* colors) {
95   const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x());
96   const SkScalar width = static_cast<SkScalar>(fade_rect.width());
97   const SkScalar p0 = left / text_rect.width();
98   const SkScalar p1 = (left + width) / text_rect.width();
99   // Prepend 0.0 to |positions|, as required by Skia.
100   if (positions->empty() && p0 != 0.0) {
101     positions->push_back(0.0);
102     colors->push_back(c0);
103   }
104   positions->push_back(p0);
105   colors->push_back(c0);
106   positions->push_back(p1);
107   colors->push_back(c1);
108 }
109 
110 // Creates a SkShader to fade the text, with |left_part| specifying the left
111 // fade effect, if any, and |right_part| specifying the right fade effect.
CreateFadeShader(const FontList & font_list,const Rect & text_rect,const Rect & left_part,const Rect & right_part,SkColor color)112 sk_sp<cc::PaintShader> CreateFadeShader(const FontList& font_list,
113                                         const Rect& text_rect,
114                                         const Rect& left_part,
115                                         const Rect& right_part,
116                                         SkColor color) {
117   // The shader should only specify transparency of the fade itself, not the
118   // original transparency, which will be applied by the actual renderer.
119   DCHECK_EQ(SkColorGetA(color), static_cast<uint8_t>(0xff));
120 
121   // In general, fade down to 0 alpha.  But when the available width is less
122   // than four characters, linearly ramp up the fade target alpha to as high as
123   // 20% at zero width.  This allows the user to see the last faded characters a
124   // little better when there are only a few characters shown.
125   const float width_fraction =
126       text_rect.width() / static_cast<float>(font_list.GetExpectedTextWidth(4));
127   const SkAlpha kAlphaAtZeroWidth = 51;
128   const SkAlpha alpha =
129       (width_fraction < 1)
130           ? gfx::ToRoundedInt((1 - width_fraction) * kAlphaAtZeroWidth)
131           : 0;
132   const SkColor fade_color = SkColorSetA(color, alpha);
133 
134   std::vector<SkScalar> positions;
135   std::vector<SkColor> colors;
136 
137   if (!left_part.IsEmpty())
138     AddFadeEffect(text_rect, left_part, fade_color, color,
139                   &positions, &colors);
140   if (!right_part.IsEmpty())
141     AddFadeEffect(text_rect, right_part, color, fade_color,
142                   &positions, &colors);
143   DCHECK(!positions.empty());
144 
145   // Terminate |positions| with 1.0, as required by Skia.
146   if (positions.back() != 1.0) {
147     positions.push_back(1.0);
148     colors.push_back(colors.back());
149   }
150 
151   const SkPoint points[2] = { PointToSkPoint(text_rect.origin()),
152                               PointToSkPoint(text_rect.top_right()) };
153   return cc::PaintShader::MakeLinearGradient(
154       &points[0], &colors[0], &positions[0], static_cast<int>(colors.size()),
155       SkTileMode::kClamp);
156 }
157 
158 // Converts a FontRenderParams::Hinting value to the corresponding
159 // SkFontHinting value.
FontRenderParamsHintingToSkFontHinting(FontRenderParams::Hinting params_hinting)160 SkFontHinting FontRenderParamsHintingToSkFontHinting(
161     FontRenderParams::Hinting params_hinting) {
162   switch (params_hinting) {
163     case FontRenderParams::HINTING_NONE:
164       return SkFontHinting::kNone;
165     case FontRenderParams::HINTING_SLIGHT:
166       return SkFontHinting::kSlight;
167     case FontRenderParams::HINTING_MEDIUM:
168       return SkFontHinting::kNormal;
169     case FontRenderParams::HINTING_FULL:
170       return SkFontHinting::kFull;
171   }
172   return SkFontHinting::kNone;
173 }
174 
175 // Make sure ranges don't break text graphemes.  If a range in |break_list|
176 // does break a grapheme in |render_text|, the range will be slightly
177 // extended to encompass the grapheme.
178 template <typename T>
RestoreBreakList(RenderText * render_text,BreakList<T> * break_list)179 void RestoreBreakList(RenderText* render_text, BreakList<T>* break_list) {
180   break_list->SetMax(render_text->text().length());
181   Range range;
182   while (range.end() < break_list->max()) {
183     const auto& current_break = break_list->GetBreak(range.end());
184     range = break_list->GetRange(current_break);
185     if (range.end() < break_list->max() &&
186         !render_text->IsValidCursorIndex(range.end())) {
187       range.set_end(
188           render_text->IndexOfAdjacentGrapheme(range.end(), CURSOR_FORWARD));
189       break_list->ApplyValue(current_break->second, range);
190     }
191   }
192 }
193 
194 // Move the iterator |iter| forward until |position| is included in the range.
195 template <typename T>
IncrementBreakListIteratorToPosition(const BreakList<T> & break_list,typename BreakList<T>::const_iterator iter,size_t position)196 typename BreakList<T>::const_iterator IncrementBreakListIteratorToPosition(
197     const BreakList<T>& break_list,
198     typename BreakList<T>::const_iterator iter,
199     size_t position) {
200   for (; iter != break_list.breaks().end(); ++iter) {
201     const gfx::Range range = break_list.GetRange(iter);
202     if (position >= range.start() && position < range.end())
203       break;
204   }
205   return iter;
206 }
207 
208 // Replaces the unicode control characters, control characters and PUA (Private
209 // Use Areas) codepoints.
ReplaceControlCharacter(UChar32 codepoint)210 UChar32 ReplaceControlCharacter(UChar32 codepoint) {
211   // 'REPLACEMENT CHARACTER' used to replace an unknown,
212   // unrecognized or unrepresentable character.
213   constexpr base::char16 kReplacementCodepoint = 0xFFFD;
214   // Control Pictures block (see:
215   // https://unicode.org/charts/PDF/U2400.pdf).
216   constexpr base::char16 kSymbolsCodepoint = 0x2400;
217 
218   if (codepoint >= 0 && codepoint <= 0x1F) {
219     // Replace codepoints with their visual symbols, which are
220     // at the same offset from kSymbolsCodepoint.
221     return kSymbolsCodepoint + codepoint;
222   }
223   if (codepoint == 0x7F) {
224     // Replace the 'del' codepoint by its symbol (u2421).
225     return kSymbolsCodepoint + 0x21;
226   }
227   if (!U_IS_UNICODE_CHAR(codepoint)) {
228     // Unicode codepoint that can't be assigned a character.
229     // This handles:
230     // - single surrogate codepoints,
231     // - last two codepoints on each plane,
232     // - invalid characters (e.g. u+fdd0..u+fdef)
233     // - codepoints above u+10ffff
234     return kReplacementCodepoint;
235   }
236   if (codepoint > 0x7F) {
237     // Private use codepoints are working with a pair of font
238     // and codepoint, but they are not used in Chrome.
239     const int8_t codepoint_category = u_charType(codepoint);
240     if (codepoint_category == U_PRIVATE_USE_CHAR ||
241         codepoint_category == U_CONTROL_CHAR) {
242       return kReplacementCodepoint;
243     }
244   }
245 
246   return codepoint;
247 }
248 
249 // Returns the line segment index for the |line|, |text_x| pair. |text_x| is
250 // relative to text in the given line. Returns -1 if |text_x| is to the left
251 // of text in the line and |line|.segments.size() if it's to the right.
252 // |offset_relative_segment| will contain the offset of |text_x| relative to
253 // the start of the segment it is contained in.
GetLineSegmentContainingXCoord(const internal::Line & line,float line_x,float * offset_relative_segment)254 int GetLineSegmentContainingXCoord(const internal::Line& line,
255                                    float line_x,
256                                    float* offset_relative_segment) {
257   DCHECK(offset_relative_segment);
258 
259   *offset_relative_segment = 0;
260   if (line_x < 0)
261     return -1;
262   for (size_t i = 0; i < line.segments.size(); i++) {
263     const internal::LineSegment& segment = line.segments[i];
264     // segment.x_range is not used because it is in text space.
265     if (line_x < segment.width()) {
266       *offset_relative_segment = line_x;
267       return i;
268     }
269     line_x -= segment.width();
270   }
271   return line.segments.size();
272 }
273 
274 }  // namespace
275 
276 namespace internal {
277 
SkiaTextRenderer(Canvas * canvas)278 SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
279     : canvas_(canvas), canvas_skia_(canvas->sk_canvas()) {
280   DCHECK(canvas_skia_);
281   flags_.setStyle(cc::PaintFlags::kFill_Style);
282 
283   font_.setEdging(SkFont::Edging::kSubpixelAntiAlias);
284   font_.setSubpixel(true);
285   font_.setHinting(SkFontHinting::kNormal);
286 }
287 
~SkiaTextRenderer()288 SkiaTextRenderer::~SkiaTextRenderer() {
289 }
290 
SetDrawLooper(sk_sp<SkDrawLooper> draw_looper)291 void SkiaTextRenderer::SetDrawLooper(sk_sp<SkDrawLooper> draw_looper) {
292   flags_.setLooper(std::move(draw_looper));
293 }
294 
SetFontRenderParams(const FontRenderParams & params,bool subpixel_rendering_suppressed)295 void SkiaTextRenderer::SetFontRenderParams(const FontRenderParams& params,
296                                            bool subpixel_rendering_suppressed) {
297   ApplyRenderParams(params, subpixel_rendering_suppressed, &font_);
298 }
299 
SetTypeface(sk_sp<SkTypeface> typeface)300 void SkiaTextRenderer::SetTypeface(sk_sp<SkTypeface> typeface) {
301   font_.setTypeface(std::move(typeface));
302 }
303 
SetTextSize(SkScalar size)304 void SkiaTextRenderer::SetTextSize(SkScalar size) {
305   font_.setSize(size);
306 }
307 
SetForegroundColor(SkColor foreground)308 void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
309   flags_.setColor(foreground);
310 }
311 
SetShader(sk_sp<cc::PaintShader> shader)312 void SkiaTextRenderer::SetShader(sk_sp<cc::PaintShader> shader) {
313   flags_.setShader(std::move(shader));
314 }
315 
DrawPosText(const SkPoint * pos,const uint16_t * glyphs,size_t glyph_count)316 void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
317                                    const uint16_t* glyphs,
318                                    size_t glyph_count) {
319   SkTextBlobBuilder builder;
320   const auto& run_buffer = builder.allocRunPos(font_, glyph_count);
321 
322   static_assert(sizeof(*glyphs) == sizeof(*run_buffer.glyphs), "");
323   memcpy(run_buffer.glyphs, glyphs, glyph_count * sizeof(*glyphs));
324 
325   static_assert(sizeof(*pos) == 2 * sizeof(*run_buffer.pos), "");
326   memcpy(run_buffer.pos, pos, glyph_count * sizeof(*pos));
327 
328   canvas_skia_->drawTextBlob(builder.make(), 0, 0, flags_);
329 }
330 
DrawUnderline(int x,int y,int width,SkScalar thickness_factor)331 void SkiaTextRenderer::DrawUnderline(int x,
332                                      int y,
333                                      int width,
334                                      SkScalar thickness_factor) {
335   SkScalar x_scalar = SkIntToScalar(x);
336   const SkScalar text_size = font_.getSize();
337   SkRect r = SkRect::MakeLTRB(
338       x_scalar, y + text_size * kUnderlineOffset, x_scalar + width,
339       y + (text_size *
340            (kUnderlineOffset + (thickness_factor * kLineThicknessFactor))));
341   canvas_skia_->drawRect(r, flags_);
342 }
343 
DrawStrike(int x,int y,int width,SkScalar thickness_factor)344 void SkiaTextRenderer::DrawStrike(int x,
345                                   int y,
346                                   int width,
347                                   SkScalar thickness_factor) {
348   const SkScalar text_size = font_.getSize();
349   const SkScalar height = text_size * thickness_factor;
350   const SkScalar top = y - text_size * kStrikeThroughOffset - height / 2;
351   SkScalar x_scalar = SkIntToScalar(x);
352   const SkRect r =
353       SkRect::MakeLTRB(x_scalar, top, x_scalar + width, top + height);
354   canvas_skia_->drawRect(r, flags_);
355 }
356 
StyleIterator(const BreakList<SkColor> * colors,const BreakList<BaselineStyle> * baselines,const BreakList<int> * font_size_overrides,const BreakList<Font::Weight> * weights,const std::vector<BreakList<bool>> * styles)357 StyleIterator::StyleIterator(const BreakList<SkColor>* colors,
358                              const BreakList<BaselineStyle>* baselines,
359                              const BreakList<int>* font_size_overrides,
360                              const BreakList<Font::Weight>* weights,
361                              const std::vector<BreakList<bool>>* styles)
362     : colors_(colors),
363       baselines_(baselines),
364       font_size_overrides_(font_size_overrides),
365       weights_(weights),
366       styles_(styles) {
367   color_ = colors_->breaks().begin();
368   baseline_ = baselines_->breaks().begin();
369   font_size_override_ = font_size_overrides_->breaks().begin();
370   weight_ = weights_->breaks().begin();
371   for (size_t i = 0; i < styles_->size(); ++i)
372     style_.push_back((*styles_)[i].breaks().begin());
373 }
374 
375 StyleIterator::StyleIterator(const StyleIterator& style) = default;
376 StyleIterator::~StyleIterator() = default;
377 StyleIterator& StyleIterator::operator=(const StyleIterator& style) = default;
378 
GetRange() const379 Range StyleIterator::GetRange() const {
380   return GetTextBreakingRange().Intersect(colors_->GetRange(color_));
381 }
382 
GetTextBreakingRange() const383 Range StyleIterator::GetTextBreakingRange() const {
384   Range range = baselines_->GetRange(baseline_);
385   range = range.Intersect(font_size_overrides_->GetRange(font_size_override_));
386   range = range.Intersect(weights_->GetRange(weight_));
387   for (size_t i = 0; i < TEXT_STYLE_COUNT; ++i)
388     range = range.Intersect((*styles_)[i].GetRange(style_[i]));
389   return range;
390 }
391 
IncrementToPosition(size_t position)392 void StyleIterator::IncrementToPosition(size_t position) {
393   color_ = IncrementBreakListIteratorToPosition(*colors_, color_, position);
394   baseline_ =
395       IncrementBreakListIteratorToPosition(*baselines_, baseline_, position);
396   font_size_override_ = IncrementBreakListIteratorToPosition(
397       *font_size_overrides_, font_size_override_, position);
398   weight_ = IncrementBreakListIteratorToPosition(*weights_, weight_, position);
399   for (size_t i = 0; i < TEXT_STYLE_COUNT; ++i) {
400     style_[i] = IncrementBreakListIteratorToPosition((*styles_)[i], style_[i],
401                                                      position);
402   }
403 }
404 
LineSegment()405 LineSegment::LineSegment() : run(0) {}
406 
~LineSegment()407 LineSegment::~LineSegment() {}
408 
Line()409 Line::Line() : preceding_heights(0), baseline(0) {}
410 
411 Line::Line(const Line& other) = default;
412 
~Line()413 Line::~Line() {}
414 
ShapedText(std::vector<Line> lines)415 ShapedText::ShapedText(std::vector<Line> lines) : lines_(std::move(lines)) {}
416 ShapedText::~ShapedText() = default;
417 
ApplyRenderParams(const FontRenderParams & params,bool subpixel_rendering_suppressed,SkFont * font)418 void ApplyRenderParams(const FontRenderParams& params,
419                        bool subpixel_rendering_suppressed,
420                        SkFont* font) {
421   if (!params.antialiasing) {
422     font->setEdging(SkFont::Edging::kAlias);
423   } else if (subpixel_rendering_suppressed ||
424              params.subpixel_rendering ==
425                  FontRenderParams::SUBPIXEL_RENDERING_NONE) {
426     font->setEdging(SkFont::Edging::kAntiAlias);
427   } else {
428     font->setEdging(SkFont::Edging::kSubpixelAntiAlias);
429   }
430 
431   font->setSubpixel(params.subpixel_positioning);
432   font->setForceAutoHinting(params.autohinter);
433   font->setHinting(FontRenderParamsHintingToSkFontHinting(params.hinting));
434 }
435 
436 }  // namespace internal
437 
438 // static
439 constexpr base::char16 RenderText::kPasswordReplacementChar;
440 constexpr bool RenderText::kDragToEndIfOutsideVerticalBounds;
441 
~RenderText()442 RenderText::~RenderText() {
443 }
444 
445 // static
CreateRenderText()446 std::unique_ptr<RenderText> RenderText::CreateRenderText() {
447   return std::make_unique<RenderTextHarfBuzz>();
448 }
449 
CreateInstanceOfSameStyle(const base::string16 & text) const450 std::unique_ptr<RenderText> RenderText::CreateInstanceOfSameStyle(
451     const base::string16& text) const {
452   std::unique_ptr<RenderText> render_text = CreateRenderText();
453   // |SetText()| must be called before styles are set.
454   render_text->SetText(text);
455   render_text->SetFontList(font_list_);
456   render_text->SetDirectionalityMode(directionality_mode_);
457   render_text->SetCursorEnabled(cursor_enabled_);
458   render_text->set_truncate_length(truncate_length_);
459   render_text->styles_ = styles_;
460   render_text->baselines_ = baselines_;
461   render_text->font_size_overrides_ = font_size_overrides_;
462   render_text->colors_ = colors_;
463   render_text->weights_ = weights_;
464   render_text->glyph_width_for_test_ = glyph_width_for_test_;
465   return render_text;
466 }
467 
SetText(const base::string16 & text)468 void RenderText::SetText(const base::string16& text) {
469   DCHECK(!composition_range_.IsValid());
470   if (text_ == text)
471     return;
472   text_ = text;
473   UpdateStyleLengths();
474 
475   // Clear style ranges as they might break new text graphemes and apply
476   // the first style to the whole text instead.
477   colors_.SetValue(colors_.breaks().begin()->second);
478   baselines_.SetValue(baselines_.breaks().begin()->second);
479   font_size_overrides_.SetValue(font_size_overrides_.breaks().begin()->second);
480   weights_.SetValue(weights_.breaks().begin()->second);
481   for (size_t style = 0; style < TEXT_STYLE_COUNT; ++style)
482     styles_[style].SetValue(styles_[style].breaks().begin()->second);
483   cached_bounds_and_offset_valid_ = false;
484 
485   // Reset selection model. SetText should always followed by SetSelectionModel
486   // or SetCursorPosition in upper layer.
487   SetSelectionModel(SelectionModel());
488 
489   // Invalidate the cached text direction if it depends on the text contents.
490   if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT)
491     text_direction_ = base::i18n::UNKNOWN_DIRECTION;
492 
493   obscured_reveal_index_ = -1;
494   OnTextAttributeChanged();
495 }
496 
AppendText(const base::string16 & text)497 void RenderText::AppendText(const base::string16& text) {
498   text_ += text;
499   UpdateStyleLengths();
500   cached_bounds_and_offset_valid_ = false;
501   obscured_reveal_index_ = -1;
502   OnTextAttributeChanged();
503 }
504 
SetHorizontalAlignment(HorizontalAlignment alignment)505 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
506   if (horizontal_alignment_ != alignment) {
507     horizontal_alignment_ = alignment;
508     display_offset_ = Vector2d();
509     cached_bounds_and_offset_valid_ = false;
510   }
511 }
512 
SetVerticalAlignment(VerticalAlignment alignment)513 void RenderText::SetVerticalAlignment(VerticalAlignment alignment) {
514   if (vertical_alignment_ != alignment) {
515     vertical_alignment_ = alignment;
516     display_offset_ = Vector2d();
517     cached_bounds_and_offset_valid_ = false;
518   }
519 }
520 
SetFontList(const FontList & font_list)521 void RenderText::SetFontList(const FontList& font_list) {
522   font_list_ = font_list;
523   const int font_style = font_list.GetFontStyle();
524   weights_.SetValue(font_list.GetFontWeight());
525   styles_[TEXT_STYLE_ITALIC].SetValue((font_style & Font::ITALIC) != 0);
526   styles_[TEXT_STYLE_UNDERLINE].SetValue((font_style & Font::UNDERLINE) != 0);
527   styles_[TEXT_STYLE_HEAVY_UNDERLINE].SetValue(false);
528   baseline_ = kInvalidBaseline;
529   cached_bounds_and_offset_valid_ = false;
530   OnLayoutTextAttributeChanged(false);
531 }
532 
SetCursorEnabled(bool cursor_enabled)533 void RenderText::SetCursorEnabled(bool cursor_enabled) {
534   cursor_enabled_ = cursor_enabled;
535   cached_bounds_and_offset_valid_ = false;
536 }
537 
SetObscured(bool obscured)538 void RenderText::SetObscured(bool obscured) {
539   if (obscured != obscured_) {
540     obscured_ = obscured;
541     obscured_reveal_index_ = -1;
542     cached_bounds_and_offset_valid_ = false;
543     OnTextAttributeChanged();
544   }
545 }
546 
SetObscuredRevealIndex(int index)547 void RenderText::SetObscuredRevealIndex(int index) {
548   if (obscured_reveal_index_ != index) {
549     obscured_reveal_index_ = index;
550     cached_bounds_and_offset_valid_ = false;
551     OnTextAttributeChanged();
552   }
553 }
554 
SetObscuredGlyphSpacing(int spacing)555 void RenderText::SetObscuredGlyphSpacing(int spacing) {
556   if (obscured_glyph_spacing_ != spacing) {
557     obscured_glyph_spacing_ = spacing;
558     OnLayoutTextAttributeChanged(true);
559   }
560 }
561 
SetMultiline(bool multiline)562 void RenderText::SetMultiline(bool multiline) {
563   if (multiline != multiline_) {
564     multiline_ = multiline;
565     cached_bounds_and_offset_valid_ = false;
566     OnTextAttributeChanged();
567   }
568 }
569 
SetMaxLines(size_t max_lines)570 void RenderText::SetMaxLines(size_t max_lines) {
571   max_lines_ = max_lines;
572   OnDisplayTextAttributeChanged();
573 }
574 
GetNumLines()575 size_t RenderText::GetNumLines() {
576   return GetShapedText()->lines().size();
577 }
578 
SetWordWrapBehavior(WordWrapBehavior behavior)579 void RenderText::SetWordWrapBehavior(WordWrapBehavior behavior) {
580   if (word_wrap_behavior_ != behavior) {
581     word_wrap_behavior_ = behavior;
582     if (multiline_) {
583       cached_bounds_and_offset_valid_ = false;
584       OnTextAttributeChanged();
585     }
586   }
587 }
588 
SetMinLineHeight(int line_height)589 void RenderText::SetMinLineHeight(int line_height) {
590   if (min_line_height_ != line_height) {
591     min_line_height_ = line_height;
592     cached_bounds_and_offset_valid_ = false;
593     OnDisplayTextAttributeChanged();
594   }
595 }
596 
SetElideBehavior(ElideBehavior elide_behavior)597 void RenderText::SetElideBehavior(ElideBehavior elide_behavior) {
598   // TODO(skanuj) : Add a test for triggering layout change.
599   if (elide_behavior_ != elide_behavior) {
600     elide_behavior_ = elide_behavior;
601     OnDisplayTextAttributeChanged();
602   }
603 }
604 
SetWhitespaceElision(base::Optional<bool> whitespace_elision)605 void RenderText::SetWhitespaceElision(base::Optional<bool> whitespace_elision) {
606   if (whitespace_elision_ != whitespace_elision) {
607     whitespace_elision_ = whitespace_elision;
608     OnDisplayTextAttributeChanged();
609   }
610 }
611 
SetDisplayRect(const Rect & r)612 void RenderText::SetDisplayRect(const Rect& r) {
613   if (r != display_rect_) {
614     display_rect_ = r;
615     baseline_ = kInvalidBaseline;
616     cached_bounds_and_offset_valid_ = false;
617     OnDisplayTextAttributeChanged();
618   }
619 }
620 
SetCursorPosition(size_t position)621 void RenderText::SetCursorPosition(size_t position) {
622   size_t cursor = std::min(position, text().length());
623   if (IsValidCursorIndex(cursor)) {
624     SetSelectionModel(SelectionModel(
625         cursor, (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD));
626   }
627 }
628 
MoveCursor(BreakType break_type,VisualCursorDirection direction,SelectionBehavior selection_behavior)629 void RenderText::MoveCursor(BreakType break_type,
630                             VisualCursorDirection direction,
631                             SelectionBehavior selection_behavior) {
632   SelectionModel cursor(cursor_position(), selection_model_.caret_affinity());
633 
634   // Ensure |cursor| is at the "end" of the current selection, since this
635   // determines which side should grow or shrink. If the prior change to the
636   // selection wasn't from cursor movement, the selection may be undirected. Or,
637   // the selection may be collapsing. In these cases, pick the "end" using
638   // |direction| (e.g. the arrow key) rather than the current selection range.
639   if ((!has_directed_selection_ || selection_behavior == SELECTION_NONE) &&
640       !selection().is_empty()) {
641     SelectionModel selection_start = GetSelectionModelForSelectionStart();
642     Point start = GetCursorBounds(selection_start, true).origin();
643     Point end = GetCursorBounds(cursor, true).origin();
644 
645     // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
646     // or right (when |direction| is CURSOR_RIGHT) of the selection end.
647     // Consider only the y-coordinates if the selection start and end are on
648     // different lines.
649     const bool cursor_is_leading =
650         (start.y() > end.y()) ||
651         ((start.y() == end.y()) && (start.x() > end.x()));
652     const bool cursor_should_be_trailing =
653         (direction == CURSOR_RIGHT) || (direction == CURSOR_DOWN);
654     if (cursor_is_leading == cursor_should_be_trailing) {
655       // In this case, a direction has been chosen that doesn't match
656       // |selection_model|, so the range must be reversed to place the cursor at
657       // the other end. Note the affinity won't matter: only the affinity of
658       // |start| (which points "in" to the selection) determines the movement.
659       Range range = selection_model_.selection();
660       selection_model_ = SelectionModel(Range(range.end(), range.start()),
661                                         selection_model_.caret_affinity());
662       cursor = selection_start;
663     }
664   }
665 
666   // Cancelling a selection moves to the edge of the selection.
667   if (break_type != FIELD_BREAK && break_type != LINE_BREAK &&
668       !selection().is_empty() && selection_behavior == SELECTION_NONE) {
669     // Use the nearest word boundary in the proper |direction| for word breaks.
670     if (break_type == WORD_BREAK)
671       cursor = GetAdjacentSelectionModel(cursor, break_type, direction);
672     // Use an adjacent selection model if the cursor is not at a valid position.
673     if (!IsValidCursorIndex(cursor.caret_pos()))
674       cursor = GetAdjacentSelectionModel(cursor, CHARACTER_BREAK, direction);
675   } else {
676     cursor = GetAdjacentSelectionModel(cursor, break_type, direction);
677   }
678 
679   // |cursor| corresponds to the tentative end point of the new selection. The
680   // selection direction is reversed iff the current selection is non-empty and
681   // the old selection end point and |cursor| are at the opposite ends of the
682   // old selection start point.
683   uint32_t min_end = std::min(selection().end(), cursor.selection().end());
684   uint32_t max_end = std::max(selection().end(), cursor.selection().end());
685   uint32_t current_start = selection().start();
686 
687   bool selection_reversed = !selection().is_empty() &&
688                             min_end <= current_start &&
689                             current_start <= max_end;
690 
691   // Take |selection_behavior| into account.
692   switch (selection_behavior) {
693     case SELECTION_RETAIN:
694       cursor.set_selection_start(current_start);
695       break;
696     case SELECTION_EXTEND:
697       cursor.set_selection_start(selection_reversed ? selection().end()
698                                                     : current_start);
699       break;
700     case SELECTION_CARET:
701       if (selection_reversed) {
702         cursor =
703             SelectionModel(current_start, selection_model_.caret_affinity());
704       } else {
705         cursor.set_selection_start(current_start);
706       }
707       break;
708     case SELECTION_NONE:
709       // Do nothing.
710       break;
711   }
712 
713   SetSelection(cursor);
714   has_directed_selection_ = true;
715 
716   // |cached_cursor_x| keeps the initial x-coordinates where CURSOR_UP or
717   // CURSOR_DOWN starts. This enables the cursor to keep the same x-coordinates
718   // even when the cursor passes through empty or short lines. The cached
719   // x-coordinates should be reset when the cursor moves in a horizontal
720   // direction.
721   if (direction != CURSOR_UP && direction != CURSOR_DOWN)
722     reset_cached_cursor_x();
723 }
724 
SetSelection(const SelectionModel & model)725 bool RenderText::SetSelection(const SelectionModel& model) {
726   // Enforce valid selection model components.
727   size_t text_length = text().length();
728   Range range(
729       std::min(model.selection().start(), static_cast<uint32_t>(text_length)),
730       std::min(model.caret_pos(), text_length));
731   // The current model only supports caret positions at valid cursor indices.
732   if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end()))
733     return false;
734   SelectionModel sel(range, model.caret_affinity());
735   bool changed = sel != selection_model_;
736   SetSelectionModel(sel);
737   return changed;
738 }
739 
MoveCursorToPoint(const gfx::Point & point,bool select,const gfx::Point & drag_origin)740 bool RenderText::MoveCursorToPoint(const gfx::Point& point,
741                                    bool select,
742                                    const gfx::Point& drag_origin) {
743   reset_cached_cursor_x();
744   gfx::SelectionModel model = FindCursorPosition(point, drag_origin);
745   if (select)
746     model.set_selection_start(selection().start());
747   return SetSelection(model);
748 }
749 
SelectRange(const Range & range)750 bool RenderText::SelectRange(const Range& range) {
751   uint32_t text_length = static_cast<uint32_t>(text().length());
752   Range sel(std::min(range.start(), text_length),
753             std::min(range.end(), text_length));
754   // Allow selection bounds at valid indices amid multi-character graphemes.
755   if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end()))
756     return false;
757   LogicalCursorDirection affinity =
758       (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD;
759   SetSelectionModel(SelectionModel(sel, affinity));
760   return true;
761 }
762 
IsPointInSelection(const Point & point)763 bool RenderText::IsPointInSelection(const Point& point) {
764   if (selection().is_empty())
765     return false;
766   SelectionModel cursor = FindCursorPosition(point);
767   return RangeContainsCaret(
768       selection(), cursor.caret_pos(), cursor.caret_affinity());
769 }
770 
ClearSelection()771 void RenderText::ClearSelection() {
772   SetSelectionModel(
773       SelectionModel(cursor_position(), selection_model_.caret_affinity()));
774 }
775 
SelectAll(bool reversed)776 void RenderText::SelectAll(bool reversed) {
777   const size_t length = text().length();
778   const Range all = reversed ? Range(length, 0) : Range(0, length);
779   const bool success = SelectRange(all);
780   DCHECK(success);
781 }
782 
SelectWord()783 void RenderText::SelectWord() {
784   SelectRange(ExpandRangeToWordBoundary(selection()));
785 }
786 
SetCompositionRange(const Range & composition_range)787 void RenderText::SetCompositionRange(const Range& composition_range) {
788   CHECK(!composition_range.IsValid() ||
789         Range(0, text_.length()).Contains(composition_range));
790   composition_range_.set_end(composition_range.end());
791   composition_range_.set_start(composition_range.start());
792   OnLayoutTextAttributeChanged(false);
793 }
794 
SetColor(SkColor value)795 void RenderText::SetColor(SkColor value) {
796   colors_.SetValue(value);
797   OnLayoutTextAttributeChanged(false);
798 }
799 
ApplyColor(SkColor value,const Range & range)800 void RenderText::ApplyColor(SkColor value, const Range& range) {
801   colors_.ApplyValue(value, range);
802   OnLayoutTextAttributeChanged(false);
803 }
804 
SetBaselineStyle(BaselineStyle value)805 void RenderText::SetBaselineStyle(BaselineStyle value) {
806   baselines_.SetValue(value);
807   OnLayoutTextAttributeChanged(false);
808 }
809 
ApplyBaselineStyle(BaselineStyle value,const Range & range)810 void RenderText::ApplyBaselineStyle(BaselineStyle value, const Range& range) {
811   baselines_.ApplyValue(value, range);
812   OnLayoutTextAttributeChanged(false);
813 }
814 
ApplyFontSizeOverride(int font_size_override,const Range & range)815 void RenderText::ApplyFontSizeOverride(int font_size_override,
816                                        const Range& range) {
817   font_size_overrides_.ApplyValue(font_size_override, range);
818   OnLayoutTextAttributeChanged(false);
819 }
820 
SetStyle(TextStyle style,bool value)821 void RenderText::SetStyle(TextStyle style, bool value) {
822   styles_[style].SetValue(value);
823 
824   cached_bounds_and_offset_valid_ = false;
825   // TODO(oshima|msw): Not all style change requires layout changes.
826   // Consider optimizing based on the type of change.
827   OnLayoutTextAttributeChanged(false);
828 }
829 
ApplyStyle(TextStyle style,bool value,const Range & range)830 void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) {
831   styles_[style].ApplyValue(value, range);
832 
833   cached_bounds_and_offset_valid_ = false;
834   // TODO(oshima|msw): Not all style change requires layout changes.
835   // Consider optimizing based on the type of change.
836   OnLayoutTextAttributeChanged(false);
837 }
838 
SetWeight(Font::Weight weight)839 void RenderText::SetWeight(Font::Weight weight) {
840   weights_.SetValue(weight);
841 
842   cached_bounds_and_offset_valid_ = false;
843   OnLayoutTextAttributeChanged(false);
844 }
845 
ApplyWeight(Font::Weight weight,const Range & range)846 void RenderText::ApplyWeight(Font::Weight weight, const Range& range) {
847   weights_.ApplyValue(weight, range);
848 
849   cached_bounds_and_offset_valid_ = false;
850   OnLayoutTextAttributeChanged(false);
851 }
852 
GetStyle(TextStyle style) const853 bool RenderText::GetStyle(TextStyle style) const {
854   return (styles_[style].breaks().size() == 1) &&
855          styles_[style].breaks().front().second;
856 }
857 
SetDirectionalityMode(DirectionalityMode mode)858 void RenderText::SetDirectionalityMode(DirectionalityMode mode) {
859   if (mode != directionality_mode_) {
860     directionality_mode_ = mode;
861     text_direction_ = base::i18n::UNKNOWN_DIRECTION;
862     cached_bounds_and_offset_valid_ = false;
863     OnLayoutTextAttributeChanged(false);
864   }
865 }
866 
GetDisplayTextDirection()867 base::i18n::TextDirection RenderText::GetDisplayTextDirection() {
868   return GetTextDirection(GetDisplayText());
869 }
870 
GetVisualDirectionOfLogicalEnd()871 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
872   return GetDisplayTextDirection() == base::i18n::LEFT_TO_RIGHT ? CURSOR_RIGHT
873                                                                 : CURSOR_LEFT;
874 }
875 
GetVisualDirectionOfLogicalBeginning()876 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalBeginning() {
877   return GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT ? CURSOR_RIGHT
878                                                                 : CURSOR_LEFT;
879 }
880 
GetStringSize()881 Size RenderText::GetStringSize() {
882   const SizeF size_f = GetStringSizeF();
883   return Size(std::ceil(size_f.width()), size_f.height());
884 }
885 
TotalLineWidth()886 float RenderText::TotalLineWidth() {
887   float total_width = 0;
888   const internal::ShapedText* shaped_text = GetShapedText();
889   for (const auto& line : shaped_text->lines())
890     total_width += line.size.width();
891   return total_width;
892 }
893 
GetContentWidthF()894 float RenderText::GetContentWidthF() {
895   const float string_size = GetStringSizeF().width();
896   // The cursor is drawn one pixel beyond the int-enclosed text bounds.
897   return cursor_enabled_ ? std::ceil(string_size) + 1 : string_size;
898 }
899 
GetContentWidth()900 int RenderText::GetContentWidth() {
901   return ToCeiledInt(GetContentWidthF());
902 }
903 
GetBaseline()904 int RenderText::GetBaseline() {
905   if (baseline_ == kInvalidBaseline) {
906     baseline_ =
907         DetermineBaselineCenteringText(display_rect().height(), font_list());
908   }
909   DCHECK_NE(kInvalidBaseline, baseline_);
910   return baseline_;
911 }
912 
Draw(Canvas * canvas,bool select_all)913 void RenderText::Draw(Canvas* canvas, bool select_all) {
914   EnsureLayout();
915 
916   if (clip_to_display_rect()) {
917     Rect clip_rect(display_rect());
918     clip_rect.Inset(ShadowValue::GetMargin(shadows_));
919 
920     canvas->Save();
921     canvas->ClipRect(clip_rect);
922   }
923 
924   if (!text().empty()) {
925     Range draw_selection;
926     if (select_all)
927       draw_selection = Range(0, text().length());
928     else if (focused())
929       draw_selection = selection();
930 
931     DrawSelection(canvas, draw_selection);
932     internal::SkiaTextRenderer renderer(canvas);
933     DrawVisualText(&renderer, draw_selection);
934   }
935 
936   if (clip_to_display_rect())
937     canvas->Restore();
938 }
939 
FindCursorPosition(const Point & view_point,const Point & drag_origin)940 SelectionModel RenderText::FindCursorPosition(const Point& view_point,
941                                               const Point& drag_origin) {
942   const internal::ShapedText* shaped_text = GetShapedText();
943   DCHECK(!shaped_text->lines().empty());
944 
945   int line_index = GetLineContainingYCoord((view_point - GetLineOffset(0)).y());
946   // Handle kDragToEndIfOutsideVerticalBounds above or below the text in a
947   // single-line by extending towards the mouse cursor.
948   if (RenderText::kDragToEndIfOutsideVerticalBounds && !multiline() &&
949       (line_index < 0 ||
950        line_index >= static_cast<int>(shaped_text->lines().size()))) {
951     SelectionModel selection_start = GetSelectionModelForSelectionStart();
952     int edge = drag_origin.x() == 0 ? GetCursorBounds(selection_start, true).x()
953                                     : drag_origin.x();
954     bool left = view_point.x() < edge;
955     return EdgeSelectionModel(left ? CURSOR_LEFT : CURSOR_RIGHT);
956   }
957   // Otherwise, clamp |line_index| to a valid value or drag to logical ends.
958   if (line_index < 0) {
959     if (RenderText::kDragToEndIfOutsideVerticalBounds)
960       return EdgeSelectionModel(GetVisualDirectionOfLogicalBeginning());
961     line_index = 0;
962   }
963   if (line_index >= static_cast<int>(shaped_text->lines().size())) {
964     if (RenderText::kDragToEndIfOutsideVerticalBounds)
965       return EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
966     line_index = shaped_text->lines().size() - 1;
967   }
968   const internal::Line& line = shaped_text->lines()[line_index];
969   // Newline segment should be ignored in finding segment index with x
970   // coordinate because it's not drawn.
971   Vector2d newline_offset;
972   if (line.segments.size() > 1 && IsNewlineSegment(line.segments.front()))
973     newline_offset.set_x(line.segments.front().width());
974 
975   float point_offset_relative_segment = 0;
976   const int segment_index = GetLineSegmentContainingXCoord(
977       line, (view_point - GetLineOffset(line_index) + newline_offset).x(),
978       &point_offset_relative_segment);
979   if (segment_index < 0)
980     return LineSelectionModel(line_index, CURSOR_LEFT);
981   if (segment_index >= static_cast<int>(line.segments.size()))
982     return LineSelectionModel(line_index, CURSOR_RIGHT);
983   const internal::LineSegment& segment = line.segments[segment_index];
984 
985   const internal::TextRunHarfBuzz& run = *GetRunList()->runs()[segment.run];
986   const size_t segment_min_glyph_index =
987       run.CharRangeToGlyphRange(segment.char_range).GetMin();
988   const float segment_offset_relative_run =
989       segment_min_glyph_index != 0
990           ? SkScalarToFloat(run.shape.positions[segment_min_glyph_index].x())
991           : 0;
992   const float point_offset_relative_run =
993       point_offset_relative_segment + segment_offset_relative_run;
994 
995   // TODO(crbug.com/676287): Use offset within the glyph to return the correct
996   // grapheme position within a multi-grapheme glyph.
997   for (size_t i = 0; i < run.shape.glyph_count; ++i) {
998     const float end = i + 1 == run.shape.glyph_count
999                           ? run.shape.width
1000                           : SkScalarToFloat(run.shape.positions[i + 1].x());
1001     const float middle =
1002         (end + SkScalarToFloat(run.shape.positions[i].x())) / 2;
1003     const size_t index = DisplayIndexToTextIndex(run.shape.glyph_to_char[i]);
1004     if (point_offset_relative_run < middle) {
1005       return run.font_params.is_rtl ? SelectionModel(IndexOfAdjacentGrapheme(
1006                                                          index, CURSOR_FORWARD),
1007                                                      CURSOR_BACKWARD)
1008                                     : SelectionModel(index, CURSOR_FORWARD);
1009     }
1010     if (point_offset_relative_run < end) {
1011       return run.font_params.is_rtl ? SelectionModel(index, CURSOR_FORWARD)
1012                                     : SelectionModel(IndexOfAdjacentGrapheme(
1013                                                          index, CURSOR_FORWARD),
1014                                                      CURSOR_BACKWARD);
1015     }
1016   }
1017 
1018   return LineSelectionModel(line_index, CURSOR_RIGHT);
1019 }
1020 
IsValidLogicalIndex(size_t index) const1021 bool RenderText::IsValidLogicalIndex(size_t index) const {
1022   // Check that the index is at a valid code point (not mid-surrogate-pair) and
1023   // that it's not truncated from the display text (its glyph may be shown).
1024   //
1025   // Indices within truncated text are disallowed so users can easily interact
1026   // with the underlying truncated text using the ellipsis as a proxy. This lets
1027   // users select all text, select the truncated text, and transition from the
1028   // last rendered glyph to the end of the text without getting invisible cursor
1029   // positions nor needing unbounded arrow key presses to traverse the ellipsis.
1030   return index == 0 || index == text().length() ||
1031       (index < text().length() &&
1032        (truncate_length_ == 0 || index < truncate_length_) &&
1033        IsValidCodePointIndex(text(), index));
1034 }
1035 
IsValidCursorIndex(size_t index) const1036 bool RenderText::IsValidCursorIndex(size_t index) const {
1037   return index == 0 || index == text().length() ||
1038          (IsValidLogicalIndex(index) && IsGraphemeBoundary(index));
1039 }
1040 
GetCursorBounds(const SelectionModel & caret,bool insert_mode)1041 Rect RenderText::GetCursorBounds(const SelectionModel& caret,
1042                                  bool insert_mode) {
1043   EnsureLayout();
1044   size_t caret_pos = caret.caret_pos();
1045   DCHECK(IsValidLogicalIndex(caret_pos));
1046   // In overtype mode, ignore the affinity and always indicate that we will
1047   // overtype the next character.
1048   LogicalCursorDirection caret_affinity =
1049       insert_mode ? caret.caret_affinity() : CURSOR_FORWARD;
1050   float x = 0;
1051   int width = 1;
1052 
1053   // Check whether the caret is attached to a boundary. Always return a 1-dip
1054   // width caret at the boundary. Avoid calling IndexOfAdjacentGrapheme(), since
1055   // it is slow and can impact browser startup here.
1056   // In insert mode, index 0 is always a boundary. The end, however, is not at a
1057   // boundary when the string ends in RTL text and there is LTR text around it.
1058   const bool at_boundary =
1059       (insert_mode && caret_pos == 0) ||
1060       caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length());
1061   if (at_boundary) {
1062     const bool rtl = GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT;
1063     if (rtl == (caret_pos == 0))
1064       x = TotalLineWidth();
1065   } else {
1066     // Find the next grapheme continuing in the current direction. This
1067     // determines the substring range that should be highlighted.
1068     size_t caret_end = IndexOfAdjacentGrapheme(caret_pos, caret_affinity);
1069     if (caret_end < caret_pos)
1070       std::swap(caret_end, caret_pos);
1071     const RangeF xspan = GetCursorSpan(Range(caret_pos, caret_end));
1072     if (insert_mode) {
1073       x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start();
1074     } else {  // overtype mode
1075       x = xspan.GetMin();
1076       // Ceil the start and end of the |xspan| because the cursor x-coordinates
1077       // are always ceiled.
1078       width =
1079           std::ceil(Clamp(xspan.GetMax())) - std::ceil(Clamp(xspan.GetMin()));
1080     }
1081   }
1082   return Rect(ToViewPoint(PointF(x, 0), caret_affinity),
1083               Size(width, GetLineSize(caret).height()));
1084 }
1085 
GetUpdatedCursorBounds()1086 const Rect& RenderText::GetUpdatedCursorBounds() {
1087   UpdateCachedBoundsAndOffset();
1088   return cursor_bounds_;
1089 }
1090 
GetGraphemeIteratorAtTextIndex(size_t index) const1091 internal::GraphemeIterator RenderText::GetGraphemeIteratorAtTextIndex(
1092     size_t index) const {
1093   EnsureLayoutTextUpdated();
1094   return GetGraphemeIteratorAtIndex(
1095       text_, &internal::TextToDisplayIndex::text_index, index);
1096 }
1097 
GetGraphemeIteratorAtDisplayTextIndex(size_t index) const1098 internal::GraphemeIterator RenderText::GetGraphemeIteratorAtDisplayTextIndex(
1099     size_t index) const {
1100   EnsureLayoutTextUpdated();
1101   return GetGraphemeIteratorAtIndex(
1102       layout_text_, &internal::TextToDisplayIndex::display_index, index);
1103 }
1104 
GetTextIndex(internal::GraphemeIterator iter) const1105 size_t RenderText::GetTextIndex(internal::GraphemeIterator iter) const {
1106   DCHECK(layout_text_up_to_date_);
1107   return iter == text_to_display_indices_.end() ? text_.length()
1108                                                 : iter->text_index;
1109 }
1110 
GetDisplayTextIndex(internal::GraphemeIterator iter) const1111 size_t RenderText::GetDisplayTextIndex(internal::GraphemeIterator iter) const {
1112   DCHECK(layout_text_up_to_date_);
1113   return iter == text_to_display_indices_.end() ? layout_text_.length()
1114                                                 : iter->display_index;
1115 }
1116 
IsGraphemeBoundary(size_t index) const1117 bool RenderText::IsGraphemeBoundary(size_t index) const {
1118   return index >= text_.length() ||
1119          GetTextIndex(GetGraphemeIteratorAtTextIndex(index)) == index;
1120 }
1121 
IndexOfAdjacentGrapheme(size_t index,LogicalCursorDirection direction) const1122 size_t RenderText::IndexOfAdjacentGrapheme(
1123     size_t index,
1124     LogicalCursorDirection direction) const {
1125   // The input is clamped if it is out of that range.
1126   if (text_.empty())
1127     return 0;
1128   if (index > text_.length())
1129     return text_.length();
1130 
1131   EnsureLayoutTextUpdated();
1132 
1133   internal::GraphemeIterator iter = index == text_.length()
1134                                         ? text_to_display_indices_.end()
1135                                         : GetGraphemeIteratorAtTextIndex(index);
1136   if (direction == CURSOR_FORWARD) {
1137     if (iter != text_to_display_indices_.end())
1138       ++iter;
1139   } else {
1140     DCHECK_EQ(direction, CURSOR_BACKWARD);
1141     // If the index was not at the beginning of the grapheme, it will have been
1142     // moved back to the grapheme start.
1143     if (iter != text_to_display_indices_.begin() && GetTextIndex(iter) == index)
1144       --iter;
1145   }
1146   return GetTextIndex(iter);
1147 }
1148 
GetSelectionModelForSelectionStart() const1149 SelectionModel RenderText::GetSelectionModelForSelectionStart() const {
1150   const Range& sel = selection();
1151   if (sel.is_empty())
1152     return selection_model_;
1153   return SelectionModel(sel.start(),
1154                         sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
1155 }
1156 
GetStringRect()1157 RectF RenderText::GetStringRect() {
1158   return RectF(PointF(ToViewPoint(PointF(), CURSOR_FORWARD)), GetStringSizeF());
1159 }
1160 
GetUpdatedDisplayOffset()1161 const Vector2d& RenderText::GetUpdatedDisplayOffset() {
1162   UpdateCachedBoundsAndOffset();
1163   return display_offset_;
1164 }
1165 
SetDisplayOffset(int horizontal_offset)1166 void RenderText::SetDisplayOffset(int horizontal_offset) {
1167   const int extra_content = GetContentWidth() - display_rect_.width();
1168   const int cursor_width = cursor_enabled_ ? 1 : 0;
1169 
1170   // avoid collisions with vm_map.h on FreeBSD --cmt
1171   int _min_offset = 0;
1172   int _max_offset = 0;
1173   if (extra_content > 0) {
1174     switch (GetCurrentHorizontalAlignment()) {
1175       case ALIGN_LEFT:
1176         _min_offset = -extra_content;
1177         break;
1178       case ALIGN_RIGHT:
1179         _max_offset = extra_content;
1180         break;
1181       case ALIGN_CENTER:
1182         // The extra space reserved for cursor at the end of the text is ignored
1183         // when centering text. So, to calculate the valid range for offset, we
1184         // exclude that extra space, calculate the range, and add it back to the
1185         // range (if cursor is enabled).
1186         _min_offset = -(extra_content - cursor_width + 1) / 2 - cursor_width;
1187         _max_offset = (extra_content - cursor_width) / 2;
1188         break;
1189       default:
1190         break;
1191     }
1192   }
1193   if (horizontal_offset < _min_offset)
1194     horizontal_offset = _min_offset;
1195   else if (horizontal_offset > _max_offset)
1196     horizontal_offset = _max_offset;
1197 
1198   cached_bounds_and_offset_valid_ = true;
1199   display_offset_.set_x(horizontal_offset);
1200   cursor_bounds_ = GetCursorBounds(selection_model_, true);
1201 }
1202 
GetLineOffset(size_t line_number)1203 Vector2d RenderText::GetLineOffset(size_t line_number) {
1204   const internal::ShapedText* shaped_text = GetShapedText();
1205   Vector2d offset = display_rect().OffsetFromOrigin();
1206   // TODO(ckocagil): Apply the display offset for multiline scrolling.
1207   if (!multiline()) {
1208     offset.Add(GetUpdatedDisplayOffset());
1209   } else {
1210     DCHECK_LT(line_number, shaped_text->lines().size());
1211     offset.Add(
1212         Vector2d(0, shaped_text->lines()[line_number].preceding_heights));
1213   }
1214   offset.Add(GetAlignmentOffset(line_number));
1215   return offset;
1216 }
1217 
GetWordLookupDataAtPoint(const Point & point,DecoratedText * decorated_word,Point * baseline_point)1218 bool RenderText::GetWordLookupDataAtPoint(const Point& point,
1219                                           DecoratedText* decorated_word,
1220                                           Point* baseline_point) {
1221   if (obscured())
1222     return false;
1223 
1224   EnsureLayout();
1225   const SelectionModel model_at_point = FindCursorPosition(point);
1226   const size_t word_index =
1227       GetNearestWordStartBoundary(model_at_point.caret_pos());
1228   if (word_index >= text().length())
1229     return false;
1230 
1231   const Range word_range = ExpandRangeToWordBoundary(Range(word_index));
1232   DCHECK(!word_range.is_reversed());
1233   DCHECK(!word_range.is_empty());
1234 
1235   return GetLookupDataForRange(word_range, decorated_word, baseline_point);
1236 }
1237 
GetLookupDataForRange(const Range & range,DecoratedText * decorated_text,Point * baseline_point)1238 bool RenderText::GetLookupDataForRange(const Range& range,
1239                                        DecoratedText* decorated_text,
1240                                        Point* baseline_point) {
1241   const internal::ShapedText* shaped_text = GetShapedText();
1242 
1243   const std::vector<Rect> word_bounds = GetSubstringBounds(range);
1244   if (word_bounds.empty() || !GetDecoratedTextForRange(range, decorated_text)) {
1245     return false;
1246   }
1247 
1248   // Retrieve the baseline origin of the left-most glyph.
1249   const auto left_rect = std::min_element(
1250       word_bounds.begin(), word_bounds.end(),
1251       [](const Rect& lhs, const Rect& rhs) { return lhs.x() < rhs.x(); });
1252   const int line_index = GetLineContainingYCoord(left_rect->CenterPoint().y() -
1253                                                  GetLineOffset(0).y());
1254   if (line_index < 0 ||
1255       line_index >= static_cast<int>(shaped_text->lines().size()))
1256     return false;
1257   *baseline_point = left_rect->origin() +
1258                     Vector2d(0, shaped_text->lines()[line_index].baseline);
1259   return true;
1260 }
1261 
GetTextFromRange(const Range & range) const1262 base::string16 RenderText::GetTextFromRange(const Range& range) const {
1263   if (range.IsValid() && range.GetMin() < text().length())
1264     return text().substr(range.GetMin(), range.length());
1265   return base::string16();
1266 }
1267 
ExpandRangeToGraphemeBoundary(const Range & range) const1268 Range RenderText::ExpandRangeToGraphemeBoundary(const Range& range) const {
1269   const auto snap_to_grapheme = [this](auto index, auto direction) {
1270     return IsValidCursorIndex(index)
1271                ? index
1272                : IndexOfAdjacentGrapheme(index, direction);
1273   };
1274 
1275   const size_t min_index = snap_to_grapheme(range.GetMin(), CURSOR_BACKWARD);
1276   const size_t max_index = snap_to_grapheme(range.GetMax(), CURSOR_FORWARD);
1277   return range.is_reversed() ? Range(max_index, min_index)
1278                              : Range(min_index, max_index);
1279 }
1280 
IsNewlineSegment(const internal::LineSegment & segment) const1281 bool RenderText::IsNewlineSegment(const internal::LineSegment& segment) const {
1282   return IsNewlineSegment(text_, segment);
1283 }
1284 
IsNewlineSegment(const base::string16 & text,const internal::LineSegment & segment) const1285 bool RenderText::IsNewlineSegment(const base::string16& text,
1286                                   const internal::LineSegment& segment) const {
1287   const size_t offset = segment.char_range.start();
1288   const size_t length = segment.char_range.length();
1289   DCHECK_LT(offset + length - 1, text.length());
1290   return (length == 1 && (text[offset] == '\r' || text[offset] == '\n')) ||
1291          (length == 2 && text[offset] == '\r' && text[offset + 1] == '\n');
1292 }
1293 
GetLineRange(const base::string16 & text,const internal::Line & line) const1294 Range RenderText::GetLineRange(const base::string16& text,
1295                                const internal::Line& line) const {
1296   // This will find the logical start and end indices of the given line.
1297   size_t max_index = 0;
1298   size_t min_index = text.length();
1299   for (const auto& segment : line.segments) {
1300     min_index = std::min<size_t>(min_index, segment.char_range.GetMin());
1301     max_index = std::max<size_t>(max_index, segment.char_range.GetMax());
1302   }
1303 
1304   // Do not include the newline character, as that could be considered leading
1305   // into the next line. Note that the newline character is always the last
1306   // character of the line regardless of the text direction, so decrease the
1307   // |max_index|.
1308   if (!line.segments.empty() &&
1309       (IsNewlineSegment(text, line.segments.back()) ||
1310        IsNewlineSegment(text, line.segments.front()))) {
1311     --max_index;
1312   }
1313 
1314   return Range(min_index, max_index);
1315 }
1316 
RenderText()1317 RenderText::RenderText()
1318     : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
1319       vertical_alignment_(ALIGN_MIDDLE),
1320       directionality_mode_(DIRECTIONALITY_FROM_TEXT),
1321       text_direction_(base::i18n::UNKNOWN_DIRECTION),
1322       cursor_enabled_(true),
1323       has_directed_selection_(kSelectionIsAlwaysDirected),
1324       selection_color_(kDefaultColor),
1325       selection_background_focused_color_(kDefaultSelectionBackgroundColor),
1326       focused_(false),
1327       composition_range_(Range::InvalidRange()),
1328       colors_(kDefaultColor),
1329       baselines_(NORMAL_BASELINE),
1330       font_size_overrides_(0),
1331       weights_(Font::Weight::NORMAL),
1332       styles_(TEXT_STYLE_COUNT),
1333       layout_styles_(TEXT_STYLE_COUNT),
1334       obscured_(false),
1335       obscured_reveal_index_(-1),
1336       truncate_length_(0),
1337       elide_behavior_(NO_ELIDE),
1338       text_elided_(false),
1339       min_line_height_(0),
1340       multiline_(false),
1341       max_lines_(0),
1342       word_wrap_behavior_(IGNORE_LONG_WORDS),
1343       subpixel_rendering_suppressed_(false),
1344       clip_to_display_rect_(true),
1345       baseline_(kInvalidBaseline),
1346       cached_bounds_and_offset_valid_(false),
1347       strike_thickness_factor_(kLineThicknessFactor) {}
1348 
GetTextStyleIterator() const1349 internal::StyleIterator RenderText::GetTextStyleIterator() const {
1350   return internal::StyleIterator(&colors_, &baselines_, &font_size_overrides_,
1351                                  &weights_, &styles_);
1352 }
1353 
GetLayoutTextStyleIterator() const1354 internal::StyleIterator RenderText::GetLayoutTextStyleIterator() const {
1355   EnsureLayoutTextUpdated();
1356   return internal::StyleIterator(&layout_colors_, &layout_baselines_,
1357                                  &layout_font_size_overrides_, &layout_weights_,
1358                                  &layout_styles_);
1359 }
1360 
IsHomogeneous() const1361 bool RenderText::IsHomogeneous() const {
1362   if (colors().breaks().size() > 1 || baselines().breaks().size() > 1 ||
1363       font_size_overrides().breaks().size() > 1 ||
1364       weights().breaks().size() > 1)
1365     return false;
1366   for (size_t style = 0; style < TEXT_STYLE_COUNT; ++style) {
1367     if (styles()[style].breaks().size() > 1)
1368       return false;
1369   }
1370   return true;
1371 }
1372 
GetShapedText()1373 internal::ShapedText* RenderText::GetShapedText() {
1374   EnsureLayout();
1375   DCHECK(shaped_text_);
1376   return shaped_text_.get();
1377 }
1378 
GetDisplayTextBaseline()1379 int RenderText::GetDisplayTextBaseline() {
1380   DCHECK(!GetShapedText()->lines().empty());
1381   return GetShapedText()->lines()[0].baseline;
1382 }
1383 
GetAdjacentSelectionModel(const SelectionModel & current,BreakType break_type,VisualCursorDirection direction)1384 SelectionModel RenderText::GetAdjacentSelectionModel(
1385     const SelectionModel& current,
1386     BreakType break_type,
1387     VisualCursorDirection direction) {
1388   EnsureLayout();
1389 
1390   if (direction == CURSOR_UP || direction == CURSOR_DOWN)
1391     return AdjacentLineSelectionModel(current, direction);
1392   if (break_type == FIELD_BREAK || text().empty())
1393     return EdgeSelectionModel(direction);
1394   if (break_type == LINE_BREAK)
1395     return LineSelectionModel(GetLineContainingCaret(current), direction);
1396   if (break_type == CHARACTER_BREAK)
1397     return AdjacentCharSelectionModel(current, direction);
1398   DCHECK(break_type == WORD_BREAK);
1399   return AdjacentWordSelectionModel(current, direction);
1400 }
1401 
EdgeSelectionModel(VisualCursorDirection direction)1402 SelectionModel RenderText::EdgeSelectionModel(
1403     VisualCursorDirection direction) {
1404   if (direction == GetVisualDirectionOfLogicalEnd())
1405     return SelectionModel(text().length(), CURSOR_FORWARD);
1406   return SelectionModel(0, CURSOR_BACKWARD);
1407 }
1408 
LineSelectionModel(size_t line_index,VisualCursorDirection direction)1409 SelectionModel RenderText::LineSelectionModel(size_t line_index,
1410                                               VisualCursorDirection direction) {
1411   DCHECK(direction == CURSOR_LEFT || direction == CURSOR_RIGHT);
1412   const internal::Line& line = GetShapedText()->lines()[line_index];
1413   if (line.segments.empty()) {
1414     // Only the last line can be empty.
1415     DCHECK_EQ(GetShapedText()->lines().size() - 1, line_index);
1416     return EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
1417   }
1418   if (line_index ==
1419       (direction == GetVisualDirectionOfLogicalEnd() ? GetNumLines() - 1 : 0)) {
1420     return EdgeSelectionModel(direction);
1421   }
1422 
1423   DCHECK_GT(GetNumLines(), 1U);
1424   Range line_range = GetLineRange(text(), line);
1425 
1426   // Cursor affinity should be the opposite of visual direction to preserve the
1427   // line number of the cursor in multiline text.
1428   return direction == GetVisualDirectionOfLogicalEnd()
1429              ? SelectionModel(DisplayIndexToTextIndex(line_range.end()),
1430                               CURSOR_BACKWARD)
1431              : SelectionModel(DisplayIndexToTextIndex(line_range.start()),
1432                               CURSOR_FORWARD);
1433 }
1434 
SetSelectionModel(const SelectionModel & model)1435 void RenderText::SetSelectionModel(const SelectionModel& model) {
1436   DCHECK_LE(model.selection().GetMax(), text().length());
1437   selection_model_ = model;
1438   cached_bounds_and_offset_valid_ = false;
1439   has_directed_selection_ = kSelectionIsAlwaysDirected;
1440 }
1441 
TextIndexToDisplayIndex(size_t index) const1442 size_t RenderText::TextIndexToDisplayIndex(size_t index) const {
1443   return GetDisplayTextIndex(GetGraphemeIteratorAtTextIndex(index));
1444 }
1445 
DisplayIndexToTextIndex(size_t index) const1446 size_t RenderText::DisplayIndexToTextIndex(size_t index) const {
1447   return GetTextIndex(GetGraphemeIteratorAtDisplayTextIndex(index));
1448 }
1449 
OnLayoutTextAttributeChanged(bool text_changed)1450 void RenderText::OnLayoutTextAttributeChanged(bool text_changed) {
1451   layout_text_up_to_date_ = false;
1452 }
1453 
EnsureLayoutTextUpdated() const1454 void RenderText::EnsureLayoutTextUpdated() const {
1455   if (layout_text_up_to_date_)
1456     return;
1457 
1458   layout_text_.clear();
1459   text_to_display_indices_.clear();
1460 
1461   // Reset the previous layout text attributes. Allocate enough space for
1462   // layout text attributes (upper limit to 2x characters per codepoint). The
1463   // actual size will be updated at the end of the function.
1464   UpdateLayoutStyleLengths(2 * text_.length());
1465 
1466   // Create an grapheme iterator to ensure layout BreakLists don't break
1467   // graphemes.
1468   base::i18n::BreakIterator grapheme_iter(
1469       text_, base::i18n::BreakIterator::BREAK_CHARACTER);
1470   bool success = grapheme_iter.Init();
1471   DCHECK(success);
1472 
1473   // Ensures the reveal index is at a codepoint boundary (e.g. not in a middle
1474   // of a surrogate pairs).
1475   size_t reveal_index = text_.size();
1476   if (obscured_reveal_index_ != -1) {
1477     reveal_index = base::checked_cast<size_t>(obscured_reveal_index_);
1478     // Move |reveal_index| to the beginning of the surrogate pair, if needed.
1479     if (reveal_index < text_.size())
1480       U16_SET_CP_START(text_.data(), 0, reveal_index);
1481   }
1482 
1483   // Iterates through graphemes from |text_| and rewrite its codepoints to
1484   // |layout_text_|.
1485   base::i18n::UTF16CharIterator text_iter(&text_);
1486   internal::StyleIterator styles = GetTextStyleIterator();
1487   bool text_truncated = false;
1488   while (!text_iter.end() && !text_truncated) {
1489     std::vector<uint32_t> grapheme_codepoints;
1490     const size_t text_grapheme_start_position = text_iter.array_pos();
1491     const size_t layout_grapheme_start_position = layout_text_.size();
1492 
1493     // Retrieve codepoints of the current grapheme.
1494     do {
1495       grapheme_codepoints.push_back(text_iter.get());
1496       text_iter.Advance();
1497     } while (!grapheme_iter.IsGraphemeBoundary(text_iter.array_pos()) &&
1498              !text_iter.end());
1499     const size_t text_grapheme_end_position = text_iter.array_pos();
1500 
1501     // Keep track of the mapping between |text_| and |layout_text_| indices.
1502     internal::TextToDisplayIndex mapping = {text_grapheme_start_position,
1503                                             layout_grapheme_start_position};
1504     text_to_display_indices_.push_back(mapping);
1505 
1506     // Flag telling if the current grapheme is a newline control sequence.
1507     const bool is_newline_grapheme =
1508         (grapheme_codepoints.size() == 1 &&
1509          (grapheme_codepoints[0] == '\r' || grapheme_codepoints[0] == '\n')) ||
1510         (grapheme_codepoints.size() == 2 && grapheme_codepoints[0] == '\r' &&
1511          grapheme_codepoints[1] == '\n');
1512 
1513     // Obscure the layout text by replacing the grapheme by a bullet.
1514     if (obscured_ &&
1515         (reveal_index < text_grapheme_start_position ||
1516          reveal_index >= text_grapheme_end_position) &&
1517         (!is_newline_grapheme || !multiline_)) {
1518       grapheme_codepoints.clear();
1519       grapheme_codepoints.push_back(RenderText::kPasswordReplacementChar);
1520     }
1521 
1522     // Rewrite each codepoint of the grapheme.
1523     for (uint32_t codepoint : grapheme_codepoints) {
1524       // Handle unicode control characters ISO 6429 (block C0). Range from 0 to
1525       // 0x1F and 0x7F. The newline character should be kept as-is when
1526       // rendertext is multiline.
1527       if (!multiline_ || !is_newline_grapheme)
1528         codepoint = ReplaceControlCharacter(codepoint);
1529 
1530       // Truncate the remaining codepoints if appending the codepoint to
1531       // |layout_text_| is making the text larger than |truncate_length_|.
1532       size_t codepoint_length = U16_LENGTH(codepoint);
1533       text_truncated =
1534           (truncate_length_ != 0 &&
1535            ((layout_text_.size() + codepoint_length > truncate_length_) ||
1536             (!text_iter.end() &&
1537              (layout_text_.size() + codepoint_length == truncate_length_))));
1538 
1539       if (text_truncated) {
1540         codepoint = kEllipsisCodepoint;
1541         codepoint_length = U16_LENGTH(codepoint);
1542         // On truncate, remove the whole current grapheme.
1543         layout_text_.resize(layout_grapheme_start_position);
1544       }
1545 
1546       // Append the codepoint to the layout text.
1547       const size_t current_layout_text_position = layout_text_.size();
1548       if (codepoint_length == 1) {
1549         layout_text_ += codepoint;
1550       } else {
1551         layout_text_ += U16_LEAD(codepoint);
1552         layout_text_ += U16_TRAIL(codepoint);
1553       }
1554 
1555       // Apply the style at current grapheme position to the layout text.
1556       styles.IncrementToPosition(text_grapheme_start_position);
1557 
1558       Range range(current_layout_text_position, layout_text_.size());
1559       layout_colors_.ApplyValue(styles.color(), range);
1560       layout_baselines_.ApplyValue(styles.baseline(), range);
1561       layout_font_size_overrides_.ApplyValue(styles.font_size_override(),
1562                                              range);
1563       layout_weights_.ApplyValue(styles.weight(), range);
1564       for (size_t i = 0; i < TEXT_STYLE_COUNT; ++i) {
1565         layout_styles_[i].ApplyValue(styles.style(static_cast<TextStyle>(i)),
1566                                      range);
1567       }
1568 
1569       // Apply an underline to the composition range in |underlines|.
1570       const Range grapheme_start_range(gfx::Range(
1571           text_grapheme_start_position, text_grapheme_start_position + 1));
1572       if (composition_range_.Contains(grapheme_start_range))
1573         layout_styles_[TEXT_STYLE_HEAVY_UNDERLINE].ApplyValue(true, range);
1574 
1575       // Stop appending characters if the text is truncated.
1576       if (text_truncated)
1577         break;
1578     }
1579   }
1580 
1581   // Resize the layout text attributes to the actual layout text length.
1582   UpdateLayoutStyleLengths(layout_text_.length());
1583 
1584   // Ensures that the text got truncated correctly, when needed.
1585   DCHECK(truncate_length_ == 0 || layout_text_.size() <= truncate_length_);
1586 
1587   // Wait to reset |layout_text_up_to_date_| until the end, to ensure this
1588   // function's implementation doesn't indirectly rely on it being up to date
1589   // anywhere.
1590   layout_text_up_to_date_ = true;
1591 }
1592 
GetLayoutText() const1593 const base::string16& RenderText::GetLayoutText() const {
1594   EnsureLayoutTextUpdated();
1595   return layout_text_;
1596 }
1597 
UpdateDisplayText(float text_width)1598 void RenderText::UpdateDisplayText(float text_width) {
1599   EnsureLayoutTextUpdated();
1600 
1601   // TODO(krb): Consider other elision modes for multiline.
1602   if ((multiline_ && (!max_lines_ || elide_behavior() != ELIDE_TAIL)) ||
1603       elide_behavior() == NO_ELIDE || elide_behavior() == FADE_TAIL ||
1604       (text_width > 0 && text_width < display_rect_.width()) ||
1605       layout_text_.empty()) {
1606     text_elided_ = false;
1607     display_text_.clear();
1608     return;
1609   }
1610 
1611   if (!multiline_) {
1612     // This doesn't trim styles so ellipsis may get rendered as a different
1613     // style than the preceding text. See crbug.com/327850.
1614     display_text_.assign(Elide(layout_text_, text_width,
1615                                static_cast<float>(display_rect_.width()),
1616                                elide_behavior_));
1617   } else {
1618     bool was_elided = text_elided_;
1619     text_elided_ = false;
1620     display_text_.clear();
1621 
1622     std::unique_ptr<RenderText> render_text(
1623         CreateInstanceOfSameStyle(layout_text_));
1624     render_text->SetMultiline(true);
1625     render_text->SetWordWrapBehavior(word_wrap_behavior_);
1626     render_text->SetDisplayRect(display_rect_);
1627     // Have it arrange words on |lines_|.
1628     render_text->EnsureLayout();
1629 
1630     if (render_text->GetShapedText()->lines().size() > max_lines_) {
1631       // Find the start and end index of the line to be elided.
1632       Range line_range = GetLineRange(
1633           layout_text_, render_text->GetShapedText()->lines()[max_lines_ - 1]);
1634       // Add an ellipsis character in case the last line is short enough to fit
1635       // on a single line. Otherwise that character will be elided anyway.
1636       base::string16 text_to_elide =
1637           layout_text_.substr(line_range.start(), line_range.length()) +
1638           base::string16(kEllipsisUTF16);
1639       display_text_.assign(layout_text_.substr(0, line_range.start()) +
1640                            Elide(text_to_elide, 0,
1641                                  static_cast<float>(display_rect_.width()),
1642                                  ELIDE_TAIL));
1643       // Have GetLineBreaks() re-calculate.
1644       line_breaks_.SetMax(0);
1645     } else {
1646       // If elision changed, re-calculate.
1647       if (was_elided)
1648         line_breaks_.SetMax(0);
1649       // Initial state above is fine.
1650       return;
1651     }
1652   }
1653   text_elided_ = display_text_ != layout_text_;
1654   if (!text_elided_)
1655     display_text_.clear();
1656 }
1657 
GetLineBreaks()1658 const BreakList<size_t>& RenderText::GetLineBreaks() {
1659   if (line_breaks_.max() != 0)
1660     return line_breaks_;
1661 
1662   const base::string16& layout_text = GetDisplayText();
1663   const size_t text_length = layout_text.length();
1664   line_breaks_.SetValue(0);
1665   line_breaks_.SetMax(text_length);
1666   base::i18n::BreakIterator iter(layout_text,
1667                                  base::i18n::BreakIterator::BREAK_LINE);
1668   const bool success = iter.Init();
1669   DCHECK(success);
1670   if (success) {
1671     do {
1672       line_breaks_.ApplyValue(iter.pos(), Range(iter.pos(), text_length));
1673     } while (iter.Advance());
1674   }
1675   return line_breaks_;
1676 }
1677 
ToViewPoint(const PointF & point,LogicalCursorDirection caret_affinity)1678 Point RenderText::ToViewPoint(const PointF& point,
1679                               LogicalCursorDirection caret_affinity) {
1680   const auto float_eq = [](float a, float b) {
1681     return std::fabs(a - b) <= kFloatComparisonEpsilon;
1682   };
1683   const auto float_ge = [](float a, float b) {
1684     return a > b || std::fabs(a - b) <= kFloatComparisonEpsilon;
1685   };
1686   const auto float_gt = [](float a, float b) {
1687     return a - b > kFloatComparisonEpsilon;
1688   };
1689 
1690   const size_t num_lines = GetNumLines();
1691   if (num_lines == 1) {
1692     return Point(std::ceil(Clamp(point.x())), std::round(point.y())) +
1693            GetLineOffset(0);
1694   }
1695 
1696   const internal::ShapedText* shaped_text = GetShapedText();
1697   float x = point.x();
1698   size_t line;
1699 
1700   if (GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT) {
1701     // |xspan| returned from |GetCursorSpan| in |GetCursorBounds| starts to grow
1702     // from the last character in RTL. On the other hand, the last character is
1703     // positioned in the last line in RTL. So, traverse from the last line.
1704     for (line = num_lines - 1;
1705          line > 0 && float_ge(x, shaped_text->lines()[line].size.width());
1706          --line) {
1707       x -= shaped_text->lines()[line].size.width();
1708     }
1709 
1710     // Increment the |line| when |x| is at the newline character. The line is
1711     // broken by word wrapping if the front edge of the line is not a newline
1712     // character. In that case, the same caret position where the line is broken
1713     // can be on both lines depending on the caret affinity.
1714     if (line < num_lines - 1 &&
1715         (IsNewlineSegment(shaped_text->lines()[line].segments.front()) ||
1716          caret_affinity == CURSOR_FORWARD)) {
1717       if (float_eq(x, 0))
1718         x = shaped_text->lines()[++line].size.width();
1719 
1720       // In RTL, the newline character is at the front of the line. Because the
1721       // newline character is not drawn at the front of the line, |x| should be
1722       // decreased by the width of the newline character. Check for a newline
1723       // again because the line may have changed.
1724       if (!shaped_text->lines()[line].segments.empty() &&
1725           IsNewlineSegment(shaped_text->lines()[line].segments.front())) {
1726         x -= shaped_text->lines()[line].segments.front().width();
1727       }
1728     }
1729   } else {
1730     for (line = 0; line < num_lines &&
1731                    float_gt(x, shaped_text->lines()[line].size.width());
1732          ++line) {
1733       x -= shaped_text->lines()[line].size.width();
1734     }
1735 
1736     if (line == num_lines) {
1737       x = shaped_text->lines()[--line].size.width();
1738     } else if (line < num_lines - 1 &&
1739                float_eq(shaped_text->lines()[line].size.width(), x) &&
1740                (IsNewlineSegment(shaped_text->lines()[line].segments.back()) ||
1741                 caret_affinity == CURSOR_FORWARD)) {
1742       // If |x| is at the edge of the line end, move the cursor to the start of
1743       // the next line.
1744       ++line;
1745       x = 0;
1746     }
1747   }
1748 
1749   return Point(std::ceil(Clamp(x)), std::round(point.y())) +
1750          GetLineOffset(line);
1751 }
1752 
GetCurrentHorizontalAlignment()1753 HorizontalAlignment RenderText::GetCurrentHorizontalAlignment() {
1754   if (horizontal_alignment_ != ALIGN_TO_HEAD)
1755     return horizontal_alignment_;
1756   return GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT ?
1757       ALIGN_RIGHT : ALIGN_LEFT;
1758 }
1759 
GetAlignmentOffset(size_t line_number)1760 Vector2d RenderText::GetAlignmentOffset(size_t line_number) {
1761   DCHECK(!multiline_ || (line_number < GetShapedText()->lines().size()));
1762 
1763   Vector2d offset;
1764   HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment();
1765   if (horizontal_alignment != ALIGN_LEFT) {
1766     const int width =
1767         multiline_
1768             ? std::ceil(GetShapedText()->lines()[line_number].size.width()) +
1769                   (cursor_enabled_ ? 1 : 0)
1770             : GetContentWidth();
1771     offset.set_x(display_rect().width() - width);
1772     // Put any extra margin pixel on the left to match legacy behavior.
1773     if (horizontal_alignment == ALIGN_CENTER)
1774       offset.set_x((offset.x() + 1) / 2);
1775   }
1776 
1777   switch (vertical_alignment_) {
1778     case ALIGN_TOP:
1779       offset.set_y(0);
1780       break;
1781     case ALIGN_MIDDLE:
1782       if (multiline_)
1783         offset.set_y((display_rect_.height() - GetStringSize().height()) / 2);
1784       else
1785         offset.set_y(GetBaseline() - GetDisplayTextBaseline());
1786       break;
1787     case ALIGN_BOTTOM:
1788       offset.set_y(display_rect_.height() - GetStringSize().height());
1789       break;
1790   }
1791 
1792   return offset;
1793 }
1794 
ApplyFadeEffects(internal::SkiaTextRenderer * renderer)1795 void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
1796   const int width = display_rect().width();
1797   if (multiline() || elide_behavior_ != FADE_TAIL || GetContentWidth() <= width)
1798     return;
1799 
1800   const int gradient_width = CalculateFadeGradientWidth(font_list(), width);
1801   if (gradient_width == 0)
1802     return;
1803 
1804   HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment();
1805   Rect solid_part = display_rect();
1806   Rect left_part;
1807   Rect right_part;
1808   if (horizontal_alignment != ALIGN_LEFT) {
1809     left_part = solid_part;
1810     left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
1811     solid_part.Inset(gradient_width, 0, 0, 0);
1812   }
1813   if (horizontal_alignment != ALIGN_RIGHT) {
1814     right_part = solid_part;
1815     right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
1816     solid_part.Inset(0, 0, gradient_width, 0);
1817   }
1818 
1819   // CreateFadeShader() expects at least one part to not be empty.
1820   // See https://crbug.com/706835.
1821   if (left_part.IsEmpty() && right_part.IsEmpty())
1822     return;
1823 
1824   Rect text_rect = display_rect();
1825   text_rect.Inset(GetAlignmentOffset(0).x(), 0, 0, 0);
1826 
1827   // TODO(msw): Use the actual text colors corresponding to each faded part.
1828   renderer->SetShader(
1829       CreateFadeShader(font_list(), text_rect, left_part, right_part,
1830                        SkColorSetA(colors_.breaks().front().second, 0xff)));
1831 }
1832 
ApplyTextShadows(internal::SkiaTextRenderer * renderer)1833 void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) {
1834   renderer->SetDrawLooper(CreateShadowDrawLooper(shadows_));
1835 }
1836 
GetTextDirection(const base::string16 & text)1837 base::i18n::TextDirection RenderText::GetTextDirection(
1838     const base::string16& text) {
1839   if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) {
1840     switch (directionality_mode_) {
1841       case DIRECTIONALITY_FROM_TEXT:
1842         // Derive the direction from the display text, which differs from text()
1843         // in the case of obscured (password) textfields.
1844         text_direction_ =
1845             base::i18n::GetFirstStrongCharacterDirection(text);
1846         break;
1847       case DIRECTIONALITY_FROM_UI:
1848         text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT :
1849                                                 base::i18n::LEFT_TO_RIGHT;
1850         break;
1851       case DIRECTIONALITY_FORCE_LTR:
1852         text_direction_ = base::i18n::LEFT_TO_RIGHT;
1853         break;
1854       case DIRECTIONALITY_FORCE_RTL:
1855         text_direction_ = base::i18n::RIGHT_TO_LEFT;
1856         break;
1857       case DIRECTIONALITY_AS_URL:
1858         // Rendering as a URL implies left-to-right paragraph direction.
1859         // URL Standard specifies that a URL "should be rendered as if it were
1860         // in a left-to-right embedding".
1861         // https://url.spec.whatwg.org/#url-rendering
1862         //
1863         // Consider logical string for domain "ABC.com/hello" (where ABC are
1864         // Hebrew (RTL) characters). The normal Bidi algorithm renders this as
1865         // "com/hello.CBA"; by forcing LTR, it is rendered as "CBA.com/hello".
1866         //
1867         // Note that this only applies a LTR embedding at the top level; it
1868         // doesn't change the Bidi algorithm, so there are still some URLs that
1869         // will render in a confusing order. Consider the logical string
1870         // "abc.COM/HELLO/world", which will render as "abc.OLLEH/MOC/world".
1871         // See https://crbug.com/351639.
1872         //
1873         // Note that the LeftToRightUrls feature flag enables additional
1874         // behaviour for DIRECTIONALITY_AS_URL, but the left-to-right embedding
1875         // behaviour is always enabled, regardless of the flag.
1876         text_direction_ = base::i18n::LEFT_TO_RIGHT;
1877         break;
1878       default:
1879         NOTREACHED();
1880     }
1881   }
1882 
1883   return text_direction_;
1884 }
1885 
UpdateStyleLengths()1886 void RenderText::UpdateStyleLengths() {
1887   const size_t text_length = text_.length();
1888   colors_.SetMax(text_length);
1889   baselines_.SetMax(text_length);
1890   font_size_overrides_.SetMax(text_length);
1891   weights_.SetMax(text_length);
1892   for (auto& style : styles_)
1893     style.SetMax(text_length);
1894 }
1895 
UpdateLayoutStyleLengths(size_t max_length) const1896 void RenderText::UpdateLayoutStyleLengths(size_t max_length) const {
1897   layout_colors_.SetMax(max_length);
1898   layout_baselines_.SetMax(max_length);
1899   layout_font_size_overrides_.SetMax(max_length);
1900   layout_weights_.SetMax(max_length);
1901   for (auto& layout_style : layout_styles_)
1902     layout_style.SetMax(max_length);
1903 }
1904 
GetLineContainingYCoord(float text_y)1905 int RenderText::GetLineContainingYCoord(float text_y) {
1906   if (text_y < 0)
1907     return -1;
1908 
1909   internal::ShapedText* shaper_text = GetShapedText();
1910   for (size_t i = 0; i < shaper_text->lines().size(); i++) {
1911     const internal::Line& line = shaper_text->lines()[i];
1912 
1913     if (text_y <= line.size.height())
1914       return i;
1915     text_y -= line.size.height();
1916   }
1917 
1918   return shaper_text->lines().size();
1919 }
1920 
1921 // static
RangeContainsCaret(const Range & range,size_t caret_pos,LogicalCursorDirection caret_affinity)1922 bool RenderText::RangeContainsCaret(const Range& range,
1923                                     size_t caret_pos,
1924                                     LogicalCursorDirection caret_affinity) {
1925   // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9).
1926   size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ?
1927       caret_pos - 1 : caret_pos + 1;
1928   return range.Contains(Range(caret_pos, adjacent));
1929 }
1930 
1931 // static
DetermineBaselineCenteringText(const int display_height,const FontList & font_list)1932 int RenderText::DetermineBaselineCenteringText(const int display_height,
1933                                                const FontList& font_list) {
1934   const int font_height = font_list.GetHeight();
1935   // Lower and upper bound of baseline shift as we try to show as much area of
1936   // text as possible.  In particular case of |display_height| == |font_height|,
1937   // we do not want to shift the baseline.
1938   const int min_shift = std::min(0, display_height - font_height);
1939   const int max_shift = std::abs(display_height - font_height);
1940   const int baseline = font_list.GetBaseline();
1941   const int cap_height = font_list.GetCapHeight();
1942   const int internal_leading = baseline - cap_height;
1943   // Some platforms don't support getting the cap height, and simply return
1944   // the entire font ascent from GetCapHeight().  Centering the ascent makes
1945   // the font look too low, so if GetCapHeight() returns the ascent, center
1946   // the entire font height instead.
1947   const int space =
1948       display_height - ((internal_leading != 0) ? cap_height : font_height);
1949   const int baseline_shift = space / 2 - internal_leading;
1950   return baseline + base::ClampToRange(baseline_shift, min_shift, max_shift);
1951 }
1952 
1953 // static
ExpandToBeVerticallySymmetric(const gfx::Rect & rect,const gfx::Rect & display_rect)1954 gfx::Rect RenderText::ExpandToBeVerticallySymmetric(
1955     const gfx::Rect& rect,
1956     const gfx::Rect& display_rect) {
1957   // Mirror |rect| across the horizontal line dividing |display_rect| in half.
1958   gfx::Rect result = rect;
1959   int mid_y = display_rect.CenterPoint().y();
1960   // The top of the mirror rect must be equidistant with the bottom of the
1961   // original rect from the mid-line.
1962   result.set_y(mid_y + (mid_y - rect.bottom()));
1963 
1964   // Now make a union with the original rect to ensure we are encompassing both.
1965   result.Union(rect);
1966   return result;
1967 }
1968 
OnTextAttributeChanged()1969 void RenderText::OnTextAttributeChanged() {
1970   layout_text_.clear();
1971   display_text_.clear();
1972   text_elided_ = false;
1973   line_breaks_.SetMax(0);
1974 
1975   layout_text_up_to_date_ = false;
1976 
1977   OnLayoutTextAttributeChanged(true);
1978 }
1979 
Elide(const base::string16 & text,float text_width,float available_width,ElideBehavior behavior)1980 base::string16 RenderText::Elide(const base::string16& text,
1981                                  float text_width,
1982                                  float available_width,
1983                                  ElideBehavior behavior) {
1984   if (available_width <= 0 || text.empty())
1985     return base::string16();
1986   if (behavior == ELIDE_EMAIL)
1987     return ElideEmail(text, available_width);
1988   if (text_width > 0 && text_width <= available_width)
1989     return text;
1990 
1991   TRACE_EVENT0("ui", "RenderText::Elide");
1992 
1993   // Create a RenderText copy with attributes that affect the rendering width.
1994   std::unique_ptr<RenderText> render_text = CreateInstanceOfSameStyle(text);
1995   render_text->UpdateStyleLengths();
1996   if (text_width == 0)
1997     text_width = render_text->GetContentWidthF();
1998   if (text_width <= available_width)
1999     return text;
2000 
2001   const base::string16 ellipsis = base::string16(kEllipsisUTF16);
2002   const bool insert_ellipsis = (behavior != TRUNCATE);
2003   const bool elide_in_middle = (behavior == ELIDE_MIDDLE);
2004   const bool elide_at_beginning = (behavior == ELIDE_HEAD);
2005 
2006   if (insert_ellipsis) {
2007     render_text->SetText(ellipsis);
2008     const float ellipsis_width = render_text->GetContentWidthF();
2009     if (ellipsis_width > available_width)
2010       return base::string16();
2011   }
2012 
2013   StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning,
2014                       whitespace_elision_);
2015 
2016   // Use binary(-like) search to compute the elided text.  In particular, do
2017   // an interpolation search, which is a binary search in which each guess
2018   // is an attempt to smartly calculate the right point rather than blindly
2019   // guessing midway between the endpoints.
2020   size_t lo = 0;
2021   size_t hi = text.length() - 1;
2022   size_t guess = std::string::npos;
2023   // These two widths are not exactly right but they're good enough to provide
2024   // some guidance to the search.  For example, |text_width| is actually the
2025   // length of text.length(), not text.length()-1.
2026   float lo_width = 0;
2027   float hi_width = text_width;
2028   const base::i18n::TextDirection text_direction = GetTextDirection(text);
2029   while (lo <= hi) {
2030     // Linearly interpolate between |lo| and |hi|, which correspond to widths
2031     // of |lo_width| and |hi_width| to estimate at what position
2032     // |available_width| would be at.  Because |lo_width| and |hi_width| are
2033     // both estimates (may be off by a little because, for example, |lo_width|
2034     // may have been calculated from |lo| minus one, not |lo|), we clamp to the
2035     // the valid range.
2036     // |last_guess| is merely used to verify that we're not repeating guesses.
2037     const size_t last_guess = guess;
2038     if (hi_width != lo_width) {
2039       guess = lo + static_cast<size_t>(
2040                        ToRoundedInt((available_width - lo_width) * (hi - lo) /
2041                                     (hi_width - lo_width)));
2042     }
2043     guess = base::ClampToRange(guess, lo, hi);
2044     DCHECK_NE(last_guess, guess);
2045 
2046     // Restore colors. They will be truncated to size by SetText.
2047     render_text->colors_ = colors_;
2048     base::string16 new_text =
2049         slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL);
2050 
2051     // This has to be an additional step so that the ellipsis is rendered with
2052     // same style as trailing part of the text.
2053     if (insert_ellipsis && behavior == ELIDE_TAIL) {
2054       // When ellipsis follows text whose directionality is not the same as that
2055       // of the whole text, it will be rendered with the directionality of the
2056       // whole text. Since we want ellipsis to indicate continuation of the
2057       // preceding text, we force the directionality of ellipsis to be same as
2058       // the preceding text using LTR or RTL markers.
2059       base::i18n::TextDirection trailing_text_direction =
2060           base::i18n::GetLastStrongCharacterDirection(new_text);
2061 
2062       // Ensures that the |new_text| will always be smaller or equal to the
2063       // original text. There is a corner case when only one character is elided
2064       // and two characters are added back (ellipsis and directional marker).
2065       if (trailing_text_direction != text_direction &&
2066           new_text.length() + 2 > text.length() && guess >= 1) {
2067         new_text = slicer.CutString(guess - 1, false);
2068       }
2069 
2070       // Append the ellipsis and the optional directional marker characters.
2071       new_text.append(ellipsis);
2072       if (trailing_text_direction != text_direction) {
2073         if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
2074           new_text += base::i18n::kLeftToRightMark;
2075         else
2076           new_text += base::i18n::kRightToLeftMark;
2077       }
2078     }
2079 
2080     // The elided text must be smaller in bytes. Otherwise, break-lists are not
2081     // consistent and the characters after the last range are not styled.
2082     DCHECK_LE(new_text.size(), text.size());
2083     render_text->SetText(new_text);
2084 
2085     // Restore styles and baselines without breaking multi-character graphemes.
2086     render_text->styles_ = styles_;
2087     for (size_t style = 0; style < TEXT_STYLE_COUNT; ++style)
2088       RestoreBreakList(render_text.get(), &render_text->styles_[style]);
2089     RestoreBreakList(render_text.get(), &render_text->baselines_);
2090     RestoreBreakList(render_text.get(), &render_text->font_size_overrides_);
2091     render_text->weights_ = weights_;
2092     RestoreBreakList(render_text.get(), &render_text->weights_);
2093 
2094     // We check the width of the whole desired string at once to ensure we
2095     // handle kerning/ligatures/etc. correctly.
2096     const float guess_width = render_text->GetContentWidthF();
2097     if (guess_width == available_width)
2098       break;
2099     if (guess_width > available_width) {
2100       hi = guess - 1;
2101       hi_width = guess_width;
2102       // Move back on the loop terminating condition when the guess is too wide.
2103       if (hi < lo) {
2104         lo = hi;
2105         lo_width = guess_width;
2106       }
2107     } else {
2108       lo = guess + 1;
2109       lo_width = guess_width;
2110     }
2111   }
2112 
2113   return render_text->text();
2114 }
2115 
ElideEmail(const base::string16 & email,float available_width)2116 base::string16 RenderText::ElideEmail(const base::string16& email,
2117                                       float available_width) {
2118   // The returned string will have at least one character besides the ellipsis
2119   // on either side of '@'; if that's impossible, a single ellipsis is returned.
2120   // If possible, only the username is elided. Otherwise, the domain is elided
2121   // in the middle, splitting available width equally with the elided username.
2122   // If the username is short enough that it doesn't need half the available
2123   // width, the elided domain will occupy that extra width.
2124 
2125   // Split the email into its local-part (username) and domain-part. The email
2126   // spec allows for @ symbols in the username under some special requirements,
2127   // but not in the domain part, so splitting at the last @ symbol is safe.
2128   const size_t split_index = email.find_last_of('@');
2129   DCHECK_NE(split_index, base::string16::npos);
2130   base::string16 username = email.substr(0, split_index);
2131   base::string16 domain = email.substr(split_index + 1);
2132   DCHECK(!username.empty());
2133   DCHECK(!domain.empty());
2134 
2135   // Subtract the @ symbol from the available width as it is mandatory.
2136   const base::string16 kAtSignUTF16 = base::ASCIIToUTF16("@");
2137   available_width -= GetStringWidthF(kAtSignUTF16, font_list());
2138 
2139   // Check whether eliding the domain is necessary: if eliding the username
2140   // is sufficient, the domain will not be elided.
2141   const float full_username_width = GetStringWidthF(username, font_list());
2142   const float available_domain_width = available_width -
2143       std::min(full_username_width,
2144           GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16, font_list()));
2145   if (GetStringWidthF(domain, font_list()) > available_domain_width) {
2146     // Elide the domain so that it only takes half of the available width.
2147     // Should the username not need all the width available in its half, the
2148     // domain will occupy the leftover width.
2149     // If |desired_domain_width| is greater than |available_domain_width|: the
2150     // minimal username elision allowed by the specifications will not fit; thus
2151     // |desired_domain_width| must be <= |available_domain_width| at all cost.
2152     const float desired_domain_width =
2153         std::min<float>(available_domain_width,
2154             std::max<float>(available_width - full_username_width,
2155                             available_width / 2));
2156     domain = Elide(domain, 0, desired_domain_width, ELIDE_MIDDLE);
2157     // Failing to elide the domain such that at least one character remains
2158     // (other than the ellipsis itself) remains: return a single ellipsis.
2159     if (domain.length() <= 1U)
2160       return base::string16(kEllipsisUTF16);
2161   }
2162 
2163   // Fit the username in the remaining width (at this point the elided username
2164   // is guaranteed to fit with at least one character remaining given all the
2165   // precautions taken earlier).
2166   available_width -= GetStringWidthF(domain, font_list());
2167   username = Elide(username, 0, available_width, ELIDE_TAIL);
2168   return username + kAtSignUTF16 + domain;
2169 }
2170 
UpdateCachedBoundsAndOffset()2171 void RenderText::UpdateCachedBoundsAndOffset() {
2172   if (cached_bounds_and_offset_valid_)
2173     return;
2174 
2175   // TODO(ckocagil): Add support for scrolling multiline text.
2176 
2177   int delta_x = 0;
2178 
2179   if (cursor_enabled()) {
2180     // When cursor is enabled, ensure it is visible. For this, set the valid
2181     // flag true and calculate the current cursor bounds using the stale
2182     // |display_offset_|. Then calculate the change in offset needed to move the
2183     // cursor into the visible area.
2184     cached_bounds_and_offset_valid_ = true;
2185     cursor_bounds_ = GetCursorBounds(selection_model_, true);
2186 
2187     // TODO(bidi): Show RTL glyphs at the cursor position for ALIGN_LEFT, etc.
2188     if (cursor_bounds_.right() > display_rect_.right())
2189       delta_x = display_rect_.right() - cursor_bounds_.right();
2190     else if (cursor_bounds_.x() < display_rect_.x())
2191       delta_x = display_rect_.x() - cursor_bounds_.x();
2192   }
2193 
2194   SetDisplayOffset(display_offset_.x() + delta_x);
2195 }
2196 
GetGraphemeIteratorAtIndex(const base::string16 & text,const size_t internal::TextToDisplayIndex::* field,size_t index) const2197 internal::GraphemeIterator RenderText::GetGraphemeIteratorAtIndex(
2198     const base::string16& text,
2199     const size_t internal::TextToDisplayIndex::*field,
2200     size_t index) const {
2201   DCHECK_LE(index, text.length());
2202   if (index == text.length())
2203     return text_to_display_indices_.end();
2204 
2205   DCHECK(layout_text_up_to_date_);
2206   DCHECK(!text_to_display_indices_.empty());
2207 
2208   // The function std::lower_bound(...) finds the first not less than |index|.
2209   internal::GraphemeIterator iter = std::lower_bound(
2210       text_to_display_indices_.begin(), text_to_display_indices_.end(), index,
2211       [field](const internal::TextToDisplayIndex& lhs, size_t rhs) {
2212         return lhs.*field < rhs;
2213       });
2214 
2215   if (iter == text_to_display_indices_.end() || *iter.*field != index) {
2216     DCHECK(iter != text_to_display_indices_.begin());
2217     --iter;
2218   }
2219 
2220   return iter;
2221 }
2222 
DrawSelection(Canvas * canvas,const Range & selection)2223 void RenderText::DrawSelection(Canvas* canvas, const Range& selection) {
2224   if (!selection.is_empty()) {
2225     for (Rect s : GetSubstringBounds(selection)) {
2226       if (symmetric_selection_visual_bounds() && !multiline())
2227         s = ExpandToBeVerticallySymmetric(s, display_rect());
2228       canvas->FillRect(s, selection_background_focused_color_);
2229     }
2230   }
2231 }
2232 
GetNearestWordStartBoundary(size_t index) const2233 size_t RenderText::GetNearestWordStartBoundary(size_t index) const {
2234   const size_t length = text().length();
2235   if (obscured() || length == 0)
2236     return length;
2237 
2238   base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
2239   const bool success = iter.Init();
2240   DCHECK(success);
2241   if (!success)
2242     return length;
2243 
2244   // First search for the word start boundary in the CURSOR_BACKWARD direction,
2245   // then in the CURSOR_FORWARD direction.
2246   for (int i = static_cast<int>(std::min(index, length - 1)); i >= 0; i--)
2247     if (iter.IsStartOfWord(i))
2248       return i;
2249 
2250   for (size_t i = index + 1; i < length; i++)
2251     if (iter.IsStartOfWord(i))
2252       return i;
2253 
2254   return length;
2255 }
2256 
ExpandRangeToWordBoundary(const Range & range) const2257 Range RenderText::ExpandRangeToWordBoundary(const Range& range) const {
2258   const size_t length = text().length();
2259   DCHECK_LE(range.GetMax(), length);
2260   if (obscured())
2261     return range.is_reversed() ? Range(length, 0) : Range(0, length);
2262 
2263   base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
2264   const bool success = iter.Init();
2265   DCHECK(success);
2266   if (!success)
2267     return range;
2268 
2269   size_t range_min = range.GetMin();
2270   if (range_min == length && range_min != 0)
2271     --range_min;
2272 
2273   for (; range_min != 0; --range_min)
2274     if (iter.IsStartOfWord(range_min) || iter.IsEndOfWord(range_min))
2275       break;
2276 
2277   size_t range_max = range.GetMax();
2278   if (range_min == range_max && range_max != length)
2279     ++range_max;
2280 
2281   for (; range_max < length; ++range_max)
2282     if (iter.IsEndOfWord(range_max) || iter.IsStartOfWord(range_max))
2283       break;
2284 
2285   return range.is_reversed() ? Range(range_max, range_min)
2286                              : Range(range_min, range_max);
2287 }
2288 
2289 }  // namespace gfx
2290