Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/gfx/render_text.h" | 5 #include "ui/gfx/render_text.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/i18n/break_iterator.h" | 9 #include "base/i18n/break_iterator.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/stl_util.h" | 11 #include "base/stl_util.h" |
| 12 #include "third_party/skia/include/core/SkTypeface.h" | 12 #include "third_party/skia/include/core/SkTypeface.h" |
| 13 #include "third_party/skia/include/effects/SkGradientShader.h" | 13 #include "third_party/skia/include/effects/SkGradientShader.h" |
| 14 #include "ui/base/text/utf16_indexing.h" | 14 #include "ui/base/text/utf16_indexing.h" |
| 15 #include "ui/gfx/canvas.h" | 15 #include "ui/gfx/canvas.h" |
| 16 #include "ui/gfx/insets.h" | 16 #include "ui/gfx/insets.h" |
| 17 #include "ui/gfx/skia_util.h" | 17 #include "ui/gfx/skia_util.h" |
| 18 #include "ui/gfx/text_constants.h" | 18 #include "ui/gfx/text_constants.h" |
| 19 | 19 |
| 20 namespace gfx { | |
| 21 | |
| 20 namespace { | 22 namespace { |
| 21 | 23 |
| 22 // All chars are replaced by this char when the password style is set. | 24 // All chars are replaced by this char when the password style is set. |
| 23 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*' | 25 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*' |
| 24 // that's available in the font (find_invisible_char() in gtkentry.c). | 26 // that's available in the font (find_invisible_char() in gtkentry.c). |
| 25 const char16 kPasswordReplacementChar = '*'; | 27 const char16 kPasswordReplacementChar = '*'; |
| 26 | 28 |
| 27 // Default color used for the cursor. | 29 // Default color used for the text and cursor. |
| 28 const SkColor kDefaultCursorColor = SK_ColorBLACK; | 30 const SkColor kDefaultColor = SK_ColorBLACK; |
| 29 | |
| 30 // Default color used for drawing selection text. | |
| 31 const SkColor kDefaultSelectionColor = SK_ColorBLACK; | |
| 32 | 31 |
| 33 // Default color used for drawing selection background. | 32 // Default color used for drawing selection background. |
| 34 const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY; | 33 const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY; |
| 35 | 34 |
| 36 #ifndef NDEBUG | 35 // Fraction of the text size to lower a strike through below the baseline. |
| 37 // Check StyleRanges invariant conditions: sorted and non-overlapping ranges. | 36 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21); |
| 38 void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) { | 37 // Fraction of the text size to lower an underline below the baseline. |
| 39 if (length == 0) { | 38 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); |
| 40 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text."; | 39 // Fraction of the text size to use for a strike through or under-line. |
| 41 return; | 40 const SkScalar kLineThickness = (SK_Scalar1 / 18); |
| 42 } | 41 // Fraction of the text size to use for a top margin of a diagonal strike. |
| 43 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) { | 42 const SkScalar kDiagonalStrikeMarginOffset = (SK_Scalar1 / 4); |
| 44 const ui::Range& former = style_ranges[i].range; | |
| 45 const ui::Range& latter = style_ranges[i + 1].range; | |
| 46 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << | |
| 47 former.ToString(); | |
| 48 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << | |
| 49 former.ToString(); | |
| 50 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << | |
| 51 former.ToString(); | |
| 52 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." << | |
| 53 "former:" << former.ToString() << ", latter:" << latter.ToString(); | |
| 54 } | |
| 55 const gfx::StyleRange& end_style = *style_ranges.rbegin(); | |
| 56 DCHECK(!end_style.range.is_empty()) << "Empty range at end."; | |
| 57 DCHECK(end_style.range.IsValid()) << "Invalid range at end."; | |
| 58 DCHECK(!end_style.range.is_reversed()) << "Reversed range at end."; | |
| 59 DCHECK(end_style.range.end() == length) << "Style and text length mismatch."; | |
| 60 } | |
| 61 #endif | |
| 62 | |
| 63 void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges, | |
| 64 const gfx::StyleRange& style_range) { | |
| 65 const ui::Range& new_range = style_range.range; | |
| 66 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges. | |
| 67 gfx::StyleRanges::iterator i; | |
| 68 for (i = style_ranges->begin(); i != style_ranges->end();) { | |
| 69 if (i->range.end() < new_range.start()) { | |
| 70 i++; | |
| 71 } else if (i->range.start() == new_range.end()) { | |
| 72 break; | |
| 73 } else if (new_range.Contains(i->range)) { | |
| 74 i = style_ranges->erase(i); | |
| 75 if (i == style_ranges->end()) | |
| 76 break; | |
| 77 } else if (i->range.start() < new_range.start() && | |
| 78 i->range.end() > new_range.end()) { | |
| 79 // Split the current style into two styles. | |
| 80 gfx::StyleRange split_style = gfx::StyleRange(*i); | |
| 81 split_style.range.set_end(new_range.start()); | |
| 82 i = style_ranges->insert(i, split_style) + 1; | |
| 83 i->range.set_start(new_range.end()); | |
| 84 break; | |
| 85 } else if (i->range.start() < new_range.start()) { | |
| 86 i->range.set_end(new_range.start()); | |
| 87 i++; | |
| 88 } else if (i->range.end() > new_range.end()) { | |
| 89 i->range.set_start(new_range.end()); | |
| 90 break; | |
| 91 } else { | |
| 92 NOTREACHED(); | |
| 93 } | |
| 94 } | |
| 95 // Add the new range in its sorted location. | |
| 96 style_ranges->insert(i, style_range); | |
| 97 } | |
| 98 | 43 |
| 99 // Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags. | 44 // Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags. |
| 100 SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) { | 45 SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) { |
| 101 int skia_style = SkTypeface::kNormal; | 46 int skia_style = SkTypeface::kNormal; |
| 102 if (font_style & gfx::Font::BOLD) | 47 skia_style |= (font_style & gfx::Font::BOLD) ? SkTypeface::kBold : 0; |
| 103 skia_style |= SkTypeface::kBold; | 48 skia_style |= (font_style & gfx::Font::ITALIC) ? SkTypeface::kItalic : 0; |
| 104 if (font_style & gfx::Font::ITALIC) | |
| 105 skia_style |= SkTypeface::kItalic; | |
| 106 return static_cast<SkTypeface::Style>(skia_style); | 49 return static_cast<SkTypeface::Style>(skia_style); |
| 107 } | 50 } |
| 108 | 51 |
| 109 // Given |font| and |display_width|, returns the width of the fade gradient. | 52 // Given |font| and |display_width|, returns the width of the fade gradient. |
| 110 int CalculateFadeGradientWidth(const gfx::Font& font, int display_width) { | 53 int CalculateFadeGradientWidth(const Font& font, int display_width) { |
| 111 // Fade in/out about 2.5 characters of the beginning/end of the string. | 54 // Fade in/out about 2.5 characters of the beginning/end of the string. |
| 112 // The .5 here is helpful if one of the characters is a space. | 55 // The .5 here is helpful if one of the characters is a space. |
| 113 // Use a quarter of the display width if the display width is very short. | 56 // Use a quarter of the display width if the display width is very short. |
| 114 const int average_character_width = font.GetAverageCharacterWidth(); | 57 const int average_character_width = font.GetAverageCharacterWidth(); |
| 115 const double gradient_width = std::min(average_character_width * 2.5, | 58 const double gradient_width = std::min(average_character_width * 2.5, |
| 116 display_width / 4.0); | 59 display_width / 4.0); |
| 117 DCHECK_GE(gradient_width, 0.0); | 60 DCHECK_GE(gradient_width, 0.0); |
| 118 return static_cast<int>(floor(gradient_width + 0.5)); | 61 return static_cast<int>(floor(gradient_width + 0.5)); |
| 119 } | 62 } |
| 120 | 63 |
| 121 // Appends to |positions| and |colors| values corresponding to the fade over | 64 // Appends to |positions| and |colors| values corresponding to the fade over |
| 122 // |fade_rect| from color |c0| to color |c1|. | 65 // |fade_rect| from color |c0| to color |c1|. |
| 123 void AddFadeEffect(const gfx::Rect& text_rect, | 66 void AddFadeEffect(const Rect& text_rect, |
| 124 const gfx::Rect& fade_rect, | 67 const Rect& fade_rect, |
| 125 SkColor c0, | 68 SkColor c0, |
| 126 SkColor c1, | 69 SkColor c1, |
| 127 std::vector<SkScalar>* positions, | 70 std::vector<SkScalar>* positions, |
| 128 std::vector<SkColor>* colors) { | 71 std::vector<SkColor>* colors) { |
| 129 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x()); | 72 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x()); |
| 130 const SkScalar width = static_cast<SkScalar>(fade_rect.width()); | 73 const SkScalar width = static_cast<SkScalar>(fade_rect.width()); |
| 131 const SkScalar p0 = left / text_rect.width(); | 74 const SkScalar p0 = left / text_rect.width(); |
| 132 const SkScalar p1 = (left + width) / text_rect.width(); | 75 const SkScalar p1 = (left + width) / text_rect.width(); |
| 133 // Prepend 0.0 to |positions|, as required by Skia. | 76 // Prepend 0.0 to |positions|, as required by Skia. |
| 134 if (positions->empty() && p0 != 0.0) { | 77 if (positions->empty() && p0 != 0.0) { |
| 135 positions->push_back(0.0); | 78 positions->push_back(0.0); |
| 136 colors->push_back(c0); | 79 colors->push_back(c0); |
| 137 } | 80 } |
| 138 positions->push_back(p0); | 81 positions->push_back(p0); |
| 139 colors->push_back(c0); | 82 colors->push_back(c0); |
| 140 positions->push_back(p1); | 83 positions->push_back(p1); |
| 141 colors->push_back(c1); | 84 colors->push_back(c1); |
| 142 } | 85 } |
| 143 | 86 |
| 144 // Creates a SkShader to fade the text, with |left_part| specifying the left | 87 // Creates a SkShader to fade the text, with |left_part| specifying the left |
| 145 // fade effect, if any, and |right_part| specifying the right fade effect. | 88 // fade effect, if any, and |right_part| specifying the right fade effect. |
| 146 skia::RefPtr<SkShader> CreateFadeShader(const gfx::Rect& text_rect, | 89 skia::RefPtr<SkShader> CreateFadeShader(const Rect& text_rect, |
| 147 const gfx::Rect& left_part, | 90 const Rect& left_part, |
| 148 const gfx::Rect& right_part, | 91 const Rect& right_part, |
| 149 SkColor color) { | 92 SkColor color) { |
| 150 // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color. | 93 // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color. |
| 151 const SkColor fade_color = SkColorSetA(color, 51); | 94 const SkColor fade_color = SkColorSetA(color, 51); |
| 152 std::vector<SkScalar> positions; | 95 std::vector<SkScalar> positions; |
| 153 std::vector<SkColor> colors; | 96 std::vector<SkColor> colors; |
| 154 | 97 |
| 155 if (!left_part.IsEmpty()) | 98 if (!left_part.IsEmpty()) |
| 156 AddFadeEffect(text_rect, left_part, fade_color, color, | 99 AddFadeEffect(text_rect, left_part, fade_color, color, |
| 157 &positions, &colors); | 100 &positions, &colors); |
| 158 if (!right_part.IsEmpty()) | 101 if (!right_part.IsEmpty()) |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 170 points[0].iset(text_rect.x(), text_rect.y()); | 113 points[0].iset(text_rect.x(), text_rect.y()); |
| 171 points[1].iset(text_rect.right(), text_rect.y()); | 114 points[1].iset(text_rect.right(), text_rect.y()); |
| 172 | 115 |
| 173 return skia::AdoptRef( | 116 return skia::AdoptRef( |
| 174 SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0], | 117 SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0], |
| 175 colors.size(), SkShader::kClamp_TileMode)); | 118 colors.size(), SkShader::kClamp_TileMode)); |
| 176 } | 119 } |
| 177 | 120 |
| 178 } // namespace | 121 } // namespace |
| 179 | 122 |
| 180 namespace gfx { | |
| 181 | |
| 182 namespace internal { | 123 namespace internal { |
| 183 | 124 |
| 184 // Value of |underline_thickness_| that indicates that underline metrics have | 125 // Value of |underline_thickness_| that indicates that underline metrics have |
| 185 // not been set explicitly. | 126 // not been set explicitly. |
| 186 const SkScalar kUnderlineMetricsNotSet = -1.0f; | 127 const SkScalar kUnderlineMetricsNotSet = -1.0f; |
| 187 | 128 |
| 188 SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas) | 129 SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas) |
| 189 : canvas_skia_(canvas->sk_canvas()), | 130 : canvas_skia_(canvas->sk_canvas()), |
| 190 started_drawing_(false), | 131 started_drawing_(false), |
| 191 underline_thickness_(kUnderlineMetricsNotSet), | 132 underline_thickness_(kUnderlineMetricsNotSet), |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 286 deferred_fade_shader_ = paint_.getShader(); | 227 deferred_fade_shader_ = paint_.getShader(); |
| 287 paint_.setShader(NULL); | 228 paint_.setShader(NULL); |
| 288 canvas_skia_->saveLayer(&bounds_, NULL); | 229 canvas_skia_->saveLayer(&bounds_, NULL); |
| 289 } | 230 } |
| 290 } | 231 } |
| 291 | 232 |
| 292 const size_t byte_length = glyph_count * sizeof(glyphs[0]); | 233 const size_t byte_length = glyph_count * sizeof(glyphs[0]); |
| 293 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_); | 234 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_); |
| 294 } | 235 } |
| 295 | 236 |
| 296 // Draw underline and strike through text decorations. | 237 void SkiaTextRenderer::DrawDecorations(int x, int y, int width, bool underline, |
| 297 // Based on |SkCanvas::DrawTextDecorations()| and constants from: | 238 bool strike, bool diagonal_strike) { |
| 298 // third_party/skia/src/core/SkTextFormatParams.h | 239 if (underline) |
| 299 void SkiaTextRenderer::DrawDecorations(int x, int y, int width, | 240 DrawUnderline(x, y, width); |
| 300 const StyleRange& style) { | 241 if (strike) |
| 301 if (!style.underline && !style.strike && !style.diagonal_strike) | 242 DrawStrike(x, y, width); |
| 302 return; | 243 if (diagonal_strike) |
| 244 DrawDiagonalStrike(x, y, width); | |
| 245 } | |
| 303 | 246 |
| 304 // Fraction of the text size to lower a strike through below the baseline. | 247 void SkiaTextRenderer::DrawUnderline(int x, int y, int width) { |
| 305 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21); | 248 SkRect r = SkRect::MakeLTRB(x, y + underline_position_, x + width, |
| 306 // Fraction of the text size to lower an underline below the baseline. | 249 y + underline_position_ + underline_thickness_); |
| 307 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); | 250 if (underline_thickness_ == kUnderlineMetricsNotSet) { |
| 308 // Fraction of the text size to use for a strike through or under-line. | 251 const SkScalar text_size = paint_.getTextSize(); |
| 309 const SkScalar kLineThickness = (SK_Scalar1 / 18); | 252 r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y); |
| 310 // Fraction of the text size to use for a top margin of a diagonal strike. | 253 r.fBottom = r.fTop + SkScalarMul(text_size, kLineThickness); |
| 311 const SkScalar kDiagonalStrikeThroughMarginOffset = (SK_Scalar1 / 4); | 254 } |
| 255 canvas_skia_->drawRect(r, paint_); | |
| 256 } | |
| 312 | 257 |
| 313 SkScalar text_size = paint_.getTextSize(); | 258 void SkiaTextRenderer::DrawStrike(int x, int y, int width) const { |
| 314 SkScalar height = SkScalarMul(text_size, kLineThickness); | 259 const SkScalar text_size = paint_.getTextSize(); |
| 315 SkRect r; | 260 const SkScalar height = SkScalarMul(text_size, kLineThickness); |
| 261 const SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y); | |
| 262 const SkRect r = SkRect::MakeLTRB(x, offset, x + width, offset + height); | |
| 263 canvas_skia_->drawRect(r, paint_); | |
| 264 } | |
| 316 | 265 |
| 317 r.fLeft = x; | 266 void SkiaTextRenderer::DrawDiagonalStrike(int x, int y, int width) const { |
| 318 r.fRight = x + width; | 267 const SkScalar text_size = paint_.getTextSize(); |
| 268 const SkScalar offset = SkScalarMul(text_size, kDiagonalStrikeMarginOffset); | |
| 319 | 269 |
| 320 if (style.underline) { | 270 SkPaint paint(paint_); |
| 321 if (underline_thickness_ == kUnderlineMetricsNotSet) { | 271 paint.setAntiAlias(true); |
| 322 r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y); | 272 paint.setStyle(SkPaint::kFill_Style); |
| 323 r.fBottom = r.fTop + height; | 273 paint.setStrokeWidth(SkScalarMul(text_size, kLineThickness) * 2); |
| 324 } else { | 274 canvas_skia_->drawLine(x, y, x + width, y - text_size + offset, paint); |
| 325 r.fTop = y + underline_position_; | |
| 326 r.fBottom = r.fTop + underline_thickness_; | |
| 327 } | |
| 328 canvas_skia_->drawRect(r, paint_); | |
| 329 } | |
| 330 if (style.strike) { | |
| 331 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y); | |
| 332 r.fTop = offset; | |
| 333 r.fBottom = offset + height; | |
| 334 canvas_skia_->drawRect(r, paint_); | |
| 335 } | |
| 336 if (style.diagonal_strike) { | |
| 337 SkScalar offset = | |
| 338 SkScalarMul(text_size, kDiagonalStrikeThroughMarginOffset); | |
| 339 SkPaint paint(paint_); | |
| 340 paint.setAntiAlias(true); | |
| 341 paint.setStyle(SkPaint::kFill_Style); | |
| 342 paint.setStrokeWidth(height * 2); | |
| 343 canvas_skia_->drawLine( | |
| 344 SkIntToScalar(x), SkIntToScalar(y), | |
| 345 SkIntToScalar(x + width), SkIntToScalar(y) - text_size + offset, | |
| 346 paint); | |
| 347 } | |
| 348 } | 275 } |
| 349 | 276 |
| 350 } // namespace internal | 277 } // namespace internal |
| 351 | 278 |
| 352 | |
| 353 StyleRange::StyleRange() | |
| 354 : foreground(SK_ColorBLACK), | |
| 355 font_style(gfx::Font::NORMAL), | |
| 356 strike(false), | |
| 357 diagonal_strike(false), | |
| 358 underline(false) { | |
| 359 } | |
| 360 | |
| 361 RenderText::~RenderText() { | 279 RenderText::~RenderText() { |
| 362 } | 280 } |
| 363 | 281 |
| 364 void RenderText::SetText(const string16& text) { | 282 void RenderText::SetText(const string16& text) { |
| 365 DCHECK(!composition_range_.IsValid()); | 283 DCHECK(!composition_range_.IsValid()); |
| 366 size_t old_text_length = text_.length(); | 284 const size_t old_length = text_.length(); |
| 367 text_ = text; | 285 text_ = text; |
| 368 | 286 |
| 369 // Update the style ranges as needed. | 287 // Adjust any ranged styles and colors to accommodate a new text length. |
| 370 if (text_.empty()) { | 288 const size_t new_length = text_.length(); |
| 371 style_ranges_.clear(); | 289 if (new_length < old_length) { |
| 372 } else if (style_ranges_.empty()) { | 290 colors_.TrimBreaks(new_length); |
| 373 ApplyDefaultStyle(); | 291 for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) |
| 374 } else if (text_.length() > old_text_length) { | 292 styles_[style].TrimBreaks(new_length); |
| 375 style_ranges_.back().range.set_end(text_.length()); | |
| 376 } else if (text_.length() < old_text_length) { | |
| 377 StyleRanges::iterator i; | |
| 378 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) { | |
| 379 if (i->range.start() >= text_.length()) { | |
| 380 // Style ranges are sorted and non-overlapping, so all the subsequent | |
| 381 // style ranges should be out of text_.length() as well. | |
| 382 style_ranges_.erase(i, style_ranges_.end()); | |
| 383 break; | |
| 384 } | |
| 385 } | |
| 386 // Since style ranges are sorted and non-overlapping, if there is a style | |
| 387 // range ends beyond text_.length, it must be the last one. | |
| 388 style_ranges_.back().range.set_end(text_.length()); | |
| 389 } | 293 } |
| 390 #ifndef NDEBUG | |
| 391 CheckStyleRanges(style_ranges_, text_.length()); | |
| 392 #endif | |
| 393 cached_bounds_and_offset_valid_ = false; | 294 cached_bounds_and_offset_valid_ = false; |
| 394 | 295 |
| 395 // Reset selection model. SetText should always followed by SetSelectionModel | 296 // Reset selection model. SetText should always followed by SetSelectionModel |
| 396 // or SetCursorPosition in upper layer. | 297 // or SetCursorPosition in upper layer. |
| 397 SetSelectionModel(SelectionModel()); | 298 SetSelectionModel(SelectionModel()); |
| 398 | 299 |
| 399 // Invalidate the cached text direction if it depends on the text contents. | 300 // Invalidate the cached text direction if it depends on the text contents. |
| 400 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) | 301 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) |
| 401 text_direction_ = base::i18n::UNKNOWN_DIRECTION; | 302 text_direction_ = base::i18n::UNKNOWN_DIRECTION; |
| 402 | 303 |
| (...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 575 } | 476 } |
| 576 | 477 |
| 577 void RenderText::SetCompositionRange(const ui::Range& composition_range) { | 478 void RenderText::SetCompositionRange(const ui::Range& composition_range) { |
| 578 CHECK(!composition_range.IsValid() || | 479 CHECK(!composition_range.IsValid() || |
| 579 ui::Range(0, text_.length()).Contains(composition_range)); | 480 ui::Range(0, text_.length()).Contains(composition_range)); |
| 580 composition_range_.set_end(composition_range.end()); | 481 composition_range_.set_end(composition_range.end()); |
| 581 composition_range_.set_start(composition_range.start()); | 482 composition_range_.set_start(composition_range.start()); |
| 582 ResetLayout(); | 483 ResetLayout(); |
| 583 } | 484 } |
| 584 | 485 |
| 585 void RenderText::ApplyStyleRange(const StyleRange& style_range) { | 486 void RenderText::SetColor(SkColor value) { |
| 586 const ui::Range& new_range = style_range.range; | 487 // TODO(msw): Fix Windows invalidation and use colors_.SetValue(value). |
| 587 if (!new_range.IsValid() || new_range.is_empty()) | 488 ApplyColor(value, ui::Range(0, text().length())); |
| 588 return; | 489 } |
| 589 CHECK(!new_range.is_reversed()); | 490 |
| 590 CHECK(ui::Range(0, text_.length()).Contains(new_range)); | 491 void RenderText::ApplyColor(SkColor value, const ui::Range& range) { |
| 591 ApplyStyleRangeImpl(&style_ranges_, style_range); | 492 DCHECK(ui::Range(0, text().length()).Contains(range)); |
| 592 #ifndef NDEBUG | 493 colors_.ApplyValue(value, range.end() >= text().length() ? |
| 593 CheckStyleRanges(style_ranges_, text_.length()); | 494 ui::Range(range.start(), INT_MAX) : range); |
| 594 #endif | 495 |
| 595 // TODO(xji): only invalidate if font or underline changes. | 496 #if defined(OS_WIN) |
| 497 // TODO(msw): Windows applies colors and decorations in the layout process. | |
| 596 cached_bounds_and_offset_valid_ = false; | 498 cached_bounds_and_offset_valid_ = false; |
| 597 ResetLayout(); | 499 ResetLayout(); |
| 500 #endif | |
| 598 } | 501 } |
| 599 | 502 |
| 600 void RenderText::ApplyDefaultStyle() { | 503 void RenderText::SetStyle(TextStyle style, bool value) { |
| 601 style_ranges_.clear(); | 504 // TODO(msw): Fix Windows invalidation and use styles_[style].SetValue(value). |
| 602 StyleRange style = StyleRange(default_style_); | 505 ApplyStyle(style, value, ui::Range(0, text().length())); |
| 603 style.range.set_end(text_.length()); | 506 } |
| 604 style_ranges_.push_back(style); | 507 |
| 605 cached_bounds_and_offset_valid_ = false; | 508 void RenderText::ApplyStyle(TextStyle style, |
| 606 ResetLayout(); | 509 bool value, |
| 510 const ui::Range& range) { | |
| 511 DCHECK(ui::Range(0, text().length()).Contains(range)); | |
| 512 styles_[style].ApplyValue(value, range.end() >= text().length() ? | |
| 513 ui::Range(range.start(), INT_MAX) : range); | |
| 514 | |
| 515 // Only invalidate the layout on font changes; not for colors or decorations. | |
| 516 bool invalidate = (style == BOLD) || (style == UNDERLINE); | |
|
Alexei Svitkine (slow)
2013/01/28 20:52:30
Don't you mean (style == BOLD) || (style == ITALIC
msw
2013/01/29 17:17:25
Done; thanks for the catch!
| |
| 517 #if defined(OS_WIN) | |
| 518 // TODO(msw): Windows applies colors and decorations in the layout process. | |
| 519 invalidate = true; | |
| 520 #endif | |
| 521 if (invalidate) { | |
| 522 cached_bounds_and_offset_valid_ = false; | |
| 523 ResetLayout(); | |
| 524 } | |
| 607 } | 525 } |
| 608 | 526 |
| 609 void RenderText::SetDirectionalityMode(DirectionalityMode mode) { | 527 void RenderText::SetDirectionalityMode(DirectionalityMode mode) { |
| 610 if (mode == directionality_mode_) | 528 if (mode == directionality_mode_) |
| 611 return; | 529 return; |
| 612 | 530 |
| 613 directionality_mode_ = mode; | 531 directionality_mode_ = mode; |
| 614 text_direction_ = base::i18n::UNKNOWN_DIRECTION; | 532 text_direction_ = base::i18n::UNKNOWN_DIRECTION; |
| 615 ResetLayout(); | 533 ResetLayout(); |
| 616 } | 534 } |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 644 | 562 |
| 645 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { | 563 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { |
| 646 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ? | 564 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ? |
| 647 CURSOR_RIGHT : CURSOR_LEFT; | 565 CURSOR_RIGHT : CURSOR_LEFT; |
| 648 } | 566 } |
| 649 | 567 |
| 650 void RenderText::Draw(Canvas* canvas) { | 568 void RenderText::Draw(Canvas* canvas) { |
| 651 EnsureLayout(); | 569 EnsureLayout(); |
| 652 | 570 |
| 653 if (clip_to_display_rect()) { | 571 if (clip_to_display_rect()) { |
| 654 gfx::Rect clip_rect(display_rect()); | 572 Rect clip_rect(display_rect()); |
| 655 clip_rect.Inset(ShadowValue::GetMargin(text_shadows_)); | 573 clip_rect.Inset(ShadowValue::GetMargin(text_shadows_)); |
| 656 | 574 |
| 657 canvas->Save(); | 575 canvas->Save(); |
| 658 canvas->ClipRect(clip_rect); | 576 canvas->ClipRect(clip_rect); |
| 659 } | 577 } |
| 660 | 578 |
| 661 if (!text().empty()) | 579 if (!text().empty()) |
| 662 DrawSelection(canvas); | 580 DrawSelection(canvas); |
| 663 | 581 |
| 664 DrawCursor(canvas); | 582 DrawCursor(canvas); |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 745 text_shadows_ = shadows; | 663 text_shadows_ = shadows; |
| 746 } | 664 } |
| 747 | 665 |
| 748 RenderText::RenderText() | 666 RenderText::RenderText() |
| 749 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT), | 667 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT), |
| 750 directionality_mode_(DIRECTIONALITY_FROM_TEXT), | 668 directionality_mode_(DIRECTIONALITY_FROM_TEXT), |
| 751 text_direction_(base::i18n::UNKNOWN_DIRECTION), | 669 text_direction_(base::i18n::UNKNOWN_DIRECTION), |
| 752 cursor_enabled_(true), | 670 cursor_enabled_(true), |
| 753 cursor_visible_(false), | 671 cursor_visible_(false), |
| 754 insert_mode_(true), | 672 insert_mode_(true), |
| 755 cursor_color_(kDefaultCursorColor), | 673 cursor_color_(kDefaultColor), |
| 756 selection_color_(kDefaultSelectionColor), | 674 selection_color_(kDefaultColor), |
| 757 selection_background_focused_color_(kDefaultSelectionBackgroundColor), | 675 selection_background_focused_color_(kDefaultSelectionBackgroundColor), |
| 758 selection_background_unfocused_color_(kDefaultSelectionBackgroundColor), | 676 selection_background_unfocused_color_(kDefaultSelectionBackgroundColor), |
| 759 focused_(false), | 677 focused_(false), |
| 760 composition_range_(ui::Range::InvalidRange()), | 678 composition_range_(ui::Range::InvalidRange()), |
| 679 colors_(kDefaultColor), | |
| 761 obscured_(false), | 680 obscured_(false), |
| 762 fade_head_(false), | 681 fade_head_(false), |
| 763 fade_tail_(false), | 682 fade_tail_(false), |
| 764 background_is_transparent_(false), | 683 background_is_transparent_(false), |
| 765 clip_to_display_rect_(true), | 684 clip_to_display_rect_(true), |
| 766 cached_bounds_and_offset_valid_(false) { | 685 cached_bounds_and_offset_valid_(false) { |
| 767 } | 686 } |
| 768 | 687 |
| 769 const Vector2d& RenderText::GetUpdatedDisplayOffset() { | 688 const Vector2d& RenderText::GetUpdatedDisplayOffset() { |
| 770 UpdateCachedBoundsAndOffset(); | 689 UpdateCachedBoundsAndOffset(); |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 795 void RenderText::SetSelectionModel(const SelectionModel& model) { | 714 void RenderText::SetSelectionModel(const SelectionModel& model) { |
| 796 DCHECK_LE(model.selection().GetMax(), text().length()); | 715 DCHECK_LE(model.selection().GetMax(), text().length()); |
| 797 selection_model_ = model; | 716 selection_model_ = model; |
| 798 cached_bounds_and_offset_valid_ = false; | 717 cached_bounds_and_offset_valid_ = false; |
| 799 } | 718 } |
| 800 | 719 |
| 801 const string16& RenderText::GetLayoutText() const { | 720 const string16& RenderText::GetLayoutText() const { |
| 802 return obscured() ? obscured_text_ : text(); | 721 return obscured() ? obscured_text_ : text(); |
| 803 } | 722 } |
| 804 | 723 |
| 805 void RenderText::ApplyCompositionAndSelectionStyles( | 724 size_t RenderText::GetNextBreak( |
| 806 StyleRanges* style_ranges) { | 725 const BreakList<SkColor>::const_iterator& color, |
| 807 // TODO(msw): This pattern ought to be reconsidered; what about composition | 726 const BreakList<bool>::const_iterator style[NUM_TEXT_STYLES]) const { |
| 808 // and selection overlaps, retain existing local style features? | 727 size_t next_break = colors_.GetBreakEnd(color); |
| 809 // Apply a composition style override to a copy of the style ranges. | 728 for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) |
| 810 if (composition_range_.IsValid() && !composition_range_.is_empty()) { | 729 next_break = std::min(next_break, styles_[i].GetBreakEnd(style[i])); |
| 811 StyleRange composition_style(default_style_); | 730 return TextIndexToLayoutIndex(std::min(text().length(), next_break)); |
| 812 composition_style.underline = true; | 731 } |
| 813 composition_style.range = composition_range_; | 732 |
| 814 ApplyStyleRangeImpl(style_ranges, composition_style); | 733 void RenderText::AdvanceIterators( |
| 734 size_t layout_index, | |
| 735 BreakList<SkColor>::const_iterator* color, | |
| 736 BreakList<bool>::const_iterator style[NUM_TEXT_STYLES]) const { | |
| 737 const size_t text_index = LayoutIndexToTextIndex(layout_index); | |
| 738 // Note: Using BreakList::GetBreak() would generalize better but run slower. | |
| 739 *color += colors_.GetBreakEnd(*color) <= text_index ? 1 : 0; | |
|
Alexei Svitkine (slow)
2013/01/28 20:52:30
Can you add a DCHECK() that the new |color| has a
msw
2013/01/29 17:17:25
Moot, now BreakList::GetBreak(), as per your comme
| |
| 740 for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) | |
| 741 style[i] += styles_[i].GetBreakEnd(style[i]) <= text_index ? 1 : 0; | |
|
Alexei Svitkine (slow)
2013/01/28 20:52:30
Also, DCHECK() here, same as my comment above.
msw
2013/01/29 17:17:25
Moot, now BreakList::GetBreak(), as per your comme
| |
| 742 } | |
| 743 | |
| 744 void RenderText::ApplyCompositionAndSelectionStyles() { | |
| 745 // Save the underline and color lists to undo the temporary styles later. | |
| 746 styles_[UNDERLINE].SaveList(); | |
| 747 colors_.SaveList(); | |
| 748 | |
| 749 // Apply an underline to the composition range in |underlines|. | |
| 750 if (composition_range_.IsValid() && !composition_range_.is_empty()) | |
| 751 styles_[UNDERLINE].ApplyValue(true, composition_range_); | |
| 752 | |
| 753 // Apply the selected text color to the selection range. | |
| 754 if (!selection().is_empty()) { | |
| 755 const ui::Range range(selection().GetMin(), selection().GetMax()); | |
|
Alexei Svitkine (slow)
2013/01/28 20:52:30
Is there a reason you can't just pass selection()
msw
2013/01/29 17:17:25
ApplyValue (like ApplyStyleRangeImpl) won't correc
| |
| 756 colors_.ApplyValue(selection_color_, range); | |
| 815 } | 757 } |
| 816 // Apply a selection style override to a copy of the style ranges. | 758 // Apply the selected text color to the cursor range in overtype mode. |
| 817 if (!selection().is_empty()) { | 759 if (!insert_mode_ && cursor_visible() && focused()) { |
| 818 StyleRange selection_style(default_style_); | 760 const size_t cursor = cursor_position(); |
| 819 selection_style.foreground = selection_color_; | 761 const size_t next = IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD); |
| 820 selection_style.range = ui::Range(selection().GetMin(), | 762 colors_.ApplyValue(selection_color_, ui::Range(cursor, next)); |
| 821 selection().GetMax()); | |
| 822 ApplyStyleRangeImpl(style_ranges, selection_style); | |
| 823 } | 763 } |
| 824 // Apply replacement-mode style override to a copy of the style ranges. | 764 } |
| 825 // | 765 |
| 826 // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to | 766 void RenderText::UndoCompositionAndSelectionStyles() { |
|
Alexei Svitkine (slow)
2013/01/28 20:52:30
Can you add a bool to keep track of whether the se
msw
2013/01/29 17:17:25
Done.
| |
| 827 // apply styles) in ItemizeLogicalText(). In order for the cursor's underline | 767 // Restore the underline and color lists to undo the temporary styles. |
| 828 // character to be drawn correctly, we will need to re-layout the text. It's | 768 styles_[UNDERLINE].RestoreList(); |
| 829 // not practical to do layout on every cursor blink. We need to fix Windows | 769 colors_.RestoreList(); |
| 830 // port to apply styles during drawing phase like Linux port does. | |
| 831 // http://crbug.com/110109 | |
| 832 if (!insert_mode_ && cursor_visible() && focused()) { | |
| 833 StyleRange replacement_mode_style(default_style_); | |
| 834 replacement_mode_style.foreground = selection_color_; | |
| 835 size_t cursor = cursor_position(); | |
| 836 replacement_mode_style.range.set_start(cursor); | |
| 837 replacement_mode_style.range.set_end( | |
| 838 IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD)); | |
| 839 ApplyStyleRangeImpl(style_ranges, replacement_mode_style); | |
| 840 } | |
| 841 } | 770 } |
| 842 | 771 |
| 843 Vector2d RenderText::GetTextOffset() { | 772 Vector2d RenderText::GetTextOffset() { |
| 844 Vector2d offset = display_rect().OffsetFromOrigin(); | 773 Vector2d offset = display_rect().OffsetFromOrigin(); |
| 845 offset.Add(GetUpdatedDisplayOffset()); | 774 offset.Add(GetUpdatedDisplayOffset()); |
| 846 offset.Add(GetAlignmentOffset()); | 775 offset.Add(GetAlignmentOffset()); |
| 847 return offset; | 776 return offset; |
| 848 } | 777 } |
| 849 | 778 |
| 850 Point RenderText::ToTextPoint(const Point& point) { | 779 Point RenderText::ToTextPoint(const Point& point) { |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 892 | 821 |
| 893 bool fade_left = fade_head(); | 822 bool fade_left = fade_head(); |
| 894 bool fade_right = fade_tail(); | 823 bool fade_right = fade_tail(); |
| 895 // Under RTL, |fade_right| == |fade_head|. | 824 // Under RTL, |fade_right| == |fade_head|. |
| 896 // TODO(asvitkine): This is currently not based on GetTextDirection() because | 825 // TODO(asvitkine): This is currently not based on GetTextDirection() because |
| 897 // RenderTextWin does not return a direction that's based on | 826 // RenderTextWin does not return a direction that's based on |
| 898 // the text content. | 827 // the text content. |
| 899 if (horizontal_alignment() == ALIGN_RIGHT) | 828 if (horizontal_alignment() == ALIGN_RIGHT) |
| 900 std::swap(fade_left, fade_right); | 829 std::swap(fade_left, fade_right); |
| 901 | 830 |
| 902 gfx::Rect solid_part = display_rect(); | 831 Rect solid_part = display_rect(); |
| 903 gfx::Rect left_part; | 832 Rect left_part; |
| 904 gfx::Rect right_part; | 833 Rect right_part; |
| 905 if (fade_left) { | 834 if (fade_left) { |
| 906 left_part = solid_part; | 835 left_part = solid_part; |
| 907 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0); | 836 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0); |
| 908 solid_part.Inset(gradient_width, 0, 0, 0); | 837 solid_part.Inset(gradient_width, 0, 0, 0); |
| 909 } | 838 } |
| 910 if (fade_right) { | 839 if (fade_right) { |
| 911 right_part = solid_part; | 840 right_part = solid_part; |
| 912 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0); | 841 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0); |
| 913 solid_part.Inset(0, 0, gradient_width, 0); | 842 solid_part.Inset(0, 0, gradient_width, 0); |
| 914 } | 843 } |
| 915 | 844 |
| 916 gfx::Rect text_rect = display_rect(); | 845 Rect text_rect = display_rect(); |
| 917 text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0); | 846 text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0); |
| 918 | 847 |
| 919 const SkColor color = default_style().foreground; | 848 skia::RefPtr<SkShader> shader = CreateFadeShader( |
| 920 skia::RefPtr<SkShader> shader = | 849 text_rect, left_part, right_part, colors_.list().front().second); |
| 921 CreateFadeShader(text_rect, left_part, right_part, color); | |
| 922 if (shader) | 850 if (shader) |
| 923 renderer->SetShader(shader.get(), display_rect()); | 851 renderer->SetShader(shader.get(), display_rect()); |
| 924 } | 852 } |
| 925 | 853 |
| 926 void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { | 854 void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { |
| 927 skia::RefPtr<SkDrawLooper> looper = | 855 skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(text_shadows_); |
| 928 gfx::CreateShadowDrawLooper(text_shadows_); | |
| 929 renderer->SetDrawLooper(looper.get()); | 856 renderer->SetDrawLooper(looper.get()); |
| 930 } | 857 } |
| 931 | 858 |
| 932 // static | 859 // static |
| 933 bool RenderText::RangeContainsCaret(const ui::Range& range, | 860 bool RenderText::RangeContainsCaret(const ui::Range& range, |
| 934 size_t caret_pos, | 861 size_t caret_pos, |
| 935 LogicalCursorDirection caret_affinity) { | 862 LogicalCursorDirection caret_affinity) { |
| 936 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9). | 863 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9). |
| 937 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ? | 864 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ? |
| 938 caret_pos - 1 : caret_pos + 1; | 865 caret_pos - 1 : caret_pos + 1; |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 992 } else if (display_offset_.x() != 0) { | 919 } else if (display_offset_.x() != 0) { |
| 993 // Reduce the pan offset to show additional overflow text when the display | 920 // Reduce the pan offset to show additional overflow text when the display |
| 994 // width increases. | 921 // width increases. |
| 995 const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1; | 922 const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1; |
| 996 const int offset = negate_rtl * display_offset_.x(); | 923 const int offset = negate_rtl * display_offset_.x(); |
| 997 if (display_width > (content_width + offset)) { | 924 if (display_width > (content_width + offset)) { |
| 998 delta_x = negate_rtl * (display_width - (content_width + offset)); | 925 delta_x = negate_rtl * (display_width - (content_width + offset)); |
| 999 } | 926 } |
| 1000 } | 927 } |
| 1001 | 928 |
| 1002 gfx::Vector2d delta_offset(delta_x, 0); | 929 Vector2d delta_offset(delta_x, 0); |
| 1003 display_offset_ += delta_offset; | 930 display_offset_ += delta_offset; |
| 1004 cursor_bounds_ += delta_offset; | 931 cursor_bounds_ += delta_offset; |
| 1005 } | 932 } |
| 1006 | 933 |
| 1007 void RenderText::DrawSelection(Canvas* canvas) { | 934 void RenderText::DrawSelection(Canvas* canvas) { |
| 1008 const SkColor color = focused() ? selection_background_focused_color_ : | 935 const SkColor color = focused() ? selection_background_focused_color_ : |
| 1009 selection_background_unfocused_color_; | 936 selection_background_unfocused_color_; |
| 1010 const std::vector<Rect> sel = GetSubstringBounds(selection()); | 937 const std::vector<Rect> sel = GetSubstringBounds(selection()); |
| 1011 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) | 938 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) |
| 1012 canvas->FillRect(*i, color); | 939 canvas->FillRect(*i, color); |
| 1013 } | 940 } |
| 1014 | 941 |
| 1015 void RenderText::DrawCursor(Canvas* canvas) { | 942 void RenderText::DrawCursor(Canvas* canvas) { |
| 1016 // Paint cursor. Replace cursor is drawn as rectangle for now. | 943 // Paint cursor. Replace cursor is drawn as rectangle for now. |
| 1017 // TODO(msw): Draw a better cursor with a better indication of association. | 944 // TODO(msw): Draw a better cursor with a better indication of association. |
| 1018 if (cursor_enabled() && cursor_visible() && focused()) { | 945 if (cursor_enabled() && cursor_visible() && focused()) { |
| 1019 const Rect& bounds = GetUpdatedCursorBounds(); | 946 const Rect& bounds = GetUpdatedCursorBounds(); |
| 1020 DCHECK(bounds.width()); | 947 DCHECK(bounds.width()); |
| 1021 canvas->FillRect(bounds, cursor_color_); | 948 canvas->FillRect(bounds, cursor_color_); |
| 1022 } | 949 } |
| 1023 } | 950 } |
| 1024 | 951 |
| 1025 } // namespace gfx | 952 } // namespace gfx |
| OLD | NEW |