[email protected] | 8d901a8 | 2012-01-04 19:41:30 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 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 | |
avi | 51ba3e69 | 2015-12-26 17:30:50 | [diff] [blame] | 7 | #include <limits.h> |
| 8 | |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 9 | #include <algorithm> |
[email protected] | 56e5057 | 2013-11-01 14:25:31 | [diff] [blame] | 10 | #include <climits> |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 11 | |
[email protected] | 5284802 | 2014-05-22 19:01:07 | [diff] [blame] | 12 | #include "base/command_line.h" |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 13 | #include "base/i18n/break_iterator.h" |
| 14 | #include "base/logging.h" |
| 15 | #include "base/stl_util.h" |
[email protected] | 102402c | 2014-07-09 19:53:38 | [diff] [blame] | 16 | #include "base/strings/string_util.h" |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 17 | #include "base/strings/utf_string_conversions.h" |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 18 | #include "base/trace_event/trace_event.h" |
avi | c89eb8d4 | 2015-12-23 08:08:18 | [diff] [blame] | 19 | #include "build/build_config.h" |
[email protected] | 8bbf619 | 2013-07-18 11:14:04 | [diff] [blame] | 20 | #include "third_party/icu/source/common/unicode/rbbi.h" |
| 21 | #include "third_party/icu/source/common/unicode/utf16.h" |
senorblanco | cf6533a5 | 2015-08-14 20:09:19 | [diff] [blame] | 22 | #include "third_party/skia/include/core/SkDrawLooper.h" |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 23 | #include "third_party/skia/include/core/SkFontStyle.h" |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 24 | #include "third_party/skia/include/core/SkTypeface.h" |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 25 | #include "third_party/skia/include/effects/SkGradientShader.h" |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 26 | #include "ui/gfx/canvas.h" |
tfarina | 655f81d | 2014-12-23 02:38:50 | [diff] [blame] | 27 | #include "ui/gfx/geometry/insets.h" |
pkasting | bc91db5 | 2014-10-21 20:35:42 | [diff] [blame] | 28 | #include "ui/gfx/geometry/safe_integer_conversions.h" |
erikchen | 98ef615 | 2015-12-09 00:27:22 | [diff] [blame] | 29 | #include "ui/gfx/platform_font.h" |
[email protected] | 5284802 | 2014-05-22 19:01:07 | [diff] [blame] | 30 | #include "ui/gfx/render_text_harfbuzz.h" |
| 31 | #include "ui/gfx/scoped_canvas.h" |
[email protected] | 40be201 | 2012-05-14 22:34:45 | [diff] [blame] | 32 | #include "ui/gfx/skia_util.h" |
[email protected] | 5284802 | 2014-05-22 19:01:07 | [diff] [blame] | 33 | #include "ui/gfx/switches.h" |
[email protected] | dbb97ba | 2013-09-09 22:15:25 | [diff] [blame] | 34 | #include "ui/gfx/text_elider.h" |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 35 | #include "ui/gfx/text_utils.h" |
[email protected] | bde885b | 2013-09-12 20:55:53 | [diff] [blame] | 36 | #include "ui/gfx/utf16_indexing.h" |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 37 | |
derat | a55b0115d | 2015-02-03 01:59:08 | [diff] [blame] | 38 | #if defined(OS_MACOSX) |
erikchen | 98ef615 | 2015-12-09 00:27:22 | [diff] [blame] | 39 | #include "third_party/skia/include/ports/SkTypeface_mac.h" |
derat | a55b0115d | 2015-02-03 01:59:08 | [diff] [blame] | 40 | #include "ui/gfx/render_text_mac.h" |
| 41 | #endif // defined(OS_MACOSX) |
| 42 | |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 43 | namespace gfx { |
| 44 | |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 45 | namespace { |
| 46 | |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 47 | // 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] | 476dafb | 2013-12-03 00:39:26 | [diff] [blame] | 50 | const base::char16 kPasswordReplacementChar = '*'; |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 51 | |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 52 | // Default color used for the text and cursor. |
| 53 | const SkColor kDefaultColor = SK_ColorBLACK; |
[email protected] | 311a09d | 2012-05-14 20:38:17 | [diff] [blame] | 54 | |
| 55 | // Default color used for drawing selection background. |
| 56 | const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY; |
| 57 | |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 58 | // Fraction of the text size to lower a strike through below the baseline. |
| 59 | const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21); |
| 60 | // Fraction of the text size to lower an underline below the baseline. |
| 61 | const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); |
| 62 | // Fraction of the text size to use for a strike through or under-line. |
| 63 | const SkScalar kLineThickness = (SK_Scalar1 / 18); |
| 64 | // Fraction of the text size to use for a top margin of a diagonal strike. |
| 65 | const SkScalar kDiagonalStrikeMarginOffset = (SK_Scalar1 / 4); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 66 | |
[email protected] | 56e5057 | 2013-11-01 14:25:31 | [diff] [blame] | 67 | // Invalid value of baseline. Assigning this value to |baseline_| causes |
| 68 | // re-calculation of baseline. |
| 69 | const int kInvalidBaseline = INT_MAX; |
| 70 | |
| 71 | // Returns the baseline, with which the text best appears vertically centered. |
| 72 | int 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] | a61c6b23c | 2013-11-09 19:22:07 | [diff] [blame] | 84 | // 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); |
pkasting | 477c647 | 2015-02-20 03:08:48 | [diff] [blame] | 90 | const int baseline_shift = space / 2 - internal_leading; |
[email protected] | 56e5057 | 2013-11-01 14:25:31 | [diff] [blame] | 91 | return baseline + std::max(min_shift, std::min(max_shift, baseline_shift)); |
| 92 | } |
| 93 | |
tmoniuszko | d6fa6e4 | 2015-12-04 12:06:21 | [diff] [blame] | 94 | int round(float value) { |
| 95 | return static_cast<int>(floor(value + 0.5f)); |
| 96 | } |
| 97 | |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 98 | // Given |font| and |display_width|, returns the width of the fade gradient. |
[email protected] | 6300df1 | 2013-12-29 07:54:21 | [diff] [blame] | 99 | int CalculateFadeGradientWidth(const FontList& font_list, int display_width) { |
tmoniuszko | d6fa6e4 | 2015-12-04 12:06:21 | [diff] [blame] | 100 | // 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] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 106 | } |
| 107 | |
| 108 | // Appends to |positions| and |colors| values corresponding to the fade over |
| 109 | // |fade_rect| from color |c0| to color |c1|. |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 110 | void AddFadeEffect(const Rect& text_rect, |
| 111 | const Rect& fade_rect, |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 112 | 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. |
tomhudson | 2b45cf9 | 2016-03-31 14:10:15 | [diff] [blame] | 133 | sk_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) { |
mboc | f700cdb9 | 2016-03-30 19:34:33 | [diff] [blame] | 138 | // 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 | |
tmoniuszko | d6fa6e4 | 2015-12-04 12:06:21 | [diff] [blame] | 142 | // 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] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 153 | 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 | |
pkasting | 07f4446 | 2015-11-03 21:00:21 | [diff] [blame] | 170 | const SkPoint points[2] = { PointToSkPoint(text_rect.origin()), |
| 171 | PointToSkPoint(text_rect.top_right()) }; |
tomhudson | 2b45cf9 | 2016-03-31 14:10:15 | [diff] [blame] | 172 | return |
| 173 | SkGradientShader::MakeLinear(&points[0], &colors[0], &positions[0], |
| 174 | colors.size(), SkShader::kClamp_TileMode); |
[email protected] | 959e190 | 2012-03-03 02:02:17 | [diff] [blame] | 175 | } |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 176 | |
[email protected] | 6b2e237 | 2014-07-11 21:21:18 | [diff] [blame] | 177 | // Converts a FontRenderParams::Hinting value to the corresponding |
| 178 | // SkPaint::Hinting value. |
| 179 | SkPaint::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 | |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 190 | // 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. |
| 193 | template <typename T> |
mgiuca | 859e336 | 2015-06-03 08:34:13 | [diff] [blame] | 194 | void RestoreBreakList(RenderText* render_text, BreakList<T>* break_list) { |
| 195 | break_list->SetMax(render_text->text().length()); |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 196 | Range range; |
mgiuca | 859e336 | 2015-06-03 08:34:13 | [diff] [blame] | 197 | 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() && |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 201 | !render_text->IsValidCursorIndex(range.end())) { |
| 202 | range.set_end( |
| 203 | render_text->IndexOfAdjacentGrapheme(range.end(), CURSOR_FORWARD)); |
mgiuca | 859e336 | 2015-06-03 08:34:13 | [diff] [blame] | 204 | break_list->ApplyValue(current_break->second, range); |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 205 | } |
| 206 | } |
| 207 | } |
| 208 | |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 209 | } // namespace |
| 210 | |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 211 | namespace internal { |
| 212 | |
[email protected] | e232601 | 2012-06-12 22:59:41 | [diff] [blame] | 213 | // Value of |underline_thickness_| that indicates that underline metrics have |
| 214 | // not been set explicitly. |
| 215 | const SkScalar kUnderlineMetricsNotSet = -1.0f; |
| 216 | |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 217 | SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas) |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 218 | : canvas_(canvas), |
| 219 | canvas_skia_(canvas->sk_canvas()), |
[email protected] | e232601 | 2012-06-12 22:59:41 | [diff] [blame] | 220 | underline_thickness_(kUnderlineMetricsNotSet), |
| 221 | underline_position_(0.0f) { |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 222 | 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] | 564e2e5 | 2013-12-19 01:44:21 | [diff] [blame] | 228 | paint_.setHinting(SkPaint::kNormal_Hinting); |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 229 | } |
| 230 | |
| 231 | SkiaTextRenderer::~SkiaTextRenderer() { |
| 232 | } |
| 233 | |
tomhudson | 5397394 | 2016-03-31 17:05:00 | [diff] [blame] | 234 | void SkiaTextRenderer::SetDrawLooper(sk_sp<SkDrawLooper> draw_looper) { |
| 235 | paint_.setLooper(std::move(draw_looper)); |
[email protected] | 0834e1b1 | 2012-04-11 02:23:56 | [diff] [blame] | 236 | } |
| 237 | |
[email protected] | cd9c65e | 2014-07-10 02:57:01 | [diff] [blame] | 238 | void SkiaTextRenderer::SetFontRenderParams(const FontRenderParams& params, |
mukai | 4b75bd78 | 2015-02-19 21:44:42 | [diff] [blame] | 239 | bool subpixel_rendering_suppressed) { |
| 240 | ApplyRenderParams(params, subpixel_rendering_suppressed, &paint_); |
[email protected] | 564e2e5 | 2013-12-19 01:44:21 | [diff] [blame] | 241 | } |
| 242 | |
fmalita | 6d979c279 | 2016-06-20 14:17:50 | [diff] [blame] | 243 | void SkiaTextRenderer::SetTypeface(sk_sp<SkTypeface> typeface) { |
| 244 | paint_.setTypeface(std::move(typeface)); |
[email protected] | cd8e552 | 2011-12-15 00:24:10 | [diff] [blame] | 245 | } |
| 246 | |
[email protected] | e232601 | 2012-06-12 22:59:41 | [diff] [blame] | 247 | void SkiaTextRenderer::SetTextSize(SkScalar size) { |
[email protected] | cd8e552 | 2011-12-15 00:24:10 | [diff] [blame] | 248 | paint_.setTextSize(size); |
| 249 | } |
| 250 | |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 251 | void SkiaTextRenderer::SetForegroundColor(SkColor foreground) { |
| 252 | paint_.setColor(foreground); |
| 253 | } |
| 254 | |
tomhudson | 2b45cf9 | 2016-03-31 14:10:15 | [diff] [blame] | 255 | void SkiaTextRenderer::SetShader(sk_sp<SkShader> shader) { |
| 256 | paint_.setShader(std::move(shader)); |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 257 | } |
| 258 | |
[email protected] | e232601 | 2012-06-12 22:59:41 | [diff] [blame] | 259 | void SkiaTextRenderer::SetUnderlineMetrics(SkScalar thickness, |
| 260 | SkScalar position) { |
| 261 | underline_thickness_ = thickness; |
| 262 | underline_position_ = position; |
| 263 | } |
| 264 | |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 265 | void SkiaTextRenderer::DrawPosText(const SkPoint* pos, |
avi | c89eb8d4 | 2015-12-23 08:08:18 | [diff] [blame] | 266 | const uint16_t* glyphs, |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 267 | size_t glyph_count) { |
[email protected] | 3bf21e1 | 2012-05-02 15:33:36 | [diff] [blame] | 268 | const size_t byte_length = glyph_count * sizeof(glyphs[0]); |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 269 | canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_); |
| 270 | } |
| 271 | |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 272 | void 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] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 278 | 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 | |
| 287 | void SkiaTextRenderer::EndDiagonalStrike() { |
| 288 | if (diagonal_) { |
| 289 | diagonal_->Draw(); |
| 290 | diagonal_.reset(); |
| 291 | } |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 292 | } |
[email protected] | 54d32cc | 2012-01-27 19:52:18 | [diff] [blame] | 293 | |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 294 | void SkiaTextRenderer::DrawUnderline(int x, int y, int width) { |
pkasting | bc91db5 | 2014-10-21 20:35:42 | [diff] [blame] | 295 | 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] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 299 | 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] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 303 | } |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 304 | canvas_skia_->drawRect(r, paint_); |
| 305 | } |
| 306 | |
| 307 | void 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); |
pkasting | bc91db5 | 2014-10-21 20:35:42 | [diff] [blame] | 311 | SkScalar x_scalar = SkIntToScalar(x); |
| 312 | const SkRect r = |
| 313 | SkRect::MakeLTRB(x_scalar, offset, x_scalar + width, offset + height); |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 314 | canvas_skia_->drawRect(r, paint_); |
| 315 | } |
| 316 | |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 317 | SkiaTextRenderer::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 | |
| 326 | SkiaTextRenderer::DiagonalStrike::~DiagonalStrike() { |
| 327 | } |
| 328 | |
| 329 | void SkiaTextRenderer::DiagonalStrike::AddPiece(int length, SkColor color) { |
| 330 | pieces_.push_back(Piece(length, color)); |
| 331 | total_length_ += length; |
| 332 | } |
| 333 | |
| 334 | void SkiaTextRenderer::DiagonalStrike::Draw() { |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 335 | const SkScalar text_size = paint_.getTextSize(); |
| 336 | const SkScalar offset = SkScalarMul(text_size, kDiagonalStrikeMarginOffset); |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 337 | 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] | 5284802 | 2014-05-22 19:01:07 | [diff] [blame] | 341 | const int clip_height = height + 2 * thickness; |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 342 | |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 343 | paint_.setAntiAlias(true); |
pkasting | bc91db5 | 2014-10-21 20:35:42 | [diff] [blame] | 344 | paint_.setStrokeWidth(SkIntToScalar(thickness)); |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 345 | |
| 346 | const bool clipped = pieces_.size() > 1; |
[email protected] | 24355dd | 2014-06-30 06:51:12 | [diff] [blame] | 347 | SkCanvas* sk_canvas = canvas_->sk_canvas(); |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 348 | int x = start_.x(); |
[email protected] | 24355dd | 2014-06-30 06:51:12 | [diff] [blame] | 349 | |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 350 | for (size_t i = 0; i < pieces_.size(); ++i) { |
| 351 | paint_.setColor(pieces_[i].second); |
| 352 | |
| 353 | if (clipped) { |
[email protected] | 24355dd | 2014-06-30 06:51:12 | [diff] [blame] | 354 | canvas_->Save(); |
[email protected] | 5284802 | 2014-05-22 19:01:07 | [diff] [blame] | 355 | sk_canvas->clipRect(RectToSkRect( |
[email protected] | 24355dd | 2014-06-30 06:51:12 | [diff] [blame] | 356 | Rect(x, end.y() - thickness, pieces_[i].first, clip_height))); |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 357 | } |
| 358 | |
| 359 | canvas_->DrawLine(start_, end, paint_); |
| 360 | |
[email protected] | 24355dd | 2014-06-30 06:51:12 | [diff] [blame] | 361 | if (clipped) |
| 362 | canvas_->Restore(); |
| 363 | |
[email protected] | 1c9b1cc | 2014-03-27 05:37:36 | [diff] [blame] | 364 | x += pieces_[i].first; |
| 365 | } |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 366 | } |
| 367 | |
| 368 | StyleIterator::StyleIterator(const BreakList<SkColor>& colors, |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 369 | const BreakList<BaselineStyle>& baselines, |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 370 | const BreakList<Font::Weight>& weights, |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 371 | const std::vector<BreakList<bool>>& styles) |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 372 | : colors_(colors), |
| 373 | baselines_(baselines), |
| 374 | weights_(weights), |
| 375 | styles_(styles) { |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 376 | color_ = colors_.breaks().begin(); |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 377 | baseline_ = baselines_.breaks().begin(); |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 378 | weight_ = weights_.breaks().begin(); |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 379 | for (size_t i = 0; i < styles_.size(); ++i) |
| 380 | style_.push_back(styles_[i].breaks().begin()); |
| 381 | } |
| 382 | |
| 383 | StyleIterator::~StyleIterator() {} |
| 384 | |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 385 | Range StyleIterator::GetRange() const { |
| 386 | Range range(colors_.GetRange(color_)); |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 387 | range = range.Intersect(baselines_.GetRange(baseline_)); |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 388 | range = range.Intersect(weights_.GetRange(weight_)); |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 389 | for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) |
| 390 | range = range.Intersect(styles_[i].GetRange(style_[i])); |
| 391 | return range; |
| 392 | } |
| 393 | |
| 394 | void StyleIterator::UpdatePosition(size_t position) { |
| 395 | color_ = colors_.GetBreak(position); |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 396 | baseline_ = baselines_.GetBreak(position); |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 397 | weight_ = weights_.GetBreak(position); |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 398 | for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) |
| 399 | style_[i] = styles_[i].GetBreak(position); |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 400 | } |
| 401 | |
xdai | 08820ef | 2015-06-23 20:22:03 | [diff] [blame] | 402 | LineSegment::LineSegment() : run(0) {} |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 403 | |
| 404 | LineSegment::~LineSegment() {} |
| 405 | |
| 406 | Line::Line() : preceding_heights(0), baseline(0) {} |
| 407 | |
vmpstr | 0ae825e7 | 2016-02-25 20:31:31 | [diff] [blame] | 408 | Line::Line(const Line& other) = default; |
| 409 | |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 410 | Line::~Line() {} |
| 411 | |
ckocagil | 04f49f4 | 2014-08-25 22:13:43 | [diff] [blame] | 412 | void ApplyRenderParams(const FontRenderParams& params, |
mukai | 4b75bd78 | 2015-02-19 21:44:42 | [diff] [blame] | 413 | bool subpixel_rendering_suppressed, |
ckocagil | 04f49f4 | 2014-08-25 22:13:43 | [diff] [blame] | 414 | SkPaint* paint) { |
| 415 | paint->setAntiAlias(params.antialiasing); |
mukai | 4b75bd78 | 2015-02-19 21:44:42 | [diff] [blame] | 416 | paint->setLCDRenderText(!subpixel_rendering_suppressed && |
ckocagil | 04f49f4 | 2014-08-25 22:13:43 | [diff] [blame] | 417 | 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] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 423 | } // namespace internal |
| 424 | |
[email protected] | 8e42ba2 | 2011-08-04 21:47:08 | [diff] [blame] | 425 | RenderText::~RenderText() { |
| 426 | } |
| 427 | |
mukai | 1dfc595f | 2015-03-11 20:30:05 | [diff] [blame] | 428 | // static |
[email protected] | 5284802 | 2014-05-22 19:01:07 | [diff] [blame] | 429 | RenderText* RenderText::CreateInstance() { |
andresantoso | 62fd79bb | 2014-12-09 01:33:57 | [diff] [blame] | 430 | #if defined(OS_MACOSX) |
derat | a55b0115d | 2015-02-03 01:59:08 | [diff] [blame] | 431 | static const bool use_native = |
avi | 6b10fd0 | 2014-12-23 05:51:23 | [diff] [blame] | 432 | !base::CommandLine::ForCurrentProcess()->HasSwitch( |
derat | a55b0115d | 2015-02-03 01:59:08 | [diff] [blame] | 433 | switches::kEnableHarfBuzzRenderText); |
| 434 | if (use_native) |
| 435 | return new RenderTextMac; |
| 436 | #endif // defined(OS_MACOSX) |
| 437 | return new RenderTextHarfBuzz; |
[email protected] | 5284802 | 2014-05-22 19:01:07 | [diff] [blame] | 438 | } |
| 439 | |
mukai | 1dfc595f | 2015-03-11 20:30:05 | [diff] [blame] | 440 | // static |
andresantoso | 62fd79bb | 2014-12-09 01:33:57 | [diff] [blame] | 441 | RenderText* RenderText::CreateInstanceForEditing() { |
derat | a55b0115d | 2015-02-03 01:59:08 | [diff] [blame] | 442 | return new RenderTextHarfBuzz; |
andresantoso | 62fd79bb | 2014-12-09 01:33:57 | [diff] [blame] | 443 | } |
| 444 | |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 445 | std::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_; |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 457 | render_text->weights_ = weights_; |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 458 | return render_text; |
| 459 | } |
| 460 | |
[email protected] | 031ffed | 2013-06-09 03:32:36 | [diff] [blame] | 461 | void RenderText::SetText(const base::string16& text) { |
[email protected] | d3c6b060 | 2011-09-07 19:26:06 | [diff] [blame] | 462 | DCHECK(!composition_range_.IsValid()); |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 463 | if (text_ == text) |
| 464 | return; |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 465 | text_ = text; |
dschuyler | 92bc0de | 2015-03-20 03:24:21 | [diff] [blame] | 466 | UpdateStyleLengths(); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 467 | |
dschuyler | 92bc0de | 2015-03-20 03:24:21 | [diff] [blame] | 468 | // Clear style ranges as they might break new text graphemes and apply |
tmoniuszko | a7fd79d | 2014-09-18 10:12:01 | [diff] [blame] | 469 | // the first style to the whole text instead. |
dschuyler | 92bc0de | 2015-03-20 03:24:21 | [diff] [blame] | 470 | colors_.SetValue(colors_.breaks().begin()->second); |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 471 | baselines_.SetValue(baselines_.breaks().begin()->second); |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 472 | weights_.SetValue(weights_.breaks().begin()->second); |
dschuyler | 92bc0de | 2015-03-20 03:24:21 | [diff] [blame] | 473 | for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) |
| 474 | styles_[style].SetValue(styles_[style].breaks().begin()->second); |
[email protected] | f6aaa0f | 2011-08-11 07:05:46 | [diff] [blame] | 475 | cached_bounds_and_offset_valid_ = false; |
[email protected] | d3c6b060 | 2011-09-07 19:26:06 | [diff] [blame] | 476 | |
| 477 | // Reset selection model. SetText should always followed by SetSelectionModel |
| 478 | // or SetCursorPosition in upper layer. |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 479 | SetSelectionModel(SelectionModel()); |
[email protected] | 6002a4f3 | 2011-11-30 10:18:42 | [diff] [blame] | 480 | |
[email protected] | 46cb538 | 2012-08-01 21:57:31 | [diff] [blame] | 481 | // 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] | 01e06a3 | 2013-06-07 12:41:33 | [diff] [blame] | 485 | obscured_reveal_index_ = -1; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 486 | OnTextAttributeChanged(); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 487 | } |
| 488 | |
dschuyler | 92bc0de | 2015-03-20 03:24:21 | [diff] [blame] | 489 | void 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] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 497 | void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { |
| 498 | if (horizontal_alignment_ != alignment) { |
| 499 | horizontal_alignment_ = alignment; |
[email protected] | ceb36f7d | 2012-10-31 18:33:24 | [diff] [blame] | 500 | display_offset_ = Vector2d(); |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 501 | cached_bounds_and_offset_valid_ = false; |
| 502 | } |
| 503 | } |
| 504 | |
[email protected] | 8d901a8 | 2012-01-04 19:41:30 | [diff] [blame] | 505 | void RenderText::SetFontList(const FontList& font_list) { |
| 506 | font_list_ = font_list; |
[email protected] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 507 | const int font_style = font_list.GetFontStyle(); |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 508 | 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] | 56e5057 | 2013-11-01 14:25:31 | [diff] [blame] | 511 | baseline_ = kInvalidBaseline; |
[email protected] | 8d901a8 | 2012-01-04 19:41:30 | [diff] [blame] | 512 | cached_bounds_and_offset_valid_ = false; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 513 | OnLayoutTextAttributeChanged(false); |
[email protected] | 8d901a8 | 2012-01-04 19:41:30 | [diff] [blame] | 514 | } |
| 515 | |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 516 | void RenderText::SetCursorEnabled(bool cursor_enabled) { |
| 517 | cursor_enabled_ = cursor_enabled; |
| 518 | cached_bounds_and_offset_valid_ = false; |
| 519 | } |
| 520 | |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 521 | void RenderText::SetObscured(bool obscured) { |
| 522 | if (obscured != obscured_) { |
| 523 | obscured_ = obscured; |
[email protected] | 01e06a3 | 2013-06-07 12:41:33 | [diff] [blame] | 524 | obscured_reveal_index_ = -1; |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 525 | cached_bounds_and_offset_valid_ = false; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 526 | OnTextAttributeChanged(); |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 527 | } |
| 528 | } |
| 529 | |
[email protected] | 01e06a3 | 2013-06-07 12:41:33 | [diff] [blame] | 530 | void 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; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 536 | OnTextAttributeChanged(); |
[email protected] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 537 | } |
| 538 | |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 539 | void RenderText::SetMultiline(bool multiline) { |
| 540 | if (multiline != multiline_) { |
| 541 | multiline_ = multiline; |
| 542 | cached_bounds_and_offset_valid_ = false; |
| 543 | lines_.clear(); |
mukai | 9b589ee | 2015-02-20 01:02:14 | [diff] [blame] | 544 | OnTextAttributeChanged(); |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 545 | } |
| 546 | } |
| 547 | |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 548 | void RenderText::SetMaxLines(size_t max_lines) { |
| 549 | max_lines_ = max_lines; |
| 550 | OnDisplayTextAttributeChanged(); |
| 551 | } |
| 552 | |
| 553 | size_t RenderText::GetNumLines() { |
| 554 | return lines_.size(); |
| 555 | } |
| 556 | |
mukai | a193393 | 2015-03-28 00:07:05 | [diff] [blame] | 557 | void 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 | |
mukai | 1dfc595f | 2015-03-11 20:30:05 | [diff] [blame] | 568 | void 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 | |
mukai | f32fdd5b | 2015-02-10 22:26:50 | [diff] [blame] | 576 | void 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(); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 582 | OnDisplayTextAttributeChanged(); |
mukai | f32fdd5b | 2015-02-10 22:26:50 | [diff] [blame] | 583 | } |
| 584 | |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 585 | void 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; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 589 | OnDisplayTextAttributeChanged(); |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 590 | } |
| 591 | } |
| 592 | |
[email protected] | f6aaa0f | 2011-08-11 07:05:46 | [diff] [blame] | 593 | void RenderText::SetDisplayRect(const Rect& r) { |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 594 | if (r != display_rect_) { |
| 595 | display_rect_ = r; |
| 596 | baseline_ = kInvalidBaseline; |
| 597 | cached_bounds_and_offset_valid_ = false; |
| 598 | lines_.clear(); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 599 | if (elide_behavior_ != NO_ELIDE && |
| 600 | elide_behavior_ != FADE_TAIL) { |
| 601 | OnDisplayTextAttributeChanged(); |
| 602 | } |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 603 | } |
[email protected] | 8e42ba2 | 2011-08-04 21:47:08 | [diff] [blame] | 604 | } |
| 605 | |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 606 | void RenderText::SetCursorPosition(size_t position) { |
| 607 | MoveCursorTo(position, false); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 608 | } |
| 609 | |
[email protected] | d66009e | 2012-01-21 01:27:28 | [diff] [blame] | 610 | void RenderText::MoveCursor(BreakType break_type, |
| 611 | VisualCursorDirection direction, |
| 612 | bool select) { |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 613 | SelectionModel cursor(cursor_position(), selection_model_.caret_affinity()); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 614 | // Cancelling a selection moves to the edge of the selection. |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 615 | if (break_type != LINE_BREAK && !selection().is_empty() && !select) { |
[email protected] | d3c6b060 | 2011-09-07 19:26:06 | [diff] [blame] | 616 | SelectionModel selection_start = GetSelectionModelForSelectionStart(); |
[email protected] | d66009e | 2012-01-21 01:27:28 | [diff] [blame] | 617 | int start_x = GetCursorBounds(selection_start, true).x(); |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 618 | int cursor_x = GetCursorBounds(cursor, true).x(); |
[email protected] | d66009e | 2012-01-21 01:27:28 | [diff] [blame] | 619 | // 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] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 622 | cursor = selection_start; |
| 623 | // Use the nearest word boundary in the proper |direction| for word breaks. |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 624 | if (break_type == WORD_BREAK) |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 625 | 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] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 629 | } else { |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 630 | cursor = GetAdjacentSelectionModel(cursor, break_type, direction); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 631 | } |
[email protected] | ec7f48d | 2011-08-09 03:48:50 | [diff] [blame] | 632 | if (select) |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 633 | cursor.set_selection_start(selection().start()); |
| 634 | MoveCursorTo(cursor); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 635 | } |
| 636 | |
[email protected] | f9a221b | 2011-12-10 20:25:38 | [diff] [blame] | 637 | bool RenderText::MoveCursorTo(const SelectionModel& model) { |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 638 | // Enforce valid selection model components. |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 639 | size_t text_length = text().length(); |
jam | 1f0b47c | 2016-02-10 01:49:25 | [diff] [blame] | 640 | Range range(std::min(model.selection().start(), |
| 641 | static_cast<uint32_t>(text_length)), |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 642 | std::min(model.caret_pos(), text_length)); |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 643 | // The current model only supports caret positions at valid cursor indices. |
| 644 | if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end())) |
[email protected] | 53c0b1b | 2011-09-21 20:32:29 | [diff] [blame] | 645 | return false; |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 646 | SelectionModel sel(range, model.caret_affinity()); |
| 647 | bool changed = sel != selection_model_; |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 648 | SetSelectionModel(sel); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 649 | return changed; |
| 650 | } |
| 651 | |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 652 | bool RenderText::SelectRange(const Range& range) { |
jam | 1f0b47c | 2016-02-10 01:49:25 | [diff] [blame] | 653 | 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] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 656 | // Allow selection bounds at valid indicies amid multi-character graphemes. |
| 657 | if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end())) |
[email protected] | 67e8551 | 2011-10-12 20:03:45 | [diff] [blame] | 658 | return false; |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 659 | LogicalCursorDirection affinity = |
| 660 | (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD; |
| 661 | SetSelectionModel(SelectionModel(sel, affinity)); |
[email protected] | 67e8551 | 2011-10-12 20:03:45 | [diff] [blame] | 662 | return true; |
| 663 | } |
| 664 | |
[email protected] | 8e42ba2 | 2011-08-04 21:47:08 | [diff] [blame] | 665 | bool RenderText::IsPointInSelection(const Point& point) { |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 666 | if (selection().is_empty()) |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 667 | return false; |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 668 | SelectionModel cursor = FindCursorPosition(point); |
| 669 | return RangeContainsCaret( |
| 670 | selection(), cursor.caret_pos(), cursor.caret_affinity()); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 671 | } |
| 672 | |
| 673 | void RenderText::ClearSelection() { |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 674 | SetSelectionModel(SelectionModel(cursor_position(), |
| 675 | selection_model_.caret_affinity())); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 676 | } |
| 677 | |
[email protected] | f5f0c2a | 2012-07-13 04:44:55 | [diff] [blame] | 678 | void RenderText::SelectAll(bool reversed) { |
| 679 | const size_t length = text().length(); |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 680 | const Range all = reversed ? Range(length, 0) : Range(0, length); |
[email protected] | f5f0c2a | 2012-07-13 04:44:55 | [diff] [blame] | 681 | const bool success = SelectRange(all); |
| 682 | DCHECK(success); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 683 | } |
| 684 | |
| 685 | void RenderText::SelectWord() { |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 686 | if (obscured_) { |
[email protected] | f5f0c2a | 2012-07-13 04:44:55 | [diff] [blame] | 687 | SelectAll(false); |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 688 | return; |
| 689 | } |
| 690 | |
[email protected] | fa7b96f1 | 2013-05-14 14:45:44 | [diff] [blame] | 691 | size_t selection_max = selection().GetMax(); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 692 | |
[email protected] | 53c0b1b | 2011-09-21 20:32:29 | [diff] [blame] | 693 | 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] | fa7b96f1 | 2013-05-14 14:45:44 | [diff] [blame] | 699 | size_t selection_min = selection().GetMin(); |
| 700 | if (selection_min == text().length() && selection_min != 0) |
| 701 | --selection_min; |
[email protected] | 8c496131 | 2013-04-18 12:43:57 | [diff] [blame] | 702 | |
[email protected] | fa7b96f1 | 2013-05-14 14:45:44 | [diff] [blame] | 703 | for (; selection_min != 0; --selection_min) { |
| 704 | if (iter.IsStartOfWord(selection_min) || |
| 705 | iter.IsEndOfWord(selection_min)) |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 706 | break; |
| 707 | } |
| 708 | |
[email protected] | fa7b96f1 | 2013-05-14 14:45:44 | [diff] [blame] | 709 | if (selection_min == selection_max && selection_max != text().length()) |
| 710 | ++selection_max; |
[email protected] | 53c0b1b | 2011-09-21 20:32:29 | [diff] [blame] | 711 | |
[email protected] | fa7b96f1 | 2013-05-14 14:45:44 | [diff] [blame] | 712 | for (; selection_max < text().length(); ++selection_max) |
| 713 | if (iter.IsEndOfWord(selection_max) || iter.IsStartOfWord(selection_max)) |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 714 | break; |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 715 | |
[email protected] | fa7b96f1 | 2013-05-14 14:45:44 | [diff] [blame] | 716 | const bool reversed = selection().is_reversed(); |
| 717 | MoveCursorTo(reversed ? selection_max : selection_min, false); |
| 718 | MoveCursorTo(reversed ? selection_min : selection_max, true); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 719 | } |
| 720 | |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 721 | void RenderText::SetCompositionRange(const Range& composition_range) { |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 722 | CHECK(!composition_range.IsValid() || |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 723 | Range(0, text_.length()).Contains(composition_range)); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 724 | composition_range_.set_end(composition_range.end()); |
| 725 | composition_range_.set_start(composition_range.start()); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 726 | // 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] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 731 | } |
| 732 | |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 733 | void RenderText::SetColor(SkColor value) { |
| 734 | colors_.SetValue(value); |
tapted | 1575b629 | 2015-09-25 03:05:31 | [diff] [blame] | 735 | OnTextColorChanged(); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 736 | } |
| 737 | |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 738 | void RenderText::ApplyColor(SkColor value, const Range& range) { |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 739 | colors_.ApplyValue(value, range); |
tapted | 1575b629 | 2015-09-25 03:05:31 | [diff] [blame] | 740 | OnTextColorChanged(); |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 741 | } |
| 742 | |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 743 | void RenderText::SetBaselineStyle(BaselineStyle value) { |
| 744 | baselines_.SetValue(value); |
| 745 | } |
| 746 | |
| 747 | void RenderText::ApplyBaselineStyle(BaselineStyle value, const Range& range) { |
| 748 | baselines_.ApplyValue(value, range); |
| 749 | } |
| 750 | |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 751 | void RenderText::SetStyle(TextStyle style, bool value) { |
| 752 | styles_[style].SetValue(value); |
| 753 | |
ckocagil | 3be7af4 | 2014-09-03 05:04:56 | [diff] [blame] | 754 | cached_bounds_and_offset_valid_ = false; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 755 | // TODO(oshima|msw): Not all style change requires layout changes. |
| 756 | // Consider optimizing based on the type of change. |
| 757 | OnLayoutTextAttributeChanged(false); |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 758 | } |
| 759 | |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 760 | void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) { |
tmoniuszko | a7fd79d | 2014-09-18 10:12:01 | [diff] [blame] | 761 | // 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] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 767 | |
ckocagil | 3be7af4 | 2014-09-03 05:04:56 | [diff] [blame] | 768 | cached_bounds_and_offset_valid_ = false; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 769 | // TODO(oshima|msw): Not all style change requires layout changes. |
| 770 | // Consider optimizing based on the type of change. |
| 771 | OnLayoutTextAttributeChanged(false); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 772 | } |
| 773 | |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 774 | void RenderText::SetWeight(Font::Weight weight) { |
| 775 | weights_.SetValue(weight); |
| 776 | |
| 777 | cached_bounds_and_offset_valid_ = false; |
| 778 | OnLayoutTextAttributeChanged(false); |
| 779 | } |
| 780 | |
| 781 | void 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] | ae0230a | 2013-06-14 23:47:32 | [diff] [blame] | 788 | bool RenderText::GetStyle(TextStyle style) const { |
| 789 | return (styles_[style].breaks().size() == 1) && |
| 790 | styles_[style].breaks().front().second; |
| 791 | } |
| 792 | |
[email protected] | 46cb538 | 2012-08-01 21:57:31 | [diff] [blame] | 793 | void 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] | 7ad72fb | 2013-11-08 01:33:20 | [diff] [blame] | 799 | cached_bounds_and_offset_valid_ = false; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 800 | OnLayoutTextAttributeChanged(false); |
[email protected] | 46cb538 | 2012-08-01 21:57:31 | [diff] [blame] | 801 | } |
| 802 | |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 803 | base::i18n::TextDirection RenderText::GetDisplayTextDirection() { |
| 804 | return GetTextDirection(GetDisplayText()); |
[email protected] | 46cb538 | 2012-08-01 21:57:31 | [diff] [blame] | 805 | } |
| 806 | |
[email protected] | d66009e | 2012-01-21 01:27:28 | [diff] [blame] | 807 | VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 808 | return GetDisplayTextDirection() == base::i18n::LEFT_TO_RIGHT ? |
[email protected] | d66009e | 2012-01-21 01:27:28 | [diff] [blame] | 809 | CURSOR_RIGHT : CURSOR_LEFT; |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 810 | } |
| 811 | |
[email protected] | 8ad3c5a | 2013-10-10 09:57:19 | [diff] [blame] | 812 | SizeF RenderText::GetStringSizeF() { |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 813 | return SizeF(GetStringSize()); |
[email protected] | 8ad3c5a | 2013-10-10 09:57:19 | [diff] [blame] | 814 | } |
| 815 | |
ckocagil | 4bd060c | 2014-12-08 22:32:35 | [diff] [blame] | 816 | float RenderText::GetContentWidthF() { |
| 817 | const float string_size = GetStringSizeF().width(); |
ckocagil | 9a4438e | 2014-12-02 04:48:30 | [diff] [blame] | 818 | // The cursor is drawn one pixel beyond the int-enclosed text bounds. |
ckocagil | 4bd060c | 2014-12-08 22:32:35 | [diff] [blame] | 819 | return cursor_enabled_ ? std::ceil(string_size) + 1 : string_size; |
| 820 | } |
| 821 | |
| 822 | int RenderText::GetContentWidth() { |
| 823 | return ToCeiledInt(GetContentWidthF()); |
[email protected] | ad0e54c0 | 2013-04-27 20:40:38 | [diff] [blame] | 824 | } |
| 825 | |
[email protected] | 56e5057 | 2013-11-01 14:25:31 | [diff] [blame] | 826 | int 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] | 7b3cb4b2 | 2011-08-02 18:36:40 | [diff] [blame] | 833 | void RenderText::Draw(Canvas* canvas) { |
[email protected] | 49aa120a | 2012-05-31 00:01:22 | [diff] [blame] | 834 | EnsureLayout(); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 835 | |
[email protected] | f7816ad | 2012-06-22 22:15:43 | [diff] [blame] | 836 | if (clip_to_display_rect()) { |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 837 | Rect clip_rect(display_rect()); |
[email protected] | 2073de7 | 2014-06-18 16:43:12 | [diff] [blame] | 838 | clip_rect.Inset(ShadowValue::GetMargin(shadows_)); |
[email protected] | 0834e1b1 | 2012-04-11 02:23:56 | [diff] [blame] | 839 | |
[email protected] | f7816ad | 2012-06-22 22:15:43 | [diff] [blame] | 840 | canvas->Save(); |
| 841 | canvas->ClipRect(clip_rect); |
| 842 | } |
[email protected] | 1a353f1 | 2012-01-18 04:20:56 | [diff] [blame] | 843 | |
[email protected] | 4778727 | 2013-07-02 23:51:12 | [diff] [blame] | 844 | if (!text().empty() && focused()) |
[email protected] | 1a353f1 | 2012-01-18 04:20:56 | [diff] [blame] | 845 | DrawSelection(canvas); |
| 846 | |
[email protected] | 1e12a30 | 2013-04-18 12:50:42 | [diff] [blame] | 847 | if (cursor_enabled() && cursor_visible() && focused()) |
| 848 | DrawCursor(canvas, selection_model_); |
[email protected] | 1a353f1 | 2012-01-18 04:20:56 | [diff] [blame] | 849 | |
tapted | 1575b629 | 2015-09-25 03:05:31 | [diff] [blame] | 850 | if (!text().empty()) { |
| 851 | internal::SkiaTextRenderer renderer(canvas); |
| 852 | DrawVisualText(&renderer); |
| 853 | } |
[email protected] | f7816ad | 2012-06-22 22:15:43 | [diff] [blame] | 854 | |
| 855 | if (clip_to_display_rect()) |
| 856 | canvas->Restore(); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 857 | } |
| 858 | |
[email protected] | 1e12a30 | 2013-04-18 12:50:42 | [diff] [blame] | 859 | void 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 | |
mgiuca | 859e336 | 2015-06-03 08:34:13 | [diff] [blame] | 865 | bool RenderText::IsValidLogicalIndex(size_t index) const { |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 866 | // Check that the index is at a valid code point (not mid-surrgate-pair) and |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 867 | // that it's not truncated from the display text (its glyph may be shown). |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 868 | // |
| 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] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 880 | Rect RenderText::GetCursorBounds(const SelectionModel& caret, |
| 881 | bool insert_mode) { |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 882 | // 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] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 886 | EnsureLayout(); |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 887 | size_t caret_pos = caret.caret_pos(); |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 888 | DCHECK(IsValidLogicalIndex(caret_pos)); |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 889 | // 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] | 6f938f1 | 2013-05-24 00:49:06 | [diff] [blame] | 893 | int x = 0, width = 1; |
| 894 | Size size = GetStringSize(); |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 895 | if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) { |
[email protected] | c33b290 | 2012-12-06 13:04:45 | [diff] [blame] | 896 | // The caret is attached to the boundary. Always return a 1-dip width caret, |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 897 | // since there is nothing to overtype. |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 898 | if ((GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT) |
| 899 | == (caret_pos == 0)) { |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 900 | x = size.width(); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 901 | } |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 902 | } else { |
| 903 | size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ? |
| 904 | caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD); |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 905 | Range xspan(GetGlyphBounds(grapheme_start)); |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 906 | 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] | 6f938f1 | 2013-05-24 00:49:06 | [diff] [blame] | 913 | return Rect(ToViewPoint(Point(x, 0)), Size(width, size.height())); |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 914 | } |
| 915 | |
[email protected] | f6aaa0f | 2011-08-11 07:05:46 | [diff] [blame] | 916 | const Rect& RenderText::GetUpdatedCursorBounds() { |
| 917 | UpdateCachedBoundsAndOffset(); |
[email protected] | 8e42ba2 | 2011-08-04 21:47:08 | [diff] [blame] | 918 | return cursor_bounds_; |
| 919 | } |
| 920 | |
[email protected] | 0a12d5f | 2012-05-16 00:37:09 | [diff] [blame] | 921 | size_t RenderText::IndexOfAdjacentGrapheme(size_t index, |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 922 | LogicalCursorDirection direction) { |
[email protected] | 0a12d5f | 2012-05-16 00:37:09 | [diff] [blame] | 923 | 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] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 931 | if (IsValidCursorIndex(index)) |
[email protected] | 0a12d5f | 2012-05-16 00:37:09 | [diff] [blame] | 932 | return index; |
| 933 | } |
| 934 | return text().length(); |
| 935 | } |
| 936 | |
| 937 | while (index > 0) { |
| 938 | index--; |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 939 | if (IsValidCursorIndex(index)) |
[email protected] | 0a12d5f | 2012-05-16 00:37:09 | [diff] [blame] | 940 | return index; |
| 941 | } |
| 942 | return 0; |
| 943 | } |
| 944 | |
mgiuca | 859e336 | 2015-06-03 08:34:13 | [diff] [blame] | 945 | SelectionModel RenderText::GetSelectionModelForSelectionStart() const { |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 946 | const Range& sel = selection(); |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 947 | if (sel.is_empty()) |
| 948 | return selection_model_; |
| 949 | return SelectionModel(sel.start(), |
| 950 | sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD); |
[email protected] | 04b7bf32 | 2011-10-03 19:08:46 | [diff] [blame] | 951 | } |
| 952 | |
[email protected] | 7b4c25ce | 2014-07-10 11:14:58 | [diff] [blame] | 953 | const Vector2d& RenderText::GetUpdatedDisplayOffset() { |
| 954 | UpdateCachedBoundsAndOffset(); |
| 955 | return display_offset_; |
| 956 | } |
| 957 | |
| 958 | void RenderText::SetDisplayOffset(int horizontal_offset) { |
ckocagil | 4bd060c | 2014-12-08 22:32:35 | [diff] [blame] | 959 | const int extra_content = GetContentWidth() - display_rect_.width(); |
[email protected] | ff0d853 | 2014-07-23 09:36:59 | [diff] [blame] | 960 | const int cursor_width = cursor_enabled_ ? 1 : 0; |
[email protected] | 7b4c25ce | 2014-07-10 11:14:58 | [diff] [blame] | 961 | |
| 962 | int min_offset = 0; |
| 963 | int max_offset = 0; |
| 964 | if (extra_content > 0) { |
[email protected] | ff0d853 | 2014-07-23 09:36:59 | [diff] [blame] | 965 | switch (GetCurrentHorizontalAlignment()) { |
[email protected] | 7b4c25ce | 2014-07-10 11:14:58 | [diff] [blame] | 966 | 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] | ff0d853 | 2014-07-23 09:36:59 | [diff] [blame] | 973 | // 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] | 7b4c25ce | 2014-07-10 11:14:58 | [diff] [blame] | 979 | 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); |
msw | 13ce21b | 2016-06-22 03:06:56 | [diff] [blame] | 991 | cursor_bounds_ = GetCursorBounds(selection_model_, true); |
[email protected] | 7b4c25ce | 2014-07-10 11:14:58 | [diff] [blame] | 992 | } |
| 993 | |
mukai | 26abb0b | 2015-03-17 18:11:27 | [diff] [blame] | 994 | Vector2d RenderText::GetLineOffset(size_t line_number) { |
| 995 | Vector2d offset = display_rect().OffsetFromOrigin(); |
| 996 | // TODO(ckocagil): Apply the display offset for multiline scrolling. |
| 997 | if (!multiline()) |
| 998 | offset.Add(GetUpdatedDisplayOffset()); |
| 999 | else |
| 1000 | offset.Add(Vector2d(0, lines_[line_number].preceding_heights)); |
| 1001 | offset.Add(GetAlignmentOffset(line_number)); |
| 1002 | return offset; |
| 1003 | } |
| 1004 | |
[email protected] | 8e42ba2 | 2011-08-04 21:47:08 | [diff] [blame] | 1005 | RenderText::RenderText() |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1006 | : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT), |
[email protected] | 46cb538 | 2012-08-01 21:57:31 | [diff] [blame] | 1007 | directionality_mode_(DIRECTIONALITY_FROM_TEXT), |
| 1008 | text_direction_(base::i18n::UNKNOWN_DIRECTION), |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1009 | cursor_enabled_(true), |
| 1010 | cursor_visible_(false), |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1011 | cursor_color_(kDefaultColor), |
| 1012 | selection_color_(kDefaultColor), |
[email protected] | 311a09d | 2012-05-14 20:38:17 | [diff] [blame] | 1013 | selection_background_focused_color_(kDefaultSelectionBackgroundColor), |
[email protected] | 5abe630 | 2011-12-20 23:44:32 | [diff] [blame] | 1014 | focused_(false), |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 1015 | composition_range_(Range::InvalidRange()), |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1016 | colors_(kDefaultColor), |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 1017 | baselines_(NORMAL_BASELINE), |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 1018 | weights_(Font::Weight::NORMAL), |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1019 | styles_(NUM_TEXT_STYLES), |
| 1020 | composition_and_selection_styles_applied_(false), |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 1021 | obscured_(false), |
[email protected] | 01e06a3 | 2013-06-07 12:41:33 | [diff] [blame] | 1022 | obscured_reveal_index_(-1), |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1023 | truncate_length_(0), |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1024 | elide_behavior_(NO_ELIDE), |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1025 | text_elided_(false), |
mukai | f32fdd5b | 2015-02-10 22:26:50 | [diff] [blame] | 1026 | min_line_height_(0), |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1027 | multiline_(false), |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 1028 | max_lines_(0), |
mukai | a193393 | 2015-03-28 00:07:05 | [diff] [blame] | 1029 | word_wrap_behavior_(IGNORE_LONG_WORDS), |
mukai | 1dfc595f | 2015-03-11 20:30:05 | [diff] [blame] | 1030 | replace_newline_chars_with_symbols_(true), |
mukai | 4b75bd78 | 2015-02-19 21:44:42 | [diff] [blame] | 1031 | subpixel_rendering_suppressed_(false), |
[email protected] | f7816ad | 2012-06-22 22:15:43 | [diff] [blame] | 1032 | clip_to_display_rect_(true), |
[email protected] | 56e5057 | 2013-11-01 14:25:31 | [diff] [blame] | 1033 | baseline_(kInvalidBaseline), |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 1034 | cached_bounds_and_offset_valid_(false) {} |
[email protected] | f6aaa0f | 2011-08-11 07:05:46 | [diff] [blame] | 1035 | |
[email protected] | d66009e | 2012-01-21 01:27:28 | [diff] [blame] | 1036 | SelectionModel 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] | ec7f48d | 2011-08-09 03:48:50 | [diff] [blame] | 1044 | if (break_type == CHARACTER_BREAK) |
[email protected] | d66009e | 2012-01-21 01:27:28 | [diff] [blame] | 1045 | return AdjacentCharSelectionModel(current, direction); |
| 1046 | DCHECK(break_type == WORD_BREAK); |
| 1047 | return AdjacentWordSelectionModel(current, direction); |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 1048 | } |
| 1049 | |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 1050 | SelectionModel 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] | 6002a4f3 | 2011-11-30 10:18:42 | [diff] [blame] | 1056 | |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 1057 | void RenderText::SetSelectionModel(const SelectionModel& model) { |
| 1058 | DCHECK_LE(model.selection().GetMax(), text().length()); |
| 1059 | selection_model_ = model; |
[email protected] | 6002a4f3 | 2011-11-30 10:18:42 | [diff] [blame] | 1060 | cached_bounds_and_offset_valid_ = false; |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 1061 | } |
| 1062 | |
tapted | 1575b629 | 2015-09-25 03:05:31 | [diff] [blame] | 1063 | void RenderText::OnTextColorChanged() { |
| 1064 | } |
| 1065 | |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1066 | void RenderText::UpdateDisplayText(float text_width) { |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 1067 | // 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()) || |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1071 | layout_text_.empty()) { |
| 1072 | text_elided_ = false; |
| 1073 | display_text_.clear(); |
| 1074 | return; |
| 1075 | } |
| 1076 | |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 1077 | 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(); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1087 | |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 1088 | 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 | } |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1114 | text_elided_ = display_text_ != layout_text_; |
| 1115 | if (!text_elided_) |
| 1116 | display_text_.clear(); |
[email protected] | bec929c | 2012-03-02 06:23:50 | [diff] [blame] | 1117 | } |
| 1118 | |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1119 | const BreakList<size_t>& RenderText::GetLineBreaks() { |
| 1120 | if (line_breaks_.max() != 0) |
| 1121 | return line_breaks_; |
| 1122 | |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1123 | const base::string16& layout_text = GetDisplayText(); |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1124 | 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] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1139 | void 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] | 08726d5e | 2013-09-24 21:52:55 | [diff] [blame] | 1150 | if (!selection().is_empty() && focused()) { |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 1151 | const Range range(selection().GetMin(), selection().GetMax()); |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1152 | colors_.ApplyValue(selection_color_, range); |
[email protected] | 8e42ba2 | 2011-08-04 21:47:08 | [diff] [blame] | 1153 | } |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1154 | composition_and_selection_styles_applied_ = true; |
| 1155 | } |
| 1156 | |
| 1157 | void 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] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 1163 | } |
| 1164 | |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 1165 | Point RenderText::ToTextPoint(const Point& point) { |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1166 | return point - GetLineOffset(0); |
| 1167 | // TODO(ckocagil): Convert multiline view space points to text space. |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 1168 | } |
| 1169 | |
| 1170 | Point RenderText::ToViewPoint(const Point& point) { |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1171 | 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] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1181 | } |
| 1182 | |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1183 | std::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]; |
xdai | 08820ef | 2015-06-23 20:22:03 | [diff] [blame] | 1202 | const Range intersection = segment->x_range.Intersect(x).Ceil(); |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1203 | 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] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 1215 | HorizontalAlignment RenderText::GetCurrentHorizontalAlignment() { |
| 1216 | if (horizontal_alignment_ != ALIGN_TO_HEAD) |
| 1217 | return horizontal_alignment_; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1218 | return GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT ? |
| 1219 | ALIGN_RIGHT : ALIGN_LEFT; |
[email protected] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 1220 | } |
| 1221 | |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1222 | Vector2d RenderText::GetAlignmentOffset(size_t line_number) { |
oshima | 403e5606 | 2015-02-24 03:31:06 | [diff] [blame] | 1223 | // TODO(ckocagil): Enable |lines_| usage on RenderTextMac. |
mukai | 1dfc595f | 2015-03-11 20:30:05 | [diff] [blame] | 1224 | if (MultilineSupported() && multiline_) |
oshima | e008451a | 2015-02-25 00:43:39 | [diff] [blame] | 1225 | DCHECK_LT(line_number, lines_.size()); |
[email protected] | 9bb78438 | 2013-05-20 20:40:33 | [diff] [blame] | 1226 | Vector2d offset; |
[email protected] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 1227 | HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment(); |
| 1228 | if (horizontal_alignment != ALIGN_LEFT) { |
oshima | e008451a | 2015-02-25 00:43:39 | [diff] [blame] | 1229 | const int width = multiline_ ? |
| 1230 | std::ceil(lines_[line_number].size.width()) + |
| 1231 | (cursor_enabled_ ? 1 : 0) : |
| 1232 | GetContentWidth(); |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1233 | offset.set_x(display_rect().width() - width); |
[email protected] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 1234 | // 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] | 9bb78438 | 2013-05-20 20:40:33 | [diff] [blame] | 1237 | } |
[email protected] | 6c0078f5 | 2013-11-21 23:43:53 | [diff] [blame] | 1238 | |
| 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 { |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1245 | offset.set_y(GetBaseline() - GetDisplayTextBaseline()); |
[email protected] | 6c0078f5 | 2013-11-21 23:43:53 | [diff] [blame] | 1246 | } |
| 1247 | |
[email protected] | 9bb78438 | 2013-05-20 20:40:33 | [diff] [blame] | 1248 | return offset; |
[email protected] | 67b98156 | 2011-12-09 00:35:05 | [diff] [blame] | 1249 | } |
| 1250 | |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1251 | void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) { |
[email protected] | f3ce621 | 2014-06-05 22:42:08 | [diff] [blame] | 1252 | const int width = display_rect().width(); |
ckocagil | 4bd060c | 2014-12-08 22:32:35 | [diff] [blame] | 1253 | if (multiline() || elide_behavior_ != FADE_TAIL || GetContentWidth() <= width) |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1254 | return; |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 1255 | |
[email protected] | f3ce621 | 2014-06-05 22:42:08 | [diff] [blame] | 1256 | const int gradient_width = CalculateFadeGradientWidth(font_list(), width); |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 1257 | if (gradient_width == 0) |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1258 | return; |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 1259 | |
[email protected] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 1260 | HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment(); |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1261 | Rect solid_part = display_rect(); |
| 1262 | Rect left_part; |
| 1263 | Rect right_part; |
[email protected] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 1264 | if (horizontal_alignment != ALIGN_LEFT) { |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 1265 | 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] | b183dd1 | 2014-07-21 07:55:51 | [diff] [blame] | 1269 | if (horizontal_alignment != ALIGN_RIGHT) { |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 1270 | 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] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1275 | Rect text_rect = display_rect(); |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1276 | text_rect.Inset(GetAlignmentOffset(0).x(), 0, 0, 0); |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 1277 | |
[email protected] | ccfa43f0 | 2013-02-01 04:42:17 | [diff] [blame] | 1278 | // TODO(msw): Use the actual text colors corresponding to each faded part. |
tomhudson | 2b45cf9 | 2016-03-31 14:10:15 | [diff] [blame] | 1279 | renderer->SetShader( |
tmoniuszko | d6fa6e4 | 2015-12-04 12:06:21 | [diff] [blame] | 1280 | CreateFadeShader(font_list(), text_rect, left_part, right_part, |
tomhudson | 2b45cf9 | 2016-03-31 14:10:15 | [diff] [blame] | 1281 | SkColorSetA(colors_.breaks().front().second, 0xff))); |
[email protected] | f308e5b1 | 2012-01-17 19:09:18 | [diff] [blame] | 1282 | } |
| 1283 | |
[email protected] | 0834e1b1 | 2012-04-11 02:23:56 | [diff] [blame] | 1284 | void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { |
tomhudson | 5397394 | 2016-03-31 17:05:00 | [diff] [blame] | 1285 | renderer->SetDrawLooper(CreateShadowDrawLooper(shadows_)); |
[email protected] | 0834e1b1 | 2012-04-11 02:23:56 | [diff] [blame] | 1286 | } |
| 1287 | |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1288 | base::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 | |
mukai | fea1a11 | 2015-03-09 22:06:56 | [diff] [blame] | 1316 | size_t RenderText::TextIndexToGivenTextIndex(const base::string16& given_text, |
mgiuca | 859e336 | 2015-06-03 08:34:13 | [diff] [blame] | 1317 | size_t index) const { |
mukai | fea1a11 | 2015-03-09 22:06:56 | [diff] [blame] | 1318 | 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 | |
dschuyler | 92bc0de | 2015-03-20 03:24:21 | [diff] [blame] | 1326 | void RenderText::UpdateStyleLengths() { |
| 1327 | const size_t text_length = text_.length(); |
| 1328 | colors_.SetMax(text_length); |
| 1329 | baselines_.SetMax(text_length); |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 1330 | weights_.SetMax(text_length); |
dschuyler | 92bc0de | 2015-03-20 03:24:21 | [diff] [blame] | 1331 | for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) |
| 1332 | styles_[style].SetMax(text_length); |
| 1333 | } |
| 1334 | |
[email protected] | 4712ae4 | 2013-04-27 04:55:08 | [diff] [blame] | 1335 | // static |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 1336 | bool RenderText::RangeContainsCaret(const Range& range, |
[email protected] | 4712ae4 | 2013-04-27 04:55:08 | [diff] [blame] | 1337 | 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] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 1342 | return range.Contains(Range(caret_pos, adjacent)); |
[email protected] | 4712ae4 | 2013-04-27 04:55:08 | [diff] [blame] | 1343 | } |
| 1344 | |
[email protected] | 0d71760 | 2011-08-30 06:21:14 | [diff] [blame] | 1345 | void RenderText::MoveCursorTo(size_t position, bool select) { |
| 1346 | size_t cursor = std::min(position, text().length()); |
[email protected] | 3dbc8a6 | 2014-05-02 17:08:17 | [diff] [blame] | 1347 | if (IsValidCursorIndex(cursor)) |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 1348 | SetSelectionModel(SelectionModel( |
[email protected] | 3dfe5c5 | 2013-09-18 22:01:22 | [diff] [blame] | 1349 | Range(select ? selection().start() : cursor, cursor), |
[email protected] | d999021 | 2012-03-13 01:09:31 | [diff] [blame] | 1350 | (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD)); |
[email protected] | 8e42ba2 | 2011-08-04 21:47:08 | [diff] [blame] | 1351 | } |
| 1352 | |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1353 | void RenderText::OnTextAttributeChanged() { |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1354 | layout_text_.clear(); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1355 | display_text_.clear(); |
mukai | fea1a11 | 2015-03-09 22:06:56 | [diff] [blame] | 1356 | text_elided_ = false; |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1357 | line_breaks_.SetMax(0); |
[email protected] | 6bc200d | 2012-09-28 21:01:51 | [diff] [blame] | 1358 | |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1359 | if (obscured_) { |
| 1360 | size_t obscured_text_length = |
[email protected] | f3ce621 | 2014-06-05 22:42:08 | [diff] [blame] | 1361 | static_cast<size_t>(UTF16IndexToOffset(text_, 0, text_.length())); |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1362 | layout_text_.assign(obscured_text_length, kPasswordReplacementChar); |
[email protected] | 01e06a3 | 2013-06-07 12:41:33 | [diff] [blame] | 1363 | |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1364 | 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] | 01e06a3 | 2013-06-07 12:41:33 | [diff] [blame] | 1372 | |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1373 | // Gets the index in |layout_text_| to be replaced. |
| 1374 | const size_t cp_start = |
[email protected] | f3ce621 | 2014-06-05 22:42:08 | [diff] [blame] | 1375 | static_cast<size_t>(UTF16IndexToOffset(text_, 0, start)); |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1376 | if (layout_text_.length() > cp_start) |
| 1377 | layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); |
| 1378 | } |
[email protected] | 61f9908 | 2014-02-24 02:17:49 | [diff] [blame] | 1379 | } else { |
| 1380 | layout_text_ = text_; |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1381 | } |
| 1382 | |
[email protected] | 61f9908 | 2014-02-24 02:17:49 | [diff] [blame] | 1383 | const base::string16& text = layout_text_; |
[email protected] | fecf9f7 | 2013-06-29 13:02:47 | [diff] [blame] | 1384 | 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] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1387 | // 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] | 01e06a3 | 2013-06-07 12:41:33 | [diff] [blame] | 1403 | } |
[email protected] | 102402c | 2014-07-09 19:53:38 | [diff] [blame] | 1404 | static const base::char16 kNewline[] = { '\n', 0 }; |
| 1405 | static const base::char16 kNewlineSymbol[] = { 0x2424, 0 }; |
mukai | 1dfc595f | 2015-03-11 20:30:05 | [diff] [blame] | 1406 | if (!multiline_ && replace_newline_chars_with_symbols_) |
[email protected] | 102402c | 2014-07-09 19:53:38 | [diff] [blame] | 1407 | base::ReplaceChars(layout_text_, kNewline, kNewlineSymbol, &layout_text_); |
| 1408 | |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1409 | OnLayoutTextAttributeChanged(true); |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1410 | } |
| 1411 | |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1412 | base::string16 RenderText::Elide(const base::string16& text, |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1413 | float text_width, |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1414 | 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); |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 1420 | if (text_width > 0 && text_width <= available_width) |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1421 | return text; |
| 1422 | |
| 1423 | TRACE_EVENT0("ui", "RenderText::Elide"); |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1424 | |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1425 | // Create a RenderText copy with attributes that affect the rendering width. |
krb | a468b18b | 2016-05-17 21:00:35 | [diff] [blame] | 1426 | std::unique_ptr<RenderText> render_text = CreateInstanceOfSameStyle(text); |
| 1427 | render_text->UpdateStyleLengths(); |
| 1428 | if (text_width == 0) |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1429 | text_width = render_text->GetContentWidthF(); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1430 | if (text_width <= available_width) |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1431 | return text; |
| 1432 | |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1433 | 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); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1437 | |
| 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] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1445 | StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning); |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1446 | |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1447 | // Use binary search to compute the elided text. |
| 1448 | size_t lo = 0; |
| 1449 | size_t hi = text.length() - 1; |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1450 | const base::i18n::TextDirection text_direction = GetTextDirection(text); |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1451 | for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { |
tmoniuszko | 4757aae | 2014-10-16 07:37:27 | [diff] [blame] | 1452 | // Restore colors. They will be truncated to size by SetText. |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1453 | render_text->colors_ = colors_; |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1454 | base::string16 new_text = |
| 1455 | slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL); |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1456 | 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] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1460 | if (insert_ellipsis && behavior == ELIDE_TAIL) { |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1461 | // 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] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1466 | base::i18n::TextDirection trailing_text_direction = |
| 1467 | base::i18n::GetLastStrongCharacterDirection(new_text); |
| 1468 | new_text.append(ellipsis); |
[email protected] | 61f9908 | 2014-02-24 02:17:49 | [diff] [blame] | 1469 | if (trailing_text_direction != text_direction) { |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1470 | 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 | |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 1478 | // Restore styles and baselines without breaking multi-character graphemes. |
tmoniuszko | 4757aae | 2014-10-16 07:37:27 | [diff] [blame] | 1479 | render_text->styles_ = styles_; |
dschuyler | fcd7691 | 2015-03-11 01:40:11 | [diff] [blame] | 1480 | for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) |
mgiuca | 859e336 | 2015-06-03 08:34:13 | [diff] [blame] | 1481 | RestoreBreakList(render_text.get(), &render_text->styles_[style]); |
| 1482 | RestoreBreakList(render_text.get(), &render_text->baselines_); |
mboc | 998e890 | 2016-06-02 11:40:35 | [diff] [blame] | 1483 | render_text->weights_ = weights_; |
| 1484 | RestoreBreakList(render_text.get(), &render_text->weights_); |
tmoniuszko | 4757aae | 2014-10-16 07:37:27 | [diff] [blame] | 1485 | |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1486 | // We check the width of the whole desired string at once to ensure we |
| 1487 | // handle kerning/ligatures/etc. correctly. |
ckocagil | 4bd060c | 2014-12-08 22:32:35 | [diff] [blame] | 1488 | const float guess_width = render_text->GetContentWidthF(); |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1489 | if (guess_width == available_width) |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1490 | break; |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1491 | if (guess_width > available_width) { |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1492 | hi = guess - 1; |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1493 | // Move back on the loop terminating condition when the guess is too wide. |
[email protected] | ec2ce92 | 2014-01-02 23:06:47 | [diff] [blame] | 1494 | if (hi < lo) |
| 1495 | lo = hi; |
| 1496 | } else { |
| 1497 | lo = guess + 1; |
| 1498 | } |
| 1499 | } |
| 1500 | |
| 1501 | return render_text->text(); |
[email protected] | 6bc200d | 2012-09-28 21:01:51 | [diff] [blame] | 1502 | } |
| 1503 | |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1504 | base::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)); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1544 | domain = Elide(domain, 0, desired_domain_width, ELIDE_MIDDLE); |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1545 | // 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()); |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1555 | username = Elide(username, 0, available_width, ELIDE_TAIL); |
[email protected] | 8d93614 | 2014-07-10 21:40:37 | [diff] [blame] | 1556 | return username + kAtSignUTF16 + domain; |
| 1557 | } |
| 1558 | |
[email protected] | f6aaa0f | 2011-08-11 07:05:46 | [diff] [blame] | 1559 | void RenderText::UpdateCachedBoundsAndOffset() { |
| 1560 | if (cached_bounds_and_offset_valid_) |
| 1561 | return; |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1562 | |
[email protected] | eced6cb | 2013-09-16 20:16:20 | [diff] [blame] | 1563 | // TODO(ckocagil): Add support for scrolling multiline text. |
| 1564 | |
[email protected] | ff0d853 | 2014-07-23 09:36:59 | [diff] [blame] | 1565 | 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; |
msw | 13ce21b | 2016-06-22 03:06:56 | [diff] [blame] | 1573 | cursor_bounds_ = GetCursorBounds(selection_model_, true); |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1574 | |
[email protected] | ff0d853 | 2014-07-23 09:36:59 | [diff] [blame] | 1575 | // 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] | 8e42ba2 | 2011-08-04 21:47:08 | [diff] [blame] | 1580 | } |
[email protected] | f0ed8a2f | 2012-01-24 17:45:59 | [diff] [blame] | 1581 | |
[email protected] | ff0d853 | 2014-07-23 09:36:59 | [diff] [blame] | 1582 | SetDisplayOffset(display_offset_.x() + delta_x); |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 1583 | } |
| 1584 | |
[email protected] | 6002a4f3 | 2011-11-30 10:18:42 | [diff] [blame] | 1585 | void RenderText::DrawSelection(Canvas* canvas) { |
oshima | 0a243a4 | 2015-02-14 04:46:31 | [diff] [blame] | 1586 | for (const Rect& s : GetSubstringBounds(selection())) |
| 1587 | canvas->FillRect(s, selection_background_focused_color_); |
[email protected] | 6002a4f3 | 2011-11-30 10:18:42 | [diff] [blame] | 1588 | } |
| 1589 | |
[email protected] | ff44d71 | 2011-07-25 08:42:52 | [diff] [blame] | 1590 | } // namespace gfx |