blob: b9397062d6ce91035cd763c1c8d2561fbdbcedf5 [file] [log] [blame]
[email protected]8d901a82012-01-04 19:41:301// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]ff44d712011-07-25 08:42:522// 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
avi51ba3e692015-12-26 17:30:507#include <limits.h>
8
[email protected]ff44d712011-07-25 08:42:529#include <algorithm>
[email protected]56e50572013-11-01 14:25:3110#include <climits>
[email protected]ff44d712011-07-25 08:42:5211
[email protected]52848022014-05-22 19:01:0712#include "base/command_line.h"
[email protected]ff44d712011-07-25 08:42:5213#include "base/i18n/break_iterator.h"
14#include "base/logging.h"
15#include "base/stl_util.h"
[email protected]102402c2014-07-09 19:53:3816#include "base/strings/string_util.h"
[email protected]ec2ce922014-01-02 23:06:4717#include "base/strings/utf_string_conversions.h"
oshima0a243a42015-02-14 04:46:3118#include "base/trace_event/trace_event.h"
avic89eb8d42015-12-23 08:08:1819#include "build/build_config.h"
[email protected]8bbf6192013-07-18 11:14:0420#include "third_party/icu/source/common/unicode/rbbi.h"
21#include "third_party/icu/source/common/unicode/utf16.h"
senorblancocf6533a52015-08-14 20:09:1922#include "third_party/skia/include/core/SkDrawLooper.h"
mboc998e8902016-06-02 11:40:3523#include "third_party/skia/include/core/SkFontStyle.h"
[email protected]67b981562011-12-09 00:35:0524#include "third_party/skia/include/core/SkTypeface.h"
[email protected]f308e5b12012-01-17 19:09:1825#include "third_party/skia/include/effects/SkGradientShader.h"
[email protected]ff44d712011-07-25 08:42:5226#include "ui/gfx/canvas.h"
tfarina655f81d2014-12-23 02:38:5027#include "ui/gfx/geometry/insets.h"
pkastingbc91db52014-10-21 20:35:4228#include "ui/gfx/geometry/safe_integer_conversions.h"
erikchen98ef6152015-12-09 00:27:2229#include "ui/gfx/platform_font.h"
[email protected]52848022014-05-22 19:01:0730#include "ui/gfx/render_text_harfbuzz.h"
31#include "ui/gfx/scoped_canvas.h"
[email protected]40be2012012-05-14 22:34:4532#include "ui/gfx/skia_util.h"
[email protected]52848022014-05-22 19:01:0733#include "ui/gfx/switches.h"
[email protected]dbb97ba2013-09-09 22:15:2534#include "ui/gfx/text_elider.h"
[email protected]ec2ce922014-01-02 23:06:4735#include "ui/gfx/text_utils.h"
[email protected]bde885b2013-09-12 20:55:5336#include "ui/gfx/utf16_indexing.h"
[email protected]ff44d712011-07-25 08:42:5237
derata55b0115d2015-02-03 01:59:0838#if defined(OS_MACOSX)
erikchen98ef6152015-12-09 00:27:2239#include "third_party/skia/include/ports/SkTypeface_mac.h"
derata55b0115d2015-02-03 01:59:0840#include "ui/gfx/render_text_mac.h"
41#endif // defined(OS_MACOSX)
42
[email protected]ccfa43f02013-02-01 04:42:1743namespace gfx {
44
[email protected]ff44d712011-07-25 08:42:5245namespace {
46
[email protected]bec929c2012-03-02 06:23:5047// All chars are replaced by this char when the password style is set.
48// TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*'
49// that's available in the font (find_invisible_char() in gtkentry.c).
[email protected]476dafb2013-12-03 00:39:2650const base::char16 kPasswordReplacementChar = '*';
[email protected]bec929c2012-03-02 06:23:5051
[email protected]ccfa43f02013-02-01 04:42:1752// Default color used for the text and cursor.
53const SkColor kDefaultColor = SK_ColorBLACK;
[email protected]311a09d2012-05-14 20:38:1754
55// Default color used for drawing selection background.
56const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY;
57
[email protected]ccfa43f02013-02-01 04:42:1758// Fraction of the text size to lower a strike through below the baseline.
59const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
60// Fraction of the text size to lower an underline below the baseline.
61const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
62// Fraction of the text size to use for a strike through or under-line.
63const SkScalar kLineThickness = (SK_Scalar1 / 18);
64// Fraction of the text size to use for a top margin of a diagonal strike.
65const SkScalar kDiagonalStrikeMarginOffset = (SK_Scalar1 / 4);
[email protected]ff44d712011-07-25 08:42:5266
[email protected]56e50572013-11-01 14:25:3167// Invalid value of baseline. Assigning this value to |baseline_| causes
68// re-calculation of baseline.
69const int kInvalidBaseline = INT_MAX;
70
71// Returns the baseline, with which the text best appears vertically centered.
72int DetermineBaselineCenteringText(const Rect& display_rect,
73 const FontList& font_list) {
74 const int display_height = display_rect.height();
75 const int font_height = font_list.GetHeight();
76 // Lower and upper bound of baseline shift as we try to show as much area of
77 // text as possible. In particular case of |display_height| == |font_height|,
78 // we do not want to shift the baseline.
79 const int min_shift = std::min(0, display_height - font_height);
80 const int max_shift = std::abs(display_height - font_height);
81 const int baseline = font_list.GetBaseline();
82 const int cap_height = font_list.GetCapHeight();
83 const int internal_leading = baseline - cap_height;
[email protected]a61c6b23c2013-11-09 19:22:0784 // Some platforms don't support getting the cap height, and simply return
85 // the entire font ascent from GetCapHeight(). Centering the ascent makes
86 // the font look too low, so if GetCapHeight() returns the ascent, center
87 // the entire font height instead.
88 const int space =
89 display_height - ((internal_leading != 0) ? cap_height : font_height);
pkasting477c6472015-02-20 03:08:4890 const int baseline_shift = space / 2 - internal_leading;
[email protected]56e50572013-11-01 14:25:3191 return baseline + std::max(min_shift, std::min(max_shift, baseline_shift));
92}
93
tmoniuszkod6fa6e42015-12-04 12:06:2194int round(float value) {
95 return static_cast<int>(floor(value + 0.5f));
96}
97
[email protected]f308e5b12012-01-17 19:09:1898// Given |font| and |display_width|, returns the width of the fade gradient.
[email protected]6300df12013-12-29 07:54:2199int CalculateFadeGradientWidth(const FontList& font_list, int display_width) {
tmoniuszkod6fa6e42015-12-04 12:06:21100 // Fade in/out about 3 characters of the beginning/end of the string.
101 // Use a 1/3 of the display width if the display width is very short.
102 const int narrow_width = font_list.GetExpectedTextWidth(3);
103 const int gradient_width = std::min(narrow_width, round(display_width / 3.f));
104 DCHECK_GE(gradient_width, 0);
105 return gradient_width;
[email protected]f308e5b12012-01-17 19:09:18106}
107
108// Appends to |positions| and |colors| values corresponding to the fade over
109// |fade_rect| from color |c0| to color |c1|.
[email protected]ccfa43f02013-02-01 04:42:17110void AddFadeEffect(const Rect& text_rect,
111 const Rect& fade_rect,
[email protected]f308e5b12012-01-17 19:09:18112 SkColor c0,
113 SkColor c1,
114 std::vector<SkScalar>* positions,
115 std::vector<SkColor>* colors) {
116 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x());
117 const SkScalar width = static_cast<SkScalar>(fade_rect.width());
118 const SkScalar p0 = left / text_rect.width();
119 const SkScalar p1 = (left + width) / text_rect.width();
120 // Prepend 0.0 to |positions|, as required by Skia.
121 if (positions->empty() && p0 != 0.0) {
122 positions->push_back(0.0);
123 colors->push_back(c0);
124 }
125 positions->push_back(p0);
126 colors->push_back(c0);
127 positions->push_back(p1);
128 colors->push_back(c1);
129}
130
131// Creates a SkShader to fade the text, with |left_part| specifying the left
132// fade effect, if any, and |right_part| specifying the right fade effect.
tomhudson2b45cf92016-03-31 14:10:15133sk_sp<SkShader> CreateFadeShader(const FontList& font_list,
134 const Rect& text_rect,
135 const Rect& left_part,
136 const Rect& right_part,
137 SkColor color) {
mbocf700cdb92016-03-30 19:34:33138 // The shader should only specify transparency of the fade itself, not the
139 // original transparency, which will be applied by the actual renderer.
140 DCHECK_EQ(SkColorGetA(color), static_cast<uint8_t>(0xff));
141
tmoniuszkod6fa6e42015-12-04 12:06:21142 // In general, fade down to 0 alpha. But when the available width is less
143 // than four characters, linearly ramp up the fade target alpha to as high as
144 // 20% at zero width. This allows the user to see the last faded characters a
145 // little better when there are only a few characters shown.
146 const float width_fraction =
147 text_rect.width() / static_cast<float>(font_list.GetExpectedTextWidth(4));
148 const SkAlpha kAlphaAtZeroWidth = 51;
149 const SkAlpha alpha = (width_fraction < 1) ?
150 static_cast<SkAlpha>(round((1 - width_fraction) * kAlphaAtZeroWidth)) : 0;
151 const SkColor fade_color = SkColorSetA(color, alpha);
152
[email protected]f308e5b12012-01-17 19:09:18153 std::vector<SkScalar> positions;
154 std::vector<SkColor> colors;
155
156 if (!left_part.IsEmpty())
157 AddFadeEffect(text_rect, left_part, fade_color, color,
158 &positions, &colors);
159 if (!right_part.IsEmpty())
160 AddFadeEffect(text_rect, right_part, color, fade_color,
161 &positions, &colors);
162 DCHECK(!positions.empty());
163
164 // Terminate |positions| with 1.0, as required by Skia.
165 if (positions.back() != 1.0) {
166 positions.push_back(1.0);
167 colors.push_back(colors.back());
168 }
169
pkasting07f44462015-11-03 21:00:21170 const SkPoint points[2] = { PointToSkPoint(text_rect.origin()),
171 PointToSkPoint(text_rect.top_right()) };
tomhudson2b45cf92016-03-31 14:10:15172 return
173 SkGradientShader::MakeLinear(&points[0], &colors[0], &positions[0],
174 colors.size(), SkShader::kClamp_TileMode);
[email protected]959e1902012-03-03 02:02:17175}
[email protected]f308e5b12012-01-17 19:09:18176
[email protected]6b2e2372014-07-11 21:21:18177// Converts a FontRenderParams::Hinting value to the corresponding
178// SkPaint::Hinting value.
179SkPaint::Hinting FontRenderParamsHintingToSkPaintHinting(
180 FontRenderParams::Hinting params_hinting) {
181 switch (params_hinting) {
182 case FontRenderParams::HINTING_NONE: return SkPaint::kNo_Hinting;
183 case FontRenderParams::HINTING_SLIGHT: return SkPaint::kSlight_Hinting;
184 case FontRenderParams::HINTING_MEDIUM: return SkPaint::kNormal_Hinting;
185 case FontRenderParams::HINTING_FULL: return SkPaint::kFull_Hinting;
186 }
187 return SkPaint::kNo_Hinting;
188}
189
dschuylerfcd76912015-03-11 01:40:11190// Make sure ranges don't break text graphemes. If a range in |break_list|
191// does break a grapheme in |render_text|, the range will be slightly
192// extended to encompass the grapheme.
193template <typename T>
mgiuca859e3362015-06-03 08:34:13194void RestoreBreakList(RenderText* render_text, BreakList<T>* break_list) {
195 break_list->SetMax(render_text->text().length());
dschuylerfcd76912015-03-11 01:40:11196 Range range;
mgiuca859e3362015-06-03 08:34:13197 while (range.end() < break_list->max()) {
198 const auto& current_break = break_list->GetBreak(range.end());
199 range = break_list->GetRange(current_break);
200 if (range.end() < break_list->max() &&
dschuylerfcd76912015-03-11 01:40:11201 !render_text->IsValidCursorIndex(range.end())) {
202 range.set_end(
203 render_text->IndexOfAdjacentGrapheme(range.end(), CURSOR_FORWARD));
mgiuca859e3362015-06-03 08:34:13204 break_list->ApplyValue(current_break->second, range);
dschuylerfcd76912015-03-11 01:40:11205 }
206 }
207}
208
[email protected]ff44d712011-07-25 08:42:52209} // namespace
210
[email protected]67b981562011-12-09 00:35:05211namespace internal {
212
[email protected]e2326012012-06-12 22:59:41213// Value of |underline_thickness_| that indicates that underline metrics have
214// not been set explicitly.
215const SkScalar kUnderlineMetricsNotSet = -1.0f;
216
[email protected]67b981562011-12-09 00:35:05217SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
[email protected]1c9b1cc2014-03-27 05:37:36218 : canvas_(canvas),
219 canvas_skia_(canvas->sk_canvas()),
[email protected]e2326012012-06-12 22:59:41220 underline_thickness_(kUnderlineMetricsNotSet),
221 underline_position_(0.0f) {
[email protected]67b981562011-12-09 00:35:05222 DCHECK(canvas_skia_);
223 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
224 paint_.setStyle(SkPaint::kFill_Style);
225 paint_.setAntiAlias(true);
226 paint_.setSubpixelText(true);
227 paint_.setLCDRenderText(true);
[email protected]564e2e52013-12-19 01:44:21228 paint_.setHinting(SkPaint::kNormal_Hinting);
[email protected]67b981562011-12-09 00:35:05229}
230
231SkiaTextRenderer::~SkiaTextRenderer() {
232}
233
tomhudson53973942016-03-31 17:05:00234void SkiaTextRenderer::SetDrawLooper(sk_sp<SkDrawLooper> draw_looper) {
235 paint_.setLooper(std::move(draw_looper));
[email protected]0834e1b12012-04-11 02:23:56236}
237
[email protected]cd9c65e2014-07-10 02:57:01238void SkiaTextRenderer::SetFontRenderParams(const FontRenderParams& params,
mukai4b75bd782015-02-19 21:44:42239 bool subpixel_rendering_suppressed) {
240 ApplyRenderParams(params, subpixel_rendering_suppressed, &paint_);
[email protected]564e2e52013-12-19 01:44:21241}
242
fmalita6d979c2792016-06-20 14:17:50243void SkiaTextRenderer::SetTypeface(sk_sp<SkTypeface> typeface) {
244 paint_.setTypeface(std::move(typeface));
[email protected]cd8e5522011-12-15 00:24:10245}
246
[email protected]e2326012012-06-12 22:59:41247void SkiaTextRenderer::SetTextSize(SkScalar size) {
[email protected]cd8e5522011-12-15 00:24:10248 paint_.setTextSize(size);
249}
250
[email protected]67b981562011-12-09 00:35:05251void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
252 paint_.setColor(foreground);
253}
254
tomhudson2b45cf92016-03-31 14:10:15255void SkiaTextRenderer::SetShader(sk_sp<SkShader> shader) {
256 paint_.setShader(std::move(shader));
[email protected]f308e5b12012-01-17 19:09:18257}
258
[email protected]e2326012012-06-12 22:59:41259void SkiaTextRenderer::SetUnderlineMetrics(SkScalar thickness,
260 SkScalar position) {
261 underline_thickness_ = thickness;
262 underline_position_ = position;
263}
264
[email protected]67b981562011-12-09 00:35:05265void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
avic89eb8d42015-12-23 08:08:18266 const uint16_t* glyphs,
[email protected]67b981562011-12-09 00:35:05267 size_t glyph_count) {
[email protected]3bf21e12012-05-02 15:33:36268 const size_t byte_length = glyph_count * sizeof(glyphs[0]);
[email protected]67b981562011-12-09 00:35:05269 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
270}
271
[email protected]ccfa43f02013-02-01 04:42:17272void SkiaTextRenderer::DrawDecorations(int x, int y, int width, bool underline,
273 bool strike, bool diagonal_strike) {
274 if (underline)
275 DrawUnderline(x, y, width);
276 if (strike)
277 DrawStrike(x, y, width);
[email protected]1c9b1cc2014-03-27 05:37:36278 if (diagonal_strike) {
279 if (!diagonal_)
280 diagonal_.reset(new DiagonalStrike(canvas_, Point(x, y), paint_));
281 diagonal_->AddPiece(width, paint_.getColor());
282 } else if (diagonal_) {
283 EndDiagonalStrike();
284 }
285}
286
287void SkiaTextRenderer::EndDiagonalStrike() {
288 if (diagonal_) {
289 diagonal_->Draw();
290 diagonal_.reset();
291 }
[email protected]ccfa43f02013-02-01 04:42:17292}
[email protected]54d32cc2012-01-27 19:52:18293
[email protected]ccfa43f02013-02-01 04:42:17294void SkiaTextRenderer::DrawUnderline(int x, int y, int width) {
pkastingbc91db52014-10-21 20:35:42295 SkScalar x_scalar = SkIntToScalar(x);
296 SkRect r = SkRect::MakeLTRB(
297 x_scalar, y + underline_position_, x_scalar + width,
298 y + underline_position_ + underline_thickness_);
[email protected]ccfa43f02013-02-01 04:42:17299 if (underline_thickness_ == kUnderlineMetricsNotSet) {
300 const SkScalar text_size = paint_.getTextSize();
301 r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y);
302 r.fBottom = r.fTop + SkScalarMul(text_size, kLineThickness);
[email protected]67b981562011-12-09 00:35:05303 }
[email protected]ccfa43f02013-02-01 04:42:17304 canvas_skia_->drawRect(r, paint_);
305}
306
307void SkiaTextRenderer::DrawStrike(int x, int y, int width) const {
308 const SkScalar text_size = paint_.getTextSize();
309 const SkScalar height = SkScalarMul(text_size, kLineThickness);
310 const SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
pkastingbc91db52014-10-21 20:35:42311 SkScalar x_scalar = SkIntToScalar(x);
312 const SkRect r =
313 SkRect::MakeLTRB(x_scalar, offset, x_scalar + width, offset + height);
[email protected]ccfa43f02013-02-01 04:42:17314 canvas_skia_->drawRect(r, paint_);
315}
316
[email protected]1c9b1cc2014-03-27 05:37:36317SkiaTextRenderer::DiagonalStrike::DiagonalStrike(Canvas* canvas,
318 Point start,
319 const SkPaint& paint)
320 : canvas_(canvas),
321 start_(start),
322 paint_(paint),
323 total_length_(0) {
324}
325
326SkiaTextRenderer::DiagonalStrike::~DiagonalStrike() {
327}
328
329void SkiaTextRenderer::DiagonalStrike::AddPiece(int length, SkColor color) {
330 pieces_.push_back(Piece(length, color));
331 total_length_ += length;
332}
333
334void SkiaTextRenderer::DiagonalStrike::Draw() {
[email protected]ccfa43f02013-02-01 04:42:17335 const SkScalar text_size = paint_.getTextSize();
336 const SkScalar offset = SkScalarMul(text_size, kDiagonalStrikeMarginOffset);
[email protected]1c9b1cc2014-03-27 05:37:36337 const int thickness =
338 SkScalarCeilToInt(SkScalarMul(text_size, kLineThickness) * 2);
339 const int height = SkScalarCeilToInt(text_size - offset);
340 const Point end = start_ + Vector2d(total_length_, -height);
[email protected]52848022014-05-22 19:01:07341 const int clip_height = height + 2 * thickness;
[email protected]ccfa43f02013-02-01 04:42:17342
[email protected]1c9b1cc2014-03-27 05:37:36343 paint_.setAntiAlias(true);
pkastingbc91db52014-10-21 20:35:42344 paint_.setStrokeWidth(SkIntToScalar(thickness));
[email protected]1c9b1cc2014-03-27 05:37:36345
346 const bool clipped = pieces_.size() > 1;
[email protected]24355dd2014-06-30 06:51:12347 SkCanvas* sk_canvas = canvas_->sk_canvas();
[email protected]1c9b1cc2014-03-27 05:37:36348 int x = start_.x();
[email protected]24355dd2014-06-30 06:51:12349
[email protected]1c9b1cc2014-03-27 05:37:36350 for (size_t i = 0; i < pieces_.size(); ++i) {
351 paint_.setColor(pieces_[i].second);
352
353 if (clipped) {
[email protected]24355dd2014-06-30 06:51:12354 canvas_->Save();
[email protected]52848022014-05-22 19:01:07355 sk_canvas->clipRect(RectToSkRect(
[email protected]24355dd2014-06-30 06:51:12356 Rect(x, end.y() - thickness, pieces_[i].first, clip_height)));
[email protected]1c9b1cc2014-03-27 05:37:36357 }
358
359 canvas_->DrawLine(start_, end, paint_);
360
[email protected]24355dd2014-06-30 06:51:12361 if (clipped)
362 canvas_->Restore();
363
[email protected]1c9b1cc2014-03-27 05:37:36364 x += pieces_[i].first;
365 }
[email protected]ccfa43f02013-02-01 04:42:17366}
367
368StyleIterator::StyleIterator(const BreakList<SkColor>& colors,
dschuylerfcd76912015-03-11 01:40:11369 const BreakList<BaselineStyle>& baselines,
mboc998e8902016-06-02 11:40:35370 const BreakList<Font::Weight>& weights,
dschuylerfcd76912015-03-11 01:40:11371 const std::vector<BreakList<bool>>& styles)
mboc998e8902016-06-02 11:40:35372 : colors_(colors),
373 baselines_(baselines),
374 weights_(weights),
375 styles_(styles) {
[email protected]ccfa43f02013-02-01 04:42:17376 color_ = colors_.breaks().begin();
dschuylerfcd76912015-03-11 01:40:11377 baseline_ = baselines_.breaks().begin();
mboc998e8902016-06-02 11:40:35378 weight_ = weights_.breaks().begin();
[email protected]ccfa43f02013-02-01 04:42:17379 for (size_t i = 0; i < styles_.size(); ++i)
380 style_.push_back(styles_[i].breaks().begin());
381}
382
383StyleIterator::~StyleIterator() {}
384
[email protected]3dfe5c52013-09-18 22:01:22385Range StyleIterator::GetRange() const {
386 Range range(colors_.GetRange(color_));
dschuylerfcd76912015-03-11 01:40:11387 range = range.Intersect(baselines_.GetRange(baseline_));
mboc998e8902016-06-02 11:40:35388 range = range.Intersect(weights_.GetRange(weight_));
[email protected]ccfa43f02013-02-01 04:42:17389 for (size_t i = 0; i < NUM_TEXT_STYLES; ++i)
390 range = range.Intersect(styles_[i].GetRange(style_[i]));
391 return range;
392}
393
394void StyleIterator::UpdatePosition(size_t position) {
395 color_ = colors_.GetBreak(position);
dschuylerfcd76912015-03-11 01:40:11396 baseline_ = baselines_.GetBreak(position);
mboc998e8902016-06-02 11:40:35397 weight_ = weights_.GetBreak(position);
[email protected]ccfa43f02013-02-01 04:42:17398 for (size_t i = 0; i < NUM_TEXT_STYLES; ++i)
399 style_[i] = styles_[i].GetBreak(position);
[email protected]67b981562011-12-09 00:35:05400}
401
xdai08820ef2015-06-23 20:22:03402LineSegment::LineSegment() : run(0) {}
[email protected]eced6cb2013-09-16 20:16:20403
404LineSegment::~LineSegment() {}
405
406Line::Line() : preceding_heights(0), baseline(0) {}
407
vmpstr0ae825e72016-02-25 20:31:31408Line::Line(const Line& other) = default;
409
[email protected]eced6cb2013-09-16 20:16:20410Line::~Line() {}
411
ckocagil04f49f42014-08-25 22:13:43412void ApplyRenderParams(const FontRenderParams& params,
mukai4b75bd782015-02-19 21:44:42413 bool subpixel_rendering_suppressed,
ckocagil04f49f42014-08-25 22:13:43414 SkPaint* paint) {
415 paint->setAntiAlias(params.antialiasing);
mukai4b75bd782015-02-19 21:44:42416 paint->setLCDRenderText(!subpixel_rendering_suppressed &&
ckocagil04f49f42014-08-25 22:13:43417 params.subpixel_rendering != FontRenderParams::SUBPIXEL_RENDERING_NONE);
418 paint->setSubpixelText(params.subpixel_positioning);
419 paint->setAutohinted(params.autohinter);
420 paint->setHinting(FontRenderParamsHintingToSkPaintHinting(params.hinting));
421}
422
[email protected]67b981562011-12-09 00:35:05423} // namespace internal
424
[email protected]8e42ba22011-08-04 21:47:08425RenderText::~RenderText() {
426}
427
mukai1dfc595f2015-03-11 20:30:05428// static
[email protected]52848022014-05-22 19:01:07429RenderText* RenderText::CreateInstance() {
andresantoso62fd79bb2014-12-09 01:33:57430#if defined(OS_MACOSX)
derata55b0115d2015-02-03 01:59:08431 static const bool use_native =
avi6b10fd02014-12-23 05:51:23432 !base::CommandLine::ForCurrentProcess()->HasSwitch(
derata55b0115d2015-02-03 01:59:08433 switches::kEnableHarfBuzzRenderText);
434 if (use_native)
435 return new RenderTextMac;
436#endif // defined(OS_MACOSX)
437 return new RenderTextHarfBuzz;
[email protected]52848022014-05-22 19:01:07438}
439
mukai1dfc595f2015-03-11 20:30:05440// static
andresantoso62fd79bb2014-12-09 01:33:57441RenderText* RenderText::CreateInstanceForEditing() {
derata55b0115d2015-02-03 01:59:08442 return new RenderTextHarfBuzz;
andresantoso62fd79bb2014-12-09 01:33:57443}
444
krba468b18b2016-05-17 21:00:35445std::unique_ptr<RenderText> RenderText::CreateInstanceOfSameStyle(
446 const base::string16& text) const {
447 std::unique_ptr<RenderText> render_text = CreateInstanceOfSameType();
448 // |SetText()| must be called before styles are set.
449 render_text->SetText(text);
450 render_text->SetFontList(font_list_);
451 render_text->SetDirectionalityMode(directionality_mode_);
452 render_text->SetCursorEnabled(cursor_enabled_);
453 render_text->set_truncate_length(truncate_length_);
454 render_text->styles_ = styles_;
455 render_text->baselines_ = baselines_;
456 render_text->colors_ = colors_;
mboc998e8902016-06-02 11:40:35457 render_text->weights_ = weights_;
krba468b18b2016-05-17 21:00:35458 return render_text;
459}
460
[email protected]031ffed2013-06-09 03:32:36461void RenderText::SetText(const base::string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06462 DCHECK(!composition_range_.IsValid());
[email protected]eced6cb2013-09-16 20:16:20463 if (text_ == text)
464 return;
[email protected]ff44d712011-07-25 08:42:52465 text_ = text;
dschuyler92bc0de2015-03-20 03:24:21466 UpdateStyleLengths();
[email protected]ff44d712011-07-25 08:42:52467
dschuyler92bc0de2015-03-20 03:24:21468 // Clear style ranges as they might break new text graphemes and apply
tmoniuszkoa7fd79d2014-09-18 10:12:01469 // the first style to the whole text instead.
dschuyler92bc0de2015-03-20 03:24:21470 colors_.SetValue(colors_.breaks().begin()->second);
dschuylerfcd76912015-03-11 01:40:11471 baselines_.SetValue(baselines_.breaks().begin()->second);
mboc998e8902016-06-02 11:40:35472 weights_.SetValue(weights_.breaks().begin()->second);
dschuyler92bc0de2015-03-20 03:24:21473 for (size_t style = 0; style < NUM_TEXT_STYLES; ++style)
474 styles_[style].SetValue(styles_[style].breaks().begin()->second);
[email protected]f6aaa0f2011-08-11 07:05:46475 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06476
477 // Reset selection model. SetText should always followed by SetSelectionModel
478 // or SetCursorPosition in upper layer.
[email protected]d9990212012-03-13 01:09:31479 SetSelectionModel(SelectionModel());
[email protected]6002a4f32011-11-30 10:18:42480
[email protected]46cb5382012-08-01 21:57:31481 // Invalidate the cached text direction if it depends on the text contents.
482 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT)
483 text_direction_ = base::i18n::UNKNOWN_DIRECTION;
484
[email protected]01e06a32013-06-07 12:41:33485 obscured_reveal_index_ = -1;
oshima0a243a42015-02-14 04:46:31486 OnTextAttributeChanged();
[email protected]ff44d712011-07-25 08:42:52487}
488
dschuyler92bc0de2015-03-20 03:24:21489void RenderText::AppendText(const base::string16& text) {
490 text_ += text;
491 UpdateStyleLengths();
492 cached_bounds_and_offset_valid_ = false;
493 obscured_reveal_index_ = -1;
494 OnTextAttributeChanged();
495}
496
[email protected]f0ed8a2f2012-01-24 17:45:59497void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
498 if (horizontal_alignment_ != alignment) {
499 horizontal_alignment_ = alignment;
[email protected]ceb36f7d2012-10-31 18:33:24500 display_offset_ = Vector2d();
[email protected]f0ed8a2f2012-01-24 17:45:59501 cached_bounds_and_offset_valid_ = false;
502 }
503}
504
[email protected]8d901a82012-01-04 19:41:30505void RenderText::SetFontList(const FontList& font_list) {
506 font_list_ = font_list;
[email protected]b183dd12014-07-21 07:55:51507 const int font_style = font_list.GetFontStyle();
mboc998e8902016-06-02 11:40:35508 weights_.SetValue(font_list.GetFontWeight());
509 styles_[ITALIC].SetValue((font_style & Font::ITALIC) != 0);
510 styles_[UNDERLINE].SetValue((font_style & Font::UNDERLINE) != 0);
[email protected]56e50572013-11-01 14:25:31511 baseline_ = kInvalidBaseline;
[email protected]8d901a82012-01-04 19:41:30512 cached_bounds_and_offset_valid_ = false;
oshima0a243a42015-02-14 04:46:31513 OnLayoutTextAttributeChanged(false);
[email protected]8d901a82012-01-04 19:41:30514}
515
[email protected]f0ed8a2f2012-01-24 17:45:59516void RenderText::SetCursorEnabled(bool cursor_enabled) {
517 cursor_enabled_ = cursor_enabled;
518 cached_bounds_and_offset_valid_ = false;
519}
520
[email protected]bec929c2012-03-02 06:23:50521void RenderText::SetObscured(bool obscured) {
522 if (obscured != obscured_) {
523 obscured_ = obscured;
[email protected]01e06a32013-06-07 12:41:33524 obscured_reveal_index_ = -1;
[email protected]bec929c2012-03-02 06:23:50525 cached_bounds_and_offset_valid_ = false;
oshima0a243a42015-02-14 04:46:31526 OnTextAttributeChanged();
[email protected]bec929c2012-03-02 06:23:50527 }
528}
529
[email protected]01e06a32013-06-07 12:41:33530void RenderText::SetObscuredRevealIndex(int index) {
531 if (obscured_reveal_index_ == index)
532 return;
533
534 obscured_reveal_index_ = index;
535 cached_bounds_and_offset_valid_ = false;
oshima0a243a42015-02-14 04:46:31536 OnTextAttributeChanged();
[email protected]b183dd12014-07-21 07:55:51537}
538
[email protected]eced6cb2013-09-16 20:16:20539void RenderText::SetMultiline(bool multiline) {
540 if (multiline != multiline_) {
541 multiline_ = multiline;
542 cached_bounds_and_offset_valid_ = false;
543 lines_.clear();
mukai9b589ee2015-02-20 01:02:14544 OnTextAttributeChanged();
[email protected]eced6cb2013-09-16 20:16:20545 }
546}
547
krba468b18b2016-05-17 21:00:35548void RenderText::SetMaxLines(size_t max_lines) {
549 max_lines_ = max_lines;
550 OnDisplayTextAttributeChanged();
551}
552
553size_t RenderText::GetNumLines() {
554 return lines_.size();
555}
556
mukaia1933932015-03-28 00:07:05557void RenderText::SetWordWrapBehavior(WordWrapBehavior behavior) {
558 if (word_wrap_behavior_ == behavior)
559 return;
560 word_wrap_behavior_ = behavior;
561 if (multiline_) {
562 cached_bounds_and_offset_valid_ = false;
563 lines_.clear();
564 OnTextAttributeChanged();
565 }
566}
567
mukai1dfc595f2015-03-11 20:30:05568void RenderText::SetReplaceNewlineCharsWithSymbols(bool replace) {
569 if (replace_newline_chars_with_symbols_ == replace)
570 return;
571 replace_newline_chars_with_symbols_ = replace;
572 cached_bounds_and_offset_valid_ = false;
573 OnTextAttributeChanged();
574}
575
mukaif32fdd5b2015-02-10 22:26:50576void RenderText::SetMinLineHeight(int line_height) {
577 if (min_line_height_ == line_height)
578 return;
579 min_line_height_ = line_height;
580 cached_bounds_and_offset_valid_ = false;
581 lines_.clear();
oshima0a243a42015-02-14 04:46:31582 OnDisplayTextAttributeChanged();
mukaif32fdd5b2015-02-10 22:26:50583}
584
[email protected]ec2ce922014-01-02 23:06:47585void RenderText::SetElideBehavior(ElideBehavior elide_behavior) {
586 // TODO(skanuj) : Add a test for triggering layout change.
587 if (elide_behavior_ != elide_behavior) {
588 elide_behavior_ = elide_behavior;
oshima0a243a42015-02-14 04:46:31589 OnDisplayTextAttributeChanged();
[email protected]ec2ce922014-01-02 23:06:47590 }
591}
592
[email protected]f6aaa0f2011-08-11 07:05:46593void RenderText::SetDisplayRect(const Rect& r) {
[email protected]ec2ce922014-01-02 23:06:47594 if (r != display_rect_) {
595 display_rect_ = r;
596 baseline_ = kInvalidBaseline;
597 cached_bounds_and_offset_valid_ = false;
598 lines_.clear();
oshima0a243a42015-02-14 04:46:31599 if (elide_behavior_ != NO_ELIDE &&
600 elide_behavior_ != FADE_TAIL) {
601 OnDisplayTextAttributeChanged();
602 }
[email protected]ec2ce922014-01-02 23:06:47603 }
[email protected]8e42ba22011-08-04 21:47:08604}
605
[email protected]0d717602011-08-30 06:21:14606void RenderText::SetCursorPosition(size_t position) {
607 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52608}
609
[email protected]d66009e2012-01-21 01:27:28610void RenderText::MoveCursor(BreakType break_type,
611 VisualCursorDirection direction,
612 bool select) {
[email protected]3dbc8a62014-05-02 17:08:17613 SelectionModel cursor(cursor_position(), selection_model_.caret_affinity());
[email protected]ff44d712011-07-25 08:42:52614 // Cancelling a selection moves to the edge of the selection.
[email protected]d9990212012-03-13 01:09:31615 if (break_type != LINE_BREAK && !selection().is_empty() && !select) {
[email protected]d3c6b0602011-09-07 19:26:06616 SelectionModel selection_start = GetSelectionModelForSelectionStart();
[email protected]d66009e2012-01-21 01:27:28617 int start_x = GetCursorBounds(selection_start, true).x();
[email protected]3dbc8a62014-05-02 17:08:17618 int cursor_x = GetCursorBounds(cursor, true).x();
[email protected]d66009e2012-01-21 01:27:28619 // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
620 // or right (when |direction| is CURSOR_RIGHT) of the selection end.
621 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
[email protected]3dbc8a62014-05-02 17:08:17622 cursor = selection_start;
623 // Use the nearest word boundary in the proper |direction| for word breaks.
[email protected]ff44d712011-07-25 08:42:52624 if (break_type == WORD_BREAK)
[email protected]3dbc8a62014-05-02 17:08:17625 cursor = GetAdjacentSelectionModel(cursor, break_type, direction);
626 // Use an adjacent selection model if the cursor is not at a valid position.
627 if (!IsValidCursorIndex(cursor.caret_pos()))
628 cursor = GetAdjacentSelectionModel(cursor, CHARACTER_BREAK, direction);
[email protected]ff44d712011-07-25 08:42:52629 } else {
[email protected]3dbc8a62014-05-02 17:08:17630 cursor = GetAdjacentSelectionModel(cursor, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52631 }
[email protected]ec7f48d2011-08-09 03:48:50632 if (select)
[email protected]3dbc8a62014-05-02 17:08:17633 cursor.set_selection_start(selection().start());
634 MoveCursorTo(cursor);
[email protected]ff44d712011-07-25 08:42:52635}
636
[email protected]f9a221b2011-12-10 20:25:38637bool RenderText::MoveCursorTo(const SelectionModel& model) {
[email protected]0d717602011-08-30 06:21:14638 // Enforce valid selection model components.
[email protected]d9990212012-03-13 01:09:31639 size_t text_length = text().length();
jam1f0b47c2016-02-10 01:49:25640 Range range(std::min(model.selection().start(),
641 static_cast<uint32_t>(text_length)),
[email protected]3dfe5c52013-09-18 22:01:22642 std::min(model.caret_pos(), text_length));
[email protected]3dbc8a62014-05-02 17:08:17643 // The current model only supports caret positions at valid cursor indices.
644 if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end()))
[email protected]53c0b1b2011-09-21 20:32:29645 return false;
[email protected]d9990212012-03-13 01:09:31646 SelectionModel sel(range, model.caret_affinity());
647 bool changed = sel != selection_model_;
[email protected]0d717602011-08-30 06:21:14648 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52649 return changed;
650}
651
[email protected]3dfe5c52013-09-18 22:01:22652bool RenderText::SelectRange(const Range& range) {
jam1f0b47c2016-02-10 01:49:25653 uint32_t text_length = static_cast<uint32_t>(text().length());
654 Range sel(std::min(range.start(), text_length),
655 std::min(range.end(), text_length));
[email protected]3dbc8a62014-05-02 17:08:17656 // Allow selection bounds at valid indicies amid multi-character graphemes.
657 if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end()))
[email protected]67e85512011-10-12 20:03:45658 return false;
[email protected]d9990212012-03-13 01:09:31659 LogicalCursorDirection affinity =
660 (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD;
661 SetSelectionModel(SelectionModel(sel, affinity));
[email protected]67e85512011-10-12 20:03:45662 return true;
663}
664
[email protected]8e42ba22011-08-04 21:47:08665bool RenderText::IsPointInSelection(const Point& point) {
[email protected]d9990212012-03-13 01:09:31666 if (selection().is_empty())
[email protected]0d717602011-08-30 06:21:14667 return false;
[email protected]d9990212012-03-13 01:09:31668 SelectionModel cursor = FindCursorPosition(point);
669 return RangeContainsCaret(
670 selection(), cursor.caret_pos(), cursor.caret_affinity());
[email protected]ff44d712011-07-25 08:42:52671}
672
673void RenderText::ClearSelection() {
[email protected]d9990212012-03-13 01:09:31674 SetSelectionModel(SelectionModel(cursor_position(),
675 selection_model_.caret_affinity()));
[email protected]ff44d712011-07-25 08:42:52676}
677
[email protected]f5f0c2a2012-07-13 04:44:55678void RenderText::SelectAll(bool reversed) {
679 const size_t length = text().length();
[email protected]3dfe5c52013-09-18 22:01:22680 const Range all = reversed ? Range(length, 0) : Range(0, length);
[email protected]f5f0c2a2012-07-13 04:44:55681 const bool success = SelectRange(all);
682 DCHECK(success);
[email protected]ff44d712011-07-25 08:42:52683}
684
685void RenderText::SelectWord() {
[email protected]bec929c2012-03-02 06:23:50686 if (obscured_) {
[email protected]f5f0c2a2012-07-13 04:44:55687 SelectAll(false);
[email protected]bec929c2012-03-02 06:23:50688 return;
689 }
690
[email protected]fa7b96f12013-05-14 14:45:44691 size_t selection_max = selection().GetMax();
[email protected]ff44d712011-07-25 08:42:52692
[email protected]53c0b1b2011-09-21 20:32:29693 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
694 bool success = iter.Init();
695 DCHECK(success);
696 if (!success)
697 return;
698
[email protected]fa7b96f12013-05-14 14:45:44699 size_t selection_min = selection().GetMin();
700 if (selection_min == text().length() && selection_min != 0)
701 --selection_min;
[email protected]8c4961312013-04-18 12:43:57702
[email protected]fa7b96f12013-05-14 14:45:44703 for (; selection_min != 0; --selection_min) {
704 if (iter.IsStartOfWord(selection_min) ||
705 iter.IsEndOfWord(selection_min))
[email protected]ff44d712011-07-25 08:42:52706 break;
707 }
708
[email protected]fa7b96f12013-05-14 14:45:44709 if (selection_min == selection_max && selection_max != text().length())
710 ++selection_max;
[email protected]53c0b1b2011-09-21 20:32:29711
[email protected]fa7b96f12013-05-14 14:45:44712 for (; selection_max < text().length(); ++selection_max)
713 if (iter.IsEndOfWord(selection_max) || iter.IsStartOfWord(selection_max))
[email protected]ff44d712011-07-25 08:42:52714 break;
[email protected]ff44d712011-07-25 08:42:52715
[email protected]fa7b96f12013-05-14 14:45:44716 const bool reversed = selection().is_reversed();
717 MoveCursorTo(reversed ? selection_max : selection_min, false);
718 MoveCursorTo(reversed ? selection_min : selection_max, true);
[email protected]ff44d712011-07-25 08:42:52719}
720
[email protected]3dfe5c52013-09-18 22:01:22721void RenderText::SetCompositionRange(const Range& composition_range) {
[email protected]ff44d712011-07-25 08:42:52722 CHECK(!composition_range.IsValid() ||
[email protected]3dfe5c52013-09-18 22:01:22723 Range(0, text_.length()).Contains(composition_range));
[email protected]ff44d712011-07-25 08:42:52724 composition_range_.set_end(composition_range.end());
725 composition_range_.set_start(composition_range.start());
oshima0a243a42015-02-14 04:46:31726 // TODO(oshima|msw): Altering composition underlines shouldn't
727 // require layout changes. It's currently necessary because
728 // RenderTextHarfBuzz paints text decorations by run, and
729 // RenderTextMac applies all styles during layout.
730 OnLayoutTextAttributeChanged(false);
[email protected]ff44d712011-07-25 08:42:52731}
732
[email protected]ccfa43f02013-02-01 04:42:17733void RenderText::SetColor(SkColor value) {
734 colors_.SetValue(value);
tapted1575b6292015-09-25 03:05:31735 OnTextColorChanged();
[email protected]ff44d712011-07-25 08:42:52736}
737
[email protected]3dfe5c52013-09-18 22:01:22738void RenderText::ApplyColor(SkColor value, const Range& range) {
[email protected]ccfa43f02013-02-01 04:42:17739 colors_.ApplyValue(value, range);
tapted1575b6292015-09-25 03:05:31740 OnTextColorChanged();
[email protected]ccfa43f02013-02-01 04:42:17741}
742
dschuylerfcd76912015-03-11 01:40:11743void RenderText::SetBaselineStyle(BaselineStyle value) {
744 baselines_.SetValue(value);
745}
746
747void RenderText::ApplyBaselineStyle(BaselineStyle value, const Range& range) {
748 baselines_.ApplyValue(value, range);
749}
750
[email protected]ccfa43f02013-02-01 04:42:17751void RenderText::SetStyle(TextStyle style, bool value) {
752 styles_[style].SetValue(value);
753
ckocagil3be7af42014-09-03 05:04:56754 cached_bounds_and_offset_valid_ = false;
oshima0a243a42015-02-14 04:46:31755 // TODO(oshima|msw): Not all style change requires layout changes.
756 // Consider optimizing based on the type of change.
757 OnLayoutTextAttributeChanged(false);
[email protected]ccfa43f02013-02-01 04:42:17758}
759
[email protected]3dfe5c52013-09-18 22:01:22760void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) {
tmoniuszkoa7fd79d2014-09-18 10:12:01761 // Do not change styles mid-grapheme to avoid breaking ligatures.
762 const size_t start = IsValidCursorIndex(range.start()) ? range.start() :
763 IndexOfAdjacentGrapheme(range.start(), CURSOR_BACKWARD);
764 const size_t end = IsValidCursorIndex(range.end()) ? range.end() :
765 IndexOfAdjacentGrapheme(range.end(), CURSOR_FORWARD);
766 styles_[style].ApplyValue(value, Range(start, end));
[email protected]ccfa43f02013-02-01 04:42:17767
ckocagil3be7af42014-09-03 05:04:56768 cached_bounds_and_offset_valid_ = false;
oshima0a243a42015-02-14 04:46:31769 // TODO(oshima|msw): Not all style change requires layout changes.
770 // Consider optimizing based on the type of change.
771 OnLayoutTextAttributeChanged(false);
[email protected]ff44d712011-07-25 08:42:52772}
773
mboc998e8902016-06-02 11:40:35774void RenderText::SetWeight(Font::Weight weight) {
775 weights_.SetValue(weight);
776
777 cached_bounds_and_offset_valid_ = false;
778 OnLayoutTextAttributeChanged(false);
779}
780
781void RenderText::ApplyWeight(Font::Weight weight, const Range& range) {
782 weights_.ApplyValue(weight, range);
783
784 cached_bounds_and_offset_valid_ = false;
785 OnLayoutTextAttributeChanged(false);
786}
787
[email protected]ae0230a2013-06-14 23:47:32788bool RenderText::GetStyle(TextStyle style) const {
789 return (styles_[style].breaks().size() == 1) &&
790 styles_[style].breaks().front().second;
791}
792
[email protected]46cb5382012-08-01 21:57:31793void RenderText::SetDirectionalityMode(DirectionalityMode mode) {
794 if (mode == directionality_mode_)
795 return;
796
797 directionality_mode_ = mode;
798 text_direction_ = base::i18n::UNKNOWN_DIRECTION;
[email protected]7ad72fb2013-11-08 01:33:20799 cached_bounds_and_offset_valid_ = false;
oshima0a243a42015-02-14 04:46:31800 OnLayoutTextAttributeChanged(false);
[email protected]46cb5382012-08-01 21:57:31801}
802
oshima0a243a42015-02-14 04:46:31803base::i18n::TextDirection RenderText::GetDisplayTextDirection() {
804 return GetTextDirection(GetDisplayText());
[email protected]46cb5382012-08-01 21:57:31805}
806
[email protected]d66009e2012-01-21 01:27:28807VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
oshima0a243a42015-02-14 04:46:31808 return GetDisplayTextDirection() == base::i18n::LEFT_TO_RIGHT ?
[email protected]d66009e2012-01-21 01:27:28809 CURSOR_RIGHT : CURSOR_LEFT;
[email protected]ff44d712011-07-25 08:42:52810}
811
[email protected]8ad3c5a2013-10-10 09:57:19812SizeF RenderText::GetStringSizeF() {
mboc998e8902016-06-02 11:40:35813 return SizeF(GetStringSize());
[email protected]8ad3c5a2013-10-10 09:57:19814}
815
ckocagil4bd060c2014-12-08 22:32:35816float RenderText::GetContentWidthF() {
817 const float string_size = GetStringSizeF().width();
ckocagil9a4438e2014-12-02 04:48:30818 // The cursor is drawn one pixel beyond the int-enclosed text bounds.
ckocagil4bd060c2014-12-08 22:32:35819 return cursor_enabled_ ? std::ceil(string_size) + 1 : string_size;
820}
821
822int RenderText::GetContentWidth() {
823 return ToCeiledInt(GetContentWidthF());
[email protected]ad0e54c02013-04-27 20:40:38824}
825
[email protected]56e50572013-11-01 14:25:31826int RenderText::GetBaseline() {
827 if (baseline_ == kInvalidBaseline)
828 baseline_ = DetermineBaselineCenteringText(display_rect(), font_list());
829 DCHECK_NE(kInvalidBaseline, baseline_);
830 return baseline_;
831}
832
[email protected]7b3cb4b22011-08-02 18:36:40833void RenderText::Draw(Canvas* canvas) {
[email protected]49aa120a2012-05-31 00:01:22834 EnsureLayout();
[email protected]ff44d712011-07-25 08:42:52835
[email protected]f7816ad2012-06-22 22:15:43836 if (clip_to_display_rect()) {
[email protected]ccfa43f02013-02-01 04:42:17837 Rect clip_rect(display_rect());
[email protected]2073de72014-06-18 16:43:12838 clip_rect.Inset(ShadowValue::GetMargin(shadows_));
[email protected]0834e1b12012-04-11 02:23:56839
[email protected]f7816ad2012-06-22 22:15:43840 canvas->Save();
841 canvas->ClipRect(clip_rect);
842 }
[email protected]1a353f12012-01-18 04:20:56843
[email protected]47787272013-07-02 23:51:12844 if (!text().empty() && focused())
[email protected]1a353f12012-01-18 04:20:56845 DrawSelection(canvas);
846
[email protected]1e12a302013-04-18 12:50:42847 if (cursor_enabled() && cursor_visible() && focused())
848 DrawCursor(canvas, selection_model_);
[email protected]1a353f12012-01-18 04:20:56849
tapted1575b6292015-09-25 03:05:31850 if (!text().empty()) {
851 internal::SkiaTextRenderer renderer(canvas);
852 DrawVisualText(&renderer);
853 }
[email protected]f7816ad2012-06-22 22:15:43854
855 if (clip_to_display_rect())
856 canvas->Restore();
[email protected]ff44d712011-07-25 08:42:52857}
858
[email protected]1e12a302013-04-18 12:50:42859void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) {
860 // Paint cursor. Replace cursor is drawn as rectangle for now.
861 // TODO(msw): Draw a better cursor with a better indication of association.
862 canvas->FillRect(GetCursorBounds(position, true), cursor_color_);
863}
864
mgiuca859e3362015-06-03 08:34:13865bool RenderText::IsValidLogicalIndex(size_t index) const {
[email protected]3dbc8a62014-05-02 17:08:17866 // Check that the index is at a valid code point (not mid-surrgate-pair) and
oshima0a243a42015-02-14 04:46:31867 // that it's not truncated from the display text (its glyph may be shown).
[email protected]3dbc8a62014-05-02 17:08:17868 //
869 // Indices within truncated text are disallowed so users can easily interact
870 // with the underlying truncated text using the ellipsis as a proxy. This lets
871 // users select all text, select the truncated text, and transition from the
872 // last rendered glyph to the end of the text without getting invisible cursor
873 // positions nor needing unbounded arrow key presses to traverse the ellipsis.
874 return index == 0 || index == text().length() ||
875 (index < text().length() &&
876 (truncate_length_ == 0 || index < truncate_length_) &&
877 IsValidCodePointIndex(text(), index));
878}
879
[email protected]d9990212012-03-13 01:09:31880Rect RenderText::GetCursorBounds(const SelectionModel& caret,
881 bool insert_mode) {
[email protected]eced6cb2013-09-16 20:16:20882 // TODO(ckocagil): Support multiline. This function should return the height
883 // of the line the cursor is on. |GetStringSize()| now returns
884 // the multiline size, eliminate its use here.
885
[email protected]d9990212012-03-13 01:09:31886 EnsureLayout();
[email protected]d9990212012-03-13 01:09:31887 size_t caret_pos = caret.caret_pos();
[email protected]3dbc8a62014-05-02 17:08:17888 DCHECK(IsValidLogicalIndex(caret_pos));
[email protected]d9990212012-03-13 01:09:31889 // In overtype mode, ignore the affinity and always indicate that we will
890 // overtype the next character.
891 LogicalCursorDirection caret_affinity =
892 insert_mode ? caret.caret_affinity() : CURSOR_FORWARD;
[email protected]6f938f12013-05-24 00:49:06893 int x = 0, width = 1;
894 Size size = GetStringSize();
[email protected]d9990212012-03-13 01:09:31895 if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) {
[email protected]c33b2902012-12-06 13:04:45896 // The caret is attached to the boundary. Always return a 1-dip width caret,
[email protected]d9990212012-03-13 01:09:31897 // since there is nothing to overtype.
oshima0a243a42015-02-14 04:46:31898 if ((GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT)
899 == (caret_pos == 0)) {
[email protected]d9990212012-03-13 01:09:31900 x = size.width();
oshima0a243a42015-02-14 04:46:31901 }
[email protected]d9990212012-03-13 01:09:31902 } else {
903 size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ?
904 caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD);
[email protected]3dfe5c52013-09-18 22:01:22905 Range xspan(GetGlyphBounds(grapheme_start));
[email protected]d9990212012-03-13 01:09:31906 if (insert_mode) {
907 x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start();
908 } else { // overtype mode
909 x = xspan.GetMin();
910 width = xspan.length();
911 }
912 }
[email protected]6f938f12013-05-24 00:49:06913 return Rect(ToViewPoint(Point(x, 0)), Size(width, size.height()));
[email protected]d9990212012-03-13 01:09:31914}
915
[email protected]f6aaa0f2011-08-11 07:05:46916const Rect& RenderText::GetUpdatedCursorBounds() {
917 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08918 return cursor_bounds_;
919}
920
[email protected]0a12d5f2012-05-16 00:37:09921size_t RenderText::IndexOfAdjacentGrapheme(size_t index,
[email protected]fecf9f72013-06-29 13:02:47922 LogicalCursorDirection direction) {
[email protected]0a12d5f2012-05-16 00:37:09923 if (index > text().length())
924 return text().length();
925
926 EnsureLayout();
927
928 if (direction == CURSOR_FORWARD) {
929 while (index < text().length()) {
930 index++;
[email protected]3dbc8a62014-05-02 17:08:17931 if (IsValidCursorIndex(index))
[email protected]0a12d5f2012-05-16 00:37:09932 return index;
933 }
934 return text().length();
935 }
936
937 while (index > 0) {
938 index--;
[email protected]3dbc8a62014-05-02 17:08:17939 if (IsValidCursorIndex(index))
[email protected]0a12d5f2012-05-16 00:37:09940 return index;
941 }
942 return 0;
943}
944
mgiuca859e3362015-06-03 08:34:13945SelectionModel RenderText::GetSelectionModelForSelectionStart() const {
[email protected]3dfe5c52013-09-18 22:01:22946 const Range& sel = selection();
[email protected]d9990212012-03-13 01:09:31947 if (sel.is_empty())
948 return selection_model_;
949 return SelectionModel(sel.start(),
950 sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
[email protected]04b7bf322011-10-03 19:08:46951}
952
[email protected]7b4c25ce2014-07-10 11:14:58953const Vector2d& RenderText::GetUpdatedDisplayOffset() {
954 UpdateCachedBoundsAndOffset();
955 return display_offset_;
956}
957
958void RenderText::SetDisplayOffset(int horizontal_offset) {
ckocagil4bd060c2014-12-08 22:32:35959 const int extra_content = GetContentWidth() - display_rect_.width();
[email protected]ff0d8532014-07-23 09:36:59960 const int cursor_width = cursor_enabled_ ? 1 : 0;
[email protected]7b4c25ce2014-07-10 11:14:58961
962 int min_offset = 0;
963 int max_offset = 0;
964 if (extra_content > 0) {
[email protected]ff0d8532014-07-23 09:36:59965 switch (GetCurrentHorizontalAlignment()) {
[email protected]7b4c25ce2014-07-10 11:14:58966 case ALIGN_LEFT:
967 min_offset = -extra_content;
968 break;
969 case ALIGN_RIGHT:
970 max_offset = extra_content;
971 break;
972 case ALIGN_CENTER:
[email protected]ff0d8532014-07-23 09:36:59973 // The extra space reserved for cursor at the end of the text is ignored
974 // when centering text. So, to calculate the valid range for offset, we
975 // exclude that extra space, calculate the range, and add it back to the
976 // range (if cursor is enabled).
977 min_offset = -(extra_content - cursor_width + 1) / 2 - cursor_width;
978 max_offset = (extra_content - cursor_width) / 2;
[email protected]7b4c25ce2014-07-10 11:14:58979 break;
980 default:
981 break;
982 }
983 }
984 if (horizontal_offset < min_offset)
985 horizontal_offset = min_offset;
986 else if (horizontal_offset > max_offset)
987 horizontal_offset = max_offset;
988
989 cached_bounds_and_offset_valid_ = true;
990 display_offset_.set_x(horizontal_offset);
msw13ce21b2016-06-22 03:06:56991 cursor_bounds_ = GetCursorBounds(selection_model_, true);
[email protected]7b4c25ce2014-07-10 11:14:58992}
993
mukai26abb0b2015-03-17 18:11:27994Vector2d RenderText::GetLineOffset(size_t line_number) {
995 Vector2d offset = display_rect().OffsetFromOrigin();
996 // TODO(ckocagil): Apply the display offset for multiline scrolling.
997 if (!multiline())
998 offset.Add(GetUpdatedDisplayOffset());
999 else
1000 offset.Add(Vector2d(0, lines_[line_number].preceding_heights));
1001 offset.Add(GetAlignmentOffset(line_number));
1002 return offset;
1003}
1004
[email protected]8e42ba22011-08-04 21:47:081005RenderText::RenderText()
[email protected]f0ed8a2f2012-01-24 17:45:591006 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
[email protected]46cb5382012-08-01 21:57:311007 directionality_mode_(DIRECTIONALITY_FROM_TEXT),
1008 text_direction_(base::i18n::UNKNOWN_DIRECTION),
[email protected]f0ed8a2f2012-01-24 17:45:591009 cursor_enabled_(true),
1010 cursor_visible_(false),
[email protected]ccfa43f02013-02-01 04:42:171011 cursor_color_(kDefaultColor),
1012 selection_color_(kDefaultColor),
[email protected]311a09d2012-05-14 20:38:171013 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
[email protected]5abe6302011-12-20 23:44:321014 focused_(false),
[email protected]3dfe5c52013-09-18 22:01:221015 composition_range_(Range::InvalidRange()),
[email protected]ccfa43f02013-02-01 04:42:171016 colors_(kDefaultColor),
dschuylerfcd76912015-03-11 01:40:111017 baselines_(NORMAL_BASELINE),
mboc998e8902016-06-02 11:40:351018 weights_(Font::Weight::NORMAL),
[email protected]ccfa43f02013-02-01 04:42:171019 styles_(NUM_TEXT_STYLES),
1020 composition_and_selection_styles_applied_(false),
[email protected]bec929c2012-03-02 06:23:501021 obscured_(false),
[email protected]01e06a32013-06-07 12:41:331022 obscured_reveal_index_(-1),
[email protected]fecf9f72013-06-29 13:02:471023 truncate_length_(0),
[email protected]8d936142014-07-10 21:40:371024 elide_behavior_(NO_ELIDE),
oshima0a243a42015-02-14 04:46:311025 text_elided_(false),
mukaif32fdd5b2015-02-10 22:26:501026 min_line_height_(0),
[email protected]eced6cb2013-09-16 20:16:201027 multiline_(false),
krba468b18b2016-05-17 21:00:351028 max_lines_(0),
mukaia1933932015-03-28 00:07:051029 word_wrap_behavior_(IGNORE_LONG_WORDS),
mukai1dfc595f2015-03-11 20:30:051030 replace_newline_chars_with_symbols_(true),
mukai4b75bd782015-02-19 21:44:421031 subpixel_rendering_suppressed_(false),
[email protected]f7816ad2012-06-22 22:15:431032 clip_to_display_rect_(true),
[email protected]56e50572013-11-01 14:25:311033 baseline_(kInvalidBaseline),
krba468b18b2016-05-17 21:00:351034 cached_bounds_and_offset_valid_(false) {}
[email protected]f6aaa0f2011-08-11 07:05:461035
[email protected]d66009e2012-01-21 01:27:281036SelectionModel RenderText::GetAdjacentSelectionModel(
1037 const SelectionModel& current,
1038 BreakType break_type,
1039 VisualCursorDirection direction) {
1040 EnsureLayout();
1041
1042 if (break_type == LINE_BREAK || text().empty())
1043 return EdgeSelectionModel(direction);
[email protected]ec7f48d2011-08-09 03:48:501044 if (break_type == CHARACTER_BREAK)
[email protected]d66009e2012-01-21 01:27:281045 return AdjacentCharSelectionModel(current, direction);
1046 DCHECK(break_type == WORD_BREAK);
1047 return AdjacentWordSelectionModel(current, direction);
[email protected]0d717602011-08-30 06:21:141048}
1049
[email protected]d9990212012-03-13 01:09:311050SelectionModel RenderText::EdgeSelectionModel(
1051 VisualCursorDirection direction) {
1052 if (direction == GetVisualDirectionOfLogicalEnd())
1053 return SelectionModel(text().length(), CURSOR_FORWARD);
1054 return SelectionModel(0, CURSOR_BACKWARD);
1055}
[email protected]6002a4f32011-11-30 10:18:421056
[email protected]d9990212012-03-13 01:09:311057void RenderText::SetSelectionModel(const SelectionModel& model) {
1058 DCHECK_LE(model.selection().GetMax(), text().length());
1059 selection_model_ = model;
[email protected]6002a4f32011-11-30 10:18:421060 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:141061}
1062
tapted1575b6292015-09-25 03:05:311063void RenderText::OnTextColorChanged() {
1064}
1065
oshima0a243a42015-02-14 04:46:311066void RenderText::UpdateDisplayText(float text_width) {
krba468b18b2016-05-17 21:00:351067 // TODO(krb): Consider other elision modes for multiline.
1068 if ((multiline_ && (!max_lines_ || elide_behavior() != ELIDE_TAIL)) ||
1069 elide_behavior() == NO_ELIDE || elide_behavior() == FADE_TAIL ||
1070 (text_width > 0 && text_width < display_rect_.width()) ||
oshima0a243a42015-02-14 04:46:311071 layout_text_.empty()) {
1072 text_elided_ = false;
1073 display_text_.clear();
1074 return;
1075 }
1076
krba468b18b2016-05-17 21:00:351077 if (!multiline_) {
1078 // This doesn't trim styles so ellipsis may get rendered as a different
1079 // style than the preceding text. See crbug.com/327850.
1080 display_text_.assign(Elide(layout_text_, text_width,
1081 static_cast<float>(display_rect_.width()),
1082 elide_behavior_));
1083 } else {
1084 bool was_elided = text_elided_;
1085 text_elided_ = false;
1086 display_text_.clear();
oshima0a243a42015-02-14 04:46:311087
krba468b18b2016-05-17 21:00:351088 std::unique_ptr<RenderText> render_text(
1089 CreateInstanceOfSameStyle(layout_text_));
1090 render_text->SetMultiline(true);
1091 render_text->SetDisplayRect(display_rect_);
1092 // Have it arrange words on |lines_|.
1093 render_text->EnsureLayout();
1094
1095 if (render_text->lines_.size() > max_lines_) {
1096 size_t start_of_elision = render_text->lines_[max_lines_ - 1]
1097 .segments.front()
1098 .char_range.start();
1099 base::string16 text_to_elide = layout_text_.substr(start_of_elision);
1100 display_text_.assign(layout_text_.substr(0, start_of_elision) +
1101 Elide(text_to_elide, 0,
1102 static_cast<float>(display_rect_.width()),
1103 ELIDE_TAIL));
1104 // Have GetLineBreaks() re-calculate.
1105 line_breaks_.SetMax(0);
1106 } else {
1107 // If elision changed, re-calculate.
1108 if (was_elided)
1109 line_breaks_.SetMax(0);
1110 // Initial state above is fine.
1111 return;
1112 }
1113 }
oshima0a243a42015-02-14 04:46:311114 text_elided_ = display_text_ != layout_text_;
1115 if (!text_elided_)
1116 display_text_.clear();
[email protected]bec929c2012-03-02 06:23:501117}
1118
[email protected]eced6cb2013-09-16 20:16:201119const BreakList<size_t>& RenderText::GetLineBreaks() {
1120 if (line_breaks_.max() != 0)
1121 return line_breaks_;
1122
oshima0a243a42015-02-14 04:46:311123 const base::string16& layout_text = GetDisplayText();
[email protected]eced6cb2013-09-16 20:16:201124 const size_t text_length = layout_text.length();
1125 line_breaks_.SetValue(0);
1126 line_breaks_.SetMax(text_length);
1127 base::i18n::BreakIterator iter(layout_text,
1128 base::i18n::BreakIterator::BREAK_LINE);
1129 const bool success = iter.Init();
1130 DCHECK(success);
1131 if (success) {
1132 do {
1133 line_breaks_.ApplyValue(iter.pos(), Range(iter.pos(), text_length));
1134 } while (iter.Advance());
1135 }
1136 return line_breaks_;
1137}
1138
[email protected]ccfa43f02013-02-01 04:42:171139void RenderText::ApplyCompositionAndSelectionStyles() {
1140 // Save the underline and color breaks to undo the temporary styles later.
1141 DCHECK(!composition_and_selection_styles_applied_);
1142 saved_colors_ = colors_;
1143 saved_underlines_ = styles_[UNDERLINE];
1144
1145 // Apply an underline to the composition range in |underlines|.
1146 if (composition_range_.IsValid() && !composition_range_.is_empty())
1147 styles_[UNDERLINE].ApplyValue(true, composition_range_);
1148
1149 // Apply the selected text color to the [un-reversed] selection range.
[email protected]08726d5e2013-09-24 21:52:551150 if (!selection().is_empty() && focused()) {
[email protected]3dfe5c52013-09-18 22:01:221151 const Range range(selection().GetMin(), selection().GetMax());
[email protected]ccfa43f02013-02-01 04:42:171152 colors_.ApplyValue(selection_color_, range);
[email protected]8e42ba22011-08-04 21:47:081153 }
[email protected]ccfa43f02013-02-01 04:42:171154 composition_and_selection_styles_applied_ = true;
1155}
1156
1157void RenderText::UndoCompositionAndSelectionStyles() {
1158 // Restore the underline and color breaks to undo the temporary styles.
1159 DCHECK(composition_and_selection_styles_applied_);
1160 colors_ = saved_colors_;
1161 styles_[UNDERLINE] = saved_underlines_;
1162 composition_and_selection_styles_applied_ = false;
[email protected]ff44d712011-07-25 08:42:521163}
1164
[email protected]0d717602011-08-30 06:21:141165Point RenderText::ToTextPoint(const Point& point) {
[email protected]eced6cb2013-09-16 20:16:201166 return point - GetLineOffset(0);
1167 // TODO(ckocagil): Convert multiline view space points to text space.
[email protected]0d717602011-08-30 06:21:141168}
1169
1170Point RenderText::ToViewPoint(const Point& point) {
[email protected]eced6cb2013-09-16 20:16:201171 if (!multiline())
1172 return point + GetLineOffset(0);
1173
1174 // TODO(ckocagil): Traverse individual line segments for RTL support.
1175 DCHECK(!lines_.empty());
1176 int x = point.x();
1177 size_t line = 0;
1178 for (; line < lines_.size() && x > lines_[line].size.width(); ++line)
1179 x -= lines_[line].size.width();
1180 return Point(x, point.y()) + GetLineOffset(line);
[email protected]f0ed8a2f2012-01-24 17:45:591181}
1182
[email protected]eced6cb2013-09-16 20:16:201183std::vector<Rect> RenderText::TextBoundsToViewBounds(const Range& x) {
1184 std::vector<Rect> rects;
1185
1186 if (!multiline()) {
1187 rects.push_back(Rect(ToViewPoint(Point(x.GetMin(), 0)),
1188 Size(x.length(), GetStringSize().height())));
1189 return rects;
1190 }
1191
1192 EnsureLayout();
1193
1194 // Each line segment keeps its position in text coordinates. Traverse all line
1195 // segments and if the segment intersects with the given range, add the view
1196 // rect corresponding to the intersection to |rects|.
1197 for (size_t line = 0; line < lines_.size(); ++line) {
1198 int line_x = 0;
1199 const Vector2d offset = GetLineOffset(line);
1200 for (size_t i = 0; i < lines_[line].segments.size(); ++i) {
1201 const internal::LineSegment* segment = &lines_[line].segments[i];
xdai08820ef2015-06-23 20:22:031202 const Range intersection = segment->x_range.Intersect(x).Ceil();
[email protected]eced6cb2013-09-16 20:16:201203 if (!intersection.is_empty()) {
1204 Rect rect(line_x + intersection.start() - segment->x_range.start(),
1205 0, intersection.length(), lines_[line].size.height());
1206 rects.push_back(rect + offset);
1207 }
1208 line_x += segment->x_range.length();
1209 }
1210 }
1211
1212 return rects;
1213}
1214
[email protected]b183dd12014-07-21 07:55:511215HorizontalAlignment RenderText::GetCurrentHorizontalAlignment() {
1216 if (horizontal_alignment_ != ALIGN_TO_HEAD)
1217 return horizontal_alignment_;
oshima0a243a42015-02-14 04:46:311218 return GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT ?
1219 ALIGN_RIGHT : ALIGN_LEFT;
[email protected]b183dd12014-07-21 07:55:511220}
1221
[email protected]eced6cb2013-09-16 20:16:201222Vector2d RenderText::GetAlignmentOffset(size_t line_number) {
oshima403e56062015-02-24 03:31:061223 // TODO(ckocagil): Enable |lines_| usage on RenderTextMac.
mukai1dfc595f2015-03-11 20:30:051224 if (MultilineSupported() && multiline_)
oshimae008451a2015-02-25 00:43:391225 DCHECK_LT(line_number, lines_.size());
[email protected]9bb784382013-05-20 20:40:331226 Vector2d offset;
[email protected]b183dd12014-07-21 07:55:511227 HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment();
1228 if (horizontal_alignment != ALIGN_LEFT) {
oshimae008451a2015-02-25 00:43:391229 const int width = multiline_ ?
1230 std::ceil(lines_[line_number].size.width()) +
1231 (cursor_enabled_ ? 1 : 0) :
1232 GetContentWidth();
[email protected]eced6cb2013-09-16 20:16:201233 offset.set_x(display_rect().width() - width);
[email protected]b183dd12014-07-21 07:55:511234 // Put any extra margin pixel on the left to match legacy behavior.
1235 if (horizontal_alignment == ALIGN_CENTER)
1236 offset.set_x((offset.x() + 1) / 2);
[email protected]9bb784382013-05-20 20:40:331237 }
[email protected]6c0078f52013-11-21 23:43:531238
1239 // Vertically center the text.
1240 if (multiline_) {
1241 const int text_height = lines_.back().preceding_heights +
1242 lines_.back().size.height();
1243 offset.set_y((display_rect_.height() - text_height) / 2);
1244 } else {
oshima0a243a42015-02-14 04:46:311245 offset.set_y(GetBaseline() - GetDisplayTextBaseline());
[email protected]6c0078f52013-11-21 23:43:531246 }
1247
[email protected]9bb784382013-05-20 20:40:331248 return offset;
[email protected]67b981562011-12-09 00:35:051249}
1250
[email protected]f0ed8a2f2012-01-24 17:45:591251void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
[email protected]f3ce6212014-06-05 22:42:081252 const int width = display_rect().width();
ckocagil4bd060c2014-12-08 22:32:351253 if (multiline() || elide_behavior_ != FADE_TAIL || GetContentWidth() <= width)
[email protected]f0ed8a2f2012-01-24 17:45:591254 return;
[email protected]f308e5b12012-01-17 19:09:181255
[email protected]f3ce6212014-06-05 22:42:081256 const int gradient_width = CalculateFadeGradientWidth(font_list(), width);
[email protected]f308e5b12012-01-17 19:09:181257 if (gradient_width == 0)
[email protected]f0ed8a2f2012-01-24 17:45:591258 return;
[email protected]f308e5b12012-01-17 19:09:181259
[email protected]b183dd12014-07-21 07:55:511260 HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment();
[email protected]ccfa43f02013-02-01 04:42:171261 Rect solid_part = display_rect();
1262 Rect left_part;
1263 Rect right_part;
[email protected]b183dd12014-07-21 07:55:511264 if (horizontal_alignment != ALIGN_LEFT) {
[email protected]f308e5b12012-01-17 19:09:181265 left_part = solid_part;
1266 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
1267 solid_part.Inset(gradient_width, 0, 0, 0);
1268 }
[email protected]b183dd12014-07-21 07:55:511269 if (horizontal_alignment != ALIGN_RIGHT) {
[email protected]f308e5b12012-01-17 19:09:181270 right_part = solid_part;
1271 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
1272 solid_part.Inset(0, 0, gradient_width, 0);
1273 }
1274
[email protected]ccfa43f02013-02-01 04:42:171275 Rect text_rect = display_rect();
[email protected]eced6cb2013-09-16 20:16:201276 text_rect.Inset(GetAlignmentOffset(0).x(), 0, 0, 0);
[email protected]f308e5b12012-01-17 19:09:181277
[email protected]ccfa43f02013-02-01 04:42:171278 // TODO(msw): Use the actual text colors corresponding to each faded part.
tomhudson2b45cf92016-03-31 14:10:151279 renderer->SetShader(
tmoniuszkod6fa6e42015-12-04 12:06:211280 CreateFadeShader(font_list(), text_rect, left_part, right_part,
tomhudson2b45cf92016-03-31 14:10:151281 SkColorSetA(colors_.breaks().front().second, 0xff)));
[email protected]f308e5b12012-01-17 19:09:181282}
1283
[email protected]0834e1b12012-04-11 02:23:561284void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) {
tomhudson53973942016-03-31 17:05:001285 renderer->SetDrawLooper(CreateShadowDrawLooper(shadows_));
[email protected]0834e1b12012-04-11 02:23:561286}
1287
oshima0a243a42015-02-14 04:46:311288base::i18n::TextDirection RenderText::GetTextDirection(
1289 const base::string16& text) {
1290 if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) {
1291 switch (directionality_mode_) {
1292 case DIRECTIONALITY_FROM_TEXT:
1293 // Derive the direction from the display text, which differs from text()
1294 // in the case of obscured (password) textfields.
1295 text_direction_ =
1296 base::i18n::GetFirstStrongCharacterDirection(text);
1297 break;
1298 case DIRECTIONALITY_FROM_UI:
1299 text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT :
1300 base::i18n::LEFT_TO_RIGHT;
1301 break;
1302 case DIRECTIONALITY_FORCE_LTR:
1303 text_direction_ = base::i18n::LEFT_TO_RIGHT;
1304 break;
1305 case DIRECTIONALITY_FORCE_RTL:
1306 text_direction_ = base::i18n::RIGHT_TO_LEFT;
1307 break;
1308 default:
1309 NOTREACHED();
1310 }
1311 }
1312
1313 return text_direction_;
1314}
1315
mukaifea1a112015-03-09 22:06:561316size_t RenderText::TextIndexToGivenTextIndex(const base::string16& given_text,
mgiuca859e3362015-06-03 08:34:131317 size_t index) const {
mukaifea1a112015-03-09 22:06:561318 DCHECK(given_text == layout_text() || given_text == display_text());
1319 DCHECK_LE(index, text().length());
1320 ptrdiff_t i = obscured() ? UTF16IndexToOffset(text(), 0, index) : index;
1321 CHECK_GE(i, 0);
1322 // Clamp indices to the length of the given layout or display text.
1323 return std::min<size_t>(given_text.length(), i);
1324}
1325
dschuyler92bc0de2015-03-20 03:24:211326void RenderText::UpdateStyleLengths() {
1327 const size_t text_length = text_.length();
1328 colors_.SetMax(text_length);
1329 baselines_.SetMax(text_length);
mboc998e8902016-06-02 11:40:351330 weights_.SetMax(text_length);
dschuyler92bc0de2015-03-20 03:24:211331 for (size_t style = 0; style < NUM_TEXT_STYLES; ++style)
1332 styles_[style].SetMax(text_length);
1333}
1334
[email protected]4712ae42013-04-27 04:55:081335// static
[email protected]3dfe5c52013-09-18 22:01:221336bool RenderText::RangeContainsCaret(const Range& range,
[email protected]4712ae42013-04-27 04:55:081337 size_t caret_pos,
1338 LogicalCursorDirection caret_affinity) {
1339 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9).
1340 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ?
1341 caret_pos - 1 : caret_pos + 1;
[email protected]3dfe5c52013-09-18 22:01:221342 return range.Contains(Range(caret_pos, adjacent));
[email protected]4712ae42013-04-27 04:55:081343}
1344
[email protected]0d717602011-08-30 06:21:141345void RenderText::MoveCursorTo(size_t position, bool select) {
1346 size_t cursor = std::min(position, text().length());
[email protected]3dbc8a62014-05-02 17:08:171347 if (IsValidCursorIndex(cursor))
[email protected]d9990212012-03-13 01:09:311348 SetSelectionModel(SelectionModel(
[email protected]3dfe5c52013-09-18 22:01:221349 Range(select ? selection().start() : cursor, cursor),
[email protected]d9990212012-03-13 01:09:311350 (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD));
[email protected]8e42ba22011-08-04 21:47:081351}
1352
oshima0a243a42015-02-14 04:46:311353void RenderText::OnTextAttributeChanged() {
[email protected]fecf9f72013-06-29 13:02:471354 layout_text_.clear();
oshima0a243a42015-02-14 04:46:311355 display_text_.clear();
mukaifea1a112015-03-09 22:06:561356 text_elided_ = false;
[email protected]eced6cb2013-09-16 20:16:201357 line_breaks_.SetMax(0);
[email protected]6bc200d2012-09-28 21:01:511358
[email protected]fecf9f72013-06-29 13:02:471359 if (obscured_) {
1360 size_t obscured_text_length =
[email protected]f3ce6212014-06-05 22:42:081361 static_cast<size_t>(UTF16IndexToOffset(text_, 0, text_.length()));
[email protected]fecf9f72013-06-29 13:02:471362 layout_text_.assign(obscured_text_length, kPasswordReplacementChar);
[email protected]01e06a32013-06-07 12:41:331363
[email protected]fecf9f72013-06-29 13:02:471364 if (obscured_reveal_index_ >= 0 &&
1365 obscured_reveal_index_ < static_cast<int>(text_.length())) {
1366 // Gets the index range in |text_| to be revealed.
1367 size_t start = obscured_reveal_index_;
1368 U16_SET_CP_START(text_.data(), 0, start);
1369 size_t end = start;
1370 UChar32 unused_char;
1371 U16_NEXT(text_.data(), end, text_.length(), unused_char);
[email protected]01e06a32013-06-07 12:41:331372
[email protected]fecf9f72013-06-29 13:02:471373 // Gets the index in |layout_text_| to be replaced.
1374 const size_t cp_start =
[email protected]f3ce6212014-06-05 22:42:081375 static_cast<size_t>(UTF16IndexToOffset(text_, 0, start));
[email protected]fecf9f72013-06-29 13:02:471376 if (layout_text_.length() > cp_start)
1377 layout_text_.replace(cp_start, 1, text_.substr(start, end - start));
1378 }
[email protected]61f99082014-02-24 02:17:491379 } else {
1380 layout_text_ = text_;
[email protected]fecf9f72013-06-29 13:02:471381 }
1382
[email protected]61f99082014-02-24 02:17:491383 const base::string16& text = layout_text_;
[email protected]fecf9f72013-06-29 13:02:471384 if (truncate_length_ > 0 && truncate_length_ < text.length()) {
1385 // Truncate the text at a valid character break and append an ellipsis.
1386 icu::StringCharacterIterator iter(text.c_str());
[email protected]8d936142014-07-10 21:40:371387 // Respect ELIDE_HEAD and ELIDE_MIDDLE preferences during truncation.
1388 if (elide_behavior_ == ELIDE_HEAD) {
1389 iter.setIndex32(text.length() - truncate_length_ + 1);
1390 layout_text_.assign(kEllipsisUTF16 + text.substr(iter.getIndex()));
1391 } else if (elide_behavior_ == ELIDE_MIDDLE) {
1392 iter.setIndex32(truncate_length_ / 2);
1393 const size_t ellipsis_start = iter.getIndex();
1394 iter.setIndex32(text.length() - (truncate_length_ / 2));
1395 const size_t ellipsis_end = iter.getIndex();
1396 DCHECK_LE(ellipsis_start, ellipsis_end);
1397 layout_text_.assign(text.substr(0, ellipsis_start) + kEllipsisUTF16 +
1398 text.substr(ellipsis_end));
1399 } else {
1400 iter.setIndex32(truncate_length_ - 1);
1401 layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16);
1402 }
[email protected]01e06a32013-06-07 12:41:331403 }
[email protected]102402c2014-07-09 19:53:381404 static const base::char16 kNewline[] = { '\n', 0 };
1405 static const base::char16 kNewlineSymbol[] = { 0x2424, 0 };
mukai1dfc595f2015-03-11 20:30:051406 if (!multiline_ && replace_newline_chars_with_symbols_)
[email protected]102402c2014-07-09 19:53:381407 base::ReplaceChars(layout_text_, kNewline, kNewlineSymbol, &layout_text_);
1408
oshima0a243a42015-02-14 04:46:311409 OnLayoutTextAttributeChanged(true);
[email protected]ec2ce922014-01-02 23:06:471410}
1411
[email protected]8d936142014-07-10 21:40:371412base::string16 RenderText::Elide(const base::string16& text,
oshima0a243a42015-02-14 04:46:311413 float text_width,
[email protected]8d936142014-07-10 21:40:371414 float available_width,
1415 ElideBehavior behavior) {
1416 if (available_width <= 0 || text.empty())
1417 return base::string16();
1418 if (behavior == ELIDE_EMAIL)
1419 return ElideEmail(text, available_width);
krba468b18b2016-05-17 21:00:351420 if (text_width > 0 && text_width <= available_width)
oshima0a243a42015-02-14 04:46:311421 return text;
1422
1423 TRACE_EVENT0("ui", "RenderText::Elide");
[email protected]8d936142014-07-10 21:40:371424
[email protected]ec2ce922014-01-02 23:06:471425 // Create a RenderText copy with attributes that affect the rendering width.
krba468b18b2016-05-17 21:00:351426 std::unique_ptr<RenderText> render_text = CreateInstanceOfSameStyle(text);
1427 render_text->UpdateStyleLengths();
1428 if (text_width == 0)
oshima0a243a42015-02-14 04:46:311429 text_width = render_text->GetContentWidthF();
oshima0a243a42015-02-14 04:46:311430 if (text_width <= available_width)
[email protected]ec2ce922014-01-02 23:06:471431 return text;
1432
[email protected]8d936142014-07-10 21:40:371433 const base::string16 ellipsis = base::string16(kEllipsisUTF16);
1434 const bool insert_ellipsis = (behavior != TRUNCATE);
1435 const bool elide_in_middle = (behavior == ELIDE_MIDDLE);
1436 const bool elide_at_beginning = (behavior == ELIDE_HEAD);
oshima0a243a42015-02-14 04:46:311437
1438 if (insert_ellipsis) {
1439 render_text->SetText(ellipsis);
1440 const float ellipsis_width = render_text->GetContentWidthF();
1441 if (ellipsis_width > available_width)
1442 return base::string16();
1443 }
1444
[email protected]8d936142014-07-10 21:40:371445 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning);
[email protected]ec2ce922014-01-02 23:06:471446
[email protected]ec2ce922014-01-02 23:06:471447 // Use binary search to compute the elided text.
1448 size_t lo = 0;
1449 size_t hi = text.length() - 1;
oshima0a243a42015-02-14 04:46:311450 const base::i18n::TextDirection text_direction = GetTextDirection(text);
[email protected]ec2ce922014-01-02 23:06:471451 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
tmoniuszko4757aae2014-10-16 07:37:271452 // Restore colors. They will be truncated to size by SetText.
[email protected]ec2ce922014-01-02 23:06:471453 render_text->colors_ = colors_;
[email protected]8d936142014-07-10 21:40:371454 base::string16 new_text =
1455 slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL);
[email protected]ec2ce922014-01-02 23:06:471456 render_text->SetText(new_text);
1457
1458 // This has to be an additional step so that the ellipsis is rendered with
1459 // same style as trailing part of the text.
[email protected]8d936142014-07-10 21:40:371460 if (insert_ellipsis && behavior == ELIDE_TAIL) {
[email protected]ec2ce922014-01-02 23:06:471461 // When ellipsis follows text whose directionality is not the same as that
1462 // of the whole text, it will be rendered with the directionality of the
1463 // whole text. Since we want ellipsis to indicate continuation of the
1464 // preceding text, we force the directionality of ellipsis to be same as
1465 // the preceding text using LTR or RTL markers.
[email protected]ec2ce922014-01-02 23:06:471466 base::i18n::TextDirection trailing_text_direction =
1467 base::i18n::GetLastStrongCharacterDirection(new_text);
1468 new_text.append(ellipsis);
[email protected]61f99082014-02-24 02:17:491469 if (trailing_text_direction != text_direction) {
[email protected]ec2ce922014-01-02 23:06:471470 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
1471 new_text += base::i18n::kLeftToRightMark;
1472 else
1473 new_text += base::i18n::kRightToLeftMark;
1474 }
1475 render_text->SetText(new_text);
1476 }
1477
dschuylerfcd76912015-03-11 01:40:111478 // Restore styles and baselines without breaking multi-character graphemes.
tmoniuszko4757aae2014-10-16 07:37:271479 render_text->styles_ = styles_;
dschuylerfcd76912015-03-11 01:40:111480 for (size_t style = 0; style < NUM_TEXT_STYLES; ++style)
mgiuca859e3362015-06-03 08:34:131481 RestoreBreakList(render_text.get(), &render_text->styles_[style]);
1482 RestoreBreakList(render_text.get(), &render_text->baselines_);
mboc998e8902016-06-02 11:40:351483 render_text->weights_ = weights_;
1484 RestoreBreakList(render_text.get(), &render_text->weights_);
tmoniuszko4757aae2014-10-16 07:37:271485
[email protected]ec2ce922014-01-02 23:06:471486 // We check the width of the whole desired string at once to ensure we
1487 // handle kerning/ligatures/etc. correctly.
ckocagil4bd060c2014-12-08 22:32:351488 const float guess_width = render_text->GetContentWidthF();
[email protected]8d936142014-07-10 21:40:371489 if (guess_width == available_width)
[email protected]ec2ce922014-01-02 23:06:471490 break;
[email protected]8d936142014-07-10 21:40:371491 if (guess_width > available_width) {
[email protected]ec2ce922014-01-02 23:06:471492 hi = guess - 1;
[email protected]8d936142014-07-10 21:40:371493 // Move back on the loop terminating condition when the guess is too wide.
[email protected]ec2ce922014-01-02 23:06:471494 if (hi < lo)
1495 lo = hi;
1496 } else {
1497 lo = guess + 1;
1498 }
1499 }
1500
1501 return render_text->text();
[email protected]6bc200d2012-09-28 21:01:511502}
1503
[email protected]8d936142014-07-10 21:40:371504base::string16 RenderText::ElideEmail(const base::string16& email,
1505 float available_width) {
1506 // The returned string will have at least one character besides the ellipsis
1507 // on either side of '@'; if that's impossible, a single ellipsis is returned.
1508 // If possible, only the username is elided. Otherwise, the domain is elided
1509 // in the middle, splitting available width equally with the elided username.
1510 // If the username is short enough that it doesn't need half the available
1511 // width, the elided domain will occupy that extra width.
1512
1513 // Split the email into its local-part (username) and domain-part. The email
1514 // spec allows for @ symbols in the username under some special requirements,
1515 // but not in the domain part, so splitting at the last @ symbol is safe.
1516 const size_t split_index = email.find_last_of('@');
1517 DCHECK_NE(split_index, base::string16::npos);
1518 base::string16 username = email.substr(0, split_index);
1519 base::string16 domain = email.substr(split_index + 1);
1520 DCHECK(!username.empty());
1521 DCHECK(!domain.empty());
1522
1523 // Subtract the @ symbol from the available width as it is mandatory.
1524 const base::string16 kAtSignUTF16 = base::ASCIIToUTF16("@");
1525 available_width -= GetStringWidthF(kAtSignUTF16, font_list());
1526
1527 // Check whether eliding the domain is necessary: if eliding the username
1528 // is sufficient, the domain will not be elided.
1529 const float full_username_width = GetStringWidthF(username, font_list());
1530 const float available_domain_width = available_width -
1531 std::min(full_username_width,
1532 GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16, font_list()));
1533 if (GetStringWidthF(domain, font_list()) > available_domain_width) {
1534 // Elide the domain so that it only takes half of the available width.
1535 // Should the username not need all the width available in its half, the
1536 // domain will occupy the leftover width.
1537 // If |desired_domain_width| is greater than |available_domain_width|: the
1538 // minimal username elision allowed by the specifications will not fit; thus
1539 // |desired_domain_width| must be <= |available_domain_width| at all cost.
1540 const float desired_domain_width =
1541 std::min<float>(available_domain_width,
1542 std::max<float>(available_width - full_username_width,
1543 available_width / 2));
oshima0a243a42015-02-14 04:46:311544 domain = Elide(domain, 0, desired_domain_width, ELIDE_MIDDLE);
[email protected]8d936142014-07-10 21:40:371545 // Failing to elide the domain such that at least one character remains
1546 // (other than the ellipsis itself) remains: return a single ellipsis.
1547 if (domain.length() <= 1U)
1548 return base::string16(kEllipsisUTF16);
1549 }
1550
1551 // Fit the username in the remaining width (at this point the elided username
1552 // is guaranteed to fit with at least one character remaining given all the
1553 // precautions taken earlier).
1554 available_width -= GetStringWidthF(domain, font_list());
oshima0a243a42015-02-14 04:46:311555 username = Elide(username, 0, available_width, ELIDE_TAIL);
[email protected]8d936142014-07-10 21:40:371556 return username + kAtSignUTF16 + domain;
1557}
1558
[email protected]f6aaa0f2011-08-11 07:05:461559void RenderText::UpdateCachedBoundsAndOffset() {
1560 if (cached_bounds_and_offset_valid_)
1561 return;
[email protected]f0ed8a2f2012-01-24 17:45:591562
[email protected]eced6cb2013-09-16 20:16:201563 // TODO(ckocagil): Add support for scrolling multiline text.
1564
[email protected]ff0d8532014-07-23 09:36:591565 int delta_x = 0;
1566
1567 if (cursor_enabled()) {
1568 // When cursor is enabled, ensure it is visible. For this, set the valid
1569 // flag true and calculate the current cursor bounds using the stale
1570 // |display_offset_|. Then calculate the change in offset needed to move the
1571 // cursor into the visible area.
1572 cached_bounds_and_offset_valid_ = true;
msw13ce21b2016-06-22 03:06:561573 cursor_bounds_ = GetCursorBounds(selection_model_, true);
[email protected]f0ed8a2f2012-01-24 17:45:591574
[email protected]ff0d8532014-07-23 09:36:591575 // TODO(bidi): Show RTL glyphs at the cursor position for ALIGN_LEFT, etc.
1576 if (cursor_bounds_.right() > display_rect_.right())
1577 delta_x = display_rect_.right() - cursor_bounds_.right();
1578 else if (cursor_bounds_.x() < display_rect_.x())
1579 delta_x = display_rect_.x() - cursor_bounds_.x();
[email protected]8e42ba22011-08-04 21:47:081580 }
[email protected]f0ed8a2f2012-01-24 17:45:591581
[email protected]ff0d8532014-07-23 09:36:591582 SetDisplayOffset(display_offset_.x() + delta_x);
[email protected]ff44d712011-07-25 08:42:521583}
1584
[email protected]6002a4f32011-11-30 10:18:421585void RenderText::DrawSelection(Canvas* canvas) {
oshima0a243a42015-02-14 04:46:311586 for (const Rect& s : GetSubstringBounds(selection()))
1587 canvas->FillRect(s, selection_background_focused_color_);
[email protected]6002a4f32011-11-30 10:18:421588}
1589
[email protected]ff44d712011-07-25 08:42:521590} // namespace gfx