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