diff options
author | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-01 04:42:17 +0000 |
---|---|---|
committer | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-02-01 04:42:17 +0000 |
commit | ccfa43f042db868f6584f9cceef94ca7c4ddf239 (patch) | |
tree | f3f3980981147928cae30754448f88d45527d2d0 /ui | |
parent | 8c49a50da6675ed9329b01a1e960c78e42d4ba82 (diff) | |
download | chromium_src-ccfa43f042db868f6584f9cceef94ca7c4ddf239.zip chromium_src-ccfa43f042db868f6584f9cceef94ca7c4ddf239.tar.gz chromium_src-ccfa43f042db868f6584f9cceef94ca7c4ddf239.tar.bz2 |
Replace StyleRange with BreakList; update RenderText, etc.
This is a functional rewrite with no observable behavior/appearance changes.
(it helps by merging adjacent equivalent styles, reducing artificial run breaks)
(it helps disambiguate font/adornment styles for application in layout/drawing)
Remove gfx::StyleRange and its use within gfx::RenderText[Win|Linux|Mac].
Add new BreakList class for managing [ranged] colors and styles; add/update tests.
Add gfx::TextStyle enum for bold, italic, underline, strike, and diagonal strike.
Split ApplyStyleRange into [Set|Apply]Color and [Set|Apply]Style.
Split ApplyDefaultStyle and |default_style_| into the first colors_ and styles_.
Split up SkiaTextRenderer::DrawDecorations for Underline/Strike/DiagonalStrike.
Update ApplyCompositionAndSelectionStyles, add UndoCompositionAndSelectionStyles.
Add temporary StyleIterator convenience class for RenderText subclass style iteration.
Update RenderText[Win|Linux|Mac], Textfield classes, and other users.
Simplify OmniboxResultView (nix bold font, and ClassificationData).
Rename gfx::Font::FontStyle::UNDERLINE (was UNDERLINED);
TODO(followup): Only break runs for bold/italic, color/adorn while drawing.
TODO(followup): Support more custom/ranged colors; merge TextStyle/FontStyle?
BUG=90426,164047,131660
TEST=No observable appearance/performance/behavior changes.
R=asvitkine@chromium.org,pkasting@chromium.org,sky@chromium.org
Review URL: https://chromiumcodereview.appspot.com/11535014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@180067 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
32 files changed, 1016 insertions, 817 deletions
diff --git a/ui/app_list/views/search_result_view.cc b/ui/app_list/views/search_result_view.cc index 47b9825..85caf54 100644 --- a/ui/app_list/views/search_result_view.cc +++ b/ui/app_list/views/search_result_view.cc @@ -64,11 +64,7 @@ gfx::RenderText* CreateRenderText(const string16& text, const app_list::SearchResult::Tags& tags) { gfx::RenderText* render_text = gfx::RenderText::CreateInstance(); render_text->SetText(text); - - gfx::StyleRange default_style; - default_style.foreground = kDefaultTextColor; - render_text->set_default_style(default_style); - render_text->ApplyDefaultStyle(); + render_text->SetColor(kDefaultTextColor); for (app_list::SearchResult::Tags::const_iterator it = tags.begin(); it != tags.end(); @@ -77,17 +73,12 @@ gfx::RenderText* CreateRenderText(const string16& text, if (it->styles == app_list::SearchResult::Tag::NONE) continue; - gfx::StyleRange style; - style.range = it->range; - if (it->styles & app_list::SearchResult::Tag::MATCH) - style.font_style = gfx::Font::BOLD; - if (it->styles & app_list::SearchResult::Tag::URL) - style.foreground = kURLTextColor; + render_text->ApplyStyle(gfx::BOLD, true, it->range); if (it->styles & app_list::SearchResult::Tag::DIM) - style.foreground = kDimmedTextColor; - - render_text->ApplyStyleRange(style); + render_text->ApplyColor(kDimmedTextColor, it->range); + else if (it->styles & app_list::SearchResult::Tag::URL) + render_text->ApplyColor(kURLTextColor, it->range); } return render_text; diff --git a/ui/gfx/break_list.h b/ui/gfx/break_list.h new file mode 100644 index 0000000..c380222 --- /dev/null +++ b/ui/gfx/break_list.h @@ -0,0 +1,165 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BREAK_LIST_H_ +#define UI_GFX_BREAK_LIST_H_ + +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "ui/base/range/range.h" + +namespace gfx { + +// BreakLists manage ordered, non-overlapping, and non-repeating ranged values. +// These may be used to apply ranged colors and styles to text, for an example. +// +// Each break stores the start position and value of its associated range. +// A solitary break at position 0 applies to the entire space [0, max_). +// |max_| is initially 0 and should be set to match the available ranged space. +// The first break always has position 0, to ensure all positions have a value. +// The value of the terminal break applies to the range [break.first, max_). +// The value of other breaks apply to the range [break.first, (break+1).first). +template <typename T> +class BreakList { + public: + // The break type and const iterator, typedef'ed for convenience. + typedef std::pair<size_t, T> Break; + typedef typename std::vector<Break>::const_iterator const_iterator; + + // Initialize a break at position 0 with the default or supplied |value|. + BreakList(); + explicit BreakList(T value); + + const std::vector<Break>& breaks() const { return breaks_; } + + // Clear the breaks and set a break at position 0 with the supplied |value|. + void SetValue(T value); + + // Adjust the breaks to apply |value| over the supplied |range|. + void ApplyValue(T value, const ui::Range& range); + + // Set the max position and trim any breaks at or beyond that position. + void SetMax(size_t max); + + // Get the break applicable to |position| (at or preceeding |position|). + typename std::vector<Break>::iterator GetBreak(size_t position); + + // Get the range of the supplied break; returns the break's start position and + // the next break's start position (or |max_| for the terminal break). + ui::Range GetRange(const typename BreakList<T>::const_iterator& i) const; + + // Comparison functions for testing purposes. + bool EqualsValueForTesting(T value) const; + bool EqualsForTesting(const std::vector<Break>& breaks) const; + + private: +#ifndef NDEBUG + // Check for ordered breaks [0, |max_|) with no adjacent equivalent values. + void CheckBreaks(); +#endif + + std::vector<Break> breaks_; + size_t max_; +}; + +template<class T> +BreakList<T>::BreakList() : breaks_(1, Break(0, T())), max_(0) { +} + +template<class T> +BreakList<T>::BreakList(T value) : breaks_(1, Break(0, value)), max_(0) { +} + +template<class T> +void BreakList<T>::SetValue(T value) { + breaks_.clear(); + breaks_.push_back(Break(0, value)); +} + +template<class T> +void BreakList<T>::ApplyValue(T value, const ui::Range& range) { + if (!range.IsValid() || range.is_empty()) + return; + DCHECK(!breaks_.empty()); + DCHECK(!range.is_reversed()); + DCHECK(ui::Range(0, max_).Contains(range)); + + // Erase any breaks in |range|, then add start and end breaks as needed. + typename std::vector<Break>::iterator start = GetBreak(range.start()); + start += start->first < range.start() ? 1 : 0; + typename std::vector<Break>::iterator end = GetBreak(range.end()); + T trailing_value = end->second; + typename std::vector<Break>::iterator i = + start == breaks_.end() ? start : breaks_.erase(start, end + 1); + if (range.start() == 0 || (i - 1)->second != value) + i = breaks_.insert(i, Break(range.start(), value)) + 1; + if (trailing_value != value && range.end() != max_) + breaks_.insert(i, Break(range.end(), trailing_value)); + +#ifndef NDEBUG + CheckBreaks(); +#endif +} + +template<class T> +void BreakList<T>::SetMax(size_t max) { + typename std::vector<Break>::iterator i = GetBreak(max); + i += (i == breaks_.begin() || i->first < max) ? 1 : 0; + breaks_.erase(i, breaks_.end()); + max_ = max; + +#ifndef NDEBUG + CheckBreaks(); +#endif +} + +template<class T> +typename std::vector<std::pair<size_t, T> >::iterator BreakList<T>::GetBreak( + size_t position) { + typename std::vector<Break>::iterator i = breaks_.end() - 1; + for (; i != breaks_.begin() && i->first > position; --i); + return i; +} + +template<class T> +ui::Range BreakList<T>::GetRange( + const typename BreakList<T>::const_iterator& i) const { + const typename BreakList<T>::const_iterator next = i + 1; + return ui::Range(i->first, next == breaks_.end() ? max_ : next->first); +} + +template<class T> +bool BreakList<T>::EqualsValueForTesting(T value) const { + return breaks_.size() == 1 && breaks_[0] == Break(0, value); +} + +template<class T> +bool BreakList<T>::EqualsForTesting(const std::vector<Break>& breaks) const { + if (breaks_.size() != breaks.size()) + return false; + for (size_t i = 0; i < breaks.size(); ++i) + if (breaks_[i] != breaks[i]) + return false; + return true; +} + +#ifndef NDEBUG +template <class T> +void BreakList<T>::CheckBreaks() { + DCHECK_EQ(breaks_[0].first, 0U) << "The first break must be at position 0."; + for (size_t i = 0; i < breaks_.size() - 1; ++i) { + DCHECK_LT(breaks_[i].first, breaks_[i + 1].first) << "Break out of order."; + DCHECK_NE(breaks_[i].second, breaks_[i + 1].second) << "Redundant break."; + } + if (max_ > 0) + DCHECK_LT(breaks_.back().first, max_) << "Break beyond max position."; +} +#endif + +} // namespace gfx + +#endif // UI_GFX_BREAK_LIST_H_ diff --git a/ui/gfx/break_list_unittest.cc b/ui/gfx/break_list_unittest.cc new file mode 100644 index 0000000..4acb0d2 --- /dev/null +++ b/ui/gfx/break_list_unittest.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/break_list.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/base/range/range.h" + +namespace gfx { + +class BreakListTest : public testing::Test {}; + +TEST_F(BreakListTest, SetValue) { + // Check the default values applied to new instances. + BreakList<bool> style_breaks(false); + EXPECT_TRUE(style_breaks.EqualsValueForTesting(false)); + style_breaks.SetValue(true); + EXPECT_TRUE(style_breaks.EqualsValueForTesting(true)); + + // Ensure that setting values works correctly. + BreakList<SkColor> color_breaks(SK_ColorRED); + EXPECT_TRUE(color_breaks.EqualsValueForTesting(SK_ColorRED)); + color_breaks.SetValue(SK_ColorBLACK); + EXPECT_TRUE(color_breaks.EqualsValueForTesting(SK_ColorBLACK)); +} + +TEST_F(BreakListTest, ApplyValue) { + BreakList<bool> breaks(false); + const size_t max = 99; + breaks.SetMax(max); + + // Ensure ApplyValue is a no-op on invalid and empty ranges. + breaks.ApplyValue(true, ui::Range::InvalidRange()); + EXPECT_TRUE(breaks.EqualsValueForTesting(false)); + for (size_t i = 0; i < 3; ++i) { + breaks.ApplyValue(true, ui::Range(i, i)); + EXPECT_TRUE(breaks.EqualsValueForTesting(false)); + } + + // Apply a value to a valid range, check breaks; repeating should be no-op. + std::vector<std::pair<size_t, bool> > expected; + expected.push_back(std::pair<size_t, bool>(0, false)); + expected.push_back(std::pair<size_t, bool>(2, true)); + expected.push_back(std::pair<size_t, bool>(3, false)); + for (size_t i = 0; i < 2; ++i) { + breaks.ApplyValue(true, ui::Range(2, 3)); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + } + + // Ensure setting a value overrides the ranged value. + breaks.SetValue(true); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + + // Ensure applying a value over [0, |max|) is the same as setting a value. + breaks.ApplyValue(false, ui::Range(0, max)); + EXPECT_TRUE(breaks.EqualsValueForTesting(false)); + + // Ensure applying a value that is already applied has no effect. + breaks.ApplyValue(false, ui::Range(0, 2)); + breaks.ApplyValue(false, ui::Range(3, 6)); + breaks.ApplyValue(false, ui::Range(7, max)); + EXPECT_TRUE(breaks.EqualsValueForTesting(false)); + + // Ensure applying an identical neighboring value merges the ranges. + breaks.ApplyValue(true, ui::Range(0, 3)); + breaks.ApplyValue(true, ui::Range(3, 6)); + breaks.ApplyValue(true, ui::Range(6, max)); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + + // Ensure applying a value with the same range overrides the ranged value. + breaks.ApplyValue(false, ui::Range(2, 3)); + breaks.ApplyValue(true, ui::Range(2, 3)); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + + // Ensure applying a value with a containing range overrides contained values. + breaks.ApplyValue(false, ui::Range(0, 1)); + breaks.ApplyValue(false, ui::Range(2, 3)); + breaks.ApplyValue(true, ui::Range(0, 3)); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + breaks.ApplyValue(false, ui::Range(4, 5)); + breaks.ApplyValue(false, ui::Range(6, 7)); + breaks.ApplyValue(false, ui::Range(8, 9)); + breaks.ApplyValue(true, ui::Range(4, 9)); + EXPECT_TRUE(breaks.EqualsValueForTesting(true)); + + // Ensure applying various overlapping values yields the intended results. + breaks.ApplyValue(false, ui::Range(1, 4)); + breaks.ApplyValue(false, ui::Range(5, 8)); + breaks.ApplyValue(true, ui::Range(0, 2)); + breaks.ApplyValue(true, ui::Range(3, 6)); + breaks.ApplyValue(true, ui::Range(7, max)); + std::vector<std::pair<size_t, bool> > overlap; + overlap.push_back(std::pair<size_t, bool>(0, true)); + overlap.push_back(std::pair<size_t, bool>(2, false)); + overlap.push_back(std::pair<size_t, bool>(3, true)); + overlap.push_back(std::pair<size_t, bool>(6, false)); + overlap.push_back(std::pair<size_t, bool>(7, true)); + EXPECT_TRUE(breaks.EqualsForTesting(overlap)); +} + +TEST_F(BreakListTest, SetMax) { + // Ensure values adjust to accommodate max position changes. + BreakList<bool> breaks(false); + breaks.SetMax(9); + breaks.ApplyValue(true, ui::Range(0, 2)); + breaks.ApplyValue(true, ui::Range(3, 6)); + breaks.ApplyValue(true, ui::Range(7, 9)); + + std::vector<std::pair<size_t, bool> > expected; + expected.push_back(std::pair<size_t, bool>(0, true)); + expected.push_back(std::pair<size_t, bool>(2, false)); + expected.push_back(std::pair<size_t, bool>(3, true)); + expected.push_back(std::pair<size_t, bool>(6, false)); + expected.push_back(std::pair<size_t, bool>(7, true)); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + + // Setting a smaller max should remove any corresponding breaks. + breaks.SetMax(7); + expected.resize(4); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + breaks.SetMax(4); + expected.resize(3); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + breaks.SetMax(4); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); + + // Setting a larger max should not change any breaks. + breaks.SetMax(50); + EXPECT_TRUE(breaks.EqualsForTesting(expected)); +} + +TEST_F(BreakListTest, GetBreakAndRange) { + BreakList<bool> breaks(false); + breaks.SetMax(8); + breaks.ApplyValue(true, ui::Range(1, 2)); + breaks.ApplyValue(true, ui::Range(4, 6)); + + struct { + size_t position; + size_t break_index; + ui::Range range; + } cases[] = { + { 0, 0, ui::Range(0, 1) }, + { 1, 1, ui::Range(1, 2) }, + { 2, 2, ui::Range(2, 4) }, + { 3, 2, ui::Range(2, 4) }, + { 4, 3, ui::Range(4, 6) }, + { 5, 3, ui::Range(4, 6) }, + { 6, 4, ui::Range(6, 8) }, + { 7, 4, ui::Range(6, 8) }, + // Positions at or beyond the max simply return the last break and range. + { 8, 4, ui::Range(6, 8) }, + { 9, 4, ui::Range(6, 8) }, + }; + + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + BreakList<bool>::const_iterator it = breaks.GetBreak(cases[i].position); + EXPECT_EQ(breaks.breaks()[cases[i].break_index], *it); + EXPECT_EQ(breaks.GetRange(it), cases[i].range); + } +} + +} // namespace gfx diff --git a/ui/gfx/canvas_skia.cc b/ui/gfx/canvas_skia.cc index 263819b..2d995cf 100644 --- a/ui/gfx/canvas_skia.cc +++ b/ui/gfx/canvas_skia.cc @@ -17,6 +17,8 @@ #include "ui/gfx/shadow_value.h" #include "ui/gfx/text_utils.h" +namespace gfx { + namespace { // If necessary, wraps |text| with RTL/LTR directionality characters based on @@ -28,11 +30,11 @@ bool AdjustStringDirection(int flags, string16* text) { // If the string is empty or LTR was forced, simply return false since the // default RenderText directionality is already LTR. - if (text->empty() || (flags & gfx::Canvas::FORCE_LTR_DIRECTIONALITY)) + if (text->empty() || (flags & Canvas::FORCE_LTR_DIRECTIONALITY)) return false; // If RTL is forced, apply it to the string. - if (flags & gfx::Canvas::FORCE_RTL_DIRECTIONALITY) { + if (flags & Canvas::FORCE_RTL_DIRECTIONALITY) { base::i18n::WrapStringWithRTLFormatting(text); return true; } @@ -84,11 +86,11 @@ bool PixelShouldGetHalo(const SkBitmap& bitmap, // Returns a range in |text| to underline or ui::Range::InvalidRange() if // underlining is not needed. ui::Range StripAcceleratorChars(int flags, string16* text) { - if (flags & (gfx::Canvas::SHOW_PREFIX | gfx::Canvas::HIDE_PREFIX)) { + if (flags & (Canvas::SHOW_PREFIX | Canvas::HIDE_PREFIX)) { int char_pos = -1; int char_span = 0; - *text = gfx::RemoveAcceleratorChar(*text, '&', &char_pos, &char_span); - if ((flags & gfx::Canvas::SHOW_PREFIX) && char_pos != -1) + *text = RemoveAcceleratorChar(*text, '&', &char_pos, &char_span); + if ((flags & Canvas::SHOW_PREFIX) && char_pos != -1) return ui::Range(char_pos, char_pos + char_span); } return ui::Range::InvalidRange(); @@ -96,7 +98,7 @@ ui::Range StripAcceleratorChars(int flags, string16* text) { // Elides |text| and adjusts |range| appropriately. If eliding causes |range| // to no longer point to the same character in |text|, |range| is made invalid. -void ElideTextAndAdjustRange(const gfx::Font& font, +void ElideTextAndAdjustRange(const Font& font, int width, string16* text, ui::Range* range) { @@ -111,55 +113,42 @@ void ElideTextAndAdjustRange(const gfx::Font& font, } // Updates |render_text| from the specified parameters. -void UpdateRenderText(const gfx::Rect& rect, +void UpdateRenderText(const Rect& rect, const string16& text, - const gfx::Font& font, + const Font& font, int flags, SkColor color, - gfx::RenderText* render_text) { + RenderText* render_text) { render_text->SetFont(font); render_text->SetText(text); render_text->SetCursorEnabled(false); - gfx::Rect display_rect = rect; + Rect display_rect = rect; display_rect.set_height(font.GetHeight()); render_text->SetDisplayRect(display_rect); // Set the text alignment explicitly based on the directionality of the UI, // if not specified. - if (!(flags & (gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT | - gfx::Canvas::TEXT_ALIGN_LEFT))) { - flags |= gfx::Canvas::DefaultCanvasTextAlignment(); + if (!(flags & (Canvas::TEXT_ALIGN_CENTER | + Canvas::TEXT_ALIGN_RIGHT | + Canvas::TEXT_ALIGN_LEFT))) { + flags |= Canvas::DefaultCanvasTextAlignment(); } - if (flags & gfx::Canvas::TEXT_ALIGN_RIGHT) - render_text->SetHorizontalAlignment(gfx::ALIGN_RIGHT); - else if (flags & gfx::Canvas::TEXT_ALIGN_CENTER) - render_text->SetHorizontalAlignment(gfx::ALIGN_CENTER); + if (flags & Canvas::TEXT_ALIGN_RIGHT) + render_text->SetHorizontalAlignment(ALIGN_RIGHT); + else if (flags & Canvas::TEXT_ALIGN_CENTER) + render_text->SetHorizontalAlignment(ALIGN_CENTER); else - render_text->SetHorizontalAlignment(gfx::ALIGN_LEFT); + render_text->SetHorizontalAlignment(ALIGN_LEFT); - if (flags & gfx::Canvas::NO_SUBPIXEL_RENDERING) + if (flags & Canvas::NO_SUBPIXEL_RENDERING) render_text->set_background_is_transparent(true); - gfx::StyleRange style; - style.foreground = color; - style.font_style = font.GetStyle(); - if (font.GetStyle() & gfx::Font::UNDERLINED) - style.underline = true; - render_text->set_default_style(style); - render_text->ApplyDefaultStyle(); -} - -// Adds an underline style to |render_text| over |range|. -void ApplyUnderlineStyle(const ui::Range& range, gfx::RenderText* render_text) { - gfx::StyleRange style = render_text->default_style(); - if (range.IsValid() && !style.underline) { - style.range = range; - style.underline = true; - render_text->ApplyStyleRange(style); - } + render_text->SetColor(color); + render_text->SetStyle(BOLD, (font.GetStyle() & Font::BOLD) != 0); + render_text->SetStyle(ITALIC, (font.GetStyle() & Font::ITALIC) != 0); + render_text->SetStyle(UNDERLINE, (font.GetStyle() & Font::UNDERLINE) != 0); } // Returns updated |flags| to match platform-specific expected behavior. @@ -168,7 +157,7 @@ int AdjustPlatformSpecificFlags(const string16& text, int flags) { // TODO(asvitkine): ash/tooltips/tooltip_controller.cc adds \n's to the string // without passing MULTI_LINE. if (text.find('\n') != string16::npos) - flags |= gfx::Canvas::MULTI_LINE; + flags |= Canvas::MULTI_LINE; #endif return flags; @@ -176,11 +165,9 @@ int AdjustPlatformSpecificFlags(const string16& text, int flags) { } // namespace -namespace gfx { - // static void Canvas::SizeStringInt(const string16& text, - const gfx::Font& font, + const Font& font, int* width, int* height, int flags) { DCHECK_GE(*width, 0); @@ -200,7 +187,7 @@ void Canvas::SizeStringInt(const string16& text, else if (!(flags & NO_ELLIPSIS)) wrap_behavior = ui::ELIDE_LONG_WORDS; - gfx::Rect rect(*width, INT_MAX); + Rect rect(*width, INT_MAX); std::vector<string16> strings; ui::ElideRectangleText(adjusted_text, font, rect.width(), rect.height(), wrap_behavior, &strings); @@ -227,7 +214,7 @@ void Canvas::SizeStringInt(const string16& text, *height = font.GetHeight(); } else { scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); - gfx::Rect rect(*width, *height); + Rect rect(*width, *height); StripAcceleratorChars(flags, &adjusted_text); UpdateRenderText(rect, adjusted_text, font, flags, 0, render_text.get()); const Size string_size = render_text->GetStringSize(); @@ -238,9 +225,9 @@ void Canvas::SizeStringInt(const string16& text, } void Canvas::DrawStringWithShadows(const string16& text, - const gfx::Font& font, + const Font& font, SkColor color, - const gfx::Rect& text_bounds, + const Rect& text_bounds, int flags, const ShadowValues& shadows) { if (!IntersectsClipRect(text_bounds)) @@ -248,13 +235,13 @@ void Canvas::DrawStringWithShadows(const string16& text, flags = AdjustPlatformSpecificFlags(text, flags); - gfx::Rect clip_rect(text_bounds); + Rect clip_rect(text_bounds); clip_rect.Inset(ShadowValue::GetMargin(shadows)); canvas_->save(SkCanvas::kClip_SaveFlag); ClipRect(clip_rect); - gfx::Rect rect(text_bounds); + Rect rect(text_bounds); string16 adjusted_text = text; #if defined(OS_WIN) @@ -288,16 +275,17 @@ void Canvas::DrawStringWithShadows(const string16& text, if (i == 0) { // TODO(msw|asvitkine): Support multi-line text with varied heights. const int aggregate_height = strings.size() * line_height; - rect += gfx::Vector2d(0, (text_bounds.height() - aggregate_height) / 2); + rect += Vector2d(0, (text_bounds.height() - aggregate_height) / 2); } #endif rect.set_height(line_height); - ApplyUnderlineStyle(range, render_text.get()); + if (range.IsValid()) + render_text->ApplyStyle(UNDERLINE, true, range); render_text->SetDisplayRect(rect); render_text->Draw(this); - rect += gfx::Vector2d(0, line_height); + rect += Vector2d(0, line_height); } } else { ui::Range range = StripAcceleratorChars(flags, &adjusted_text); @@ -327,11 +315,11 @@ void Canvas::DrawStringWithShadows(const string16& text, const int line_height = render_text->GetStringSize().height(); // Center the text vertically. - rect += gfx::Vector2d(0, (text_bounds.height() - line_height) / 2); + rect += Vector2d(0, (text_bounds.height() - line_height) / 2); rect.set_height(line_height); render_text->SetDisplayRect(rect); - - ApplyUnderlineStyle(range, render_text.get()); + if (range.IsValid()) + render_text->ApplyStyle(UNDERLINE, true, range); render_text->Draw(this); } @@ -339,7 +327,7 @@ void Canvas::DrawStringWithShadows(const string16& text, } void Canvas::DrawStringWithHalo(const string16& text, - const gfx::Font& font, + const Font& font, SkColor text_color, SkColor halo_color_in, int x, int y, int w, int h, @@ -354,7 +342,7 @@ void Canvas::DrawStringWithHalo(const string16& text, Canvas text_canvas(size, scale_factor(), true); SkPaint bkgnd_paint; bkgnd_paint.setColor(halo_color); - text_canvas.DrawRect(gfx::Rect(size), bkgnd_paint); + text_canvas.DrawRect(Rect(size), bkgnd_paint); // Draw the text into the temporary buffer. This will have correct // ClearType since the background color is the same as the halo color. @@ -379,7 +367,7 @@ void Canvas::DrawStringWithHalo(const string16& text, } // Draw the halo bitmap with blur. - gfx::ImageSkia text_image = gfx::ImageSkia(gfx::ImageSkiaRep(text_bitmap, + ImageSkia text_image = ImageSkia(ImageSkiaRep(text_bitmap, text_canvas.scale_factor())); DrawImageInt(text_image, x - 1, y - 1); } @@ -388,9 +376,9 @@ void Canvas::DrawFadeTruncatingString( const string16& text, TruncateFadeMode truncate_mode, size_t desired_characters_to_truncate_from_head, - const gfx::Font& font, + const Font& font, SkColor color, - const gfx::Rect& display_rect) { + const Rect& display_rect) { int flags = NO_ELLIPSIS; // If the whole string fits in the destination then just draw it directly. @@ -441,12 +429,12 @@ void Canvas::DrawFadeTruncatingString( if (!(flags & TEXT_ALIGN_RIGHT)) flags |= TEXT_ALIGN_LEFT; - gfx::Rect rect = display_rect; + Rect rect = display_rect; UpdateRenderText(rect, clipped_text, font, flags, color, render_text.get()); const int line_height = render_text->GetStringSize().height(); // Center the text vertically. - rect += gfx::Vector2d(0, (display_rect.height() - line_height) / 2); + rect += Vector2d(0, (display_rect.height() - line_height) / 2); rect.set_height(line_height); render_text->SetDisplayRect(rect); diff --git a/ui/gfx/font.h b/ui/gfx/font.h index aa2b1c7..c05e76c 100644 --- a/ui/gfx/font.h +++ b/ui/gfx/font.h @@ -25,7 +25,7 @@ class UI_EXPORT Font { NORMAL = 0, BOLD = 1, ITALIC = 2, - UNDERLINED = 4, + UNDERLINE = 4, }; // Creates a font with the default name and style. @@ -56,7 +56,7 @@ class UI_EXPORT Font { // |size_delta| is the size in pixels to add to the current font. See the // single argument version of this method for an example. // The style parameter specifies the new style for the font, and is a - // bitmask of the values: BOLD, ITALIC and UNDERLINED. + // bitmask of the values: BOLD, ITALIC and UNDERLINE. Font DeriveFont(int size_delta, int style) const; // Returns the number of vertical pixels needed to display characters from diff --git a/ui/gfx/font_list.cc b/ui/gfx/font_list.cc index dc42c2f..464e722 100644 --- a/ui/gfx/font_list.cc +++ b/ui/gfx/font_list.cc @@ -28,8 +28,7 @@ void ParseFontDescriptionString(const std::string& font_description_string, DCHECK_GT(*font_size, 0); font_names->pop_back(); - // Besides underline (which is supported through StyleRange), Font only - // supports BOLD and ITALIC, but not other styles. + // Font supports BOLD and ITALIC; underline is supported via RenderText. *font_style = 0; for (size_t i = 0; i < styles_size.size() - 1; ++i) { // Styles are separated by white spaces. base::SplitString splits styles diff --git a/ui/gfx/pango_util.cc b/ui/gfx/pango_util.cc index 424eb45..612cd63 100644 --- a/ui/gfx/pango_util.cc +++ b/ui/gfx/pango_util.cc @@ -346,7 +346,7 @@ void DrawPangoLayout(cairo_t* cr, cairo_move_to(cr, text_rect.x(), text_rect.y()); pango_cairo_show_layout(cr, layout); - if (font.GetStyle() & gfx::Font::UNDERLINED) { + if (font.GetStyle() & gfx::Font::UNDERLINE) { gfx::PlatformFontPango* platform_font = static_cast<gfx::PlatformFontPango*>(font.platform_font()); DrawPangoTextUnderline(cr, platform_font, 0.0, text_rect); diff --git a/ui/gfx/platform_font.h b/ui/gfx/platform_font.h index e624847..a78eda6 100644 --- a/ui/gfx/platform_font.h +++ b/ui/gfx/platform_font.h @@ -29,7 +29,7 @@ class UI_EXPORT PlatformFont : public base::RefCounted<PlatformFont> { // Returns a new Font derived from the existing font. // |size_delta| is the size in pixels to add to the current font. // The style parameter specifies the new style for the font, and is a - // bitmask of the values: BOLD, ITALIC and UNDERLINED. + // bitmask of the values: BOLD, ITALIC and UNDERLINE. virtual Font DeriveFont(int size_delta, int style) const = 0; // Returns the number of vertical pixels needed to display characters from diff --git a/ui/gfx/platform_font_pango.cc b/ui/gfx/platform_font_pango.cc index 6bb580b..3d2b986 100644 --- a/ui/gfx/platform_font_pango.cc +++ b/ui/gfx/platform_font_pango.cc @@ -240,8 +240,8 @@ NativeFont PlatformFontPango::GetNativeFont() const { case gfx::Font::ITALIC: pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC); break; - case gfx::Font::UNDERLINED: - // TODO(deanm): How to do underlined? Where do we use it? Probably have + case gfx::Font::UNDERLINE: + // TODO(deanm): How to do underline? Where do we use it? Probably have // to paint it ourselves, see pango_font_metrics_get_underline_position. break; } diff --git a/ui/gfx/platform_font_win.cc b/ui/gfx/platform_font_win.cc index e25395d..66649c9 100644 --- a/ui/gfx/platform_font_win.cc +++ b/ui/gfx/platform_font_win.cc @@ -55,7 +55,7 @@ int AdjustFontSize(int lf_height, int size_delta) { // Sets style properties on |font_info| based on |font_style|. void SetLogFontStyle(int font_style, LOGFONT* font_info) { - font_info->lfUnderline = (font_style & gfx::Font::UNDERLINED) != 0; + font_info->lfUnderline = (font_style & gfx::Font::UNDERLINE) != 0; font_info->lfItalic = (font_style & gfx::Font::ITALIC) != 0; font_info->lfWeight = (font_style & gfx::Font::BOLD) ? FW_BOLD : FW_NORMAL; } @@ -242,7 +242,7 @@ PlatformFontWin::HFontRef* PlatformFontWin::CreateHFontRef(HFONT font) { if (font_metrics.tmItalic) style |= Font::ITALIC; if (font_metrics.tmUnderlined) - style |= Font::UNDERLINED; + style |= Font::UNDERLINE; if (font_metrics.tmWeight >= kTextMetricWeightBold) style |= Font::BOLD; diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc index d68d7aa..8f02724 100644 --- a/ui/gfx/render_text.cc +++ b/ui/gfx/render_text.cc @@ -17,6 +17,8 @@ #include "ui/gfx/skia_util.h" #include "ui/gfx/text_constants.h" +namespace gfx { + namespace { // All chars are replaced by this char when the password style is set. @@ -24,90 +26,31 @@ namespace { // that's available in the font (find_invisible_char() in gtkentry.c). const char16 kPasswordReplacementChar = '*'; -// Default color used for the cursor. -const SkColor kDefaultCursorColor = SK_ColorBLACK; - -// Default color used for drawing selection text. -const SkColor kDefaultSelectionColor = SK_ColorBLACK; +// Default color used for the text and cursor. +const SkColor kDefaultColor = SK_ColorBLACK; // Default color used for drawing selection background. const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY; -#ifndef NDEBUG -// Check StyleRanges invariant conditions: sorted and non-overlapping ranges. -void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) { - if (length == 0) { - DCHECK(style_ranges.empty()) << "Style ranges exist for empty text."; - return; - } - for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) { - const ui::Range& former = style_ranges[i].range; - const ui::Range& latter = style_ranges[i + 1].range; - DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << - former.ToString(); - DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << - former.ToString(); - DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << - former.ToString(); - DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." << - "former:" << former.ToString() << ", latter:" << latter.ToString(); - } - const gfx::StyleRange& end_style = *style_ranges.rbegin(); - DCHECK(!end_style.range.is_empty()) << "Empty range at end."; - DCHECK(end_style.range.IsValid()) << "Invalid range at end."; - DCHECK(!end_style.range.is_reversed()) << "Reversed range at end."; - DCHECK(end_style.range.end() == length) << "Style and text length mismatch."; -} -#endif - -void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges, - const gfx::StyleRange& style_range) { - const ui::Range& new_range = style_range.range; - // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges. - gfx::StyleRanges::iterator i; - for (i = style_ranges->begin(); i != style_ranges->end();) { - if (i->range.end() < new_range.start()) { - i++; - } else if (i->range.start() == new_range.end()) { - break; - } else if (new_range.Contains(i->range)) { - i = style_ranges->erase(i); - if (i == style_ranges->end()) - break; - } else if (i->range.start() < new_range.start() && - i->range.end() > new_range.end()) { - // Split the current style into two styles. - gfx::StyleRange split_style = gfx::StyleRange(*i); - split_style.range.set_end(new_range.start()); - i = style_ranges->insert(i, split_style) + 1; - i->range.set_start(new_range.end()); - break; - } else if (i->range.start() < new_range.start()) { - i->range.set_end(new_range.start()); - i++; - } else if (i->range.end() > new_range.end()) { - i->range.set_start(new_range.end()); - break; - } else { - NOTREACHED(); - } - } - // Add the new range in its sorted location. - style_ranges->insert(i, style_range); -} +// Fraction of the text size to lower a strike through below the baseline. +const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21); +// Fraction of the text size to lower an underline below the baseline. +const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); +// Fraction of the text size to use for a strike through or under-line. +const SkScalar kLineThickness = (SK_Scalar1 / 18); +// Fraction of the text size to use for a top margin of a diagonal strike. +const SkScalar kDiagonalStrikeMarginOffset = (SK_Scalar1 / 4); // Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags. SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) { int skia_style = SkTypeface::kNormal; - if (font_style & gfx::Font::BOLD) - skia_style |= SkTypeface::kBold; - if (font_style & gfx::Font::ITALIC) - skia_style |= SkTypeface::kItalic; + skia_style |= (font_style & gfx::Font::BOLD) ? SkTypeface::kBold : 0; + skia_style |= (font_style & gfx::Font::ITALIC) ? SkTypeface::kItalic : 0; return static_cast<SkTypeface::Style>(skia_style); } // Given |font| and |display_width|, returns the width of the fade gradient. -int CalculateFadeGradientWidth(const gfx::Font& font, int display_width) { +int CalculateFadeGradientWidth(const Font& font, int display_width) { // Fade in/out about 2.5 characters of the beginning/end of the string. // The .5 here is helpful if one of the characters is a space. // Use a quarter of the display width if the display width is very short. @@ -120,8 +63,8 @@ int CalculateFadeGradientWidth(const gfx::Font& font, int display_width) { // Appends to |positions| and |colors| values corresponding to the fade over // |fade_rect| from color |c0| to color |c1|. -void AddFadeEffect(const gfx::Rect& text_rect, - const gfx::Rect& fade_rect, +void AddFadeEffect(const Rect& text_rect, + const Rect& fade_rect, SkColor c0, SkColor c1, std::vector<SkScalar>* positions, @@ -143,9 +86,9 @@ void AddFadeEffect(const gfx::Rect& text_rect, // Creates a SkShader to fade the text, with |left_part| specifying the left // fade effect, if any, and |right_part| specifying the right fade effect. -skia::RefPtr<SkShader> CreateFadeShader(const gfx::Rect& text_rect, - const gfx::Rect& left_part, - const gfx::Rect& right_part, +skia::RefPtr<SkShader> CreateFadeShader(const Rect& text_rect, + const Rect& left_part, + const Rect& right_part, SkColor color) { // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color. const SkColor fade_color = SkColorSetA(color, 51); @@ -177,8 +120,6 @@ skia::RefPtr<SkShader> CreateFadeShader(const gfx::Rect& text_rect, } // namespace -namespace gfx { - namespace internal { // Value of |underline_thickness_| that indicates that underline metrics have @@ -293,103 +234,84 @@ void SkiaTextRenderer::DrawPosText(const SkPoint* pos, canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_); } -// Draw underline and strike through text decorations. -// Based on |SkCanvas::DrawTextDecorations()| and constants from: -// third_party/skia/src/core/SkTextFormatParams.h -void SkiaTextRenderer::DrawDecorations(int x, int y, int width, - const StyleRange& style) { - if (!style.underline && !style.strike && !style.diagonal_strike) - return; +void SkiaTextRenderer::DrawDecorations(int x, int y, int width, bool underline, + bool strike, bool diagonal_strike) { + if (underline) + DrawUnderline(x, y, width); + if (strike) + DrawStrike(x, y, width); + if (diagonal_strike) + DrawDiagonalStrike(x, y, width); +} - // Fraction of the text size to lower a strike through below the baseline. - const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21); - // Fraction of the text size to lower an underline below the baseline. - const SkScalar kUnderlineOffset = (SK_Scalar1 / 9); - // Fraction of the text size to use for a strike through or under-line. - const SkScalar kLineThickness = (SK_Scalar1 / 18); - // Fraction of the text size to use for a top margin of a diagonal strike. - const SkScalar kDiagonalStrikeThroughMarginOffset = (SK_Scalar1 / 4); - - SkScalar text_size = paint_.getTextSize(); - SkScalar height = SkScalarMul(text_size, kLineThickness); - SkRect r; - - r.fLeft = x; - r.fRight = x + width; - - if (style.underline) { - if (underline_thickness_ == kUnderlineMetricsNotSet) { - r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y); - r.fBottom = r.fTop + height; - } else { - r.fTop = y + underline_position_; - r.fBottom = r.fTop + underline_thickness_; - } - canvas_skia_->drawRect(r, paint_); - } - if (style.strike) { - SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y); - r.fTop = offset; - r.fBottom = offset + height; - canvas_skia_->drawRect(r, paint_); - } - if (style.diagonal_strike) { - SkScalar offset = - SkScalarMul(text_size, kDiagonalStrikeThroughMarginOffset); - SkPaint paint(paint_); - paint.setAntiAlias(true); - paint.setStyle(SkPaint::kFill_Style); - paint.setStrokeWidth(height * 2); - canvas_skia_->drawLine( - SkIntToScalar(x), SkIntToScalar(y), - SkIntToScalar(x + width), SkIntToScalar(y) - text_size + offset, - paint); +void SkiaTextRenderer::DrawUnderline(int x, int y, int width) { + SkRect r = SkRect::MakeLTRB(x, y + underline_position_, x + width, + y + underline_position_ + underline_thickness_); + if (underline_thickness_ == kUnderlineMetricsNotSet) { + const SkScalar text_size = paint_.getTextSize(); + r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y); + r.fBottom = r.fTop + SkScalarMul(text_size, kLineThickness); } + canvas_skia_->drawRect(r, paint_); } -} // namespace internal +void SkiaTextRenderer::DrawStrike(int x, int y, int width) const { + const SkScalar text_size = paint_.getTextSize(); + const SkScalar height = SkScalarMul(text_size, kLineThickness); + const SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y); + const SkRect r = SkRect::MakeLTRB(x, offset, x + width, offset + height); + canvas_skia_->drawRect(r, paint_); +} + +void SkiaTextRenderer::DrawDiagonalStrike(int x, int y, int width) const { + const SkScalar text_size = paint_.getTextSize(); + const SkScalar offset = SkScalarMul(text_size, kDiagonalStrikeMarginOffset); + + SkPaint paint(paint_); + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + paint.setStrokeWidth(SkScalarMul(text_size, kLineThickness) * 2); + canvas_skia_->drawLine(x, y, x + width, y - text_size + offset, paint); +} +StyleIterator::StyleIterator(const BreakList<SkColor>& colors, + const std::vector<BreakList<bool> >& styles) + : colors_(colors), + styles_(styles) { + color_ = colors_.breaks().begin(); + for (size_t i = 0; i < styles_.size(); ++i) + style_.push_back(styles_[i].breaks().begin()); +} + +StyleIterator::~StyleIterator() {} + +ui::Range StyleIterator::GetRange() const { + ui::Range range(colors_.GetRange(color_)); + for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) + range = range.Intersect(styles_[i].GetRange(style_[i])); + return range; +} -StyleRange::StyleRange() - : foreground(SK_ColorBLACK), - font_style(gfx::Font::NORMAL), - strike(false), - diagonal_strike(false), - underline(false) { +void StyleIterator::UpdatePosition(size_t position) { + color_ = colors_.GetBreak(position); + for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) + style_[i] = styles_[i].GetBreak(position); } +} // namespace internal + RenderText::~RenderText() { } void RenderText::SetText(const string16& text) { DCHECK(!composition_range_.IsValid()); - size_t old_text_length = text_.length(); text_ = text; - // Update the style ranges as needed. - if (text_.empty()) { - style_ranges_.clear(); - } else if (style_ranges_.empty()) { - ApplyDefaultStyle(); - } else if (text_.length() > old_text_length) { - style_ranges_.back().range.set_end(text_.length()); - } else if (text_.length() < old_text_length) { - StyleRanges::iterator i; - for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) { - if (i->range.start() >= text_.length()) { - // Style ranges are sorted and non-overlapping, so all the subsequent - // style ranges should be out of text_.length() as well. - style_ranges_.erase(i, style_ranges_.end()); - break; - } - } - // Since style ranges are sorted and non-overlapping, if there is a style - // range ends beyond text_.length, it must be the last one. - style_ranges_.back().range.set_end(text_.length()); - } -#ifndef NDEBUG - CheckStyleRanges(style_ranges_, text_.length()); -#endif + // Adjust ranged styles and colors to accommodate a new text length. + const size_t text_length = text_.length(); + colors_.SetMax(text_length); + for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) + styles_[style].SetMax(text_length); cached_bounds_and_offset_valid_ = false; // Reset selection model. SetText should always followed by SetSelectionModel @@ -582,28 +504,56 @@ void RenderText::SetCompositionRange(const ui::Range& composition_range) { ResetLayout(); } -void RenderText::ApplyStyleRange(const StyleRange& style_range) { - const ui::Range& new_range = style_range.range; - if (!new_range.IsValid() || new_range.is_empty()) - return; - CHECK(!new_range.is_reversed()); - CHECK(ui::Range(0, text_.length()).Contains(new_range)); - ApplyStyleRangeImpl(&style_ranges_, style_range); -#ifndef NDEBUG - CheckStyleRanges(style_ranges_, text_.length()); -#endif - // TODO(xji): only invalidate if font or underline changes. +void RenderText::SetColor(SkColor value) { + colors_.SetValue(value); + +#if defined(OS_WIN) + // TODO(msw): Windows applies colors and decorations in the layout process. cached_bounds_and_offset_valid_ = false; ResetLayout(); +#endif } -void RenderText::ApplyDefaultStyle() { - style_ranges_.clear(); - StyleRange style = StyleRange(default_style_); - style.range.set_end(text_.length()); - style_ranges_.push_back(style); +void RenderText::ApplyColor(SkColor value, const ui::Range& range) { + colors_.ApplyValue(value, range); + +#if defined(OS_WIN) + // TODO(msw): Windows applies colors and decorations in the layout process. cached_bounds_and_offset_valid_ = false; ResetLayout(); +#endif +} + +void RenderText::SetStyle(TextStyle style, bool value) { + styles_[style].SetValue(value); + + // Only invalidate the layout on font changes; not for colors or decorations. + bool invalidate = (style == BOLD) || (style == ITALIC); +#if defined(OS_WIN) + // TODO(msw): Windows applies colors and decorations in the layout process. + invalidate = true; +#endif + if (invalidate) { + cached_bounds_and_offset_valid_ = false; + ResetLayout(); + } +} + +void RenderText::ApplyStyle(TextStyle style, + bool value, + const ui::Range& range) { + styles_[style].ApplyValue(value, range); + + // Only invalidate the layout on font changes; not for colors or decorations. + bool invalidate = (style == BOLD) || (style == ITALIC); +#if defined(OS_WIN) + // TODO(msw): Windows applies colors and decorations in the layout process. + invalidate = true; +#endif + if (invalidate) { + cached_bounds_and_offset_valid_ = false; + ResetLayout(); + } } void RenderText::SetDirectionalityMode(DirectionalityMode mode) { @@ -651,7 +601,7 @@ void RenderText::Draw(Canvas* canvas) { EnsureLayout(); if (clip_to_display_rect()) { - gfx::Rect clip_rect(display_rect()); + Rect clip_rect(display_rect()); clip_rect.Inset(ShadowValue::GetMargin(text_shadows_)); canvas->Save(); @@ -752,12 +702,15 @@ RenderText::RenderText() cursor_enabled_(true), cursor_visible_(false), insert_mode_(true), - cursor_color_(kDefaultCursorColor), - selection_color_(kDefaultSelectionColor), + cursor_color_(kDefaultColor), + selection_color_(kDefaultColor), selection_background_focused_color_(kDefaultSelectionBackgroundColor), selection_background_unfocused_color_(kDefaultSelectionBackgroundColor), focused_(false), composition_range_(ui::Range::InvalidRange()), + colors_(kDefaultColor), + styles_(NUM_TEXT_STYLES), + composition_and_selection_styles_applied_(false), obscured_(false), fade_head_(false), fade_tail_(false), @@ -802,42 +755,36 @@ const string16& RenderText::GetLayoutText() const { return obscured() ? obscured_text_ : text(); } -void RenderText::ApplyCompositionAndSelectionStyles( - StyleRanges* style_ranges) { - // TODO(msw): This pattern ought to be reconsidered; what about composition - // and selection overlaps, retain existing local style features? - // Apply a composition style override to a copy of the style ranges. - if (composition_range_.IsValid() && !composition_range_.is_empty()) { - StyleRange composition_style(default_style_); - composition_style.underline = true; - composition_style.range = composition_range_; - ApplyStyleRangeImpl(style_ranges, composition_style); - } - // Apply a selection style override to a copy of the style ranges. +void RenderText::ApplyCompositionAndSelectionStyles() { + // Save the underline and color breaks to undo the temporary styles later. + DCHECK(!composition_and_selection_styles_applied_); + saved_colors_ = colors_; + saved_underlines_ = styles_[UNDERLINE]; + + // Apply an underline to the composition range in |underlines|. + if (composition_range_.IsValid() && !composition_range_.is_empty()) + styles_[UNDERLINE].ApplyValue(true, composition_range_); + + // Apply the selected text color to the [un-reversed] selection range. if (!selection().is_empty()) { - StyleRange selection_style(default_style_); - selection_style.foreground = selection_color_; - selection_style.range = ui::Range(selection().GetMin(), - selection().GetMax()); - ApplyStyleRangeImpl(style_ranges, selection_style); + const ui::Range range(selection().GetMin(), selection().GetMax()); + colors_.ApplyValue(selection_color_, range); } - // Apply replacement-mode style override to a copy of the style ranges. - // - // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to - // apply styles) in ItemizeLogicalText(). In order for the cursor's underline - // character to be drawn correctly, we will need to re-layout the text. It's - // not practical to do layout on every cursor blink. We need to fix Windows - // port to apply styles during drawing phase like Linux port does. - // http://crbug.com/110109 + // Apply the selected text color to the cursor range in overtype mode. if (!insert_mode_ && cursor_visible() && focused()) { - StyleRange replacement_mode_style(default_style_); - replacement_mode_style.foreground = selection_color_; - size_t cursor = cursor_position(); - replacement_mode_style.range.set_start(cursor); - replacement_mode_style.range.set_end( - IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD)); - ApplyStyleRangeImpl(style_ranges, replacement_mode_style); + const size_t cursor = cursor_position(); + const size_t next = IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD); + colors_.ApplyValue(selection_color_, ui::Range(cursor, next)); } + composition_and_selection_styles_applied_ = true; +} + +void RenderText::UndoCompositionAndSelectionStyles() { + // Restore the underline and color breaks to undo the temporary styles. + DCHECK(composition_and_selection_styles_applied_); + colors_ = saved_colors_; + styles_[UNDERLINE] = saved_underlines_; + composition_and_selection_styles_applied_ = false; } Vector2d RenderText::GetTextOffset() { @@ -899,9 +846,9 @@ void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) { if (horizontal_alignment() == ALIGN_RIGHT) std::swap(fade_left, fade_right); - gfx::Rect solid_part = display_rect(); - gfx::Rect left_part; - gfx::Rect right_part; + Rect solid_part = display_rect(); + Rect left_part; + Rect right_part; if (fade_left) { left_part = solid_part; left_part.Inset(0, 0, solid_part.width() - gradient_width, 0); @@ -913,19 +860,18 @@ void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) { solid_part.Inset(0, 0, gradient_width, 0); } - gfx::Rect text_rect = display_rect(); + Rect text_rect = display_rect(); text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0); - const SkColor color = default_style().foreground; - skia::RefPtr<SkShader> shader = - CreateFadeShader(text_rect, left_part, right_part, color); + // TODO(msw): Use the actual text colors corresponding to each faded part. + skia::RefPtr<SkShader> shader = CreateFadeShader( + text_rect, left_part, right_part, colors_.breaks().front().second); if (shader) renderer->SetShader(shader.get(), display_rect()); } void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { - skia::RefPtr<SkDrawLooper> looper = - gfx::CreateShadowDrawLooper(text_shadows_); + skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(text_shadows_); renderer->SetDrawLooper(looper.get()); } @@ -999,7 +945,7 @@ void RenderText::UpdateCachedBoundsAndOffset() { } } - gfx::Vector2d delta_offset(delta_x, 0); + Vector2d delta_offset(delta_x, 0); display_offset_ += delta_offset; cursor_bounds_ += delta_offset; } diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h index dd86fc5..f45fd101 100644 --- a/ui/gfx/render_text.h +++ b/ui/gfx/render_text.h @@ -18,6 +18,7 @@ #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkRect.h" #include "ui/base/range/range.h" +#include "ui/gfx/break_list.h" #include "ui/gfx/font_list.h" #include "ui/gfx/point.h" #include "ui/gfx/rect.h" @@ -37,7 +38,6 @@ namespace gfx { class Canvas; class Font; class RenderTextTest; -struct StyleRange; namespace internal { @@ -62,7 +62,14 @@ class SkiaTextRenderer { void DrawPosText(const SkPoint* pos, const uint16* glyphs, size_t glyph_count); - void DrawDecorations(int x, int y, int width, const StyleRange& style); + // Draw underline and strike-through text decorations. + // Based on |SkCanvas::DrawTextDecorations()| and constants from: + // third_party/skia/src/core/SkTextFormatParams.h + void DrawDecorations(int x, int y, int width, bool underline, bool strike, + bool diagonal_strike); + void DrawUnderline(int x, int y, int width); + void DrawStrike(int x, int y, int width) const; + void DrawDiagonalStrike(int x, int y, int width) const; private: SkCanvas* canvas_skia_; @@ -76,22 +83,34 @@ class SkiaTextRenderer { DISALLOW_COPY_AND_ASSIGN(SkiaTextRenderer); }; -} // namespace internal +// Internal helper class used by derived classes to iterate colors and styles. +class StyleIterator { + public: + StyleIterator(const BreakList<SkColor>& colors, + const std::vector<BreakList<bool> >& styles); + ~StyleIterator(); + + // Get the colors and styles at the current iterator position. + SkColor color() const { return color_->second; } + bool style(TextStyle s) const { return style_[s]->second; } + + // Get the intersecting range of the current iterator set. + ui::Range GetRange() const; + + // Update the iterator to point to colors and styles applicable at |position|. + void UpdatePosition(size_t position); + + private: + BreakList<SkColor> colors_; + std::vector<BreakList<bool> > styles_; -// A visual style applicable to a range of text. -struct UI_EXPORT StyleRange { - StyleRange(); - - SkColor foreground; - // A gfx::Font::FontStyle flag to specify bold and italic styles. - int font_style; - bool strike; - bool diagonal_strike; - bool underline; - ui::Range range; + BreakList<SkColor>::const_iterator color_; + std::vector<BreakList<bool>::const_iterator> style_; + + DISALLOW_COPY_AND_ASSIGN(StyleIterator); }; -typedef std::vector<StyleRange> StyleRanges; +} // namespace internal // RenderText represents an abstract model of styled text and its corresponding // visual layout. Support is built in for a cursor, a selection, simple styling, @@ -157,9 +176,6 @@ class UI_EXPORT RenderText { bool clip_to_display_rect() const { return clip_to_display_rect_; } void set_clip_to_display_rect(bool clip) { clip_to_display_rect_ = clip; } - const StyleRange& default_style() const { return default_style_; } - void set_default_style(const StyleRange& style) { default_style_ = style; } - // In an obscured (password) field, all text is drawn as asterisks or bullets. bool obscured() const { return obscured_; } void SetObscured(bool obscured); @@ -227,11 +243,16 @@ class UI_EXPORT RenderText { const ui::Range& GetCompositionRange() const; void SetCompositionRange(const ui::Range& composition_range); - // Apply |style_range| to the internal style model. - void ApplyStyleRange(const StyleRange& style_range); + // Set the text color over the entire text or a logical character range. + // The |range| should be valid, non-reversed, and within [0, text().length()]. + void SetColor(SkColor value); + void ApplyColor(SkColor value, const ui::Range& range); - // Apply |default_style_| over the entire text range. - void ApplyDefaultStyle(); + // Set various text styles over the entire text or a logical character range. + // The respective |style| is applied if |value| is true, or removed if false. + // The |range| should be valid, non-reversed, and within [0, text().length()]. + void SetStyle(TextStyle style, bool value); + void ApplyStyle(TextStyle style, bool value, const ui::Range& range); // Set the text directionality mode and get the text direction yielded. void SetDirectionalityMode(DirectionalityMode mode); @@ -295,14 +316,15 @@ class UI_EXPORT RenderText { protected: RenderText(); + const BreakList<SkColor>& colors() const { return colors_; } + const std::vector<BreakList<bool> >& styles() const { return styles_; } + const Vector2d& GetUpdatedDisplayOffset(); void set_cached_bounds_and_offset_valid(bool valid) { cached_bounds_and_offset_valid_ = valid; } - const StyleRanges& style_ranges() const { return style_ranges_; } - // Get the selection model that visually neighbors |position| by |break_type|. // The returned value represents a cursor/caret position without a selection. SelectionModel GetAdjacentSelectionModel(const SelectionModel& current, @@ -364,9 +386,9 @@ class UI_EXPORT RenderText { // Returns the text used for layout, which may be |obscured_text_|. const string16& GetLayoutText() const; - // Apply composition style (underline) to composition range and selection - // style (foreground) to selection range. - void ApplyCompositionAndSelectionStyles(StyleRanges* style_ranges); + // Apply (and undo) temporary composition underlines and selection colors. + void ApplyCompositionAndSelectionStyles(); + void UndoCompositionAndSelectionStyles(); // Returns the text offset from the origin after applying text alignment and // display offset. @@ -402,11 +424,9 @@ class UI_EXPORT RenderText { private: friend class RenderTextTest; - FRIEND_TEST_ALL_PREFIXES(RenderTextTest, DefaultStyle); - FRIEND_TEST_ALL_PREFIXES(RenderTextTest, CustomDefaultStyle); - FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange); - FRIEND_TEST_ALL_PREFIXES(RenderTextTest, StyleRangesAdjust); + FRIEND_TEST_ALL_PREFIXES(RenderTextTest, SetColorAndStyle); + FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyColorAndStyle); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ObscuredText); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, GraphemePositions); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, EdgeSelectionModels); @@ -478,10 +498,16 @@ class UI_EXPORT RenderText { // Composition text range. ui::Range composition_range_; - // List of style ranges. Elements in the list never overlap each other. - StyleRanges style_ranges_; - // The default text style. - StyleRange default_style_; + // Color and style breaks, used to color and stylize ranges of text. + // BreakList positions are stored with text indices, not layout indices. + // TODO(msw): Expand to support cursor, selection, background, etc. colors. + BreakList<SkColor> colors_; + std::vector<BreakList<bool> > styles_; + + // Breaks saved without temporary composition and selection styling. + BreakList<SkColor> saved_colors_; + BreakList<bool> saved_underlines_; + bool composition_and_selection_styles_applied_; // A flag and the text to display for obscured (password) fields. // Asterisks are used instead of the actual text glyphs when true. diff --git a/ui/gfx/render_text_linux.cc b/ui/gfx/render_text_linux.cc index 3d3cdef..47268ae 100644 --- a/ui/gfx/render_text_linux.cc +++ b/ui/gfx/render_text_linux.cc @@ -325,26 +325,33 @@ void RenderTextLinux::EnsureLayout() { void RenderTextLinux::SetupPangoAttributes(PangoLayout* layout) { PangoAttrList* attrs = pango_attr_list_new(); - int default_font_style = font_list().GetFontStyle(); - for (StyleRanges::const_iterator i = style_ranges().begin(); - i < style_ranges().end(); ++i) { - // In Pango, different fonts means different runs, and it breaks Arabic - // shaping across run boundaries. So, set font only when it is different - // from the default font. - // TODO(xji): We'll eventually need to split up StyleRange into components - // (ColorRange, FontRange, etc.) so that we can combine adjacent ranges - // with the same Fonts (to avoid unnecessarily splitting up runs). - if (i->font_style != default_font_style) { - FontList derived_font_list = font_list().DeriveFontList(i->font_style); + // Splitting text runs to accommodate styling can break Arabic glyph shaping. + // Only split text runs as needed for bold and italic font styles changes. + BreakList<bool>::const_iterator bold = styles()[BOLD].breaks().begin(); + BreakList<bool>::const_iterator italic = styles()[ITALIC].breaks().begin(); + while (bold != styles()[BOLD].breaks().end() && + italic != styles()[ITALIC].breaks().end()) { + const int style = (bold->second ? Font::BOLD : 0) | + (italic->second ? Font::ITALIC : 0); + const size_t bold_end = styles()[BOLD].GetRange(bold).end(); + const size_t italic_end = styles()[ITALIC].GetRange(italic).end(); + const size_t style_end = std::min(bold_end, italic_end); + if (style != font_list().GetFontStyle()) { + FontList derived_font_list = font_list().DeriveFontList(style); ScopedPangoFontDescription desc(pango_font_description_from_string( derived_font_list.GetFontDescriptionString().c_str())); PangoAttribute* pango_attr = pango_attr_font_desc_new(desc.get()); - pango_attr->start_index = TextIndexToLayoutIndex(i->range.start()); - pango_attr->end_index = TextIndexToLayoutIndex(i->range.end()); + pango_attr->start_index = + TextIndexToLayoutIndex(std::max(bold->first, italic->first)); + pango_attr->end_index = TextIndexToLayoutIndex(style_end); pango_attr_list_insert(attrs, pango_attr); } + bold += bold_end == style_end ? 1 : 0; + italic += italic_end == style_end ? 1 : 0; } + DCHECK(bold == styles()[BOLD].breaks().end()); + DCHECK(italic == styles()[ITALIC].breaks().end()); pango_layout_set_attributes(layout, attrs); pango_attr_list_unref(attrs); @@ -363,20 +370,6 @@ void RenderTextLinux::DrawVisualText(Canvas* canvas) { std::vector<SkPoint> pos; std::vector<uint16> glyphs; - StyleRanges styles(style_ranges()); - ApplyCompositionAndSelectionStyles(&styles); - - // Pre-calculate UTF8 indices from UTF16 indices. - // TODO(asvitkine): Can we cache these? - std::vector<ui::Range> style_ranges_utf8; - style_ranges_utf8.reserve(styles.size()); - size_t start_index = 0; - for (size_t i = 0; i < styles.size(); ++i) { - size_t end_index = TextIndexToLayoutIndex(styles[i].range.end()); - style_ranges_utf8.push_back(ui::Range(start_index, end_index)); - start_index = end_index; - } - internal::SkiaTextRenderer renderer(canvas); ApplyFadeEffects(&renderer); ApplyTextShadows(&renderer); @@ -391,27 +384,16 @@ void RenderTextLinux::DrawVisualText(Canvas* canvas) { render_params.antialiasing, use_subpixel_rendering && !background_is_transparent()); + // Temporarily apply composition underlines and selection colors. + ApplyCompositionAndSelectionStyles(); + + internal::StyleIterator style(colors(), styles()); for (GSList* it = current_line_->runs; it; it = it->next) { PangoLayoutRun* run = reinterpret_cast<PangoLayoutRun*>(it->data); int glyph_count = run->glyphs->num_glyphs; if (glyph_count == 0) continue; - size_t run_start = run->item->offset; - size_t first_glyph_byte_index = run_start + run->glyphs->log_clusters[0]; - size_t style_increment = IsForwardMotion(CURSOR_RIGHT, run->item) ? 1 : -1; - - // Find the initial style for this run. - // TODO(asvitkine): Can we avoid looping here, e.g. by caching this per run? - int style = -1; - for (size_t i = 0; i < style_ranges_utf8.size(); ++i) { - if (IndexInRange(style_ranges_utf8[i], first_glyph_byte_index)) { - style = i; - break; - } - } - DCHECK_GE(style, 0); - ScopedPangoFontDescription desc( pango_font_describe(run->item->analysis.font)); @@ -419,60 +401,59 @@ void RenderTextLinux::DrawVisualText(Canvas* canvas) { pango_font_description_get_family(desc.get()); renderer.SetTextSize(GetPangoFontSizeInPixels(desc.get())); - SkScalar glyph_x = x; - SkScalar start_x = x; - int start = 0; - glyphs.resize(glyph_count); pos.resize(glyph_count); - for (int i = 0; i < glyph_count; ++i) { - const PangoGlyphInfo& glyph = run->glyphs->glyphs[i]; - glyphs[i] = static_cast<uint16>(glyph.glyph); - // Use pango_units_to_double() rather than PANGO_PIXELS() here so that - // units won't get rounded to the pixel grid if we're using subpixel - // positioning. - pos[i].set(glyph_x + pango_units_to_double(glyph.geometry.x_offset), - y + pango_units_to_double(glyph.geometry.y_offset)); - - // If this glyph is beyond the current style, draw the glyphs so far and - // advance to the next style. - size_t glyph_byte_index = run_start + run->glyphs->log_clusters[i]; - DCHECK_GE(style, 0); - DCHECK_LT(style, static_cast<int>(styles.size())); - if (!IndexInRange(style_ranges_utf8[style], glyph_byte_index)) { + // Track the current glyph and the glyph at the start of its styled range. + int glyph_index = 0; + int style_start_glyph_index = glyph_index; + + // Track the x-coordinates for each styled range (|x| marks the current). + SkScalar style_start_x = x; + + // Track the current style and its text (not layout) index range. + style.UpdatePosition(GetGlyphTextIndex(run, style_start_glyph_index)); + ui::Range style_range = style.GetRange(); + + do { + const PangoGlyphInfo& glyph = run->glyphs->glyphs[glyph_index]; + glyphs[glyph_index] = static_cast<uint16>(glyph.glyph); + // Use pango_units_to_double() rather than PANGO_PIXELS() here, so units + // are not rounded to the pixel grid if subpixel positioning is enabled. + pos[glyph_index].set(x + pango_units_to_double(glyph.geometry.x_offset), + y + pango_units_to_double(glyph.geometry.y_offset)); + x += pango_units_to_double(glyph.geometry.width); + + ++glyph_index; + const size_t glyph_text_index = (glyph_index == glyph_count) ? + style_range.end() : GetGlyphTextIndex(run, glyph_index); + if (!IndexInRange(style_range, glyph_text_index)) { // TODO(asvitkine): For cases like "fi", where "fi" is a single glyph // but can span multiple styles, Pango splits the // styles evenly over the glyph. We can do this too by // clipping and drawing the glyph several times. - renderer.SetForegroundColor(styles[style].foreground); - renderer.SetFontFamilyWithStyle(family_name, styles[style].font_style); - renderer.DrawPosText(&pos[start], &glyphs[start], i - start); - if (styles[style].underline) + renderer.SetForegroundColor(style.color()); + const int font_style = (style.style(BOLD) ? Font::BOLD : 0) | + (style.style(ITALIC) ? Font::ITALIC : 0); + renderer.SetFontFamilyWithStyle(family_name, font_style); + renderer.DrawPosText(&pos[style_start_glyph_index], + &glyphs[style_start_glyph_index], + glyph_index - style_start_glyph_index); + if (style.style(UNDERLINE)) SetPangoUnderlineMetrics(desc.get(), &renderer); - renderer.DrawDecorations(start_x, y, glyph_x - start_x, styles[style]); - - start = i; - start_x = glyph_x; - // Loop to find the next style, in case the glyph spans multiple styles. - do { - style += style_increment; - } while (style >= 0 && style < static_cast<int>(styles.size()) && - !IndexInRange(style_ranges_utf8[style], glyph_byte_index)); + renderer.DrawDecorations(style_start_x, y, x - style_start_x, + style.style(UNDERLINE), style.style(STRIKE), + style.style(DIAGONAL_STRIKE)); + style.UpdatePosition(glyph_text_index); + style_range = style.GetRange(); + style_start_glyph_index = glyph_index; + style_start_x = x; } - - glyph_x += pango_units_to_double(glyph.geometry.width); - } - - // Draw the remaining glyphs. - renderer.SetForegroundColor(styles[style].foreground); - renderer.SetFontFamilyWithStyle(family_name, styles[style].font_style); - renderer.DrawPosText(&pos[start], &glyphs[start], glyph_count - start); - if (styles[style].underline) - SetPangoUnderlineMetrics(desc.get(), &renderer); - renderer.DrawDecorations(start_x, y, glyph_x - start_x, styles[style]); - x = glyph_x; + } while (glyph_index < glyph_count); } + + // Undo the temporarily applied composition underlines and selection colors. + UndoCompositionAndSelectionStyles(); } GSList* RenderTextLinux::GetRunContainingCaret( @@ -538,6 +519,12 @@ std::vector<Rect> RenderTextLinux::GetSelectionBounds() { return selection_visual_bounds_; } +size_t RenderTextLinux::GetGlyphTextIndex(PangoLayoutRun* run, + int glyph_index) const { + return LayoutIndexToTextIndex(run->item->offset + + run->glyphs->log_clusters[glyph_index]); +} + RenderText* RenderText::CreateInstance() { return new RenderTextLinux; } diff --git a/ui/gfx/render_text_linux.h b/ui/gfx/render_text_linux.h index 12f61ba..2abdf2b 100644 --- a/ui/gfx/render_text_linux.h +++ b/ui/gfx/render_text_linux.h @@ -45,6 +45,9 @@ class RenderTextLinux : public RenderText { virtual void DrawVisualText(Canvas* canvas) OVERRIDE; private: + friend class RenderTextTest; + FRIEND_TEST_ALL_PREFIXES(RenderTextTest, PangoAttributes); + // Returns the run that contains the character attached to the caret in the // given selection model. Return NULL if not found. GSList* GetRunContainingCaret(const SelectionModel& caret) const; @@ -71,6 +74,9 @@ class RenderTextLinux : public RenderText { // Get the visual bounds of the logical selection. std::vector<Rect> GetSelectionBounds(); + // Get the text index corresponding to the |run|'s |glyph_index|. + size_t GetGlyphTextIndex(PangoLayoutRun* run, int glyph_index) const; + // Pango Layout. PangoLayout* layout_; // A single line layout resulting from laying out via |layout_|. diff --git a/ui/gfx/render_text_mac.cc b/ui/gfx/render_text_mac.cc index a636714..5eafdb0 100644 --- a/ui/gfx/render_text_mac.cc +++ b/ui/gfx/render_text_mac.cc @@ -158,7 +158,7 @@ void RenderTextMac::DrawVisualText(Canvas* canvas) { renderer.DrawPosText(&run.glyph_positions[0], &run.glyphs[0], run.glyphs.size()); renderer.DrawDecorations(run.origin.x(), run.origin.y(), run.width, - run.style); + run.underline, run.strike, run.diagonal_strike); } } @@ -168,7 +168,10 @@ RenderTextMac::TextRun::TextRun() width(0), font_style(Font::NORMAL), text_size(0), - foreground(SK_ColorBLACK) { + foreground(SK_ColorBLACK), + underline(false), + strike(false), + diagonal_strike(false) { } RenderTextMac::TextRun::~TextRun() { @@ -176,44 +179,40 @@ RenderTextMac::TextRun::~TextRun() { void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string, CTFontRef font) { - // Clear attributes and reserve space to hold the maximum number of entries, - // which is at most three per style range per the code below. - attributes_.reset(CFArrayCreateMutable(NULL, 3 * style_ranges().size(), - &kCFTypeArrayCallBacks)); + // Temporarily apply composition underlines and selection colors. + ApplyCompositionAndSelectionStyles(); - // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html - for (size_t i = 0; i < style_ranges().size(); ++i) { - const StyleRange& style = style_ranges()[i]; - const CFRange range = CFRangeMake(style.range.start(), - style.range.length()); - - // Note: CFAttributedStringSetAttribute() does not appear to retain the - // values passed in, as can be verified via CFGetRetainCount(). To ensure - // the attribute objects do not leak, they are saved to |attributes_|. + // Note: CFAttributedStringSetAttribute() does not appear to retain the values + // passed in, as can be verified via CFGetRetainCount(). To ensure the + // attribute objects do not leak, they are saved to |attributes_|. + // Clear the attributes storage. + attributes_.reset(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); + // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html + internal::StyleIterator style(colors(), styles()); + const size_t layout_text_length = GetLayoutText().length(); + for (size_t i = 0, end = 0; i < layout_text_length; i = end) { + end = TextIndexToLayoutIndex(style.GetRange().end()); + const CFRange range = CFRangeMake(i, end - i); base::mac::ScopedCFTypeRef<CGColorRef> foreground( - gfx::CGColorCreateFromSkColor(style.foreground)); + gfx::CGColorCreateFromSkColor(style.color())); CFAttributedStringSetAttribute(attr_string, range, - kCTForegroundColorAttributeName, - foreground); + kCTForegroundColorAttributeName, foreground); CFArrayAppendValue(attributes_, foreground); - if (style.underline) { + if (style.style(UNDERLINE)) { CTUnderlineStyle value = kCTUnderlineStyleSingle; - base::mac::ScopedCFTypeRef<CFNumberRef> underline( + base::mac::ScopedCFTypeRef<CFNumberRef> underline_value( CFNumberCreate(NULL, kCFNumberSInt32Type, &value)); CFAttributedStringSetAttribute(attr_string, range, kCTUnderlineStyleAttributeName, - underline); - CFArrayAppendValue(attributes_, underline); + underline_value); + CFArrayAppendValue(attributes_, underline_value); } - if (style.font_style & (Font::BOLD | Font::ITALIC)) { - int traits = 0; - if (style.font_style & Font::BOLD) - traits |= kCTFontBoldTrait; - if (style.font_style & Font::ITALIC) - traits |= kCTFontItalicTrait; + const int traits = (style.style(BOLD) ? kCTFontBoldTrait : 0) | + (style.style(ITALIC) ? kCTFontItalicTrait : 0); + if (traits != 0) { base::mac::ScopedCFTypeRef<CTFontRef> styled_font( CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits)); // TODO(asvitkine): Handle |styled_font| == NULL case better. @@ -223,7 +222,12 @@ void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string, CFArrayAppendValue(attributes_, styled_font); } } + + style.UpdatePosition(LayoutIndexToTextIndex(end)); } + + // Undo the temporarily applied composition underlines and selection colors. + UndoCompositionAndSelectionStyles(); } void RenderTextMac::ComputeRuns() { @@ -283,7 +287,7 @@ void RenderTextMac::ComputeRuns() { } // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle - // this better. + // this better. Also, support strike and diagonal_strike. CFDictionaryRef attributes = CTRunGetAttributes(ct_run); CTFontRef ct_font = base::mac::GetValueFromDictionary<CTFontRef>(attributes, @@ -310,7 +314,7 @@ void RenderTextMac::ComputeRuns() { attributes, kCTUnderlineStyleAttributeName); CTUnderlineStyle value = kCTUnderlineStyleNone; if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value)) - run->style.underline = (value == kCTUnderlineStyleSingle); + run->underline = (value == kCTUnderlineStyleSingle); run_origin.offset(run_width, 0); } diff --git a/ui/gfx/render_text_mac.h b/ui/gfx/render_text_mac.h index 3bbfae4..ba73945 100644 --- a/ui/gfx/render_text_mac.h +++ b/ui/gfx/render_text_mac.h @@ -61,7 +61,9 @@ class RenderTextMac : public RenderText { int font_style; SkScalar text_size; SkColor foreground; - StyleRange style; + bool underline; + bool strike; + bool diagonal_strike; TextRun(); ~TextRun(); diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc index e27135e..3935f7f 100644 --- a/ui/gfx/render_text_unittest.cc +++ b/ui/gfx/render_text_unittest.cc @@ -7,12 +7,16 @@ #include "base/memory/scoped_ptr.h" #include "base/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" -#include "ui/gfx/text_constants.h" +#include "ui/gfx/break_list.h" #if defined(OS_WIN) #include "base/win/windows_version.h" #endif +#if defined(OS_LINUX) +#include "ui/gfx/render_text_linux.h" +#endif + #if defined(TOOLKIT_GTK) #include <gtk/gtk.h> #endif @@ -56,267 +60,146 @@ class RenderTextTest : public testing::Test { }; TEST_F(RenderTextTest, DefaultStyle) { - // Defaults to empty text with no styles. + // Check the default styles applied to new instances and adjusted text. scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); EXPECT_TRUE(render_text->text().empty()); - EXPECT_TRUE(render_text->style_ranges().empty()); - - // Test that the built-in default style is applied for new text. - render_text->SetText(ASCIIToUTF16("abc")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - StyleRange style; - EXPECT_EQ(style.foreground, render_text->style_ranges()[0].foreground); - EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range); - EXPECT_EQ(style.strike, render_text->style_ranges()[0].strike); - EXPECT_EQ(style.underline, render_text->style_ranges()[0].underline); - - // Test that clearing the text also clears the styles. - render_text->SetText(string16()); - EXPECT_TRUE(render_text->text().empty()); - EXPECT_TRUE(render_text->style_ranges().empty()); + const wchar_t* const cases[] = { kWeak, kLtr, L"Hello", kRtl, L"", L"" }; + for (size_t i = 0; i < arraysize(cases); ++i) { + EXPECT_TRUE(render_text->colors().EqualsValueForTesting(SK_ColorBLACK)); + for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) + EXPECT_TRUE(render_text->styles()[style].EqualsValueForTesting(false)); + render_text->SetText(WideToUTF16(cases[i])); + } } -TEST_F(RenderTextTest, CustomDefaultStyle) { - // Test a custom default style. +TEST_F(RenderTextTest, SetColorAndStyle) { + // Ensure custom default styles persist across setting and clearing text. scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); - StyleRange color; - color.foreground = SK_ColorRED; - render_text->set_default_style(color); - render_text->SetText(ASCIIToUTF16("abc")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(color.foreground, render_text->style_ranges()[0].foreground); - - // Test that the custom default style persists across clearing text. - render_text->SetText(string16()); - EXPECT_TRUE(render_text->style_ranges().empty()); - render_text->SetText(ASCIIToUTF16("abc")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(color.foreground, render_text->style_ranges()[0].foreground); - - // Test ApplyDefaultStyle after setting a new default. - StyleRange strike; - strike.strike = true; - render_text->set_default_style(strike); - render_text->ApplyDefaultStyle(); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_TRUE(render_text->style_ranges()[0].strike); - EXPECT_EQ(strike.foreground, render_text->style_ranges()[0].foreground); + const SkColor color = SK_ColorRED; + render_text->SetColor(color); + render_text->SetStyle(BOLD, true); + render_text->SetStyle(UNDERLINE, false); + const wchar_t* const cases[] = { kWeak, kLtr, L"Hello", kRtl, L"", L"" }; + for (size_t i = 0; i < arraysize(cases); ++i) { + EXPECT_TRUE(render_text->colors().EqualsValueForTesting(color)); + EXPECT_TRUE(render_text->styles()[BOLD].EqualsValueForTesting(true)); + EXPECT_TRUE(render_text->styles()[UNDERLINE].EqualsValueForTesting(false)); + render_text->SetText(WideToUTF16(cases[i])); + + // Ensure custom default styles can be applied after text has been set. + if (i == 1) + render_text->SetStyle(STRIKE, true); + if (i >= 1) + EXPECT_TRUE(render_text->styles()[STRIKE].EqualsValueForTesting(true)); + } } -TEST_F(RenderTextTest, ApplyStyleRange) { +TEST_F(RenderTextTest, ApplyColorAndStyle) { scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); + render_text->SetText(ASCIIToUTF16("012345678")); + + // Apply a ranged color and style and check the resulting breaks. + render_text->ApplyColor(SK_ColorRED, ui::Range(1, 4)); + render_text->ApplyStyle(BOLD, true, ui::Range(2, 5)); + std::vector<std::pair<size_t, SkColor> > expected_color; + expected_color.push_back(std::pair<size_t, SkColor>(0, SK_ColorBLACK)); + expected_color.push_back(std::pair<size_t, SkColor>(1, SK_ColorRED)); + expected_color.push_back(std::pair<size_t, SkColor>(4, SK_ColorBLACK)); + EXPECT_TRUE(render_text->colors().EqualsForTesting(expected_color)); + std::vector<std::pair<size_t, bool> > expected_style; + expected_style.push_back(std::pair<size_t, bool>(0, false)); + expected_style.push_back(std::pair<size_t, bool>(2, true)); + expected_style.push_back(std::pair<size_t, bool>(5, false)); + EXPECT_TRUE(render_text->styles()[BOLD].EqualsForTesting(expected_style)); + + // Ensure setting a color and style overrides the ranged colors and styles. + render_text->SetColor(SK_ColorBLUE); + EXPECT_TRUE(render_text->colors().EqualsValueForTesting(SK_ColorBLUE)); + render_text->SetStyle(BOLD, false); + EXPECT_TRUE(render_text->styles()[BOLD].EqualsValueForTesting(false)); + + // Apply a color and style over the text end and check the resulting breaks. + // (INT_MAX should be used instead of the text length for the range end) + const size_t text_length = render_text->text().length(); + render_text->ApplyColor(SK_ColorRED, ui::Range(0, text_length)); + render_text->ApplyStyle(BOLD, true, ui::Range(2, text_length)); + std::vector<std::pair<size_t, SkColor> > expected_color_end; + expected_color_end.push_back(std::pair<size_t, SkColor>(0, SK_ColorRED)); + EXPECT_TRUE(render_text->colors().EqualsForTesting(expected_color_end)); + std::vector<std::pair<size_t, bool> > expected_style_end; + expected_style_end.push_back(std::pair<size_t, bool>(0, false)); + expected_style_end.push_back(std::pair<size_t, bool>(2, true)); + EXPECT_TRUE(render_text->styles()[BOLD].EqualsForTesting(expected_style_end)); + + // Ensure ranged values adjust to accommodate text length changes. + render_text->ApplyStyle(ITALIC, true, ui::Range(0, 2)); + render_text->ApplyStyle(ITALIC, true, ui::Range(3, 6)); + render_text->ApplyStyle(ITALIC, true, ui::Range(7, text_length)); + std::vector<std::pair<size_t, bool> > expected_italic; + expected_italic.push_back(std::pair<size_t, bool>(0, true)); + expected_italic.push_back(std::pair<size_t, bool>(2, false)); + expected_italic.push_back(std::pair<size_t, bool>(3, true)); + expected_italic.push_back(std::pair<size_t, bool>(6, false)); + expected_italic.push_back(std::pair<size_t, bool>(7, true)); + EXPECT_TRUE(render_text->styles()[ITALIC].EqualsForTesting(expected_italic)); + + // Truncating the text should trim any corresponding breaks. + render_text->SetText(ASCIIToUTF16("0123456")); + expected_italic.resize(4); + EXPECT_TRUE(render_text->styles()[ITALIC].EqualsForTesting(expected_italic)); render_text->SetText(ASCIIToUTF16("01234")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - - // Test ApplyStyleRange (no-op on empty range). - StyleRange empty; - empty.range = ui::Range(1, 1); - render_text->ApplyStyleRange(empty); - EXPECT_EQ(1U, render_text->style_ranges().size()); - - // Test ApplyStyleRange (no-op on invalid range). - StyleRange invalid; - invalid.range = ui::Range::InvalidRange(); - render_text->ApplyStyleRange(invalid); - EXPECT_EQ(1U, render_text->style_ranges().size()); - - // Apply a style with a range contained by an existing range. - StyleRange underline; - underline.underline = true; - underline.range = ui::Range(2, 3); - render_text->ApplyStyleRange(underline); - EXPECT_EQ(3U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range); - EXPECT_FALSE(render_text->style_ranges()[0].underline); - EXPECT_EQ(ui::Range(2, 3), render_text->style_ranges()[1].range); - EXPECT_TRUE(render_text->style_ranges()[1].underline); - EXPECT_EQ(ui::Range(3, 5), render_text->style_ranges()[2].range); - EXPECT_FALSE(render_text->style_ranges()[2].underline); - - // Apply a style with a range equal to another range. - StyleRange color; - color.foreground = SK_ColorWHITE; - color.range = ui::Range(2, 3); - render_text->ApplyStyleRange(color); - EXPECT_EQ(3U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range); - EXPECT_NE(SK_ColorWHITE, render_text->style_ranges()[0].foreground); - EXPECT_FALSE(render_text->style_ranges()[0].underline); - EXPECT_EQ(ui::Range(2, 3), render_text->style_ranges()[1].range); - EXPECT_EQ(SK_ColorWHITE, render_text->style_ranges()[1].foreground); - EXPECT_FALSE(render_text->style_ranges()[1].underline); - EXPECT_EQ(ui::Range(3, 5), render_text->style_ranges()[2].range); - EXPECT_NE(SK_ColorWHITE, render_text->style_ranges()[2].foreground); - EXPECT_FALSE(render_text->style_ranges()[2].underline); - - // Apply a style with a range containing an existing range. - // This new style also overlaps portions of neighboring ranges. - StyleRange strike; - strike.strike = true; - strike.range = ui::Range(1, 4); - render_text->ApplyStyleRange(strike); - EXPECT_EQ(3U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 1), render_text->style_ranges()[0].range); - EXPECT_FALSE(render_text->style_ranges()[0].strike); - EXPECT_EQ(ui::Range(1, 4), render_text->style_ranges()[1].range); - EXPECT_TRUE(render_text->style_ranges()[1].strike); - EXPECT_EQ(ui::Range(4, 5), render_text->style_ranges()[2].range); - EXPECT_FALSE(render_text->style_ranges()[2].strike); - - // Apply a style overlapping all ranges. - StyleRange strike_underline; - strike_underline.strike = true; - strike_underline.underline = true; - strike_underline.range = ui::Range(0, render_text->text().length()); - render_text->ApplyStyleRange(strike_underline); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 5), render_text->style_ranges()[0].range); - EXPECT_TRUE(render_text->style_ranges()[0].underline); - EXPECT_TRUE(render_text->style_ranges()[0].strike); - - // Apply the default style. - render_text->ApplyDefaultStyle(); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 5), render_text->style_ranges()[0].range); - EXPECT_FALSE(render_text->style_ranges()[0].underline); - EXPECT_FALSE(render_text->style_ranges()[0].strike); - - // Apply new style range that contains the 2nd last old style range. - render_text->SetText(ASCIIToUTF16("abcdefghi")); - underline.range = ui::Range(0, 3); - render_text->ApplyStyleRange(underline); - color.range = ui::Range(3, 6); - render_text->ApplyStyleRange(color); - strike.range = ui::Range(6, 9); - render_text->ApplyStyleRange(strike); - EXPECT_EQ(3U, render_text->style_ranges().size()); - - color.foreground = SK_ColorRED; - color.range = ui::Range(2, 8); - render_text->ApplyStyleRange(color); - EXPECT_EQ(3U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range); - EXPECT_TRUE(render_text->style_ranges()[0].underline); - EXPECT_EQ(ui::Range(2, 8), render_text->style_ranges()[1].range); - EXPECT_EQ(SK_ColorRED, render_text->style_ranges()[1].foreground); - EXPECT_EQ(ui::Range(8, 9), render_text->style_ranges()[2].range); - EXPECT_TRUE(render_text->style_ranges()[2].strike); - - // Apply new style range that contains multiple old style ranges. - render_text->SetText(ASCIIToUTF16("abcdefghiopq")); - underline.range = ui::Range(0, 3); - render_text->ApplyStyleRange(underline); - color.range = ui::Range(3, 6); - render_text->ApplyStyleRange(color); - strike.range = ui::Range(6, 9); - render_text->ApplyStyleRange(strike); - strike_underline.range = ui::Range(9, 12); - render_text->ApplyStyleRange(strike_underline); - EXPECT_EQ(4U, render_text->style_ranges().size()); - - color.foreground = SK_ColorRED; - color.range = ui::Range(2, 10); - render_text->ApplyStyleRange(color); - EXPECT_EQ(3U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range); - EXPECT_TRUE(render_text->style_ranges()[0].underline); - EXPECT_EQ(ui::Range(2, 10), render_text->style_ranges()[1].range); - EXPECT_EQ(SK_ColorRED, render_text->style_ranges()[1].foreground); - EXPECT_EQ(ui::Range(10, 12), render_text->style_ranges()[2].range); - EXPECT_TRUE(render_text->style_ranges()[2].underline); - EXPECT_TRUE(render_text->style_ranges()[2].strike); -} - -static void SetTextWith2ExtraStyles(RenderText* render_text) { - render_text->SetText(ASCIIToUTF16("abcdefghi")); + expected_italic.resize(3); + EXPECT_TRUE(render_text->styles()[ITALIC].EqualsForTesting(expected_italic)); - StyleRange strike; - strike.strike = true; - strike.range = ui::Range(0, 3); - render_text->ApplyStyleRange(strike); - - StyleRange underline; - underline.underline = true; - underline.range = ui::Range(3, 6); - render_text->ApplyStyleRange(underline); + // Appending text should extend the terminal styles without changing breaks. + render_text->SetText(ASCIIToUTF16("012345678")); + EXPECT_TRUE(render_text->styles()[ITALIC].EqualsForTesting(expected_italic)); } -TEST_F(RenderTextTest, StyleRangesAdjust) { - // Test that style ranges adjust to the text size. +#if defined(OS_LINUX) +TEST_F(RenderTextTest, PangoAttributes) { scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); - render_text->SetText(ASCIIToUTF16("abcdef")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 6), render_text->style_ranges()[0].range); + render_text->SetText(ASCIIToUTF16("012345678")); - // Test that the range is clipped to the length of shorter text. - render_text->SetText(ASCIIToUTF16("abc")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range); - - // Test that the last range extends to the length of longer text. - StyleRange strike; - strike.strike = true; - strike.range = ui::Range(2, 3); - render_text->ApplyStyleRange(strike); - render_text->SetText(ASCIIToUTF16("abcdefghi")); - EXPECT_EQ(2U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range); - EXPECT_EQ(ui::Range(2, 9), render_text->style_ranges()[1].range); - EXPECT_TRUE(render_text->style_ranges()[1].strike); - - // Test that ranges are removed if they're outside the range of shorter text. - render_text->SetText(ASCIIToUTF16("ab")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 2), render_text->style_ranges()[0].range); - EXPECT_FALSE(render_text->style_ranges()[0].strike); - - // Test that previously removed ranges don't return. - render_text->SetText(ASCIIToUTF16("abcdef")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 6), render_text->style_ranges()[0].range); - EXPECT_FALSE(render_text->style_ranges()[0].strike); - - // Test that ranges are removed correctly if they are outside the range of - // shorter text. - SetTextWith2ExtraStyles(render_text.get()); - EXPECT_EQ(3U, render_text->style_ranges().size()); - - render_text->SetText(ASCIIToUTF16("abcdefg")); - EXPECT_EQ(3U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range); - EXPECT_EQ(ui::Range(3, 6), render_text->style_ranges()[1].range); - EXPECT_EQ(ui::Range(6, 7), render_text->style_ranges()[2].range); + // Apply ranged BOLD/ITALIC styles and check the resulting Pango attributes. + render_text->ApplyStyle(BOLD, true, ui::Range(2, 4)); + render_text->ApplyStyle(ITALIC, true, ui::Range(1, 3)); - SetTextWith2ExtraStyles(render_text.get()); - EXPECT_EQ(3U, render_text->style_ranges().size()); - - render_text->SetText(ASCIIToUTF16("abcdef")); - EXPECT_EQ(2U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range); - EXPECT_EQ(ui::Range(3, 6), render_text->style_ranges()[1].range); - - SetTextWith2ExtraStyles(render_text.get()); - EXPECT_EQ(3U, render_text->style_ranges().size()); - - render_text->SetText(ASCIIToUTF16("abcde")); - EXPECT_EQ(2U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range); - EXPECT_EQ(ui::Range(3, 5), render_text->style_ranges()[1].range); - - SetTextWith2ExtraStyles(render_text.get()); - EXPECT_EQ(3U, render_text->style_ranges().size()); - - render_text->SetText(ASCIIToUTF16("abc")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range); - - SetTextWith2ExtraStyles(render_text.get()); - EXPECT_EQ(3U, render_text->style_ranges().size()); + struct { + int start; + int end; + bool bold; + bool italic; + } cases[] = { + { 0, 1, false, false }, + { 1, 2, false, true }, + { 2, 3, true, true }, + { 3, 4, true, false }, + { 4, INT_MAX, false, false }, + }; - render_text->SetText(ASCIIToUTF16("a")); - EXPECT_EQ(1U, render_text->style_ranges().size()); - EXPECT_EQ(ui::Range(0, 1), render_text->style_ranges()[0].range); + int start = 0, end = 0; + RenderTextLinux* rt_linux = static_cast<RenderTextLinux*>(render_text.get()); + rt_linux->EnsureLayout(); + PangoAttrList* attributes = pango_layout_get_attributes(rt_linux->layout_); + PangoAttrIterator* iter = pango_attr_list_get_iterator(attributes); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + pango_attr_iterator_range(iter, &start, &end); + EXPECT_EQ(cases[i].start, start); + EXPECT_EQ(cases[i].end, end); + PangoFontDescription* font = pango_font_description_new(); + pango_attr_iterator_get_font(iter, font, NULL, NULL); + const string16 desc = ASCIIToUTF16(pango_font_description_to_string(font)); + const bool bold = desc.find(ASCIIToUTF16("Bold")) != std::string::npos; + EXPECT_EQ(cases[i].bold, bold); + const bool italic = desc.find(ASCIIToUTF16("Italic")) != std::string::npos; + EXPECT_EQ(cases[i].italic, italic); + pango_attr_iterator_next(iter); + pango_font_description_free(font); + } + EXPECT_FALSE(pango_attr_iterator_next(iter)); } +#endif // TODO(asvitkine): Cursor movements tests disabled on Mac because RenderTextMac // does not implement this yet. http://crbug.com/131618 @@ -1051,33 +934,23 @@ TEST_F(RenderTextTest, StringSizeBoldWidth) { EXPECT_GT(plain_width, 0); // Apply a bold style and check that the new width is greater. - StyleRange bold; - bold.font_style |= Font::BOLD; - render_text->set_default_style(bold); - render_text->ApplyDefaultStyle(); - + render_text->SetStyle(gfx::BOLD, true); const int bold_width = render_text->GetStringSize().width(); EXPECT_GT(bold_width, plain_width); // Now, apply a plain style over the first word only. - StyleRange plain; - plain.font_style = Font::NORMAL; - plain.range = ui::Range(0, 5); - render_text->ApplyStyleRange(plain); - + render_text->ApplyStyle(gfx::BOLD, false, ui::Range(0, 5)); const int plain_bold_width = render_text->GetStringSize().width(); EXPECT_GT(plain_bold_width, plain_width); EXPECT_LT(plain_bold_width, bold_width); } TEST_F(RenderTextTest, StringSizeHeight) { - struct { - string16 text; - } cases[] = { - { WideToUTF16(L"Hello World!") }, // English - { WideToUTF16(L"\x6328\x62f6") }, // Japanese - { WideToUTF16(L"\x0915\x093f") }, // Hindi - { WideToUTF16(L"\x05e0\x05b8") }, // Hebrew + string16 cases[] = { + WideToUTF16(L"Hello World!"), // English + WideToUTF16(L"\x6328\x62f6"), // Japanese + WideToUTF16(L"\x0915\x093f"), // Hindi + WideToUTF16(L"\x05e0\x05b8"), // Hebrew }; Font default_font; @@ -1087,7 +960,7 @@ TEST_F(RenderTextTest, StringSizeHeight) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) { scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); render_text->SetFont(default_font); - render_text->SetText(cases[i].text); + render_text->SetText(cases[i]); const int height1 = render_text->GetStringSize().height(); EXPECT_GT(height1, 0); diff --git a/ui/gfx/render_text_win.cc b/ui/gfx/render_text_win.cc index d17baee..89a0f87 100644 --- a/ui/gfx/render_text_win.cc +++ b/ui/gfx/render_text_win.cc @@ -111,7 +111,7 @@ void DeriveFontIfNecessary(int font_size, const int current_style = (font->GetStyle() & kStyleMask); const int current_size = font->GetFontSize(); if (current_style != target_style || current_size != font_size) - *font = font->DeriveFont(font_size - current_size, font_style); + *font = font->DeriveFont(font_size - current_size, target_style); } // Returns true if |c| is a Unicode BiDi control character. @@ -334,11 +334,9 @@ SelectionModel RenderTextWin::AdjacentWordSelectionModel( void RenderTextWin::SetSelectionModel(const SelectionModel& model) { RenderText::SetSelectionModel(model); - // TODO(xji): The styles are applied to text inside ItemizeLogicalText(). So, - // we need to update layout here in order for the styles, such as selection - // foreground, to be picked up. Eventually, we should separate styles from - // layout by applying foreground, strike, and underline styles during - // DrawVisualText as what RenderTextLinux does. + // TODO(xji|msw): The text selection color is applied in ItemizeLogicalText(). + // So, the layout must be updated in order to draw the proper selection range. + // Colors should be applied in DrawVisualText(), as done by RenderTextLinux. ResetLayout(); } @@ -487,15 +485,8 @@ void RenderTextWin::DrawVisualText(Canvas* canvas) { renderer.SetFontFamilyWithStyle(run->font.GetFontName(), run->font_style); renderer.SetForegroundColor(run->foreground); renderer.DrawPosText(&pos[0], run->glyphs.get(), run->glyph_count); - // TODO(oshima|msw): Consider refactoring StyleRange into Style - // class and StyleRange containing Style, and use Style class in - // TextRun class. This may conflict with msw's comment in - // TextRun, so please consult with msw when refactoring. - StyleRange style; - style.strike = run->strike; - style.diagonal_strike = run->diagonal_strike; - style.underline = run->underline; - renderer.DrawDecorations(x, y, run->width, style); + renderer.DrawDecorations(x, y, run->width, run->underline, run->strike, + run->diagonal_strike); x = glyph_x; } @@ -516,7 +507,7 @@ void RenderTextWin::ItemizeLogicalText() { HRESULT hr = E_OUTOFMEMORY; int script_items_count = 0; std::vector<SCRIPT_ITEM> script_items; - const int text_length = GetLayoutText().length(); + const size_t text_length = GetLayoutText().length(); for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { // Derive the array of Uniscribe script items from the logical text. // ScriptItemize always adds a terminal array item so that the length of the @@ -535,36 +526,41 @@ void RenderTextWin::ItemizeLogicalText() { if (script_items_count <= 0) return; - // Build the list of runs, merge font/underline styles. - // TODO(msw): Only break for font changes, not color etc. See TextRun comment. - StyleRanges styles(style_ranges()); - ApplyCompositionAndSelectionStyles(&styles); - StyleRanges::const_iterator style = styles.begin(); + // Temporarily apply composition underlines and selection colors. + ApplyCompositionAndSelectionStyles(); + + // Build the list of runs from the script items and ranged colors/styles. + // TODO(msw): Only break for bold/italic, not color etc. See TextRun comment. + internal::StyleIterator style(colors(), styles()); SCRIPT_ITEM* script_item = &script_items[0]; - for (int run_break = 0; run_break < text_length;) { + const size_t layout_text_length = GetLayoutText().length(); + for (size_t run_break = 0; run_break < layout_text_length;) { internal::TextRun* run = new internal::TextRun(); run->range.set_start(run_break); run->font = GetFont(); - run->font_style = style->font_style; + run->font_style = (style.style(BOLD) ? Font::BOLD : 0) | + (style.style(ITALIC) ? Font::ITALIC : 0); DeriveFontIfNecessary(run->font.GetFontSize(), run->font.GetHeight(), run->font_style, &run->font); - run->foreground = style->foreground; - run->strike = style->strike; - run->diagonal_strike = style->diagonal_strike; - run->underline = style->underline; + run->foreground = style.color(); + run->strike = style.style(STRIKE); + run->diagonal_strike = style.style(DIAGONAL_STRIKE); + run->underline = style.style(UNDERLINE); run->script_analysis = script_item->a; - // Find the range end and advance the structures as needed. - const int script_item_end = (script_item + 1)->iCharPos; - const int style_range_end = TextIndexToLayoutIndex(style->range.end()); - run_break = std::min(script_item_end, style_range_end); - if (script_item_end <= style_range_end) + // Find the next break and advance the iterators as needed. + const size_t script_item_break = (script_item + 1)->iCharPos; + run_break = std::min(script_item_break, + TextIndexToLayoutIndex(style.GetRange().end())); + style.UpdatePosition(LayoutIndexToTextIndex(run_break)); + if (script_item_break == run_break) script_item++; - if (script_item_end >= style_range_end) - style++; run->range.set_end(run_break); runs_.push_back(run); } + + // Undo the temporarily applied composition underlines and selection colors. + UndoCompositionAndSelectionStyles(); } void RenderTextWin::LayoutVisualText() { diff --git a/ui/gfx/render_text_win.h b/ui/gfx/render_text_win.h index 8b42bc9..a419669 100644 --- a/ui/gfx/render_text_win.h +++ b/ui/gfx/render_text_win.h @@ -25,14 +25,14 @@ struct TextRun { ui::Range range; Font font; - // TODO(msw): Disambiguate color, strike, etc. from TextRuns. - // Otherwise, this breaks the glyph shaping process. - // See the example at: http://www.catch22.net/tuts/neatpad/12. - SkColor foreground; // A gfx::Font::FontStyle flag to specify bold and italic styles. // Supersedes |font.GetFontStyle()|. Stored separately to avoid calling // |font.DeriveFont()|, which is expensive on Windows. int font_style; + + // TODO(msw): Disambiguate color/style from TextRuns for proper glyph shaping. + // See an example: http://www.catch22.net/tuts/uniscribe-mysteries + SkColor foreground; bool strike; bool diagonal_strike; bool underline; diff --git a/ui/gfx/text_constants.h b/ui/gfx/text_constants.h index 3c644ab..4ac788e 100644 --- a/ui/gfx/text_constants.h +++ b/ui/gfx/text_constants.h @@ -40,6 +40,17 @@ enum DirectionalityMode { DIRECTIONALITY_FORCE_RTL, }; +// Text styles and adornments. +// TODO(msw): Merge with gfx::Font::FontStyle. +enum TextStyle { + BOLD = 0, + ITALIC, + STRIKE, + DIAGONAL_STRIKE, + UNDERLINE, + NUM_TEXT_STYLES, +}; + } // namespace gfx #endif // UI_GFX_TEXT_CONSTANTS_H_ @@ -332,6 +332,7 @@ 'gfx/android/window_android.h', 'gfx/blit.cc', 'gfx/blit.h', + 'gfx/break_list.h', 'gfx/canvas.cc', 'gfx/canvas.h', 'gfx/canvas_android.cc', diff --git a/ui/ui_unittests.gypi b/ui/ui_unittests.gypi index 617589d..8a5c9f2 100644 --- a/ui/ui_unittests.gypi +++ b/ui/ui_unittests.gypi @@ -124,6 +124,7 @@ 'base/text/utf16_indexing_unittest.cc', 'base/view_prop_unittest.cc', 'gfx/blit_unittest.cc', + 'gfx/break_list_unittest.cc', 'gfx/canvas_unittest.cc', 'gfx/codec/jpeg_codec_unittest.cc', 'gfx/color_analysis_unittest.cc', diff --git a/ui/views/controls/link.cc b/ui/views/controls/link.cc index 04c83ac..94aaa60 100644 --- a/ui/views/controls/link.cc +++ b/ui/views/controls/link.cc @@ -204,14 +204,12 @@ void Link::SetPressed(bool pressed) { } void Link::RecalculateFont() { - // The font should be underlined iff the link is enabled and |underline_| is - // true. - if ((enabled() && underline_) == - !(font().GetStyle() & gfx::Font::UNDERLINED)) { - Label::SetFont(font().DeriveFont(0, enabled() && underline_ ? - (font().GetStyle() | gfx::Font::UNDERLINED) : - (font().GetStyle() & ~gfx::Font::UNDERLINED))); - } + // Underline the link iff it is enabled and |underline_| is true. + const int style = font().GetStyle(); + const int intended_style = (enabled() && underline_) ? + (style | gfx::Font::UNDERLINE) : (style & ~gfx::Font::UNDERLINE); + if (style != intended_style) + Label::SetFont(font().DeriveFont(0, intended_style)); } } // namespace views diff --git a/ui/views/controls/textfield/native_textfield_views.cc b/ui/views/controls/textfield/native_textfield_views.cc index be33761..c8cfb99 100644 --- a/ui/views/controls/textfield/native_textfield_views.cc +++ b/ui/views/controls/textfield/native_textfield_views.cc @@ -441,11 +441,7 @@ void NativeTextfieldViews::UpdateBorderColor() { } void NativeTextfieldViews::UpdateTextColor() { - gfx::StyleRange default_style(GetRenderText()->default_style()); - default_style.foreground = textfield_->GetTextColor(); - GetRenderText()->set_default_style(default_style); - GetRenderText()->ApplyDefaultStyle(); - SchedulePaint(); + SetColor(textfield_->GetTextColor()); } void NativeTextfieldViews::UpdateBackgroundColor() { @@ -698,13 +694,25 @@ void NativeTextfieldViews::ExecuteCommand(int command_id) { OnAfterUserAction(); } -void NativeTextfieldViews::ApplyStyleRange(const gfx::StyleRange& style) { - GetRenderText()->ApplyStyleRange(style); +void NativeTextfieldViews::SetColor(SkColor value) { + GetRenderText()->SetColor(value); + SchedulePaint(); +} + +void NativeTextfieldViews::ApplyColor(SkColor value, const ui::Range& range) { + GetRenderText()->ApplyColor(value, range); + SchedulePaint(); +} + +void NativeTextfieldViews::SetStyle(gfx::TextStyle style, bool value) { + GetRenderText()->SetStyle(style, value); SchedulePaint(); } -void NativeTextfieldViews::ApplyDefaultStyle() { - GetRenderText()->ApplyDefaultStyle(); +void NativeTextfieldViews::ApplyStyle(gfx::TextStyle style, + bool value, + const ui::Range& range) { + GetRenderText()->ApplyStyle(style, value, range); SchedulePaint(); } diff --git a/ui/views/controls/textfield/native_textfield_views.h b/ui/views/controls/textfield/native_textfield_views.h index df878a9..80259d8 100644 --- a/ui/views/controls/textfield/native_textfield_views.h +++ b/ui/views/controls/textfield/native_textfield_views.h @@ -130,8 +130,12 @@ class VIEWS_EXPORT NativeTextfieldViews : public TouchSelectionClientView, virtual void HandleFocus() OVERRIDE; virtual void HandleBlur() OVERRIDE; virtual ui::TextInputClient* GetTextInputClient() OVERRIDE; - virtual void ApplyStyleRange(const gfx::StyleRange& style) OVERRIDE; - virtual void ApplyDefaultStyle() OVERRIDE; + virtual void SetColor(SkColor value) OVERRIDE; + virtual void ApplyColor(SkColor value, const ui::Range& range) OVERRIDE; + virtual void SetStyle(gfx::TextStyle style, bool value) OVERRIDE; + virtual void ApplyStyle(gfx::TextStyle style, + bool value, + const ui::Range& range) OVERRIDE; virtual void ClearEditHistory() OVERRIDE; virtual int GetFontHeight() OVERRIDE; virtual int GetTextfieldBaseline() const OVERRIDE; diff --git a/ui/views/controls/textfield/native_textfield_win.cc b/ui/views/controls/textfield/native_textfield_win.cc index 3655f65..48a3fa1 100644 --- a/ui/views/controls/textfield/native_textfield_win.cc +++ b/ui/views/controls/textfield/native_textfield_win.cc @@ -413,11 +413,21 @@ ui::TextInputClient* NativeTextfieldWin::GetTextInputClient() { return NULL; } -void NativeTextfieldWin::ApplyStyleRange(const gfx::StyleRange& style) { +void NativeTextfieldWin::SetColor(SkColor value) { NOTREACHED(); } -void NativeTextfieldWin::ApplyDefaultStyle() { +void NativeTextfieldWin::ApplyColor(SkColor value, const ui::Range& range) { + NOTREACHED(); +} + +void NativeTextfieldWin::SetStyle(gfx::TextStyle style, bool value) { + NOTREACHED(); +} + +void NativeTextfieldWin::ApplyStyle(gfx::TextStyle style, + bool value, + const ui::Range& range) { NOTREACHED(); } diff --git a/ui/views/controls/textfield/native_textfield_win.h b/ui/views/controls/textfield/native_textfield_win.h index 7e072ae..c4a2161 100644 --- a/ui/views/controls/textfield/native_textfield_win.h +++ b/ui/views/controls/textfield/native_textfield_win.h @@ -101,8 +101,12 @@ class NativeTextfieldWin virtual void HandleFocus() OVERRIDE; virtual void HandleBlur() OVERRIDE; virtual ui::TextInputClient* GetTextInputClient() OVERRIDE; - virtual void ApplyStyleRange(const gfx::StyleRange& style) OVERRIDE; - virtual void ApplyDefaultStyle() OVERRIDE; + virtual void SetColor(SkColor value) OVERRIDE; + virtual void ApplyColor(SkColor value, const ui::Range& range) OVERRIDE; + virtual void SetStyle(gfx::TextStyle style, bool value) OVERRIDE; + virtual void ApplyStyle(gfx::TextStyle style, + bool value, + const ui::Range& range) OVERRIDE; virtual void ClearEditHistory() OVERRIDE; virtual int GetFontHeight() OVERRIDE; virtual int GetTextfieldBaseline() const OVERRIDE; diff --git a/ui/views/controls/textfield/native_textfield_wrapper.h b/ui/views/controls/textfield/native_textfield_wrapper.h index 53e4c6c..69dfa6e 100644 --- a/ui/views/controls/textfield/native_textfield_wrapper.h +++ b/ui/views/controls/textfield/native_textfield_wrapper.h @@ -8,12 +8,12 @@ #include "base/string16.h" #include "base/i18n/rtl.h" #include "ui/gfx/native_widget_types.h" +#include "ui/gfx/text_constants.h" #include "ui/views/views_export.h" namespace gfx { class Insets; class SelectionModel; -struct StyleRange; } // namespace gfx namespace ui { @@ -148,12 +148,15 @@ class VIEWS_EXPORT NativeTextfieldWrapper { // support text input. virtual ui::TextInputClient* GetTextInputClient() = 0; - // Applies the |style| to the text specified by its range. - // See |Textfield::ApplyStyleRange| for detail. - virtual void ApplyStyleRange(const gfx::StyleRange& style) = 0; + // Set the text colors; see the corresponding Textfield functions for details. + virtual void SetColor(SkColor value) = 0; + virtual void ApplyColor(SkColor value, const ui::Range& range) = 0; - // Applies the default style to the textfield. - virtual void ApplyDefaultStyle() = 0; + // Set the text styles; see the corresponding Textfield functions for details. + virtual void SetStyle(gfx::TextStyle style, bool value) = 0; + virtual void ApplyStyle(gfx::TextStyle style, + bool value, + const ui::Range& range) = 0; // Clears Edit history. virtual void ClearEditHistory() = 0; diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc index 191ed35..78304aa 100644 --- a/ui/views/controls/textfield/textfield.cc +++ b/ui/views/controls/textfield/textfield.cc @@ -368,14 +368,26 @@ size_t Textfield::GetCursorPosition() const { return native_wrapper_->GetCursorPosition(); } -void Textfield::ApplyStyleRange(const gfx::StyleRange& style) { +void Textfield::SetColor(SkColor value) { DCHECK(native_wrapper_); - return native_wrapper_->ApplyStyleRange(style); + return native_wrapper_->SetColor(value); } -void Textfield::ApplyDefaultStyle() { +void Textfield::ApplyColor(SkColor value, const ui::Range& range) { DCHECK(native_wrapper_); - native_wrapper_->ApplyDefaultStyle(); + return native_wrapper_->ApplyColor(value, range); +} + +void Textfield::SetStyle(gfx::TextStyle style, bool value) { + DCHECK(native_wrapper_); + return native_wrapper_->SetStyle(style, value); +} + +void Textfield::ApplyStyle(gfx::TextStyle style, + bool value, + const ui::Range& range) { + DCHECK(native_wrapper_); + return native_wrapper_->ApplyStyle(style, value, range); } void Textfield::ClearEditHistory() { diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h index 57509ea..33101b1 100644 --- a/ui/views/controls/textfield/textfield.h +++ b/ui/views/controls/textfield/textfield.h @@ -16,6 +16,7 @@ #include "ui/base/keycodes/keyboard_codes.h" #include "ui/gfx/font.h" #include "ui/gfx/native_widget_types.h" +#include "ui/gfx/text_constants.h" #include "ui/views/controls/textfield/native_textfield_wrapper.h" #include "ui/views/view.h" @@ -23,10 +24,6 @@ #include "base/logging.h" #endif -namespace gfx { -struct StyleRange; -} // namespace gfx - namespace ui { class Range; class TextInputClient; @@ -209,14 +206,18 @@ class VIEWS_EXPORT Textfield : public View { // only and has to be called after the wrapper is created. size_t GetCursorPosition() const; - // Applies |style| to the text specified by its range. The style will be - // ignored if range is empty or invalid. This is views-implementation only and + // Set the text color over the entire text or a logical character range. + // Empty and invalid ranges are ignored. This is views-implementation only and // has to be called after the wrapper is created. - void ApplyStyleRange(const gfx::StyleRange& style); + void SetColor(SkColor value); + void ApplyColor(SkColor value, const ui::Range& range); - // Applies the default style to the textfield. This is views-implementation - // only and has to be called after the wrapper is created. - void ApplyDefaultStyle(); + // Set various text styles over the entire text or a logical character range. + // The respective |style| is applied if |value| is true, or removed if false. + // Empty and invalid ranges are ignored. This is views-implementation only and + // has to be called after the wrapper is created. + void SetStyle(gfx::TextStyle style, bool value); + void ApplyStyle(gfx::TextStyle style, bool value, const ui::Range& range); // Clears Edit history. void ClearEditHistory(); diff --git a/ui/views/examples/text_example.cc b/ui/views/examples/text_example.cc index 5fb6662..6ceed4a 100644 --- a/ui/views/examples/text_example.cc +++ b/ui/views/examples/text_example.cc @@ -256,7 +256,7 @@ void TextExample::ButtonPressed(Button* button, const ui::Event& event) { SetFlagFromCheckbox(break_checkbox_, &flags, gfx::Canvas::CHARACTER_BREAK); SetFlagFromCheckbox(bold_checkbox_, &style, gfx::Font::BOLD); SetFlagFromCheckbox(italic_checkbox_, &style, gfx::Font::ITALIC); - SetFlagFromCheckbox(underline_checkbox_, &style, gfx::Font::UNDERLINED); + SetFlagFromCheckbox(underline_checkbox_, &style, gfx::Font::UNDERLINE); text_view_->set_halo(halo_checkbox_->checked()); text_view_->set_text_flags(flags); text_view_->SetFontStyle(style); diff --git a/ui/views/examples/textfield_example.cc b/ui/views/examples/textfield_example.cc index e06a4d6..bdf03d3 100644 --- a/ui/views/examples/textfield_example.cc +++ b/ui/views/examples/textfield_example.cc @@ -93,24 +93,21 @@ void TextfieldExample::ButtonPressed(Button* sender, const ui::Event& event) { name_->SetText(ASCIIToUTF16("[set]")); } else if (sender == set_style_) { if (!name_->text().empty()) { - gfx::StyleRange color; - color.foreground = SK_ColorYELLOW; - color.range = ui::Range(0, name_->text().length()); - name_->ApplyStyleRange(color); + name_->SetColor(SK_ColorGREEN); + name_->SetStyle(gfx::BOLD, true); if (name_->text().length() >= 5) { size_t fifth = name_->text().length() / 5; - gfx::StyleRange underline; - underline.underline = true; - underline.foreground = SK_ColorBLUE; - underline.range = ui::Range(1 * fifth, 4 * fifth); - name_->ApplyStyleRange(underline); + const ui::Range big_range(1 * fifth, 4 * fifth); + name_->ApplyStyle(gfx::BOLD, false, big_range); + name_->ApplyStyle(gfx::UNDERLINE, true, big_range); + name_->ApplyColor(SK_ColorBLUE, big_range); - gfx::StyleRange strike; - strike.strike = true; - strike.foreground = SK_ColorRED; - strike.range = ui::Range(2 * fifth, 3 * fifth); - name_->ApplyStyleRange(strike); + const ui::Range small_range(2 * fifth, 3 * fifth); + name_->ApplyStyle(gfx::ITALIC, true, small_range); + name_->ApplyStyle(gfx::UNDERLINE, false, small_range); + name_->ApplyStyle(gfx::DIAGONAL_STRIKE, true, small_range); + name_->ApplyColor(SK_ColorRED, small_range); } } } |